From 255d3b4b33e385c38bf783751c04a2dcb1ba9dcb Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 21 Oct 2026 21:25:21 -0700 Subject: [PATCH] chore: roll Playwright to 1.65.0-next (6e2c003f16) --- DRIVER_VERSION | 2 +- NODE_VERSION | 2 +- README.md | 2 +- playwright/_impl/_protocol.py | 7 +- playwright/_impl/browser_context.py | 5 +- playwright/_impl/connection.py | 3 + playwright/_impl/protocol/validator.py | 1 + playwright/_impl/protocol_channels.py | 6 +- playwright/_impl/tracing.py | 6 +- .../async/library/test_browsercontext_har.py | 8 +- .../library/test_browsercontext_locale.py | 25 ++- .../test_browsercontext_storage_state.py | 6 - tests/async/library/test_har.py | 28 +++ tests/async/page/test_page_drag.py | 41 +++- tests/async/page/test_page_fill.py | 5 +- tests/async/page/test_page_mouse.py | 12 +- tests/async/page/test_page_route.py | 19 +- tests/async/page/test_page_screenshot.py | 38 ++++ tests/sync/library/test_browsercontext_har.py | 8 +- .../library/test_browsercontext_locale.py | 22 +- .../test_browsercontext_storage_state.py | 6 - tests/sync/library/test_har.py | 28 +++ tests/sync/page/test_page_drag.py | 41 +++- tests/sync/page/test_page_fill.py | 5 +- tests/sync/page/test_page_mouse.py | 12 +- tests/sync/page/test_page_route.py | 17 +- tests/sync/page/test_page_screenshot.py | 38 ++++ tests/upstream_registry.md | 205 +++++++++--------- 28 files changed, 434 insertions(+), 164 deletions(-) diff --git a/DRIVER_VERSION b/DRIVER_VERSION index e95c523d9..97c65529f 100644 --- a/DRIVER_VERSION +++ b/DRIVER_VERSION @@ -1 +1 @@ -b335271cdc2b57cac2be8ad37c5dd283ac6b8938 +6e2c003f16e01822c3ea45bc894c2542a6e98189 diff --git a/NODE_VERSION b/NODE_VERSION index a43a5b943..ce62013c3 100644 --- a/NODE_VERSION +++ b/NODE_VERSION @@ -1 +1 @@ -24.22.0 +24.22.1 diff --git a/README.md b/README.md index 174f26bc6..fd83daf67 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H | :--- | :---: | :---: | :---: | | Chromium 156.0.8078.12 | ✅ | ✅ | ✅ | | WebKit 27.2 | ✅ | ✅ | ✅ | -| Firefox 157.0 | ✅ | ✅ | ✅ | +| Firefox 158.0 | ✅ | ✅ | ✅ | ## Documentation diff --git a/playwright/_impl/_protocol.py b/playwright/_impl/_protocol.py index 90aac2129..3e0d7384c 100644 --- a/playwright/_impl/_protocol.py +++ b/playwright/_impl/_protocol.py @@ -2207,7 +2207,12 @@ }, "webStorageClear": {"kind": {"type": "enum", "literals": ["local", "session"]}}, }, - "Root": {"initialize": {"sdkLanguage": "SDKLanguage"}}, + "Root": { + "initialize": { + "sdkLanguage": "SDKLanguage", + "supportsLoneSurrogates": "boolean?", + } + }, "Playwright": { "newRequest": { "baseURL": "string?", diff --git a/playwright/_impl/browser_context.py b/playwright/_impl/browser_context.py index db9ea750b..2ac048522 100644 --- a/playwright/_impl/browser_context.py +++ b/playwright/_impl/browser_context.py @@ -35,6 +35,7 @@ import base64 import json import os +import re from pathlib import Path from typing import ( TYPE_CHECKING, @@ -324,7 +325,9 @@ async def _initialize_har_from_options( if not record_har_path: return default_content: Literal["attach", "embed"] = ( - "attach" if str(record_har_path).endswith(".zip") else "embed" + "attach" + if re.search(r"\.zip$", str(record_har_path), re.IGNORECASE) + else "embed" ) await self.tracing._record_into_har( har=record_har_path, diff --git a/playwright/_impl/connection.py b/playwright/_impl/connection.py index 845f403d3..6c387f940 100644 --- a/playwright/_impl/connection.py +++ b/playwright/_impl/connection.py @@ -83,6 +83,9 @@ async def initialize(self) -> "Playwright": await self._channel.initialize( # Python: the SDK language of this client. sdkLanguage="python", + # json.dumps() escapes a lone surrogate as \udxxx, and json.loads() + # reads it back, as the JS client does. + supportsLoneSurrogates=True, timeout=NO_TIMEOUT, ) )["playwright"] diff --git a/playwright/_impl/protocol/validator.py b/playwright/_impl/protocol/validator.py index b16749396..c8f925af7 100644 --- a/playwright/_impl/protocol/validator.py +++ b/playwright/_impl/protocol/validator.py @@ -4181,6 +4181,7 @@ scheme["RootInitializeParams"] = t_object( { "sdkLanguage": t_type("SDKLanguage"), + "supportsLoneSurrogates": t_optional(t_boolean), } ) scheme["RootInitializeResult"] = t_object( diff --git a/playwright/_impl/protocol_channels.py b/playwright/_impl/protocol_channels.py index e73c755c4..94220c522 100644 --- a/playwright/_impl/protocol_channels.py +++ b/playwright/_impl/protocol_channels.py @@ -4952,11 +4952,15 @@ class RootChannel(Channel): def initialize( self, sdkLanguage: channels.SDKLanguage, + supportsLoneSurrogates: Optional[bool], timeout: float = 0, ) -> Awaitable[channels.RootInitializeResult]: return self._call( "initialize", - {"sdkLanguage": sdkLanguage}, + { + "sdkLanguage": sdkLanguage, + "supportsLoneSurrogates": supportsLoneSurrogates, + }, timeout, lambda result: {"playwright": result["playwright"]}, is_internal=True, diff --git a/playwright/_impl/tracing.py b/playwright/_impl/tracing.py index 969caff67..8453bedf9 100644 --- a/playwright/_impl/tracing.py +++ b/playwright/_impl/tracing.py @@ -192,7 +192,7 @@ async def start_har( # Not ported: the check of the resourcesDir option against a .zip har file; # the option is JavaScript only. default_content: Literal["attach", "embed"] = ( - "attach" if str(path).endswith(".zip") else "embed" + "attach" if re.search(r"\.zip$", str(path), re.IGNORECASE) else "embed" ) self._har_id = await self._record_into_har( har=path, @@ -222,7 +222,7 @@ async def _record_into_har( # Not ported: the resourcesDir option; it is JavaScript only. # Python: the path can be a pathlib.Path. har = str(har) - is_zip = har.endswith(".zip") + is_zip = bool(re.search(r"\.zip$", har, re.IGNORECASE)) options: Dict[str, Any] = { "content": update_content if update_content is not None else "attach", "urlGlob": url if isinstance(url, str) else None, @@ -250,7 +250,7 @@ async def _export_har(self, har_id: str) -> None: return del self._har_recorders[har_id] is_local = not self._connection.is_remote() - is_zip = har_params["path"].endswith(".zip") + is_zip = bool(re.search(r"\.zip$", har_params["path"], re.IGNORECASE)) if is_local: entries = ( diff --git a/tests/async/library/test_browsercontext_har.py b/tests/async/library/test_browsercontext_har.py index dde1a8a25..39683ad12 100644 --- a/tests/async/library/test_browsercontext_har.py +++ b/tests/async/library/test_browsercontext_har.py @@ -292,7 +292,7 @@ async def test_HA_085_should_round_trip_har_zip( server: Server, tmp_path: Path, ) -> None: - har_path = tmp_path / "har.zip" + har_path = tmp_path / "har.ZIP" context1 = await context_factory( record_har_mode="minimal", record_har_path=har_path ) @@ -300,6 +300,12 @@ async def test_HA_085_should_round_trip_har_zip( await page1.goto(server.PREFIX + "/one-style.html") await context1.close() + har_dir = tmp_path / "har" + with zipfile.ZipFile(har_path) as zip_file: + zip_file.extractall(har_dir) + log = json.loads((har_dir / "har.har").read_text(encoding="utf-8"))["log"] + assert isinstance(log["entries"][0]["response"]["content"]["_file"], str) + context2 = await context_factory() await context2.route_from_har(har_path, not_found="abort") page2 = await context2.new_page() diff --git a/tests/async/library/test_browsercontext_locale.py b/tests/async/library/test_browsercontext_locale.py index 8004d3dc5..f9cf75768 100644 --- a/tests/async/library/test_browsercontext_locale.py +++ b/tests/async/library/test_browsercontext_locale.py @@ -183,18 +183,33 @@ async def test_LC_010_should_propagate_locale_to_workers( context = await browser.new_context(locale="ru-RU") page = await context.new_page() await page.goto(server.EMPTY_PAGE) - async with page.expect_console_message( - lambda e: e.text.startswith("locale:") - ) as msg_info: - await page.evaluate( - """() => new Worker(URL.createObjectURL(new Blob(['console.log("locale:" + Intl.NumberFormat().resolvedOptions().locale)'], { type: 'application/javascript' })))""" + async with ( + page.expect_console_message(lambda e: e.text.startswith("locale:")) as msg_info, + page.expect_console_message( + lambda e: e.text.startswith("languages:") + ) as languages_info, + ): + request, _ = await asyncio.gather( + server.wait_for_request("/title.html"), + page.evaluate( + """url => new Worker(URL.createObjectURL(new Blob([` + console.log("locale:" + Intl.NumberFormat().resolvedOptions().locale); + console.log("languages:" + navigator.languages); + fetch(${JSON.stringify(url)}); + `], { type: 'application/javascript' })))""", + server.PREFIX + "/title.html", + ), ) msg = await msg_info.value + languages = await languages_info.value if browser_name == "webkit": # Webkit on Ubuntu is "ru-RU", and on other platforms is "ru" assert "locale:ru" in msg.text + assert "languages:ru" in languages.text else: assert msg.text == "locale:ru-RU" + assert languages.text == "languages:ru-RU" + assert "ru-RU" in (request.getHeader("accept-language") or "") await context.close() diff --git a/tests/async/library/test_browsercontext_storage_state.py b/tests/async/library/test_browsercontext_storage_state.py index dec2415ae..e07326ea3 100644 --- a/tests/async/library/test_browsercontext_storage_state.py +++ b/tests/async/library/test_browsercontext_storage_state.py @@ -433,12 +433,6 @@ async def test_ST_010_should_handle_malformed_file( ) in str(exc_info.value) -@pytest.mark.xfail( - reason="driver: it replaces lone surrogates with U+FFFD in the messages to the " - "clients other than JavaScript (toWellFormed() in cli/driver.ts), " - "https://github.com/microsoft/playwright/issues/43542", - strict=True, -) async def test_ST_011_should_serialize_storage_state_with_lone_surrogates( page: Page, context: BrowserContext, server: Server ) -> None: diff --git a/tests/async/library/test_har.py b/tests/async/library/test_har.py index 7451940b0..6196d9455 100644 --- a/tests/async/library/test_har.py +++ b/tests/async/library/test_har.py @@ -516,6 +516,34 @@ async def test_HA_021_should_include_content_smoke( assert content["compression"] == 0 +async def test_HA_113_should_store_binary_content_with_a_textual_mime_type_as_base64( + page_with_har: PageWithHar, server: Server, browser_name: str +) -> None: + if browser_name == "webkit": + pytest.skip( + "fixme: WebKit decodes the body of a textual resource before it gets to us" + ) + body = bytes([0x80, 0xC3, 0x28, 0xFF, 0x41]) + + def handle(request: TestServerRequest) -> None: + request.setHeader("Content-Type", "application/javascript") + request.write(body) + request.finish() + + server.set_route("/binary.js", handle) + har = await page_with_har() + await har.page.goto(server.EMPTY_PAGE) + await har.page.evaluate("() => fetch('/binary.js').then(r => r.arrayBuffer())") + log = await har.get_log() + content = next( + entry + for entry in log["entries"] + if entry["request"]["url"].endswith("/binary.js") + )["response"]["content"] + assert content["encoding"] == "base64" + assert base64.b64decode(content["text"]) == body + + async def test_HA_022_should_use_attach_mode_for_zip_extension( page_with_har: PageWithHar, server: Server ) -> None: diff --git a/tests/async/page/test_page_drag.py b/tests/async/page/test_page_drag.py index 44c732714..e54f7d4e5 100644 --- a/tests/async/page/test_page_drag.py +++ b/tests/async/page/test_page_drag.py @@ -278,10 +278,6 @@ async def test_DD_008_drag_and_drop_should_respect_the_drop_effect( is_mac: bool, headless: bool, ) -> None: - if browser_name == "webkit" and not is_linux: - pytest.skip( - "fixme: WebKit doesn't handle the drop effect correctly outside of linux." - ) if browser_name == "webkit" and is_linux and not headless: pytest.skip("fixme: https://github.com/microsoft/playwright/issues/21646") if browser_name == "chromium" and not is_mac and not headless: @@ -340,6 +336,43 @@ async def test_if_dropped(effect_allowed: str, drop_effect: str) -> Any: assert await test_if_dropped("uninitialized", "copy") is True +async def test_DD_028_drag_and_drop_should_report_the_default_drop_effect_from_effect_allowed( + page: Page, browser_name: str, is_linux: bool, headless: bool +) -> None: + if browser_name == "webkit" and is_linux and not headless: + pytest.skip("fixme: https://github.com/microsoft/playwright/issues/21646") + + await page.set_content( + """ +
drag target
+ this is the drop target + """ + ) + await page.evaluate( + """() => { + window['dropEffect'] = 'none'; + + document.querySelector('div').addEventListener('dragstart', event => { + event.dataTransfer.effectAllowed = 'copyMove'; + event.dataTransfer.setData('text/plain', 'drag data'); + }); + document.querySelector('div').addEventListener('dragend', event => { + window['dropEffect'] = event.dataTransfer.dropEffect; + }); + + // The target accepts the drag without setting dropEffect. + document.querySelector('drop-target').addEventListener('dragover', event => { + event.preventDefault(); + }); + }""" + ) + await page.hover("div") + await page.mouse.down() + await page.hover("drop-target") + await page.mouse.up() + assert await page.evaluate("dropEffect") == "move" + + async def test_DD_009_drag_and_drop_should_work_if_the_drag_is_canceled( page: Page, server: Server ) -> None: diff --git a/tests/async/page/test_page_fill.py b/tests/async/page/test_page_fill.py index 722e2ae89..13ea7d86e 100644 --- a/tests/async/page/test_page_fill.py +++ b/tests/async/page/test_page_fill.py @@ -412,12 +412,15 @@ async def test_FI_025_should_be_able_to_fill_when_focus_is_in_the_wrong_frame( await page.set_content( """
- + """ ) await page.focus("iframe") await page.fill("div", "some value") assert await page.eval_on_selector("div", "d => d.textContent") == "some value" + paragraph = page.frame_locator("iframe").locator("p") + await paragraph.fill("new") + await expect(paragraph).to_have_text("new") async def test_FI_026_should_be_able_to_fill_the_input_type_number( diff --git a/tests/async/page/test_page_mouse.py b/tests/async/page/test_page_mouse.py index 29cdb0532..27996fce8 100644 --- a/tests/async/page/test_page_mouse.py +++ b/tests/async/page/test_page_mouse.py @@ -359,17 +359,17 @@ async def test_MO_012_should_tween_mouse_movement( """() => { window['result'] = []; document.addEventListener('mousemove', event => { - window['result'].push([event.clientX, event.clientY]); + window['result'].push([event.clientX, event.clientY, event.movementX, event.movementY]); }); }""" ) await page.mouse.move(200, 300, steps=5) assert await page.evaluate("result") == [ - [120, 140], - [140, 180], - [160, 220], - [180, 260], - [200, 300], + [120, 140, 20, 40], + [140, 180, 20, 40], + [160, 220, 20, 40], + [180, 260, 20, 40], + [200, 300, 20, 40], ] diff --git a/tests/async/page/test_page_route.py b/tests/async/page/test_page_route.py index 80550def3..baa67e81b 100644 --- a/tests/async/page/test_page_route.py +++ b/tests/async/page/test_page_route.py @@ -1220,16 +1220,27 @@ async def test_RO_044_should_support_cors_for_different_methods( async def test_RO_045_should_support_the_times_parameter_with_route_matching( page: Page, server: Server ) -> None: + await page.goto(server.EMPTY_PAGE) + + async def fetch_body() -> str: + return await page.evaluate( + "() => fetch('/empty.html').then(response => response.text())" + ) + intercepted: List[int] = [] async def handler(route: Route) -> None: intercepted.append(1) - await route.continue_() + await route.fulfill(body="first") await page.route("**/empty.html", handler, times=1) - await page.goto(server.EMPTY_PAGE) - await page.goto(server.EMPTY_PAGE) - await page.goto(server.EMPTY_PAGE) + assert await fetch_body() == "first" + await page.route( + "**/empty.html", lambda route: route.fulfill(body="second"), times=1 + ) + assert await fetch_body() == "second" + await fetch_body() + await fetch_body() assert len(intercepted) == 1 diff --git a/tests/async/page/test_page_screenshot.py b/tests/async/page/test_page_screenshot.py index 1a2d20f5b..aefdbd546 100644 --- a/tests/async/page/test_page_screenshot.py +++ b/tests/async/page/test_page_screenshot.py @@ -505,6 +505,44 @@ async def test_SS_025_page_screenshot_should_work_for_webgl( assert_to_be_golden(screenshot, "screenshot-webgl.png") +@pytest.mark.usefixtures("skip_firefox_headed") +async def test_SS_095_page_screenshot_should_capture_webgl_canvas_after_its_drawing_buffer_is_resized( + page: Page, browser_name: str, platform: str, utils: Utils +) -> None: + if ( + browser_name == "webkit" + and platform == "darwin" + and os.uname().machine == "x86_64" + ): + pytest.skip( + "WebGL is not available on Intel macOS - https://bugs.webkit.org/show_bug.cgi?id=278277" + ) + + await page.set_viewport_size({"width": 200, "height": 200}) + await page.set_content( + '
' + ) + expected = await page.screenshot() + + await page.set_content( + """""" + ) + await utils.rafraf(page) + await page.evaluate("() => window.draw(50)") + await utils.rafraf(page) + assert compare_images(await page.screenshot(), expected) is None + + @pytest.mark.usefixtures("skip_firefox_headed") async def test_SS_026_page_screenshot_should_work_for_translate_z( page: Page, server: Server, assert_to_be_golden: GoldenAssert diff --git a/tests/sync/library/test_browsercontext_har.py b/tests/sync/library/test_browsercontext_har.py index 986c29b47..79c56d6f0 100644 --- a/tests/sync/library/test_browsercontext_har.py +++ b/tests/sync/library/test_browsercontext_har.py @@ -280,12 +280,18 @@ def test_HA_085_should_round_trip_har_zip( server: Server, tmp_path: Path, ) -> None: - har_path = tmp_path / "har.zip" + har_path = tmp_path / "har.ZIP" context1 = context_factory(record_har_mode="minimal", record_har_path=har_path) page1 = context1.new_page() page1.goto(server.PREFIX + "/one-style.html") context1.close() + har_dir = tmp_path / "har" + with zipfile.ZipFile(har_path) as zip_file: + zip_file.extractall(har_dir) + log = json.loads((har_dir / "har.har").read_text(encoding="utf-8"))["log"] + assert isinstance(log["entries"][0]["response"]["content"]["_file"], str) + context2 = context_factory() context2.route_from_har(har_path, not_found="abort") page2 = context2.new_page() diff --git a/tests/sync/library/test_browsercontext_locale.py b/tests/sync/library/test_browsercontext_locale.py index c764dd843..c11bbb4e3 100644 --- a/tests/sync/library/test_browsercontext_locale.py +++ b/tests/sync/library/test_browsercontext_locale.py @@ -188,18 +188,32 @@ def test_LC_010_should_propagate_locale_to_workers( context = browser.new_context(locale="ru-RU") page = context.new_page() page.goto(server.EMPTY_PAGE) - with page.expect_console_message( - lambda e: e.text.startswith("locale:") - ) as msg_info: + with ( + page.expect_console_message(lambda e: e.text.startswith("locale:")) as msg_info, + page.expect_console_message( + lambda e: e.text.startswith("languages:") + ) as languages_info, + server.expect_request("/title.html") as request_info, + ): page.evaluate( - """() => new Worker(URL.createObjectURL(new Blob(['console.log("locale:" + Intl.NumberFormat().resolvedOptions().locale)'], { type: 'application/javascript' })))""" + """url => new Worker(URL.createObjectURL(new Blob([` + console.log("locale:" + Intl.NumberFormat().resolvedOptions().locale); + console.log("languages:" + navigator.languages); + fetch(${JSON.stringify(url)}); + `], { type: 'application/javascript' })))""", + server.PREFIX + "/title.html", ) msg = msg_info.value + languages = languages_info.value + request = wait_for_server_request(page, request_info) if browser_name == "webkit": # Webkit on Ubuntu is "ru-RU", and on other platforms is "ru" assert "locale:ru" in msg.text + assert "languages:ru" in languages.text else: assert msg.text == "locale:ru-RU" + assert languages.text == "languages:ru-RU" + assert "ru-RU" in (request.getHeader("accept-language") or "") context.close() diff --git a/tests/sync/library/test_browsercontext_storage_state.py b/tests/sync/library/test_browsercontext_storage_state.py index d97a5e9c9..7c53b4d9a 100644 --- a/tests/sync/library/test_browsercontext_storage_state.py +++ b/tests/sync/library/test_browsercontext_storage_state.py @@ -433,12 +433,6 @@ def test_ST_010_should_handle_malformed_file( ) in str(exc_info.value) -@pytest.mark.xfail( - reason="driver: it replaces lone surrogates with U+FFFD in the messages to the " - "clients other than JavaScript (toWellFormed() in cli/driver.ts), " - "https://github.com/microsoft/playwright/issues/43542", - strict=True, -) def test_ST_011_should_serialize_storage_state_with_lone_surrogates( page: Page, context: BrowserContext, server: Server ) -> None: diff --git a/tests/sync/library/test_har.py b/tests/sync/library/test_har.py index ac0e740a6..19d352804 100644 --- a/tests/sync/library/test_har.py +++ b/tests/sync/library/test_har.py @@ -515,6 +515,34 @@ def test_HA_021_should_include_content_smoke( assert content["compression"] == 0 +def test_HA_113_should_store_binary_content_with_a_textual_mime_type_as_base64( + page_with_har: PageWithHar, server: Server, browser_name: str +) -> None: + if browser_name == "webkit": + pytest.skip( + "fixme: WebKit decodes the body of a textual resource before it gets to us" + ) + body = bytes([0x80, 0xC3, 0x28, 0xFF, 0x41]) + + def handle(request: TestServerRequest) -> None: + request.setHeader("Content-Type", "application/javascript") + request.write(body) + request.finish() + + server.set_route("/binary.js", handle) + har = page_with_har() + har.page.goto(server.EMPTY_PAGE) + har.page.evaluate("() => fetch('/binary.js').then(r => r.arrayBuffer())") + log = har.get_log() + content = next( + entry + for entry in log["entries"] + if entry["request"]["url"].endswith("/binary.js") + )["response"]["content"] + assert content["encoding"] == "base64" + assert base64.b64decode(content["text"]) == body + + def test_HA_022_should_use_attach_mode_for_zip_extension( page_with_har: PageWithHar, server: Server ) -> None: diff --git a/tests/sync/page/test_page_drag.py b/tests/sync/page/test_page_drag.py index 4257f056f..e0029d112 100644 --- a/tests/sync/page/test_page_drag.py +++ b/tests/sync/page/test_page_drag.py @@ -273,10 +273,6 @@ def test_DD_008_drag_and_drop_should_respect_the_drop_effect( is_mac: bool, headless: bool, ) -> None: - if browser_name == "webkit" and not is_linux: - pytest.skip( - "fixme: WebKit doesn't handle the drop effect correctly outside of linux." - ) if browser_name == "webkit" and is_linux and not headless: pytest.skip("fixme: https://github.com/microsoft/playwright/issues/21646") if browser_name == "chromium" and not is_mac and not headless: @@ -335,6 +331,43 @@ def test_if_dropped(effect_allowed: str, drop_effect: str) -> Any: assert test_if_dropped("uninitialized", "copy") is True +def test_DD_028_drag_and_drop_should_report_the_default_drop_effect_from_effect_allowed( + page: Page, browser_name: str, is_linux: bool, headless: bool +) -> None: + if browser_name == "webkit" and is_linux and not headless: + pytest.skip("fixme: https://github.com/microsoft/playwright/issues/21646") + + page.set_content( + """ +
drag target
+ this is the drop target + """ + ) + page.evaluate( + """() => { + window['dropEffect'] = 'none'; + + document.querySelector('div').addEventListener('dragstart', event => { + event.dataTransfer.effectAllowed = 'copyMove'; + event.dataTransfer.setData('text/plain', 'drag data'); + }); + document.querySelector('div').addEventListener('dragend', event => { + window['dropEffect'] = event.dataTransfer.dropEffect; + }); + + // The target accepts the drag without setting dropEffect. + document.querySelector('drop-target').addEventListener('dragover', event => { + event.preventDefault(); + }); + }""" + ) + page.hover("div") + page.mouse.down() + page.hover("drop-target") + page.mouse.up() + assert page.evaluate("dropEffect") == "move" + + def test_DD_009_drag_and_drop_should_work_if_the_drag_is_canceled( page: Page, server: Server ) -> None: diff --git a/tests/sync/page/test_page_fill.py b/tests/sync/page/test_page_fill.py index 2365592d5..535d62520 100644 --- a/tests/sync/page/test_page_fill.py +++ b/tests/sync/page/test_page_fill.py @@ -402,12 +402,15 @@ def test_FI_025_should_be_able_to_fill_when_focus_is_in_the_wrong_frame( page.set_content( """
- + """ ) page.focus("iframe") page.fill("div", "some value") assert page.eval_on_selector("div", "d => d.textContent") == "some value" + paragraph = page.frame_locator("iframe").locator("p") + paragraph.fill("new") + expect(paragraph).to_have_text("new") def test_FI_026_should_be_able_to_fill_the_input_type_number( diff --git a/tests/sync/page/test_page_mouse.py b/tests/sync/page/test_page_mouse.py index 518e0d6e5..3b8c6ecac 100644 --- a/tests/sync/page/test_page_mouse.py +++ b/tests/sync/page/test_page_mouse.py @@ -348,17 +348,17 @@ def test_MO_012_should_tween_mouse_movement( """() => { window['result'] = []; document.addEventListener('mousemove', event => { - window['result'].push([event.clientX, event.clientY]); + window['result'].push([event.clientX, event.clientY, event.movementX, event.movementY]); }); }""" ) page.mouse.move(200, 300, steps=5) assert page.evaluate("result") == [ - [120, 140], - [140, 180], - [160, 220], - [180, 260], - [200, 300], + [120, 140, 20, 40], + [140, 180, 20, 40], + [160, 220, 20, 40], + [180, 260, 20, 40], + [200, 300, 20, 40], ] diff --git a/tests/sync/page/test_page_route.py b/tests/sync/page/test_page_route.py index de2293a92..7a10557a2 100644 --- a/tests/sync/page/test_page_route.py +++ b/tests/sync/page/test_page_route.py @@ -1201,16 +1201,25 @@ def test_RO_044_should_support_cors_for_different_methods( def test_RO_045_should_support_the_times_parameter_with_route_matching( page: Page, server: Server ) -> None: + page.goto(server.EMPTY_PAGE) + + def fetch_body() -> str: + return page.evaluate( + "() => fetch('/empty.html').then(response => response.text())" + ) + intercepted: List[int] = [] def handler(route: Route) -> None: intercepted.append(1) - route.continue_() + route.fulfill(body="first") page.route("**/empty.html", handler, times=1) - page.goto(server.EMPTY_PAGE) - page.goto(server.EMPTY_PAGE) - page.goto(server.EMPTY_PAGE) + assert fetch_body() == "first" + page.route("**/empty.html", lambda route: route.fulfill(body="second"), times=1) + assert fetch_body() == "second" + fetch_body() + fetch_body() assert len(intercepted) == 1 diff --git a/tests/sync/page/test_page_screenshot.py b/tests/sync/page/test_page_screenshot.py index 3d7e434ba..200d4317e 100644 --- a/tests/sync/page/test_page_screenshot.py +++ b/tests/sync/page/test_page_screenshot.py @@ -505,6 +505,44 @@ def test_SS_025_page_screenshot_should_work_for_webgl( assert_to_be_golden(screenshot, "screenshot-webgl.png") +@pytest.mark.usefixtures("skip_firefox_headed") +def test_SS_095_page_screenshot_should_capture_webgl_canvas_after_its_drawing_buffer_is_resized( + page: Page, browser_name: str, platform: str, utils: Utils +) -> None: + if ( + browser_name == "webkit" + and platform == "darwin" + and os.uname().machine == "x86_64" + ): + pytest.skip( + "WebGL is not available on Intel macOS - https://bugs.webkit.org/show_bug.cgi?id=278277" + ) + + page.set_viewport_size({"width": 200, "height": 200}) + page.set_content( + '
' + ) + expected = page.screenshot() + + page.set_content( + """""" + ) + utils.rafraf(page) + page.evaluate("() => window.draw(50)") + utils.rafraf(page) + assert compare_images(page.screenshot(), expected) is None + + @pytest.mark.usefixtures("skip_firefox_headed") def test_SS_026_page_screenshot_should_work_for_translate_z( page: Page, server: Server, assert_to_be_golden: GoldenAssert diff --git a/tests/upstream_registry.md b/tests/upstream_registry.md index 442c8c679..ad2c6a1c5 100644 --- a/tests/upstream_registry.md +++ b/tests/upstream_registry.md @@ -26,108 +26,106 @@ subsystem, add a row to the Subsystems table. Then update this file: ## Subsystems -98 subsystems, 4933 tests (5330 with loop copies). - -| Code | Subsystem | Files | Tests | -|---|---|---|---| -| `AN` | Android WebViews | `library/unit/android-webviews` | 1 | -| `AR` | Aria snapshot | `page/{page-aria-snapshot, page-aria-snapshot-ai, page-aria-snapshot-json, to-match-aria-snapshot}`
`library/role-utils` | 170 | -| `AT` | Add script and style tags | `page/{page-add-script-tag, page-add-style-tag}` | 20 | -| `AU` | Authentication | `library/{browsercontext-credentials, browsercontext-webauthn}` | 24 | -| `AW` | Auto-waiting | `page/{page-autowaiting-basic, page-autowaiting-no-hang}` | 23 | -| `BC` | BrowserContext | `library/{browsercontext-basic, browsercontext-events, browsercontext-pages, browsercontext-page-event, browsercontext-reuse}` | 83 | -| `BP` | Browsers path | `library/browsers-path` | 2 | -| `BR` | Browser | `library/{browser, browser-server}` | 10 | -| `BT` | BrowserType | `library/{browsertype-basic, browsertype-launch, launcher, browsertype-launch-server, browsertype-connect, browsertype-launch-selenium}` | 103 | -| `BU` | Before unload | `library/beforeunload` | 12 | -| `CA` | Capabilities | `library/{capabilities, modernizr}` | 31 | -| `CC` | Client certificates | `library/client-certificates` | 41 | -| `CD` | CDP | `library/chromium/{connect-over-cdp, session}` | 52 | -| `CF` | Code frame | `library/unit/code-frame` | 13 | -| `CG` | Codegen | `library/inspector/{cli-codegen-1, cli-codegen-2, cli-codegen-3, cli-codegen-aria, cli-codegen-pick-locator, cli-codegen-javascript, cli-codegen-test, cli-codegen-python, cli-codegen-python-async, cli-codegen-pytest, cli-codegen-java, cli-codegen-csharp}`
`library/unit/codegen` | 191 | -| `CH` | Check | `page/page-check` | 17 | -| `CK` | Clock | `library/{page-clock, page-clock.frozen}`
`library/unit/clock` | 219 | -| `CL` | Click | `page/{page-click, page-click-scroll, page-click-react, page-click-during-navigation, page-click-timeout-1, page-click-timeout-2, page-click-timeout-3, page-click-timeout-4}` | 122 | -| `CM` | Console messages | `page/page-event-console` | 20 | -| `CN` | Channels | `library/channels` | 6 | -| `CO` | Cookies | `library/{browsercontext-cookies, browsercontext-add-cookies, browsercontext-clearcookies, browsercontext-cookies-third-party}` | 62 | -| `CR` | Chromium | `library/chromium/{chromium, launcher, extensions, bfcache, connect-to-worker, disable-web-security}` | 45 | -| `CS` | CSP | `library/browsercontext-csp` | 5 | -| `CV` | Coverage | `library/chromium/{js-coverage, css-coverage}`
`library/coverage` | 30 | -| `DB` | Debug | `library/{debugger, debug-controller}` | 22 | -| `DC` | Default (persistent) context | `library/{defaultbrowsercontext-1, defaultbrowsercontext-2}` | 42 | -| `DD` | Drag and drop | `page/{page-drag, page-drop}` | 27 | -| `DE` | Dispatch event | `page/page-dispatchevent` | 17 | -| `DI` | Dialog | `page/page-dialog` | 13 | -| `DO` | Download | `library/{download, downloads-path}` | 44 | -| `DV` | Device | `library/{browsercontext-device, browsercontext-dsf, browsercontext-viewport-mobile}` | 26 | -| `EE` | Event emitter | `page/page-listeners`
`library/events/{add-listeners, check-listener-leaks, events-list, listener-count, listeners-side-effects, listeners, max-listeners, method-names, modify-in-emit, num-args, once, prepend, remove-all-listeners-wait, remove-all-listeners, remove-listeners, set-max-listeners-side-effects, special-event-names, subclass, symbols}` | 56 | -| `EF` | Expose function | `page/page-expose-function`
`library/browsercontext-expose-function` | 28 | -| `EH` | ElementHandle | `page/{elementhandle-bounding-box, elementhandle-click, elementhandle-content-frame, elementhandle-convenience, elementhandle-eval-on-selector, elementhandle-misc, elementhandle-owner-frame, elementhandle-press, elementhandle-query-selector, elementhandle-screenshot, elementhandle-scroll-into-view, elementhandle-select-text, elementhandle-type, elementhandle-wait-for-element-state}` | 123 | -| `EM` | Emulation | `page/page-emulate-media`
`library/{emulation-focus, emulation-coop}` | 27 | -| `ER` | Page errors and crashes | `page/page-event-pageerror`
`library/page-event-crash` | 21 | -| `ES` | Eval on selector | `page/{eval-on-selector, eval-on-selector-all}` | 35 | -| `EV` | Evaluate | `page/{page-evaluate, page-evaluate-handle, page-evaluate-callback, page-evaluate-no-stall}` | 137 | -| `EX` | Expect | `page/{expect-boolean, expect-builtins, expect-matcher-result, expect-misc, expect-timeout, expect-to-have-accessible, expect-to-have-text, expect-to-have-value, expect-with-snapshot, matchers.misc}` | 328 | -| `FA` | Favicon | `library/favicon` | 1 | -| `FC` | File chooser | `page/page-filechooser` | 28 | -| `FE` | Fetch | `library/{global-fetch, global-fetch-cookie, browsercontext-fetch, browsercontext-fetch-algorithms, browsercontext-fetch-happy-eyeballs}` | 188 | -| `FF` | Firefox | `library/firefox/launcher`
`page/firefox-network-response` | 8 | -| `FI` | Fill | `page/page-fill` | 36 | -| `FO` | Focus | `page/page-focus` | 12 | -| `FR` | Frame | `page/{frame-evaluate, frame-frame-element, frame-goto, frame-hierarchy}` | 36 | -| `GE` | Geolocation | `library/geolocation` | 9 | -| `GN` | Locator and selector generator | `library/{locator-generator, selector-generator}` | 104 | -| `HA` | HAR | `library/{har, browsercontext-har, har-websocket}` | 112 | -| `HE` | Headful | `library/headful` | 16 | -| `HT` | Hit target | `library/hit-target` | 22 | -| `IF` | Input files | `page/page-set-input-files` | 21 | -| `IN` | Inspector | `library/inspector/{pause, recorder-api, console-api, title}` | 58 | -| `IS` | Init script | `page/{page-add-init-script, page-add-init-script-callback}`
`library/browsercontext-add-init-script` | 35 | -| `JH` | JSHandle | `page/{jshandle-as-element, jshandle-evaluate, jshandle-json-value, jshandle-properties, jshandle-to-string}` | 23 | -| `JS` | JSON schema | `library/unit/json-schema` | 10 | -| `KB` | Keyboard | `page/page-keyboard` | 53 | -| `LC` | Locale | `library/browsercontext-locale` | 14 | -| `LE` | Leaks | `page/page-leaks`
`library/heap` | 14 | -| `LG` | Logger | `library/logger` | 2 | -| `LH` | Locator handler | `page/page-add-locator-handler` | 14 | -| `LO` | Locator | `page/{locator-any-frame, locator-click, locator-convenience, locator-element-handle, locator-evaluate, locator-frame, locator-highlight, locator-is-visible, locator-list, locator-misc-1, locator-misc-2, locator-query, locator-wait-for-function}`
`library/locator-highlight` | 199 | -| `MC` | Multiple clients | `library/multiclient` | 19 | -| `MO` | Mouse | `page/{page-mouse, wheel}` | 23 | -| `NA` | Navigation | `page/{page-goto, page-navigation, page-history, page-event-load}`
`library/browsercontext-base-url` | 96 | -| `NE` | Network | `page/{page-network-request, page-network-response, page-network-sizes, page-network-idle, page-event-network, page-event-request, network-post-data, page-set-extra-http-headers}`
`library/{browsercontext-set-extra-http-headers, browsercontext-network-event, network-timeout, resource-timing, ignorehttpserrors}` | 142 | -| `OO` | OOPIF | `library/chromium/oopif` | 29 | -| `PA` | Page | `page/{page-basic, page-set-content, page-transition}`
`library/page-close` | 56 | -| `PC` | Playwright client | `library/playwright-client` | 1 | -| `PD` | PDF | `library/pdf` | 2 | -| `PE` | Permissions | `library/permissions` | 23 | -| `PO` | Popup | `page/page-event-popup`
`library/popup` | 28 | -| `PR` | Proxy | `library/{proxy, browsercontext-proxy, proxy-pattern, fetch-proxy}` | 47 | -| `PS` | Parser | `library/{locator-parser, component-parser, css-parser}` | 51 | -| `QS` | Query selector | `page/queryselector` | 17 | -| `RE` | Retarget | `page/retarget` | 15 | -| `RO` | Route | `page/{page-route, page-request-continue, page-request-fulfill, page-request-fallback, page-request-intercept, page-request-gc, interception}`
`library/{browsercontext-route, unroute-behavior}` | 201 | -| `SC` | Screencast | `library/{screencast, screencast-actions, screencast-overlay}` | 38 | -| `SE` | Selectors | `page/{selectors-css, selectors-text, selectors-role, selectors-get-by, selectors-frame, selectors-misc, selectors-register}`
`library/selectors-register` | 167 | -| `SI` | Signals | `library/signals` | 10 | -| `SL` | Slow motion | `library/slowmo` | 39 | -| `SM` | Strict mode | `page/page-strict`
`library/browsercontext-strict` | 14 | -| `SO` | Select option | `page/page-select-option` | 33 | -| `SQ` | Sequence | `library/unit/sequence` | 16 | -| `SS` | Screenshot | `page/page-screenshot`
`library/screenshot` | 94 | -| `ST` | Storage | `library/browsercontext-storage-state`
`page/{page-localstorage, page-cache-storage}` | 32 | -| `TA` | Tap | `library/{tap, locator-dispatchevent-touch}` | 10 | -| `TO` | Timeout runner | `library/unit/timeout-runner` | 1 | -| `TR` | Tracing | `library/tracing`
`library/chromium/tracing` | 49 | -| `TV` | Trace viewer | `library/{trace-viewer, snapshot-renderer}` | 135 | -| `TZ` | Timezone | `library/browsercontext-timezone-id` | 6 | -| `UA` | User agent | `library/browsercontext-user-agent` | 6 | -| `VI` | Video | `library/video` | 31 | -| `VP` | Viewport | `library/browsercontext-viewport` | 17 | -| `WA` | Wait for | `page/{page-wait-for-function, page-wait-for-selector-1, page-wait-for-selector-2, page-wait-for-load-state, page-wait-for-navigation, page-wait-for-url, page-wait-for-request, page-wait-for-response}` | 136 | -| `WM` | WebMCP | `library/{webmcp, webmcp-disabled}` | 15 | -| `WO` | Workers | `page/workers`
`library/{shared-worker, browsercontext-service-worker-policy}` | 31 | -| `WS` | WebSocket | `library/{web-socket, route-web-socket}` | 40 | +| Code | Subsystem | Files | +|---|---|---| +| `AN` | Android WebViews | `library/unit/android-webviews` | +| `AR` | Aria snapshot | `page/{page-aria-snapshot, page-aria-snapshot-ai, page-aria-snapshot-json, to-match-aria-snapshot}`
`library/role-utils` | +| `AT` | Add script and style tags | `page/{page-add-script-tag, page-add-style-tag}` | +| `AU` | Authentication | `library/{browsercontext-credentials, browsercontext-webauthn}` | +| `AW` | Auto-waiting | `page/{page-autowaiting-basic, page-autowaiting-no-hang}` | +| `BC` | BrowserContext | `library/{browsercontext-basic, browsercontext-events, browsercontext-pages, browsercontext-page-event, browsercontext-reuse}` | +| `BP` | Browsers path | `library/browsers-path` | +| `BR` | Browser | `library/{browser, browser-server}` | +| `BT` | BrowserType | `library/{browsertype-basic, browsertype-launch, launcher, browsertype-launch-server, browsertype-connect, browsertype-launch-selenium}` | +| `BU` | Before unload | `library/beforeunload` | +| `CA` | Capabilities | `library/{capabilities, modernizr}` | +| `CC` | Client certificates | `library/client-certificates` | +| `CD` | CDP | `library/chromium/{connect-over-cdp, session}` | +| `CF` | Code frame | `library/unit/code-frame` | +| `CG` | Codegen | `library/inspector/{cli-codegen-1, cli-codegen-2, cli-codegen-3, cli-codegen-aria, cli-codegen-pick-locator, cli-codegen-javascript, cli-codegen-test, cli-codegen-python, cli-codegen-python-async, cli-codegen-pytest, cli-codegen-java, cli-codegen-csharp}`
`library/unit/codegen` | +| `CH` | Check | `page/page-check` | +| `CK` | Clock | `library/{page-clock, page-clock.frozen}`
`library/unit/clock` | +| `CL` | Click | `page/{page-click, page-click-scroll, page-click-react, page-click-during-navigation, page-click-timeout-1, page-click-timeout-2, page-click-timeout-3, page-click-timeout-4}` | +| `CM` | Console messages | `page/page-event-console` | +| `CN` | Channels | `library/channels` | +| `CO` | Cookies | `library/{browsercontext-cookies, browsercontext-add-cookies, browsercontext-clearcookies, browsercontext-cookies-third-party}` | +| `CR` | Chromium | `library/chromium/{chromium, launcher, extensions, bfcache, connect-to-worker, disable-web-security}` | +| `CS` | CSP | `library/browsercontext-csp` | +| `CV` | Coverage | `library/chromium/{js-coverage, css-coverage}`
`library/coverage` | +| `DB` | Debug | `library/{debugger, debug-controller}` | +| `DC` | Default (persistent) context | `library/{defaultbrowsercontext-1, defaultbrowsercontext-2}` | +| `DD` | Drag and drop | `page/{page-drag, page-drop}` | +| `DE` | Dispatch event | `page/page-dispatchevent` | +| `DI` | Dialog | `page/page-dialog` | +| `DO` | Download | `library/{download, downloads-path}` | +| `DV` | Device | `library/{browsercontext-device, browsercontext-dsf, browsercontext-viewport-mobile}` | +| `EE` | Event emitter | `page/page-listeners`
`library/events/{add-listeners, check-listener-leaks, events-list, listener-count, listeners-side-effects, listeners, max-listeners, method-names, modify-in-emit, num-args, once, prepend, remove-all-listeners-wait, remove-all-listeners, remove-listeners, set-max-listeners-side-effects, special-event-names, subclass, symbols}` | +| `EF` | Expose function | `page/page-expose-function`
`library/browsercontext-expose-function` | +| `EH` | ElementHandle | `page/{elementhandle-bounding-box, elementhandle-click, elementhandle-content-frame, elementhandle-convenience, elementhandle-eval-on-selector, elementhandle-misc, elementhandle-owner-frame, elementhandle-press, elementhandle-query-selector, elementhandle-screenshot, elementhandle-scroll-into-view, elementhandle-select-text, elementhandle-type, elementhandle-wait-for-element-state}` | +| `EM` | Emulation | `page/page-emulate-media`
`library/{emulation-focus, emulation-coop}` | +| `ER` | Page errors and crashes | `page/page-event-pageerror`
`library/page-event-crash` | +| `ES` | Eval on selector | `page/{eval-on-selector, eval-on-selector-all}` | +| `EV` | Evaluate | `page/{page-evaluate, page-evaluate-handle, page-evaluate-callback, page-evaluate-no-stall}` | +| `EX` | Expect | `page/{expect-boolean, expect-builtins, expect-matcher-result, expect-misc, expect-timeout, expect-to-have-accessible, expect-to-have-text, expect-to-have-value, expect-with-snapshot, matchers.misc}` | +| `FA` | Favicon | `library/favicon` | +| `FC` | File chooser | `page/page-filechooser` | +| `FE` | Fetch | `library/{global-fetch, global-fetch-cookie, browsercontext-fetch, browsercontext-fetch-algorithms, browsercontext-fetch-happy-eyeballs}` | +| `FF` | Firefox | `library/firefox/launcher`
`page/firefox-network-response` | +| `FI` | Fill | `page/page-fill` | +| `FO` | Focus | `page/page-focus` | +| `FR` | Frame | `page/{frame-evaluate, frame-frame-element, frame-goto, frame-hierarchy}` | +| `GE` | Geolocation | `library/geolocation` | +| `GN` | Locator and selector generator | `library/{locator-generator, selector-generator}` | +| `HA` | HAR | `library/{har, browsercontext-har, har-websocket}` | +| `HE` | Headful | `library/headful` | +| `HT` | Hit target | `library/hit-target` | +| `IF` | Input files | `page/page-set-input-files` | +| `IN` | Inspector | `library/inspector/{pause, recorder-api, console-api, title}` | +| `IS` | Init script | `page/{page-add-init-script, page-add-init-script-callback}`
`library/browsercontext-add-init-script` | +| `JH` | JSHandle | `page/{jshandle-as-element, jshandle-evaluate, jshandle-json-value, jshandle-properties, jshandle-to-string}` | +| `JS` | JSON schema | `library/unit/json-schema` | +| `KB` | Keyboard | `page/page-keyboard` | +| `LC` | Locale | `library/browsercontext-locale` | +| `LE` | Leaks | `page/page-leaks`
`library/heap` | +| `LG` | Logger | `library/logger` | +| `LH` | Locator handler | `page/page-add-locator-handler` | +| `LO` | Locator | `page/{locator-any-frame, locator-click, locator-convenience, locator-element-handle, locator-evaluate, locator-frame, locator-highlight, locator-is-visible, locator-list, locator-misc-1, locator-misc-2, locator-query, locator-wait-for-function}`
`library/locator-highlight` | +| `MC` | Multiple clients | `library/multiclient` | +| `MO` | Mouse | `page/{page-mouse, wheel}` | +| `NA` | Navigation | `page/{page-goto, page-navigation, page-history, page-event-load}`
`library/browsercontext-base-url` | +| `NE` | Network | `page/{page-network-request, page-network-response, page-network-sizes, page-network-idle, page-event-network, page-event-request, network-post-data, page-set-extra-http-headers}`
`library/{browsercontext-set-extra-http-headers, browsercontext-network-event, network-timeout, resource-timing, ignorehttpserrors}` | +| `OO` | OOPIF | `library/chromium/oopif` | +| `PA` | Page | `page/{page-basic, page-set-content, page-transition}`
`library/page-close` | +| `PC` | Playwright client | `library/playwright-client` | +| `PD` | PDF | `library/pdf` | +| `PE` | Permissions | `library/permissions` | +| `PO` | Popup | `page/page-event-popup`
`library/popup` | +| `PR` | Proxy | `library/{proxy, browsercontext-proxy, proxy-pattern, fetch-proxy}` | +| `PS` | Parser | `library/{locator-parser, component-parser, css-parser}` | +| `QS` | Query selector | `page/queryselector` | +| `RE` | Retarget | `page/retarget` | +| `RO` | Route | `page/{page-route, page-request-continue, page-request-fulfill, page-request-fallback, page-request-intercept, page-request-gc, interception}`
`library/{browsercontext-route, unroute-behavior}` | +| `SC` | Screencast | `library/{screencast, screencast-actions, screencast-overlay}` | +| `SE` | Selectors | `page/{selectors-css, selectors-text, selectors-role, selectors-get-by, selectors-frame, selectors-misc, selectors-register}`
`library/selectors-register` | +| `SI` | Signals | `library/signals` | +| `SL` | Slow motion | `library/slowmo` | +| `SM` | Strict mode | `page/page-strict`
`library/browsercontext-strict` | +| `SO` | Select option | `page/page-select-option` | +| `SQ` | Sequence | `library/unit/sequence` | +| `SS` | Screenshot | `page/page-screenshot`
`library/screenshot` | +| `ST` | Storage | `library/browsercontext-storage-state`
`page/{page-localstorage, page-cache-storage}` | +| `TA` | Tap | `library/{tap, locator-dispatchevent-touch}` | +| `TO` | Timeout runner | `library/unit/timeout-runner` | +| `TR` | Tracing | `library/tracing`
`library/chromium/tracing` | +| `TV` | Trace viewer | `library/{trace-viewer, snapshot-renderer}` | +| `TZ` | Timezone | `library/browsercontext-timezone-id` | +| `UA` | User agent | `library/browsercontext-user-agent` | +| `VI` | Video | `library/video` | +| `VP` | Viewport | `library/browsercontext-viewport` | +| `WA` | Wait for | `page/{page-wait-for-function, page-wait-for-selector-1, page-wait-for-selector-2, page-wait-for-load-state, page-wait-for-navigation, page-wait-for-url, page-wait-for-request, page-wait-for-response}` | +| `WM` | WebMCP | `library/{webmcp, webmcp-disabled}` | +| `WO` | Workers | `page/workers`
`library/{shared-worker, browsercontext-service-worker-policy}` | +| `WS` | WebSocket | `library/{web-socket, route-web-socket}` | ## Tests @@ -1658,6 +1656,7 @@ subsystem, add a row to the Subsystems table. Then update this file: | `DD-025` | `page/page-drop.spec.ts` | should drop files and data together | | `DD-026` | `page/page-drop.spec.ts` | should throw when target does not accept drop | | `DD-027` | `page/page-drop.spec.ts` | should throw when neither files nor data provided | +| `DD-028` | `page/page-drag.spec.ts` | Drag and drop › should report the default drop effect from effectAllowed | ### DE — Dispatch event @@ -3157,6 +3156,7 @@ subsystem, add a row to the Subsystems table. Then update this file: | `HA-110` | `library/har-websocket.spec.ts` | should still allow routeWebSocket to fully mock the connection when capturing HAR | | `HA-111` | `library/har-websocket.spec.ts` | should still allow routeWebSocket to modify messages when capturing HAR | | `HA-112` | `library/har-websocket.spec.ts` | should respect PLAYWRIGHT\_HAR\_NO\_WEBSOCKET\_FRAMES | +| `HA-113` | `library/har.spec.ts` | should store binary content with a textual mime type as base64 | ### HE — Headful @@ -4979,6 +4979,7 @@ subsystem, add a row to the Subsystems table. Then update this file: | `SS-092` | `library/screenshot.spec.ts` | element screenshot › should capture full element when larger than viewport with device scale factor | | `SS-093` | `library/screenshot.spec.ts` | element screenshot › should capture full element when larger than viewport with device scale factor and scale:css | | `SS-094` | `library/screenshot.spec.ts` | element screenshot › page screenshot should capture css transform with device pixels | +| `SS-095` | `page/page-screenshot.spec.ts` | page screenshot › should capture webgl canvas after its drawing buffer is resized | ### ST — Storage