diff options
Diffstat (limited to 'testing/web-platform/tests/webdriver')
14 files changed, 1497 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webdriver/OWNERS b/testing/web-platform/tests/webdriver/OWNERS new file mode 100644 index 000000000..45cf0102c --- /dev/null +++ b/testing/web-platform/tests/webdriver/OWNERS @@ -0,0 +1,4 @@ +@andreastt +@lukeis +@AutomatedTester +@shs96c diff --git a/testing/web-platform/tests/webdriver/README.md b/testing/web-platform/tests/webdriver/README.md new file mode 100644 index 000000000..bc863c47f --- /dev/null +++ b/testing/web-platform/tests/webdriver/README.md @@ -0,0 +1,100 @@ +# WebDriver specification tests + +Herein lies a set of conformance tests +for the W3C web browser automation specification +known as [WebDriver](http://w3c.github.io/webdriver/webdriver-spec.html). +The purpose of these tests is determine implementation compliance +so that different driver implementations can determine +whether they meet the recognised standard. + +## Chapters of the Spec that still need tests + +Note: Sections that are currently we believe are not quite stable enough for tests yet are in <span style="color:red;">red</span>. +Note: Sections that likely have enough tests for now are marked in <span style="color:green;">green</span>. + +* Routing Requests +* List of Endpoints (existance tests) +* List of Error Codes (Description is NON Normative) +* Capabilities +* Sessions +* Delete Session +* Set Timeouts +* Navigation +** Get Current URL +** Back +** Forward +** Refresh +** Get Title +* Command Contexts +** Get Window Handle +** Close Window +** Switch To Window +** Get Window Handles +** Switch To Frame +** Switch To Parent Frame +* Resizing and Positioning Windows +** Get Window Size +** Set Window Size +** Get Window Position +** Set Window Position +** Maximize Window +** Minimize Window +** Fullscreen Window +* Elements +** Element Interactability +** Get Active Element +* Element Retrieval +** Locator Strategies +*** CSS Selectors +*** Link Text +*** Partial Link Text +*** XPath +** Find Element +** Find Elements +** Find Element from Element +** Find Elements from Element +* Element State +** Is Element Selected +** Get Element Attribute +** Get Element Property +** Get Element CSS value +** Get Element Text +** Get Element Tag name +** Get Element Rect +** Is Element Enabled +* Element Interaction +** Element Click +** Element Clear +** Element Send Keys +* Document Handling +** Getting Page Source +** Executing Script +** Execute Script +** Execute Async Script +* Cookies +** Get All Cookies +** Get Named Cookies +** Add Cookie +** Delete Cookie +** Delete All Cookies +* <span style="color:red;">Actions +** Input State +** Processing Actions Requests +** Dispatching Actions +** General Actions +** Keyboard Actions +** Pointer Actions +** Perform Actions +** Remote End Steps (non-Normative) +** Releasing Actions</span> +* User Prompts +** Dismiss Alert +** Accept Alert +** Get Alert Text +** Send Alert Text +* Screen Capture +** Take Screenshot +** Take Element Screenshot +* <span style="color:green;">Privacy</span> +* <span style="color:green;">Security</span> +* Element Displayedness
\ No newline at end of file diff --git a/testing/web-platform/tests/webdriver/actions/__init__.py b/testing/web-platform/tests/webdriver/actions/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/__init__.py diff --git a/testing/web-platform/tests/webdriver/actions/conftest.py b/testing/web-platform/tests/webdriver/actions/conftest.py new file mode 100644 index 000000000..b51a6d4e0 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/conftest.py @@ -0,0 +1,34 @@ +import pytest + + +@pytest.fixture +def key_chain(session): + return session.actions.sequence("key", "keyboard_id") + + +@pytest.fixture +def mouse_chain(session): + return session.actions.sequence( + "pointer", + "pointer_id", + {"pointerType": "mouse"}) + + +@pytest.fixture(autouse=True) +def release_actions(session, request): + # release all actions after each test + # equivalent to a teardown_function, but with access to session fixture + request.addfinalizer(session.actions.release) + + +@pytest.fixture +def key_reporter(session, test_actions_page, request): + """Represents focused input element from `test_keys_page` fixture.""" + input_el = session.find.css("#keys", all=False) + input_el.click() + return input_el + + +@pytest.fixture +def test_actions_page(session, server): + session.url = server.where_is("webdriver/actions/support/test_actions_wdspec.html") diff --git a/testing/web-platform/tests/webdriver/actions/key.py b/testing/web-platform/tests/webdriver/actions/key.py new file mode 100644 index 000000000..d4f313c47 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/key.py @@ -0,0 +1,170 @@ +import pytest + +from support.keys import Keys +from support.refine import get_keys, filter_dict, get_events + + +def test_lone_keyup_sends_no_events(session, key_reporter, key_chain): + key_chain.key_up("a").perform() + assert len(get_keys(key_reporter)) == 0 + assert len(get_events(session)) == 0 + session.actions.release() + assert len(get_keys(key_reporter)) == 0 + assert len(get_events(session)) == 0 + + +# TODO - the harness bails with TIMEOUT before all these subtests complete +# The timeout is per file, so move to separate file with longer timeout? +# Need a way to set timeouts in py files (since can't do html meta) +# @pytest.mark.parametrize("name,expected", ALL_EVENTS.items()) +# def test_webdriver_special_key_sends_keydown(session, +# key_reporter, +# key_chain, +# name, +# expected): +# key_chain.key_down(getattr(Keys, name)).perform() +# # only interested in keydown +# first_event = get_events(session)[0] +# # make a copy so we throw out irrelevant keys and compare to events +# expected = dict(expected) +# del expected["value"] +# # check and remove keys that aren't in expected +# assert first_event["type"] == "keydown" +# assert first_event["repeat"] == False +# first_event = filter_dict(first_event, expected) +# assert first_event == expected +# # check that printable character was recorded in input field +# if len(expected["key"]) == 1: +# assert get_keys(key_reporter) == expected["key"] + + +@pytest.mark.parametrize("value,code", [ + (u"a", "KeyA",), + ("a", "KeyA",), + (u"\"", "Quote"), + (u",", "Comma"), + (u"\u00E0", ""), + (u"\u0416", ""), + (u"@", "Digit2"), + (u"\u2603", ""), + (u"\uF6C2", ""), # PUA +]) +def test_single_printable_key_sends_correct_events(session, + key_reporter, + key_chain, + value, + code): + key_chain \ + .key_down(value) \ + .key_up(value) \ + .perform() + expected = [ + {"code": code, "key": value, "type": "keydown"}, + {"code": code, "key": value, "type": "keypress"}, + {"code": code, "key": value, "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + assert events == expected + assert get_keys(key_reporter) == value + + +@pytest.mark.parametrize("value", [ + (u"\U0001F604"), + (u"\U0001F60D"), +]) +def test_single_emoji_records_correct_key(session, key_reporter, key_chain, value): + # Not using key_chain.send_keys() because we always want to treat value as + # one character here. `len(value)` varies by platform for non-BMP characters, + # so we don't want to iterate over value. + key_chain \ + .key_down(value) \ + .key_up(value) \ + .perform() + # events sent by major browsers are inconsistent so only check key value + assert get_keys(key_reporter) == value + + +@pytest.mark.parametrize("value,code,key", [ + (u"\uE050", "ShiftRight", "Shift"), + (u"\uE053", "OSRight", "Meta"), + (Keys.CONTROL, "ControlLeft", "Control"), +]) +def test_single_modifier_key_sends_correct_events(session, + key_reporter, + key_chain, + value, + code, + key): + key_chain \ + .key_down(value) \ + .key_up(value) \ + .perform() + all_events = get_events(session) + expected = [ + {"code": code, "key": key, "type": "keydown"}, + {"code": code, "key": key, "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in all_events] + assert events == expected + assert len(get_keys(key_reporter)) == 0 + + +@pytest.mark.parametrize("value,code,key", [ + (Keys.ESCAPE, "Escape", "Escape"), + (Keys.RIGHT, "ArrowRight", "ArrowRight"), +]) +def test_single_nonprintable_key_sends_events(session, + key_reporter, + key_chain, + value, + code, + key): + key_chain \ + .key_down(value) \ + .key_up(value) \ + .perform() + expected = [ + {"code": code, "key": key, "type": "keydown"}, + {"code": code, "key": key, "type": "keypress"}, + {"code": code, "key": key, "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + if len(events) == 2: + # most browsers don't send a keypress for non-printable keys + assert events == [expected[0], expected[2]] + else: + assert events == expected + assert len(get_keys(key_reporter)) == 0 + + +def test_sequence_of_keydown_printable_keys_sends_events(session, + key_reporter, + key_chain): + key_chain \ + .key_down("a") \ + .key_down("b") \ + .perform() + expected = [ + {"code": "KeyA", "key": "a", "type": "keydown"}, + {"code": "KeyA", "key": "a", "type": "keypress"}, + {"code": "KeyB", "key": "b", "type": "keydown"}, + {"code": "KeyB", "key": "b", "type": "keypress"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + assert events == expected + assert get_keys(key_reporter) == "ab" + + +def test_sequence_of_keydown_character_keys(session, key_reporter, key_chain): + key_chain.send_keys("ef").perform() + expected = [ + {"code": "KeyE", "key": "e", "type": "keydown"}, + {"code": "KeyE", "key": "e", "type": "keypress"}, + {"code": "KeyE", "key": "e", "type": "keyup"}, + {"code": "KeyF", "key": "f", "type": "keydown"}, + {"code": "KeyF", "key": "f", "type": "keypress"}, + {"code": "KeyF", "key": "f", "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + assert events == expected + assert get_keys(key_reporter) == "ef" diff --git a/testing/web-platform/tests/webdriver/actions/mouse.py b/testing/web-platform/tests/webdriver/actions/mouse.py new file mode 100644 index 000000000..a7192ef3b --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/mouse.py @@ -0,0 +1,31 @@ +from support.refine import get_events, filter_dict + + +def test_click_at_coordinates(session, test_actions_page, mouse_chain): + div_point = { + "x": 82, + "y": 187, + } + button = 0 + mouse_chain \ + .pointer_move(div_point["x"], div_point["y"], duration=1000) \ + .pointer_down(button) \ + .pointer_up(button) \ + .perform() + events = get_events(session) + assert len(events) == 4 + for e in events: + if e["type"] != "mousemove": + assert e["pageX"] == div_point["x"] + assert e["pageY"] == div_point["y"] + assert e["target"] == "outer" + if e["type"] != "mousedown": + assert e["buttons"] == 0 + assert e["button"] == button + expected = [ + {"type": "mousedown", "buttons": 1}, + {"type": "mouseup", "buttons": 0}, + {"type": "click", "buttons": 0}, + ] + filtered_events = [filter_dict(e, expected[0]) for e in events] + assert expected == filtered_events[1:] diff --git a/testing/web-platform/tests/webdriver/actions/sequence.py b/testing/web-platform/tests/webdriver/actions/sequence.py new file mode 100644 index 000000000..960b800df --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/sequence.py @@ -0,0 +1,31 @@ +from support.refine import get_keys, filter_dict, get_events + + +def test_no_actions_send_no_events(session, key_reporter, key_chain): + key_chain.perform() + assert len(get_keys(key_reporter)) == 0 + assert len(get_events(session)) == 0 + + +def test_release_char_sequence_sends_keyup_events_in_reverse(session, + key_reporter, + key_chain): + key_chain \ + .key_down("a") \ + .key_down("b") \ + .perform() + # reset so we only see the release events + session.execute_script("resetEvents();") + session.actions.release() + expected = [ + {"code": "KeyB", "key": "b", "type": "keyup"}, + {"code": "KeyA", "key": "a", "type": "keyup"}, + ] + events = [filter_dict(e, expected[0]) for e in get_events(session)] + assert events == expected + + +def test_release_no_actions_sends_no_events(session, key_reporter): + session.actions.release() + assert len(get_keys(key_reporter)) == 0 + assert len(get_events(session)) == 0 diff --git a/testing/web-platform/tests/webdriver/actions/support/__init__.py b/testing/web-platform/tests/webdriver/actions/support/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/support/__init__.py diff --git a/testing/web-platform/tests/webdriver/actions/support/keys.py b/testing/web-platform/tests/webdriver/actions/support/keys.py new file mode 100644 index 000000000..842c3db73 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/support/keys.py @@ -0,0 +1,812 @@ +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +""" +The Keys implementation. +""" + +from inspect import getmembers + + +class Keys(object): + """ + Set of special keys codes. + + See also https://w3c.github.io/webdriver/webdriver-spec.html#h-keyboard-actions + """ + + NULL = u"\ue000" + CANCEL = u"\ue001" # ^break + HELP = u"\ue002" + BACKSPACE = u"\ue003" + TAB = u"\ue004" + CLEAR = u"\ue005" + RETURN = u"\ue006" + ENTER = u"\ue007" + SHIFT = u"\ue008" + CONTROL = u"\ue009" + ALT = u"\ue00a" + PAUSE = u"\ue00b" + ESCAPE = u"\ue00c" + SPACE = u"\ue00d" + PAGE_UP = u"\ue00e" + PAGE_DOWN = u"\ue00f" + END = u"\ue010" + HOME = u"\ue011" + LEFT = u"\ue012" + UP = u"\ue013" + RIGHT = u"\ue014" + DOWN = u"\ue015" + INSERT = u"\ue016" + DELETE = u"\ue017" + SEMICOLON = u"\ue018" + EQUALS = u"\ue019" + + NUMPAD0 = u"\ue01a" # number pad keys + NUMPAD1 = u"\ue01b" + NUMPAD2 = u"\ue01c" + NUMPAD3 = u"\ue01d" + NUMPAD4 = u"\ue01e" + NUMPAD5 = u"\ue01f" + NUMPAD6 = u"\ue020" + NUMPAD7 = u"\ue021" + NUMPAD8 = u"\ue022" + NUMPAD9 = u"\ue023" + MULTIPLY = u"\ue024" + ADD = u"\ue025" + SEPARATOR = u"\ue026" + SUBTRACT = u"\ue027" + DECIMAL = u"\ue028" + DIVIDE = u"\ue029" + + F1 = u"\ue031" # function keys + F2 = u"\ue032" + F3 = u"\ue033" + F4 = u"\ue034" + F5 = u"\ue035" + F6 = u"\ue036" + F7 = u"\ue037" + F8 = u"\ue038" + F9 = u"\ue039" + F10 = u"\ue03a" + F11 = u"\ue03b" + F12 = u"\ue03c" + + META = u"\ue03d" + + # More keys from webdriver spec + ZENKAKUHANKAKU = u"\uE040" + R_SHIFT = u"\uE050" + R_CONTROL = u"\uE051" + R_ALT = u"\uE052" + R_META = u"\uE053" + R_PAGEUP = u"\uE054" + R_PAGEDOWN = u"\uE055" + R_END = u"\uE056" + R_HOME = u"\uE057" + R_ARROWLEFT = u"\uE058" + R_ARROWUP = u"\uE059" + R_ARROWRIGHT = u"\uE05A" + R_ARROWDOWN = u"\uE05B" + R_INSERT = u"\uE05C" + R_DELETE = u"\uE05D" + + +ALL_KEYS = getmembers(Keys, lambda x: type(x) == unicode) + +ALL_EVENTS = { + "ADD": { + "code": "", + "ctrl": False, + "key": "+", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue025", + "which": 0, + }, + "ALT": { + "code": "AltLeft", + "ctrl": False, + "key": "Alt", + "location": 1, + "meta": False, + "shift": False, + "value": u"\ue00a", + "which": 0, + }, + "BACKSPACE": { + "code": "Backspace", + "ctrl": False, + "key": "Backspace", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue003", + "which": 0, + }, + "CANCEL": { + "code": "", + "ctrl": False, + "key": "Cancel", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue001", + "which": 0, + }, + "CLEAR": { + "code": "", + "ctrl": False, + "key": "Clear", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue005", + "which": 0, + }, + "CONTROL": { + "code": "ControlLeft", + "ctrl": True, + "key": "Control", + "location": 1, + "meta": False, + "shift": False, + "value": u"\ue009", + "which": 0, + }, + "DECIMAL": { + "code": "NumpadDecimal", + "ctrl": False, + "key": ".", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue028", + "which": 0, + }, + "DELETE": { + "code": "Delete", + "ctrl": False, + "key": "Delete", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue017", + "which": 0, + }, + "DIVIDE": { + "code": "NumpadDivide", + "ctrl": False, + "key": "/", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue029", + "which": 0, + }, + "DOWN": { + "code": "ArrowDown", + "ctrl": False, + "key": "ArrowDown", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue015", + "which": 0, + }, + "END": { + "code": "End", + "ctrl": False, + "key": "End", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue010", + "which": 0, + }, + "ENTER": { + "code": "NumpadEnter", + "ctrl": False, + "key": "Enter", + "location": 1, + "meta": False, + "shift": False, + "value": u"\ue007", + "which": 0, + }, + "EQUALS": { + "code": "", + "ctrl": False, + "key": "=", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue019", + "which": 0, + }, + "ESCAPE": { + "code": "Escape", + "ctrl": False, + "key": "Escape", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00c", + "which": 0, + }, + "F1": { + "code": "F1", + "ctrl": False, + "key": "F1", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue031", + "which": 0, + }, + "F10": { + "code": "F10", + "ctrl": False, + "key": "F10", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue03a", + "which": 0, + }, + "F11": { + "code": "F11", + "ctrl": False, + "key": "F11", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue03b", + "which": 0, + }, + "F12": { + "code": "F12", + "ctrl": False, + "key": "F12", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue03c", + "which": 0, + }, + "F2": { + "code": "F2", + "ctrl": False, + "key": "F2", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue032", + "which": 0, + }, + "F3": { + "code": "F3", + "ctrl": False, + "key": "F3", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue033", + "which": 0, + }, + "F4": { + "code": "F4", + "ctrl": False, + "key": "F4", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue034", + "which": 0, + }, + "F5": { + "code": "F5", + "ctrl": False, + "key": "F5", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue035", + "which": 0, + }, + "F6": { + "code": "F6", + "ctrl": False, + "key": "F6", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue036", + "which": 0, + }, + "F7": { + "code": "F7", + "ctrl": False, + "key": "F7", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue037", + "which": 0, + }, + "F8": { + "code": "F8", + "ctrl": False, + "key": "F8", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue038", + "which": 0, + }, + "F9": { + "code": "F9", + "ctrl": False, + "key": "F9", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue039", + "which": 0, + }, + "HELP": { + "code": "Help", + "ctrl": False, + "key": "Help", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue002", + "which": 0, + }, + "HOME": { + "code": "Home", + "ctrl": False, + "key": "Home", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue011", + "which": 0, + }, + "INSERT": { + "code": "Insert", + "ctrl": False, + "key": "Insert", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue016", + "which": 0, + }, + "LEFT": { + "code": "ArrowLeft", + "ctrl": False, + "key": "ArrowLeft", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue012", + "which": 0, + }, + "META": { + "code": "OSLeft", + "ctrl": False, + "key": "Meta", + "location": 1, + "meta": True, + "shift": False, + "value": u"\ue03d", + "which": 0, + }, + "MULTIPLY": { + "code": "NumpadMultiply", + "ctrl": False, + "key": "*", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue024", + "which": 0, + }, + "NULL": { + "code": "", + "ctrl": False, + "key": "Unidentified", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue000", + "which": 0, + }, + "NUMPAD0": { + "code": "Numpad0", + "ctrl": False, + "key": "0", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01a", + "which": 0, + }, + "NUMPAD1": { + "code": "Numpad1", + "ctrl": False, + "key": "1", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01b", + "which": 0, + }, + "NUMPAD2": { + "code": "Numpad2", + "ctrl": False, + "key": "2", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01c", + "which": 0, + }, + "NUMPAD3": { + "code": "Numpad3", + "ctrl": False, + "key": "3", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01d", + "which": 0, + }, + "NUMPAD4": { + "code": "PageDown", + "ctrl": False, + "key": "4", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01e", + "which": 0, + }, + "NUMPAD5": { + "code": "PageUp", + "ctrl": False, + "key": "5", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue01f", + "which": 0, + }, + "NUMPAD6": { + "code": "Numpad6", + "ctrl": False, + "key": "6", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue020", + "which": 0, + }, + "NUMPAD7": { + "code": "Numpad7", + "ctrl": False, + "key": "7", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue021", + "which": 0, + }, + "NUMPAD8": { + "code": "Numpad8", + "ctrl": False, + "key": "8", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue022", + "which": 0, + }, + "NUMPAD9": { + "code": "Numpad9", + "ctrl": False, + "key": "9", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue023", + "which": 0, + }, + "PAGE_DOWN": { + "code": "", + "ctrl": False, + "key": "PageDown", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00f", + "which": 0, + }, + "PAGE_UP": { + "code": "", + "ctrl": False, + "key": "PageUp", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00e", + "which": 0, + }, + "PAUSE": { + "code": "", + "ctrl": False, + "key": "Pause", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00b", + "which": 0, + }, + "RETURN": { + "code": "Enter", + "ctrl": False, + "key": "Return", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue006", + "which": 0, + }, + "RIGHT": { + "code": "ArrowRight", + "ctrl": False, + "key": "ArrowRight", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue014", + "which": 0, + }, + "R_ALT": { + "code": "AltRight", + "ctrl": False, + "key": "Alt", + "location": 2, + "meta": False, + "shift": False, + "value": u"\ue052", + "which": 0, + }, + "R_ARROWDOWN": { + "code": "Numpad2", + "ctrl": False, + "key": "ArrowDown", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue05b", + "which": 0, + }, + "R_ARROWLEFT": { + "code": "Numpad4", + "ctrl": False, + "key": "ArrowLeft", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue058", + "which": 0, + }, + "R_ARROWRIGHT": { + "code": "Numpad6", + "ctrl": False, + "key": "ArrowRight", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue05a", + "which": 0, + }, + "R_ARROWUP": { + "code": "Numpad8", + "ctrl": False, + "key": "ArrowUp", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue059", + "which": 0, + }, + "R_CONTROL": { + "code": "ControlRight", + "ctrl": True, + "key": "Control", + "location": 2, + "meta": False, + "shift": False, + "value": u"\ue051", + "which": 0, + }, + "R_DELETE": { + "code": "NumpadDecimal", + "ctrl": False, + "key": "Delete", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue05d", + "which": 0, + }, + "R_END": { + "code": "Numpad1", + "ctrl": False, + "key": "End", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue056", + "which": 0, + }, + "R_HOME": { + "code": "Numpad7", + "ctrl": False, + "key": "Home", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue057", + "which": 0, + }, + "R_INSERT": { + "code": "Numpad0", + "ctrl": False, + "key": "Insert", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue05c", + "which": 0, + }, + "R_META": { + "code": "OSRight", + "ctrl": False, + "key": "Meta", + "location": 2, + "meta": True, + "shift": False, + "value": u"\ue053", + "which": 0, + }, + "R_PAGEDOWN": { + "code": "Numpad3", + "ctrl": False, + "key": "PageDown", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue055", + "which": 0, + }, + "R_PAGEUP": { + "code": "Numpad9", + "ctrl": False, + "key": "PageUp", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue054", + "which": 0, + }, + "R_SHIFT": { + "code": "ShiftRight", + "ctrl": False, + "key": "Shift", + "location": 2, + "meta": False, + "shift": True, + "value": u"\ue050", + "which": 0, + }, + "SEMICOLON": { + "code": "", + "ctrl": False, + "key": ";", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue018", + "which": 0, + }, + "SEPARATOR": { + "code": "NumpadSubtract", + "ctrl": False, + "key": ",", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue026", + "which": 0, + }, + "SHIFT": { + "code": "ShiftLeft", + "ctrl": False, + "key": "Shift", + "location": 1, + "meta": False, + "shift": True, + "value": u"\ue008", + "which": 0, + }, + "SPACE": { + "code": "Space", + "ctrl": False, + "key": " ", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue00d", + "which": 0, + }, + "SUBTRACT": { + "code": "", + "ctrl": False, + "key": "-", + "location": 3, + "meta": False, + "shift": False, + "value": u"\ue027", + "which": 0, + }, + "TAB": { + "code": "Tab", + "ctrl": False, + "key": "Tab", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue004", + "which": 0, + }, + "UP": { + "code": "ArrowUp", + "ctrl": False, + "key": "ArrowUp", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue013", + "which": 0, + }, + "ZENKAKUHANKAKU": { + "code": "", + "ctrl": False, + "key": "ZenkakuHankaku", + "location": 0, + "meta": False, + "shift": False, + "value": u"\ue040", + "which": 0, + } +} diff --git a/testing/web-platform/tests/webdriver/actions/support/refine.py b/testing/web-platform/tests/webdriver/actions/support/refine.py new file mode 100644 index 000000000..3a6d63e04 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/support/refine.py @@ -0,0 +1,33 @@ +def get_events(session): + """Return list of key events recorded in the test_keys_page fixture.""" + events = session.execute_script("return allEvents.events;") or [] + # `key` values in `allEvents` may be escaped (see `escapeSurrogateHalf` in + # test_keys_wdspec.html), so this converts them back into unicode literals. + for e in events: + # example: turn "U+d83d" (6 chars) into u"\ud83d" (1 char) + if "key" in e and e["key"].startswith(u"U+"): + key = e["key"] + hex_suffix = key[key.index("+") + 1:] + e["key"] = unichr(int(hex_suffix, 16)) + return events + + +def get_keys(input_el): + """Get printable characters entered into `input_el`. + + :param input_el: HTML input element. + """ + rv = input_el.property("value") + if rv is None: + return "" + else: + return rv + + +def filter_dict(source, d): + """Filter `source` dict to only contain same keys as `d` dict. + + :param source: dictionary to filter. + :param d: dictionary whose keys determine the filtering. + """ + return {k: source[k] for k in d.keys()} diff --git a/testing/web-platform/tests/webdriver/actions/support/test_actions_wdspec.html b/testing/web-platform/tests/webdriver/actions/support/test_actions_wdspec.html new file mode 100644 index 000000000..38b3885f6 --- /dev/null +++ b/testing/web-platform/tests/webdriver/actions/support/test_actions_wdspec.html @@ -0,0 +1,131 @@ +<!doctype html> +<meta charset=utf-8> +<head> + <title>Test Actions</title> + <style> + div { padding:0px; margin: 0px; } + #resultContainer { width: 600px; height: 60px; } + #outer { width: 100px; height: 50px; background-color: #ccc; } + #trackPointer { + width: 5px; + height: 5px; + border: solid 1px red; + position: fixed; } + </style> + <script> + var allEvents = {events: []}; + function displayMessage(message) { + document.getElementById("events").innerHTML = "<p>" + message + "</p>"; + } + + function appendMessage(message) { + document.getElementById("events").innerHTML += "<p>" + message + "</p>"; + } + + /** + * Escape |key| if it's in a surrogate-half character range. + * + * Example: given "\ud83d" return "U+d83d". + * + * Otherwise JSON.stringify will convert it to U+FFFD (REPLACEMENT CHARACTER) + * when returning a value from executeScript, for example. + */ + function escapeSurrogateHalf(key) { + if (typeof key !== "undefined" && key.length === 1) { + var charCode = key.charCodeAt(0); + var highSurrogate = charCode >= 0xD800 && charCode <= 0xDBFF; + var surrogate = highSurrogate || (charCode >= 0xDC00 && charCode <= 0xDFFF); + if (surrogate) { + key = "U+" + charCode.toString(16); + } + } + return key; + } + + function recordKeyboardEvent(event) { + var key = escapeSurrogateHalf(event.key); + allEvents.events.push({ + "code": event.code, + "key": key, + "which": event.which, + "location": event.location, + "ctrl": event.ctrlKey, + "meta": event.metaKey, + "shift": event.shiftKey, + "repeat": event.repeat, + "type": event.type + }); + appendMessage(`${event.type}(` + + `code: ${event.code}, ` + + `key: ${key}, ` + + `which: ${event.which})`); + } + + function recordPointerEvent(event) { + allEvents.events.push({ + "type": event.type, + "button": event.button, + "buttons": event.buttons, + "pageX": event.pageX, + "pageY": event.pageY, + "target": event.target.id + }); + appendMessage(`${event.type}(` + + `button: ${event.button}, ` + + `pageX: ${event.pageX}, ` + + `pageY: ${event.pageY}, ` + + `button: ${event.button}, ` + + `buttons: ${event.buttons}, ` + + `target id: ${event.target.id})`); + } + + function recordFirstPointerMove(event) { + recordPointerEvent(event); + window.removeEventListener("mousemove", recordFirstPointerMove); + } + + function resetEvents() { + allEvents.events.length = 0; + displayMessage(""); + } + + document.addEventListener("DOMContentLoaded", () => { + var keyReporter = document.getElementById("keys"); + ["keyup", "keypress", "keydown"].forEach((e) => { + keyReporter.addEventListener(e, recordKeyboardEvent); + }); + var outer = document.getElementById("outer"); + ["click", "dblclick", "mousedown", + "mouseup", "contextmenu"].forEach((e) => { + outer.addEventListener(e, recordPointerEvent); + }); + window.addEventListener("mousemove", recordFirstPointerMove); + //visual cue for mousemove + var pointer = document.getElementById("trackPointer"); + window.addEventListener("mousemove", (e) => { + setTimeout(() => { + let offset = 15; + pointer.style.top = e.pageY + offset + "px"; + pointer.style.left = e.pageX + offset + "px"; + }, 30); + }); + }); + </script> +</head> +<body> + <div id="trackPointer"></div> + <div> + <h2>KeyReporter</h2> + <input type="text" id="keys" size="80"> + </div> + <div> + <h2>ClickReporter</h2> + <div id="outer"> + </div> + </div> + <div id="resultContainer"> + <h2>Events</h2> + <div id="events"></div> + </div> +</body> +</html> diff --git a/testing/web-platform/tests/webdriver/contexts.py b/testing/web-platform/tests/webdriver/contexts.py new file mode 100644 index 000000000..9bbf97d46 --- /dev/null +++ b/testing/web-platform/tests/webdriver/contexts.py @@ -0,0 +1,27 @@ +def test_resize(session): + # setting the window size by webdriver is synchronous + # so we should see the results immediately + + session.window.size = (200, 100) + assert session.window.size == {"width": 100, "height": 200} + + session.window.size = (100, 200) + assert session.window.size == {"width": 200, "height": 100} + +def test_resize_by_script(session): + # setting the window size by JS is asynchronous + # so we poll waiting for the results + + size0 = session.window.size + + session.execute_script("window.resizeTo(100, 200)") + size1 = session.window.size + while size0 == size1: + size1 = session.window.size + assert size1 == {"width": 100, "height": 200} + + session.execute_script("window.resizeTo(200, 100)") + size2 = session.window.size + while size1 == size2: + size2 = session.window.size + assert size2 == {"width": 200, "height": 100} diff --git a/testing/web-platform/tests/webdriver/interface.html b/testing/web-platform/tests/webdriver/interface.html new file mode 100644 index 000000000..143a8643f --- /dev/null +++ b/testing/web-platform/tests/webdriver/interface.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<title>WebDriver interface test</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src=/resources/WebIDLParser.js></script> +<script src=/resources/idlharness.js></script> + +<script> +var t = new IdlArray(); +t.add_untested_idls("interface Navigator {};"); +t.add_idls("partial interface Navigator { readonly attribute boolean webdriver; };"); +t.add_objects({Navigator: ["navigator"]}); +t.test(); +</script> diff --git a/testing/web-platform/tests/webdriver/navigation.py b/testing/web-platform/tests/webdriver/navigation.py new file mode 100644 index 000000000..74f4ef9f7 --- /dev/null +++ b/testing/web-platform/tests/webdriver/navigation.py @@ -0,0 +1,109 @@ +import json +import pytest +import types +import urllib + +import webdriver + + +def inline(doc): + return "data:text/html;charset=utf-8,%s" % urllib.quote(doc) + + +alert_doc = inline("<script>window.alert()</script>") +frame_doc = inline("<p>frame") +one_frame_doc = inline("<iframe src='%s'></iframe>" % frame_doc) +two_frames_doc = inline("<iframe src='%s'></iframe>" % one_frame_doc) + + +@pytest.fixture +def new_window(session): + """Open new window and return the window handle.""" + windows_before = session.window_handles + name = session.execute_script("window.open()") + assert len(session.window_handles) == len(windows_before) + 1 + new_windows = session.window_handles - windows_before + return new_windows.pop() + + +# TODO(ato): 7.1 Get + + +def test_get_current_url_no_browsing_context(session, new_window): + # 7.2 step 1 + session.window_handle = new_window + session.close() + with pytest.raises(webdriver.NoSuchWindowException): + session.url = "about:blank" + + +def test_get_current_url_alert_prompt(session): + # 7.2 step 2 + session.url = alert_doc + with pytest.raises(webdriver.UnexpectedAlertOpenException): + session.url = "about:blank" + + +def test_get_current_url_matches_location(session): + # 7.2 step 3 + url = session.execute_script("return window.location.href") + assert session.url == url + + +def test_get_current_url_payload(http, session): + # 7.2 step 4-5 + session.start() + with http.get("/session/%s/url" % session.session_id) as resp: + assert resp.status == 200 + body = json.load(resp) + assert "value" in body + assert isinstance(body["value"], types.StringTypes) + + +def test_get_current_url_special_pages(session): + session.url = "about:blank" + assert session.url == "about:blank" + + +# TODO(ato): This test requires modification to pass on Windows +def test_get_current_url_file_protocol(session): + # tests that the browsing context remains the same + # when navigated privileged documents + session.url = "file:///" + assert session.url == "file:///" + + +# TODO(ato): Test for http:// and https:// protocols. +# We need to expose a fixture for accessing +# documents served by wptserve in order to test this. + + +def test_get_current_url_malformed_url(session): + session.url = "foo" + assert session.url + + +def test_get_current_url_after_modified_location(session): + session.execute_script("window.location.href = 'about:blank'") + assert session.url == "about:blank" + + +def test_get_current_url_nested_browsing_context(session): + session.url = one_frame_doc + top_level_url = session.url + frame = session.find.css("iframe", all=False) + session.switch_frame(frame) + assert session.url == top_level_url + + +def test_get_current_url_nested_browsing_contexts(session): + session.url = two_frames_doc + top_level_url = session.url + + outer_frame = session.find("iframe", all=False) + session.switch_frame(outer_frame) + + inner_frame = session.find("iframe", all=False) + session.switch_frame(frame) + + assert session.url == top_level_url |