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
5import logging
6import time
7import urlparse
8
9from telemetry.core import exceptions
10from telemetry.internal.actions.drag import DragAction
11from telemetry.internal.actions.javascript_click import ClickElementAction
12from telemetry.internal.actions.key_event import KeyPressAction
13from telemetry.internal.actions.load_media import LoadMediaAction
14from telemetry.internal.actions.loop import LoopAction
15from telemetry.internal.actions.mouse_click import MouseClickAction
16from telemetry.internal.actions.navigate import NavigateAction
17from telemetry.internal.actions.page_action import GESTURE_SOURCE_DEFAULT
18from telemetry.internal.actions.page_action import SUPPORTED_GESTURE_SOURCES
19from telemetry.internal.actions.pinch import PinchAction
20from telemetry.internal.actions.play import PlayAction
21from telemetry.internal.actions.repaint_continuously import (
22    RepaintContinuouslyAction)
23from telemetry.internal.actions.repeatable_scroll import RepeatableScrollAction
24from telemetry.internal.actions.scroll import ScrollAction
25from telemetry.internal.actions.scroll_bounce import ScrollBounceAction
26from telemetry.internal.actions.scroll_to_element import ScrollToElementAction
27from telemetry.internal.actions.seek import SeekAction
28from telemetry.internal.actions.swipe import SwipeAction
29from telemetry.internal.actions.tap import TapAction
30from telemetry.internal.actions.wait import WaitForElementAction
31from telemetry.web_perf import timeline_interaction_record
32
33from py_trace_event import trace_event
34
35import py_utils
36
37
38_DUMP_WAIT_TIME = 3
39
40
41class ActionRunner(object):
42
43  __metaclass__ = trace_event.TracedMetaClass
44
45  def __init__(self, tab, skip_waits=False):
46    self._tab = tab
47    self._skip_waits = skip_waits
48
49  @property
50  def tab(self):
51    """Returns the tab on which actions are performed."""
52    return self._tab
53
54  def _RunAction(self, action):
55    action.WillRunAction(self._tab)
56    action.RunAction(self._tab)
57
58  def CreateInteraction(self, label, repeatable=False):
59    """ Create an action.Interaction object that issues interaction record.
60
61    An interaction record is a labeled time period containing
62    interaction that developers care about. Each set of metrics
63    specified in flags will be calculated for this time period.
64
65    To mark the start of interaction record, call Begin() method on the returned
66    object. To mark the finish of interaction record, call End() method on
67    it. Or better yet, use the with statement to create an
68    interaction record that covers the actions in the with block.
69
70    e.g:
71      with action_runner.CreateInteraction('Animation-1'):
72        action_runner.TapElement(...)
73        action_runner.WaitForJavaScriptCondition(...)
74
75    Args:
76      label: A label for this particular interaction. This can be any
77          user-defined string, but must not contain '/'.
78      repeatable: Whether other interactions may use the same logical name
79          as this interaction. All interactions with the same logical name must
80          have the same flags.
81
82    Returns:
83      An instance of action_runner.Interaction
84    """
85    flags = []
86    if repeatable:
87      flags.append(timeline_interaction_record.REPEATABLE)
88
89    return Interaction(self, label, flags)
90
91  def CreateGestureInteraction(self, label, repeatable=False):
92    """ Create an action.Interaction object that issues gesture-based
93    interaction record.
94
95    This is similar to normal interaction record, but it will
96    auto-narrow the interaction time period to only include the
97    synthetic gesture event output by Chrome. This is typically use to
98    reduce noise in gesture-based analysis (e.g., analysis for a
99    swipe/scroll).
100
101    The interaction record label will be prepended with 'Gesture_'.
102
103    e.g:
104      with action_runner.CreateGestureInteraction('Scroll-1'):
105        action_runner.ScrollPage()
106
107    Args:
108      label: A label for this particular interaction. This can be any
109          user-defined string, but must not contain '/'.
110      repeatable: Whether other interactions may use the same logical name
111          as this interaction. All interactions with the same logical name must
112          have the same flags.
113
114    Returns:
115      An instance of action_runner.Interaction
116    """
117    return self.CreateInteraction('Gesture_' + label, repeatable)
118
119  def WaitForNetworkQuiescence(self, timeout_in_seconds=10):
120    """ Wait for network quiesence on the page.
121    Args:
122      timeout_in_seconds: maximum amount of time (seconds) to wait for network
123        quiesence unil raising exception.
124
125    Raises:
126      py_utils.TimeoutException when the timeout is reached but the page's
127        network is not quiet.
128    """
129
130    py_utils.WaitFor(self.tab.HasReachedQuiescence, timeout_in_seconds)
131
132  def MeasureMemory(self, deterministic_mode=False):
133    """Add a memory measurement to the trace being recorded.
134
135    Behaves as a no-op if tracing is not enabled.
136
137    TODO(perezju): Also behave as a no-op if tracing is enabled but
138    memory-infra is not.
139
140    Args:
141      deterministic_mode: A boolean indicating whether to attempt or not to
142          control the environment (force GCs, clear caches) before making the
143          measurement in an attempt to obtain more deterministic results.
144
145    Returns:
146      GUID of the generated dump if one was triggered, None otherwise.
147    """
148    platform = self.tab.browser.platform
149    if not platform.tracing_controller.is_tracing_running:
150      logging.warning('Tracing is off. No memory dumps are being recorded.')
151      return None
152    if deterministic_mode:
153      self.Wait(_DUMP_WAIT_TIME)
154      self.ForceGarbageCollection()
155      if platform.SupportFlushEntireSystemCache():
156        platform.FlushEntireSystemCache()
157      self.Wait(_DUMP_WAIT_TIME)
158    dump_id = self.tab.browser.DumpMemory()
159    if not dump_id:
160      raise exceptions.Error('Unable to obtain memory dump')
161    return dump_id
162
163  def Navigate(self, url, script_to_evaluate_on_commit=None,
164               timeout_in_seconds=60):
165    """Navigates to |url|.
166
167    If |script_to_evaluate_on_commit| is given, the script source string will be
168    evaluated when the navigation is committed. This is after the context of
169    the page exists, but before any script on the page itself has executed.
170    """
171    if urlparse.urlparse(url).scheme == 'file':
172      url = self._tab.browser.platform.http_server.UrlOf(url[7:])
173
174    self._RunAction(NavigateAction(
175        url=url,
176        script_to_evaluate_on_commit=script_to_evaluate_on_commit,
177        timeout_in_seconds=timeout_in_seconds))
178
179  def NavigateBack(self):
180    """ Navigate back to the previous page."""
181    self.ExecuteJavaScript('window.history.back()')
182
183  def WaitForNavigate(self, timeout_in_seconds_seconds=60):
184    start_time = time.time()
185    self._tab.WaitForNavigate(timeout_in_seconds_seconds)
186
187    time_left_in_seconds = (start_time + timeout_in_seconds_seconds
188                            - time.time())
189    time_left_in_seconds = max(0, time_left_in_seconds)
190    self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter(
191        time_left_in_seconds)
192
193  def ReloadPage(self):
194    """Reloads the page."""
195    self._tab.ExecuteJavaScript('window.location.reload()')
196    self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
197
198  def ExecuteJavaScript(self, *args, **kwargs):
199    """Executes a given JavaScript statement. Does not return the result.
200
201    Example: runner.ExecuteJavaScript('var foo = {{ value }};', value='hi');
202
203    Args:
204      statement: The statement to execute (provided as a string).
205
206    Optional keyword args:
207      timeout: The number of seconds to wait for the statement to execute.
208      Additional keyword arguments provide values to be interpolated within
209          the statement. See telemetry.util.js_template for details.
210
211    Raises:
212      EvaluationException: The statement failed to execute.
213    """
214    return self._tab.ExecuteJavaScript(*args, **kwargs)
215
216  def EvaluateJavaScript(self, *args, **kwargs):
217    """Returns the result of evaluating a given JavaScript expression.
218
219    The evaluation results must be convertible to JSON. If the result
220    is not needed, use ExecuteJavaScript instead.
221
222    Example: runner.ExecuteJavaScript('document.location.href');
223
224    Args:
225      expression: The expression to execute (provided as a string).
226
227    Optional keyword args:
228      timeout: The number of seconds to wait for the expression to evaluate.
229      Additional keyword arguments provide values to be interpolated within
230          the expression. See telemetry.util.js_template for details.
231
232    Raises:
233      EvaluationException: The statement expression failed to execute
234          or the evaluation result can not be JSON-ized.
235    """
236    return self._tab.EvaluateJavaScript(*args, **kwargs)
237
238  def WaitForJavaScriptCondition(self, *args, **kwargs):
239    """Wait for a JavaScript condition to become true.
240
241    Example: runner.WaitForJavaScriptCondition('window.foo == 10');
242
243    Args:
244      condition: The JavaScript condition (provided as string).
245
246    Optional keyword args:
247      timeout: The number in seconds to wait for the condition to become
248          True (default to 60).
249      Additional keyword arguments provide values to be interpolated within
250          the expression. See telemetry.util.js_template for details.
251    """
252    return self._tab.WaitForJavaScriptCondition(*args, **kwargs)
253
254  def Wait(self, seconds):
255    """Wait for the number of seconds specified.
256
257    Args:
258      seconds: The number of seconds to wait.
259    """
260    if not self._skip_waits:
261      time.sleep(seconds)
262
263  def WaitForElement(self, selector=None, text=None, element_function=None,
264                     timeout_in_seconds=60):
265    """Wait for an element to appear in the document.
266
267    The element may be selected via selector, text, or element_function.
268    Only one of these arguments must be specified.
269
270    Args:
271      selector: A CSS selector describing the element.
272      text: The element must contains this exact text.
273      element_function: A JavaScript function (as string) that is used
274          to retrieve the element. For example:
275          '(function() { return foo.element; })()'.
276      timeout_in_seconds: The timeout in seconds (default to 60).
277    """
278    self._RunAction(WaitForElementAction(
279        selector=selector, text=text, element_function=element_function,
280        timeout_in_seconds=timeout_in_seconds))
281
282  def TapElement(self, selector=None, text=None, element_function=None):
283    """Tap an element.
284
285    The element may be selected via selector, text, or element_function.
286    Only one of these arguments must be specified.
287
288    Args:
289      selector: A CSS selector describing the element.
290      text: The element must contains this exact text.
291      element_function: A JavaScript function (as string) that is used
292          to retrieve the element. For example:
293          '(function() { return foo.element; })()'.
294    """
295    self._RunAction(TapAction(
296        selector=selector, text=text, element_function=element_function))
297
298  def ClickElement(self, selector=None, text=None, element_function=None):
299    """Click an element.
300
301    The element may be selected via selector, text, or element_function.
302    Only one of these arguments must be specified.
303
304    Args:
305      selector: A CSS selector describing the element.
306      text: The element must contains this exact text.
307      element_function: A JavaScript function (as string) that is used
308          to retrieve the element. For example:
309          '(function() { return foo.element; })()'.
310    """
311    self._RunAction(ClickElementAction(
312        selector=selector, text=text, element_function=element_function))
313
314  def DragPage(self, left_start_ratio, top_start_ratio, left_end_ratio,
315               top_end_ratio, speed_in_pixels_per_second=800, use_touch=False,
316               selector=None, text=None, element_function=None):
317    """Perform a drag gesture on the page.
318
319    You should specify a start and an end point in ratios of page width and
320    height (see drag.js for full implementation).
321
322    Args:
323      left_start_ratio: The horizontal starting coordinate of the
324          gesture, as a ratio of the visible bounding rectangle for
325          document.body.
326      top_start_ratio: The vertical starting coordinate of the
327          gesture, as a ratio of the visible bounding rectangle for
328          document.body.
329      left_end_ratio: The horizontal ending coordinate of the
330          gesture, as a ratio of the visible bounding rectangle for
331          document.body.
332      top_end_ratio: The vertical ending coordinate of the
333          gesture, as a ratio of the visible bounding rectangle for
334          document.body.
335      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
336      use_touch: Whether dragging should be done with touch input.
337    """
338    self._RunAction(DragAction(
339        left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
340        left_end_ratio=left_end_ratio, top_end_ratio=top_end_ratio,
341        speed_in_pixels_per_second=speed_in_pixels_per_second,
342        use_touch=use_touch, selector=selector, text=text,
343        element_function=element_function))
344
345  def PinchPage(self, left_anchor_ratio=0.5, top_anchor_ratio=0.5,
346                scale_factor=None, speed_in_pixels_per_second=800):
347    """Perform the pinch gesture on the page.
348
349    It computes the pinch gesture automatically based on the anchor
350    coordinate and the scale factor. The scale factor is the ratio of
351    of the final span and the initial span of the gesture.
352
353    Args:
354      left_anchor_ratio: The horizontal pinch anchor coordinate of the
355          gesture, as a ratio of the visible bounding rectangle for
356          document.body.
357      top_anchor_ratio: The vertical pinch anchor coordinate of the
358          gesture, as a ratio of the visible bounding rectangle for
359          document.body.
360      scale_factor: The ratio of the final span to the initial span.
361          The default scale factor is
362          3.0 / (window.outerWidth/window.innerWidth).
363      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
364    """
365    self._RunAction(PinchAction(
366        left_anchor_ratio=left_anchor_ratio, top_anchor_ratio=top_anchor_ratio,
367        scale_factor=scale_factor,
368        speed_in_pixels_per_second=speed_in_pixels_per_second))
369
370  def PinchElement(self, selector=None, text=None, element_function=None,
371                   left_anchor_ratio=0.5, top_anchor_ratio=0.5,
372                   scale_factor=None, speed_in_pixels_per_second=800):
373    """Perform the pinch gesture on an element.
374
375    It computes the pinch gesture automatically based on the anchor
376    coordinate and the scale factor. The scale factor is the ratio of
377    of the final span and the initial span of the gesture.
378
379    Args:
380      selector: A CSS selector describing the element.
381      text: The element must contains this exact text.
382      element_function: A JavaScript function (as string) that is used
383          to retrieve the element. For example:
384          'function() { return foo.element; }'.
385      left_anchor_ratio: The horizontal pinch anchor coordinate of the
386          gesture, as a ratio of the visible bounding rectangle for
387          the element.
388      top_anchor_ratio: The vertical pinch anchor coordinate of the
389          gesture, as a ratio of the visible bounding rectangle for
390          the element.
391      scale_factor: The ratio of the final span to the initial span.
392          The default scale factor is
393          3.0 / (window.outerWidth/window.innerWidth).
394      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
395    """
396    self._RunAction(PinchAction(
397        selector=selector, text=text, element_function=element_function,
398        left_anchor_ratio=left_anchor_ratio, top_anchor_ratio=top_anchor_ratio,
399        scale_factor=scale_factor,
400        speed_in_pixels_per_second=speed_in_pixels_per_second))
401
402  def ScrollPage(self, left_start_ratio=0.5, top_start_ratio=0.5,
403                 direction='down', distance=None, distance_expr=None,
404                 speed_in_pixels_per_second=800, use_touch=False,
405                 synthetic_gesture_source=GESTURE_SOURCE_DEFAULT):
406    """Perform scroll gesture on the page.
407
408    You may specify distance or distance_expr, but not both. If
409    neither is specified, the default scroll distance is variable
410    depending on direction (see scroll.js for full implementation).
411
412    Args:
413      left_start_ratio: The horizontal starting coordinate of the
414          gesture, as a ratio of the visible bounding rectangle for
415          document.body.
416      top_start_ratio: The vertical starting coordinate of the
417          gesture, as a ratio of the visible bounding rectangle for
418          document.body.
419      direction: The direction of scroll, either 'left', 'right',
420          'up', 'down', 'upleft', 'upright', 'downleft', or 'downright'
421      distance: The distance to scroll (in pixel).
422      distance_expr: A JavaScript expression (as string) that can be
423          evaluated to compute scroll distance. Example:
424          'window.scrollTop' or '(function() { return crazyMath(); })()'.
425      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
426      use_touch: Whether scrolling should be done with touch input.
427      synthetic_gesture_source: the source input device type for the
428          synthetic gesture: 'DEFAULT', 'TOUCH' or 'MOUSE'.
429    """
430    assert synthetic_gesture_source in SUPPORTED_GESTURE_SOURCES
431    self._RunAction(ScrollAction(
432        left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
433        direction=direction, distance=distance, distance_expr=distance_expr,
434        speed_in_pixels_per_second=speed_in_pixels_per_second,
435        use_touch=use_touch, synthetic_gesture_source=synthetic_gesture_source))
436
437  def ScrollPageToElement(self, selector=None, element_function=None,
438                          container_selector=None,
439                          container_element_function=None,
440                          speed_in_pixels_per_second=800):
441    """Perform scroll gesture on container until an element is in view.
442
443    Both the element and the container can be specified by a CSS selector
444    xor a JavaScript function, provided as a string, which returns an element.
445    The element is required so exactly one of selector and element_function
446    must be provided. The container is optional so at most one of
447    container_selector and container_element_function can be provided.
448    The container defaults to document.scrollingElement or document.body if
449    scrollingElement is not set.
450
451    Args:
452      selector: A CSS selector describing the element.
453      element_function: A JavaScript function (as string) that is used
454          to retrieve the element. For example:
455          'function() { return foo.element; }'.
456      container_selector: A CSS selector describing the container element.
457      container_element_function: A JavaScript function (as a string) that is
458          used to retrieve the container element.
459      speed_in_pixels_per_second: Speed to scroll.
460    """
461    self._RunAction(ScrollToElementAction(
462        selector=selector, element_function=element_function,
463        container_selector=container_selector,
464        container_element_function=container_element_function,
465        speed_in_pixels_per_second=speed_in_pixels_per_second))
466
467  def RepeatableBrowserDrivenScroll(self, x_scroll_distance_ratio=0.0,
468                                    y_scroll_distance_ratio=0.5,
469                                    repeat_count=0,
470                                    repeat_delay_ms=250,
471                                    timeout=60,
472                                    prevent_fling=None,
473                                    speed=None):
474    """Perform a browser driven repeatable scroll gesture.
475
476    The scroll gesture is driven from the browser, this is useful because the
477    main thread often isn't resposive but the browser process usually is, so the
478    delay between the scroll gestures should be consistent.
479
480    Args:
481      x_scroll_distance_ratio: The horizontal length of the scroll as a fraction
482          of the screen width.
483      y_scroll_distance_ratio: The vertical length of the scroll as a fraction
484          of the screen height.
485      repeat_count: The number of additional times to repeat the gesture.
486      repeat_delay_ms: The delay in milliseconds between each scroll gesture.
487      prevent_fling: Prevents a fling gesture.
488      speed: Swipe speed in pixels per second.
489    """
490    self._RunAction(RepeatableScrollAction(
491        x_scroll_distance_ratio=x_scroll_distance_ratio,
492        y_scroll_distance_ratio=y_scroll_distance_ratio,
493        repeat_count=repeat_count,
494        repeat_delay_ms=repeat_delay_ms, timeout=timeout,
495        prevent_fling=prevent_fling, speed=speed))
496
497  def ScrollElement(self, selector=None, text=None, element_function=None,
498                    left_start_ratio=0.5, top_start_ratio=0.5,
499                    direction='down', distance=None, distance_expr=None,
500                    speed_in_pixels_per_second=800, use_touch=False,
501                    synthetic_gesture_source=GESTURE_SOURCE_DEFAULT):
502    """Perform scroll gesture on the element.
503
504    The element may be selected via selector, text, or element_function.
505    Only one of these arguments must be specified.
506
507    You may specify distance or distance_expr, but not both. If
508    neither is specified, the default scroll distance is variable
509    depending on direction (see scroll.js for full implementation).
510
511    Args:
512      selector: A CSS selector describing the element.
513      text: The element must contains this exact text.
514      element_function: A JavaScript function (as string) that is used
515          to retrieve the element. For example:
516          'function() { return foo.element; }'.
517      left_start_ratio: The horizontal starting coordinate of the
518          gesture, as a ratio of the visible bounding rectangle for
519          the element.
520      top_start_ratio: The vertical starting coordinate of the
521          gesture, as a ratio of the visible bounding rectangle for
522          the element.
523      direction: The direction of scroll, either 'left', 'right',
524          'up', 'down', 'upleft', 'upright', 'downleft', or 'downright'
525      distance: The distance to scroll (in pixel).
526      distance_expr: A JavaScript expression (as string) that can be
527          evaluated to compute scroll distance. Example:
528          'window.scrollTop' or '(function() { return crazyMath(); })()'.
529      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
530      use_touch: Whether scrolling should be done with touch input.
531      synthetic_gesture_source: the source input device type for the
532          synthetic gesture: 'DEFAULT', 'TOUCH' or 'MOUSE'.
533    """
534    assert synthetic_gesture_source in SUPPORTED_GESTURE_SOURCES
535    self._RunAction(ScrollAction(
536        selector=selector, text=text, element_function=element_function,
537        left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
538        direction=direction, distance=distance, distance_expr=distance_expr,
539        speed_in_pixels_per_second=speed_in_pixels_per_second,
540        use_touch=use_touch, synthetic_gesture_source=synthetic_gesture_source))
541
542  def ScrollBouncePage(self, left_start_ratio=0.5, top_start_ratio=0.5,
543                       direction='down', distance=100,
544                       overscroll=10, repeat_count=10,
545                       speed_in_pixels_per_second=400):
546    """Perform scroll bounce gesture on the page.
547
548    This gesture scrolls the page by the number of pixels specified in
549    distance, in the given direction, followed by a scroll by
550    (distance + overscroll) pixels in the opposite direction.
551    The above gesture is repeated repeat_count times.
552
553    Args:
554      left_start_ratio: The horizontal starting coordinate of the
555          gesture, as a ratio of the visible bounding rectangle for
556          document.body.
557      top_start_ratio: The vertical starting coordinate of the
558          gesture, as a ratio of the visible bounding rectangle for
559          document.body.
560      direction: The direction of scroll, either 'left', 'right',
561          'up', 'down', 'upleft', 'upright', 'downleft', or 'downright'
562      distance: The distance to scroll (in pixel).
563      overscroll: The number of additional pixels to scroll back, in
564          addition to the givendistance.
565      repeat_count: How often we want to repeat the full gesture.
566      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
567    """
568    self._RunAction(ScrollBounceAction(
569        left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
570        direction=direction, distance=distance,
571        overscroll=overscroll, repeat_count=repeat_count,
572        speed_in_pixels_per_second=speed_in_pixels_per_second))
573
574  def ScrollBounceElement(
575      self, selector=None, text=None, element_function=None,
576      left_start_ratio=0.5, top_start_ratio=0.5,
577      direction='down', distance=100,
578      overscroll=10, repeat_count=10,
579      speed_in_pixels_per_second=400):
580    """Perform scroll bounce gesture on the element.
581
582    This gesture scrolls on the element by the number of pixels specified in
583    distance, in the given direction, followed by a scroll by
584    (distance + overscroll) pixels in the opposite direction.
585    The above gesture is repeated repeat_count times.
586
587    Args:
588      selector: A CSS selector describing the element.
589      text: The element must contains this exact text.
590      element_function: A JavaScript function (as string) that is used
591          to retrieve the element. For example:
592          'function() { return foo.element; }'.
593      left_start_ratio: The horizontal starting coordinate of the
594          gesture, as a ratio of the visible bounding rectangle for
595          document.body.
596      top_start_ratio: The vertical starting coordinate of the
597          gesture, as a ratio of the visible bounding rectangle for
598          document.body.
599      direction: The direction of scroll, either 'left', 'right',
600          'up', 'down', 'upleft', 'upright', 'downleft', or 'downright'
601      distance: The distance to scroll (in pixel).
602      overscroll: The number of additional pixels to scroll back, in
603          addition to the given distance.
604      repeat_count: How often we want to repeat the full gesture.
605      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
606    """
607    self._RunAction(ScrollBounceAction(
608        selector=selector, text=text, element_function=element_function,
609        left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
610        direction=direction, distance=distance,
611        overscroll=overscroll, repeat_count=repeat_count,
612        speed_in_pixels_per_second=speed_in_pixels_per_second))
613
614  def MouseClick(self, selector=None):
615    """Mouse click the given element.
616
617    Args:
618      selector: A CSS selector describing the element.
619    """
620    self._RunAction(MouseClickAction(selector=selector))
621
622  def SwipePage(self, left_start_ratio=0.5, top_start_ratio=0.5,
623                direction='left', distance=100, speed_in_pixels_per_second=800):
624    """Perform swipe gesture on the page.
625
626    Args:
627      left_start_ratio: The horizontal starting coordinate of the
628          gesture, as a ratio of the visible bounding rectangle for
629          document.body.
630      top_start_ratio: The vertical starting coordinate of the
631          gesture, as a ratio of the visible bounding rectangle for
632          document.body.
633      direction: The direction of swipe, either 'left', 'right',
634          'up', or 'down'
635      distance: The distance to swipe (in pixel).
636      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
637    """
638    self._RunAction(SwipeAction(
639        left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
640        direction=direction, distance=distance,
641        speed_in_pixels_per_second=speed_in_pixels_per_second))
642
643  def SwipeElement(self, selector=None, text=None, element_function=None,
644                   left_start_ratio=0.5, top_start_ratio=0.5,
645                   direction='left', distance=100,
646                   speed_in_pixels_per_second=800):
647    """Perform swipe gesture on the element.
648
649    The element may be selected via selector, text, or element_function.
650    Only one of these arguments must be specified.
651
652    Args:
653      selector: A CSS selector describing the element.
654      text: The element must contains this exact text.
655      element_function: A JavaScript function (as string) that is used
656          to retrieve the element. For example:
657          'function() { return foo.element; }'.
658      left_start_ratio: The horizontal starting coordinate of the
659          gesture, as a ratio of the visible bounding rectangle for
660          the element.
661      top_start_ratio: The vertical starting coordinate of the
662          gesture, as a ratio of the visible bounding rectangle for
663          the element.
664      direction: The direction of swipe, either 'left', 'right',
665          'up', or 'down'
666      distance: The distance to swipe (in pixel).
667      speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
668    """
669    self._RunAction(SwipeAction(
670        selector=selector, text=text, element_function=element_function,
671        left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
672        direction=direction, distance=distance,
673        speed_in_pixels_per_second=speed_in_pixels_per_second))
674
675  def PressKey(self, key, repeat_count=1, repeat_delay_ms=100, timeout=60):
676    """Perform a key press.
677
678    Args:
679      key: DOM value of the pressed key (e.g. 'PageDown', see
680          https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key).
681      repeat_count: How many times the key should be pressed.
682      repeat_delay_ms: Delay after each keypress (including the last one) in
683          milliseconds.
684    """
685    for _ in xrange(repeat_count):
686      self._RunAction(KeyPressAction(key, timeout=timeout))
687      self.Wait(repeat_delay_ms / 1000.0)
688
689  def EnterText(self, text, character_delay_ms=100, timeout=60):
690    """Enter text by performing key presses.
691
692    Args:
693      text: The text to enter.
694      character_delay_ms: Delay after each keypress (including the last one) in
695          milliseconds.
696    """
697    for c in text:
698      self.PressKey(c, repeat_delay_ms=character_delay_ms, timeout=timeout)
699
700  def LoadMedia(self, selector=None, event_timeout_in_seconds=0,
701                event_to_await='canplaythrough'):
702    """Invokes load() on media elements and awaits an event.
703
704    Args:
705      selector: A CSS selector describing the element. If none is
706          specified, play the first media element on the page. If the
707          selector matches more than 1 media element, all of them will
708          be played.
709      event_timeout_in_seconds: Maximum waiting time for the event to be fired.
710          0 means do not wait.
711      event_to_await: Which event to await. For example: 'canplaythrough' or
712          'loadedmetadata'.
713
714    Raises:
715      TimeoutException: If the maximum waiting time is exceeded.
716    """
717    self._RunAction(LoadMediaAction(
718        selector=selector, timeout_in_seconds=event_timeout_in_seconds,
719        event_to_await=event_to_await))
720
721  def PlayMedia(self, selector=None,
722                playing_event_timeout_in_seconds=0,
723                ended_event_timeout_in_seconds=0):
724    """Invokes the "play" action on media elements (such as video).
725
726    Args:
727      selector: A CSS selector describing the element. If none is
728          specified, play the first media element on the page. If the
729          selector matches more than 1 media element, all of them will
730          be played.
731      playing_event_timeout_in_seconds: Maximum waiting time for the "playing"
732          event (dispatched when the media begins to play) to be fired.
733          0 means do not wait.
734      ended_event_timeout_in_seconds: Maximum waiting time for the "ended"
735          event (dispatched when playback completes) to be fired.
736          0 means do not wait.
737
738    Raises:
739      TimeoutException: If the maximum waiting time is exceeded.
740    """
741    self._RunAction(PlayAction(
742        selector=selector,
743        playing_event_timeout_in_seconds=playing_event_timeout_in_seconds,
744        ended_event_timeout_in_seconds=ended_event_timeout_in_seconds))
745
746  def SeekMedia(self, seconds, selector=None, timeout_in_seconds=0,
747                log_time=True, label=''):
748    """Performs a seek action on media elements (such as video).
749
750    Args:
751      seconds: The media time to seek to.
752      selector: A CSS selector describing the element. If none is
753          specified, seek the first media element on the page. If the
754          selector matches more than 1 media element, all of them will
755          be seeked.
756      timeout_in_seconds: Maximum waiting time for the "seeked" event
757          (dispatched when the seeked operation completes) to be
758          fired.  0 means do not wait.
759      log_time: Whether to log the seek time for the perf
760          measurement. Useful when performing multiple seek.
761      label: A suffix string to name the seek perf measurement.
762
763    Raises:
764      TimeoutException: If the maximum waiting time is exceeded.
765    """
766    self._RunAction(SeekAction(
767        seconds=seconds, selector=selector,
768        timeout_in_seconds=timeout_in_seconds,
769        log_time=log_time, label=label))
770
771  def LoopMedia(self, loop_count, selector=None, timeout_in_seconds=None):
772    """Loops a media playback.
773
774    Args:
775      loop_count: The number of times to loop the playback.
776      selector: A CSS selector describing the element. If none is
777          specified, loop the first media element on the page. If the
778          selector matches more than 1 media element, all of them will
779          be looped.
780      timeout_in_seconds: Maximum waiting time for the looped playback to
781          complete. 0 means do not wait. None (the default) means to
782          wait loop_count * 60 seconds.
783
784    Raises:
785      TimeoutException: If the maximum waiting time is exceeded.
786    """
787    self._RunAction(LoopAction(
788        loop_count=loop_count, selector=selector,
789        timeout_in_seconds=timeout_in_seconds))
790
791  def ForceGarbageCollection(self):
792    """Forces JavaScript garbage collection on the page."""
793    self._tab.CollectGarbage()
794
795  def SimulateMemoryPressureNotification(self, pressure_level):
796    """Simulate memory pressure notification.
797
798    Args:
799      pressure_level: 'moderate' or 'critical'.
800    """
801    self._tab.browser.SimulateMemoryPressureNotification(pressure_level)
802
803  def PauseInteractive(self):
804    """Pause the page execution and wait for terminal interaction.
805
806    This is typically used for debugging. You can use this to pause
807    the page execution and inspect the browser state before
808    continuing.
809    """
810    raw_input("Interacting... Press Enter to continue.")
811
812  def RepaintContinuously(self, seconds):
813    """Continuously repaints the visible content.
814
815    It does this by requesting animation frames until the given number
816    of seconds have elapsed AND at least three RAFs have been
817    fired. Times out after max(60, self.seconds), if less than three
818    RAFs were fired."""
819    self._RunAction(RepaintContinuouslyAction(
820        seconds=0 if self._skip_waits else seconds))
821
822
823class Interaction(object):
824
825  def __init__(self, action_runner, label, flags):
826    assert action_runner
827    assert label
828    assert isinstance(flags, list)
829
830    self._action_runner = action_runner
831    self._label = label
832    self._flags = flags
833    self._started = False
834
835  def __enter__(self):
836    self.Begin()
837    return self
838
839  def __exit__(self, exc_type, exc_value, traceback):
840    if exc_value is None:
841      self.End()
842    else:
843      logging.warning(
844          'Exception was raised in the with statement block, the end of '
845          'interaction record is not marked.')
846
847  def Begin(self):
848    assert not self._started
849    self._started = True
850    self._action_runner.ExecuteJavaScript(
851        'console.time({{ marker }});',
852        marker=timeline_interaction_record.GetJavaScriptMarker(
853            self._label, self._flags))
854
855  def End(self):
856    assert self._started
857    self._started = False
858    self._action_runner.ExecuteJavaScript(
859        'console.timeEnd({{ marker }});',
860        marker=timeline_interaction_record.GetJavaScriptMarker(
861            self._label, self._flags))
862