From aa244fa9a0cba650410736bb3eae9c23221e64ee Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 14 Oct 2026 19:42:24 -0700 Subject: [PATCH] chore(codegen): import the impl classes from one module --- .claude/skills/playwright-roll/SKILL.md | 4 +- playwright/_impl/_api_classes.py | 101 +++++++++++++++++++ playwright/_impl/_frame.py | 2 +- playwright/_impl/_page.py | 2 +- playwright/_impl/{_webmcp.py => _web_mcp.py} | 0 playwright/async_api/_generated.py | 86 ++++++++-------- playwright/sync_api/_generated.py | 86 ++++++++-------- scripts/codegen/generate_api.js | 9 +- scripts/codegen/python_rules.js | 22 ---- 9 files changed, 193 insertions(+), 119 deletions(-) create mode 100644 playwright/_impl/_api_classes.py rename playwright/_impl/{_webmcp.py => _web_mcp.py} (100%) diff --git a/.claude/skills/playwright-roll/SKILL.md b/.claude/skills/playwright-roll/SKILL.md index 7080d746f..f3fe9e675 100644 --- a/.claude/skills/playwright-roll/SKILL.md +++ b/.claude/skills/playwright-roll/SKILL.md @@ -183,9 +183,10 @@ Implement the change in `playwright/_impl/.py`. Use the upstream JS impl When implementing a new ChannelOwner subclass (one constructed by the protocol with `(parent, type, guid, initializer)`): 1. Register it in `playwright/_impl/_object_factory.py:create_remote_object` — otherwise the guid resolves to `DummyObject` and downstream code breaks mysteriously. -2. Nothing to do in codegen: the generator emits every Python class in the docs. If the class does not live in `_impl/_.py`, add it to `implModules` in `scripts/codegen/python_rules.js`. +2. Add it to `playwright/_impl/_api_classes.py` (the import and `__all__`). The generator emits every Python class in the docs, and the generated API imports each implementation from that file. If the class is missing there, `_generated.py` fails to import. When implementing a non-ChannelOwner wrapper class (a plain class that holds a Page/Context reference, like `Screencast`, `Clock`): +- Add it to `playwright/_impl/_api_classes.py`, as for a ChannelOwner subclass. - Set `self._loop = parent._loop` and `self._dispatcher_fiber = parent._dispatcher_fiber` in `__init__`. The generated `AsyncBase`/`SyncBase` wrappers read these; missing them gives `AttributeError: 'X' object has no attribute '_loop'` at first use. When the docs add a new struct (an inline `[Object]` with named fields): @@ -314,7 +315,6 @@ All keys use docs names: `Class.member` or `Class.member.arg`, with the upstream | `extraMembers`, `extraOptions` | Python-only members and hidden options; generated without docstrings. | | `positionalOptions` | Option names that are positional everywhere (`predicate`). | | `structTotal` | `total=` of a TypedDict, where it differs from "total=False if any field is optional". | -| `implModules` | `_impl/` module of classes not in `_.py`. | ## Tips & gotchas diff --git a/playwright/_impl/_api_classes.py b/playwright/_impl/_api_classes.py new file mode 100644 index 000000000..5ea2d5dcf --- /dev/null +++ b/playwright/_impl/_api_classes.py @@ -0,0 +1,101 @@ +# Copyright (c) Microsoft Corporation. +# +# Licensed 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 implementation of every class in the generated API. The generated API +# imports them from here, so the generator does not need to know which module +# holds each class. + +from playwright._impl._assertions import ( + APIResponseAssertions, + LocatorAssertions, + PageAssertions, +) +from playwright._impl._browser import Browser +from playwright._impl._browser_context import BrowserContext +from playwright._impl._browser_type import BrowserType +from playwright._impl._cdp_session import CDPSession +from playwright._impl._clock import Clock +from playwright._impl._console_message import ConsoleMessage +from playwright._impl._credentials import Credentials +from playwright._impl._debugger import Debugger +from playwright._impl._dialog import Dialog +from playwright._impl._disposable import Disposable +from playwright._impl._download import Download +from playwright._impl._element_handle import ElementHandle +from playwright._impl._fetch import APIRequest, APIRequestContext, APIResponse +from playwright._impl._file_chooser import FileChooser +from playwright._impl._frame import Frame +from playwright._impl._input import Keyboard, Mouse, Touchscreen +from playwright._impl._js_handle import JSHandle +from playwright._impl._locator import FrameLocator, Locator +from playwright._impl._network import ( + Request, + Response, + Route, + WebSocket, + WebSocketRoute, +) +from playwright._impl._page import Page, Worker +from playwright._impl._playwright import Playwright +from playwright._impl._screencast import Screencast +from playwright._impl._selectors import Selectors +from playwright._impl._tracing import Tracing +from playwright._impl._video import Video +from playwright._impl._web_error import WebError +from playwright._impl._web_mcp import WebMCP +from playwright._impl._web_storage import WebStorage + +__all__ = [ + "APIRequest", + "APIRequestContext", + "APIResponse", + "APIResponseAssertions", + "Browser", + "BrowserContext", + "BrowserType", + "CDPSession", + "Clock", + "ConsoleMessage", + "Credentials", + "Debugger", + "Dialog", + "Disposable", + "Download", + "ElementHandle", + "FileChooser", + "Frame", + "FrameLocator", + "JSHandle", + "Keyboard", + "Locator", + "LocatorAssertions", + "Mouse", + "Page", + "PageAssertions", + "Playwright", + "Request", + "Response", + "Route", + "Screencast", + "Selectors", + "Touchscreen", + "Tracing", + "Video", + "WebError", + "WebMCP", + "WebSocket", + "WebSocketRoute", + "WebStorage", + "Worker", +] diff --git a/playwright/_impl/_frame.py b/playwright/_impl/_frame.py index c0e781558..1bf4a8ae9 100644 --- a/playwright/_impl/_frame.py +++ b/playwright/_impl/_frame.py @@ -80,7 +80,7 @@ from playwright._impl._network import Response from playwright._impl._set_input_files_helpers import convert_input_files from playwright._impl._waiter import Waiter -from playwright._impl._webmcp import WebMCP +from playwright._impl._web_mcp import WebMCP if TYPE_CHECKING: # pragma: no cover from playwright._impl._page import Page diff --git a/playwright/_impl/_page.py b/playwright/_impl/_page.py index 589bc3539..cff2ab606 100644 --- a/playwright/_impl/_page.py +++ b/playwright/_impl/_page.py @@ -104,8 +104,8 @@ from playwright._impl._signature import signature from playwright._impl._video import Video from playwright._impl._waiter import Waiter +from playwright._impl._web_mcp import WebMCP from playwright._impl._web_storage import WebStorage -from playwright._impl._webmcp import WebMCP if TYPE_CHECKING: # pragma: no cover from playwright._impl._browser_context import BrowserContext diff --git a/playwright/_impl/_webmcp.py b/playwright/_impl/_web_mcp.py similarity index 100% rename from playwright/_impl/_webmcp.py rename to playwright/_impl/_web_mcp.py diff --git a/playwright/async_api/_generated.py b/playwright/async_api/_generated.py index 64c16db74..000396c9e 100644 --- a/playwright/async_api/_generated.py +++ b/playwright/async_api/_generated.py @@ -18,6 +18,49 @@ import typing from typing import Literal +from playwright._impl._api_classes import APIRequest as APIRequestImpl +from playwright._impl._api_classes import APIRequestContext as APIRequestContextImpl +from playwright._impl._api_classes import APIResponse as APIResponseImpl +from playwright._impl._api_classes import ( + APIResponseAssertions as APIResponseAssertionsImpl, +) +from playwright._impl._api_classes import Browser as BrowserImpl +from playwright._impl._api_classes import BrowserContext as BrowserContextImpl +from playwright._impl._api_classes import BrowserType as BrowserTypeImpl +from playwright._impl._api_classes import CDPSession as CDPSessionImpl +from playwright._impl._api_classes import Clock as ClockImpl +from playwright._impl._api_classes import ConsoleMessage as ConsoleMessageImpl +from playwright._impl._api_classes import Credentials as CredentialsImpl +from playwright._impl._api_classes import Debugger as DebuggerImpl +from playwright._impl._api_classes import Dialog as DialogImpl +from playwright._impl._api_classes import Disposable as DisposableImpl +from playwright._impl._api_classes import Download as DownloadImpl +from playwright._impl._api_classes import ElementHandle as ElementHandleImpl +from playwright._impl._api_classes import FileChooser as FileChooserImpl +from playwright._impl._api_classes import Frame as FrameImpl +from playwright._impl._api_classes import FrameLocator as FrameLocatorImpl +from playwright._impl._api_classes import JSHandle as JSHandleImpl +from playwright._impl._api_classes import Keyboard as KeyboardImpl +from playwright._impl._api_classes import Locator as LocatorImpl +from playwright._impl._api_classes import LocatorAssertions as LocatorAssertionsImpl +from playwright._impl._api_classes import Mouse as MouseImpl +from playwright._impl._api_classes import Page as PageImpl +from playwright._impl._api_classes import PageAssertions as PageAssertionsImpl +from playwright._impl._api_classes import Playwright as PlaywrightImpl +from playwright._impl._api_classes import Request as RequestImpl +from playwright._impl._api_classes import Response as ResponseImpl +from playwright._impl._api_classes import Route as RouteImpl +from playwright._impl._api_classes import Screencast as ScreencastImpl +from playwright._impl._api_classes import Selectors as SelectorsImpl +from playwright._impl._api_classes import Touchscreen as TouchscreenImpl +from playwright._impl._api_classes import Tracing as TracingImpl +from playwright._impl._api_classes import Video as VideoImpl +from playwright._impl._api_classes import WebError as WebErrorImpl +from playwright._impl._api_classes import WebMCP as WebMCPImpl +from playwright._impl._api_classes import WebSocket as WebSocketImpl +from playwright._impl._api_classes import WebSocketRoute as WebSocketRouteImpl +from playwright._impl._api_classes import WebStorage as WebStorageImpl +from playwright._impl._api_classes import Worker as WorkerImpl from playwright._impl._api_structures import ( BrowserBindResult, ClientCertificate, @@ -53,58 +96,15 @@ WebMCPTool, WebStorageItem, ) -from playwright._impl._assertions import ( - APIResponseAssertions as APIResponseAssertionsImpl, -) -from playwright._impl._assertions import LocatorAssertions as LocatorAssertionsImpl -from playwright._impl._assertions import PageAssertions as PageAssertionsImpl from playwright._impl._async_base import ( AsyncBase, AsyncContextManager, AsyncEventContextManager, mapping, ) -from playwright._impl._browser import Browser as BrowserImpl -from playwright._impl._browser_context import BrowserContext as BrowserContextImpl -from playwright._impl._browser_type import BrowserType as BrowserTypeImpl -from playwright._impl._cdp_session import CDPSession as CDPSessionImpl -from playwright._impl._clock import Clock as ClockImpl -from playwright._impl._console_message import ConsoleMessage as ConsoleMessageImpl -from playwright._impl._credentials import Credentials as CredentialsImpl -from playwright._impl._debugger import Debugger as DebuggerImpl -from playwright._impl._dialog import Dialog as DialogImpl -from playwright._impl._disposable import Disposable as DisposableImpl -from playwright._impl._download import Download as DownloadImpl -from playwright._impl._element_handle import ElementHandle as ElementHandleImpl from playwright._impl._errors import Error -from playwright._impl._fetch import APIRequest as APIRequestImpl -from playwright._impl._fetch import APIRequestContext as APIRequestContextImpl -from playwright._impl._fetch import APIResponse as APIResponseImpl -from playwright._impl._file_chooser import FileChooser as FileChooserImpl from playwright._impl._form_data import FormData -from playwright._impl._frame import Frame as FrameImpl from playwright._impl._helper import to_milliseconds -from playwright._impl._input import Keyboard as KeyboardImpl -from playwright._impl._input import Mouse as MouseImpl -from playwright._impl._input import Touchscreen as TouchscreenImpl -from playwright._impl._js_handle import JSHandle as JSHandleImpl -from playwright._impl._locator import FrameLocator as FrameLocatorImpl -from playwright._impl._locator import Locator as LocatorImpl -from playwright._impl._network import Request as RequestImpl -from playwright._impl._network import Response as ResponseImpl -from playwright._impl._network import Route as RouteImpl -from playwright._impl._network import WebSocket as WebSocketImpl -from playwright._impl._network import WebSocketRoute as WebSocketRouteImpl -from playwright._impl._page import Page as PageImpl -from playwright._impl._page import Worker as WorkerImpl -from playwright._impl._playwright import Playwright as PlaywrightImpl -from playwright._impl._screencast import Screencast as ScreencastImpl -from playwright._impl._selectors import Selectors as SelectorsImpl -from playwright._impl._tracing import Tracing as TracingImpl -from playwright._impl._video import Video as VideoImpl -from playwright._impl._web_error import WebError as WebErrorImpl -from playwright._impl._web_storage import WebStorage as WebStorageImpl -from playwright._impl._webmcp import WebMCP as WebMCPImpl class APIRequest(AsyncBase): diff --git a/playwright/sync_api/_generated.py b/playwright/sync_api/_generated.py index 6bdc78f64..85cf04039 100644 --- a/playwright/sync_api/_generated.py +++ b/playwright/sync_api/_generated.py @@ -18,6 +18,49 @@ import typing from typing import Literal +from playwright._impl._api_classes import APIRequest as APIRequestImpl +from playwright._impl._api_classes import APIRequestContext as APIRequestContextImpl +from playwright._impl._api_classes import APIResponse as APIResponseImpl +from playwright._impl._api_classes import ( + APIResponseAssertions as APIResponseAssertionsImpl, +) +from playwright._impl._api_classes import Browser as BrowserImpl +from playwright._impl._api_classes import BrowserContext as BrowserContextImpl +from playwright._impl._api_classes import BrowserType as BrowserTypeImpl +from playwright._impl._api_classes import CDPSession as CDPSessionImpl +from playwright._impl._api_classes import Clock as ClockImpl +from playwright._impl._api_classes import ConsoleMessage as ConsoleMessageImpl +from playwright._impl._api_classes import Credentials as CredentialsImpl +from playwright._impl._api_classes import Debugger as DebuggerImpl +from playwright._impl._api_classes import Dialog as DialogImpl +from playwright._impl._api_classes import Disposable as DisposableImpl +from playwright._impl._api_classes import Download as DownloadImpl +from playwright._impl._api_classes import ElementHandle as ElementHandleImpl +from playwright._impl._api_classes import FileChooser as FileChooserImpl +from playwright._impl._api_classes import Frame as FrameImpl +from playwright._impl._api_classes import FrameLocator as FrameLocatorImpl +from playwright._impl._api_classes import JSHandle as JSHandleImpl +from playwright._impl._api_classes import Keyboard as KeyboardImpl +from playwright._impl._api_classes import Locator as LocatorImpl +from playwright._impl._api_classes import LocatorAssertions as LocatorAssertionsImpl +from playwright._impl._api_classes import Mouse as MouseImpl +from playwright._impl._api_classes import Page as PageImpl +from playwright._impl._api_classes import PageAssertions as PageAssertionsImpl +from playwright._impl._api_classes import Playwright as PlaywrightImpl +from playwright._impl._api_classes import Request as RequestImpl +from playwright._impl._api_classes import Response as ResponseImpl +from playwright._impl._api_classes import Route as RouteImpl +from playwright._impl._api_classes import Screencast as ScreencastImpl +from playwright._impl._api_classes import Selectors as SelectorsImpl +from playwright._impl._api_classes import Touchscreen as TouchscreenImpl +from playwright._impl._api_classes import Tracing as TracingImpl +from playwright._impl._api_classes import Video as VideoImpl +from playwright._impl._api_classes import WebError as WebErrorImpl +from playwright._impl._api_classes import WebMCP as WebMCPImpl +from playwright._impl._api_classes import WebSocket as WebSocketImpl +from playwright._impl._api_classes import WebSocketRoute as WebSocketRouteImpl +from playwright._impl._api_classes import WebStorage as WebStorageImpl +from playwright._impl._api_classes import Worker as WorkerImpl from playwright._impl._api_structures import ( BrowserBindResult, ClientCertificate, @@ -53,58 +96,15 @@ WebMCPTool, WebStorageItem, ) -from playwright._impl._assertions import ( - APIResponseAssertions as APIResponseAssertionsImpl, -) -from playwright._impl._assertions import LocatorAssertions as LocatorAssertionsImpl -from playwright._impl._assertions import PageAssertions as PageAssertionsImpl -from playwright._impl._browser import Browser as BrowserImpl -from playwright._impl._browser_context import BrowserContext as BrowserContextImpl -from playwright._impl._browser_type import BrowserType as BrowserTypeImpl -from playwright._impl._cdp_session import CDPSession as CDPSessionImpl -from playwright._impl._clock import Clock as ClockImpl -from playwright._impl._console_message import ConsoleMessage as ConsoleMessageImpl -from playwright._impl._credentials import Credentials as CredentialsImpl -from playwright._impl._debugger import Debugger as DebuggerImpl -from playwright._impl._dialog import Dialog as DialogImpl -from playwright._impl._disposable import Disposable as DisposableImpl -from playwright._impl._download import Download as DownloadImpl -from playwright._impl._element_handle import ElementHandle as ElementHandleImpl from playwright._impl._errors import Error -from playwright._impl._fetch import APIRequest as APIRequestImpl -from playwright._impl._fetch import APIRequestContext as APIRequestContextImpl -from playwright._impl._fetch import APIResponse as APIResponseImpl -from playwright._impl._file_chooser import FileChooser as FileChooserImpl from playwright._impl._form_data import FormData -from playwright._impl._frame import Frame as FrameImpl from playwright._impl._helper import to_milliseconds -from playwright._impl._input import Keyboard as KeyboardImpl -from playwright._impl._input import Mouse as MouseImpl -from playwright._impl._input import Touchscreen as TouchscreenImpl -from playwright._impl._js_handle import JSHandle as JSHandleImpl -from playwright._impl._locator import FrameLocator as FrameLocatorImpl -from playwright._impl._locator import Locator as LocatorImpl -from playwright._impl._network import Request as RequestImpl -from playwright._impl._network import Response as ResponseImpl -from playwright._impl._network import Route as RouteImpl -from playwright._impl._network import WebSocket as WebSocketImpl -from playwright._impl._network import WebSocketRoute as WebSocketRouteImpl -from playwright._impl._page import Page as PageImpl -from playwright._impl._page import Worker as WorkerImpl -from playwright._impl._playwright import Playwright as PlaywrightImpl -from playwright._impl._screencast import Screencast as ScreencastImpl -from playwright._impl._selectors import Selectors as SelectorsImpl from playwright._impl._sync_base import ( EventContextManager, SyncBase, SyncContextManager, mapping, ) -from playwright._impl._tracing import Tracing as TracingImpl -from playwright._impl._video import Video as VideoImpl -from playwright._impl._web_error import WebError as WebErrorImpl -from playwright._impl._web_storage import WebStorage as WebStorageImpl -from playwright._impl._webmcp import WebMCP as WebMCPImpl class APIRequest(SyncBase): diff --git a/scripts/codegen/generate_api.js b/scripts/codegen/generate_api.js index baa93a46d..bfdcfdd31 100644 --- a/scripts/codegen/generate_api.js +++ b/scripts/codegen/generate_api.js @@ -821,11 +821,6 @@ const LICENSE = `# Copyright (c) Microsoft Corporation. # limitations under the License. `; -/** @param {string} className */ -function implModule(className) { - return lookupRule('implModules', className) || `_${toSnakeCase(className)}`; -} - /** @param {any} event */ function eventPayload(clazz, event) { let payload = docType(toPy(event.type, { dir: 'out', place: `${clazz.name}.${event.name}` })); @@ -1104,7 +1099,7 @@ function emitStructs() { } function checkRules() { - for (const table of ['extraMembers', 'extraOptions', 'structTotal', 'implModules']) { + for (const table of ['extraMembers', 'extraOptions', 'structTotal']) { for (const key of Object.keys(/** @type {any} */ (rules)[table])) { if (!usedRules.has(`${table}:${key}`)) errors.push(`${table}: the entry "${key}" no longer matches anything; remove it`); @@ -1128,7 +1123,7 @@ function main() { if (usedStructs.size) header.push(`from playwright._impl._api_structures import ${[...usedStructs].sort().join(', ')}`); for (const clazz of classes) - header.push(`from playwright._impl.${implModule(clazz.name)} import ${clazz.name} as ${clazz.name}Impl`); + header.push(`from playwright._impl._api_classes import ${clazz.name} as ${clazz.name}Impl`); header.push('from playwright._impl._errors import Error'); header.push('from playwright._impl._form_data import FormData'); header.push('from playwright._impl._helper import to_milliseconds'); diff --git a/scripts/codegen/python_rules.js b/scripts/codegen/python_rules.js index 10cd60b01..39e345c18 100644 --- a/scripts/codegen/python_rules.js +++ b/scripts/codegen/python_rules.js @@ -78,26 +78,4 @@ module.exports = { // Shipped as total=False, although every field is required. StorageStateCookie: false, }, - - // Old implementation: the module in playwright/_impl/ that holds each class. - // Classes not listed are in `_.py`. - implModules: { - APIRequest: '_fetch', - APIRequestContext: '_fetch', - APIResponse: '_fetch', - APIResponseAssertions: '_assertions', - FrameLocator: '_locator', - Keyboard: '_input', - LocatorAssertions: '_assertions', - Mouse: '_input', - PageAssertions: '_assertions', - Request: '_network', - Response: '_network', - Route: '_network', - Touchscreen: '_input', - WebMCP: '_webmcp', - WebSocket: '_network', - WebSocketRoute: '_network', - Worker: '_page', - }, };