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