Home | History | Annotate | Download | only in actions
      1 # Copyright 2014 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import time
      6 
      7 from telemetry.page.actions.javascript_click import ClickElementAction
      8 from telemetry.page.actions.loop import LoopAction
      9 from telemetry.page.actions.navigate import NavigateAction
     10 from telemetry.page.actions.pinch import PinchAction
     11 from telemetry.page.actions.play import PlayAction
     12 from telemetry.page.actions.repaint_continuously import (
     13     RepaintContinuouslyAction)
     14 from telemetry.page.actions.scroll import ScrollAction
     15 from telemetry.page.actions.scroll_bounce import ScrollBounceAction
     16 from telemetry.page.actions.seek import SeekAction
     17 from telemetry.page.actions.swipe import SwipeAction
     18 from telemetry.page.actions.tap import TapAction
     19 from telemetry.page.actions.wait import WaitForElementAction
     20 from telemetry.web_perf import timeline_interaction_record
     21 
     22 
     23 class ActionRunner(object):
     24 
     25   def __init__(self, tab, skip_waits=False):
     26     self._tab = tab
     27     self._skip_waits = skip_waits
     28 
     29   def _RunAction(self, action):
     30     action.WillRunAction(self._tab)
     31     action.RunAction(self._tab)
     32 
     33   def BeginInteraction(self, label, is_fast=False, is_smooth=False,
     34                        is_responsive=False, repeatable=False):
     35     """Marks the beginning of an interaction record.
     36 
     37     An interaction record is a labeled time period containing
     38     interaction that developers care about. Each set of metrics
     39     specified in flags will be calculated for this time period.. The
     40     End() method in the returned object must be called once to mark
     41     the end of the timeline.
     42 
     43     Args:
     44       label: A label for this particular interaction. This can be any
     45           user-defined string, but must not contain '/'.
     46       is_fast: Whether to measure how fast the browser completes necessary work
     47           for this interaction record. See fast_metric.py for details.
     48       is_smooth: Whether to check for smoothness metrics for this interaction.
     49       is_responsive: Whether to check for responsiveness metrics for
     50           this interaction.
     51       repeatable: Whether other interactions may use the same logical name
     52           as this interaction. All interactions with the same logical name must
     53           have the same flags.
     54     """
     55     flags = []
     56     if is_fast:
     57       flags.append(timeline_interaction_record.IS_FAST)
     58     if is_smooth:
     59       flags.append(timeline_interaction_record.IS_SMOOTH)
     60     if is_responsive:
     61       flags.append(timeline_interaction_record.IS_RESPONSIVE)
     62     if repeatable:
     63       flags.append(timeline_interaction_record.REPEATABLE)
     64 
     65     interaction = Interaction(self._tab, label, flags)
     66     interaction.Begin()
     67     return interaction
     68 
     69   def BeginGestureInteraction(self, label, is_fast=False, is_smooth=False,
     70                               is_responsive=False, repeatable=False):
     71     """Marks the beginning of a gesture-based interaction record.
     72 
     73     This is similar to normal interaction record, but it will
     74     auto-narrow the interaction time period to only include the
     75     synthetic gesture event output by Chrome. This is typically use to
     76     reduce noise in gesture-based analysis (e.g., analysis for a
     77     swipe/scroll).
     78 
     79     The interaction record label will be prepended with 'Gesture_'.
     80 
     81     Args:
     82       label: A label for this particular interaction. This can be any
     83           user-defined string, but must not contain '/'.
     84       is_fast: Whether to measure how fast the browser completes necessary work
     85           for this interaction record. See fast_metric.py for details.
     86       is_smooth: Whether to check for smoothness metrics for this interaction.
     87       is_responsive: Whether to check for responsiveness metrics for
     88           this interaction.
     89       repeatable: Whether other interactions may use the same logical name
     90           as this interaction. All interactions with the same logical name must
     91           have the same flags.
     92     """
     93     return self.BeginInteraction('Gesture_' + label, is_fast, is_smooth,
     94                                  is_responsive, repeatable)
     95 
     96   def NavigateToPage(self, page, timeout_in_seconds=60):
     97     """Navigate to the given page.
     98 
     99     Args:
    100       page: page is an instance of page.Page
    101       timeout_in_seconds: The timeout in seconds (default to 60).
    102     """
    103     if page.is_file:
    104       target_side_url = self._tab.browser.http_server.UrlOf(page.file_path_url)
    105     else:
    106       target_side_url = page.url
    107     self._RunAction(NavigateAction(
    108         url=target_side_url,
    109         script_to_evaluate_on_commit=page.script_to_evaluate_on_commit,
    110         timeout_in_seconds=timeout_in_seconds))
    111 
    112   def WaitForNavigate(self, timeout_in_seconds_seconds=60):
    113     self._tab.WaitForNavigate(timeout_in_seconds_seconds)
    114     self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
    115 
    116   def ReloadPage(self):
    117     """Reloads the page."""
    118     self._tab.ExecuteJavaScript('window.location.reload()')
    119     self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
    120 
    121   def ExecuteJavaScript(self, statement):
    122     """Executes a given JavaScript expression. Does not return the result.
    123 
    124     Example: runner.ExecuteJavaScript('var foo = 1;');
    125 
    126     Args:
    127       statement: The statement to execute (provided as string).
    128 
    129     Raises:
    130       EvaluationException: The statement failed to execute.
    131     """
    132     self._tab.ExecuteJavaScript(statement)
    133 
    134   def EvaluateJavaScript(self, expression):
    135     """Returns the evaluation result of the given JavaScript expression.
    136 
    137     The evaluation results must be convertible to JSON. If the result
    138     is not needed, use ExecuteJavaScript instead.
    139 
    140     Example: num = runner.EvaluateJavaScript('document.location.href')
    141 
    142     Args:
    143       expression: The expression to evaluate (provided as string).
    144 
    145     Raises:
    146       EvaluationException: The statement expression failed to execute
    147           or the evaluation result can not be JSON-ized.
    148     """
    149     return self._tab.EvaluateJavaScript(expression)
    150 
    151   def Wait(self, seconds):
    152     """Wait for the number of seconds specified.
    153 
    154     Args:
    155       seconds: The number of seconds to wait.
    156     """
    157     if not self._skip_waits:
    158       time.sleep(seconds)
    159 
    160   def WaitForJavaScriptCondition(self, condition, timeout_in_seconds=60):
    161     """Wait for a JavaScript condition to become true.
    162 
    163     Example: runner.WaitForJavaScriptCondition('window.foo == 10');
    164 
    165     Args:
    166       condition: The JavaScript condition (as string).
    167       timeout_in_seconds: The timeout in seconds (default to 60).
    168     """
    169     self._tab.WaitForJavaScriptExpression(condition, timeout_in_seconds)
    170 
    171   def WaitForElement(self, selector=None, text=None, element_function=None,
    172                      timeout_in_seconds=60):
    173     """Wait for an element to appear in the document.
    174 
    175     The element may be selected via selector, text, or element_function.
    176     Only one of these arguments must be specified.
    177 
    178     Args:
    179       selector: A CSS selector describing the element.
    180       text: The element must contains this exact text.
    181       element_function: A JavaScript function (as string) that is used
    182           to retrieve the element. For example:
    183           '(function() { return foo.element; })()'.
    184       timeout_in_seconds: The timeout in seconds (default to 60).
    185     """
    186     self._RunAction(WaitForElementAction(
    187         selector=selector, text=text, element_function=element_function,
    188         timeout_in_seconds=timeout_in_seconds))
    189 
    190   def TapElement(self, selector=None, text=None, element_function=None):
    191     """Tap an element.
    192 
    193     The element may be selected via selector, text, or element_function.
    194     Only one of these arguments must be specified.
    195 
    196     Args:
    197       selector: A CSS selector describing the element.
    198       text: The element must contains this exact text.
    199       element_function: A JavaScript function (as string) that is used
    200           to retrieve the element. For example:
    201           '(function() { return foo.element; })()'.
    202     """
    203     self._RunAction(TapAction(
    204         selector=selector, text=text, element_function=element_function))
    205 
    206   def ClickElement(self, selector=None, text=None, element_function=None):
    207     """Click an element.
    208 
    209     The element may be selected via selector, text, or element_function.
    210     Only one of these arguments must be specified.
    211 
    212     Args:
    213       selector: A CSS selector describing the element.
    214       text: The element must contains this exact text.
    215       element_function: A JavaScript function (as string) that is used
    216           to retrieve the element. For example:
    217           '(function() { return foo.element; })()'.
    218     """
    219     self._RunAction(ClickElementAction(
    220         selector=selector, text=text, element_function=element_function))
    221 
    222   def PinchPage(self, left_anchor_ratio=0.5, top_anchor_ratio=0.5,
    223                 scale_factor=None, speed_in_pixels_per_second=800):
    224     """Perform the pinch gesture on the page.
    225 
    226     It computes the pinch gesture automatically based on the anchor
    227     coordinate and the scale factor. The scale factor is the ratio of
    228     of the final span and the initial span of the gesture.
    229 
    230     Args:
    231       left_anchor_ratio: The horizontal pinch anchor coordinate of the
    232           gesture, as a ratio of the visible bounding rectangle for
    233           document.body.
    234       top_anchor_ratio: The vertical pinch anchor coordinate of the
    235           gesture, as a ratio of the visible bounding rectangle for
    236           document.body.
    237       scale_factor: The ratio of the final span to the initial span.
    238           The default scale factor is
    239           3.0 / (window.outerWidth/window.innerWidth).
    240       speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
    241     """
    242     self._RunAction(PinchAction(
    243         left_anchor_ratio=left_anchor_ratio, top_anchor_ratio=top_anchor_ratio,
    244         scale_factor=scale_factor,
    245         speed_in_pixels_per_second=speed_in_pixels_per_second))
    246 
    247   def PinchElement(self, selector=None, text=None, element_function=None,
    248                    left_anchor_ratio=0.5, top_anchor_ratio=0.5,
    249                    scale_factor=None, speed_in_pixels_per_second=800):
    250     """Perform the pinch gesture on an element.
    251 
    252     It computes the pinch gesture automatically based on the anchor
    253     coordinate and the scale factor. The scale factor is the ratio of
    254     of the final span and the initial span of the gesture.
    255 
    256     Args:
    257       selector: A CSS selector describing the element.
    258       text: The element must contains this exact text.
    259       element_function: A JavaScript function (as string) that is used
    260           to retrieve the element. For example:
    261           'function() { return foo.element; }'.
    262       left_anchor_ratio: The horizontal pinch anchor coordinate of the
    263           gesture, as a ratio of the visible bounding rectangle for
    264           the element.
    265       top_anchor_ratio: The vertical pinch anchor coordinate of the
    266           gesture, as a ratio of the visible bounding rectangle for
    267           the element.
    268       scale_factor: The ratio of the final span to the initial span.
    269           The default scale factor is
    270           3.0 / (window.outerWidth/window.innerWidth).
    271       speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
    272     """
    273     self._RunAction(PinchAction(
    274         selector=selector, text=text, element_function=element_function,
    275         left_anchor_ratio=left_anchor_ratio, top_anchor_ratio=top_anchor_ratio,
    276         scale_factor=scale_factor,
    277         speed_in_pixels_per_second=speed_in_pixels_per_second))
    278 
    279   def ScrollPage(self, left_start_ratio=0.5, top_start_ratio=0.5,
    280                  direction='down', distance=None, distance_expr=None,
    281                  speed_in_pixels_per_second=800, use_touch=False):
    282     """Perform scroll gesture on the page.
    283 
    284     You may specify distance or distance_expr, but not both. If
    285     neither is specified, the default scroll distance is variable
    286     depending on direction (see scroll.js for full implementation).
    287 
    288     Args:
    289       left_start_ratio: The horizontal starting coordinate of the
    290           gesture, as a ratio of the visible bounding rectangle for
    291           document.body.
    292       top_start_ratio: The vertical starting coordinate of the
    293           gesture, as a ratio of the visible bounding rectangle for
    294           document.body.
    295       direction: The direction of scroll, either 'left', 'right',
    296           'up', or 'down'
    297       distance: The distance to scroll (in pixel).
    298       distance_expr: A JavaScript expression (as string) that can be
    299           evaluated to compute scroll distance. Example:
    300           'window.scrollTop' or '(function() { return crazyMath(); })()'.
    301       speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
    302       use_touch: Whether scrolling should be done with touch input.
    303     """
    304     self._RunAction(ScrollAction(
    305         left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
    306         direction=direction, distance=distance, distance_expr=distance_expr,
    307         speed_in_pixels_per_second=speed_in_pixels_per_second,
    308         use_touch=use_touch))
    309 
    310   def ScrollElement(self, selector=None, text=None, element_function=None,
    311                     left_start_ratio=0.5, top_start_ratio=0.5,
    312                     direction='down', distance=None, distance_expr=None,
    313                     speed_in_pixels_per_second=800, use_touch=False):
    314     """Perform scroll gesture on the element.
    315 
    316     The element may be selected via selector, text, or element_function.
    317     Only one of these arguments must be specified.
    318 
    319     You may specify distance or distance_expr, but not both. If
    320     neither is specified, the default scroll distance is variable
    321     depending on direction (see scroll.js for full implementation).
    322 
    323     Args:
    324       selector: A CSS selector describing the element.
    325       text: The element must contains this exact text.
    326       element_function: A JavaScript function (as string) that is used
    327           to retrieve the element. For example:
    328           'function() { return foo.element; }'.
    329       left_start_ratio: The horizontal starting coordinate of the
    330           gesture, as a ratio of the visible bounding rectangle for
    331           the element.
    332       top_start_ratio: The vertical starting coordinate of the
    333           gesture, as a ratio of the visible bounding rectangle for
    334           the element.
    335       direction: The direction of scroll, either 'left', 'right',
    336           'up', or 'down'
    337       distance: The distance to scroll (in pixel).
    338       distance_expr: A JavaScript expression (as string) that can be
    339           evaluated to compute scroll distance. Example:
    340           'window.scrollTop' or '(function() { return crazyMath(); })()'.
    341       speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
    342       use_touch: Whether scrolling should be done with touch input.
    343     """
    344     self._RunAction(ScrollAction(
    345         selector=selector, text=text, element_function=element_function,
    346         left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
    347         direction=direction, distance=distance, distance_expr=distance_expr,
    348         speed_in_pixels_per_second=speed_in_pixels_per_second,
    349         use_touch=use_touch))
    350 
    351   def ScrollBouncePage(self, left_start_ratio=0.5, top_start_ratio=0.5,
    352                        direction='down', distance=100,
    353                        overscroll=10, repeat_count=10,
    354                        speed_in_pixels_per_second=400):
    355     """Perform scroll bounce gesture on the page.
    356 
    357     This gesture scrolls the page by the number of pixels specified in
    358     distance, in the given direction, followed by a scroll by
    359     (distance + overscroll) pixels in the opposite direction.
    360     The above gesture is repeated repeat_count times.
    361 
    362     Args:
    363       left_start_ratio: The horizontal starting coordinate of the
    364           gesture, as a ratio of the visible bounding rectangle for
    365           document.body.
    366       top_start_ratio: The vertical starting coordinate of the
    367           gesture, as a ratio of the visible bounding rectangle for
    368           document.body.
    369       direction: The direction of scroll, either 'left', 'right',
    370           'up', or 'down'
    371       distance: The distance to scroll (in pixel).
    372       overscroll: The number of additional pixels to scroll back, in
    373           addition to the givendistance.
    374       repeat_count: How often we want to repeat the full gesture.
    375       speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
    376     """
    377     self._RunAction(ScrollBounceAction(
    378         left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
    379         direction=direction, distance=distance,
    380         overscroll=overscroll, repeat_count=repeat_count,
    381         speed_in_pixels_per_second=speed_in_pixels_per_second))
    382 
    383   def ScrollBounceElement(self, selector=None, text=None, element_function=None,
    384                           left_start_ratio=0.5, top_start_ratio=0.5,
    385                           direction='down', distance=100,
    386                           overscroll=10, repeat_count=10,
    387                           speed_in_pixels_per_second=400):
    388     """Perform scroll bounce gesture on the element.
    389 
    390     This gesture scrolls on the element by the number of pixels specified in
    391     distance, in the given direction, followed by a scroll by
    392     (distance + overscroll) pixels in the opposite direction.
    393     The above gesture is repeated repeat_count times.
    394 
    395     Args:
    396       selector: A CSS selector describing the element.
    397       text: The element must contains this exact text.
    398       element_function: A JavaScript function (as string) that is used
    399           to retrieve the element. For example:
    400           'function() { return foo.element; }'.
    401       left_start_ratio: The horizontal starting coordinate of the
    402           gesture, as a ratio of the visible bounding rectangle for
    403           document.body.
    404       top_start_ratio: The vertical starting coordinate of the
    405           gesture, as a ratio of the visible bounding rectangle for
    406           document.body.
    407       direction: The direction of scroll, either 'left', 'right',
    408           'up', or 'down'
    409       distance: The distance to scroll (in pixel).
    410       overscroll: The number of additional pixels to scroll back, in
    411           addition to the givendistance.
    412       repeat_count: How often we want to repeat the full gesture.
    413       speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
    414     """
    415     self._RunAction(ScrollBounceAction(
    416         selector=selector, text=text, element_function=element_function,
    417         left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
    418         direction=direction, distance=distance,
    419         overscroll=overscroll, repeat_count=repeat_count,
    420         speed_in_pixels_per_second=speed_in_pixels_per_second))
    421 
    422   def SwipePage(self, left_start_ratio=0.5, top_start_ratio=0.5,
    423                 direction='left', distance=100, speed_in_pixels_per_second=800):
    424     """Perform swipe gesture on the page.
    425 
    426     Args:
    427       left_start_ratio: The horizontal starting coordinate of the
    428           gesture, as a ratio of the visible bounding rectangle for
    429           document.body.
    430       top_start_ratio: The vertical starting coordinate of the
    431           gesture, as a ratio of the visible bounding rectangle for
    432           document.body.
    433       direction: The direction of swipe, either 'left', 'right',
    434           'up', or 'down'
    435       distance: The distance to swipe (in pixel).
    436       speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
    437     """
    438     self._RunAction(SwipeAction(
    439         left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
    440         direction=direction, distance=distance,
    441         speed_in_pixels_per_second=speed_in_pixels_per_second))
    442 
    443   def SwipeElement(self, selector=None, text=None, element_function=None,
    444                    left_start_ratio=0.5, top_start_ratio=0.5,
    445                    direction='left', distance=100,
    446                    speed_in_pixels_per_second=800):
    447     """Perform swipe gesture on the element.
    448 
    449     The element may be selected via selector, text, or element_function.
    450     Only one of these arguments must be specified.
    451 
    452     Args:
    453       selector: A CSS selector describing the element.
    454       text: The element must contains this exact text.
    455       element_function: A JavaScript function (as string) that is used
    456           to retrieve the element. For example:
    457           'function() { return foo.element; }'.
    458       left_start_ratio: The horizontal starting coordinate of the
    459           gesture, as a ratio of the visible bounding rectangle for
    460           the element.
    461       top_start_ratio: The vertical starting coordinate of the
    462           gesture, as a ratio of the visible bounding rectangle for
    463           the element.
    464       direction: The direction of swipe, either 'left', 'right',
    465           'up', or 'down'
    466       distance: The distance to swipe (in pixel).
    467       speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
    468     """
    469     self._RunAction(SwipeAction(
    470         selector=selector, text=text, element_function=element_function,
    471         left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
    472         direction=direction, distance=distance,
    473         speed_in_pixels_per_second=speed_in_pixels_per_second))
    474 
    475   def PlayMedia(self, selector=None,
    476                 playing_event_timeout_in_seconds=0,
    477                 ended_event_timeout_in_seconds=0):
    478     """Invokes the "play" action on media elements (such as video).
    479 
    480     Args:
    481       selector: A CSS selector describing the element. If none is
    482           specified, play the first media element on the page. If the
    483           selector matches more than 1 media element, all of them will
    484           be played.
    485       playing_event_timeout_in_seconds: Maximum waiting time for the "playing"
    486           event (dispatched when the media begins to play) to be fired.
    487           0 means do not wait.
    488       ended_event_timeout_in_seconds: Maximum waiting time for the "ended"
    489           event (dispatched when playback completes) to be fired.
    490           0 means do not wait.
    491 
    492     Raises:
    493       TimeoutException: If the maximum waiting time is exceeded.
    494     """
    495     self._RunAction(PlayAction(
    496         selector=selector,
    497         playing_event_timeout_in_seconds=playing_event_timeout_in_seconds,
    498         ended_event_timeout_in_seconds=ended_event_timeout_in_seconds))
    499 
    500   def SeekMedia(self, seconds, selector=None, timeout_in_seconds=0,
    501                 log_time=True, label=''):
    502     """Performs a seek action on media elements (such as video).
    503 
    504     Args:
    505       seconds: The media time to seek to.
    506       selector: A CSS selector describing the element. If none is
    507           specified, seek the first media element on the page. If the
    508           selector matches more than 1 media element, all of them will
    509           be seeked.
    510       timeout_in_seconds: Maximum waiting time for the "seeked" event
    511           (dispatched when the seeked operation completes) to be
    512           fired.  0 means do not wait.
    513       log_time: Whether to log the seek time for the perf
    514           measurement. Useful when performing multiple seek.
    515       label: A suffix string to name the seek perf measurement.
    516 
    517     Raises:
    518       TimeoutException: If the maximum waiting time is exceeded.
    519     """
    520     self._RunAction(SeekAction(
    521         seconds=seconds, selector=selector,
    522         timeout_in_seconds=timeout_in_seconds,
    523         log_time=log_time, label=label))
    524 
    525   def LoopMedia(self, loop_count, selector=None, timeout_in_seconds=None):
    526     """Loops a media playback.
    527 
    528     Args:
    529       loop_count: The number of times to loop the playback.
    530       selector: A CSS selector describing the element. If none is
    531           specified, loop the first media element on the page. If the
    532           selector matches more than 1 media element, all of them will
    533           be looped.
    534       timeout_in_seconds: Maximum waiting time for the looped playback to
    535           complete. 0 means do not wait. None (the default) means to
    536           wait loop_count * 60 seconds.
    537 
    538     Raises:
    539       TimeoutException: If the maximum waiting time is exceeded.
    540     """
    541     self._RunAction(LoopAction(
    542         loop_count=loop_count, selector=selector,
    543         timeout_in_seconds=timeout_in_seconds))
    544 
    545   def ForceGarbageCollection(self):
    546     """Forces JavaScript garbage collection on the page."""
    547     self._tab.CollectGarbage()
    548 
    549   def PauseInteractive(self):
    550     """Pause the page execution and wait for terminal interaction.
    551 
    552     This is typically used for debugging. You can use this to pause
    553     the page execution and inspect the browser state before
    554     continuing.
    555     """
    556     raw_input("Interacting... Press Enter to continue.")
    557 
    558   def RepaintContinuously(self, seconds):
    559     """Continuously repaints the visible content.
    560 
    561     It does this by requesting animation frames until the given number
    562     of seconds have elapsed AND at least three RAFs have been
    563     fired. Times out after max(60, self.seconds), if less than three
    564     RAFs were fired."""
    565     self._RunAction(RepaintContinuouslyAction(
    566         seconds=0 if self._skip_waits else seconds))
    567 
    568 class Interaction(object):
    569 
    570   def __init__(self, action_runner, label, flags):
    571     assert action_runner
    572     assert label
    573     assert isinstance(flags, list)
    574 
    575     self._action_runner = action_runner
    576     self._label = label
    577     self._flags = flags
    578     self._started = False
    579 
    580   def Begin(self):
    581     assert not self._started
    582     self._started = True
    583     self._action_runner.ExecuteJavaScript('console.time("%s");' %
    584         timeline_interaction_record.GetJavaScriptMarker(
    585             self._label, self._flags))
    586 
    587   def End(self):
    588     assert self._started
    589     self._started = False
    590     self._action_runner.ExecuteJavaScript('console.timeEnd("%s");' %
    591         timeline_interaction_record.GetJavaScriptMarker(
    592             self._label, self._flags))
    593