test_click.py (17391B)
1 # This Source Code Form is subject to the terms of the Mozilla Public 2 # License, v. 2.0. If a copy of the MPL was not distributed with this 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5 from urllib.parse import quote 6 7 from marionette_driver import By, errors 8 9 from marionette_harness import ( 10 MarionetteTestCase, 11 WindowManagerMixin, 12 ) 13 14 15 def inline(doc): 16 return "data:text/html;charset=utf-8,{}".format(quote(doc)) 17 18 19 # The <a> element in the following HTML is not interactable because it 20 # is hidden by an overlay when scrolled into the top of the viewport. 21 # It should be interactable when scrolled in at the bottom of the 22 # viewport. 23 fixed_overlay = inline( 24 """ 25 <style> 26 * { margin: 0; padding: 0; } 27 body { height: 300vh } 28 div, a { display: block } 29 div { 30 background-color: pink; 31 position: fixed; 32 width: 100%; 33 height: 40px; 34 top: 0; 35 } 36 a { 37 margin-top: 1000px; 38 } 39 </style> 40 41 <div>overlay</div> 42 <a href=#>link</a> 43 44 <script> 45 window.clicked = false; 46 47 let link = document.querySelector("a"); 48 link.addEventListener("click", () => window.clicked = true); 49 </script> 50 """ 51 ) 52 53 54 obscured_overlay = inline( 55 """ 56 <style> 57 * { margin: 0; padding: 0; } 58 body { height: 100vh } 59 #overlay { 60 background-color: pink; 61 position: absolute; 62 width: 100%; 63 height: 100%; 64 } 65 </style> 66 67 <div id=overlay></div> 68 <a id=obscured href=#>link</a> 69 70 <script> 71 window.clicked = false; 72 73 let link = document.querySelector("#obscured"); 74 link.addEventListener("click", () => window.clicked = true); 75 </script> 76 """ 77 ) 78 79 80 class ClickBaseTestCase(WindowManagerMixin, MarionetteTestCase): 81 def setUp(self): 82 super(ClickBaseTestCase, self).setUp() 83 84 # Always use a blank new tab for an empty history 85 self.new_tab = self.open_tab() 86 self.marionette.switch_to_window(self.new_tab) 87 88 def tearDown(self): 89 self.close_all_tabs() 90 91 def test_click(self): 92 self.marionette.navigate( 93 inline( 94 """ 95 <button>click me</button> 96 <script> 97 window.clicks = 0; 98 let button = document.querySelector("button"); 99 button.addEventListener("click", () => window.clicks++); 100 </script> 101 """ 102 ) 103 ) 104 button = self.marionette.find_element(By.TAG_NAME, "button") 105 button.click() 106 self.assertEqual( 107 1, self.marionette.execute_script("return window.clicks", sandbox=None) 108 ) 109 110 def test_click_number_link(self): 111 test_html = self.marionette.absolute_url("clicks.html") 112 self.marionette.navigate(test_html) 113 self.marionette.find_element(By.LINK_TEXT, "333333").click() 114 self.marionette.find_element(By.ID, "testDiv") 115 self.assertEqual(self.marionette.title, "Marionette Test") 116 117 def test_clicking_an_element_that_is_not_displayed_raises(self): 118 self.marionette.navigate( 119 inline( 120 """ 121 <p hidden>foo</p> 122 """ 123 ) 124 ) 125 126 with self.assertRaises(errors.ElementNotInteractableException): 127 self.marionette.find_element(By.TAG_NAME, "p").click() 128 129 def test_clicking_on_a_multiline_link(self): 130 test_html = self.marionette.absolute_url("clicks.html") 131 self.marionette.navigate(test_html) 132 self.marionette.find_element(By.ID, "overflowLink").click() 133 self.marionette.find_element(By.ID, "testDiv") 134 self.assertEqual(self.marionette.title, "Marionette Test") 135 136 def test_click_mathml(self): 137 self.marionette.navigate( 138 inline( 139 """ 140 <math><mtext id="target">click me</mtext></math> 141 <script> 142 window.clicks = 0; 143 let mtext = document.getElementById("target"); 144 mtext.addEventListener("click", () => window.clicks++); 145 </script> 146 """ 147 ) 148 ) 149 mtext = self.marionette.find_element(By.ID, "target") 150 mtext.click() 151 self.assertEqual( 152 1, self.marionette.execute_script("return window.clicks", sandbox=None) 153 ) 154 155 def test_scroll_into_view_near_end(self): 156 self.marionette.navigate(fixed_overlay) 157 link = self.marionette.find_element(By.TAG_NAME, "a") 158 link.click() 159 self.assertTrue( 160 self.marionette.execute_script("return window.clicked", sandbox=None) 161 ) 162 163 def test_inclusive_descendant(self): 164 self.marionette.navigate( 165 inline( 166 """ 167 <select multiple> 168 <option>first 169 <option>second 170 <option>third 171 </select>""" 172 ) 173 ) 174 select = self.marionette.find_element(By.TAG_NAME, "select") 175 176 # This tests that the pointer-interactability test does not 177 # cause an ElementClickInterceptedException. 178 # 179 # At a <select multiple>'s in-view centre point, you might 180 # find a fully rendered <option>. Marionette should test that 181 # the paint tree at this point _contains_ <option>, not that the 182 # first element of the paint tree is _equal_ to <select>. 183 select.click() 184 185 # Bug 1413821 - Click does not select an option on Android 186 if self.marionette.session_capabilities["browserName"] != "fennec": 187 self.assertNotEqual(select.get_property("selectedIndex"), -1) 188 189 def test_container_is_select(self): 190 self.marionette.navigate( 191 inline( 192 """ 193 <select> 194 <option>foo</option> 195 </select>""" 196 ) 197 ) 198 option = self.marionette.find_element(By.TAG_NAME, "option") 199 option.click() 200 self.assertTrue(option.get_property("selected")) 201 202 def test_container_is_button(self): 203 self.marionette.navigate( 204 inline( 205 """ 206 <button onclick="window.clicked = true;"> 207 <span><em>foo</em></span> 208 </button>""" 209 ) 210 ) 211 span = self.marionette.find_element(By.TAG_NAME, "span") 212 span.click() 213 self.assertTrue( 214 self.marionette.execute_script("return window.clicked", sandbox=None) 215 ) 216 217 def test_container_element_outside_view(self): 218 self.marionette.navigate( 219 inline( 220 """ 221 <select style="margin-top: 100vh"> 222 <option>foo</option> 223 </select>""" 224 ) 225 ) 226 option = self.marionette.find_element(By.TAG_NAME, "option") 227 option.click() 228 self.assertTrue(option.get_property("selected")) 229 230 def test_table_tr(self): 231 self.marionette.navigate( 232 inline( 233 """ 234 <table> 235 <tr><td onclick="window.clicked = true;"> 236 foo 237 </td></tr> 238 </table>""" 239 ) 240 ) 241 tr = self.marionette.find_element(By.TAG_NAME, "tr") 242 tr.click() 243 self.assertTrue( 244 self.marionette.execute_script("return window.clicked", sandbox=None) 245 ) 246 247 248 class TestLegacyClick(ClickBaseTestCase): 249 """Uses legacy Selenium element displayedness checks.""" 250 251 def setUp(self): 252 super(TestLegacyClick, self).setUp() 253 254 self.marionette.delete_session() 255 self.marionette.start_session({"moz:webdriverClick": False}) 256 257 258 class TestClick(ClickBaseTestCase): 259 """Uses WebDriver specification compatible element interactability checks.""" 260 261 def setUp(self): 262 super(TestClick, self).setUp() 263 264 self.marionette.delete_session() 265 self.marionette.start_session({"moz:webdriverClick": True}) 266 267 def test_click_element_obscured_by_absolute_positioned_element(self): 268 self.marionette.navigate(obscured_overlay) 269 overlay = self.marionette.find_element(By.ID, "overlay") 270 obscured = self.marionette.find_element(By.ID, "obscured") 271 272 overlay.click() 273 with self.assertRaises(errors.ElementClickInterceptedException): 274 obscured.click() 275 276 def test_centre_outside_viewport_vertically(self): 277 self.marionette.navigate( 278 inline( 279 """ 280 <style> 281 * { margin: 0; padding: 0; } 282 div { 283 display: block; 284 position: absolute; 285 background-color: blue; 286 width: 200px; 287 height: 200px; 288 289 /* move centre point off viewport vertically */ 290 top: -105px; 291 } 292 </style> 293 294 <div onclick="window.clicked = true;"></div>""" 295 ) 296 ) 297 298 self.marionette.find_element(By.TAG_NAME, "div").click() 299 self.assertTrue( 300 self.marionette.execute_script("return window.clicked", sandbox=None) 301 ) 302 303 def test_centre_outside_viewport_horizontally(self): 304 self.marionette.navigate( 305 inline( 306 """ 307 <style> 308 * { margin: 0; padding: 0; } 309 div { 310 display: block; 311 position: absolute; 312 background-color: blue; 313 width: 200px; 314 height: 200px; 315 316 /* move centre point off viewport horizontally */ 317 left: -105px; 318 } 319 </style> 320 321 <div onclick="window.clicked = true;"></div>""" 322 ) 323 ) 324 325 self.marionette.find_element(By.TAG_NAME, "div").click() 326 self.assertTrue( 327 self.marionette.execute_script("return window.clicked", sandbox=None) 328 ) 329 330 def test_centre_outside_viewport(self): 331 self.marionette.navigate( 332 inline( 333 """ 334 <style> 335 * { margin: 0; padding: 0; } 336 div { 337 display: block; 338 position: absolute; 339 background-color: blue; 340 width: 200px; 341 height: 200px; 342 343 /* move centre point off viewport */ 344 left: -105px; 345 top: -105px; 346 } 347 </style> 348 349 <div onclick="window.clicked = true;"></div>""" 350 ) 351 ) 352 353 self.marionette.find_element(By.TAG_NAME, "div").click() 354 self.assertTrue( 355 self.marionette.execute_script("return window.clicked", sandbox=None) 356 ) 357 358 def test_css_transforms(self): 359 self.marionette.navigate( 360 inline( 361 """ 362 <style> 363 * { margin: 0; padding: 0; } 364 div { 365 display: block; 366 background-color: blue; 367 width: 200px; 368 height: 200px; 369 370 transform: translateX(-105px); 371 } 372 </style> 373 374 <div onclick="window.clicked = true;"></div>""" 375 ) 376 ) 377 378 self.marionette.find_element(By.TAG_NAME, "div").click() 379 self.assertTrue( 380 self.marionette.execute_script("return window.clicked", sandbox=None) 381 ) 382 383 def test_input_file(self): 384 self.marionette.navigate(inline("<input type=file>")) 385 with self.assertRaises(errors.InvalidArgumentException): 386 self.marionette.find_element(By.TAG_NAME, "input").click() 387 388 def test_obscured_element(self): 389 self.marionette.navigate(obscured_overlay) 390 overlay = self.marionette.find_element(By.ID, "overlay") 391 obscured = self.marionette.find_element(By.ID, "obscured") 392 393 overlay.click() 394 with self.assertRaises(errors.ElementClickInterceptedException): 395 obscured.click() 396 self.assertFalse( 397 self.marionette.execute_script("return window.clicked", sandbox=None) 398 ) 399 400 def test_pointer_events_none(self): 401 self.marionette.navigate( 402 inline( 403 """ 404 <button style="pointer-events: none">click me</button> 405 <script> 406 window.clicked = false; 407 let button = document.querySelector("button"); 408 button.addEventListener("click", () => window.clicked = true); 409 </script> 410 """ 411 ) 412 ) 413 button = self.marionette.find_element(By.TAG_NAME, "button") 414 self.assertEqual("none", button.value_of_css_property("pointer-events")) 415 416 with self.assertRaisesRegex( 417 errors.ElementClickInterceptedException, 418 "does not have pointer events enabled", 419 ): 420 button.click() 421 self.assertFalse( 422 self.marionette.execute_script("return window.clicked", sandbox=None) 423 ) 424 425 def test_prevent_default(self): 426 self.marionette.navigate( 427 inline( 428 """ 429 <button>click me</button> 430 <script> 431 let button = document.querySelector("button"); 432 button.addEventListener("click", event => event.preventDefault()); 433 </script> 434 """ 435 ) 436 ) 437 button = self.marionette.find_element(By.TAG_NAME, "button") 438 # should not time out 439 button.click() 440 441 def test_stop_propagation(self): 442 self.marionette.navigate( 443 inline( 444 """ 445 <button>click me</button> 446 <script> 447 let button = document.querySelector("button"); 448 button.addEventListener("click", event => event.stopPropagation()); 449 </script> 450 """ 451 ) 452 ) 453 button = self.marionette.find_element(By.TAG_NAME, "button") 454 # should not time out 455 button.click() 456 457 def test_stop_immediate_propagation(self): 458 self.marionette.navigate( 459 inline( 460 """ 461 <button>click me</button> 462 <script> 463 let button = document.querySelector("button"); 464 button.addEventListener("click", event => event.stopImmediatePropagation()); 465 </script> 466 """ 467 ) 468 ) 469 button = self.marionette.find_element(By.TAG_NAME, "button") 470 # should not time out 471 button.click() 472 473 474 class TestClickNavigation(WindowManagerMixin, MarionetteTestCase): 475 def setUp(self): 476 super(TestClickNavigation, self).setUp() 477 478 # Always use a blank new tab for an empty history 479 self.new_tab = self.open_tab() 480 self.marionette.switch_to_window(self.new_tab) 481 482 self.test_page = self.marionette.absolute_url("clicks.html") 483 self.marionette.navigate(self.test_page) 484 485 def tearDown(self): 486 self.close_all_tabs() 487 488 def close_notification(self): 489 try: 490 with self.marionette.using_context("chrome"): 491 elem = self.marionette.find_element( 492 By.CSS_SELECTOR, 493 "#notification-popup popupnotification .popup-notification-closebutton", 494 ) 495 elem.click() 496 except errors.NoSuchElementException: 497 pass 498 499 def test_click_link_page_load(self): 500 self.marionette.find_element(By.LINK_TEXT, "333333").click() 501 self.assertNotEqual(self.marionette.get_url(), self.test_page) 502 self.assertEqual(self.marionette.title, "Marionette Test") 503 504 def test_click_link_anchor(self): 505 self.marionette.find_element(By.ID, "anchor").click() 506 self.assertEqual(self.marionette.get_url(), "{}#".format(self.test_page)) 507 508 def test_click_link_install_addon(self): 509 try: 510 self.marionette.find_element(By.ID, "install-addon").click() 511 self.assertEqual(self.marionette.get_url(), self.test_page) 512 finally: 513 self.close_notification() 514 515 def test_click_no_link(self): 516 self.marionette.find_element(By.ID, "links").click() 517 self.assertEqual(self.marionette.get_url(), self.test_page) 518 519 def test_click_option_navigate(self): 520 self.marionette.find_element(By.ID, "option").click() 521 self.marionette.find_element(By.ID, "delay") 522 523 def test_click_remoteness_change(self): 524 self.marionette.navigate("about:robots") 525 self.marionette.navigate(self.test_page) 526 self.marionette.find_element(By.ID, "anchor") 527 528 self.marionette.navigate("about:robots") 529 with self.assertRaises(errors.NoSuchElementException): 530 self.marionette.find_element(By.ID, "anchor") 531 532 self.marionette.go_back() 533 self.marionette.find_element(By.ID, "anchor") 534 535 self.marionette.find_element(By.ID, "history-back").click() 536 with self.assertRaises(errors.NoSuchElementException): 537 self.marionette.find_element(By.ID, "anchor") 538 539 540 class TestClickCloseContext(WindowManagerMixin, MarionetteTestCase): 541 def setUp(self): 542 super(TestClickCloseContext, self).setUp() 543 544 self.test_page = self.marionette.absolute_url("clicks.html") 545 546 def tearDown(self): 547 self.close_all_tabs() 548 549 super(TestClickCloseContext, self).tearDown() 550 551 def test_click_close_tab(self): 552 new_tab = self.open_tab() 553 self.marionette.switch_to_window(new_tab) 554 555 self.marionette.navigate(self.test_page) 556 self.marionette.find_element(By.ID, "close-window").click() 557 558 def test_click_close_window(self): 559 new_tab = self.open_window() 560 self.marionette.switch_to_window(new_tab) 561 562 self.marionette.navigate(self.test_page) 563 self.marionette.find_element(By.ID, "close-window").click()