Unreviewed. Update W3C WebDriver imported tests.
authorcarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 31 Jan 2018 09:07:43 +0000 (09:07 +0000)
committercarlosgc@webkit.org <carlosgc@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 31 Jan 2018 09:07:43 +0000 (09:07 +0000)
Tools:

Show full pytest failure logs.

pytest truncates long lists when printing a failed assertion.
This is not great for debugging and arguably it should be the
default to show the full diff when comparing lists.

* Scripts/webkitpy/webdriver_tests/pytest_runner.py:
(run):

WebDriverTests:

* imported/w3c/importer.json:
* imported/w3c/tools/wptrunner/requirements_sauce.txt:
* imported/w3c/tools/wptrunner/tox.ini:
* imported/w3c/tools/wptrunner/wptrunner/browsers/sauce.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/executormarionette.py:
* imported/w3c/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py:
* imported/w3c/tools/wptrunner/wptrunner/metadata.py:
* imported/w3c/webdriver/OWNERS:
* imported/w3c/webdriver/tests/__init__.py:
* imported/w3c/webdriver/tests/actions/mouse.py:
* imported/w3c/webdriver/tests/actions/mouse_dblclick.py:
* imported/w3c/webdriver/tests/actions/support/mouse.py:
* imported/w3c/webdriver/tests/cookies/add_cookie.py:
* imported/w3c/webdriver/tests/cookies/delete_cookie.py:
* imported/w3c/webdriver/tests/cookies/get_named_cookie.py:
* imported/w3c/webdriver/tests/element_send_keys/form_controls.py: Added.
* imported/w3c/webdriver/tests/element_send_keys/interactability.py:
* imported/w3c/webdriver/tests/interaction/element_clear.py:
* imported/w3c/webdriver/tests/support/asserts.py:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@227880 268f45cc-cd09-0410-ab3c-d52691b4dbfc

22 files changed:
Tools/ChangeLog
Tools/Scripts/webkitpy/webdriver_tests/pytest_runner.py
WebDriverTests/ChangeLog
WebDriverTests/imported/w3c/importer.json
WebDriverTests/imported/w3c/tools/wptrunner/requirements_sauce.txt
WebDriverTests/imported/w3c/tools/wptrunner/tox.ini
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/browsers/sauce.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/executormarionette.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py
WebDriverTests/imported/w3c/tools/wptrunner/wptrunner/metadata.py
WebDriverTests/imported/w3c/webdriver/OWNERS
WebDriverTests/imported/w3c/webdriver/tests/__init__.py
WebDriverTests/imported/w3c/webdriver/tests/actions/mouse.py
WebDriverTests/imported/w3c/webdriver/tests/actions/mouse_dblclick.py
WebDriverTests/imported/w3c/webdriver/tests/actions/support/mouse.py
WebDriverTests/imported/w3c/webdriver/tests/cookies/add_cookie.py
WebDriverTests/imported/w3c/webdriver/tests/cookies/delete_cookie.py
WebDriverTests/imported/w3c/webdriver/tests/cookies/get_named_cookie.py
WebDriverTests/imported/w3c/webdriver/tests/element_send_keys/form_controls.py [new file with mode: 0644]
WebDriverTests/imported/w3c/webdriver/tests/element_send_keys/interactability.py
WebDriverTests/imported/w3c/webdriver/tests/interaction/element_clear.py
WebDriverTests/imported/w3c/webdriver/tests/support/asserts.py

index 745e4e6..799a029 100644 (file)
@@ -1,3 +1,16 @@
+2018-01-31  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        Unreviewed. Update W3C WebDriver imported tests.
+
+        Show full pytest failure logs.
+
+        pytest truncates long lists when printing a failed assertion.
+        This is not great for debugging and arguably it should be the
+        default to show the full diff when comparing lists.
+
+        * Scripts/webkitpy/webdriver_tests/pytest_runner.py:
+        (run):
+
 2018-01-30  John Wilander  <wilander@apple.com>
 
         Add callbacks to testRunner.statisticsSetShouldPartitionCookiesForHost() and testRunner.statisticsUpdateCookiePartitioning()
index 46ac8db..7cf0504 100644 (file)
@@ -190,7 +190,7 @@ def run(path, args, timeout, env, expectations, ignore_param=None):
 
     with TemporaryDirectory() as cache_directory:
         try:
-            cmd = ['--verbose',
+            cmd = ['-vv',
                    '--capture=no',
                    '--basetemp=%s' % cache_directory,
                    '--showlocals',
index 94fa7e2..e4dbd83 100644 (file)
@@ -1,3 +1,27 @@
+2018-01-31  Carlos Garcia Campos  <cgarcia@igalia.com>
+
+        Unreviewed. Update W3C WebDriver imported tests.
+
+        * imported/w3c/importer.json:
+        * imported/w3c/tools/wptrunner/requirements_sauce.txt:
+        * imported/w3c/tools/wptrunner/tox.ini:
+        * imported/w3c/tools/wptrunner/wptrunner/browsers/sauce.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/executormarionette.py:
+        * imported/w3c/tools/wptrunner/wptrunner/executors/pytestrunner/runner.py:
+        * imported/w3c/tools/wptrunner/wptrunner/metadata.py:
+        * imported/w3c/webdriver/OWNERS:
+        * imported/w3c/webdriver/tests/__init__.py:
+        * imported/w3c/webdriver/tests/actions/mouse.py:
+        * imported/w3c/webdriver/tests/actions/mouse_dblclick.py:
+        * imported/w3c/webdriver/tests/actions/support/mouse.py:
+        * imported/w3c/webdriver/tests/cookies/add_cookie.py:
+        * imported/w3c/webdriver/tests/cookies/delete_cookie.py:
+        * imported/w3c/webdriver/tests/cookies/get_named_cookie.py:
+        * imported/w3c/webdriver/tests/element_send_keys/form_controls.py: Added.
+        * imported/w3c/webdriver/tests/element_send_keys/interactability.py:
+        * imported/w3c/webdriver/tests/interaction/element_clear.py:
+        * imported/w3c/webdriver/tests/support/asserts.py:
+
 2018-01-26  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         Unreviewed. Update W3C WebDriver imported tests.
index 6197c9c..5395bd2 100644 (file)
@@ -1,6 +1,6 @@
 {
     "repository": "https://github.com/w3c/web-platform-tests.git",
-    "revision": "c83b6394196c00c260f021ffd3fb7d294cc50dd4",
+    "revision": "d848423ebc60882f98b6712d03ffe23af1ff7741",
     "paths_to_import": [
         "tools/webdriver",
         "tools/wptrunner",
index fa6aa3f..9be8c0e 100644 (file)
@@ -2,19 +2,21 @@
 xfail_strict=true
 
 [tox]
-envlist = {py27,pypy}-{base,chrome,firefox,servo},py27-flake8
+envlist = {py27,pypy}-{base,chrome,firefox,sauce,servo},py27-flake8
 
 [testenv]
 deps =
      pytest>=2.9
      pytest-cov
      pytest-xdist
+     mock
      -r{toxinidir}/requirements.txt
      chrome: -r{toxinidir}/requirements_chrome.txt
      firefox: -r{toxinidir}/requirements_firefox.txt
+     sauce: -r{toxinidir}/requirements_sauce.txt
      servo: -r{toxinidir}/requirements_servo.txt
 
-commands = pytest --cov
+commands = pytest {posargs:--cov}
 
 [testenv:py27-flake8]
 # flake8 versions should be kept in sync across tools/tox.ini, tools/wpt/tox.ini, and tools/wptrunner/tox.ini
index 25eeb56..4c1be1c 100644 (file)
@@ -156,10 +156,28 @@ class SauceConnect():
             "web-platform.test",
             "*.web-platform.test"
         ])
-        while not os.path.exists('./sauce_is_ready') and not self.sc_process.poll():
-            time.sleep(5)
 
-        if self.sc_process.returncode is not None and self.sc_process.returncode > 0:
+        # Timeout config vars
+        each_sleep_secs = 1
+        max_wait = 30
+        kill_wait = 5
+
+        tot_wait = 0
+        while not os.path.exists('./sauce_is_ready') and self.sc_process.poll() is None:
+            if tot_wait >= max_wait:
+                self.sc_process.terminate()
+                while self.sc_process.poll() is None:
+                    time.sleep(each_sleep_secs)
+                    tot_wait += each_sleep_secs
+                    if tot_wait >= (max_wait + kill_wait):
+                        self.sc_process.kill()
+                        break
+                raise SauceException("Sauce Connect Proxy was not ready after %d seconds" % tot_wait)
+
+            time.sleep(each_sleep_secs)
+            tot_wait += each_sleep_secs
+
+        if self.sc_process.returncode is not None:
             raise SauceException("Unable to start Sauce Connect Proxy. Process exited with code %s", self.sc_process.returncode)
 
     def __exit__(self, exc_type, exc_val, exc_tb):
index c76616b..faf087b 100644 (file)
@@ -131,13 +131,14 @@ class MarionetteProtocol(Protocol):
         self.logger.debug("Loading %s" % url)
         self.runner_handle = self.marionette.current_window_handle
         try:
-            self.marionette.navigate(url)
+            self.dismiss_alert(lambda: self.marionette.navigate(url))
         except Exception as e:
             self.logger.critical(
                 "Loading initial page %s failed. Ensure that the "
                 "there are no other programs bound to this port and "
                 "that your firewall rules or network setup does not "
                 "prevent access.\e%s" % (url, traceback.format_exc(e)))
+            raise
         self.marionette.execute_script(
             "document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
 
@@ -157,6 +158,7 @@ class MarionetteProtocol(Protocol):
 
         for handle in handles:
             try:
+                self.dismiss_alert(lambda: self.marionette.switch_to_window(handle))
                 self.marionette.switch_to_window(handle)
                 self.marionette.close()
             except errors.NoSuchWindowException:
@@ -168,6 +170,19 @@ class MarionetteProtocol(Protocol):
         if runner_handle != self.runner_handle:
             self.load_runner(protocol)
 
+    def dismiss_alert(self, f):
+        while True:
+            try:
+                f()
+            except errors.UnexpectedAlertOpen:
+                alert = self.marionette.switch_to_alert()
+                try:
+                    alert.dismiss()
+                except errors.NoAlertPresentException:
+                    pass
+            else:
+                break
+
     def wait(self):
         try:
             socket_timeout = self.marionette.client.socket_timeout
index e08b926..8eabf27 100644 (file)
@@ -52,7 +52,7 @@ def run(path, server_config, session_config, timeout=0):
     with TemporaryDirectory() as cache:
         try:
             pytest.main(["--strict",  # turn warnings into errors
-                         "--verbose",  # show each individual subtest
+                         "-vv",  # show each individual subtest and full failure logs
                          "--capture", "no",  # enable stdout/stderr from tests
                          "--basetemp", cache,  # temporary directory
                          "--showlocals",  # display contents of variables in local scope
index 9c31e42..92d77ba 100644 (file)
@@ -65,9 +65,9 @@ def update_expected(test_paths, serve_root, log_file_names,
                 for test in tree.iterchildren():
                     for subtest in test.iterchildren():
                         if subtest.new_disabled:
-                            print os.path.dirname(subtest.root.test_path) + "/" + subtest.name
+                            print "disabled: %s" % os.path.dirname(subtest.root.test_path) + "/" + subtest.name
                     if test.new_disabled:
-                        print test.root.test_path
+                        print "disabled: %s" % test.root.test_path
 
     results_changed = [item.test_path for item in expected_map.itervalues() if item.modified]
 
index e69de29..0ba172f 100644 (file)
@@ -0,0 +1,4 @@
+import pytest
+
+# Enable pytest assert introspection for assertion helper
+pytest.register_assert_rewrite('tests.support.asserts')
index 95d284c..bb7e145 100644 (file)
@@ -1,7 +1,8 @@
 import pytest
 
-from tests.actions.support.mouse import assert_move_to_coordinates, get_center
+from tests.actions.support.mouse import get_center
 from tests.actions.support.refine import get_events, filter_dict
+from tests.support.asserts import assert_move_to_coordinates
 from tests.support.inline import inline
 from tests.support.wait import wait
 
index f73f780..e9e4c26 100644 (file)
@@ -1,7 +1,8 @@
 import pytest
 
-from tests.actions.support.mouse import assert_move_to_coordinates, get_center
+from tests.actions.support.mouse import get_center
 from tests.actions.support.refine import get_events, filter_dict
+from tests.support.asserts import assert_move_to_coordinates
 
 
 _DBLCLICK_INTERVAL = 640
index 63a771d..d627ebb 100644 (file)
@@ -1,11 +1,3 @@
-def assert_move_to_coordinates(point, target, events):
-    for e in events:
-        if e["type"] != "mousemove":
-            assert e["pageX"] == point["x"]
-            assert e["pageY"] == point["y"]
-            assert e["target"] == target
-
-
 def get_center(rect):
     return {
         "x": rect["width"] / 2 + rect["x"],
index 1ebbcb4..ff4a0c9 100644 (file)
@@ -17,7 +17,7 @@ def test_add_domain_cookie(session, url, server_config):
     result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
     assert result.status == 200
     assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
+    assert result.body["value"] is None
 
     result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
     assert result.status == 200
@@ -54,7 +54,7 @@ def test_add_cookie_for_ip(session, url, server_config, configuration):
     result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
     assert result.status == 200
     assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
+    assert result.body["value"] is None
 
     result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
     assert result.status == 200
@@ -89,7 +89,7 @@ def test_add_non_session_cookie(session, url):
     result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
     assert result.status == 200
     assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
+    assert result.body["value"] is None
 
     result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
     assert result.status == 200
@@ -122,7 +122,7 @@ def test_add_session_cookie(session, url):
     result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
     assert result.status == 200
     assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
+    assert result.body["value"] is None
 
     result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
     assert result.status == 200
@@ -136,8 +136,8 @@ def test_add_session_cookie(session, url):
     assert isinstance(cookie["name"], basestring)
     assert "value" in cookie
     assert isinstance(cookie["value"], basestring)
-    assert "expiry" in cookie
-    assert cookie.get("expiry") is None
+    if "expiry" in cookie:
+        assert cookie.get("expiry") is None
 
     assert cookie["name"] == "hello"
     assert cookie["value"] == "world"
@@ -155,7 +155,7 @@ def test_add_session_cookie_with_leading_dot_character_in_domain(session, url, s
     result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
     assert result.status == 200
     assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
+    assert result.body["value"] is None
 
     result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
     assert result.status == 200
index 677fd94..813ee88 100644 (file)
@@ -119,5 +119,4 @@ def test_unknown_cookie(session):
     response = delete_cookie(session, "stilton")
     assert response.status == 200
     assert "value" in response.body
-    assert isinstance(response.body["value"], dict)
-    assert response.body["value"] == {}
+    assert response.body["value"] is None
index f367ecc..806dda3 100644 (file)
@@ -26,8 +26,8 @@ def test_get_named_session_cookie(session, url):
     assert isinstance(cookie["secure"], bool)
     assert "httpOnly" in cookie
     assert isinstance(cookie["httpOnly"], bool)
-    assert "expiry" in cookie
-    assert cookie.get("expiry") is None
+    if "expiry" in cookie:
+        assert cookie.get("expiry") is None
 
     assert cookie["name"] == "foo"
     assert cookie["value"] == "bar"
@@ -77,7 +77,7 @@ def test_duplicated_cookie(session, url, server_config):
     result = session.transport.send("POST", "session/%s/cookie" % session.session_id, create_cookie_request)
     assert result.status == 200
     assert "value" in result.body
-    assert isinstance(result.body["value"], dict)
+    assert result.body["value"] is None
 
     session.url = inline("<script>document.cookie = 'hello=newworld; domain=%s; path=/';</script>" % server_config["domains"][""])
     result = session.transport.send("GET", "session/%s/cookie" % session.session_id)
diff --git a/WebDriverTests/imported/w3c/webdriver/tests/element_send_keys/form_controls.py b/WebDriverTests/imported/w3c/webdriver/tests/element_send_keys/form_controls.py
new file mode 100644 (file)
index 0000000..ee6269e
--- /dev/null
@@ -0,0 +1,94 @@
+import pytest
+
+from tests.support.asserts import assert_error, assert_same_element, assert_success
+from tests.support.inline import inline
+
+
+def element_send_keys(session, element, text):
+    return session.transport.send(
+        "POST",
+        "/session/{session_id}/element/{element_id}/value".format(
+            session_id=session.session_id,
+            element_id=element.id),
+        {"text": text})
+
+
+def add_event_listeners(element):
+    element.session.execute_script("""
+        let [target] = arguments;
+        window.events = [];
+        for (let expected of ["focus", "blur", "change", "keypress", "keydown", "keyup", "input"]) {
+          target.addEventListener(expected, ({type}) => window.events.push(type));
+        }
+        """, args=(element,))
+
+
+def get_events(session):
+    return session.execute_script("return window.events")
+
+
+def test_input(session):
+    session.url = inline("<input>")
+    element = session.find.css("input", all=False)
+    assert element.property("value") == ""
+
+    element_send_keys(session, element, "foo")
+    assert element.property("value") == "foo"
+
+
+def test_textarea(session):
+    session.url = inline("<textarea>")
+    element = session.find.css("textarea", all=False)
+    assert element.property("value") == ""
+
+    element_send_keys(session, element, "foo")
+    assert element.property("value") == "foo"
+
+
+def test_input_append(session):
+    session.url = inline("<input value=a>")
+    element = session.find.css("input", all=False)
+    assert element.property("value") == "a"
+
+    element_send_keys(session, element, "b")
+    assert element.property("value") == "ab"
+
+    element_send_keys(session, element, "c")
+    assert element.property("value") == "abc"
+
+
+def test_textarea_append(session):
+    session.url = inline("<textarea>a</textarea>")
+    element = session.find.css("textarea", all=False)
+    assert element.property("value") == "a"
+
+    element_send_keys(session, element, "b")
+    assert element.property("value") == "ab"
+
+    element_send_keys(session, element, "c")
+    assert element.property("value") == "abc"
+
+
+@pytest.mark.parametrize("tag", ["input", "textarea"])
+def test_events(session, tag):
+    session.url = inline("<%s>" % tag)
+    element = session.find.css(tag, all=False)
+    add_event_listeners(element)
+
+    element_send_keys(session, element, "foo")
+    assert element.property("value") == "foo"
+    assert get_events(session) == ["focus",
+                                   "keydown",
+                                   "keypress",
+                                   "input",
+                                   "keyup",
+                                   "keydown",
+                                   "keypress",
+                                   "input",
+                                   "keyup",
+                                   "keydown",
+                                   "keypress",
+                                   "input",
+                                   "keyup",
+                                   "change",
+                                   "blur"]
index 5812f2b..bf1959f 100644 (file)
@@ -46,7 +46,7 @@ def test_document_element_is_interactable(session):
 
     response = send_keys_to_element(session, element, "foo")
     assert_success(response)
-    assert_same_element(session, element, session.active_element)
+    assert_same_element(session, body, session.active_element)
     assert result.property("value") == "foo"
 
 
@@ -65,7 +65,7 @@ def test_iframe_is_interactable(session):
 
     response = send_keys_to_element(session, frame, "foo")
     assert_success(response)
-    assert_same_element(session, frame, session.active_element)
+    assert_same_element(session, body, session.active_element)
 
     # Any key events are immediately routed to the nested
     # browsing context's active document.
index 73b798f..0443995 100644 (file)
@@ -1,9 +1,29 @@
+# META: timeout=long
+
 import pytest
 
-from tests.support.asserts import assert_error, assert_success
+from tests.support.asserts import (
+    assert_element_has_focus,
+    assert_error,
+    assert_success,
+)
 from tests.support.inline import inline
 
 
+def add_event_listeners(element):
+    element.session.execute_script("""
+        let [target] = arguments;
+        window.events = [];
+        for (let expected of ["focus", "blur", "change"]) {
+          target.addEventListener(expected, ({type}) => window.events.push(type));
+        }
+        """, args=(element,))
+
+
+def get_events(session):
+    return session.execute_script("return window.events")
+
+
 @pytest.fixture(scope="session")
 def text_file(tmpdir_factory):
     fh = tmpdir_factory.mktemp("tmp").join("hello.txt")
@@ -84,11 +104,17 @@ def test_keyboard_interactable(session):
 def test_input(session, type, value, default):
     session.url = inline("<input type=%s value='%s'>" % (type, value))
     element = session.find.css("input", all=False)
+    add_event_listeners(element)
     assert element.property("value") == value
 
     response = element_clear(session, element)
     assert_success(response)
     assert element.property("value") == default
+    events = get_events(session)
+    assert "focus" in events
+    assert "change" in events
+    assert "blur" in events
+    assert_element_has_focus(session.execute_script("return document.body"))
 
 
 @pytest.mark.parametrize("type",
@@ -144,11 +170,16 @@ def test_input_readonly(session, type):
 def test_textarea(session):
     session.url = inline("<textarea>foobar</textarea>")
     element = session.find.css("textarea", all=False)
+    add_event_listeners(element)
     assert element.property("value") == "foobar"
 
     response = element_clear(session, element)
     assert_success(response)
     assert element.property("value") == ""
+    events = get_events(session)
+    assert "focus" in events
+    assert "change" in events
+    assert "blur" in events
 
 
 def test_textarea_disabled(session):
@@ -190,7 +221,7 @@ def test_input_file_multiple(session, text_file):
 
 def test_select(session):
     session.url = inline("""
-        <select disabled>
+        <select>
           <option>foo
         </select>
         """)
@@ -231,33 +262,16 @@ def test_button_with_subtree(session):
 def test_contenteditable(session):
     session.url = inline("<p contenteditable>foobar</p>")
     element = session.find.css("p", all=False)
+    add_event_listeners(element)
     assert element.property("innerHTML") == "foobar"
 
     response = element_clear(session, element)
     assert_success(response)
     assert element.property("innerHTML") == ""
+    assert get_events(session) == ["focus", "change", "blur"]
+    assert_element_has_focus(session.execute_script("return document.body"))
 
 
-def test_contenteditable_focus(session):
-    session.url = inline("""
-        <p contenteditable>foobar</p>
-
-        <script>
-        window.events = [];
-        let p = document.querySelector("p");
-        for (let ev of ["focus", "blur"]) {
-          p.addEventListener(ev, ({type}) => window.events.push(type));
-        }
-        </script>
-        """)
-    element = session.find.css("p", all=False)
-    assert element.property("innerHTML") == "foobar"
-
-    response = element_clear(session, element)
-    assert_success(response)
-    assert element.property("innerHTML") == ""
-    assert session.execute_script("return window.events") == ["focus", "blur"]
-
 
 def test_designmode(session):
     session.url = inline("foobar")
@@ -268,48 +282,19 @@ def test_designmode(session):
     response = element_clear(session, element)
     assert_success(response)
     assert element.property("innerHTML") == "<br>"
-
-
-def test_resettable_element_focus(session):
-    session.url = inline("""
-        <input value="foobar">
-
-        <script>
-        window.events = [];
-        let input = document.querySelector("input");
-        for (let ev of ["focus", "blur"]) {
-          input.addEventListener(ev, ({type}) => window.events.push(type));
-        }
-        </script>
-        """)
-    element = session.find.css("input", all=False)
-    assert element.property("value") == "foobar"
-
-    response = element_clear(session, element)
-    assert_success(response)
-    assert element.property("value") == ""
-    assert session.execute_script("return window.events") == ["focus", "blur"]
+    assert_element_has_focus(session.execute_script("return document.body"))
 
 
 def test_resettable_element_focus_when_empty(session):
-    session.url = inline("""
-        <input>
-
-        <script>
-        window.events = [];
-        let p = document.querySelector("input");
-        for (let ev of ["focus", "blur"]) {
-          p.addEventListener(ev, ({type}) => window.events.push(type));
-        }
-        </script>
-        """)
+    session.url = inline("<input>")
     element = session.find.css("input", all=False)
+    add_event_listeners(element)
     assert element.property("value") == ""
 
     response = element_clear(session, element)
     assert_success(response)
     assert element.property("value") == ""
-    assert session.execute_script("return window.events") == []
+    assert get_events(session) == []
 
 
 @pytest.mark.parametrize("type,invalid_value",
index 95859ac..a1cd0f9 100644 (file)
@@ -137,3 +137,22 @@ def assert_same_element(session, a, b):
         pass
 
     raise AssertionError(message)
+
+
+def assert_element_has_focus(target_element):
+    session = target_element.session
+
+    active_element = session.execute_script("return document.activeElement")
+    active_tag = active_element.property("localName")
+    target_tag = target_element.property("localName")
+
+    assert active_element == target_element, (
+        "Focussed element is <%s>, not <%s>" % (active_tag, target_tag))
+
+
+def assert_move_to_coordinates(point, target, events):
+    for e in events:
+        if e["type"] != "mousemove":
+            assert e["pageX"] == point["x"]
+            assert e["pageY"] == point["y"]
+            assert e["target"] == target