Uh oh!
There was an error while loading. Please reload this page.
chore(roll): v1.61.0 - #3102
Conversation
Driver SHA: ac7cdd4bdf15f90fe7229243be6b35a53e0296d1 (v1.61.0-next) New APIs: - APIResponse.security_details / .server_addr - Credentials class (WebAuthn) + BrowserContext.credentials - WebStorage class + Page.local_storage / .session_storage - Screencast.start(size=), .show_actions(cursor=), ScreencastFrame.timestamp - BrowserType.connect_over_cdp(artifacts_dir=) Also: - Stop forcing options-bag properties to required=False in documentation_provider.py (Credentials.create(rp_id=) was the only case affected) - Update rolling skill to use driver/playwright-src - 37 new tests (async + sync)
| option = self_or_override(option) | ||
| option_name = to_snake_case(name_or_alias(option)) | ||
| option["name"] = option_name | ||
| option["required"] = False |
There was a problem hiding this comment.
in credentials.create(rp_id), this was making rp_id optional, which is incorrect.
There was a problem hiding this comment.
Ouch! We never had non-optional options. Let's discuss this on the meeting.
There was a problem hiding this comment.
Pull request overview
Rolls the bundled Playwright driver to a new upstream commit (ac7cdd4b…, v1.61.0-next) and ports newly added upstream APIs into the Python implementation, generated surfaces, and test suite.
Changes:
- Ported new API surface:
APIResponse.security_details()/.server_addr(), WebAuthnCredentialsviaBrowserContext.credentials,WebStorageviaPage.local_storage/.session_storage, and new Screencast options/fields (start(size=),show_actions(cursor=),ScreencastFrame.timestamp). - Extended CDP connection API with
BrowserType.connect_over_cdp(artifacts_dir=)and updated API generation inputs/types (ScreencastSize,VirtualCredential). - Updated driver pin + docs, and adjusted documentation generation to stop forcing all options-bag properties to optional.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/sync/test_screencast.py | Adds sync tests for new Screencast size/cursor/timestamp APIs. |
| tests/sync/test_page_web_storage.py | New sync tests for Page.local_storage / session_storage WebStorage access. |
| tests/sync/test_browsercontext_credentials.py | New sync tests for BrowserContext.credentials WebAuthn seeding APIs. |
| tests/async/test_screencast.py | Adds async tests for new Screencast size/cursor/timestamp APIs. |
| tests/async/test_page_web_storage.py | New async tests for Page.local_storage / session_storage WebStorage access. |
| tests/async/test_browsercontext_credentials.py | New async tests for BrowserContext.credentials WebAuthn seeding APIs. |
| scripts/generate_api.py | Updates API generation imports/registrations for newly ported impl classes and structures. |
| scripts/documentation_provider.py | Stops force-marking options-bag properties as optional (fixes requiredness for rp_id). |
| scripts/build_driver.sh | Updates driver source reference comment to main (driver pin still enforced via DRIVER_SHA). |
| README.md | Updates embedded browser version markers to match the rolled driver. |
| playwright/sync_api/_generated.py | Regenerates sync surface: adds new APIs, types, and doc updates. |
| playwright/sync_api/init.py | Re-exports new public TypedDict structures (ScreencastSize, VirtualCredential). |
| playwright/async_api/_generated.py | Regenerates async surface: adds new APIs, types, and doc updates. |
| playwright/async_api/init.py | Re-exports new public TypedDict structures (ScreencastSize, VirtualCredential). |
| playwright/_impl/_web_storage.py | Introduces WebStorage channel wrapper implementation. |
| playwright/_impl/_screencast.py | Adds Screencast size/cursor params and propagates timestamp into frames. |
| playwright/_impl/_page.py | Adds local_storage / session_storage properties backed by WebStorage instances. |
| playwright/_impl/_fetch.py | Exposes APIResponse.security_details() / .server_addr() from initializer data. |
| playwright/_impl/_credentials.py | Introduces Credentials channel wrapper implementation for WebAuthn. |
| playwright/_impl/_browser_type.py | Adds artifactsDir plumb-through for CDP connect. |
| playwright/_impl/_browser_context.py | Adds credentials property backed by new Credentials impl. |
| playwright/_impl/_api_structures.py | Adds ScreencastSize and VirtualCredential structures; extends ScreencastFrame with timestamp. |
| DRIVER_SHA | Updates pinned upstream driver commit hash. |
| .claude/skills/playwright-roll/SKILL.md | Updates roll skill docs to reference driver/playwright-src checkout path. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def test_show_actions_should_accept_cursor_param(page: Page) -> None: | ||
| page.screencast.start(on_frame=lambda f: None) | ||
| with page.screencast.show_actions(duration=100, cursor="pointer"): | ||
| pass | ||
| with page.screencast.show_actions(duration=100, cursor="none"): | ||
| pass | ||
| page.screencast.stop() |
| def test_start_should_accept_size_param(page: Page, server: Server) -> None: | ||
| received: list = [] | ||
| size: ScreencastSize = {"width": 800, "height": 600} | ||
| page.screencast.start(on_frame=lambda f: received.append(f), size=size) | ||
| page.goto(server.EMPTY_PAGE) | ||
| page.screenshot() | ||
| deadline = time.time() + 10 | ||
| while not received and time.time() < deadline: | ||
| page.wait_for_timeout(100) | ||
| page.screencast.stop() | ||
| assert len(received) >= 1 |
| def test_frames_should_include_timestamp(page: Page, server: Server) -> None: | ||
| received: list = [] | ||
| page.screencast.start(on_frame=lambda f: received.append(f)) | ||
| page.goto(server.EMPTY_PAGE) | ||
| page.screenshot() | ||
| deadline = time.time() + 10 | ||
| while not received and time.time() < deadline: | ||
| page.wait_for_timeout(100) | ||
| page.screencast.stop() | ||
| assert len(received) >= 1 | ||
| assert received[0]["timestamp"] > 0 |
| async def test_start_should_accept_size_param(page: Page, server: Server) -> None: | ||
| received: list = [] | ||
| event = asyncio.Event() | ||
| def on_frame(frame: ScreencastFrame) -> None: | ||
| received.append(frame) | ||
| event.set() | ||
| size: ScreencastSize = {"width": 800, "height": 600} | ||
| await page.screencast.start(on_frame=on_frame, size=size) | ||
| await page.goto(server.EMPTY_PAGE) | ||
| await page.screenshot() | ||
| await asyncio.wait_for(event.wait(), timeout=10) | ||
| await page.screencast.stop() | ||
| assert len(received) >= 1 |
| async def test_frames_should_include_timestamp(page: Page, server: Server) -> None: | ||
| received: list = [] | ||
| event = asyncio.Event() | ||
| def on_frame(frame: ScreencastFrame) -> None: | ||
| received.append(frame) | ||
| event.set() | ||
| await page.screencast.start(on_frame=on_frame) | ||
| await page.goto(server.EMPTY_PAGE) | ||
| await page.screenshot() | ||
| await asyncio.wait_for(event.wait(), timeout=10) | ||
| await page.screencast.stop() | ||
| assert len(received) >= 1 | ||
| assert received[0]["timestamp"] > 0 |
| async def test_local_storage_set_and_get_item(page: Page, server: Server) -> None: | ||
| await page.goto(server.EMPTY_PAGE) | ||
| await page.evaluate("() => localStorage.setItem('foo', 'bar')") | ||
| value = await page.local_storage.get_item("foo") | ||
| assert value == "bar" |
| async def test_local_storage_items(page: Page, server: Server) -> None: | ||
| await page.goto(server.EMPTY_PAGE) | ||
| await page.evaluate("() => localStorage.setItem('a', '1')") | ||
| await page.evaluate("() => localStorage.setItem('b', '2')") | ||
| items = await page.local_storage.items() | ||
| assert len(items) == 2 | ||
| assert {"name": "a", "value": "1"} in items | ||
| assert {"name": "b", "value": "2"} in items |
| async def test_session_storage_set_and_get_item(page: Page, server: Server) -> None: | ||
| await page.goto(server.EMPTY_PAGE) | ||
| await page.evaluate("() => sessionStorage.setItem('foo', 'bar')") | ||
| value = await page.session_storage.get_item("foo") | ||
| assert value == "bar" |
| result = creds.create( | ||
| rp_id="localhost", | ||
| id="test-credential-id", | ||
| private_key="private-key-data", | ||
| public_key="public-key-data", | ||
| ) | ||
| assert result["id"] == "test-credential-id" | ||
| assert result["rpId"] == "localhost" | ||
| result = await creds.create( | ||
| rp_id="localhost", | ||
| id="test-credential-id", | ||
| private_key="private-key-data", | ||
| public_key="public-key-data", | ||
| ) | ||
| assert result["id"] == "test-credential-id" | ||
| assert result["rpId"] == "localhost" | ||
- WebStorage tests: seed via dedicated API (set_item) instead of evaluate - Credentials tests: use auto-generated keys instead of fake placeholders - Screencast tests: replace size+timestamp test with upstream's onFrame receives viewport size; add ensureSomeFrames pattern; remove inconsistent try/finally cleanup
…change
Upstream commit ac7cdd4bd changed FrameExpectResult from {matches, received}
to void — expect returns nothing on success and throws ExpectError on failure.
Updates:
- _assertions.py: _expect_impl now catches driver Error and uses its message
directly; removes unused parse_value and FrameExpectResult imports.
- _frame.py: guard against None result from _expect channel call; change
return type to dict (callers don't use typed fields anymore).
- _locator.py: change _expect return type to dict for consistency.
- tests/{sync,async}/test_assertions.py: update 21 error-message assertions to
match the new upstream format (e.g. 'LocatorAssertions.to_have_text: Expect
failed\nCall log:\n - Expect "to_have_text" with timeout 300ms\n…' instead
of the old Python-formatted "Locator expected to …" / "Actual value: …").Upstream stopped recording "Wait for event" as separate trace actions. Remove the corresponding patterns from the two trace viewer tests so they match the actual 5 (context managers) and 1 (load state) actions now produced by the driver.
WebKit may report viewportWidth=1002 (instead of 1000) on the first screencast frame. Use `any()` check instead of iterating all frames, and increase rAF cycles from 3 to 100 for more reliable frame generation.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| option = self_or_override(option) | ||
| option_name = to_snake_case(name_or_alias(option)) | ||
| option["name"] = option_name | ||
| option["required"] = False |
There was a problem hiding this comment.
Ouch! We never had non-optional options. Let's discuss this on the meeting.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- port waitForEventInfo to __waitInfo__ so wait actions show in traces again - source error details from errorDetails with guid replacement, keep raw call log - restore format_call_log in assertion messages - drop unnecessary try/finally in new screencast tests
Uh oh!
There was an error while loading. Please reload this page.
This rolls the driver to
1cc5a90cfa3eaa430b1a991963100f95126caa47(v1.61) and ports the new upstream APIs:APIResponse.security_details/.server_addrCredentialsclass (WebAuthn) +BrowserContext.credentialsWebStorageclass +Page.local_storage/.session_storageScreencast.start(size=),.show_actions(cursor=),ScreencastFrame.timestampBrowserType.connect_over_cdp(artifacts_dir=)Also picks up the upstream
Credentials.create(rp_id, ...)shape, so Python can keep options-bag properties optional. The assertion port keeps the previous failure messages by readingFrameExpectErrorDetailsfrom protocol errors.The rolling skill is updated to reference
driver/playwright-srcinstead of~/code/playwright.