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 import mock 5 import unittest 6 7 from telemetry.core import exceptions 8 from telemetry import decorators 9 from telemetry.internal.actions import action_runner as action_runner_module 10 from telemetry.internal.actions import page_action 11 from telemetry.testing import tab_test_case 12 from telemetry.timeline import chrome_trace_category_filter 13 from telemetry.timeline import model 14 from telemetry.timeline import tracing_config 15 from telemetry.web_perf import timeline_interaction_record as tir_module 16 17 import py_utils 18 19 20 class ActionRunnerInteractionTest(tab_test_case.TabTestCase): 21 22 def GetInteractionRecords(self, trace_data): 23 timeline_model = model.TimelineModel(trace_data) 24 renderer_thread = timeline_model.GetRendererThreadFromTabId(self._tab.id) 25 return [ 26 tir_module.TimelineInteractionRecord.FromAsyncEvent(e) 27 for e in renderer_thread.async_slices 28 if tir_module.IsTimelineInteractionRecord(e.name) 29 ] 30 31 def VerifyIssuingInteractionRecords(self, **interaction_kwargs): 32 action_runner = action_runner_module.ActionRunner(self._tab, 33 skip_waits=True) 34 self.Navigate('interaction_enabled_page.html') 35 action_runner.Wait(1) 36 config = tracing_config.TracingConfig() 37 config.chrome_trace_config.SetLowOverheadFilter() 38 config.enable_chrome_trace = True 39 self._browser.platform.tracing_controller.StartTracing(config) 40 with action_runner.CreateInteraction('InteractionName', 41 **interaction_kwargs): 42 pass 43 trace_data = self._browser.platform.tracing_controller.StopTracing() 44 45 records = self.GetInteractionRecords(trace_data) 46 self.assertEqual( 47 1, len(records), 48 'Failed to issue the interaction record on the tracing timeline.' 49 ' Trace data:\n%s' % repr(trace_data._raw_data)) 50 self.assertEqual('InteractionName', records[0].label) 51 for attribute_name in interaction_kwargs: 52 self.assertTrue(getattr(records[0], attribute_name)) 53 54 # Test disabled for android: crbug.com/437057 55 # Test disabled for linux: crbug.com/513874 56 @decorators.Disabled('android', 'chromeos', 'linux') 57 def testIssuingMultipleMeasurementInteractionRecords(self): 58 self.VerifyIssuingInteractionRecords(repeatable=True) 59 60 61 class ActionRunnerMeasureMemoryTest(tab_test_case.TabTestCase): 62 def setUp(self): 63 super(ActionRunnerMeasureMemoryTest, self).setUp() 64 self.action_runner = action_runner_module.ActionRunner(self._tab, 65 skip_waits=True) 66 self.Navigate('blank.html') 67 68 def testWithoutTracing(self): 69 with mock.patch.object(self._tab.browser, 'DumpMemory') as mock_method: 70 self.assertIsNone(self.action_runner.MeasureMemory()) 71 self.assertFalse(mock_method.called) # No-op with no tracing. 72 73 def _testWithTracing(self, deterministic_mode=False): 74 trace_memory = chrome_trace_category_filter.ChromeTraceCategoryFilter( 75 filter_string='-*,blink.console,disabled-by-default-memory-infra') 76 config = tracing_config.TracingConfig() 77 config.enable_chrome_trace = True 78 config.chrome_trace_config.SetCategoryFilter(trace_memory) 79 self._browser.platform.tracing_controller.StartTracing(config) 80 try: 81 dump_id = self.action_runner.MeasureMemory(deterministic_mode) 82 finally: 83 trace_data = self._browser.platform.tracing_controller.StopTracing() 84 85 # If successful, i.e. we haven't balied out due to an exception, check 86 # that we can find our dump in the trace. 87 self.assertIsNotNone(dump_id) 88 timeline_model = model.TimelineModel(trace_data) 89 dump_ids = (d.dump_id for d in timeline_model.IterGlobalMemoryDumps()) 90 self.assertIn(dump_id, dump_ids) 91 92 # TODO(perezju): Enable when reference browser is >= M53 93 # https://github.com/catapult-project/catapult/issues/2610 94 @decorators.Disabled('reference') 95 def testDeterministicMode(self): 96 self._testWithTracing(deterministic_mode=True) 97 98 # TODO(perezju): Enable when reference browser is >= M53 99 # https://github.com/catapult-project/catapult/issues/2610 100 @decorators.Disabled('reference') 101 def testRealisticMode(self): 102 with mock.patch.object( 103 self.action_runner, 'ForceGarbageCollection') as mock_method: 104 self._testWithTracing(deterministic_mode=False) 105 self.assertFalse(mock_method.called) # No forced GC in "realistic" mode. 106 107 def testWithFailedDump(self): 108 with mock.patch.object(self._tab.browser, 'DumpMemory') as mock_method: 109 mock_method.return_value = False # Dump fails! 110 with self.assertRaises(exceptions.Error): 111 self._testWithTracing() 112 113 114 class ActionRunnerTest(tab_test_case.TabTestCase): 115 def testExecuteJavaScript(self): 116 action_runner = action_runner_module.ActionRunner(self._tab, 117 skip_waits=True) 118 self.Navigate('blank.html') 119 action_runner.ExecuteJavaScript('var testing = 42;') 120 self.assertEqual(42, self._tab.EvaluateJavaScript('testing')) 121 122 def testWaitForNavigate(self): 123 self.Navigate('page_with_link.html') 124 action_runner = action_runner_module.ActionRunner(self._tab, 125 skip_waits=True) 126 action_runner.ClickElement('#clickme') 127 action_runner.WaitForNavigate() 128 129 self.assertTrue(self._tab.EvaluateJavaScript( 130 'document.readyState == "interactive" || ' 131 'document.readyState == "complete"')) 132 self.assertEqual( 133 self._tab.EvaluateJavaScript('document.location.pathname;'), 134 '/blank.html') 135 136 def testNavigateBack(self): 137 action_runner = action_runner_module.ActionRunner(self._tab, 138 skip_waits=True) 139 self.Navigate('page_with_link.html') 140 action_runner.WaitForJavaScriptCondition( 141 'document.location.pathname === "/page_with_link.html"') 142 143 # Test that after 3 navigations & 3 back navs, we have to be back at the 144 # initial page 145 self.Navigate('page_with_swipeables.html') 146 action_runner.WaitForJavaScriptCondition( 147 'document.location.pathname === "/page_with_swipeables.html"') 148 149 self.Navigate('blank.html') 150 action_runner.WaitForJavaScriptCondition( 151 'document.location.pathname === "/blank.html"') 152 153 self.Navigate('page_with_swipeables.html') 154 action_runner.WaitForJavaScriptCondition( 155 'document.location.pathname === "/page_with_swipeables.html"') 156 157 action_runner.NavigateBack() 158 action_runner.WaitForJavaScriptCondition( 159 'document.location.pathname === "/blank.html"') 160 161 action_runner.NavigateBack() 162 action_runner.WaitForJavaScriptCondition( 163 'document.location.pathname === "/page_with_swipeables.html"') 164 165 action_runner.NavigateBack() 166 action_runner.WaitForJavaScriptCondition( 167 'document.location.pathname === "/page_with_link.html"') 168 169 def testWait(self): 170 action_runner = action_runner_module.ActionRunner(self._tab) 171 self.Navigate('blank.html') 172 173 action_runner.ExecuteJavaScript( 174 'window.setTimeout(function() { window.testing = 101; }, 50);') 175 action_runner.Wait(0.1) 176 self.assertEqual(101, self._tab.EvaluateJavaScript('window.testing')) 177 178 action_runner.ExecuteJavaScript( 179 'window.setTimeout(function() { window.testing = 102; }, 100);') 180 action_runner.Wait(0.2) 181 self.assertEqual(102, self._tab.EvaluateJavaScript('window.testing')) 182 183 def testWaitForJavaScriptCondition(self): 184 action_runner = action_runner_module.ActionRunner(self._tab, 185 skip_waits=True) 186 self.Navigate('blank.html') 187 188 action_runner.ExecuteJavaScript('window.testing = 219;') 189 action_runner.WaitForJavaScriptCondition( 190 'window.testing == 219', timeout=0.1) 191 action_runner.ExecuteJavaScript( 192 'window.setTimeout(function() { window.testing = 220; }, 50);') 193 action_runner.WaitForJavaScriptCondition( 194 'window.testing == 220', timeout=0.1) 195 self.assertEqual(220, self._tab.EvaluateJavaScript('window.testing')) 196 197 def testWaitForJavaScriptCondition_returnsValue(self): 198 action_runner = action_runner_module.ActionRunner(self._tab, 199 skip_waits=True) 200 self.Navigate('blank.html') 201 202 action_runner.ExecuteJavaScript('window.testing = 0;') 203 action_runner.WaitForJavaScriptCondition( 204 'window.testing == 0', timeout=0.1) 205 action_runner.ExecuteJavaScript( 206 'window.setTimeout(function() { window.testing = 42; }, 50);') 207 self.assertEqual( 208 42, 209 action_runner.WaitForJavaScriptCondition('window.testing', timeout=10)) 210 211 def testWaitForElement(self): 212 action_runner = action_runner_module.ActionRunner(self._tab, 213 skip_waits=True) 214 self.Navigate('blank.html') 215 216 action_runner.ExecuteJavaScript( 217 '(function() {' 218 ' var el = document.createElement("div");' 219 ' el.id = "test1";' 220 ' el.textContent = "foo";' 221 ' document.body.appendChild(el);' 222 '})()') 223 action_runner.WaitForElement('#test1', timeout_in_seconds=0.1) 224 action_runner.WaitForElement(text='foo', timeout_in_seconds=0.1) 225 action_runner.WaitForElement( 226 element_function='document.getElementById("test1")') 227 action_runner.ExecuteJavaScript( 228 'window.setTimeout(function() {' 229 ' var el = document.createElement("div");' 230 ' el.id = "test2";' 231 ' document.body.appendChild(el);' 232 '}, 50)') 233 action_runner.WaitForElement('#test2', timeout_in_seconds=0.1) 234 action_runner.ExecuteJavaScript( 235 'window.setTimeout(function() {' 236 ' document.getElementById("test2").textContent = "bar";' 237 '}, 50)') 238 action_runner.WaitForElement(text='bar', timeout_in_seconds=0.1) 239 action_runner.ExecuteJavaScript( 240 'window.setTimeout(function() {' 241 ' var el = document.createElement("div");' 242 ' el.id = "test3";' 243 ' document.body.appendChild(el);' 244 '}, 50)') 245 action_runner.WaitForElement( 246 element_function='document.getElementById("test3")') 247 248 def testWaitForElementWithWrongText(self): 249 action_runner = action_runner_module.ActionRunner(self._tab, 250 skip_waits=True) 251 self.Navigate('blank.html') 252 253 action_runner.ExecuteJavaScript( 254 '(function() {' 255 ' var el = document.createElement("div");' 256 ' el.id = "test1";' 257 ' el.textContent = "foo";' 258 ' document.body.appendChild(el);' 259 '})()') 260 action_runner.WaitForElement('#test1', timeout_in_seconds=0.2) 261 def WaitForElement(): 262 action_runner.WaitForElement(text='oo', timeout_in_seconds=0.2) 263 self.assertRaises(py_utils.TimeoutException, WaitForElement) 264 265 def testClickElement(self): 266 self.Navigate('page_with_clickables.html') 267 action_runner = action_runner_module.ActionRunner(self._tab, 268 skip_waits=True) 269 270 action_runner.ExecuteJavaScript('valueSettableByTest = 1;') 271 action_runner.ClickElement('#test') 272 self.assertEqual(1, action_runner.EvaluateJavaScript('valueToTest')) 273 274 action_runner.ExecuteJavaScript('valueSettableByTest = 2;') 275 action_runner.ClickElement(text='Click/tap me') 276 self.assertEqual(2, action_runner.EvaluateJavaScript('valueToTest')) 277 278 action_runner.ExecuteJavaScript('valueSettableByTest = 3;') 279 action_runner.ClickElement( 280 element_function='document.body.firstElementChild;') 281 self.assertEqual(3, action_runner.EvaluateJavaScript('valueToTest')) 282 283 def WillFail(): 284 action_runner.ClickElement('#notfound') 285 self.assertRaises(exceptions.EvaluateException, WillFail) 286 287 @decorators.Disabled('android', 'debug', # crbug.com/437068 288 'chromeos', # crbug.com/483212 289 'win') # catapult/issues/2282 290 def testTapElement(self): 291 self.Navigate('page_with_clickables.html') 292 action_runner = action_runner_module.ActionRunner(self._tab, 293 skip_waits=True) 294 295 action_runner.ExecuteJavaScript('valueSettableByTest = 1;') 296 action_runner.TapElement('#test') 297 self.assertEqual(1, action_runner.EvaluateJavaScript('valueToTest')) 298 299 action_runner.ExecuteJavaScript('valueSettableByTest = 2;') 300 action_runner.TapElement(text='Click/tap me') 301 self.assertEqual(2, action_runner.EvaluateJavaScript('valueToTest')) 302 303 action_runner.ExecuteJavaScript('valueSettableByTest = 3;') 304 action_runner.TapElement( 305 element_function='document.body.firstElementChild') 306 self.assertEqual(3, action_runner.EvaluateJavaScript('valueToTest')) 307 308 def WillFail(): 309 action_runner.TapElement('#notfound') 310 self.assertRaises(exceptions.EvaluateException, WillFail) 311 312 # https://github.com/catapult-project/catapult/issues/3099 313 @decorators.Disabled('android') 314 def testScrollToElement(self): 315 self.Navigate('page_with_swipeables.html') 316 action_runner = action_runner_module.ActionRunner(self._tab, 317 skip_waits=True) 318 319 off_screen_element = 'document.querySelectorAll("#off-screen")[0]' 320 top_bottom_element = 'document.querySelector("#top-bottom")' 321 322 def viewport_comparator(element): 323 return action_runner.EvaluateJavaScript(''' 324 (function(elem) { 325 var rect = elem.getBoundingClientRect(); 326 327 if (rect.bottom < 0) { 328 // The bottom of the element is above the viewport. 329 return -1; 330 } 331 if (rect.top - window.innerHeight > 0) { 332 // rect.top provides the pixel offset of the element from the 333 // top of the page. Because that exceeds the viewport's height, 334 // we know that the element is below the viewport. 335 return 1; 336 } 337 return 0; 338 })({{ @element }}); 339 ''', element=element) 340 341 342 self.assertEqual(viewport_comparator(off_screen_element), 1) 343 action_runner.ScrollPageToElement(selector='#off-screen', 344 speed_in_pixels_per_second=5000) 345 self.assertEqual(viewport_comparator(off_screen_element), 0) 346 347 self.assertEqual(viewport_comparator(top_bottom_element), -1) 348 action_runner.ScrollPageToElement(selector='#top-bottom', 349 container_selector='body', 350 speed_in_pixels_per_second=5000) 351 self.assertEqual(viewport_comparator(top_bottom_element), 0) 352 353 @decorators.Disabled('android', # crbug.com/437065. 354 'chromeos') # crbug.com/483212. 355 def testScroll(self): 356 if not page_action.IsGestureSourceTypeSupported( 357 self._tab, 'touch'): 358 return 359 360 self.Navigate('page_with_swipeables.html') 361 action_runner = action_runner_module.ActionRunner(self._tab, 362 skip_waits=True) 363 364 action_runner.ScrollElement( 365 selector='#left-right', direction='right', left_start_ratio=0.9) 366 self.assertTrue(action_runner.EvaluateJavaScript( 367 'document.querySelector("#left-right").scrollLeft') > 75) 368 action_runner.ScrollElement( 369 selector='#top-bottom', direction='down', top_start_ratio=0.9) 370 self.assertTrue(action_runner.EvaluateJavaScript( 371 'document.querySelector("#top-bottom").scrollTop') > 75) 372 373 action_runner.ScrollPage(direction='right', left_start_ratio=0.9, 374 distance=100) 375 self.assertTrue(action_runner.EvaluateJavaScript( 376 '(document.scrollingElement || document.body).scrollLeft') > 75) 377 378 @decorators.Disabled('android', # crbug.com/437065. 379 'chromeos') # crbug.com/483212. 380 def testSwipe(self): 381 if not page_action.IsGestureSourceTypeSupported( 382 self._tab, 'touch'): 383 return 384 385 self.Navigate('page_with_swipeables.html') 386 action_runner = action_runner_module.ActionRunner(self._tab, 387 skip_waits=True) 388 389 action_runner.SwipeElement( 390 selector='#left-right', direction='left', left_start_ratio=0.9) 391 self.assertTrue(action_runner.EvaluateJavaScript( 392 'document.querySelector("#left-right").scrollLeft') > 75) 393 action_runner.SwipeElement( 394 selector='#top-bottom', direction='up', top_start_ratio=0.9) 395 self.assertTrue(action_runner.EvaluateJavaScript( 396 'document.querySelector("#top-bottom").scrollTop') > 75) 397 398 action_runner.SwipePage(direction='left', left_start_ratio=0.9) 399 self.assertTrue(action_runner.EvaluateJavaScript( 400 '(document.scrollingElement || document.body).scrollLeft') > 75) 401 402 def testWaitForNetworkQuiescenceSmoke(self): 403 self.Navigate('blank.html') 404 action_runner = action_runner_module.ActionRunner(self._tab) 405 action_runner.WaitForNetworkQuiescence() 406 self.assertEqual( 407 self._tab.EvaluateJavaScript('document.location.pathname;'), 408 '/blank.html') 409 410 def testEnterText(self): 411 self.Navigate('blank.html') 412 self._tab.ExecuteJavaScript( 413 '(function() {' 414 ' var elem = document.createElement("textarea");' 415 ' document.body.appendChild(elem);' 416 ' elem.focus();' 417 '})();') 418 419 action_runner = action_runner_module.ActionRunner(self._tab, 420 skip_waits=True) 421 action_runner.EnterText('That is boring') # That is boring|. 422 action_runner.PressKey('Home') # |That is boring. 423 action_runner.PressKey('ArrowRight', repeat_count=2) # Th|at is boring. 424 action_runner.PressKey('Delete', repeat_count=2) # Th| is boring. 425 action_runner.EnterText('is') # This| is boring. 426 action_runner.PressKey('End') # This is boring|. 427 action_runner.PressKey('ArrowLeft', repeat_count=3) # This is bor|ing. 428 action_runner.PressKey('Backspace', repeat_count=3) # This is |ing. 429 action_runner.EnterText('interest') # This is interest|ing. 430 431 # Check that the contents of the textarea is correct. It might take a second 432 # until all keystrokes have been handled by the browser (crbug.com/630017). 433 self._tab.WaitForJavaScriptCondition( 434 'document.querySelector("textarea").value === "This is interesting"', 435 timeout=1) 436 437 438 class InteractionTest(unittest.TestCase): 439 440 def setUp(self): 441 self.mock_action_runner = mock.Mock(action_runner_module.ActionRunner) 442 443 def expected_js_call(method): 444 return mock.call.ExecuteJavaScript( 445 '%s({{ marker }});' % method, marker='Interaction.ABC') 446 447 self.expected_calls = [ 448 expected_js_call('console.time'), 449 expected_js_call('console.timeEnd')] 450 451 def testIssuingInteractionRecordCommand(self): 452 with action_runner_module.Interaction( 453 self.mock_action_runner, label='ABC', flags=[]): 454 pass 455 self.assertEqual(self.expected_calls, self.mock_action_runner.mock_calls) 456 457 def testExceptionRaisedInWithInteraction(self): 458 class FooException(Exception): 459 pass 460 # Test that the Foo exception raised in the with block is propagated to the 461 # caller. 462 with self.assertRaises(FooException): 463 with action_runner_module.Interaction( 464 self.mock_action_runner, label='ABC', flags=[]): 465 raise FooException() 466 467 # Test that the end console.timeEnd(...) isn't called because exception was 468 # raised. 469 self.assertEqual( 470 self.expected_calls[:1], self.mock_action_runner.mock_calls) 471