diff --git a/playwright/element_handle.py b/playwright/element_handle.py index c24ae80c9..3986752f6 100644 --- a/playwright/element_handle.py +++ b/playwright/element_handle.py @@ -151,7 +151,7 @@ async def type( timeout: int = None, noWaitAfter: bool = None, ) -> None: - await self._channel.send("text", locals_to_params(locals())) + await self._channel.send("type", locals_to_params(locals())) async def press( self, key: str, delay: int = None, timeout: int = None, noWaitAfter: bool = None diff --git a/tests/async/conftest.py b/tests/async/conftest.py index d563ea315..cbba5d967 100644 --- a/tests/async/conftest.py +++ b/tests/async/conftest.py @@ -17,6 +17,12 @@ from playwright import async_playwright +# Will mark all the tests as async +def pytest_collection_modifyitems(items): + for item in items: + item.add_marker(pytest.mark.asyncio) + + @pytest.fixture(scope="session") async def playwright(): async with async_playwright() as playwright_object: diff --git a/tests/async/test_headful.py b/tests/async/test_headful.py index 43aa2b98d..3bdb50c85 100644 --- a/tests/async/test_headful.py +++ b/tests/async/test_headful.py @@ -34,6 +34,7 @@ async def test_headless_should_be_able_to_read_cookies_written_by_headful( ): if is_chromium and is_win: pytest.skip("see https://github.com/microsoft/playwright/issues/717") + return # Write a cookie in headful chrome headful_context = await browser_type.launchPersistentContext( tmpdir, **{**launch_arguments, "headless": False} diff --git a/tests/async/test_keyboard.py b/tests/async/test_keyboard.py index e6e2d9eea..233c91827 100644 --- a/tests/async/test_keyboard.py +++ b/tests/async/test_keyboard.py @@ -11,6 +11,36 @@ # 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. +import pytest + +from playwright.async_api import Page +from playwright.helper import Error + + +async def captureLastKeydown(page): + lastEvent = await page.evaluateHandle( + """() => { + const lastEvent = { + repeat: false, + location: -1, + code: '', + key: '', + metaKey: false, + keyIdentifier: 'unsupported' + }; + document.addEventListener('keydown', e => { + lastEvent.repeat = e.repeat; + lastEvent.location = e.location; + lastEvent.key = e.key; + lastEvent.code = e.code; + lastEvent.metaKey = e.metaKey; + // keyIdentifier only exists in WebKit, and isn't in TypeScript's lib. + lastEvent.keyIdentifier = 'keyIdentifier' in e && e.keyIdentifier; + }, true); + return lastEvent; + }""" + ) + return lastEvent async def test_keyboard_type_into_a_textarea(page): @@ -61,3 +91,462 @@ async def test_keyboard_send_a_character_with_elementhandle_press(page, server): ) await textarea.press("b") assert await page.evaluate("document.querySelector('textarea').value") == "a" + + +async def test_should_send_a_character_with_send_character(page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + await page.keyboard.insertText("嗨") + assert await page.evaluate('() => document.querySelector("textarea").value') == "嗨" + await page.evaluate( + '() => window.addEventListener("keydown", e => e.preventDefault(), true)' + ) + await page.keyboard.insertText("a") + assert await page.evaluate('() => document.querySelector("textarea").value') == "嗨a" + + +async def test_should_only_emit_input_event(page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + page.on("console", "m => console.log(m.text())") + events = await page.evaluateHandle( + """() => { + const events = []; + document.addEventListener('keydown', e => events.push(e.type)); + document.addEventListener('keyup', e => events.push(e.type)); + document.addEventListener('keypress', e => events.push(e.type)); + document.addEventListener('input', e => events.push(e.type)); + return events; + }""" + ) + + await page.keyboard.insertText("hello world") + assert await events.jsonValue() == ["input"] + + +async def test_should_report_shiftkey(page: Page, server, is_mac, is_firefox): + if is_firefox and is_mac: + pytest.skip() + await page.goto(server.PREFIX + "/input/keyboard.html") + keyboard = page.keyboard + codeForKey = {"Shift": 16, "Alt": 18, "Control": 17} + for modifierKey in codeForKey.keys(): + await keyboard.down(modifierKey) + assert ( + await page.evaluate("() => getResult()") + == "Keydown: " + + modifierKey + + " " + + modifierKey + + "Left " + + str(codeForKey[modifierKey]) + + " [" + + modifierKey + + "]" + ) + await keyboard.down("!") + # Shift+! will generate a keypress + if modifierKey == "Shift": + assert ( + await page.evaluate("() => getResult()") + == "Keydown: ! Digit1 49 [" + + modifierKey + + "]\nKeypress: ! Digit1 33 33 [" + + modifierKey + + "]" + ) + else: + assert ( + await page.evaluate("() => getResult()") + == "Keydown: ! Digit1 49 [" + modifierKey + "]" + ) + + await keyboard.up("!") + assert ( + await page.evaluate("() => getResult()") + == "Keyup: ! Digit1 49 [" + modifierKey + "]" + ) + await keyboard.up(modifierKey) + assert ( + await page.evaluate("() => getResult()") + == "Keyup: " + + modifierKey + + " " + + modifierKey + + "Left " + + str(codeForKey[modifierKey]) + + " []" + ) + + +async def test_should_report_multiple_modifiers(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + keyboard = page.keyboard + await keyboard.down("Control") + assert ( + await page.evaluate("() => getResult()") + == "Keydown: Control ControlLeft 17 [Control]" + ) + await keyboard.down("Alt") + assert ( + await page.evaluate("() => getResult()") + == "Keydown: Alt AltLeft 18 [Alt Control]" + ) + await keyboard.down(";") + assert ( + await page.evaluate("() => getResult()") + == "Keydown: ; Semicolon 186 [Alt Control]" + ) + await keyboard.up(";") + assert ( + await page.evaluate("() => getResult()") + == "Keyup: ; Semicolon 186 [Alt Control]" + ) + await keyboard.up("Control") + assert ( + await page.evaluate("() => getResult()") + == "Keyup: Control ControlLeft 17 [Alt]" + ) + await keyboard.up("Alt") + assert await page.evaluate("() => getResult()") == "Keyup: Alt AltLeft 18 []" + + +async def test_should_send_proper_codes_while_typing(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.type("!") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: ! Digit1 49 []", + "Keypress: ! Digit1 33 33 []", + "Keyup: ! Digit1 49 []", + ] + ) + await page.keyboard.type("^") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: ^ Digit6 54 []", + "Keypress: ^ Digit6 94 94 []", + "Keyup: ^ Digit6 54 []", + ] + ) + + +async def test_should_send_proper_codes_while_typing_with_shift(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + keyboard = page.keyboard + await keyboard.down("Shift") + await page.keyboard.type("~") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Shift ShiftLeft 16 [Shift]", + "Keydown: ~ Backquote 192 [Shift]", # 192 is ` keyCode + "Keypress: ~ Backquote 126 126 [Shift]", # 126 is ~ charCode + "Keyup: ~ Backquote 192 [Shift]", + ] + ) + await keyboard.up("Shift") + + +async def test_should_not_type_canceled_events(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + await page.evaluate( + """() => { + window.addEventListener('keydown', event => { + event.stopPropagation(); + event.stopImmediatePropagation(); + if (event.key === 'l') + event.preventDefault(); + if (event.key === 'o') + event.preventDefault(); + }, false); + }""" + ) + + await page.keyboard.type("Hello World!") + assert ( + await page.evalOnSelector("textarea", "textarea => textarea.value") == "He Wrd!" + ) + + +async def test_should_press_plus(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("+") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: + Equal 187 []", # 192 is ` keyCode + "Keypress: + Equal 43 43 []", # 126 is ~ charCode + "Keyup: + Equal 187 []", + ] + ) + + +async def test_should_press_shift_plus(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("Shift++") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Shift ShiftLeft 16 [Shift]", + "Keydown: + Equal 187 [Shift]", # 192 is ` keyCode + "Keypress: + Equal 43 43 [Shift]", # 126 is ~ charCode + "Keyup: + Equal 187 [Shift]", + "Keyup: Shift ShiftLeft 16 []", + ] + ) + + +async def test_should_support_plus_separated_modifiers(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("Shift+~") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Shift ShiftLeft 16 [Shift]", + "Keydown: ~ Backquote 192 [Shift]", # 192 is ` keyCode + "Keypress: ~ Backquote 126 126 [Shift]", # 126 is ~ charCode + "Keyup: ~ Backquote 192 [Shift]", + "Keyup: Shift ShiftLeft 16 []", + ] + ) + + +async def test_should_suport_multiple_plus_separated_modifiers(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("Control+Shift+~") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Control ControlLeft 17 [Control]", + "Keydown: Shift ShiftLeft 16 [Control Shift]", + "Keydown: ~ Backquote 192 [Control Shift]", # 192 is ` keyCode + "Keyup: ~ Backquote 192 [Control Shift]", + "Keyup: Shift ShiftLeft 16 [Control]", + "Keyup: Control ControlLeft 17 []", + ] + ) + + +async def test_should_shift_raw_codes(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("Shift+Digit3") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Shift ShiftLeft 16 [Shift]", + "Keydown: # Digit3 51 [Shift]", # 51 is # keyCode + "Keypress: # Digit3 35 35 [Shift]", # 35 is # charCode + "Keyup: # Digit3 51 [Shift]", + "Keyup: Shift ShiftLeft 16 []", + ] + ) + + +async def test_should_specify_repeat_property(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + lastEvent = await captureLastKeydown(page) + await page.keyboard.down("a") + assert await lastEvent.evaluate("e => e.repeat") is False + await page.keyboard.press("a") + assert await lastEvent.evaluate("e => e.repeat") + + await page.keyboard.down("b") + assert await lastEvent.evaluate("e => e.repeat") is False + await page.keyboard.down("b") + assert await lastEvent.evaluate("e => e.repeat") + + await page.keyboard.up("a") + await page.keyboard.down("a") + assert await lastEvent.evaluate("e => e.repeat") is False + + +async def test_should_type_all_kinds_of_characters(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + text = "This text goes onto two lines.\nThis character is 嗨." + await page.keyboard.type(text) + assert await page.evalOnSelector("textarea", "t => t.value") == text + + +async def test_should_specify_location(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + lastEvent = await captureLastKeydown(page) + textarea = await page.querySelector("textarea") + assert textarea + + await textarea.press("Digit5") + assert await lastEvent.evaluate("e => e.location") == 0 + + await textarea.press("ControlLeft") + assert await lastEvent.evaluate("e => e.location") == 1 + + await textarea.press("ControlRight") + assert await lastEvent.evaluate("e => e.location") == 2 + + await textarea.press("NumpadSubtract") + assert await lastEvent.evaluate("e => e.location") == 3 + + +async def test_should_press_enter(page: Page, server): + await page.setContent("") + await page.focus("textarea") + lastEventHandle = await captureLastKeydown(page) + + async def testEnterKey(key, expectedKey, expectedCode): + await page.keyboard.press(key) + lastEvent = await lastEventHandle.jsonValue() + assert lastEvent["key"] == expectedKey + assert lastEvent["code"] == expectedCode + value = await page.evalOnSelector("textarea", "t => t.value") + assert value == "\n" + await page.evalOnSelector("textarea", "t => t.value = ''") + + await testEnterKey("Enter", "Enter", "Enter") + await testEnterKey("NumpadEnter", "Enter", "NumpadEnter") + await testEnterKey("\n", "Enter", "Enter") + await testEnterKey("\r", "Enter", "Enter") + + +async def test_should_throw_unknown_keys(page: Page, server): + with pytest.raises(Error) as exc: + await page.keyboard.press("NotARealKey") + assert exc.value.message == 'Unknown key: "NotARealKey"' + + with pytest.raises(Error) as exc: + await page.keyboard.press("ё") + assert exc.value.message == 'Unknown key: "ё"' + + with pytest.raises(Error) as exc: + await page.keyboard.press("😊") + assert exc.value.message == 'Unknown key: "😊"' + + +async def test_should_type_emoji(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.type("textarea", "👹 Tokyo street Japan 🇯🇵") + assert ( + await page.evalOnSelector("textarea", "textarea => textarea.value") + == "👹 Tokyo street Japan 🇯🇵" + ) + + +async def test_should_type_emoji_into_an_iframe(page: Page, server, utils): + await page.goto(server.EMPTY_PAGE) + await utils.attach_frame(page, "emoji-test", server.PREFIX + "/input/textarea.html") + frame = page.frames[1] + textarea = await frame.querySelector("textarea") + assert textarea + await textarea.type("👹 Tokyo street Japan 🇯🇵") + assert ( + await frame.evalOnSelector("textarea", "textarea => textarea.value") + == "👹 Tokyo street Japan 🇯🇵" + ) + + +async def test_should_handle_select_all(page: Page, server, is_mac): + await page.goto(server.PREFIX + "/input/textarea.html") + textarea = await page.querySelector("textarea") + assert textarea + await textarea.type("some text") + modifier = "Meta" if is_mac else "Control" + await page.keyboard.down(modifier) + await page.keyboard.press("a") + await page.keyboard.up(modifier) + await page.keyboard.press("Backspace") + assert await page.evalOnSelector("textarea", "textarea => textarea.value") == "" + + +async def test_should_be_able_to_prevent_select_all(page, server, is_mac): + await page.goto(server.PREFIX + "/input/textarea.html") + textarea = await page.querySelector("textarea") + await textarea.type("some text") + await page.evalOnSelector( + "textarea", + """textarea => { + textarea.addEventListener('keydown', event => { + if (event.key === 'a' && (event.metaKey || event.ctrlKey)) + event.preventDefault(); + }, false); + }""", + ) + + modifier = "Meta" if is_mac else "Control" + await page.keyboard.down(modifier) + await page.keyboard.press("a") + await page.keyboard.up(modifier) + await page.keyboard.press("Backspace") + assert ( + await page.evalOnSelector("textarea", "textarea => textarea.value") + == "some tex" + ) + + +@pytest.mark.only_platform("darwin") +async def test_should_support_macos_shortcuts(page, server, is_firefox, is_mac): + await page.goto(server.PREFIX + "/input/textarea.html") + textarea = await page.querySelector("textarea") + await textarea.type("some text") + # select one word backwards + await page.keyboard.press("Shift+Control+Alt+KeyB") + await page.keyboard.press("Backspace") + assert ( + await page.evalOnSelector("textarea", "textarea => textarea.value") == "some " + ) + + +async def test_should_press_the_meta_key(page, server, is_firefox, is_mac): + lastEvent = await captureLastKeydown(page) + await page.keyboard.press("Meta") + v = await lastEvent.jsonValue() + metaKey = v["metaKey"] + key = v["key"] + code = v["code"] + if is_firefox and not is_mac: + assert key == "OS" + else: + assert key == "Meta" + + if is_firefox: + assert code == "OSLeft" + else: + assert code == "MetaLeft" + + if is_firefox and not is_mac: + assert metaKey is False + else: + assert metaKey + + +async def test_should_work_after_a_cross_origin_navigation(page, server): + await page.goto(server.PREFIX + "/empty.html") + await page.goto(server.CROSS_PROCESS_PREFIX + "/empty.html") + lastEvent = await captureLastKeydown(page) + await page.keyboard.press("a") + assert await lastEvent.evaluate("l => l.key") == "a" + + +# event.keyIdentifier has been removed from all browsers except WebKit +@pytest.mark.only_browser("webkit") +async def test_should_expose_keyIdentifier_in_webkit(page, server): + lastEvent = await captureLastKeydown(page) + keyMap = { + "ArrowUp": "Up", + "ArrowDown": "Down", + "ArrowLeft": "Left", + "ArrowRight": "Right", + "Backspace": "U+0008", + "Tab": "U+0009", + "Delete": "U+007F", + "a": "U+0041", + "b": "U+0042", + "F12": "F12", + } + for key, keyIdentifier in keyMap.items(): + await page.keyboard.press(key) + assert await lastEvent.evaluate("e => e.keyIdentifier") == keyIdentifier + + +async def test_should_scroll_with_pagedown(page: Page, server): + await page.goto(server.PREFIX + "/input/scrollable.html") + # A click is required for WebKit to send the event into the body. + await page.click("body") + await page.keyboard.press("PageDown") + # We can't wait for the scroll to finish, so just wait for it to start. + await page.waitForFunction("() => scrollY > 0") diff --git a/tests/conftest.py b/tests/conftest.py index 8e627a325..4225100b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,11 +28,6 @@ _dirname = get_file_dirname() -# Will mark all the tests as async -def pytest_collection_modifyitems(items): - for item in items: - item.add_marker(pytest.mark.asyncio) - def pytest_generate_tests(metafunc): if "browser_name" in metafunc.fixturenames: diff --git a/tests/sync/test_sync.py b/tests/sync/test_sync.py index d3656c463..926ef4896 100644 --- a/tests/sync/test_sync.py +++ b/tests/sync/test_sync.py @@ -197,3 +197,13 @@ def test_close_should_reject_all_promises(context): lambda: new_page.close(), ) assert "Protocol error" in exc_info.value.message + + +def test_expect_response_should_work(page: Page, server): + with page.expect_response("**/*") as resp: + page.goto(server.EMPTY_PAGE) + assert resp.value + assert resp.value.url == server.EMPTY_PAGE + assert resp.value.status == 200 + assert resp.value.ok + assert resp.value.request