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