chore(roll): update driver to ac7cdd4bd, port new APIs, add tests#3102
chore(roll): update driver to ac7cdd4bd, port new APIs, add tests#3102Skn0tt wants to merge 7 commits into
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.
| ) | ||
| if result is None: | ||
| return {} | ||
| if result.get("received"): |
There was a problem hiding this comment.
I don't think there is received in result anymore.
| options: FrameExpectOptions, | ||
| title: str = None, | ||
| ) -> FrameExpectResult: | ||
| ) -> dict: |
There was a problem hiding this comment.
Might as well return None, there is nothing in the dict.
| 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.
|
|
||
| assert excinfo.match("Locator expected to contain class 'does-not-exist'") | ||
| assert excinfo.match("Actual value: foo bar baz") | ||
| assert excinfo.match("Expect failed") |
There was a problem hiding this comment.
I don't think any expectations for assertions should change.
| else: | ||
| actual = received | ||
| try: | ||
| await self._call_expect(expression, expect_options, title) |
There was a problem hiding this comment.
Hmm... I don't see where we are handling ErrorDetails received in Frame._expect error case and appending received/ariaSnapshot as before. This logic is still needed, but is triggered upon ErrorDetails instead of a Result.
This rolls the driver to
ac7cdd4bdf15f90fe7229243be6b35a53e0296d1(latest main, v1.61.0-next) 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 removes the
option["required"] = Falseline indocumentation_provider.pythat was forcing all options-bag properties to optional.Credentials.create(rp_id=)was the only case affected — it's now correctly required.The rolling skill is updated to reference
driver/playwright-srcinstead of~/code/playwright.37 new tests (async + sync).