1from __future__ import absolute_import 2 3import os 4import urllib 5 6from marionette_driver import By, errors 7from marionette_driver.marionette import Alert, HTMLElement 8from marionette_driver.wait import Wait 9 10from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin 11 12 13def inline(doc): 14 return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) 15 16 17elements = inline("<p>foo</p> <p>bar</p>") 18 19globals = set([ 20 "atob", 21 "Audio", 22 "btoa", 23 "document", 24 "navigator", 25 "URL", 26 "window", 27 ]) 28 29 30class TestExecuteContent(MarionetteTestCase): 31 32 def alert_present(self): 33 try: 34 Alert(self.marionette).text 35 return True 36 except errors.NoAlertPresentException: 37 return False 38 39 def wait_for_alert_closed(self, timeout=None): 40 Wait(self.marionette, timeout=timeout).until( 41 lambda _: not self.alert_present()) 42 43 def tearDown(self): 44 if self.alert_present(): 45 alert = self.marionette.switch_to_alert() 46 alert.dismiss() 47 self.wait_for_alert_closed() 48 49 def assert_is_defined(self, property, sandbox="default"): 50 self.assertTrue(self.marionette.execute_script( 51 "return typeof arguments[0] != 'undefined'", [property], sandbox=sandbox), 52 "property {} is undefined".format(property)) 53 54 def assert_is_web_element(self, element): 55 self.assertIsInstance(element, HTMLElement) 56 57 def test_return_number(self): 58 self.assertEqual(1, self.marionette.execute_script("return 1")) 59 self.assertEqual(1.5, self.marionette.execute_script("return 1.5")) 60 61 def test_return_boolean(self): 62 self.assertTrue(self.marionette.execute_script("return true")) 63 64 def test_return_string(self): 65 self.assertEqual("foo", self.marionette.execute_script("return 'foo'")) 66 67 def test_return_array(self): 68 self.assertEqual( 69 [1, 2], self.marionette.execute_script("return [1, 2]")) 70 self.assertEqual( 71 [1.25, 1.75], self.marionette.execute_script("return [1.25, 1.75]")) 72 self.assertEqual( 73 [True, False], self.marionette.execute_script("return [true, false]")) 74 self.assertEqual( 75 ["foo", "bar"], self.marionette.execute_script("return ['foo', 'bar']")) 76 self.assertEqual( 77 [1, 1.5, True, "foo"], self.marionette.execute_script("return [1, 1.5, true, 'foo']")) 78 self.assertEqual( 79 [1, [2]], self.marionette.execute_script("return [1, [2]]")) 80 81 def test_return_object(self): 82 self.assertEqual( 83 {"foo": 1}, self.marionette.execute_script("return {foo: 1}")) 84 self.assertEqual( 85 {"foo": 1.5}, self.marionette.execute_script("return {foo: 1.5}")) 86 self.assertEqual( 87 {"foo": True}, self.marionette.execute_script("return {foo: true}")) 88 self.assertEqual( 89 {"foo": "bar"}, self.marionette.execute_script("return {foo: 'bar'}")) 90 self.assertEqual( 91 {"foo": [1, 2]}, self.marionette.execute_script("return {foo: [1, 2]}")) 92 self.assertEqual( 93 {"foo": {"bar": [1, 2]}}, 94 self.marionette.execute_script("return {foo: {bar: [1, 2]}}")) 95 96 def test_no_return_value(self): 97 self.assertIsNone(self.marionette.execute_script("true")) 98 99 def test_argument_null(self): 100 self.assertIsNone(self.marionette.execute_script( 101 "return arguments[0]", 102 script_args=(None,), 103 sandbox="default")) 104 self.assertIsNone(self.marionette.execute_script( 105 "return arguments[0]", 106 script_args=(None,), 107 sandbox="system")) 108 self.assertIsNone(self.marionette.execute_script( 109 "return arguments[0]", 110 script_args=(None,), 111 sandbox=None)) 112 113 def test_argument_number(self): 114 self.assertEqual( 115 1, self.marionette.execute_script("return arguments[0]", (1,))) 116 self.assertEqual( 117 1.5, self.marionette.execute_script("return arguments[0]", (1.5,))) 118 119 def test_argument_boolean(self): 120 self.assertTrue(self.marionette.execute_script("return arguments[0]", (True,))) 121 122 def test_argument_string(self): 123 self.assertEqual( 124 "foo", self.marionette.execute_script("return arguments[0]", ("foo",))) 125 126 def test_argument_array(self): 127 self.assertEqual( 128 [1, 2], self.marionette.execute_script("return arguments[0]", ([1, 2],))) 129 130 def test_argument_object(self): 131 self.assertEqual({"foo": 1}, self.marionette.execute_script( 132 "return arguments[0]", ({"foo": 1},))) 133 134 def test_default_sandbox_globals(self): 135 for property in globals: 136 self.assert_is_defined(property, sandbox="default") 137 138 self.assert_is_defined("Components") 139 self.assert_is_defined("window.wrappedJSObject") 140 141 def test_system_globals(self): 142 for property in globals: 143 self.assert_is_defined(property, sandbox="system") 144 145 self.assert_is_defined("Components", sandbox="system") 146 self.assert_is_defined("window.wrappedJSObject", sandbox="system") 147 148 def test_mutable_sandbox_globals(self): 149 for property in globals: 150 self.assert_is_defined(property, sandbox=None) 151 152 # Components is there, but will be removed soon 153 self.assert_is_defined("Components", sandbox=None) 154 # wrappedJSObject is always there in sandboxes 155 self.assert_is_defined("window.wrappedJSObject", sandbox=None) 156 157 def test_exception(self): 158 self.assertRaises(errors.JavascriptException, 159 self.marionette.execute_script, "return foo") 160 161 def test_stacktrace(self): 162 with self.assertRaises(errors.JavascriptException) as cm: 163 self.marionette.execute_script("return b") 164 165 # by default execute_script pass the name of the python file 166 self.assertIn(os.path.basename(__file__.replace(".pyc", ".py")), 167 cm.exception.stacktrace) 168 self.assertIn("b is not defined", cm.exception.message) 169 self.assertIn("return b", cm.exception.stacktrace) 170 171 def test_permission(self): 172 for sandbox in ["default", None]: 173 with self.assertRaises(errors.JavascriptException): 174 self.marionette.execute_script( 175 "Components.classes['@mozilla.org/preferences-service;1']") 176 177 def test_return_web_element(self): 178 self.marionette.navigate(elements) 179 expected = self.marionette.find_element(By.TAG_NAME, "p") 180 actual = self.marionette.execute_script( 181 "return document.querySelector('p')") 182 self.assertEqual(expected, actual) 183 184 def test_return_web_element_array(self): 185 self.marionette.navigate(elements) 186 expected = self.marionette.find_elements(By.TAG_NAME, "p") 187 actual = self.marionette.execute_script(""" 188 let els = document.querySelectorAll('p') 189 return [els[0], els[1]]""") 190 self.assertEqual(expected, actual) 191 192 # Bug 938228 identifies a problem with unmarshaling NodeList 193 # objects from the DOM. document.querySelectorAll returns this 194 # construct. 195 def test_return_web_element_nodelist(self): 196 self.marionette.navigate(elements) 197 expected = self.marionette.find_elements(By.TAG_NAME, "p") 198 actual = self.marionette.execute_script( 199 "return document.querySelectorAll('p')") 200 self.assertEqual(expected, actual) 201 202 def test_sandbox_reuse(self): 203 # Sandboxes between `execute_script()` invocations are shared. 204 self.marionette.execute_script("this.foobar = [23, 42];") 205 self.assertEqual(self.marionette.execute_script( 206 "return this.foobar;", new_sandbox=False), [23, 42]) 207 208 def test_sandbox_refresh_arguments(self): 209 self.marionette.execute_script( 210 "this.foobar = [arguments[0], arguments[1]]", [23, 42]) 211 self.assertEqual(self.marionette.execute_script( 212 "return this.foobar", new_sandbox=False), [23, 42]) 213 214 def test_mutable_sandbox_wrappedjsobject(self): 215 self.assert_is_defined("window.wrappedJSObject") 216 with self.assertRaises(errors.JavascriptException): 217 self.marionette.execute_script("window.wrappedJSObject.foo = 1", sandbox=None) 218 219 def test_default_sandbox_wrappedjsobject(self): 220 self.assert_is_defined("window.wrappedJSObject", sandbox="default") 221 222 try: 223 self.marionette.execute_script( 224 "window.wrappedJSObject.foo = 4", sandbox="default") 225 self.assertEqual(self.marionette.execute_script( 226 "return window.wrappedJSObject.foo", sandbox="default"), 4) 227 finally: 228 self.marionette.execute_script( 229 "delete window.wrappedJSObject.foo", sandbox="default") 230 231 def test_system_sandbox_wrappedjsobject(self): 232 self.assert_is_defined("window.wrappedJSObject", sandbox="system") 233 234 self.marionette.execute_script( 235 "window.wrappedJSObject.foo = 4", sandbox="system") 236 self.assertEqual(self.marionette.execute_script( 237 "return window.wrappedJSObject.foo", sandbox="system"), 4) 238 239 def test_system_dead_object(self): 240 self.assert_is_defined("window.wrappedJSObject", sandbox="system") 241 242 self.marionette.execute_script( 243 "window.wrappedJSObject.foo = function() { return 'yo' }", 244 sandbox="system") 245 self.marionette.execute_script( 246 "dump(window.wrappedJSObject.foo)", sandbox="system") 247 248 self.marionette.execute_script( 249 "window.wrappedJSObject.foo = function() { return 'yolo' }", 250 sandbox="system") 251 typ = self.marionette.execute_script( 252 "return typeof window.wrappedJSObject.foo", sandbox="system") 253 self.assertEqual("function", typ) 254 obj = self.marionette.execute_script( 255 "return window.wrappedJSObject.foo.toString()", sandbox="system") 256 self.assertIn("yolo", obj) 257 258 def test_lasting_side_effects(self): 259 def send(script): 260 return self.marionette._send_message( 261 "executeScript", {"script": script}, key="value") 262 263 send("window.foo = 1") 264 foo = send("return window.foo") 265 self.assertEqual(1, foo) 266 267 for property in globals: 268 exists = send("return typeof {} != 'undefined'".format(property)) 269 self.assertTrue(exists, "property {} is undefined".format(property)) 270 271 self.assertTrue(send("return typeof Components.utils == 'undefined'")) 272 self.assertTrue(send("return typeof window.wrappedJSObject == 'undefined'")) 273 274 def test_no_callback(self): 275 self.assertTrue(self.marionette.execute_script( 276 "return typeof arguments[0] == 'undefined'")) 277 278 @skip_if_mobile("Intermittent on Android - bug 1334035") 279 def test_window_set_timeout_is_not_cancelled(self): 280 def content_timeout_triggered(mn): 281 return mn.execute_script("return window.n", sandbox=None) > 0 282 283 # subsequent call to execute_script after this 284 # should not cancel the setTimeout event 285 self.marionette.navigate(inline(""" 286 <script> 287 window.n = 0; 288 setTimeout(() => ++window.n, 4000); 289 </script>""")) 290 291 # as debug builds are inherently slow, 292 # we need to assert the event did not already fire 293 self.assertEqual(0, self.marionette.execute_script( 294 "return window.n", sandbox=None), 295 "setTimeout already fired") 296 297 # if event was cancelled, this will time out 298 Wait(self.marionette, timeout=8).until( 299 content_timeout_triggered, 300 message="Scheduled setTimeout event was cancelled by call to execute_script") 301 302 def test_access_chrome_objects_in_event_listeners(self): 303 # sandbox.window.addEventListener/removeEventListener 304 # is used by Marionette for installing the unloadHandler which 305 # is used to return an error when a document is unloaded during 306 # script execution. 307 # 308 # Certain web frameworks, notably Angular, override 309 # window.addEventListener/removeEventListener and introspects 310 # objects passed to them. If these objects originates from chrome 311 # without having been cloned, a permission denied error is thrown 312 # as part of the security precautions put in place by the sandbox. 313 314 # addEventListener is called when script is injected 315 self.marionette.navigate(inline(""" 316 <script> 317 window.addEventListener = (event, listener) => listener.toString(); 318 </script> 319 """)) 320 self.marionette.execute_script("", sandbox=None) 321 322 # removeEventListener is called when sandbox is unloaded 323 self.marionette.navigate(inline(""" 324 <script> 325 window.removeEventListener = (event, listener) => listener.toString(); 326 </script> 327 """)) 328 self.marionette.execute_script("", sandbox=None) 329 330 def test_access_global_objects_from_chrome(self): 331 # test inspection of arguments 332 self.marionette.execute_script("__webDriverArguments.toString()") 333 334 def test_toJSON(self): 335 foo = self.marionette.execute_script(""" 336 return { 337 toJSON () { 338 return "foo"; 339 } 340 }""", 341 sandbox=None) 342 self.assertEqual("foo", foo) 343 344 def test_unsafe_toJSON(self): 345 el = self.marionette.execute_script(""" 346 return { 347 toJSON () { 348 return document.documentElement; 349 } 350 }""", 351 sandbox=None) 352 self.assert_is_web_element(el) 353 354 @skip_if_mobile("Modal dialogs not supported in Fennec") 355 def test_return_value_on_alert(self): 356 res = self.marionette._send_message("executeScript", {"script": "alert()"}) 357 self.assertIn("value", res) 358 self.assertIsNone(res["value"]) 359 360 361class TestExecuteChrome(WindowManagerMixin, TestExecuteContent): 362 363 def setUp(self): 364 super(TestExecuteChrome, self).setUp() 365 366 self.marionette.set_context("chrome") 367 368 def tearDown(self): 369 super(TestExecuteChrome, self).tearDown() 370 371 def test_permission(self): 372 self.marionette.execute_script( 373 "Components.classes['@mozilla.org/preferences-service;1']") 374 375 @skip_if_mobile("New windows not supported in Fennec") 376 def test_unmarshal_element_collection(self): 377 378 def open_window_with_js(): 379 self.marionette.execute_script( 380 "window.open('chrome://marionette/content/test.xul', 'xul', 'chrome');") 381 382 try: 383 win = self.open_window(trigger=open_window_with_js) 384 self.marionette.switch_to_window(win) 385 386 expected = self.marionette.find_elements(By.TAG_NAME, "textbox") 387 actual = self.marionette.execute_script( 388 "return document.querySelectorAll('textbox')") 389 self.assertEqual(expected, actual) 390 391 finally: 392 self.close_all_windows() 393 394 def test_async_script_timeout(self): 395 with self.assertRaises(errors.ScriptTimeoutException): 396 self.marionette.execute_async_script(""" 397 var cb = arguments[arguments.length - 1]; 398 setTimeout(function() { cb() }, 2500); 399 """, script_timeout=100) 400 401 def test_lasting_side_effects(self): 402 pass 403 404 def test_return_web_element(self): 405 pass 406 407 def test_return_web_element_array(self): 408 pass 409 410 def test_return_web_element_nodelist(self): 411 pass 412 413 def test_window_set_timeout_is_not_cancelled(self): 414 pass 415 416 def test_mutable_sandbox_wrappedjsobject(self): 417 pass 418 419 def test_default_sandbox_wrappedjsobject(self): 420 pass 421 422 def test_system_sandbox_wrappedjsobject(self): 423 pass 424 425 def test_access_chrome_objects_in_event_listeners(self): 426 pass 427 428 def test_return_value_on_alert(self): 429 pass 430 431 432class TestElementCollections(MarionetteTestCase): 433 434 def assertSequenceIsInstance(self, seq, typ): 435 for item in seq: 436 self.assertIsInstance(item, typ) 437 438 def test_array(self): 439 self.marionette.navigate(inline("<p>foo <p>bar")) 440 els = self.marionette.execute_script("return Array.from(document.querySelectorAll('p'))") 441 self.assertIsInstance(els, list) 442 self.assertEqual(2, len(els)) 443 self.assertSequenceIsInstance(els, HTMLElement) 444 445 def test_html_all_collection(self): 446 self.marionette.navigate(inline("<p>foo <p>bar")) 447 els = self.marionette.execute_script("return document.all") 448 self.assertIsInstance(els, list) 449 # <html>, <head>, <body>, <p>, <p> 450 self.assertEqual(5, len(els)) 451 self.assertSequenceIsInstance(els, HTMLElement) 452 453 def test_html_collection(self): 454 self.marionette.navigate(inline("<p>foo <p>bar")) 455 els = self.marionette.execute_script("return document.getElementsByTagName('p')") 456 self.assertIsInstance(els, list) 457 self.assertEqual(2, len(els)) 458 self.assertSequenceIsInstance(els, HTMLElement) 459 460 def test_html_form_controls_collection(self): 461 self.marionette.navigate(inline("<form><input><input></form>")) 462 els = self.marionette.execute_script("return document.forms[0].elements") 463 self.assertIsInstance(els, list) 464 self.assertEqual(2, len(els)) 465 self.assertSequenceIsInstance(els, HTMLElement) 466 467 def test_html_options_collection(self): 468 self.marionette.navigate(inline("<select><option><option></select>")) 469 els = self.marionette.execute_script("return document.querySelector('select').options") 470 self.assertIsInstance(els, list) 471 self.assertEqual(2, len(els)) 472 self.assertSequenceIsInstance(els, HTMLElement) 473 474 def test_node_list(self): 475 self.marionette.navigate(inline("<p>foo <p>bar")) 476 els = self.marionette.execute_script("return document.querySelectorAll('p')") 477 self.assertIsInstance(els, list) 478 self.assertEqual(2, len(els)) 479 self.assertSequenceIsInstance(els, HTMLElement) 480