import time
from telemetry.page.actions.javascript_click import ClickElementAction
+from telemetry.page.actions.loop import LoopAction
from telemetry.page.actions.navigate import NavigateAction
+from telemetry.page.actions.pinch import PinchAction
+from telemetry.page.actions.play import PlayAction
+from telemetry.page.actions.repaint_continuously import (
+ RepaintContinuouslyAction)
+from telemetry.page.actions.scroll import ScrollAction
+from telemetry.page.actions.scroll_bounce import ScrollBounceAction
+from telemetry.page.actions.seek import SeekAction
from telemetry.page.actions.swipe import SwipeAction
from telemetry.page.actions.tap import TapAction
from telemetry.page.actions.wait import WaitForElementAction
-from telemetry.web_perf import timeline_interaction_record as tir_module
+from telemetry.web_perf import timeline_interaction_record
class ActionRunner(object):
- def __init__(self, tab):
+ def __init__(self, tab, skip_waits=False):
self._tab = tab
+ self._skip_waits = skip_waits
- # TODO(nednguyen): remove this (or make private) when
- # crbug.com/361809 is marked fixed
- def RunAction(self, action):
+ def _RunAction(self, action):
action.WillRunAction(self._tab)
action.RunAction(self._tab)
- def BeginInteraction(self, label, is_smooth=False, is_responsive=False):
+ def BeginInteraction(self, label, is_fast=False, is_smooth=False,
+ is_responsive=False, repeatable=False):
"""Marks the beginning of an interaction record.
An interaction record is a labeled time period containing
Args:
label: A label for this particular interaction. This can be any
user-defined string, but must not contain '/'.
+ is_fast: Whether to measure how fast the browser completes necessary work
+ for this interaction record. See fast_metric.py for details.
is_smooth: Whether to check for smoothness metrics for this interaction.
is_responsive: Whether to check for responsiveness metrics for
this interaction.
+ repeatable: Whether other interactions may use the same logical name
+ as this interaction. All interactions with the same logical name must
+ have the same flags.
"""
flags = []
+ if is_fast:
+ flags.append(timeline_interaction_record.IS_FAST)
if is_smooth:
- flags.append(tir_module.IS_SMOOTH)
+ flags.append(timeline_interaction_record.IS_SMOOTH)
if is_responsive:
- flags.append(tir_module.IS_RESPONSIVE)
+ flags.append(timeline_interaction_record.IS_RESPONSIVE)
+ if repeatable:
+ flags.append(timeline_interaction_record.REPEATABLE)
interaction = Interaction(self._tab, label, flags)
interaction.Begin()
return interaction
- def BeginGestureInteraction(
- self, label, is_smooth=False, is_responsive=False):
+ def BeginGestureInteraction(self, label, is_fast=False, is_smooth=False,
+ is_responsive=False, repeatable=False):
"""Marks the beginning of a gesture-based interaction record.
This is similar to normal interaction record, but it will
Args:
label: A label for this particular interaction. This can be any
user-defined string, but must not contain '/'.
+ is_fast: Whether to measure how fast the browser completes necessary work
+ for this interaction record. See fast_metric.py for details.
is_smooth: Whether to check for smoothness metrics for this interaction.
is_responsive: Whether to check for responsiveness metrics for
this interaction.
+ repeatable: Whether other interactions may use the same logical name
+ as this interaction. All interactions with the same logical name must
+ have the same flags.
"""
- return self.BeginInteraction('Gesture_' + label, is_smooth, is_responsive)
+ return self.BeginInteraction('Gesture_' + label, is_fast, is_smooth,
+ is_responsive, repeatable)
- def NavigateToPage(self, page, timeout_seconds=None):
- """ Navigate to the given page.
+ def NavigateToPage(self, page, timeout_in_seconds=60):
+ """Navigate to the given page.
Args:
page: page is an instance of page.Page
+ timeout_in_seconds: The timeout in seconds (default to 60).
"""
if page.is_file:
target_side_url = self._tab.browser.http_server.UrlOf(page.file_path_url)
else:
target_side_url = page.url
- attributes = {
- 'url': target_side_url,
- 'script_to_evaluate_on_commit': page.script_to_evaluate_on_commit}
- if timeout_seconds:
- attributes['timeout_seconds'] = timeout_seconds
- self.RunAction(NavigateAction(attributes))
-
- def WaitForNavigate(self, timeout_seconds=60):
- self._tab.WaitForNavigate(timeout_seconds)
+ self._RunAction(NavigateAction(
+ url=target_side_url,
+ script_to_evaluate_on_commit=page.script_to_evaluate_on_commit,
+ timeout_in_seconds=timeout_in_seconds))
+
+ def WaitForNavigate(self, timeout_in_seconds_seconds=60):
+ self._tab.WaitForNavigate(timeout_in_seconds_seconds)
+ self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
+
+ def ReloadPage(self):
+ """Reloads the page."""
+ self._tab.ExecuteJavaScript('window.location.reload()')
self._tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
def ExecuteJavaScript(self, statement):
Args:
seconds: The number of seconds to wait.
"""
- time.sleep(seconds)
+ if not self._skip_waits:
+ time.sleep(seconds)
- def WaitForJavaScriptCondition(self, condition, timeout=60):
+ def WaitForJavaScriptCondition(self, condition, timeout_in_seconds=60):
"""Wait for a JavaScript condition to become true.
Example: runner.WaitForJavaScriptCondition('window.foo == 10');
Args:
condition: The JavaScript condition (as string).
- timeout: The timeout in seconds (default to 60).
+ timeout_in_seconds: The timeout in seconds (default to 60).
"""
- self._tab.WaitForJavaScriptExpression(condition, timeout)
+ self._tab.WaitForJavaScriptExpression(condition, timeout_in_seconds)
def WaitForElement(self, selector=None, text=None, element_function=None,
- timeout=60):
+ timeout_in_seconds=60):
"""Wait for an element to appear in the document.
The element may be selected via selector, text, or element_function.
element_function: A JavaScript function (as string) that is used
to retrieve the element. For example:
'(function() { return foo.element; })()'.
- timeout: The timeout in seconds (default to 60).
+ timeout_in_seconds: The timeout in seconds (default to 60).
"""
- self.RunAction(WaitForElementAction(
+ self._RunAction(WaitForElementAction(
selector=selector, text=text, element_function=element_function,
- timeout=timeout))
+ timeout_in_seconds=timeout_in_seconds))
def TapElement(self, selector=None, text=None, element_function=None):
"""Tap an element.
to retrieve the element. For example:
'(function() { return foo.element; })()'.
"""
- self.RunAction(TapAction(
+ self._RunAction(TapAction(
selector=selector, text=text, element_function=element_function))
def ClickElement(self, selector=None, text=None, element_function=None):
to retrieve the element. For example:
'(function() { return foo.element; })()'.
"""
- self.RunAction(ClickElementAction(
+ self._RunAction(ClickElementAction(
selector=selector, text=text, element_function=element_function))
+ def PinchPage(self, left_anchor_ratio=0.5, top_anchor_ratio=0.5,
+ scale_factor=None, speed_in_pixels_per_second=800):
+ """Perform the pinch gesture on the page.
+
+ It computes the pinch gesture automatically based on the anchor
+ coordinate and the scale factor. The scale factor is the ratio of
+ of the final span and the initial span of the gesture.
+
+ Args:
+ left_anchor_ratio: The horizontal pinch anchor coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ document.body.
+ top_anchor_ratio: The vertical pinch anchor coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ document.body.
+ scale_factor: The ratio of the final span to the initial span.
+ The default scale factor is
+ 3.0 / (window.outerWidth/window.innerWidth).
+ speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
+ """
+ self._RunAction(PinchAction(
+ left_anchor_ratio=left_anchor_ratio, top_anchor_ratio=top_anchor_ratio,
+ scale_factor=scale_factor,
+ speed_in_pixels_per_second=speed_in_pixels_per_second))
+
+ def PinchElement(self, selector=None, text=None, element_function=None,
+ left_anchor_ratio=0.5, top_anchor_ratio=0.5,
+ scale_factor=None, speed_in_pixels_per_second=800):
+ """Perform the pinch gesture on an element.
+
+ It computes the pinch gesture automatically based on the anchor
+ coordinate and the scale factor. The scale factor is the ratio of
+ of the final span and the initial span of the gesture.
+
+ Args:
+ selector: A CSS selector describing the element.
+ text: The element must contains this exact text.
+ element_function: A JavaScript function (as string) that is used
+ to retrieve the element. For example:
+ 'function() { return foo.element; }'.
+ left_anchor_ratio: The horizontal pinch anchor coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ the element.
+ top_anchor_ratio: The vertical pinch anchor coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ the element.
+ scale_factor: The ratio of the final span to the initial span.
+ The default scale factor is
+ 3.0 / (window.outerWidth/window.innerWidth).
+ speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
+ """
+ self._RunAction(PinchAction(
+ selector=selector, text=text, element_function=element_function,
+ left_anchor_ratio=left_anchor_ratio, top_anchor_ratio=top_anchor_ratio,
+ scale_factor=scale_factor,
+ speed_in_pixels_per_second=speed_in_pixels_per_second))
+
+ def ScrollPage(self, left_start_ratio=0.5, top_start_ratio=0.5,
+ direction='down', distance=None, distance_expr=None,
+ speed_in_pixels_per_second=800, use_touch=False):
+ """Perform scroll gesture on the page.
+
+ You may specify distance or distance_expr, but not both. If
+ neither is specified, the default scroll distance is variable
+ depending on direction (see scroll.js for full implementation).
+
+ Args:
+ left_start_ratio: The horizontal starting coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ document.body.
+ top_start_ratio: The vertical starting coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ document.body.
+ direction: The direction of scroll, either 'left', 'right',
+ 'up', or 'down'
+ distance: The distance to scroll (in pixel).
+ distance_expr: A JavaScript expression (as string) that can be
+ evaluated to compute scroll distance. Example:
+ 'window.scrollTop' or '(function() { return crazyMath(); })()'.
+ speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
+ use_touch: Whether scrolling should be done with touch input.
+ """
+ self._RunAction(ScrollAction(
+ left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
+ direction=direction, distance=distance, distance_expr=distance_expr,
+ speed_in_pixels_per_second=speed_in_pixels_per_second,
+ use_touch=use_touch))
+
+ def ScrollElement(self, selector=None, text=None, element_function=None,
+ left_start_ratio=0.5, top_start_ratio=0.5,
+ direction='down', distance=None, distance_expr=None,
+ speed_in_pixels_per_second=800, use_touch=False):
+ """Perform scroll gesture on the element.
+
+ The element may be selected via selector, text, or element_function.
+ Only one of these arguments must be specified.
+
+ You may specify distance or distance_expr, but not both. If
+ neither is specified, the default scroll distance is variable
+ depending on direction (see scroll.js for full implementation).
+
+ Args:
+ selector: A CSS selector describing the element.
+ text: The element must contains this exact text.
+ element_function: A JavaScript function (as string) that is used
+ to retrieve the element. For example:
+ 'function() { return foo.element; }'.
+ left_start_ratio: The horizontal starting coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ the element.
+ top_start_ratio: The vertical starting coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ the element.
+ direction: The direction of scroll, either 'left', 'right',
+ 'up', or 'down'
+ distance: The distance to scroll (in pixel).
+ distance_expr: A JavaScript expression (as string) that can be
+ evaluated to compute scroll distance. Example:
+ 'window.scrollTop' or '(function() { return crazyMath(); })()'.
+ speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
+ use_touch: Whether scrolling should be done with touch input.
+ """
+ self._RunAction(ScrollAction(
+ selector=selector, text=text, element_function=element_function,
+ left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
+ direction=direction, distance=distance, distance_expr=distance_expr,
+ speed_in_pixels_per_second=speed_in_pixels_per_second,
+ use_touch=use_touch))
+
+ def ScrollBouncePage(self, left_start_ratio=0.5, top_start_ratio=0.5,
+ direction='down', distance=100,
+ overscroll=10, repeat_count=10,
+ speed_in_pixels_per_second=400):
+ """Perform scroll bounce gesture on the page.
+
+ This gesture scrolls the page by the number of pixels specified in
+ distance, in the given direction, followed by a scroll by
+ (distance + overscroll) pixels in the opposite direction.
+ The above gesture is repeated repeat_count times.
+
+ Args:
+ left_start_ratio: The horizontal starting coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ document.body.
+ top_start_ratio: The vertical starting coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ document.body.
+ direction: The direction of scroll, either 'left', 'right',
+ 'up', or 'down'
+ distance: The distance to scroll (in pixel).
+ overscroll: The number of additional pixels to scroll back, in
+ addition to the givendistance.
+ repeat_count: How often we want to repeat the full gesture.
+ speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
+ """
+ self._RunAction(ScrollBounceAction(
+ left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
+ direction=direction, distance=distance,
+ overscroll=overscroll, repeat_count=repeat_count,
+ speed_in_pixels_per_second=speed_in_pixels_per_second))
+
+ def ScrollBounceElement(self, selector=None, text=None, element_function=None,
+ left_start_ratio=0.5, top_start_ratio=0.5,
+ direction='down', distance=100,
+ overscroll=10, repeat_count=10,
+ speed_in_pixels_per_second=400):
+ """Perform scroll bounce gesture on the element.
+
+ This gesture scrolls on the element by the number of pixels specified in
+ distance, in the given direction, followed by a scroll by
+ (distance + overscroll) pixels in the opposite direction.
+ The above gesture is repeated repeat_count times.
+
+ Args:
+ selector: A CSS selector describing the element.
+ text: The element must contains this exact text.
+ element_function: A JavaScript function (as string) that is used
+ to retrieve the element. For example:
+ 'function() { return foo.element; }'.
+ left_start_ratio: The horizontal starting coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ document.body.
+ top_start_ratio: The vertical starting coordinate of the
+ gesture, as a ratio of the visible bounding rectangle for
+ document.body.
+ direction: The direction of scroll, either 'left', 'right',
+ 'up', or 'down'
+ distance: The distance to scroll (in pixel).
+ overscroll: The number of additional pixels to scroll back, in
+ addition to the givendistance.
+ repeat_count: How often we want to repeat the full gesture.
+ speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
+ """
+ self._RunAction(ScrollBounceAction(
+ selector=selector, text=text, element_function=element_function,
+ left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
+ direction=direction, distance=distance,
+ overscroll=overscroll, repeat_count=repeat_count,
+ speed_in_pixels_per_second=speed_in_pixels_per_second))
+
def SwipePage(self, left_start_ratio=0.5, top_start_ratio=0.5,
- direction='left', distance=100, speed=800):
+ direction='left', distance=100, speed_in_pixels_per_second=800):
"""Perform swipe gesture on the page.
Args:
direction: The direction of swipe, either 'left', 'right',
'up', or 'down'
distance: The distance to swipe (in pixel).
- speed: The speed of the gesture.
+ speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
"""
- self.RunAction(SwipeAction(
+ self._RunAction(SwipeAction(
left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
- direction=direction, distance=distance, speed=speed))
+ direction=direction, distance=distance,
+ speed_in_pixels_per_second=speed_in_pixels_per_second))
def SwipeElement(self, selector=None, text=None, element_function=None,
left_start_ratio=0.5, top_start_ratio=0.5,
- direction='left', distance=100, speed=800):
+ direction='left', distance=100,
+ speed_in_pixels_per_second=800):
"""Perform swipe gesture on the element.
+ The element may be selected via selector, text, or element_function.
+ Only one of these arguments must be specified.
+
Args:
selector: A CSS selector describing the element.
text: The element must contains this exact text.
direction: The direction of swipe, either 'left', 'right',
'up', or 'down'
distance: The distance to swipe (in pixel).
- speed: The speed of the gesture.
+ speed_in_pixels_per_second: The speed of the gesture (in pixels/s).
"""
- self.RunAction(SwipeAction(
+ self._RunAction(SwipeAction(
selector=selector, text=text, element_function=element_function,
left_start_ratio=left_start_ratio, top_start_ratio=top_start_ratio,
- direction=direction, distance=distance, speed=speed))
+ direction=direction, distance=distance,
+ speed_in_pixels_per_second=speed_in_pixels_per_second))
+
+ def PlayMedia(self, selector=None,
+ playing_event_timeout_in_seconds=0,
+ ended_event_timeout_in_seconds=0):
+ """Invokes the "play" action on media elements (such as video).
+
+ Args:
+ selector: A CSS selector describing the element. If none is
+ specified, play the first media element on the page. If the
+ selector matches more than 1 media element, all of them will
+ be played.
+ playing_event_timeout_in_seconds: Maximum waiting time for the "playing"
+ event (dispatched when the media begins to play) to be fired.
+ 0 means do not wait.
+ ended_event_timeout_in_seconds: Maximum waiting time for the "ended"
+ event (dispatched when playback completes) to be fired.
+ 0 means do not wait.
+
+ Raises:
+ TimeoutException: If the maximum waiting time is exceeded.
+ """
+ self._RunAction(PlayAction(
+ selector=selector,
+ playing_event_timeout_in_seconds=playing_event_timeout_in_seconds,
+ ended_event_timeout_in_seconds=ended_event_timeout_in_seconds))
+
+ def SeekMedia(self, seconds, selector=None, timeout_in_seconds=0,
+ log_time=True, label=''):
+ """Performs a seek action on media elements (such as video).
+
+ Args:
+ seconds: The media time to seek to.
+ selector: A CSS selector describing the element. If none is
+ specified, seek the first media element on the page. If the
+ selector matches more than 1 media element, all of them will
+ be seeked.
+ timeout_in_seconds: Maximum waiting time for the "seeked" event
+ (dispatched when the seeked operation completes) to be
+ fired. 0 means do not wait.
+ log_time: Whether to log the seek time for the perf
+ measurement. Useful when performing multiple seek.
+ label: A suffix string to name the seek perf measurement.
+
+ Raises:
+ TimeoutException: If the maximum waiting time is exceeded.
+ """
+ self._RunAction(SeekAction(
+ seconds=seconds, selector=selector,
+ timeout_in_seconds=timeout_in_seconds,
+ log_time=log_time, label=label))
+
+ def LoopMedia(self, loop_count, selector=None, timeout_in_seconds=None):
+ """Loops a media playback.
+
+ Args:
+ loop_count: The number of times to loop the playback.
+ selector: A CSS selector describing the element. If none is
+ specified, loop the first media element on the page. If the
+ selector matches more than 1 media element, all of them will
+ be looped.
+ timeout_in_seconds: Maximum waiting time for the looped playback to
+ complete. 0 means do not wait. None (the default) means to
+ wait loop_count * 60 seconds.
+
+ Raises:
+ TimeoutException: If the maximum waiting time is exceeded.
+ """
+ self._RunAction(LoopAction(
+ loop_count=loop_count, selector=selector,
+ timeout_in_seconds=timeout_in_seconds))
def ForceGarbageCollection(self):
"""Forces JavaScript garbage collection on the page."""
"""
raw_input("Interacting... Press Enter to continue.")
+ def RepaintContinuously(self, seconds):
+ """Continuously repaints the visible content.
+
+ It does this by requesting animation frames until the given number
+ of seconds have elapsed AND at least three RAFs have been
+ fired. Times out after max(60, self.seconds), if less than three
+ RAFs were fired."""
+ self._RunAction(RepaintContinuouslyAction(
+ seconds=0 if self._skip_waits else seconds))
class Interaction(object):
assert not self._started
self._started = True
self._action_runner.ExecuteJavaScript('console.time("%s");' %
- tir_module.TimelineInteractionRecord.GetJavaScriptMarker(
+ timeline_interaction_record.GetJavaScriptMarker(
self._label, self._flags))
def End(self):
assert self._started
self._started = False
self._action_runner.ExecuteJavaScript('console.timeEnd("%s");' %
- tir_module.TimelineInteractionRecord.GetJavaScriptMarker(
+ timeline_interaction_record.GetJavaScriptMarker(
self._label, self._flags))