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