From eb58558b68ea861bb1d6e8b0190fd86175000dfb Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 11 Dec 2020 15:46:18 -0800 Subject: [PATCH] chore: start making types hermetic --- playwright/__init__.py | 6 +- playwright/async_api.py | 55 ++++---- playwright/browser.py | 16 ++- playwright/browser_context.py | 5 +- playwright/browser_type.py | 9 +- playwright/console_message.py | 2 +- playwright/element_handle.py | 5 +- playwright/file_chooser.py | 2 +- playwright/frame.py | 4 +- playwright/helper.py | 128 +------------------ playwright/impl_to_api_mapping.py | 2 +- playwright/js_handle.py | 3 +- playwright/main.py | 2 +- playwright/network.py | 10 +- playwright/page.py | 19 +-- playwright/playwright.py | 4 +- playwright/selectors.py | 2 +- playwright/sync_api.py | 55 ++++---- playwright/types.py | 146 ++++++++++++++++++++++ playwright/wait_helper.py | 2 +- scripts/documentation_provider.py | 9 +- scripts/expected_api_mismatch.txt | 7 ++ scripts/generate_api.py | 8 +- tests/async/test_defaultbrowsercontext.py | 2 +- tests/async/test_keyboard.py | 2 +- tests/utils.py | 6 +- 26 files changed, 261 insertions(+), 250 deletions(-) create mode 100644 playwright/types.py diff --git a/playwright/__init__.py b/playwright/__init__.py index a96bcde55..ce59a03f6 100644 --- a/playwright/__init__.py +++ b/playwright/__init__.py @@ -19,11 +19,11 @@ and for the async API [here](async_api.html). """ -import playwright.helper as helper +import playwright.types as types from playwright.main import AsyncPlaywrightContextManager, SyncPlaywrightContextManager -Error = helper.Error -TimeoutError = helper.TimeoutError +Error = types.Error +TimeoutError = types.TimeoutError def async_playwright() -> AsyncPlaywrightContextManager: diff --git a/playwright/async_api.py b/playwright/async_api.py index 7816930c7..9e14fb530 100644 --- a/playwright/async_api.py +++ b/playwright/async_api.py @@ -37,8 +37,22 @@ from playwright.element_handle import ElementHandle as ElementHandleImpl from playwright.file_chooser import FileChooser as FileChooserImpl from playwright.frame import Frame as FrameImpl -from playwright.helper import ( +from playwright.input import Keyboard as KeyboardImpl +from playwright.input import Mouse as MouseImpl +from playwright.input import Touchscreen as TouchscreenImpl +from playwright.js_handle import JSHandle as JSHandleImpl +from playwright.network import Request as RequestImpl +from playwright.network import Response as ResponseImpl +from playwright.network import Route as RouteImpl +from playwright.network import WebSocket as WebSocketImpl +from playwright.page import BindingCall as BindingCallImpl +from playwright.page import Page as PageImpl +from playwright.page import Worker as WorkerImpl +from playwright.playwright import Playwright as PlaywrightImpl +from playwright.selectors import Selectors as SelectorsImpl +from playwright.types import ( ConsoleMessageLocation, + Cookie, Credentials, DeviceDescriptor, Error, @@ -54,23 +68,8 @@ RequestFailure, ResourceTiming, SelectOption, - SetStorageState, StorageState, - Viewport, ) -from playwright.input import Keyboard as KeyboardImpl -from playwright.input import Mouse as MouseImpl -from playwright.input import Touchscreen as TouchscreenImpl -from playwright.js_handle import JSHandle as JSHandleImpl -from playwright.network import Request as RequestImpl -from playwright.network import Response as ResponseImpl -from playwright.network import Route as RouteImpl -from playwright.network import WebSocket as WebSocketImpl -from playwright.page import BindingCall as BindingCallImpl -from playwright.page import Page as PageImpl -from playwright.page import Worker as WorkerImpl -from playwright.playwright import Playwright as PlaywrightImpl -from playwright.selectors import Selectors as SelectorsImpl from playwright.video import Video as VideoImpl NoneType = type(None) @@ -4548,7 +4547,7 @@ async def setViewportSize(self, width: int, height: int) -> NoneType: await self._impl_obj.setViewportSize(width=width, height=height) ) - def viewportSize(self) -> typing.Union[Viewport, NoneType]: + def viewportSize(self) -> typing.Union[IntSize, NoneType]: """Page.viewportSize Returns @@ -5920,7 +5919,7 @@ async def newPage(self) -> "Page": async def cookies( self, urls: typing.Union[str, typing.List[str]] = None - ) -> typing.List[typing.Dict]: + ) -> typing.List[Cookie]: """BrowserContext.cookies If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those URLs @@ -5933,11 +5932,11 @@ async def cookies( Returns ------- - List[Dict] + List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}] """ return mapping.from_maybe_impl(await self._impl_obj.cookies(urls=urls)) - async def addCookies(self, cookies: typing.List[typing.Dict]) -> NoneType: + async def addCookies(self, cookies: typing.List[Cookie]) -> NoneType: """BrowserContext.addCookies Adds cookies into this browser context. All pages within this context will have these cookies installed. Cookies can be @@ -5945,11 +5944,9 @@ async def addCookies(self, cookies: typing.List[typing.Dict]) -> NoneType: Parameters ---------- - cookies : List[Dict] + cookies : List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}] """ - return mapping.from_maybe_impl( - await self._impl_obj.addCookies(cookies=mapping.to_impl(cookies)) - ) + return mapping.from_maybe_impl(await self._impl_obj.addCookies(cookies=cookies)) async def clearCookies(self) -> NoneType: """BrowserContext.clearCookies @@ -6218,7 +6215,7 @@ async def storageState(self) -> StorageState: Returns ------- - {"cookies": List[Dict], "origins": List[Dict]} + {"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]} """ return mapping.from_maybe_impl(await self._impl_obj.storageState()) @@ -6430,7 +6427,7 @@ async def newContext( videoSize: IntSize = None, recordHar: RecordHarOptions = None, recordVideo: RecordVideoOptions = None, - storageState: SetStorageState = None, + storageState: StorageState = None, ) -> "BrowserContext": """Browser.newContext @@ -6481,7 +6478,7 @@ async def newContext( Enables HAR recording for all pages into `recordHar.path` file. If not specified, the HAR is not recorded. Make sure to await browserContext.close() for the HAR to be saved. recordVideo : Optional[{"dir": str, "size": Optional[{"width": int, "height": int}]}] Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make sure to await browserContext.close() for videos to be saved. - storageState : Optional[{"cookies": Optional[List[Dict]], "origins": Optional[List[Dict]]}] + storageState : Optional[{"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]}] Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState(). Returns @@ -6542,7 +6539,7 @@ async def newPage( videoSize: IntSize = None, recordHar: RecordHarOptions = None, recordVideo: RecordVideoOptions = None, - storageState: SetStorageState = None, + storageState: StorageState = None, ) -> "Page": """Browser.newPage @@ -6596,7 +6593,7 @@ async def newPage( Enables HAR recording for all pages into `recordHar.path` file. If not specified, the HAR is not recorded. Make sure to await browserContext.close() for the HAR to be saved. recordVideo : Optional[{"dir": str, "size": Optional[{"width": int, "height": int}]}] Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make sure to await browserContext.close() for videos to be saved. - storageState : Optional[{"cookies": Optional[List[Dict]], "origins": Optional[List[Dict]]}] + storageState : Optional[{"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]}] Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState(). Returns diff --git a/playwright/browser.py b/playwright/browser.py index bdfc06f56..6e6ad6970 100644 --- a/playwright/browser.py +++ b/playwright/browser.py @@ -18,20 +18,18 @@ from playwright.browser_context import BrowserContext from playwright.connection import ChannelOwner, from_channel -from playwright.helper import ( - ColorScheme, +from playwright.helper import ColorScheme, is_safe_close_error, locals_to_params +from playwright.network import serialize_headers +from playwright.page import Page +from playwright.types import ( Credentials, Geolocation, IntSize, ProxyServer, RecordHarOptions, RecordVideoOptions, - SetStorageState, - is_safe_close_error, - locals_to_params, + StorageState, ) -from playwright.network import serialize_headers -from playwright.page import Page if sys.version_info >= (3, 8): # pragma: no cover from typing import Literal @@ -96,7 +94,7 @@ async def newContext( videoSize: IntSize = None, recordHar: RecordHarOptions = None, recordVideo: RecordVideoOptions = None, - storageState: SetStorageState = None, + storageState: StorageState = None, ) -> BrowserContext: params = locals_to_params(locals()) # Python is strict in which variables gets passed to methods. We get this @@ -145,7 +143,7 @@ async def newPage( videoSize: IntSize = None, recordHar: RecordHarOptions = None, recordVideo: RecordVideoOptions = None, - storageState: SetStorageState = None, + storageState: StorageState = None, ) -> Page: params = locals_to_params(locals()) # Python is strict in which variables gets passed to methods. We get this diff --git a/playwright/browser_context.py b/playwright/browser_context.py index 812c88bde..9c2ac64a7 100644 --- a/playwright/browser_context.py +++ b/playwright/browser_context.py @@ -20,13 +20,9 @@ from playwright.connection import ChannelOwner, from_channel from playwright.event_context_manager import EventContextManagerImpl from playwright.helper import ( - Cookie, - Error, - Geolocation, PendingWaitEvent, RouteHandler, RouteHandlerEntry, - StorageState, TimeoutSettings, URLMatch, URLMatcher, @@ -35,6 +31,7 @@ ) from playwright.network import Request, Route, serialize_headers from playwright.page import BindingCall, Page +from playwright.types import Cookie, Error, Geolocation, StorageState from playwright.wait_helper import WaitHelper if TYPE_CHECKING: # pragma: no cover diff --git a/playwright/browser_type.py b/playwright/browser_type.py index 8898ada91..e138c74b2 100644 --- a/playwright/browser_type.py +++ b/playwright/browser_type.py @@ -19,19 +19,16 @@ from playwright.browser import Browser from playwright.browser_context import BrowserContext from playwright.connection import ChannelOwner, from_channel -from playwright.helper import ( - ColorScheme, +from playwright.helper import ColorScheme, Env, locals_to_params, not_installed_error +from playwright.network import serialize_headers +from playwright.types import ( Credentials, - Env, Geolocation, IntSize, ProxyServer, RecordHarOptions, RecordVideoOptions, - locals_to_params, - not_installed_error, ) -from playwright.network import serialize_headers if sys.version_info >= (3, 8): # pragma: no cover from typing import Literal diff --git a/playwright/console_message.py b/playwright/console_message.py index e7dd8c015..c5b0c71b7 100644 --- a/playwright/console_message.py +++ b/playwright/console_message.py @@ -15,8 +15,8 @@ from typing import Dict, List from playwright.connection import ChannelOwner, from_channel -from playwright.helper import ConsoleMessageLocation from playwright.js_handle import JSHandle +from playwright.types import ConsoleMessageLocation class ConsoleMessage(ChannelOwner): diff --git a/playwright/element_handle.py b/playwright/element_handle.py index a422352d9..9e6fccaab 100644 --- a/playwright/element_handle.py +++ b/playwright/element_handle.py @@ -21,12 +21,8 @@ from playwright.connection import ChannelOwner, from_nullable_channel from playwright.helper import ( - FilePayload, - FloatRect, KeyboardModifier, MouseButton, - MousePosition, - SelectOption, SetFilePayload, locals_to_params, ) @@ -36,6 +32,7 @@ parse_result, serialize_argument, ) +from playwright.types import FilePayload, FloatRect, MousePosition, SelectOption if sys.version_info >= (3, 8): # pragma: no cover from typing import Literal diff --git a/playwright/file_chooser.py b/playwright/file_chooser.py index cebbcebd0..55146caab 100644 --- a/playwright/file_chooser.py +++ b/playwright/file_chooser.py @@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, List, Union -from playwright.helper import FilePayload +from playwright.types import FilePayload if TYPE_CHECKING: # pragma: no cover from playwright.element_handle import ElementHandle diff --git a/playwright/frame.py b/playwright/frame.py index 0658d7aae..18ac79fc9 100644 --- a/playwright/frame.py +++ b/playwright/frame.py @@ -29,12 +29,9 @@ from playwright.event_context_manager import EventContextManagerImpl from playwright.helper import ( DocumentLoadState, - Error, - FilePayload, FrameNavigatedEvent, KeyboardModifier, MouseButton, - MousePosition, URLMatch, URLMatcher, is_function_body, @@ -48,6 +45,7 @@ serialize_argument, ) from playwright.network import Response +from playwright.types import Error, FilePayload, MousePosition from playwright.wait_helper import WaitHelper if sys.version_info >= (3, 8): # pragma: no cover diff --git a/playwright/helper.py b/playwright/helper.py index f5c6f01cb..35afdb655 100644 --- a/playwright/helper.py +++ b/playwright/helper.py @@ -32,6 +32,8 @@ cast, ) +from playwright.types import Error, TimeoutError + if sys.version_info >= (3, 8): # pragma: no cover from typing import Literal, TypedDict else: # pragma: no cover @@ -41,7 +43,6 @@ if TYPE_CHECKING: # pragma: no cover from playwright.network import Request, Route -Cookie = Dict URLMatch = Union[str, Pattern, Callable[[str], bool]] RouteHandler = Callable[["Route", "Request"], Any] @@ -51,57 +52,12 @@ MouseButton = Literal["left", "middle", "right"] -class StorageState(TypedDict): - cookies: List[Cookie] - origins: List[Dict] - - -class SetStorageState(TypedDict): - cookies: Optional[List[Cookie]] - origins: Optional[List[Dict]] - - -class MousePosition(TypedDict): - x: float - y: float - - -class ResourceTiming(TypedDict): - startTime: float - domainLookupStart: float - domainLookupEnd: float - connectStart: float - secureConnectionStart: float - connectEnd: float - requestStart: float - responseStart: float - responseEnd: float - - -class FilePayload(TypedDict): - name: str - mimeType: str - buffer: bytes - - class SetFilePayload(TypedDict): name: str mimeType: str buffer: str -class SelectOption(TypedDict): - value: Optional[str] - label: Optional[str] - index: Optional[str] - - -class ConsoleMessageLocation(TypedDict): - url: str - lineNumber: int - columnNumber: int - - class ErrorPayload(TypedDict, total=False): message: str name: str @@ -127,11 +83,6 @@ class ParsedMessageParams(TypedDict): initializer: Dict -class Viewport(TypedDict): - width: int - height: int - - class ParsedMessagePayload(TypedDict, total=False): id: int guid: str @@ -152,73 +103,9 @@ class FrameNavigatedEvent(TypedDict): error: Optional[str] -class RequestFailure(TypedDict): - errorText: str - - -class Credentials(TypedDict): - username: str - password: str - - -class IntSize(TypedDict): - width: int - height: int - - -class FloatRect(TypedDict): - x: float - y: float - width: float - height: float - - -class Geolocation(TypedDict, total=False): - latitude: float - longitude: float - accuracy: Optional[float] - - Env = Dict[str, Union[str, int, bool]] -class ProxyServer(TypedDict): - server: str - bypass: Optional[str] - username: Optional[str] - password: Optional[str] - - -class PdfMargins(TypedDict): - top: Optional[Union[str, int]] - right: Optional[Union[str, int]] - bottom: Optional[Union[str, int]] - left: Optional[Union[str, int]] - - -class RecordHarOptions(TypedDict): - omitContent: Optional[bool] - path: str - - -class RecordVideoOptions(TypedDict): - dir: str - size: Optional[Viewport] - - -DeviceDescriptor = TypedDict( - "DeviceDescriptor", - { - "userAgent": str, - "viewport": IntSize, - "deviceScaleFactor": int, - "isMobile": bool, - "hasTouch": bool, - }, -) -Devices = Dict[str, DeviceDescriptor] - - class URLMatcher: def __init__(self, match: URLMatch) -> None: self._callback: Optional[Callable[[str], bool]] = None @@ -267,17 +154,6 @@ def navigation_timeout(self) -> int: return 30000 -class Error(Exception): - def __init__(self, message: str, stack: str = None) -> None: - self.message = message - self.stack = stack - super().__init__(message) - - -class TimeoutError(Error): - pass - - def serialize_error(ex: Exception, tb: Optional[TracebackType]) -> ErrorPayload: return dict(message=str(ex), stack="".join(traceback.format_tb(tb))) diff --git a/playwright/impl_to_api_mapping.py b/playwright/impl_to_api_mapping.py index 120f57ba4..68dd97a42 100644 --- a/playwright/impl_to_api_mapping.py +++ b/playwright/impl_to_api_mapping.py @@ -16,7 +16,7 @@ from typing import Any, Callable, Dict, List, Optional from weakref import WeakKeyDictionary -from playwright.helper import Error +from playwright.types import Error class ImplWrapper: diff --git a/playwright/js_handle.py b/playwright/js_handle.py index 6bd6d7d6c..1f0de41f8 100644 --- a/playwright/js_handle.py +++ b/playwright/js_handle.py @@ -17,7 +17,8 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional from playwright.connection import ChannelOwner, from_channel -from playwright.helper import Error, is_function_body +from playwright.helper import is_function_body +from playwright.types import Error if TYPE_CHECKING: # pragma: no cover from playwright.element_handle import ElementHandle diff --git a/playwright/main.py b/playwright/main.py index 15fb1aab2..5513a34dc 100644 --- a/playwright/main.py +++ b/playwright/main.py @@ -25,10 +25,10 @@ import playwright from playwright.async_api import Playwright as AsyncPlaywright from playwright.connection import Connection -from playwright.helper import Error from playwright.object_factory import create_remote_object from playwright.playwright import Playwright from playwright.sync_api import Playwright as SyncPlaywright +from playwright.types import Error def compute_driver_executable() -> Path: diff --git a/playwright/network.py b/playwright/network.py index 4db9a7776..0d2a9f0a8 100644 --- a/playwright/network.py +++ b/playwright/network.py @@ -22,14 +22,8 @@ from playwright.connection import ChannelOwner, from_channel, from_nullable_channel from playwright.event_context_manager import EventContextManagerImpl -from playwright.helper import ( - ContinueParameters, - Error, - Header, - RequestFailure, - ResourceTiming, - locals_to_params, -) +from playwright.helper import ContinueParameters, Header, locals_to_params +from playwright.types import Error, RequestFailure, ResourceTiming from playwright.wait_helper import WaitHelper if TYPE_CHECKING: # pragma: no cover diff --git a/playwright/page.py b/playwright/page.py index 6cef049a1..bf06fe8a0 100644 --- a/playwright/page.py +++ b/playwright/page.py @@ -17,7 +17,7 @@ import sys from pathlib import Path from types import SimpleNamespace -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Union, cast +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union, cast from playwright.accessibility import Accessibility from playwright.connection import ChannelOwner, from_channel, from_nullable_channel @@ -31,21 +31,14 @@ from playwright.helper import ( ColorScheme, DocumentLoadState, - Error, - FilePayload, - FloatRect, KeyboardModifier, MouseButton, - MousePosition, - Optional, - PdfMargins, PendingWaitEvent, RouteHandler, RouteHandlerEntry, TimeoutSettings, URLMatch, URLMatcher, - Viewport, is_function_body, is_safe_close_error, locals_to_params, @@ -60,6 +53,14 @@ serialize_argument, ) from playwright.network import Request, Response, Route, serialize_headers +from playwright.types import ( + Error, + FilePayload, + FloatRect, + IntSize, + MousePosition, + PdfMargins, +) from playwright.video import Video from playwright.wait_helper import WaitHelper @@ -562,7 +563,7 @@ async def setViewportSize(self, width: int, height: int) -> None: "setViewportSize", dict(viewportSize=locals_to_params(locals())) ) - def viewportSize(self) -> Optional[Viewport]: + def viewportSize(self) -> Optional[IntSize]: return self._viewport_size async def bringToFront(self) -> None: diff --git a/playwright/playwright.py b/playwright/playwright.py index 5aca9398f..e99a8a071 100644 --- a/playwright/playwright.py +++ b/playwright/playwright.py @@ -16,8 +16,8 @@ from playwright.browser_type import BrowserType from playwright.connection import ChannelOwner, from_channel -from playwright.helper import Devices from playwright.selectors import Selectors +from playwright.types import DeviceDescriptor class Playwright(ChannelOwner): @@ -25,7 +25,7 @@ class Playwright(ChannelOwner): firefox: BrowserType webkit: BrowserType selectors: Selectors - devices: Devices + devices: Dict[str, DeviceDescriptor] def __init__( self, parent: ChannelOwner, type: str, guid: str, initializer: Dict diff --git a/playwright/selectors.py b/playwright/selectors.py index 369358d54..182283a89 100644 --- a/playwright/selectors.py +++ b/playwright/selectors.py @@ -16,7 +16,7 @@ from typing import Dict, Union from playwright.connection import ChannelOwner -from playwright.helper import Error +from playwright.types import Error class Selectors(ChannelOwner): diff --git a/playwright/sync_api.py b/playwright/sync_api.py index e9b188a4c..54a21799e 100644 --- a/playwright/sync_api.py +++ b/playwright/sync_api.py @@ -36,8 +36,23 @@ from playwright.element_handle import ElementHandle as ElementHandleImpl from playwright.file_chooser import FileChooser as FileChooserImpl from playwright.frame import Frame as FrameImpl -from playwright.helper import ( +from playwright.input import Keyboard as KeyboardImpl +from playwright.input import Mouse as MouseImpl +from playwright.input import Touchscreen as TouchscreenImpl +from playwright.js_handle import JSHandle as JSHandleImpl +from playwright.network import Request as RequestImpl +from playwright.network import Response as ResponseImpl +from playwright.network import Route as RouteImpl +from playwright.network import WebSocket as WebSocketImpl +from playwright.page import BindingCall as BindingCallImpl +from playwright.page import Page as PageImpl +from playwright.page import Worker as WorkerImpl +from playwright.playwright import Playwright as PlaywrightImpl +from playwright.selectors import Selectors as SelectorsImpl +from playwright.sync_base import EventContextManager, SyncBase, mapping +from playwright.types import ( ConsoleMessageLocation, + Cookie, Credentials, DeviceDescriptor, Error, @@ -53,24 +68,8 @@ RequestFailure, ResourceTiming, SelectOption, - SetStorageState, StorageState, - Viewport, ) -from playwright.input import Keyboard as KeyboardImpl -from playwright.input import Mouse as MouseImpl -from playwright.input import Touchscreen as TouchscreenImpl -from playwright.js_handle import JSHandle as JSHandleImpl -from playwright.network import Request as RequestImpl -from playwright.network import Response as ResponseImpl -from playwright.network import Route as RouteImpl -from playwright.network import WebSocket as WebSocketImpl -from playwright.page import BindingCall as BindingCallImpl -from playwright.page import Page as PageImpl -from playwright.page import Worker as WorkerImpl -from playwright.playwright import Playwright as PlaywrightImpl -from playwright.selectors import Selectors as SelectorsImpl -from playwright.sync_base import EventContextManager, SyncBase, mapping from playwright.video import Video as VideoImpl NoneType = type(None) @@ -4722,7 +4721,7 @@ def setViewportSize(self, width: int, height: int) -> NoneType: self._sync(self._impl_obj.setViewportSize(width=width, height=height)) ) - def viewportSize(self) -> typing.Union[Viewport, NoneType]: + def viewportSize(self) -> typing.Union[IntSize, NoneType]: """Page.viewportSize Returns @@ -6113,7 +6112,7 @@ def newPage(self) -> "Page": def cookies( self, urls: typing.Union[str, typing.List[str]] = None - ) -> typing.List[typing.Dict]: + ) -> typing.List[Cookie]: """BrowserContext.cookies If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those URLs @@ -6126,11 +6125,11 @@ def cookies( Returns ------- - List[Dict] + List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}] """ return mapping.from_maybe_impl(self._sync(self._impl_obj.cookies(urls=urls))) - def addCookies(self, cookies: typing.List[typing.Dict]) -> NoneType: + def addCookies(self, cookies: typing.List[Cookie]) -> NoneType: """BrowserContext.addCookies Adds cookies into this browser context. All pages within this context will have these cookies installed. Cookies can be @@ -6138,10 +6137,10 @@ def addCookies(self, cookies: typing.List[typing.Dict]) -> NoneType: Parameters ---------- - cookies : List[Dict] + cookies : List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}] """ return mapping.from_maybe_impl( - self._sync(self._impl_obj.addCookies(cookies=mapping.to_impl(cookies))) + self._sync(self._impl_obj.addCookies(cookies=cookies)) ) def clearCookies(self) -> NoneType: @@ -6427,7 +6426,7 @@ def storageState(self) -> StorageState: Returns ------- - {"cookies": List[Dict], "origins": List[Dict]} + {"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]} """ return mapping.from_maybe_impl(self._sync(self._impl_obj.storageState())) @@ -6641,7 +6640,7 @@ def newContext( videoSize: IntSize = None, recordHar: RecordHarOptions = None, recordVideo: RecordVideoOptions = None, - storageState: SetStorageState = None, + storageState: StorageState = None, ) -> "BrowserContext": """Browser.newContext @@ -6692,7 +6691,7 @@ def newContext( Enables HAR recording for all pages into `recordHar.path` file. If not specified, the HAR is not recorded. Make sure to await browserContext.close() for the HAR to be saved. recordVideo : Optional[{"dir": str, "size": Optional[{"width": int, "height": int}]}] Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make sure to await browserContext.close() for videos to be saved. - storageState : Optional[{"cookies": Optional[List[Dict]], "origins": Optional[List[Dict]]}] + storageState : Optional[{"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]}] Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState(). Returns @@ -6755,7 +6754,7 @@ def newPage( videoSize: IntSize = None, recordHar: RecordHarOptions = None, recordVideo: RecordVideoOptions = None, - storageState: SetStorageState = None, + storageState: StorageState = None, ) -> "Page": """Browser.newPage @@ -6809,7 +6808,7 @@ def newPage( Enables HAR recording for all pages into `recordHar.path` file. If not specified, the HAR is not recorded. Make sure to await browserContext.close() for the HAR to be saved. recordVideo : Optional[{"dir": str, "size": Optional[{"width": int, "height": int}]}] Enables video recording for all pages into `recordVideo.dir` directory. If not specified videos are not recorded. Make sure to await browserContext.close() for videos to be saved. - storageState : Optional[{"cookies": Optional[List[Dict]], "origins": Optional[List[Dict]]}] + storageState : Optional[{"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]}] Populates context with given storage state. This method can be used to initialize context with logged-in information obtained via browserContext.storageState(). Returns diff --git a/playwright/types.py b/playwright/types.py new file mode 100644 index 000000000..1dd8efb14 --- /dev/null +++ b/playwright/types.py @@ -0,0 +1,146 @@ +# 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. + +import sys +from typing import Dict, List, Optional, Union + +if sys.version_info >= (3, 8): # pragma: no cover + from typing import Literal, TypedDict +else: # pragma: no cover + from typing_extensions import Literal, TypedDict + +# Explicitly mark optional params as such for the documentation +# If there is at least one optional param, set total=False for better mypy handling. + + +class Cookie(TypedDict, total=False): + name: str + value: str + url: Optional[str] + domain: Optional[str] + path: Optional[str] + expires: Optional[int] + httpOnly: Optional[bool] + secure: Optional[bool] + sameSite: Optional[Literal["Strict", "Lax", "None"]] + + +class StorageState(TypedDict, total=False): + cookies: Optional[List[Cookie]] + origins: Optional[List[Dict]] + + +class MousePosition(TypedDict): + x: float + y: float + + +class ResourceTiming(TypedDict): + startTime: float + domainLookupStart: float + domainLookupEnd: float + connectStart: float + secureConnectionStart: float + connectEnd: float + requestStart: float + responseStart: float + responseEnd: float + + +class FilePayload(TypedDict): + name: str + mimeType: str + buffer: bytes + + +class SelectOption(TypedDict, total=False): + value: Optional[str] + label: Optional[str] + index: Optional[str] + + +class ConsoleMessageLocation(TypedDict): + url: str + lineNumber: int + columnNumber: int + + +class RequestFailure(TypedDict): + errorText: str + + +class Credentials(TypedDict): + username: str + password: str + + +class IntSize(TypedDict): + width: int + height: int + + +class FloatRect(TypedDict): + x: float + y: float + width: float + height: float + + +class Geolocation(TypedDict, total=False): + latitude: float + longitude: float + accuracy: Optional[float] + + +class ProxyServer(TypedDict, total=False): + server: str + bypass: Optional[str] + username: Optional[str] + password: Optional[str] + + +class PdfMargins(TypedDict, total=False): + top: Optional[Union[str, int]] + right: Optional[Union[str, int]] + bottom: Optional[Union[str, int]] + left: Optional[Union[str, int]] + + +class RecordHarOptions(TypedDict, total=False): + omitContent: Optional[bool] + path: str + + +class RecordVideoOptions(TypedDict, total=False): + dir: str + size: Optional[IntSize] + + +class DeviceDescriptor(TypedDict, total=False): + userAgent: Optional[str] + viewport: Optional[IntSize] + deviceScaleFactor: Optional[int] + isMobile: Optional[bool] + hasTouch: Optional[bool] + + +class Error(Exception): + def __init__(self, message: str, stack: str = None) -> None: + self.message = message + self.stack = stack + super().__init__(message) + + +class TimeoutError(Error): + pass diff --git a/playwright/wait_helper.py b/playwright/wait_helper.py index 795ceb22d..24b4ab312 100644 --- a/playwright/wait_helper.py +++ b/playwright/wait_helper.py @@ -17,7 +17,7 @@ from pyee import EventEmitter -from playwright.helper import Error, TimeoutError +from playwright.types import Error, TimeoutError class WaitHelper: diff --git a/scripts/documentation_provider.py b/scripts/documentation_provider.py index 4b2edca48..4029d4641 100644 --- a/scripts/documentation_provider.py +++ b/scripts/documentation_provider.py @@ -241,13 +241,13 @@ def compare_types(self, value: Any, doc_value: Any, fqname: str) -> None: def serialize_python_type(self, value: Any) -> str: str_value = str(value) - if str_value == "": + if str_value == "": return "Error" match = re.match(r"^$", str_value) if match: return match.group(1) match = re.match(r"^$", str_value) - if match and "helper" not in str_value: + if match and "types" not in str_value: return match.group(1) match = re.match(r"^typing\.(\w+)$", str_value) @@ -420,7 +420,10 @@ def print_remainder(self) -> None: sline = line.strip() if not len(sline) or sline.startswith("#"): continue - self.errors.remove(sline) + if sline in self.errors: + self.errors.remove(sline) + else: + print("No longer there: " + sline, file=stderr) if len(self.errors) > 0: for error in self.errors: diff --git a/scripts/expected_api_mismatch.txt b/scripts/expected_api_mismatch.txt index 4d5499edd..69ea0ba51 100644 --- a/scripts/expected_api_mismatch.txt +++ b/scripts/expected_api_mismatch.txt @@ -120,3 +120,10 @@ Method not implemented: BrowserType.connect Parameter not implemented: Page.waitForEvent(optionsOrPredicate=) Parameter not implemented: BrowserContext.waitForEvent(optionsOrPredicate=) Parameter not implemented: WebSocket.waitForEvent(optionsOrPredicate=) + +# Cookies are documented as Array in md, so not parsed. +Parameter type mismatch in Browser.newContext(storageState=): documented as Optional[{"cookies": Optional[List[Dict]], "origins": Optional[List[Dict]]}], code has Optional[{"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]}] +Parameter type mismatch in BrowserContext.addCookies(cookies=): documented as List[Dict], code has List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}] +Parameter type mismatch in BrowserContext.storageState(return=): documented as {"cookies": List[Dict], "origins": List[Dict]}, code has {"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]} +Parameter type mismatch in BrowserContext.cookies(return=): documented as List[Dict], code has List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}] +Parameter type mismatch in Browser.newPage(storageState=): documented as Optional[{"cookies": Optional[List[Dict]], "origins": Optional[List[Dict]]}], code has Optional[{"cookies": Optional[List[{"name": str, "value": str, "url": Optional[str], "domain": Optional[str], "path": Optional[str], "expires": Optional[int], "httpOnly": Optional[bool], "secure": Optional[bool], "sameSite": Optional[Literal['Strict', 'Lax', 'None']]}]], "origins": Optional[List[Dict]]}] diff --git a/scripts/generate_api.py b/scripts/generate_api.py index 1bc9a0e37..7a0ec1dd6 100644 --- a/scripts/generate_api.py +++ b/scripts/generate_api.py @@ -49,8 +49,8 @@ def process_type(value: Any, param: bool = False) -> str: value = str(value) value = re.sub(r"", r"\1", value) - if "playwright.helper" in value: - value = re.sub(r"playwright\.helper\.", "", value) + if "playwright.types" in value: + value = re.sub(r"playwright\.types\.", "", value) value = re.sub(r"playwright\.[\w]+\.([\w]+)", r'"\1"', value) value = re.sub(r"typing.Literal", "Literal", value) if param: @@ -111,7 +111,7 @@ def short_name(t: Any) -> str: def return_value(value: Any) -> List[str]: - if "playwright" not in str(value) or "playwright.helper" in str(value): + if "playwright" not in str(value) or "playwright.types" in str(value): return ["mapping.from_maybe_impl(", ")"] if ( get_origin(value) == Union @@ -163,7 +163,7 @@ def return_value(value: Any) -> List[str]: from playwright.element_handle import ElementHandle as ElementHandleImpl from playwright.file_chooser import FileChooser as FileChooserImpl from playwright.frame import Frame as FrameImpl -from playwright.helper import ConsoleMessageLocation, Credentials, MousePosition, Error, FilePayload, SelectOption, RequestFailure, Viewport, DeviceDescriptor, IntSize, FloatRect, Geolocation, ProxyServer, PdfMargins, ResourceTiming, RecordHarOptions, RecordVideoOptions, StorageState, SetStorageState +from playwright.types import ConsoleMessageLocation, Cookie, Credentials, DeviceDescriptor, MousePosition, Error, FilePayload, SelectOption, RequestFailure, IntSize, FloatRect, Geolocation, ProxyServer, PdfMargins, ResourceTiming, RecordHarOptions, RecordVideoOptions, StorageState from playwright.input import Keyboard as KeyboardImpl, Mouse as MouseImpl, Touchscreen as TouchscreenImpl from playwright.js_handle import JSHandle as JSHandleImpl from playwright.network import Request as RequestImpl, Response as ResponseImpl, Route as RouteImpl, WebSocket as WebSocketImpl diff --git a/tests/async/test_defaultbrowsercontext.py b/tests/async/test_defaultbrowsercontext.py index 6a97c2d21..dc7d8c50b 100644 --- a/tests/async/test_defaultbrowsercontext.py +++ b/tests/async/test_defaultbrowsercontext.py @@ -18,7 +18,7 @@ import pytest from flaky import flaky -from playwright.helper import Error +from playwright.types import Error @pytest.fixture() diff --git a/tests/async/test_keyboard.py b/tests/async/test_keyboard.py index 233c91827..45f1de165 100644 --- a/tests/async/test_keyboard.py +++ b/tests/async/test_keyboard.py @@ -14,7 +14,7 @@ import pytest from playwright.async_api import Page -from playwright.helper import Error +from playwright.types import Error async def captureLastKeydown(page): diff --git a/tests/utils.py b/tests/utils.py index 928a5acc1..9cc8f0d8d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -17,9 +17,9 @@ from playwright.element_handle import ElementHandle from playwright.frame import Frame -from playwright.helper import Error, Viewport from playwright.page import Page from playwright.selectors import Selectors +from playwright.types import Error, IntSize class Utils: @@ -56,8 +56,8 @@ def dump_frames(self, frame: Frame, indentation: str = "") -> List[str]: return result async def verify_viewport(self, page: Page, width: int, height: int): - assert cast(Viewport, page.viewportSize())["width"] == width - assert cast(Viewport, page.viewportSize())["height"] == height + assert cast(IntSize, page.viewportSize())["width"] == width + assert cast(IntSize, page.viewportSize())["height"] == height assert await page.evaluate("window.innerWidth") == width assert await page.evaluate("window.innerHeight") == height