test_execute_script.py (19954B)
1 import os 2 import sys 3 from urllib.parse import quote 4 5 from marionette_driver import By, errors 6 from marionette_driver.marionette import Alert, WebElement 7 from marionette_driver.wait import Wait 8 from marionette_harness import MarionetteTestCase, WindowManagerMixin 9 10 # add this directory to the path 11 sys.path.append(os.path.dirname(__file__)) 12 13 from chrome_handler_mixin import ChromeHandlerMixin 14 15 16 def inline(doc): 17 return "data:text/html;charset=utf-8,{}".format(quote(doc)) 18 19 20 elements = inline("<p>foo</p> <p>bar</p>") 21 22 shadow_dom = """ 23 <style> 24 custom-checkbox-element { 25 display:block; width:20px; height:20px; 26 } 27 </style> 28 <custom-checkbox-element></custom-checkbox-element> 29 <script> 30 customElements.define('custom-checkbox-element', 31 class extends HTMLElement { 32 constructor() { 33 super(); 34 this.attachShadow({mode: '%s'}).innerHTML = ` 35 <div><input type="checkbox"/></div> 36 `; 37 } 38 }); 39 </script>""" 40 41 42 globals = set( 43 [ 44 "atob", 45 "Audio", 46 "btoa", 47 "document", 48 "navigator", 49 "URL", 50 "window", 51 ] 52 ) 53 54 55 class TestExecuteContent(MarionetteTestCase): 56 def alert_present(self): 57 try: 58 Alert(self.marionette).text 59 return True 60 except errors.NoAlertPresentException: 61 return False 62 63 def wait_for_alert_closed(self, timeout=None): 64 Wait(self.marionette, timeout=timeout).until(lambda _: not self.alert_present()) 65 66 def tearDown(self): 67 if self.alert_present(): 68 alert = self.marionette.switch_to_alert() 69 alert.dismiss() 70 self.wait_for_alert_closed() 71 72 def assert_is_defined(self, property, sandbox="default"): 73 self.assertTrue( 74 self.marionette.execute_script( 75 "return typeof arguments[0] != 'undefined'", [property], sandbox=sandbox 76 ), 77 "property {} is undefined".format(property), 78 ) 79 80 def assert_is_web_element(self, element): 81 self.assertIsInstance(element, WebElement) 82 83 def test_return_number(self): 84 self.assertEqual(1, self.marionette.execute_script("return 1")) 85 self.assertEqual(1.5, self.marionette.execute_script("return 1.5")) 86 87 def test_return_boolean(self): 88 self.assertTrue(self.marionette.execute_script("return true")) 89 90 def test_return_string(self): 91 self.assertEqual("foo", self.marionette.execute_script("return 'foo'")) 92 93 def test_return_array(self): 94 self.assertEqual([1, 2], self.marionette.execute_script("return [1, 2]")) 95 self.assertEqual( 96 [1.25, 1.75], self.marionette.execute_script("return [1.25, 1.75]") 97 ) 98 self.assertEqual( 99 [True, False], self.marionette.execute_script("return [true, false]") 100 ) 101 self.assertEqual( 102 ["foo", "bar"], self.marionette.execute_script("return ['foo', 'bar']") 103 ) 104 self.assertEqual( 105 [1, 1.5, True, "foo"], 106 self.marionette.execute_script("return [1, 1.5, true, 'foo']"), 107 ) 108 self.assertEqual([1, [2]], self.marionette.execute_script("return [1, [2]]")) 109 110 def test_return_object(self): 111 self.assertEqual({"foo": 1}, self.marionette.execute_script("return {foo: 1}")) 112 self.assertEqual( 113 {"foo": 1.5}, self.marionette.execute_script("return {foo: 1.5}") 114 ) 115 self.assertEqual( 116 {"foo": True}, self.marionette.execute_script("return {foo: true}") 117 ) 118 self.assertEqual( 119 {"foo": "bar"}, self.marionette.execute_script("return {foo: 'bar'}") 120 ) 121 self.assertEqual( 122 {"foo": [1, 2]}, self.marionette.execute_script("return {foo: [1, 2]}") 123 ) 124 self.assertEqual( 125 {"foo": {"bar": [1, 2]}}, 126 self.marionette.execute_script("return {foo: {bar: [1, 2]}}"), 127 ) 128 129 def test_no_return_value(self): 130 self.assertIsNone(self.marionette.execute_script("true")) 131 132 def test_argument_null(self): 133 self.assertIsNone( 134 self.marionette.execute_script( 135 "return arguments[0]", script_args=(None,), sandbox="default" 136 ) 137 ) 138 self.assertIsNone( 139 self.marionette.execute_script( 140 "return arguments[0]", script_args=(None,), sandbox="system" 141 ) 142 ) 143 self.assertIsNone( 144 self.marionette.execute_script( 145 "return arguments[0]", script_args=(None,), sandbox=None 146 ) 147 ) 148 149 def test_argument_number(self): 150 self.assertEqual(1, self.marionette.execute_script("return arguments[0]", (1,))) 151 self.assertEqual( 152 1.5, self.marionette.execute_script("return arguments[0]", (1.5,)) 153 ) 154 155 def test_argument_boolean(self): 156 self.assertTrue(self.marionette.execute_script("return arguments[0]", (True,))) 157 158 def test_argument_string(self): 159 self.assertEqual( 160 "foo", self.marionette.execute_script("return arguments[0]", ("foo",)) 161 ) 162 163 def test_argument_array(self): 164 self.assertEqual( 165 [1, 2], self.marionette.execute_script("return arguments[0]", ([1, 2],)) 166 ) 167 168 def test_argument_object(self): 169 self.assertEqual( 170 {"foo": 1}, 171 self.marionette.execute_script("return arguments[0]", ({"foo": 1},)), 172 ) 173 174 def test_argument_shadow_root(self): 175 self.marionette.navigate(inline(shadow_dom % "open")) 176 elem = self.marionette.find_element(By.TAG_NAME, "custom-checkbox-element") 177 shadow_root = elem.shadow_root 178 nodeType = self.marionette.execute_script( 179 "return arguments[0].nodeType", script_args=(shadow_root,) 180 ) 181 self.assertEqual(nodeType, 11) 182 183 def test_argument_web_element(self): 184 self.marionette.navigate(elements) 185 elem = self.marionette.find_element(By.TAG_NAME, "p") 186 nodeType = self.marionette.execute_script( 187 "return arguments[0].nodeType", script_args=(elem,) 188 ) 189 self.assertEqual(nodeType, 1) 190 191 def test_default_sandbox_globals(self): 192 for property in globals: 193 self.assert_is_defined(property, sandbox="default") 194 195 self.assert_is_defined("Components") 196 self.assert_is_defined("window.wrappedJSObject") 197 198 def test_system_globals(self): 199 for property in globals: 200 self.assert_is_defined(property, sandbox="system") 201 202 self.assert_is_defined("Components", sandbox="system") 203 self.assert_is_defined("window.wrappedJSObject", sandbox="system") 204 205 def test_mutable_sandbox_globals(self): 206 for property in globals: 207 self.assert_is_defined(property, sandbox=None) 208 209 # Components is there, but will be removed soon 210 self.assert_is_defined("Components", sandbox=None) 211 # wrappedJSObject is always there in sandboxes 212 self.assert_is_defined("window.wrappedJSObject", sandbox=None) 213 214 def test_exception(self): 215 self.assertRaises( 216 errors.JavascriptException, self.marionette.execute_script, "return foo" 217 ) 218 219 def test_stacktrace(self): 220 with self.assertRaises(errors.JavascriptException) as cm: 221 self.marionette.execute_script("return b") 222 223 # by default execute_script pass the name of the python file 224 self.assertIn( 225 os.path.relpath(__file__.replace(".pyc", ".py")), cm.exception.stacktrace 226 ) 227 self.assertIn("b is not defined", str(cm.exception)) 228 229 def test_syntaxerror_stack(self): 230 with self.assertRaises(errors.JavascriptException) as cm: 231 self.marionette.execute_script("notAFunc() {") 232 233 # by default execute_script pass the name of the python file 234 self.assertIn( 235 os.path.relpath(__file__.replace(".pyc", ".py")), cm.exception.stacktrace 236 ) 237 self.assertIn("SyntaxError: unexpected token: '{'", str(cm.exception)) 238 239 def test_permission(self): 240 for sandbox in ["default", None]: 241 with self.assertRaises(errors.JavascriptException): 242 self.marionette.execute_script( 243 "Components.classes['@mozilla.org/preferences-service;1']" 244 ) 245 246 def test_return_web_element(self): 247 self.marionette.navigate(elements) 248 expected = self.marionette.find_element(By.TAG_NAME, "p") 249 actual = self.marionette.execute_script("return document.querySelector('p')") 250 self.assertEqual(expected, actual) 251 252 def test_return_web_element_array(self): 253 self.marionette.navigate(elements) 254 expected = self.marionette.find_elements(By.TAG_NAME, "p") 255 actual = self.marionette.execute_script( 256 """ 257 let els = document.querySelectorAll('p') 258 return [els[0], els[1]]""" 259 ) 260 self.assertEqual(expected, actual) 261 262 def test_return_web_element_nested_array(self): 263 self.marionette.navigate(elements) 264 expected = self.marionette.find_elements(By.TAG_NAME, "p") 265 actual = self.marionette.execute_script( 266 """ 267 let els = document.querySelectorAll('p') 268 return { els: [els[0], els[1]] }""" 269 ) 270 self.assertEqual(expected, actual["els"]) 271 272 def test_return_web_element_nested_dict(self): 273 self.marionette.navigate(elements) 274 expected = self.marionette.find_element(By.TAG_NAME, "p") 275 actual = self.marionette.execute_script( 276 """ 277 let el = document.querySelector('p') 278 return { path: { to: { el } } }""" 279 ) 280 self.assertEqual(expected, actual["path"]["to"]["el"]) 281 282 # Bug 938228 identifies a problem with unmarshaling NodeList 283 # objects from the DOM. document.querySelectorAll returns this 284 # construct. 285 def test_return_web_element_nodelist(self): 286 self.marionette.navigate(elements) 287 expected = self.marionette.find_elements(By.TAG_NAME, "p") 288 actual = self.marionette.execute_script("return document.querySelectorAll('p')") 289 self.assertEqual(expected, actual) 290 291 def test_sandbox_reuse(self): 292 # Sandboxes between `execute_script()` invocations are shared. 293 self.marionette.execute_script("this.foobar = [23, 42];") 294 self.assertEqual( 295 self.marionette.execute_script("return this.foobar;", new_sandbox=False), 296 [23, 42], 297 ) 298 299 def test_sandbox_refresh_arguments(self): 300 self.marionette.execute_script( 301 "this.foobar = [arguments[0], arguments[1]]", [23, 42] 302 ) 303 self.assertEqual( 304 self.marionette.execute_script("return this.foobar", new_sandbox=False), 305 [23, 42], 306 ) 307 308 def test_mutable_sandbox_wrappedjsobject(self): 309 self.assert_is_defined("window.wrappedJSObject") 310 with self.assertRaises(errors.JavascriptException): 311 self.marionette.execute_script( 312 "window.wrappedJSObject.foo = 1", sandbox=None 313 ) 314 315 def test_default_sandbox_wrappedjsobject(self): 316 self.assert_is_defined("window.wrappedJSObject", sandbox="default") 317 318 try: 319 self.marionette.execute_script( 320 "window.wrappedJSObject.foo = 4", sandbox="default" 321 ) 322 self.assertEqual( 323 self.marionette.execute_script( 324 "return window.wrappedJSObject.foo", sandbox="default" 325 ), 326 4, 327 ) 328 finally: 329 self.marionette.execute_script( 330 "delete window.wrappedJSObject.foo", sandbox="default" 331 ) 332 333 def test_system_sandbox_wrappedjsobject(self): 334 self.assert_is_defined("window.wrappedJSObject", sandbox="system") 335 336 self.marionette.execute_script( 337 "window.wrappedJSObject.foo = 4", sandbox="system" 338 ) 339 self.assertEqual( 340 self.marionette.execute_script( 341 "return window.wrappedJSObject.foo", sandbox="system" 342 ), 343 4, 344 ) 345 346 def test_system_dead_object(self): 347 self.assert_is_defined("window.wrappedJSObject", sandbox="system") 348 349 self.marionette.execute_script( 350 "window.wrappedJSObject.foo = function() { return 'yo' }", sandbox="system" 351 ) 352 self.marionette.execute_script( 353 "dump(window.wrappedJSObject.foo)", sandbox="system" 354 ) 355 356 self.marionette.execute_script( 357 "window.wrappedJSObject.foo = function() { return 'yolo' }", 358 sandbox="system", 359 ) 360 typ = self.marionette.execute_script( 361 "return typeof window.wrappedJSObject.foo", sandbox="system" 362 ) 363 self.assertEqual("function", typ) 364 obj = self.marionette.execute_script( 365 "return window.wrappedJSObject.foo.toString()", sandbox="system" 366 ) 367 self.assertIn("yolo", obj) 368 369 def test_lasting_side_effects(self): 370 def send(script): 371 return self.marionette._send_message( 372 "WebDriver:ExecuteScript", {"script": script}, key="value" 373 ) 374 375 send("window.foo = 1") 376 foo = send("return window.foo") 377 self.assertEqual(1, foo) 378 379 for property in globals: 380 exists = send("return typeof {} != 'undefined'".format(property)) 381 self.assertTrue(exists, "property {} is undefined".format(property)) 382 383 self.assertTrue( 384 send( 385 """ 386 return (typeof Components == 'undefined') || 387 (typeof Components.utils == 'undefined') 388 """ 389 ) 390 ) 391 self.assertTrue(send("return typeof window.wrappedJSObject == 'undefined'")) 392 393 def test_no_callback(self): 394 self.assertTrue( 395 self.marionette.execute_script("return typeof arguments[0] == 'undefined'") 396 ) 397 398 def test_window_set_timeout_is_not_cancelled(self): 399 def content_timeout_triggered(mn): 400 return mn.execute_script("return window.n", sandbox=None) > 0 401 402 # subsequent call to execute_script after this 403 # should not cancel the setTimeout event 404 self.marionette.navigate( 405 inline( 406 """ 407 <script> 408 window.n = 0; 409 setTimeout(() => ++window.n, 4000); 410 </script>""" 411 ) 412 ) 413 414 # as debug builds are inherently slow, 415 # we need to assert the event did not already fire 416 self.assertEqual( 417 0, 418 self.marionette.execute_script("return window.n", sandbox=None), 419 "setTimeout already fired", 420 ) 421 422 # if event was cancelled, this will time out 423 Wait(self.marionette, timeout=8).until( 424 content_timeout_triggered, 425 message="Scheduled setTimeout event was cancelled by call to execute_script", 426 ) 427 428 def test_access_chrome_objects_in_event_listeners(self): 429 # sandbox.window.addEventListener/removeEventListener 430 # is used by Marionette for installing the unloadHandler which 431 # is used to return an error when a document is unloaded during 432 # script execution. 433 # 434 # Certain web frameworks, notably Angular, override 435 # window.addEventListener/removeEventListener and introspects 436 # objects passed to them. If these objects originates from chrome 437 # without having been cloned, a permission denied error is thrown 438 # as part of the security precautions put in place by the sandbox. 439 440 # addEventListener is called when script is injected 441 self.marionette.navigate( 442 inline( 443 """ 444 <script> 445 window.addEventListener = (event, listener) => listener.toString(); 446 </script> 447 """ 448 ) 449 ) 450 self.marionette.execute_script("", sandbox=None) 451 452 # removeEventListener is called when sandbox is unloaded 453 self.marionette.navigate( 454 inline( 455 """ 456 <script> 457 window.removeEventListener = (event, listener) => listener.toString(); 458 </script> 459 """ 460 ) 461 ) 462 self.marionette.execute_script("", sandbox=None) 463 464 def test_access_global_objects_from_chrome(self): 465 # test inspection of arguments 466 self.marionette.execute_script("__webDriverArguments.toString()") 467 468 def test_toJSON(self): 469 foo = self.marionette.execute_script( 470 """ 471 return { 472 toJSON () { 473 return "foo"; 474 } 475 } 476 """, 477 sandbox=None, 478 ) 479 self.assertEqual("foo", foo) 480 481 def test_unsafe_toJSON(self): 482 el = self.marionette.execute_script( 483 """ 484 return { 485 toJSON () { 486 return document.documentElement; 487 } 488 } 489 """, 490 sandbox=None, 491 ) 492 self.assert_is_web_element(el) 493 self.assertEqual(el, self.marionette.find_element(By.CSS_SELECTOR, ":root")) 494 495 def test_comment_in_last_line(self): 496 self.marionette.execute_script(" // comment ") 497 498 def test_return_value_on_alert(self): 499 res = self.marionette.execute_script("alert()") 500 self.assertIsNone(res) 501 502 503 class TestExecuteChrome(ChromeHandlerMixin, WindowManagerMixin, TestExecuteContent): 504 def setUp(self): 505 super(TestExecuteChrome, self).setUp() 506 507 self.marionette.set_context("chrome") 508 win = self.open_chrome_window(self.chrome_base_url + "test.xhtml") 509 self.marionette.switch_to_window(win) 510 511 def tearDown(self): 512 self.close_all_windows() 513 514 super(TestExecuteChrome, self).tearDown() 515 516 def test_permission(self): 517 self.marionette.execute_script( 518 "Components.classes['@mozilla.org/preferences-service;1']" 519 ) 520 521 def test_unmarshal_element_collection(self): 522 expected = self.marionette.find_elements(By.TAG_NAME, "input") 523 actual = self.marionette.execute_script( 524 "return document.querySelectorAll('input')" 525 ) 526 self.assertTrue(len(expected) > 0) 527 self.assertEqual(expected, actual) 528 529 def test_argument_shadow_root(self): 530 pass 531 532 def test_argument_web_element(self): 533 elem = self.marionette.find_element(By.TAG_NAME, "input") 534 nodeType = self.marionette.execute_script( 535 "return arguments[0].nodeType", script_args=(elem,) 536 ) 537 self.assertEqual(nodeType, 1) 538 539 def test_async_script_timeout(self): 540 with self.assertRaises(errors.ScriptTimeoutException): 541 self.marionette.execute_async_script( 542 """ 543 var cb = arguments[arguments.length - 1]; 544 setTimeout(function() { cb() }, 2500); 545 """, 546 script_timeout=100, 547 ) 548 549 def test_lasting_side_effects(self): 550 pass 551 552 def test_return_web_element(self): 553 pass 554 555 def test_return_web_element_array(self): 556 pass 557 558 def test_return_web_element_nested_array(self): 559 pass 560 561 def test_return_web_element_nested_dict(self): 562 pass 563 564 def test_return_web_element_nodelist(self): 565 pass 566 567 def test_window_set_timeout_is_not_cancelled(self): 568 pass 569 570 def test_mutable_sandbox_wrappedjsobject(self): 571 pass 572 573 def test_default_sandbox_wrappedjsobject(self): 574 pass 575 576 def test_system_sandbox_wrappedjsobject(self): 577 pass 578 579 def test_access_chrome_objects_in_event_listeners(self): 580 pass 581 582 def test_return_value_on_alert(self): 583 pass 584 585 def test_syntaxerror_stack(self): 586 pass