diff options
Diffstat (limited to 'testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py')
-rw-r--r-- | testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py | 402 |
1 files changed, 402 insertions, 0 deletions
diff --git a/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py new file mode 100644 index 0000000000..1ef4549d3c --- /dev/null +++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py @@ -0,0 +1,402 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import urllib + +from marionette_driver import By, errors +from marionette_driver.marionette import HTMLElement +from marionette_driver.wait import Wait + +from marionette_harness import MarionetteTestCase, skip_if_mobile, WindowManagerMixin + + +def inline(doc): + return "data:text/html;charset=utf-8,{}".format(urllib.quote(doc)) + + +elements = inline("<p>foo</p> <p>bar</p>") + +globals = set([ + "atob", + "Audio", + "btoa", + "document", + "navigator", + "URL", + "window", + ]) + + +class TestExecuteSimpleTestContent(MarionetteTestCase): + def test_stack_trace(self): + try: + self.marionette.execute_js_script(""" + let a = 1; + throwHere(); + """, filename="file.js") + self.assertFalse(True) + except errors.JavascriptException as e: + self.assertIn("throwHere is not defined", e.message) + self.assertIn("@file.js:2", e.stacktrace) + + +class TestExecuteContent(MarionetteTestCase): + + def assert_is_defined(self, property, sandbox="default"): + self.assertTrue(self.marionette.execute_script( + "return typeof arguments[0] != 'undefined'", [property], sandbox=sandbox), + "property {} is undefined".format(property)) + + def test_return_number(self): + self.assertEqual(1, self.marionette.execute_script("return 1")) + self.assertEqual(1.5, self.marionette.execute_script("return 1.5")) + + def test_return_boolean(self): + self.assertTrue(self.marionette.execute_script("return true")) + + def test_return_string(self): + self.assertEqual("foo", self.marionette.execute_script("return 'foo'")) + + def test_return_array(self): + self.assertEqual( + [1, 2], self.marionette.execute_script("return [1, 2]")) + self.assertEqual( + [1.25, 1.75], self.marionette.execute_script("return [1.25, 1.75]")) + self.assertEqual( + [True, False], self.marionette.execute_script("return [true, false]")) + self.assertEqual( + ["foo", "bar"], self.marionette.execute_script("return ['foo', 'bar']")) + self.assertEqual( + [1, 1.5, True, "foo"], self.marionette.execute_script("return [1, 1.5, true, 'foo']")) + self.assertEqual( + [1, [2]], self.marionette.execute_script("return [1, [2]]")) + + def test_return_object(self): + self.assertEqual( + {"foo": 1}, self.marionette.execute_script("return {foo: 1}")) + self.assertEqual( + {"foo": 1.5}, self.marionette.execute_script("return {foo: 1.5}")) + self.assertEqual( + {"foo": True}, self.marionette.execute_script("return {foo: true}")) + self.assertEqual( + {"foo": "bar"}, self.marionette.execute_script("return {foo: 'bar'}")) + self.assertEqual( + {"foo": [1, 2]}, self.marionette.execute_script("return {foo: [1, 2]}")) + self.assertEqual( + {"foo": {"bar": [1, 2]}}, + self.marionette.execute_script("return {foo: {bar: [1, 2]}}")) + + def test_no_return_value(self): + self.assertIsNone(self.marionette.execute_script("true")) + + def test_argument_null(self): + self.assertIsNone(self.marionette.execute_script("return arguments[0]", [None])) + + def test_argument_number(self): + self.assertEqual( + 1, self.marionette.execute_script("return arguments[0]", [1])) + self.assertEqual( + 1.5, self.marionette.execute_script("return arguments[0]", [1.5])) + + def test_argument_boolean(self): + self.assertTrue(self.marionette.execute_script("return arguments[0]", [True])) + + def test_argument_string(self): + self.assertEqual( + "foo", self.marionette.execute_script("return arguments[0]", ["foo"])) + + def test_argument_array(self): + self.assertEqual( + [1, 2], self.marionette.execute_script("return arguments[0]", [[1, 2]])) + + def test_argument_object(self): + self.assertEqual({"foo": 1}, self.marionette.execute_script( + "return arguments[0]", [{"foo": 1}])) + + def test_globals(self): + for property in globals: + self.assert_is_defined(property) + self.assert_is_defined("Components") + self.assert_is_defined("window.wrappedJSObject") + + def test_system_globals(self): + for property in globals: + self.assert_is_defined(property, sandbox="system") + self.assert_is_defined("Components", sandbox="system") + self.assert_is_defined("window.wrappedJSObject") + + def test_exception(self): + self.assertRaises(errors.JavascriptException, + self.marionette.execute_script, "return foo") + + def test_stacktrace(self): + with self.assertRaises(errors.JavascriptException) as cm: + self.marionette.execute_script("return b") + + # by default execute_script pass the name of the python file + self.assertIn(os.path.basename(__file__.replace(".pyc", ".py")), + cm.exception.stacktrace) + self.assertIn("b is not defined", cm.exception.message) + self.assertIn("return b", cm.exception.stacktrace) + + def test_permission(self): + with self.assertRaises(errors.JavascriptException): + self.marionette.execute_script(""" + var c = Components.classes["@mozilla.org/preferences-service;1"]; + """) + + def test_return_web_element(self): + self.marionette.navigate(elements) + expected = self.marionette.find_element(By.TAG_NAME, "p") + actual = self.marionette.execute_script( + "return document.querySelector('p')") + self.assertEqual(expected, actual) + + def test_return_web_element_array(self): + self.marionette.navigate(elements) + expected = self.marionette.find_elements(By.TAG_NAME, "p") + actual = self.marionette.execute_script(""" + let els = document.querySelectorAll('p') + return [els[0], els[1]]""") + self.assertEqual(expected, actual) + + # Bug 938228 identifies a problem with unmarshaling NodeList + # objects from the DOM. document.querySelectorAll returns this + # construct. + def test_return_web_element_nodelist(self): + self.marionette.navigate(elements) + expected = self.marionette.find_elements(By.TAG_NAME, "p") + actual = self.marionette.execute_script( + "return document.querySelectorAll('p')") + self.assertEqual(expected, actual) + + def test_sandbox_reuse(self): + # Sandboxes between `execute_script()` invocations are shared. + self.marionette.execute_script("this.foobar = [23, 42];") + self.assertEqual(self.marionette.execute_script( + "return this.foobar;", new_sandbox=False), [23, 42]) + + self.marionette.execute_script("global.barfoo = [42, 23];") + self.assertEqual(self.marionette.execute_script( + "return global.barfoo;", new_sandbox=False), [42, 23]) + + def test_sandbox_refresh_arguments(self): + self.marionette.execute_script( + "this.foobar = [arguments[0], arguments[1]]", [23, 42]) + self.assertEqual(self.marionette.execute_script( + "return this.foobar", new_sandbox=False), [23, 42]) + + def test_wrappedjsobject(self): + try: + self.marionette.execute_script("window.wrappedJSObject.foo = 3") + self.assertEqual( + self.marionette.execute_script("return window.wrappedJSObject.foo"), 3) + finally: + self.marionette.execute_script("delete window.wrappedJSObject.foo") + + def test_system_sandbox_wrappedjsobject(self): + self.marionette.execute_script( + "window.wrappedJSObject.foo = 4", sandbox="system") + self.assertEqual(self.marionette.execute_script( + "return window.wrappedJSObject.foo", sandbox="system"), 4) + + def test_system_dead_object(self): + self.marionette.execute_script( + "window.wrappedJSObject.foo = function() { return 'yo' }", + sandbox="system") + self.marionette.execute_script( + "dump(window.wrappedJSObject.foo)", sandbox="system") + + self.marionette.execute_script( + "window.wrappedJSObject.foo = function() { return 'yolo' }", + sandbox="system") + typ = self.marionette.execute_script( + "return typeof window.wrappedJSObject.foo", sandbox="system") + self.assertEqual("function", typ) + obj = self.marionette.execute_script( + "return window.wrappedJSObject.foo.toString()", sandbox="system") + self.assertIn("yolo", obj) + + def test_lasting_side_effects(self): + def send(script): + return self.marionette._send_message( + "executeScript", {"script": script}, key="value") + + send("window.foo = 1") + foo = send("return window.foo") + self.assertEqual(1, foo) + + for property in globals: + exists = send("return typeof {} != 'undefined'".format(property)) + self.assertTrue(exists, "property {} is undefined".format(property)) + + self.assertTrue(send("return typeof Components.utils == 'undefined'")) + self.assertTrue(send("return typeof window.wrappedJSObject == 'undefined'")) + + def test_no_callback(self): + self.assertTrue(self.marionette.execute_script( + "return typeof arguments[0] == 'undefined'")) + + def test_window_set_timeout_is_not_cancelled(self): + def content_timeout_triggered(mn): + return mn.execute_script("return window.n", sandbox=None) > 0 + + # subsequent call to execute_script after this + # should not cancel the setTimeout event + self.marionette.navigate(inline(""" + <script> + window.n = 0; + setTimeout(() => ++window.n, 4000); + </script>""")) + + # as debug builds are inherently slow, + # we need to assert the event did not already fire + self.assertEqual(0, self.marionette.execute_script( + "return window.n", sandbox=None), + "setTimeout already fired") + + # if event was cancelled, this will time out + Wait(self.marionette, timeout=8).until( + content_timeout_triggered, + message="Scheduled setTimeout event was cancelled by call to execute_script") + + def test_privileged_code_inspection(self): + # test permission denied on toString of unload event handler + self.marionette.navigate(inline(""" + <script> + window.addEventListener = (type, handler) => handler.toString(); + </script>""")) + self.marionette.execute_script("", sandbox=None) + + # test inspection of arguments + self.marionette.execute_script("__webDriverArguments.toString()") + + +class TestExecuteChrome(WindowManagerMixin, TestExecuteContent): + + def setUp(self): + super(TestExecuteChrome, self).setUp() + + self.marionette.set_context("chrome") + + def tearDown(self): + super(TestExecuteChrome, self).tearDown() + + def test_permission(self): + self.assertEqual(1, self.marionette.execute_script(""" + var c = Components.classes["@mozilla.org/preferences-service;1"]; return 1;""")) + + @skip_if_mobile("New windows not supported in Fennec") + def test_unmarshal_element_collection(self): + + def open_window_with_js(): + self.marionette.execute_script( + "window.open('chrome://marionette/content/test.xul', 'xul', 'chrome');") + + try: + win = self.open_window(trigger=open_window_with_js) + self.marionette.switch_to_window(win) + + expected = self.marionette.find_elements(By.TAG_NAME, "textbox") + actual = self.marionette.execute_script( + "return document.querySelectorAll('textbox')") + self.assertEqual(expected, actual) + + finally: + self.close_all_windows() + + def test_async_script_timeout(self): + with self.assertRaises(errors.ScriptTimeoutException): + self.marionette.execute_async_script(""" + var cb = arguments[arguments.length - 1]; + setTimeout(function() { cb() }, 250); + """, script_timeout=100) + + @skip_if_mobile("New windows not supported in Fennec") + def test_invalid_chrome_handle(self): + try: + win = self.open_window() + self.marionette.switch_to_window(win) + + # Close new window and don't switch back to the original one + self.marionette.close_chrome_window() + self.assertNotEqual(self.start_window, win) + + # Call execute_script on an invalid chrome handle + with self.marionette.using_context('chrome'): + self.marionette.execute_script(""" + return true; + """) + + finally: + self.close_all_windows() + + def test_lasting_side_effects(self): + pass + + def test_return_web_element(self): + pass + + def test_return_web_element_array(self): + pass + + def test_return_web_element_nodelist(self): + pass + + def test_window_set_timeout_is_not_cancelled(self): + pass + + def test_privileged_code_inspection(self): + pass + + +class TestElementCollections(MarionetteTestCase): + + def assertSequenceIsInstance(self, seq, typ): + for item in seq: + self.assertIsInstance(item, typ) + + def test_array(self): + self.marionette.navigate(inline("<p>foo <p>bar")) + els = self.marionette.execute_script("return Array.from(document.querySelectorAll('p'))") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_all_collection(self): + self.marionette.navigate(inline("<p>foo <p>bar")) + els = self.marionette.execute_script("return document.all") + self.assertIsInstance(els, list) + # <html>, <head>, <body>, <p>, <p> + self.assertEqual(5, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_collection(self): + self.marionette.navigate(inline("<p>foo <p>bar")) + els = self.marionette.execute_script("return document.getElementsByTagName('p')") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_form_controls_collection(self): + self.marionette.navigate(inline("<form><input><input></form>")) + els = self.marionette.execute_script("return document.forms[0].elements") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_html_options_collection(self): + self.marionette.navigate(inline("<select><option><option></select>")) + els = self.marionette.execute_script("return document.querySelector('select').options") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) + + def test_node_list(self): + self.marionette.navigate(inline("<p>foo <p>bar")) + els = self.marionette.execute_script("return document.querySelectorAll('p')") + self.assertIsInstance(els, list) + self.assertEqual(2, len(els)) + self.assertSequenceIsInstance(els, HTMLElement) |