summaryrefslogtreecommitdiffstats
path: root/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py
diff options
context:
space:
mode:
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.py569
1 files changed, 569 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..79a6185d65
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_execute_script.py
@@ -0,0 +1,569 @@
+import os
+
+from six.moves.urllib.parse import quote
+
+from marionette_driver import By, errors
+from marionette_driver.marionette import Alert, WebElement
+from marionette_driver.wait import Wait
+
+from marionette_harness import MarionetteTestCase, WindowManagerMixin
+
+
+def inline(doc):
+ return "data:text/html;charset=utf-8,{}".format(quote(doc))
+
+
+elements = inline("<p>foo</p> <p>bar</p>")
+
+shadow_dom = """
+ <style>
+ custom-checkbox-element {
+ display:block; width:20px; height:20px;
+ }
+ </style>
+ <custom-checkbox-element></custom-checkbox-element>
+ <script>
+ customElements.define('custom-checkbox-element',
+ class extends HTMLElement {
+ constructor() {
+ super();
+ this.attachShadow({mode: '%s'}).innerHTML = `
+ <div><input type="checkbox"/></div>
+ `;
+ }
+ });
+ </script>"""
+
+
+globals = set(
+ [
+ "atob",
+ "Audio",
+ "btoa",
+ "document",
+ "navigator",
+ "URL",
+ "window",
+ ]
+)
+
+
+class TestExecuteContent(MarionetteTestCase):
+ def alert_present(self):
+ try:
+ Alert(self.marionette).text
+ return True
+ except errors.NoAlertPresentException:
+ return False
+
+ def wait_for_alert_closed(self, timeout=None):
+ Wait(self.marionette, timeout=timeout).until(lambda _: not self.alert_present())
+
+ def tearDown(self):
+ if self.alert_present():
+ alert = self.marionette.switch_to_alert()
+ alert.dismiss()
+ self.wait_for_alert_closed()
+
+ 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 assert_is_web_element(self, element):
+ self.assertIsInstance(element, WebElement)
+
+ 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]", script_args=(None,), sandbox="default"
+ )
+ )
+ self.assertIsNone(
+ self.marionette.execute_script(
+ "return arguments[0]", script_args=(None,), sandbox="system"
+ )
+ )
+ self.assertIsNone(
+ self.marionette.execute_script(
+ "return arguments[0]", script_args=(None,), sandbox=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_argument_shadow_root(self):
+ self.marionette.navigate(inline(shadow_dom % "open"))
+ elem = self.marionette.find_element(By.TAG_NAME, "custom-checkbox-element")
+ shadow_root = elem.shadow_root
+ nodeType = self.marionette.execute_script(
+ "return arguments[0].nodeType", script_args=(shadow_root,)
+ )
+ self.assertEqual(nodeType, 11)
+
+ def test_argument_web_element(self):
+ self.marionette.navigate(elements)
+ elem = self.marionette.find_element(By.TAG_NAME, "p")
+ nodeType = self.marionette.execute_script(
+ "return arguments[0].nodeType", script_args=(elem,)
+ )
+ self.assertEqual(nodeType, 1)
+
+ def test_default_sandbox_globals(self):
+ for property in globals:
+ self.assert_is_defined(property, sandbox="default")
+
+ 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", sandbox="system")
+
+ def test_mutable_sandbox_globals(self):
+ for property in globals:
+ self.assert_is_defined(property, sandbox=None)
+
+ # Components is there, but will be removed soon
+ self.assert_is_defined("Components", sandbox=None)
+ # wrappedJSObject is always there in sandboxes
+ self.assert_is_defined("window.wrappedJSObject", sandbox=None)
+
+ 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.relpath(__file__.replace(".pyc", ".py")), cm.exception.stacktrace
+ )
+ self.assertIn("b is not defined", str(cm.exception))
+
+ def test_permission(self):
+ for sandbox in ["default", None]:
+ with self.assertRaises(errors.JavascriptException):
+ self.marionette.execute_script(
+ "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)
+
+ def test_return_web_element_nested_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: [els[0], els[1]] }"""
+ )
+ self.assertEqual(expected, actual["els"])
+
+ def test_return_web_element_nested_dict(self):
+ self.marionette.navigate(elements)
+ expected = self.marionette.find_element(By.TAG_NAME, "p")
+ actual = self.marionette.execute_script(
+ """
+ let el = document.querySelector('p')
+ return { path: { to: { el } } }"""
+ )
+ self.assertEqual(expected, actual["path"]["to"]["el"])
+
+ # 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],
+ )
+
+ 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_mutable_sandbox_wrappedjsobject(self):
+ self.assert_is_defined("window.wrappedJSObject")
+ with self.assertRaises(errors.JavascriptException):
+ self.marionette.execute_script(
+ "window.wrappedJSObject.foo = 1", sandbox=None
+ )
+
+ def test_default_sandbox_wrappedjsobject(self):
+ self.assert_is_defined("window.wrappedJSObject", sandbox="default")
+
+ try:
+ self.marionette.execute_script(
+ "window.wrappedJSObject.foo = 4", sandbox="default"
+ )
+ self.assertEqual(
+ self.marionette.execute_script(
+ "return window.wrappedJSObject.foo", sandbox="default"
+ ),
+ 4,
+ )
+ finally:
+ self.marionette.execute_script(
+ "delete window.wrappedJSObject.foo", sandbox="default"
+ )
+
+ def test_system_sandbox_wrappedjsobject(self):
+ self.assert_is_defined("window.wrappedJSObject", sandbox="system")
+
+ 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.assert_is_defined("window.wrappedJSObject", sandbox="system")
+
+ 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(
+ "WebDriver: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 == 'undefined') ||
+ (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_access_chrome_objects_in_event_listeners(self):
+ # sandbox.window.addEventListener/removeEventListener
+ # is used by Marionette for installing the unloadHandler which
+ # is used to return an error when a document is unloaded during
+ # script execution.
+ #
+ # Certain web frameworks, notably Angular, override
+ # window.addEventListener/removeEventListener and introspects
+ # objects passed to them. If these objects originates from chrome
+ # without having been cloned, a permission denied error is thrown
+ # as part of the security precautions put in place by the sandbox.
+
+ # addEventListener is called when script is injected
+ self.marionette.navigate(
+ inline(
+ """
+ <script>
+ window.addEventListener = (event, listener) => listener.toString();
+ </script>
+ """
+ )
+ )
+ self.marionette.execute_script("", sandbox=None)
+
+ # removeEventListener is called when sandbox is unloaded
+ self.marionette.navigate(
+ inline(
+ """
+ <script>
+ window.removeEventListener = (event, listener) => listener.toString();
+ </script>
+ """
+ )
+ )
+ self.marionette.execute_script("", sandbox=None)
+
+ def test_access_global_objects_from_chrome(self):
+ # test inspection of arguments
+ self.marionette.execute_script("__webDriverArguments.toString()")
+
+ def test_toJSON(self):
+ foo = self.marionette.execute_script(
+ """
+ return {
+ toJSON () {
+ return "foo";
+ }
+ }
+ """,
+ sandbox=None,
+ )
+ self.assertEqual("foo", foo)
+
+ def test_unsafe_toJSON(self):
+ el = self.marionette.execute_script(
+ """
+ return {
+ toJSON () {
+ return document.documentElement;
+ }
+ }
+ """,
+ sandbox=None,
+ )
+ self.assert_is_web_element(el)
+ self.assertEqual(el, self.marionette.find_element(By.CSS_SELECTOR, ":root"))
+
+ def test_comment_in_last_line(self):
+ self.marionette.execute_script(" // comment ")
+
+ def test_return_value_on_alert(self):
+ res = self.marionette.execute_script("alert()")
+ self.assertIsNone(res)
+
+
+class TestExecuteChrome(WindowManagerMixin, TestExecuteContent):
+ def setUp(self):
+ super(TestExecuteChrome, self).setUp()
+
+ self.marionette.set_context("chrome")
+ win = self.open_chrome_window("chrome://remote/content/marionette/test.xhtml")
+ self.marionette.switch_to_window(win)
+
+ def tearDown(self):
+ self.close_all_windows()
+
+ super(TestExecuteChrome, self).tearDown()
+
+ def test_permission(self):
+ self.marionette.execute_script(
+ "Components.classes['@mozilla.org/preferences-service;1']"
+ )
+
+ def test_unmarshal_element_collection(self):
+ expected = self.marionette.find_elements(By.TAG_NAME, "input")
+ actual = self.marionette.execute_script(
+ "return document.querySelectorAll('input')"
+ )
+ self.assertTrue(len(expected) > 0)
+ self.assertEqual(expected, actual)
+
+ def test_argument_shadow_root(self):
+ pass
+
+ def test_argument_web_element(self):
+ elem = self.marionette.find_element(By.TAG_NAME, "input")
+ nodeType = self.marionette.execute_script(
+ "return arguments[0].nodeType", script_args=(elem,)
+ )
+ self.assertEqual(nodeType, 1)
+
+ 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() }, 2500);
+ """,
+ script_timeout=100,
+ )
+
+ 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_nested_array(self):
+ pass
+
+ def test_return_web_element_nested_dict(self):
+ pass
+
+ def test_return_web_element_nodelist(self):
+ pass
+
+ def test_window_set_timeout_is_not_cancelled(self):
+ pass
+
+ def test_mutable_sandbox_wrappedjsobject(self):
+ pass
+
+ def test_default_sandbox_wrappedjsobject(self):
+ pass
+
+ def test_system_sandbox_wrappedjsobject(self):
+ pass
+
+ def test_access_chrome_objects_in_event_listeners(self):
+ pass
+
+ def test_return_value_on_alert(self):
+ pass