From e860dd8bde360420afc86928a9893ee6c41768bf Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 4 Aug 2020 14:02:21 -0700 Subject: [PATCH 1/4] chore: validate dictionary keys (#137) Co-authored-by: Max Schmitt --- api.json | 14 +- playwright/accessibility.py | 6 +- playwright/async_api.py | 547 +++++++++++++++--------------- playwright/browser.py | 20 +- playwright/browser_context.py | 5 +- playwright/browser_type.py | 68 ++-- playwright/element_handle.py | 29 +- playwright/frame.py | 32 +- playwright/helper.py | 51 ++- playwright/js_handle.py | 9 +- playwright/network.py | 12 +- playwright/page.py | 44 ++- playwright/sync_api.py | 547 +++++++++++++++--------------- scripts/documentation_provider.py | 211 +++++++++--- scripts/generate_api.py | 5 +- tests/async/test_interception.py | 6 +- tests/async/test_network.py | 13 +- 17 files changed, 932 insertions(+), 687 deletions(-) diff --git a/api.json b/api.json index df57be420..807be6d17 100644 --- a/api.json +++ b/api.json @@ -9481,7 +9481,7 @@ "name": "string", "properties": {} }, - "comment": "URL of the resource if available.", + "comment": "Optional URL of the resource if available.", "returnComment": "", "required": false, "templates": [] @@ -9493,7 +9493,7 @@ "name": "number", "properties": {} }, - "comment": "0-based line number in the resource if available.", + "comment": "Optional 0-based line number in the resource if available.", "returnComment": "", "required": false, "templates": [] @@ -9505,7 +9505,7 @@ "name": "number", "properties": {} }, - "comment": "0-based column number in the resource if available.", + "comment": "Optional 0-based column number in the resource if available.", "returnComment": "", "required": false, "templates": [] @@ -9703,7 +9703,7 @@ "name": "string", "properties": {} }, - "comment": "Path where the download should be saved. The directory structure MUST exist as `saveAs` will not create it.", + "comment": "Path where the download should be saved.", "returnComment": "", "required": true, "templates": [] @@ -10632,7 +10632,7 @@ "kind": "method", "name": "headers", "type": { - "name": "Object", + "name": "Object", "properties": {} }, "comment": "", @@ -12216,7 +12216,7 @@ "kind": "property", "name": "viewport", "type": { - "name": "null|Object", + "name": "?Object", "properties": { "width": { "kind": "property", @@ -12358,7 +12358,7 @@ "name": "number", "properties": {} }, - "comment": "Non-negative accuracy value. Defaults to `0`.", + "comment": "Optional non-negative accuracy value. Defaults to `0`.", "returnComment": "", "required": false, "templates": [] diff --git a/playwright/accessibility.py b/playwright/accessibility.py index 7e4d5ed8d..ceefb1c1e 100644 --- a/playwright/accessibility.py +++ b/playwright/accessibility.py @@ -12,14 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Dict, Optional +from typing import Dict, Optional from playwright.connection import Channel from playwright.element_handle import ElementHandle from playwright.helper import locals_to_params -def _ax_node_from_protocol(axNode: Dict[str, Any]) -> Dict[str, Any]: +def _ax_node_from_protocol(axNode: Dict) -> Dict: result = {**axNode} if "valueNumber" in axNode: result["value"] = axNode["valueNumber"] @@ -60,7 +60,7 @@ def __init__(self, channel: Channel) -> None: async def snapshot( self, interestingOnly: bool = None, root: ElementHandle = None - ) -> Optional[Dict[str, Any]]: + ) -> Optional[Dict]: params = locals_to_params(locals()) if root: params["root"] = root._channel diff --git a/playwright/async_api.py b/playwright/async_api.py index efbaa86a8..27942e4c3 100644 --- a/playwright/async_api.py +++ b/playwright/async_api.py @@ -40,9 +40,17 @@ from playwright.frame import Frame as FrameImpl from playwright.helper import ( ConsoleMessageLocation, + Credentials, DeviceDescriptor, Error, FilePayload, + FloatRect, + Geolocation, + IntSize, + MousePosition, + PdfMargins, + ProxyServer, + RequestFailure, SelectOption, Viewport, ) @@ -106,7 +114,7 @@ def postData(self) -> typing.Union[str, NoneType]: Returns ------- - typing.Union[str, NoneType] + Optional[str] Request's post body, if any. """ return mapping.from_maybe_impl(self._impl_obj.postData) @@ -117,7 +125,7 @@ def headers(self) -> typing.Dict[str, str]: Returns ------- - typing.Dict[str, str] + Dict[str, str] An object with HTTP headers associated with the request. All header names are lower-case. """ return mapping.from_maybe_impl(self._impl_obj.headers) @@ -143,7 +151,7 @@ def redirectedFrom(self) -> typing.Union["Request", NoneType]: Returns ------- - typing.Union[Request, NoneType] + Optional[Request] Request that was redirected by the server to this one, if any. """ return mapping.from_impl_nullable(self._impl_obj.redirectedFrom) @@ -156,13 +164,13 @@ def redirectedTo(self) -> typing.Union["Request", NoneType]: Returns ------- - typing.Union[Request, NoneType] + Optional[Request] New request issued by the browser if the server responded with redirect. """ return mapping.from_impl_nullable(self._impl_obj.redirectedTo) @property - def failure(self) -> typing.Union[str, NoneType]: + def failure(self) -> typing.Union[RequestFailure, NoneType]: """Request.failure The method returns `null` unless this request has failed, as reported by @@ -171,7 +179,7 @@ def failure(self) -> typing.Union[str, NoneType]: Returns ------- - typing.Union[str, NoneType] + Optional[{"errorText": str}] Object describing request failure, if any """ return mapping.from_maybe_impl(self._impl_obj.failure) @@ -181,7 +189,7 @@ async def response(self) -> typing.Union["Response", NoneType]: Returns ------- - typing.Union[Response, NoneType] + Optional[Response] A matching Response object, or `null` if the response was not received due to error. """ return mapping.from_impl_nullable(await self._impl_obj.response()) @@ -259,7 +267,7 @@ def headers(self) -> typing.Dict[str, str]: Returns ------- - typing.Dict[str, str] + Dict[str, str] An object with HTTP headers associated with the response. All header names are lower-case. """ return mapping.from_maybe_impl(self._impl_obj.headers) @@ -291,7 +299,7 @@ async def finished(self) -> typing.Union[Error, NoneType]: Returns ------- - typing.Union[Error, NoneType] + Optional[Error] Waits for this response to finish, returns failure error if request failed. """ return mapping.from_maybe_impl(await self._impl_obj.finished()) @@ -323,7 +331,7 @@ async def json(self) -> typing.Union[typing.Dict, typing.List]: Returns ------- - typing.Union[typing.Dict, typing.List] + Union[Dict, List] Promise which resolves to a JSON representation of response body. """ return mapping.from_maybe_impl(await self._impl_obj.json()) @@ -393,11 +401,11 @@ async def fulfill( ---------- status : Optional[int] Response status code, defaults to `200`. - headers : Optional[typing.Dict[str, str]] + headers : Optional[Dict[str, str]] Optional response headers. Header values will be converted to a string. - body : Optional[str, bytes] + body : Union[str, bytes, NoneType] Optional response body. - path : Optional[str, pathlib.Path] + path : Union[str, pathlib.Path, NoneType] Optional file path to respond with. The content type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to current working directory. contentType : Optional[str] If set, equals to setting `Content-Type` response header. @@ -426,9 +434,9 @@ async def continue_( ---------- method : Optional[str] If set changes the request method (e.g. GET or POST) - headers : Optional[typing.Dict[str, str]] + headers : Optional[Dict[str, str]] If set changes the request HTTP headers. Header values will be converted to a string. - postData : Optional[str, bytes] + postData : Union[str, bytes, NoneType] If set changes the post data of request """ return mapping.from_maybe_impl( @@ -562,7 +570,7 @@ async def down( Parameters ---------- - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. clickCount : Optional[int] defaults to 1. See UIEvent.detail. @@ -580,7 +588,7 @@ async def up( Parameters ---------- - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. clickCount : Optional[int] defaults to 1. See UIEvent.detail. @@ -607,7 +615,7 @@ async def click( y : float delay : Optional[int] Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. clickCount : Optional[int] defaults to 1. See UIEvent.detail. @@ -635,7 +643,7 @@ async def dblclick( y : float delay : Optional[int] Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. """ return mapping.from_maybe_impl( @@ -665,12 +673,12 @@ async def evaluate( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -695,7 +703,7 @@ async def evaluateHandle( Function to be evaluated force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns @@ -734,7 +742,7 @@ async def getProperties(self) -> typing.Dict[str, "JSHandle"]: Returns ------- - typing.Dict[str, JSHandle] + Dict[str, JSHandle] """ return mapping.from_impl_dict(await self._impl_obj.getProperties()) @@ -745,7 +753,7 @@ def asElement(self) -> typing.Union["ElementHandle", NoneType]: Returns ------- - typing.Union[ElementHandle, NoneType] + Optional[ElementHandle] """ return mapping.from_impl_nullable(self._impl_obj.asElement()) @@ -767,7 +775,7 @@ async def jsonValue(self) -> typing.Any: Returns ------- - typing.Any + Any """ return mapping.from_maybe_impl(await self._impl_obj.jsonValue()) @@ -786,7 +794,7 @@ def asElement(self) -> typing.Union["ElementHandle", NoneType]: Returns ------- - typing.Union[ElementHandle, NoneType] + Optional[ElementHandle] """ return mapping.from_impl_nullable(self._impl_obj.asElement()) @@ -795,7 +803,7 @@ async def ownerFrame(self) -> typing.Union["Frame", NoneType]: Returns ------- - typing.Union[Frame, NoneType] + Optional[Frame] Returns the frame containing the given element. """ return mapping.from_impl_nullable(await self._impl_obj.ownerFrame()) @@ -805,7 +813,7 @@ async def contentFrame(self) -> typing.Union["Frame", NoneType]: Returns ------- - typing.Union[Frame, NoneType] + Optional[Frame] Resolves to the content frame for element handles referencing iframe nodes, or `null` otherwise """ return mapping.from_impl_nullable(await self._impl_obj.contentFrame()) @@ -822,7 +830,7 @@ async def getAttribute(self, name: str) -> typing.Union[str, NoneType]: Returns ------- - typing.Union[str, NoneType] + Optional[str] """ return mapping.from_maybe_impl(await self._impl_obj.getAttribute(name=name)) @@ -831,7 +839,7 @@ async def textContent(self) -> typing.Union[str, NoneType]: Returns ------- - typing.Union[str, NoneType] + Optional[str] Resolves to the `node.textContent`. """ return mapping.from_maybe_impl(await self._impl_obj.textContent()) @@ -877,7 +885,7 @@ async def dispatchEvent(self, type: str, eventInit: typing.Dict = None) -> NoneT ---------- type : str DOM event type: `"click"`, `"dragstart"`, etc. - eventInit : Optional[typing.Dict] + eventInit : Optional[Dict] event-specific initialization properties. """ return mapping.from_maybe_impl( @@ -906,7 +914,7 @@ async def hover( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, timeout: int = None, force: bool = None, ) -> NoneType: @@ -917,9 +925,9 @@ async def hover( Parameters ---------- - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the hover, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to hover relative to the top-left corner of element padding box. If not specified, hovers over some visible point of the element. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -928,10 +936,7 @@ async def hover( """ return mapping.from_maybe_impl( await self._impl_obj.hover( - modifiers=modifiers, - position=mapping.to_impl(position), - timeout=timeout, - force=force, + modifiers=modifiers, position=position, timeout=timeout, force=force ) ) @@ -940,7 +945,7 @@ async def click( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, delay: int = None, button: Literal["left", "right", "middle"] = None, clickCount: int = None, @@ -955,13 +960,13 @@ async def click( Parameters ---------- - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the click, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to click relative to the top-left corner of element padding box. If not specified, clicks to some visible point of the element. delay : Optional[int] Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. clickCount : Optional[int] defaults to 1. See UIEvent.detail. @@ -975,7 +980,7 @@ async def click( return mapping.from_maybe_impl( await self._impl_obj.click( modifiers=modifiers, - position=mapping.to_impl(position), + position=position, delay=delay, button=button, clickCount=clickCount, @@ -990,7 +995,7 @@ async def dblclick( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, delay: int = None, button: Literal["left", "right", "middle"] = None, timeout: int = None, @@ -1007,13 +1012,13 @@ async def dblclick( Parameters ---------- - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the double click, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to double click relative to the top-left corner of element padding box. If not specified, double clicks to some visible point of the element. delay : Optional[int] Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -1025,7 +1030,7 @@ async def dblclick( return mapping.from_maybe_impl( await self._impl_obj.dblclick( modifiers=modifiers, - position=mapping.to_impl(position), + position=position, delay=delay, button=button, timeout=timeout, @@ -1054,7 +1059,7 @@ async def selectOption( Parameters ---------- - values : Optional[str, ElementHandle, SelectOption, typing.List[str], typing.List[ElementHandle], typing.List[SelectOption]] + values : Union[str, ElementHandle, {"value": Optional[str], "label": Optional[str], "index": Optional[str]}, List[str], List[ElementHandle], List[{"value": Optional[str], "label": Optional[str], "index": Optional[str]}], NoneType] Options to select. If the `` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -2368,7 +2373,7 @@ async def selectOption( Returns ------- - typing.List[str] + List[str] An array of option values that have been successfully selected. """ return mapping.from_maybe_impl( @@ -2403,7 +2408,7 @@ async def setInputFiles( ---------- selector : str A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. See working with selectors for more details. - files : typing.Union[str, pathlib.Path, FilePayload, typing.List[str], typing.List[pathlib.Path], typing.List[FilePayload]] + files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]] timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. noWaitAfter : Optional[bool] @@ -2584,11 +2589,11 @@ async def waitForFunction( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` timeout : Optional[int] maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - polling : Optional[int, typing.Literal['raf']] + polling : Union[int, 'raf', NoneType] If `polling` is `'raf'`, then `pageFunction` is constantly executed in `requestAnimationFrame` callback. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. Defaults to `raf`. Returns @@ -2665,12 +2670,12 @@ async def evaluate( Function to be evaluated in the worker context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -2693,7 +2698,7 @@ async def evaluateHandle( Function to be evaluated in the page context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns @@ -2777,7 +2782,7 @@ def args(self) -> typing.List["JSHandle"]: Returns ------- - typing.List[JSHandle] + List[JSHandle] """ return mapping.from_impl_list(self._impl_obj.args) @@ -2787,7 +2792,7 @@ def location(self) -> ConsoleMessageLocation: Returns ------- - ConsoleMessageLocation + {"url": Optional[str], "lineNumber": Optional[int], "columnNumber": Optional[int]} """ return mapping.from_maybe_impl(self._impl_obj.location) @@ -2895,7 +2900,7 @@ async def failure(self) -> typing.Union[str, NoneType]: Returns ------- - typing.Union[str, NoneType] + Optional[str] """ return mapping.from_maybe_impl(await self._impl_obj.failure()) @@ -2906,7 +2911,7 @@ async def path(self) -> typing.Union[str, NoneType]: Returns ------- - typing.Union[str, NoneType] + Optional[str] """ return mapping.from_maybe_impl(await self._impl_obj.path()) @@ -2992,7 +2997,7 @@ def frames(self) -> typing.List["Frame"]: Returns ------- - typing.List[Frame] + List[Frame] An array of all frames attached to the page. """ return mapping.from_impl_list(self._impl_obj.frames) @@ -3017,7 +3022,7 @@ def workers(self) -> typing.List["Worker"]: Returns ------- - typing.List[Worker] + List[Worker] This method returns all of the dedicated WebWorkers associated with the page. """ return mapping.from_impl_list(self._impl_obj.workers) @@ -3027,7 +3032,7 @@ async def opener(self) -> typing.Union["Page", NoneType]: Returns ------- - typing.Union[Page, NoneType] + Optional[Page] Promise which resolves to the opener for popup pages and `null` for others. If the opener has been closed already the promise may resolve to `null`. """ return mapping.from_impl_nullable(await self._impl_obj.opener()) @@ -3045,12 +3050,12 @@ def frame( ---------- name : Optional[str] frame name specified in the `iframe`'s `name` attribute - url : Optional[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool], NoneType] A glob pattern, regex pattern or predicate receiving frame's `url` as a URL object. Returns ------- - typing.Union[Frame, NoneType] + Optional[Frame] frame matching the criteria. Returns `null` if no frame matches. """ return mapping.from_impl_nullable( @@ -3111,7 +3116,7 @@ async def querySelector( Returns ------- - typing.Union[ElementHandle, NoneType] + Optional[ElementHandle] """ return mapping.from_impl_nullable( await self._impl_obj.querySelector(selector=selector) @@ -3130,7 +3135,7 @@ async def querySelectorAll(self, selector: str) -> typing.List["ElementHandle"]: Returns ------- - typing.List[ElementHandle] + List[ElementHandle] """ return mapping.from_impl_list( await self._impl_obj.querySelectorAll(selector=selector) @@ -3155,7 +3160,7 @@ async def waitForSelector( A selector of an element to wait for. See working with selectors for more details. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - state : Optional[typing.Literal['attached', 'detached', 'visible', 'hidden']] + state : Optional[Literal['attached', 'detached', 'visible', 'hidden']] Defaults to `'visible'`. Can be either: - `'attached'` - wait for element to be present in DOM. - `'detached'` - wait for element to not be present in DOM. @@ -3164,7 +3169,7 @@ async def waitForSelector( Returns ------- - typing.Union[ElementHandle, NoneType] + Optional[ElementHandle] Promise which resolves when element specified by selector satisfies `state` option. Resolves to `null` if waiting for `hidden` or `detached`. """ return mapping.from_impl_nullable( @@ -3202,7 +3207,7 @@ async def dispatchEvent( A selector to search for element to use. If there are multiple elements satisfying the selector, the first will be used. See working with selectors for more details. type : str DOM event type: `"click"`, `"dragstart"`, etc. - eventInit : Optional[typing.Dict] + eventInit : Optional[Dict] event-specific initialization properties. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -3234,12 +3239,12 @@ async def evaluate( Function to be evaluated in the page context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -3264,7 +3269,7 @@ async def evaluateHandle( Function to be evaluated in the page context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns @@ -3300,12 +3305,12 @@ async def evalOnSelector( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -3338,12 +3343,12 @@ async def evalOnSelectorAll( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -3429,7 +3434,7 @@ async def exposeFunction(self, name: str, binding: typing.Callable) -> NoneType: ---------- name : str Name of the function on the window object - binding : typing.Callable + binding : Callable Callback function which will be called in Playwright's context. """ return mapping.from_maybe_impl( @@ -3456,7 +3461,7 @@ async def exposeBinding(self, name: str, binding: typing.Callable) -> NoneType: ---------- name : str Name of the function on the window object. - binding : typing.Callable + binding : Callable Callback function that will be called in the Playwright's context. """ return mapping.from_maybe_impl( @@ -3465,7 +3470,7 @@ async def exposeBinding(self, name: str, binding: typing.Callable) -> NoneType: ) ) - async def setExtraHTTPHeaders(self, headers: typing.Dict) -> NoneType: + async def setExtraHTTPHeaders(self, headers: typing.Dict[str, str]) -> NoneType: """Page.setExtraHTTPHeaders The extra HTTP headers will be sent with every request the page initiates. @@ -3474,7 +3479,7 @@ async def setExtraHTTPHeaders(self, headers: typing.Dict) -> NoneType: Parameters ---------- - headers : typing.Dict + headers : Dict[str, str] An object containing additional HTTP headers to be sent with every request. All header values must be strings. """ return mapping.from_maybe_impl( @@ -3506,7 +3511,7 @@ async def setContent( HTML markup to assign to the page. timeout : Optional[int] Maximum time in milliseconds for resources to load, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider setting markup succeeded, defaults to `load`. Given an array of event strings, setting content is considered to be successful after all events have been fired. Events can be either: - `'load'` - consider setting content to be finished when the `load` event is fired. - `'domcontentloaded'` - consider setting content to be finished when the `DOMContentLoaded` event is fired. @@ -3549,7 +3554,7 @@ async def goto( URL to navigate page to. The url should include scheme, e.g. `https://`. timeout : Optional[int] Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider navigation succeeded, defaults to `load`. Events can be either: - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - `'load'` - consider navigation to be finished when the `load` event is fired. @@ -3559,7 +3564,7 @@ async def goto( Returns ------- - typing.Union[Response, NoneType] + Optional[Response] Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. """ return mapping.from_impl_nullable( @@ -3579,7 +3584,7 @@ async def reload( ---------- timeout : Optional[int] Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider navigation succeeded, defaults to `load`. Events can be either: - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - `'load'` - consider navigation to be finished when the `load` event is fired. @@ -3587,7 +3592,7 @@ async def reload( Returns ------- - typing.Union[Response, NoneType] + Optional[Response] Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. """ return mapping.from_impl_nullable( @@ -3606,7 +3611,7 @@ async def waitForLoadState( Parameters ---------- - state : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + state : Optional[Literal['load', 'domcontentloaded', 'networkidle']] Load state to wait for, defaults to `load`. If the state has been already reached while loading current document, the method resolves immediately. - `'load'` - wait for the `load` event to be fired. - `'domcontentloaded'` - wait for the `DOMContentLoaded` event to be fired. @@ -3633,9 +3638,9 @@ async def waitForNavigation( Parameters ---------- - url : Optional[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool], NoneType] A glob pattern, regex pattern or predicate receiving URL to match while waiting for the navigation. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider navigation succeeded, defaults to `load`. Events can be either: - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - `'load'` - consider navigation to be finished when the `load` event is fired. @@ -3645,7 +3650,7 @@ async def waitForNavigation( Returns ------- - typing.Union[Response, NoneType] + Optional[Response] Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. In case of navigation to a different anchor or navigation due to History API usage, the navigation will resolve with `null`. """ return mapping.from_impl_nullable( @@ -3665,7 +3670,7 @@ async def waitForRequest( Parameters ---------- - url : Optional[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool], NoneType] Request URL string, regex or predicate receiving Request object. timeout : Optional[int] Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the page.setDefaultTimeout(timeout) method. @@ -3694,7 +3699,7 @@ async def waitForResponse( Parameters ---------- - url : Optional[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool], NoneType] Request URL string, regex or predicate receiving Response object. timeout : Optional[int] Maximum wait time in milliseconds, defaults to 30 seconds, pass `0` to disable the timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -3730,7 +3735,7 @@ async def waitForEvent( Returns ------- - typing.Any + Any Promise which resolves to the event data value. """ return mapping.from_maybe_impl( @@ -3752,7 +3757,7 @@ async def goBack( ---------- timeout : Optional[int] Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider navigation succeeded, defaults to `load`. Events can be either: - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - `'load'` - consider navigation to be finished when the `load` event is fired. @@ -3760,7 +3765,7 @@ async def goBack( Returns ------- - typing.Union[Response, NoneType] + Optional[Response] Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. If can not go back, resolves to `null`. """ @@ -3781,7 +3786,7 @@ async def goForward( ---------- timeout : Optional[int] Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider navigation succeeded, defaults to `load`. Events can be either: - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - `'load'` - consider navigation to be finished when the `load` event is fired. @@ -3789,7 +3794,7 @@ async def goForward( Returns ------- - typing.Union[Response, NoneType] + Optional[Response] Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. If can not go forward, resolves to `null`. """ @@ -3807,9 +3812,9 @@ async def emulateMedia( Parameters ---------- - media : Optional[typing.Literal['screen', 'print']] + media : Optional[Literal['screen', 'print']] Changes the CSS media type of the page. The only allowed values are `'screen'`, `'print'` and `null`. Passing `null` disables CSS media emulation. Omitting `media` or passing `undefined` does not change the emulated value. - colorScheme : Optional[typing.Literal['light', 'dark', 'no-preference']] + colorScheme : Optional[Literal['light', 'dark', 'no-preference']] Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. Passing `null` disables color scheme emulation. Omitting `colorScheme` or passing `undefined` does not change the emulated value. """ return mapping.from_maybe_impl( @@ -3838,7 +3843,7 @@ def viewportSize(self) -> typing.Union[Viewport, NoneType]: Returns ------- - typing.Union[Viewport, NoneType] + Optional[{"width": int, "height": int}] """ return mapping.from_maybe_impl(self._impl_obj.viewportSize()) @@ -3888,9 +3893,9 @@ async def route( Parameters ---------- - url : typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool]] A glob pattern, regex pattern or predicate receiving URL to match while routing. - handler : typing.Callable[[Route, Request], typing.Any] + handler : typing.Callable[[playwright.network.Route, playwright.network.Request], typing.Any] handler function to route the request. """ return mapping.from_maybe_impl( @@ -3910,9 +3915,9 @@ async def unroute( Parameters ---------- - url : typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool]] A glob pattern, regex pattern or predicate receiving URL to match while routing. - handler : Optional[typing.Callable[[Route, Request], typing.Any]] + handler : Optional[typing.Callable[[playwright.network.Route, playwright.network.Request], typing.Any]] Handler function to route the request. """ return mapping.from_maybe_impl( @@ -3929,7 +3934,7 @@ async def screenshot( quality: int = None, omitBackground: bool = None, fullPage: bool = None, - clip: typing.Dict = None, + clip: FloatRect = None, ) -> bytes: """Page.screenshot @@ -3939,7 +3944,7 @@ async def screenshot( ---------- timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - type : Optional[typing.Literal['png', 'jpeg']] + type : Optional[Literal['png', 'jpeg']] Specify screenshot type, defaults to `png`. path : Optional[str] The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to current working directory. If no path is provided, the image won't be saved to the disk. @@ -3949,7 +3954,7 @@ async def screenshot( Hides default white background and allows capturing screenshots with transparency. Not applicable to `jpeg` images. Defaults to `false`. fullPage : Optional[bool] When true, takes a screenshot of the full scrollable page, instead of the currently visibvle viewport. Defaults to `false`. - clip : Optional[typing.Dict] + clip : Optional[{"x": float, "y": float, "width": float, "height": float}] An object which specifies clipping of the resulting image. Should have the following fields: Returns @@ -3965,7 +3970,7 @@ async def screenshot( quality=quality, omitBackground=omitBackground, fullPage=fullPage, - clip=mapping.to_impl(clip), + clip=clip, ) ) @@ -4017,7 +4022,7 @@ async def click( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, delay: int = None, button: Literal["left", "right", "middle"] = None, clickCount: int = None, @@ -4035,13 +4040,13 @@ async def click( ---------- selector : str A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. See working with selectors for more details. - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the click, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to click relative to the top-left corner of element padding box. If not specified, clicks to some visible point of the element. delay : Optional[int] Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. clickCount : Optional[int] defaults to 1. See UIEvent.detail. @@ -4056,7 +4061,7 @@ async def click( await self._impl_obj.click( selector=selector, modifiers=modifiers, - position=mapping.to_impl(position), + position=position, delay=delay, button=button, clickCount=clickCount, @@ -4072,7 +4077,7 @@ async def dblclick( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, delay: int = None, button: Literal["left", "right", "middle"] = None, timeout: int = None, @@ -4092,13 +4097,13 @@ async def dblclick( ---------- selector : str A selector to search for element to double click. If there are multiple elements satisfying the selector, the first will be double clicked. See working with selectors for more details. - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the double click, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to double click relative to the top-left corner of element padding box. If not specified, double clicks to some visible point of the element. delay : Optional[int] Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -4109,7 +4114,7 @@ async def dblclick( await self._impl_obj.dblclick( selector=selector, modifiers=modifiers, - position=mapping.to_impl(position), + position=position, delay=delay, button=button, timeout=timeout, @@ -4179,7 +4184,7 @@ async def textContent( Returns ------- - typing.Union[str, NoneType] + Optional[str] """ return mapping.from_maybe_impl( await self._impl_obj.textContent(selector=selector, timeout=timeout) @@ -4243,7 +4248,7 @@ async def getAttribute( Returns ------- - typing.Union[str, NoneType] + Optional[str] """ return mapping.from_maybe_impl( await self._impl_obj.getAttribute( @@ -4257,7 +4262,7 @@ async def hover( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, timeout: int = None, force: bool = None, ) -> NoneType: @@ -4271,9 +4276,9 @@ async def hover( ---------- selector : str A selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered. See working with selectors for more details. - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the hover, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to hover relative to the top-left corner of element padding box. If not specified, hovers over some visible point of the element. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -4284,7 +4289,7 @@ async def hover( await self._impl_obj.hover( selector=selector, modifiers=modifiers, - position=mapping.to_impl(position), + position=position, timeout=timeout, force=force, ) @@ -4315,7 +4320,7 @@ async def selectOption( ---------- selector : str A selector to query page for. See working with selectors for more details. - values : Optional[str, ElementHandle, SelectOption, typing.List[str], typing.List[ElementHandle], typing.List[SelectOption]] + values : Union[str, ElementHandle, {"value": Optional[str], "label": Optional[str], "index": Optional[str]}, List[str], List[ElementHandle], List[{"value": Optional[str], "label": Optional[str], "index": Optional[str]}], NoneType] Options to select. If the `` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -1093,7 +1098,7 @@ def selectOption( Returns ------- - typing.List[str] + List[str] An array of option values that have been successfully selected. """ return mapping.from_maybe_impl( @@ -1166,7 +1171,7 @@ def setInputFiles( Parameters ---------- - files : typing.Union[str, pathlib.Path, FilePayload, typing.List[str], typing.List[pathlib.Path], typing.List[FilePayload]] + files : Union[str, pathlib.Path, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[pathlib.Path], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]] timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. noWaitAfter : Optional[bool] @@ -1299,14 +1304,14 @@ def uncheck( ) ) - def boundingBox(self) -> typing.Dict[str, float]: + def boundingBox(self) -> typing.Union[FloatRect, NoneType]: """ElementHandle.boundingBox This method returns the bounding box of the element (relative to the main frame), or `null` if the element is not visible. Returns ------- - typing.Dict[str, float] + Optional[{"x": float, "y": float, "width": float, "height": float}] """ return mapping.from_maybe_impl(self._sync(self._impl_obj.boundingBox())) @@ -1326,7 +1331,7 @@ def screenshot( ---------- timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - type : Optional[typing.Literal['png', 'jpeg']] + type : Optional[Literal['png', 'jpeg']] Specify screenshot type, defaults to `png`. path : Optional[str] The file path to save the image to. The screenshot type will be inferred from file extension. If `path` is a relative path, then it is resolved relative to current working directory. If no path is provided, the image won't be saved to the disk. @@ -1364,7 +1369,7 @@ def querySelector(self, selector: str) -> typing.Union["ElementHandle", NoneType Returns ------- - typing.Union[ElementHandle, NoneType] + Optional[ElementHandle] """ return mapping.from_impl_nullable( self._sync(self._impl_obj.querySelector(selector=selector)) @@ -1382,7 +1387,7 @@ def querySelectorAll(self, selector: str) -> typing.List["ElementHandle"]: Returns ------- - typing.List[ElementHandle] + List[ElementHandle] """ return mapping.from_impl_list( self._sync(self._impl_obj.querySelectorAll(selector=selector)) @@ -1409,12 +1414,12 @@ def evalOnSelector( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -1455,12 +1460,12 @@ def evalOnSelectorAll( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -1484,7 +1489,7 @@ def __init__(self, obj: AccessibilityImpl): def snapshot( self, interestingOnly: bool = None, root: "ElementHandle" = None - ) -> typing.Union[typing.Dict[str, typing.Any], NoneType]: + ) -> typing.Union[typing.Dict, NoneType]: """Accessibility.snapshot Captures the current state of the accessibility tree. The returned object represents the root accessible node of the page. @@ -1505,7 +1510,7 @@ def snapshot( Returns ------- - typing.Union[typing.Dict[str, typing.Any], NoneType] + Optional[Dict] An AXNode object with the following properties: """ return mapping.from_maybe_impl( @@ -1574,7 +1579,7 @@ def setFiles( Parameters ---------- - files : typing.Union[str, FilePayload, typing.List[str], typing.List[FilePayload]] + files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]] timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. noWaitAfter : Optional[bool] @@ -1629,7 +1634,7 @@ def parentFrame(self) -> typing.Union["Frame", NoneType]: Returns ------- - typing.Union[Frame, NoneType] + Optional[Frame] Parent frame, if any. Detached frames and main frames return `null`. """ return mapping.from_impl_nullable(self._impl_obj.parentFrame) @@ -1640,7 +1645,7 @@ def childFrames(self) -> typing.List["Frame"]: Returns ------- - typing.List[Frame] + List[Frame] """ return mapping.from_impl_list(self._impl_obj.childFrames) @@ -1673,7 +1678,7 @@ def goto( URL to navigate frame to. The url should include scheme, e.g. `https://`. timeout : Optional[int] Maximum navigation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider navigation succeeded, defaults to `load`. Events can be either: - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - `'load'` - consider navigation to be finished when the `load` event is fired. @@ -1683,7 +1688,7 @@ def goto( Returns ------- - typing.Union[Response, NoneType] + Optional[Response] Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. """ return mapping.from_impl_nullable( @@ -1708,9 +1713,9 @@ def waitForNavigation( Parameters ---------- - url : Optional[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool], NoneType] URL string, URL regex pattern or predicate receiving URL to match while waiting for the navigation. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider navigation succeeded, defaults to `load`. Events can be either: - `'domcontentloaded'` - consider navigation to be finished when the `DOMContentLoaded` event is fired. - `'load'` - consider navigation to be finished when the `load` event is fired. @@ -1720,7 +1725,7 @@ def waitForNavigation( Returns ------- - typing.Union[Response, NoneType] + Optional[Response] Promise which resolves to the main resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect. In case of navigation to a different anchor or navigation due to History API usage, the navigation will resolve with `null`. """ return mapping.from_impl_nullable( @@ -1742,7 +1747,7 @@ def waitForLoadState( Parameters ---------- - state : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + state : Optional[Literal['load', 'domcontentloaded', 'networkidle']] Load state to wait for, defaults to `load`. If the state has been already reached while loading current document, the method resolves immediately. - `'load'` - wait for the `load` event to be fired. - `'domcontentloaded'` - wait for the `DOMContentLoaded` event to be fired. @@ -1783,12 +1788,12 @@ def evaluate( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -1817,7 +1822,7 @@ def evaluateHandle( Function to be evaluated in the page context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns @@ -1847,7 +1852,7 @@ def querySelector(self, selector: str) -> typing.Union["ElementHandle", NoneType Returns ------- - typing.Union[ElementHandle, NoneType] + Optional[ElementHandle] Promise which resolves to ElementHandle pointing to the frame element. """ return mapping.from_impl_nullable( @@ -1866,7 +1871,7 @@ def querySelectorAll(self, selector: str) -> typing.List["ElementHandle"]: Returns ------- - typing.List[ElementHandle] + List[ElementHandle] Promise which resolves to ElementHandles pointing to the frame elements. """ return mapping.from_impl_list( @@ -1890,7 +1895,7 @@ def waitForSelector( A selector of an element to wait for. See working with selectors for more details. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - state : Optional[typing.Literal['attached', 'detached', 'visible', 'hidden']] + state : Optional[Literal['attached', 'detached', 'visible', 'hidden']] Defaults to `'visible'`. Can be either: - `'attached'` - wait for element to be present in DOM. - `'detached'` - wait for element to not be present in DOM. @@ -1899,7 +1904,7 @@ def waitForSelector( Returns ------- - typing.Union[ElementHandle, NoneType] + Optional[ElementHandle] Promise which resolves when element specified by selector satisfies `state` option. Resolves to `null` if waiting for `hidden` or `detached`. """ return mapping.from_impl_nullable( @@ -1939,7 +1944,7 @@ def dispatchEvent( A selector to search for element to use. If there are multiple elements satisfying the selector, the first will be double clicked. See working with selectors for more details. type : str DOM event type: `"click"`, `"dragstart"`, etc. - eventInit : Optional[typing.Dict] + eventInit : Optional[Dict] event-specific initialization properties. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -1976,12 +1981,12 @@ def evalOnSelector( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -2016,12 +2021,12 @@ def evalOnSelectorAll( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` Returns ------- - typing.Any + Any Promise which resolves to the return value of `pageFunction` """ return mapping.from_maybe_impl( @@ -2060,7 +2065,7 @@ def setContent( HTML markup to assign to the page. timeout : Optional[int] Maximum time in milliseconds for resources to load, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultNavigationTimeout(timeout), browserContext.setDefaultTimeout(timeout), page.setDefaultNavigationTimeout(timeout) or page.setDefaultTimeout(timeout) methods. - waitUntil : Optional[typing.Literal['load', 'domcontentloaded', 'networkidle']] + waitUntil : Optional[Literal['load', 'domcontentloaded', 'networkidle']] When to consider navigation succeeded, defaults to `load`. Events can be either: - `'domcontentloaded'` - consider setting content to be finished when the `DOMContentLoaded` event is fired. - `'load'` - consider setting content to be finished when the `load` event is fired. @@ -2147,7 +2152,7 @@ def click( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, delay: int = None, button: Literal["left", "right", "middle"] = None, clickCount: int = None, @@ -2164,13 +2169,13 @@ def click( ---------- selector : str A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. See working with selectors for more details. - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the click, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to click relative to the top-left corner of element padding box. If not specified, clicks to some visible point of the element. delay : Optional[int] Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. clickCount : Optional[int] defaults to 1. See UIEvent.detail. @@ -2186,7 +2191,7 @@ def click( self._impl_obj.click( selector=selector, modifiers=modifiers, - position=mapping.to_impl(position), + position=position, delay=delay, button=button, clickCount=clickCount, @@ -2203,7 +2208,7 @@ def dblclick( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, delay: int = None, button: Literal["left", "right", "middle"] = None, timeout: int = None, @@ -2221,13 +2226,13 @@ def dblclick( ---------- selector : str A selector to search for element to double click. If there are multiple elements satisfying the selector, the first will be double clicked. See working with selectors for more details. - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the double click, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to double click relative to the top-left corner of element padding box. If not specified, double clicks to some visible point of the element. delay : Optional[int] Time to wait between `mousedown` and `mouseup` in milliseconds. Defaults to 0. - button : Optional[typing.Literal['left', 'right', 'middle']] + button : Optional[Literal['left', 'right', 'middle']] Defaults to `left`. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -2239,7 +2244,7 @@ def dblclick( self._impl_obj.dblclick( selector=selector, modifiers=modifiers, - position=mapping.to_impl(position), + position=position, delay=delay, button=button, timeout=timeout, @@ -2313,7 +2318,7 @@ def textContent( Returns ------- - typing.Union[str, NoneType] + Optional[str] """ return mapping.from_maybe_impl( self._sync(self._impl_obj.textContent(selector=selector, timeout=timeout)) @@ -2377,7 +2382,7 @@ def getAttribute( Returns ------- - typing.Union[str, NoneType] + Optional[str] """ return mapping.from_maybe_impl( self._sync( @@ -2393,7 +2398,7 @@ def hover( modifiers: typing.Union[ typing.List[Literal["Alt", "Control", "Meta", "Shift"]] ] = None, - position: typing.Dict = None, + position: MousePosition = None, timeout: int = None, force: bool = None, ) -> NoneType: @@ -2406,9 +2411,9 @@ def hover( ---------- selector : str A selector to search for element to hover. If there are multiple elements satisfying the selector, the first will be hovered. See working with selectors for more details. - modifiers : Optional[typing.List[typing.Literal['Alt', 'Control', 'Meta', 'Shift']]] + modifiers : Optional[List[Literal['Alt', 'Control', 'Meta', 'Shift']]] Modifier keys to press. Ensures that only these modifiers are pressed during the hover, and then restores current modifiers back. If not specified, currently pressed modifiers are used. - position : Optional[typing.Dict] + position : Optional[{"x": float, "y": float}] A point to hover relative to the top-left corner of element padding box. If not specified, hovers over some visible point of the element. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -2420,7 +2425,7 @@ def hover( self._impl_obj.hover( selector=selector, modifiers=modifiers, - position=mapping.to_impl(position), + position=position, timeout=timeout, force=force, ) @@ -2450,7 +2455,7 @@ def selectOption( ---------- selector : str A selector to query frame for. See working with selectors for more details. - values : Optional[str, ElementHandle, SelectOption, typing.List[str], typing.List[ElementHandle], typing.List[SelectOption]] + values : Union[str, ElementHandle, {"value": Optional[str], "label": Optional[str], "index": Optional[str]}, List[str], List[ElementHandle], List[{"value": Optional[str], "label": Optional[str], "index": Optional[str]}], NoneType] Options to select. If the `` has the `multiple` attribute, all matching options are selected, otherwise only the first option matching one of the passed options is selected. String values are equivalent to `{value:'string'}`. Option is considered matching if all specified properties match. timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. @@ -4507,7 +4512,7 @@ def selectOption( Returns ------- - typing.List[str] + List[str] An array of option values that have been successfully selected. """ return mapping.from_maybe_impl( @@ -4539,7 +4544,7 @@ def setInputFiles( ---------- selector : str A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked. See working with selectors for more details. - files : typing.Union[str, FilePayload, typing.List[str], typing.List[FilePayload]] + files : Union[str, {"name": str, "mimeType": str, "buffer": Union[bytes, str]}, List[str], List[{"name": str, "mimeType": str, "buffer": Union[bytes, str]}]] timeout : Optional[int] Maximum time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be changed by using the browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) methods. noWaitAfter : Optional[bool] @@ -4745,11 +4750,11 @@ def waitForFunction( Function to be evaluated in browser context force_expr : bool Whether to treat given expression as JavaScript evaluate expression, even though it looks like an arrow function - arg : Optional[typing.Any] + arg : Optional[Any] Optional argument to pass to `pageFunction` timeout : Optional[int] maximum time to wait for in milliseconds. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. The default value can be changed by using the page.setDefaultTimeout(timeout) method. - polling : Optional[int, typing.Literal['raf']] + polling : Union[int, 'raf', NoneType] If `polling` is `'raf'`, then `pageFunction` is constantly executed in `requestAnimationFrame` callback. If `polling` is a number, then it is treated as an interval in milliseconds at which the function would be executed. Defaults to `raf`. Returns @@ -4782,7 +4787,7 @@ def pdf( width: typing.Union[str, float] = None, height: typing.Union[str, float] = None, preferCSSPageSize: bool = None, - margin: typing.Dict = None, + margin: PdfMargins = None, path: str = None, ) -> bytes: """Page.pdf @@ -4849,13 +4854,13 @@ def pdf( Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty string, which means print all pages. format : Optional[str] Paper format. If set, takes priority over `width` or `height` options. Defaults to 'Letter'. - width : Optional[str, float] + width : Union[str, float, NoneType] Paper width, accepts values labeled with units. - height : Optional[str, float] + height : Union[str, float, NoneType] Paper height, accepts values labeled with units. preferCSSPageSize : Optional[bool] Give any CSS `@page` size declared in the page priority over what is declared in `width` and `height` or `format` options. Defaults to `false`, which will scale the content to fit the paper size. - margin : Optional[typing.Dict] + margin : Optional[{"top": Union[str, int, NoneType], "right": Union[str, int, NoneType], "bottom": Union[str, int, NoneType], "left": Union[str, int, NoneType]}] Paper margins, defaults to none. path : Optional[str] The file path to save the PDF to. If `path` is a relative path, then it is resolved relative to current working directory. If no path is provided, the PDF won't be saved to the disk. @@ -4879,7 +4884,7 @@ def pdf( width=width, height=height, preferCSSPageSize=preferCSSPageSize, - margin=mapping.to_impl(margin), + margin=margin, path=path, ) ) @@ -5008,7 +5013,7 @@ def pages(self) -> typing.List["Page"]: Returns ------- - typing.List[Page] + List[Page] All open pages in the context. Non visible pages, such as `"background_page"`, will not be listed here. You can find them using chromiumBrowserContext.backgroundPages(). """ return mapping.from_impl_list(self._impl_obj.pages) @@ -5073,11 +5078,11 @@ def cookies( Parameters ---------- - urls : Optional[str, typing.List[str]] + urls : Union[str, List[str], NoneType] Returns ------- - typing.List[typing.Dict] + List[Dict] """ return mapping.from_maybe_impl(self._sync(self._impl_obj.cookies(urls=urls))) @@ -5087,7 +5092,7 @@ def addCookies(self, cookies: typing.List[typing.Dict]) -> NoneType: Parameters ---------- - cookies : typing.List[typing.Dict] + cookies : List[Dict] """ return mapping.from_maybe_impl( self._sync(self._impl_obj.addCookies(cookies=mapping.to_impl(cookies))) @@ -5109,7 +5114,7 @@ def grantPermissions( Parameters ---------- - permissions : typing.List[str] + permissions : List[str] A permission or an array of permissions to grant. Permissions can be one of the following values: - `'*'` - `'geolocation'` @@ -5144,7 +5149,7 @@ def clearPermissions(self) -> NoneType: """ return mapping.from_maybe_impl(self._sync(self._impl_obj.clearPermissions())) - def setGeolocation(self, geolocation: typing.Dict = None) -> NoneType: + def setGeolocation(self, geolocation: Geolocation = None) -> NoneType: """BrowserContext.setGeolocation Sets the context's geolocation. Passing `null` or `undefined` emulates position unavailable. @@ -5153,15 +5158,13 @@ def setGeolocation(self, geolocation: typing.Dict = None) -> NoneType: Parameters ---------- - geolocation : Optional[typing.Dict] + geolocation : Optional[{"latitude": float, "longitude": float, "accuracy": Optional[float]}] """ return mapping.from_maybe_impl( - self._sync( - self._impl_obj.setGeolocation(geolocation=mapping.to_impl(geolocation)) - ) + self._sync(self._impl_obj.setGeolocation(geolocation=geolocation)) ) - def setExtraHTTPHeaders(self, headers: typing.Dict) -> NoneType: + def setExtraHTTPHeaders(self, headers: typing.Dict[str, str]) -> NoneType: """BrowserContext.setExtraHTTPHeaders The extra HTTP headers will be sent with every request initiated by any page in the context. These headers are merged with page-specific extra HTTP headers set with page.setExtraHTTPHeaders(). If page overrides a particular header, page-specific header value will be used instead of the browser context header value. @@ -5170,7 +5173,7 @@ def setExtraHTTPHeaders(self, headers: typing.Dict) -> NoneType: Parameters ---------- - headers : typing.Dict + headers : Dict[str, str] An object containing additional HTTP headers to be sent with every request. All header values must be strings. """ return mapping.from_maybe_impl( @@ -5228,7 +5231,7 @@ def exposeBinding(self, name: str, binding: typing.Callable) -> NoneType: ---------- name : str Name of the function on the window object. - binding : typing.Callable + binding : Callable Callback function that will be called in the Playwright's context. """ return mapping.from_maybe_impl( @@ -5252,7 +5255,7 @@ def exposeFunction(self, name: str, binding: typing.Callable) -> NoneType: ---------- name : str Name of the function on the window object. - binding : typing.Callable + binding : Callable Callback function that will be called in the Playwright's context. """ return mapping.from_maybe_impl( @@ -5280,9 +5283,9 @@ def route( Parameters ---------- - url : typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool]] A glob pattern, regex pattern or predicate receiving URL to match while routing. - handler : typing.Callable[[Route, Request], typing.Any] + handler : typing.Callable[[playwright.network.Route, playwright.network.Request], typing.Any] handler function to route the request. """ return mapping.from_maybe_impl( @@ -5304,9 +5307,9 @@ def unroute( Parameters ---------- - url : typing.Union[str, typing.Pattern, typing.Callable[[str], bool]] + url : Union[str, Pattern, typing.Callable[[str], bool]] A glob pattern, regex pattern or predicate receiving URL used to register a routing with browserContext.route(url, handler). - handler : Optional[typing.Callable[[Route, Request], typing.Any]] + handler : Optional[typing.Callable[[playwright.network.Route, playwright.network.Request], typing.Any]] Handler function used to register a routing with browserContext.route(url, handler). """ return mapping.from_maybe_impl( @@ -5335,7 +5338,7 @@ def waitForEvent( Returns ------- - typing.Any + Any Promise which resolves to the event data value. """ return mapping.from_maybe_impl( @@ -5393,12 +5396,12 @@ def send(self, method: str, params: typing.Dict = None) -> typing.Dict: ---------- method : str protocol method name - params : Optional[typing.Dict] + params : Optional[Dict] Optional method parameters Returns ------- - typing.Dict + Dict """ return mapping.from_maybe_impl( self._sync( @@ -5427,7 +5430,7 @@ def backgroundPages(self) -> typing.List["Page"]: Returns ------- - typing.List[Page] + List[Page] All existing background pages in the context. """ return mapping.from_impl_list(self._impl_obj.backgroundPages()) @@ -5437,7 +5440,7 @@ def serviceWorkers(self) -> typing.List["Worker"]: Returns ------- - typing.List[Worker] + List[Worker] All existing service workers in the context. """ return mapping.from_impl_list(self._impl_obj.serviceWorkers()) @@ -5476,7 +5479,7 @@ def contexts(self) -> typing.List["BrowserContext"]: Returns ------- - typing.List[BrowserContext] + List[BrowserContext] """ return mapping.from_impl_list(self._impl_obj.contexts) @@ -5493,18 +5496,18 @@ def isConnected(self) -> bool: def newContext( self, - viewport: typing.Union[typing.Dict, Literal[0]] = None, + viewport: typing.Union[IntSize, Literal[0]] = None, ignoreHTTPSErrors: bool = None, javaScriptEnabled: bool = None, bypassCSP: bool = None, userAgent: str = None, locale: str = None, timezoneId: str = None, - geolocation: typing.Dict = None, + geolocation: Geolocation = None, permissions: typing.List[str] = None, extraHTTPHeaders: typing.Union[typing.Dict[str, str]] = None, offline: bool = None, - httpCredentials: typing.Dict = None, + httpCredentials: Credentials = None, deviceScaleFactor: int = None, isMobile: bool = None, hasTouch: bool = None, @@ -5517,7 +5520,7 @@ def newContext( Parameters ---------- - viewport : Optional[typing.Dict, typing.Literal[0]] + viewport : Union[{"width": int, "height": int}, '0', NoneType] Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. ignoreHTTPSErrors : Optional[bool] Whether to ignore HTTPS errors during navigation. Defaults to `false`. @@ -5531,14 +5534,14 @@ def newContext( Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules. timezoneId : Optional[str] Changes the timezone of the context. See ICU’s `metaZones.txt` for a list of supported timezone IDs. - geolocation : Optional[typing.Dict] - permissions : Optional[typing.List[str]] + geolocation : Optional[{"latitude": float, "longitude": float, "accuracy": Optional[float]}] + permissions : Optional[List[str]] A list of permissions to grant to all pages in this context. See browserContext.grantPermissions for more details. - extraHTTPHeaders : Optional[typing.Dict[str, str]] + extraHTTPHeaders : Optional[Dict[str, str]] An object containing additional HTTP headers to be sent with every request. All header values must be strings. offline : Optional[bool] Whether to emulate network being offline. Defaults to `false`. - httpCredentials : Optional[typing.Dict] + httpCredentials : Optional[{"username": str, "password": str}] Credentials for HTTP authentication. deviceScaleFactor : Optional[int] Specify device scale factor (can be thought of as dpr). Defaults to `1`. @@ -5546,7 +5549,7 @@ def newContext( Whether the `meta viewport` tag is taken into account and touch events are enabled. Defaults to `false`. Not supported in Firefox. hasTouch : Optional[bool] Specifies if viewport supports touch events. Defaults to false. - colorScheme : Optional[typing.Literal['light', 'dark', 'no-preference']] + colorScheme : Optional[Literal['light', 'dark', 'no-preference']] Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'. acceptDownloads : Optional[bool] Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled. @@ -5558,18 +5561,18 @@ def newContext( return mapping.from_impl( self._sync( self._impl_obj.newContext( - viewport=mapping.to_impl(viewport), + viewport=viewport, ignoreHTTPSErrors=ignoreHTTPSErrors, javaScriptEnabled=javaScriptEnabled, bypassCSP=bypassCSP, userAgent=userAgent, locale=locale, timezoneId=timezoneId, - geolocation=mapping.to_impl(geolocation), + geolocation=geolocation, permissions=permissions, extraHTTPHeaders=mapping.to_impl(extraHTTPHeaders), offline=offline, - httpCredentials=mapping.to_impl(httpCredentials), + httpCredentials=httpCredentials, deviceScaleFactor=deviceScaleFactor, isMobile=isMobile, hasTouch=hasTouch, @@ -5581,18 +5584,18 @@ def newContext( def newPage( self, - viewport: typing.Union[typing.Dict, Literal[0]] = None, + viewport: typing.Union[IntSize, Literal[0]] = None, ignoreHTTPSErrors: bool = None, javaScriptEnabled: bool = None, bypassCSP: bool = None, userAgent: str = None, locale: str = None, timezoneId: str = None, - geolocation: typing.Dict = None, + geolocation: Geolocation = None, permissions: typing.List[str] = None, extraHTTPHeaders: typing.Union[typing.Dict[str, str]] = None, offline: bool = None, - httpCredentials: typing.Dict = None, + httpCredentials: Credentials = None, deviceScaleFactor: int = None, isMobile: bool = None, hasTouch: bool = None, @@ -5606,7 +5609,7 @@ def newPage( Parameters ---------- - viewport : Optional[typing.Dict, typing.Literal[0]] + viewport : Union[{"width": int, "height": int}, '0', NoneType] Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. ignoreHTTPSErrors : Optional[bool] Whether to ignore HTTPS errors during navigation. Defaults to `false`. @@ -5620,14 +5623,14 @@ def newPage( Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules. timezoneId : Optional[str] Changes the timezone of the context. See ICU’s `metaZones.txt` for a list of supported timezone IDs. - geolocation : Optional[typing.Dict] - permissions : Optional[typing.List[str]] + geolocation : Optional[{"latitude": float, "longitude": float, "accuracy": Optional[float]}] + permissions : Optional[List[str]] A list of permissions to grant to all pages in this context. See browserContext.grantPermissions for more details. - extraHTTPHeaders : Optional[typing.Dict[str, str]] + extraHTTPHeaders : Optional[Dict[str, str]] An object containing additional HTTP headers to be sent with every request. All header values must be strings. offline : Optional[bool] Whether to emulate network being offline. Defaults to `false`. - httpCredentials : Optional[typing.Dict] + httpCredentials : Optional[{"username": str, "password": str}] Credentials for HTTP authentication. deviceScaleFactor : Optional[int] Specify device scale factor (can be thought of as dpr). Defaults to `1`. @@ -5635,7 +5638,7 @@ def newPage( Whether the `meta viewport` tag is taken into account and touch events are enabled. Defaults to `false`. Not supported in Firefox. hasTouch : Optional[bool] Specifies if viewport supports touch events. Defaults to false. - colorScheme : Optional[typing.Literal['light', 'dark', 'no-preference']] + colorScheme : Optional[Literal['light', 'dark', 'no-preference']] Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'. acceptDownloads : Optional[bool] Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled. @@ -5647,18 +5650,18 @@ def newPage( return mapping.from_impl( self._sync( self._impl_obj.newPage( - viewport=mapping.to_impl(viewport), + viewport=viewport, ignoreHTTPSErrors=ignoreHTTPSErrors, javaScriptEnabled=javaScriptEnabled, bypassCSP=bypassCSP, userAgent=userAgent, locale=locale, timezoneId=timezoneId, - geolocation=mapping.to_impl(geolocation), + geolocation=geolocation, permissions=permissions, extraHTTPHeaders=mapping.to_impl(extraHTTPHeaders), offline=offline, - httpCredentials=mapping.to_impl(httpCredentials), + httpCredentials=httpCredentials, deviceScaleFactor=deviceScaleFactor, isMobile=isMobile, hasTouch=hasTouch, @@ -5762,15 +5765,15 @@ def launch( self, executablePath: str = None, args: typing.List[str] = None, - ignoreDefaultArgs: typing.List[str] = None, + ignoreDefaultArgs: typing.Union[bool, typing.List[str]] = None, handleSIGINT: bool = None, handleSIGTERM: bool = None, handleSIGHUP: bool = None, timeout: int = None, - env: typing.Dict = None, + env: typing.Union[typing.Dict[str, typing.Union[str, int, bool]]] = None, headless: bool = None, devtools: bool = None, - proxy: typing.Dict = None, + proxy: ProxyServer = None, downloadsPath: str = None, slowMo: int = None, chromiumSandbox: bool = None, @@ -5788,9 +5791,9 @@ def launch( ---------- executablePath : Optional[str] Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to current working directory. Note that Playwright only works with the bundled Chromium, Firefox or WebKit, use at your own risk. - args : Optional[typing.List[str]] + args : Optional[List[str]] Additional arguments to pass to the browser instance. The list of Chromium flags can be found here. - ignoreDefaultArgs : Optional[typing.List[str]] + ignoreDefaultArgs : Union[bool, List[str], NoneType] If `true`, Playwright does not pass its own configurations args and only uses the ones from `args`. If an array is given, then filters out the given default arguments. Dangerous option; use with care. Defaults to `false`. handleSIGINT : Optional[bool] Close the browser process on Ctrl-C. Defaults to `true`. @@ -5800,13 +5803,13 @@ def launch( Close the browser process on SIGHUP. Defaults to `true`. timeout : Optional[int] Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. - env : Optional[typing.Dict] + env : Optional[Dict[str, Union[str, int, bool]]] Specify environment variables that will be visible to the browser. Defaults to `process.env`. headless : Optional[bool] Whether to run browser in headless mode. More details for Chromium and Firefox. Defaults to `true` unless the `devtools` option is `true`. devtools : Optional[bool] **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the `headless` option will be set `false`. - proxy : Optional[typing.Dict] + proxy : Optional[{"server": str, "bypass": Optional[str], "username": Optional[str], "password": Optional[str]}] Network proxy settings. downloadsPath : Optional[str] If specified, accepted downloads are downloaded into this folder. Otherwise, temporary folder is created and is deleted when browser is closed. @@ -5833,7 +5836,7 @@ def launch( env=mapping.to_impl(env), headless=headless, devtools=devtools, - proxy=mapping.to_impl(proxy), + proxy=proxy, downloadsPath=downloadsPath, slowMo=slowMo, chromiumSandbox=chromiumSandbox, @@ -5845,19 +5848,19 @@ def launchServer( self, executablePath: str = None, args: typing.List[str] = None, - ignoreDefaultArgs: typing.List[str] = None, + ignoreDefaultArgs: typing.Union[bool, typing.List[str]] = None, handleSIGINT: bool = None, handleSIGTERM: bool = None, handleSIGHUP: bool = None, timeout: int = None, - env: typing.Dict = None, + env: typing.Union[typing.Dict[str, typing.Union[str, int, bool]]] = None, headless: bool = None, devtools: bool = None, - proxy: typing.Dict = None, + proxy: ProxyServer = None, downloadsPath: str = None, port: int = None, chromiumSandbox: bool = None, - ) -> "Browser": + ) -> "BrowserServer": """BrowserType.launchServer Launches browser server that client can connect to. An example of launching a browser executable and connecting to it later: @@ -5866,9 +5869,9 @@ def launchServer( ---------- executablePath : Optional[str] Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to current working directory. **BEWARE**: Playwright is only guaranteed to work with the bundled Chromium, Firefox or WebKit, use at your own risk. - args : Optional[typing.List[str]] + args : Optional[List[str]] Additional arguments to pass to the browser instance. The list of Chromium flags can be found here. - ignoreDefaultArgs : Optional[typing.List[str]] + ignoreDefaultArgs : Union[bool, List[str], NoneType] If `true`, then do not use any of the default arguments. If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`. handleSIGINT : Optional[bool] Close the browser process on Ctrl-C. Defaults to `true`. @@ -5878,13 +5881,13 @@ def launchServer( Close the browser process on SIGHUP. Defaults to `true`. timeout : Optional[int] Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. - env : Optional[typing.Dict] + env : Optional[Dict[str, Union[str, int, bool]]] Specify environment variables that will be visible to the browser. Defaults to `process.env`. headless : Optional[bool] Whether to run browser in headless mode. More details for Chromium and Firefox. Defaults to `true` unless the `devtools` option is `true`. devtools : Optional[bool] **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the `headless` option will be set `false`. - proxy : Optional[typing.Dict] + proxy : Optional[{"server": str, "bypass": Optional[str], "username": Optional[str], "password": Optional[str]}] Network proxy settings. downloadsPath : Optional[str] If specified, accepted downloads are downloaded into this folder. Otherwise, temporary folder is created and is deleted when browser is closed. @@ -5895,7 +5898,7 @@ def launchServer( Returns ------- - Browser + BrowserServer Promise which resolves to the browser app instance. """ return mapping.from_impl( @@ -5911,7 +5914,7 @@ def launchServer( env=mapping.to_impl(env), headless=headless, devtools=devtools, - proxy=mapping.to_impl(proxy), + proxy=proxy, downloadsPath=downloadsPath, port=port, chromiumSandbox=chromiumSandbox, @@ -5924,29 +5927,29 @@ def launchPersistentContext( userDataDir: str, executablePath: str = None, args: typing.List[str] = None, - ignoreDefaultArgs: typing.List[str] = None, + ignoreDefaultArgs: typing.Union[bool, typing.List[str]] = None, handleSIGINT: bool = None, handleSIGTERM: bool = None, handleSIGHUP: bool = None, timeout: int = None, - env: typing.Dict = None, + env: typing.Union[typing.Dict[str, typing.Union[str, int, bool]]] = None, headless: bool = None, devtools: bool = None, - proxy: typing.Dict = None, + proxy: ProxyServer = None, downloadsPath: str = None, slowMo: int = None, - viewport: typing.Dict = None, + viewport: IntSize = None, ignoreHTTPSErrors: bool = None, javaScriptEnabled: bool = None, bypassCSP: bool = None, userAgent: str = None, locale: str = None, timezoneId: str = None, - geolocation: typing.Dict = None, + geolocation: Geolocation = None, permissions: typing.List[str] = None, extraHTTPHeaders: typing.Union[typing.Dict[str, str]] = None, offline: bool = None, - httpCredentials: typing.Dict = None, + httpCredentials: Credentials = None, deviceScaleFactor: int = None, isMobile: bool = None, hasTouch: bool = None, @@ -5964,9 +5967,9 @@ def launchPersistentContext( Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for Chromium and Firefox. executablePath : Optional[str] Path to a browser executable to run instead of the bundled one. If `executablePath` is a relative path, then it is resolved relative to current working directory. **BEWARE**: Playwright is only guaranteed to work with the bundled Chromium, Firefox or WebKit, use at your own risk. - args : Optional[typing.List[str]] + args : Optional[List[str]] Additional arguments to pass to the browser instance. The list of Chromium flags can be found here. - ignoreDefaultArgs : Optional[typing.List[str]] + ignoreDefaultArgs : Union[bool, List[str], NoneType] If `true`, then do not use any of the default arguments. If an array is given, then filter out the given default arguments. Dangerous option; use with care. Defaults to `false`. handleSIGINT : Optional[bool] Close the browser process on Ctrl-C. Defaults to `true`. @@ -5976,19 +5979,19 @@ def launchPersistentContext( Close the browser process on SIGHUP. Defaults to `true`. timeout : Optional[int] Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to disable timeout. - env : Optional[typing.Dict] + env : Optional[Dict[str, Union[str, int, bool]]] Specify environment variables that will be visible to the browser. Defaults to `process.env`. headless : Optional[bool] Whether to run browser in headless mode. More details for Chromium and Firefox. Defaults to `true` unless the `devtools` option is `true`. devtools : Optional[bool] **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is `true`, the `headless` option will be set `false`. - proxy : Optional[typing.Dict] + proxy : Optional[{"server": str, "bypass": Optional[str], "username": Optional[str], "password": Optional[str]}] Network proxy settings. downloadsPath : Optional[str] If specified, accepted downloads are downloaded into this folder. Otherwise, temporary folder is created and is deleted when browser is closed. slowMo : Optional[int] Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on. Defaults to 0. - viewport : Optional[typing.Dict] + viewport : Optional[{"width": int, "height": int}] Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. ignoreHTTPSErrors : Optional[bool] Whether to ignore HTTPS errors during navigation. Defaults to `false`. @@ -6002,14 +6005,14 @@ def launchPersistentContext( Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` request header value as well as number and date formatting rules. timezoneId : Optional[str] Changes the timezone of the context. See ICU’s `metaZones.txt` for a list of supported timezone IDs. - geolocation : Optional[typing.Dict] - permissions : Optional[typing.List[str]] + geolocation : Optional[{"latitude": float, "longitude": float, "accuracy": Optional[float]}] + permissions : Optional[List[str]] A list of permissions to grant to all pages in this context. See browserContext.grantPermissions for more details. - extraHTTPHeaders : Optional[typing.Dict[str, str]] + extraHTTPHeaders : Optional[Dict[str, str]] An object containing additional HTTP headers to be sent with every request. All header values must be strings. offline : Optional[bool] Whether to emulate network being offline. Defaults to `false`. - httpCredentials : Optional[typing.Dict] + httpCredentials : Optional[{"username": str, "password": str}] Credentials for HTTP authentication. deviceScaleFactor : Optional[int] Specify device scale factor (can be thought of as dpr). Defaults to `1`. @@ -6017,7 +6020,7 @@ def launchPersistentContext( Whether the `meta viewport` tag is taken into account and touch events are enabled. Defaults to `false`. Not supported in Firefox. hasTouch : Optional[bool] Specifies if viewport supports touch events. Defaults to false. - colorScheme : Optional[typing.Literal['light', 'dark', 'no-preference']] + colorScheme : Optional[Literal['light', 'dark', 'no-preference']] Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'. acceptDownloads : Optional[bool] Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled. @@ -6043,21 +6046,21 @@ def launchPersistentContext( env=mapping.to_impl(env), headless=headless, devtools=devtools, - proxy=mapping.to_impl(proxy), + proxy=proxy, downloadsPath=downloadsPath, slowMo=slowMo, - viewport=mapping.to_impl(viewport), + viewport=viewport, ignoreHTTPSErrors=ignoreHTTPSErrors, javaScriptEnabled=javaScriptEnabled, bypassCSP=bypassCSP, userAgent=userAgent, locale=locale, timezoneId=timezoneId, - geolocation=mapping.to_impl(geolocation), + geolocation=geolocation, permissions=permissions, extraHTTPHeaders=mapping.to_impl(extraHTTPHeaders), offline=offline, - httpCredentials=mapping.to_impl(httpCredentials), + httpCredentials=httpCredentials, deviceScaleFactor=deviceScaleFactor, isMobile=isMobile, hasTouch=hasTouch, diff --git a/scripts/documentation_provider.py b/scripts/documentation_provider.py index 390793dee..0cb259c34 100644 --- a/scripts/documentation_provider.py +++ b/scripts/documentation_provider.py @@ -15,11 +15,34 @@ import json import re from sys import stderr -from typing import Any, Dict, List +from typing import ( # type: ignore + Any, + Dict, + List, + Union, + get_args, + get_origin, + get_type_hints, +) enum_regex = r"^\"[^\"]+\"(?:\|\"[^\"]+\")+$" union_regex = r"^[^\|]+(?:\|[^\|]+)+$" +exceptions = { + "Route.fulfill(path=)": { + "doc": "Optional[str]", + "code": "Union[str, pathlib.Path, NoneType]", + }, + "Browser.newContext(viewport=)": { + "doc": 'Optional[{"width": int, "height": int}]', + "code": 'Union[{"width": int, "height": int}, \'0\', NoneType]', + }, + "Browser.newPage(viewport=)": { + "doc": 'Optional[{"width": int, "height": int}]', + "code": 'Union[{"width": int, "height": int}, \'0\', NoneType]', + }, +} + class DocumentationProvider: def __init__(self) -> None: @@ -91,11 +114,9 @@ def print_entry( file=stderr, ) else: - code_type = self.normalize_class_type(value) + code_type = self.serialize_python_type(value) - print( - f"{indent}{original_name} : {self.beautify_code_type(code_type)}" - ) + print(f"{indent}{original_name} : {code_type}") if doc_value["comment"]: print( f"{indent} {self.indent_paragraph(doc_value['comment'], f'{indent} ')}" @@ -112,13 +133,12 @@ def print_entry( and str(signature["return"]) != "" ): value = signature["return"] - code_type = self.normalize_class_type(value) doc_value = method - self.compare_types(code_type, doc_value, f"{fqname}(return=)") + self.compare_types(value, doc_value, f"{fqname}(return=)") print("") print(" Returns") print(" -------") - print(f" {self.normalize_class_type(signature['return'])}") + print(f" {self.serialize_python_type(value)}") if method["returnComment"]: print( f" {self.indent_paragraph(method['returnComment'], ' ')}" @@ -132,12 +152,6 @@ def print_entry( file=stderr, ) - def normalize_class_type(self, value: Any) -> str: - code_type = str(value) - code_type = re.sub(r"", r"\1", code_type) - code_type = re.sub(r"playwright\.[\w]+\.([\w]+)", r"\1", code_type) - return code_type - def indent_paragraph(self, p: str, indent: str) -> str: lines = p.split("\n") result = [lines[0]] @@ -165,84 +179,177 @@ def beautify_method_comment(self, comment: str, indent: str) -> str: in_example = False return self.indent_paragraph("\n".join(result), indent) - def beautify_code_type(self, code_type: str) -> str: - return re.sub(r"^typing.Union\[(.*), NoneType\]$", r"Optional[\1]", code_type) + def make_optional(self, text: str) -> str: + if text.startswith("Union["): + return text[:-1] + ", NoneType]" + if text.startswith("Optional["): + return text + return f"Optional[{text}]" - def compare_types(self, code_type: str, doc_value: Any, fqname: str) -> None: - type_name = doc_value["type"]["name"] - doc_type = self.serialize_doc_type(type_name, fqname) + def compare_types(self, value: Any, doc_value: Any, fqname: str) -> None: + if "(arg=)" in fqname or "(pageFunction=)" in fqname: + return + code_type = self.serialize_python_type(value) + doc_type = self.serialize_doc_type( + doc_value["type"]["name"], fqname, doc_value["type"], + ) if not doc_value["required"]: - doc_type = f"typing.Union[{doc_type}, NoneType]" + doc_type = self.make_optional(doc_type) + if ( + fqname in exceptions + and exceptions[fqname]["doc"] == doc_type + and exceptions[fqname]["code"] == code_type + ): + return + if doc_type != code_type: print( f"Parameter type mismatch in {fqname}: documented as {doc_type}, code has {code_type}", file=stderr, ) - def serialize_doc_type(self, doc_value: Any, fqname: str) -> str: - if doc_value == "string": + def serialize_python_type(self, value: Any) -> str: + str_value = 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: + return match.group(1) + + match = re.match(r"^typing\.(\w+)$", str_value) + if match: + return match.group(1) + + origin = get_origin(value) + args = get_args(value) + hints = None + try: + hints = get_type_hints(value) + except Exception: + pass + if hints: + signature: List[str] = [] + for [name, value] in hints.items(): + signature.append(f'"{name}": {self.serialize_python_type(value)}') + return f"{{{', '.join(signature)}}}" + if origin == Union: + args = get_args(value) + if len(args) == 2 and "None" in str(args[1]): + return self.make_optional(self.serialize_python_type(args[0])) + return f"Union[{', '.join(list(map(lambda a: self.serialize_python_type(a), args)))}]" + if str(origin) == "": + args = get_args(value) + return f"Dict[{', '.join(list(map(lambda a: self.serialize_python_type(a), args)))}]" + if str(origin) == "": + args = get_args(value) + return f"List[{', '.join(list(map(lambda a: self.serialize_python_type(a), args)))}]" + if str(origin) == "typing.Literal": + args = get_args(value) + if len(args) == 1: + return "'" + self.serialize_python_type(args[0]) + "'" + body = ", ".join( + list(map(lambda a: "'" + self.serialize_python_type(a) + "'", args)) + ) + return f"Literal[{body}]" + return str_value + + def serialize_doc_type( + self, type_name: Any, fqname: str, doc_type: Any = None + ) -> str: + type_name = re.sub(r"^Promise<(.*)>$", r"\1", type_name) + + if type_name == "string": return "str" - if doc_value == "Buffer": + if type_name == "Buffer": return "bytes" - if doc_value == "boolean": + if type_name == "Array": + return "List" + + if type_name == "boolean": return "bool" - if doc_value == "number": + if type_name == "number": if "Mouse" in fqname and ("(x=)" in fqname or "(y=)" in fqname): return "float" - elif fqname == "Page.pdf(width=)" or fqname == "Page.pdf(height=)": + if ( + "(position=)" in fqname + or "(geolocation=)" in fqname + or ".boundingBox(" in fqname + ): return "float" - else: - return "int" - - if doc_value == "Object": - return "typing.Dict" - - if doc_value == "?Object": - return "typing.Union[typing.Dict, NoneType]" + if "screenshot(clip=)" in fqname: + return "float" + if fqname == "Page.pdf(width=)" or fqname == "Page.pdf(height=)": + return "float" + return "int" + + if type_name == "Serializable": + return "Any" + + if type_name == "Object" or type_name == "?Object": + intermediate = "Dict" + if doc_type and len(doc_type["properties"]): + signature: List[str] = [] + for [name, value] in doc_type["properties"].items(): + value_type = self.serialize_doc_type( + value["type"]["name"], fqname, value["type"] + ) + signature.append( + f"\"{name}\": {value_type if value['required'] else self.make_optional(value_type)}" + ) + intermediate = f"{{{', '.join(signature)}}}" + return ( + intermediate + if type_name == "Object" + else self.make_optional(intermediate) + ) - if doc_value == "function": - return "typing.Callable" + if type_name == "function": + return "Callable" - match = re.match(r"^Object<([^,]+),\s*([^)]+)>$", doc_value) + match = re.match(r"^Object<([^,]+),\s*([^)]+)>$", type_name) if match: - return f"typing.Dict[{self.serialize_doc_type(match.group(1), fqname)}, {self.serialize_doc_type(match.group(2), fqname)}]" + return f"Dict[{self.serialize_doc_type(match.group(1), fqname)}, {self.serialize_doc_type(match.group(2), fqname)}]" - match = re.match(r"^Promise<(.*)>$", doc_value) + match = re.match(r"^Map<([^,]+),\s*([^)]+)>$", type_name) if match: - return self.serialize_doc_type(match.group(1), fqname) + return f"Dict[{self.serialize_doc_type(match.group(1), fqname)}, {self.serialize_doc_type(match.group(2), fqname)}]" - if re.match(enum_regex, doc_value): - result = f"typing.Literal[{', '.join(doc_value.split('|'))}]" + if re.match(enum_regex, type_name): + result = f"Literal[{', '.join(type_name.split('|'))}]" return result.replace('"', "'") - match = re.match(r"^Array<(.*)>$", doc_value) + match = re.match(r"^Array<(.*)>$", type_name) if match: - return f"typing.List[{self.serialize_doc_type(match.group(1), fqname)}]" + return f"List[{self.serialize_doc_type(match.group(1), fqname)}]" - match = re.match(r"^\?(.*)$", doc_value) + match = re.match(r"^\?(.*)$", type_name) if match: - return f"typing.Union[{self.serialize_doc_type(match.group(1), fqname)}, NoneType]" + return self.make_optional(self.serialize_doc_type(match.group(1), fqname)) - match = re.match(r"^null\|(.*)$", doc_value) + match = re.match(r"^null\|(.*)$", type_name) if match: - return f"typing.Union[{self.serialize_doc_type(match.group(1), fqname)}, NoneType]" + return self.make_optional(self.serialize_doc_type(match.group(1), fqname)) # Union detection is greedy - if re.match(union_regex, doc_value): + if re.match(union_regex, type_name): result = ", ".join( list( map( lambda a: self.serialize_doc_type(a, fqname), - doc_value.split("|"), + type_name.split("|"), ) ) ) - return result.replace('"', "'") + body = result.replace('"', "'") + return f"Union[{body}]" - return doc_value + return type_name def rewrite_param_name(self, fqname: str, method_name: str, name: str) -> str: if name == "expression": diff --git a/scripts/generate_api.py b/scripts/generate_api.py index bd242dc9b..30e1f25a0 100644 --- a/scripts/generate_api.py +++ b/scripts/generate_api.py @@ -39,7 +39,7 @@ from playwright.file_chooser import FileChooser from playwright.frame import Frame from playwright.input import Keyboard, Mouse -from playwright.js_handle import JSHandle +from playwright.js_handle import JSHandle, Serializable from playwright.network import Request, Response, Route from playwright.page import BindingCall, Page, Worker from playwright.playwright import Playwright @@ -164,7 +164,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, Error, FilePayload, SelectOption, Viewport, DeviceDescriptor +from playwright.helper import ConsoleMessageLocation, Credentials, MousePosition, Error, FilePayload, SelectOption, RequestFailure, Viewport, DeviceDescriptor, IntSize, FloatRect, Geolocation, ProxyServer, PdfMargins from playwright.input import Keyboard as KeyboardImpl, Mouse as MouseImpl from playwright.js_handle import JSHandle as JSHandleImpl from playwright.network import Request as RequestImpl, Response as ResponseImpl, Route as RouteImpl @@ -202,3 +202,4 @@ def return_value(value: Any) -> List[str]: api_globals = globals() assert ValuesToSelect +assert Serializable diff --git a/tests/async/test_interception.py b/tests/async/test_interception.py index b188be488..817a400ad 100644 --- a/tests/async/test_interception.py +++ b/tests/async/test_interception.py @@ -257,11 +257,11 @@ async def test_page_route_should_be_abortable_with_custom_error_codes( assert len(failed_requests) == 1 failed_request = failed_requests[0] if is_webkit: - assert failed_request.failure == "Request intercepted" + assert failed_request.failure["errorText"] == "Request intercepted" elif is_firefox: - assert failed_request.failure == "NS_ERROR_OFFLINE" + assert failed_request.failure["errorText"] == "NS_ERROR_OFFLINE" else: - assert failed_request.failure == "net::ERR_INTERNET_DISCONNECTED" + assert failed_request.failure["errorText"] == "net::ERR_INTERNET_DISCONNECTED" async def test_page_route_should_send_referer(page, server): diff --git a/tests/async/test_network.py b/tests/async/test_network.py index 90ae4415b..7d64f3614 100644 --- a/tests/async/test_network.py +++ b/tests/async/test_network.py @@ -336,19 +336,22 @@ def handle_request(request): assert await failed_requests[0].response() is None assert failed_requests[0].resourceType == "stylesheet" if is_chromium: - assert failed_requests[0].failure == "net::ERR_EMPTY_RESPONSE" + assert failed_requests[0].failure["errorText"] == "net::ERR_EMPTY_RESPONSE" elif is_webkit: if is_mac: - assert failed_requests[0].failure == "The network connection was lost." + assert ( + failed_requests[0].failure["errorText"] + == "The network connection was lost." + ) elif is_win: assert ( - failed_requests[0].failure + failed_requests[0].failure["errorText"] == "Server returned nothing (no headers, no data)" ) else: - assert failed_requests[0].failure == "Message Corrupt" + assert failed_requests[0].failure["errorText"] == "Message Corrupt" else: - assert failed_requests[0].failure == "NS_ERROR_NET_RESET" + assert failed_requests[0].failure["errorText"] == "NS_ERROR_NET_RESET" assert failed_requests[0].frame From 49b691ab9117aaae63373ec42943d8026cf2b881 Mon Sep 17 00:00:00 2001 From: Ross Wollman Date: Tue, 4 Aug 2020 22:34:46 -0700 Subject: [PATCH 2/4] test: add cookie test files (#142) --- .../async/test_browsercontext_add_cookies.py | 387 ++++++++++++++++++ .../async/test_browsercontext_clearcookies.py | 47 +++ tests/async/test_browsercontext_cookies.py | 192 +++++++++ 3 files changed, 626 insertions(+) create mode 100644 tests/async/test_browsercontext_add_cookies.py create mode 100644 tests/async/test_browsercontext_clearcookies.py create mode 100644 tests/async/test_browsercontext_cookies.py diff --git a/tests/async/test_browsercontext_add_cookies.py b/tests/async/test_browsercontext_add_cookies.py new file mode 100644 index 000000000..19e026cfc --- /dev/null +++ b/tests/async/test_browsercontext_add_cookies.py @@ -0,0 +1,387 @@ +# 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 asyncio +import datetime + +import pytest + +from playwright import Error + + +async def test_should_work(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "password", "value": "123456"}] + ) + assert await page.evaluate("() => document.cookie") == "password=123456" + + +async def test_should_roundtrip_cookie(context, page, server): + await page.goto(server.EMPTY_PAGE) + # @see https://en.wikipedia.org/wiki/Year_2038_problem + date = int(datetime.datetime(2038, 1, 1).timestamp() * 1000) + document_cookie = await page.evaluate( + """timestamp => { + const date = new Date(timestamp); + document.cookie = `username=John Doe;expires=${date.toUTCString()}`; + return document.cookie; + }""", + date, + ) + assert document_cookie == "username=John Doe" + cookies = await context.cookies() + await context.clearCookies() + assert await context.cookies() == [] + await context.addCookies(cookies) + assert await context.cookies() == cookies + + +async def test_should_send_cookie_header(server, context): + cookie = [] + + def handler(request): + cookie.extend(request.requestHeaders.getRawHeaders("cookie")) + request.finish() + + server.set_route("/empty.html", handler) + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "cookie", "value": "value"}] + ) + page = await context.newPage() + await page.goto(server.EMPTY_PAGE) + assert cookie == ["cookie=value"] + + +async def test_should_isolate_cookies_in_browser_contexts(context, server, browser): + another_context = await browser.newContext() + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "isolatecookie", "value": "page1value"}] + ) + await another_context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "isolatecookie", "value": "page2value"}] + ) + + cookies_1 = await context.cookies() + cookies_2 = await another_context.cookies() + assert len(cookies_1) == 1 + assert len(cookies_2) == 1 + assert cookies_1[0]["name"] == "isolatecookie" + assert cookies_1[0]["value"] == "page1value" + assert cookies_2[0]["name"] == "isolatecookie" + assert cookies_2[0]["value"] == "page2value" + await another_context.close() + + +async def test_should_isolate_session_cookies(context, server, browser): + server.set_route( + "/setcookie.html", + lambda r: (r.setHeader("Set-Cookie", "session=value"), r.finish(),), + ) + + page_1 = await context.newPage() + await page_1.goto(server.PREFIX + "/setcookie.html") + ## + page_2 = await context.newPage() + await page_2.goto(server.EMPTY_PAGE) + cookies_2 = await context.cookies() + assert len(cookies_2) == 1 + assert ",".join(list(map(lambda c: c["value"], cookies_2))) == "value" + ## + context_b = await browser.newContext() + page_3 = await context_b.newPage() + await page_3.goto(server.EMPTY_PAGE) + cookies_3 = await context_b.cookies() + assert cookies_3 == [] + await context_b.close() + + +async def test_should_isolate_persistent_cookies(context, server, browser): + server.set_route( + "/setcookie.html", + lambda r: ( + r.setHeader("Set-Cookie", "persistent=persistent-value; max-age=3600"), + r.finish(), + ), + ) + + page = await context.newPage() + await page.goto(server.PREFIX + "/setcookie.html") + + context_1 = context + context_2 = await browser.newContext() + [page_1, page_2] = await asyncio.gather(context_1.newPage(), context_2.newPage()) + await asyncio.gather(page_1.goto(server.EMPTY_PAGE), page_2.goto(server.EMPTY_PAGE)) + [cookies_1, cookies_2] = await asyncio.gather( + context_1.cookies(), context_2.cookies() + ) + assert len(cookies_1) == 1 + assert cookies_1[0]["name"] == "persistent" + assert cookies_1[0]["value"] == "persistent-value" + assert len(cookies_2) == 0 + await context_2.close() + + +async def test_should_isolate_send_cookie_header(server, context, browser): + cookie = [] + + def handler(request): + cookie.extend(request.requestHeaders.getRawHeaders("cookie") or []) + request.finish() + + server.set_route("/empty.html", handler) + + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "sendcookie", "value": "value"}] + ) + + page_1 = await context.newPage() + await page_1.goto(server.EMPTY_PAGE) + assert cookie == ["sendcookie=value"] + cookie.clear() + ## + context_2 = await browser.newContext() + page_2 = await context_2.newPage() + await page_2.goto(server.EMPTY_PAGE) + assert cookie == [] + await context_2.close() + + +async def test_should_isolate_cookies_between_launches(browser_factory, server): + browser_1 = await browser_factory() + context_1 = await browser_1.newContext() + await context_1.addCookies( + [ + { + "url": server.EMPTY_PAGE, + "name": "cookie-in-context-1", + "value": "value", + "expires": int(datetime.datetime.now().timestamp() + 10000), + } + ] + ) + await browser_1.close() + + browser_2 = await browser_factory() + context_2 = await browser_2.newContext() + cookies = await context_2.cookies() + assert cookies == [] + await browser_2.close() + + +async def test_should_set_multiple_cookies(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [ + {"url": server.EMPTY_PAGE, "name": "multiple-1", "value": "123456"}, + {"url": server.EMPTY_PAGE, "name": "multiple-2", "value": "bar"}, + ] + ) + assert ( + await page.evaluate( + """() => { + const cookies = document.cookie.split(';'); + return cookies.map(cookie => cookie.trim()).sort(); + }""" + ) + == ["multiple-1=123456", "multiple-2=bar"] + ) + + +async def test_should_have_expires_set_to_neg_1_for_session_cookies(context, server): + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "expires", "value": "123456"}] + ) + cookies = await context.cookies() + assert cookies[0]["expires"] == -1 + + +async def test_should_set_cookie_with_reasonable_defaults(context, server): + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "defaults", "value": "123456"}] + ) + cookies = await context.cookies() + cookies.sort(key=lambda r: r["name"]) + assert cookies == [ + { + "name": "defaults", + "value": "123456", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + } + ] + + +async def test_should_set_a_cookie_with_a_path(context, page, server): + await page.goto(server.PREFIX + "/grid.html") + await context.addCookies( + [ + { + "domain": "localhost", + "path": "/grid.html", + "name": "gridcookie", + "value": "GRID", + } + ] + ) + assert await context.cookies() == [ + { + "name": "gridcookie", + "value": "GRID", + "domain": "localhost", + "path": "/grid.html", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + } + ] + assert await page.evaluate("document.cookie") == "gridcookie=GRID" + await page.goto(server.EMPTY_PAGE) + assert await page.evaluate("document.cookie") == "" + await page.goto(server.PREFIX + "/grid.html") + assert await page.evaluate("document.cookie") == "gridcookie=GRID" + + +async def test_should_not_set_a_cookie_with_blank_page_url(context, server): + with pytest.raises(Error) as exc_info: + await context.addCookies( + [ + {"url": server.EMPTY_PAGE, "name": "example-cookie", "value": "best"}, + {"url": "about:blank", "name": "example-cookie-blank", "value": "best"}, + ] + ) + assert ( + 'Blank page can not have cookie "example-cookie-blank"' + in exc_info.value.message + ) + + +async def test_should_not_set_a_cookie_on_a_data_url_page(context): + with pytest.raises(Error) as exc_info: + await context.addCookies( + [ + { + "url": "data:,Hello%2C%20World!", + "name": "example-cookie", + "value": "best", + } + ] + ) + assert ( + 'Data URL page can not have cookie "example-cookie"' in exc_info.value.message + ) + + +async def test_should_default_to_setting_secure_cookie_for_https_websites( + context, page, server +): + await page.goto(server.EMPTY_PAGE) + SECURE_URL = "https://example.com" + await context.addCookies([{"url": SECURE_URL, "name": "foo", "value": "bar"}]) + [cookie] = await context.cookies(SECURE_URL) + assert cookie["secure"] + + +async def test_should_be_able_to_set_unsecure_cookie_for_http_website( + context, page, server +): + await page.goto(server.EMPTY_PAGE) + HTTP_URL = "http://example.com" + await context.addCookies([{"url": HTTP_URL, "name": "foo", "value": "bar"}]) + [cookie] = await context.cookies(HTTP_URL) + assert not cookie["secure"] + + +async def test_should_set_a_cookie_on_a_different_domain(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [{"url": "https://www.example.com", "name": "example-cookie", "value": "best"}] + ) + assert await page.evaluate("document.cookie") == "" + assert await context.cookies("https://www.example.com") == [ + { + "name": "example-cookie", + "value": "best", + "domain": "www.example.com", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": True, + "sameSite": "None", + } + ] + + +async def test_should_set_cookies_for_a_frame(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [{"url": server.PREFIX, "name": "frame-cookie", "value": "value"}] + ) + await page.evaluate( + """src => { + let fulfill; + const promise = new Promise(x => fulfill = x); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.onload = fulfill; + iframe.src = src; + return promise; + }""", + server.PREFIX + "/grid.html", + ) + + assert await page.frames[1].evaluate("document.cookie") == "frame-cookie=value" + + +async def test_should_not_block_third_party_cookies( + context, page, server, is_chromium, is_firefox +): + await page.goto(server.EMPTY_PAGE) + await page.evaluate( + """src => { + let fulfill; + const promise = new Promise(x => fulfill = x); + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + iframe.onload = fulfill; + iframe.src = src; + return promise; + }""", + server.CROSS_PROCESS_PREFIX + "/grid.html", + ) + await page.frames[1].evaluate("document.cookie = 'username=John Doe'") + await page.waitForTimeout(2000) + allows_third_party = is_chromium or is_firefox + cookies = await context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html") + + if allows_third_party: + assert cookies == [ + { + "domain": "127.0.0.1", + "expires": -1, + "httpOnly": False, + "name": "username", + "path": "/", + "sameSite": "None", + "secure": False, + "value": "John Doe", + } + ] + else: + assert cookies == [] diff --git a/tests/async/test_browsercontext_clearcookies.py b/tests/async/test_browsercontext_clearcookies.py new file mode 100644 index 000000000..014ebd660 --- /dev/null +++ b/tests/async/test_browsercontext_clearcookies.py @@ -0,0 +1,47 @@ +# 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. + + +async def test_should_clear_cookies(context, page, server): + await page.goto(server.EMPTY_PAGE) + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "cookie1", "value": "1"}] + ) + assert await page.evaluate("document.cookie") == "cookie1=1" + await context.clearCookies() + assert await context.cookies() == [] + await page.reload() + assert await page.evaluate("document.cookie") == "" + + +async def test_should_isolate_cookies_when_clearing(context, server, browser): + another_context = await browser.newContext() + await context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "page1cookie", "value": "page1value"}] + ) + await another_context.addCookies( + [{"url": server.EMPTY_PAGE, "name": "page2cookie", "value": "page2value"}] + ) + + assert len(await context.cookies()) == 1 + assert len(await another_context.cookies()) == 1 + + await context.clearCookies() + assert len(await context.cookies()) == 0 + assert len(await another_context.cookies()) == 1 + + await another_context.clearCookies() + assert len(await context.cookies()) == 0 + assert len(await another_context.cookies()) == 0 + await another_context.close() diff --git a/tests/async/test_browsercontext_cookies.py b/tests/async/test_browsercontext_cookies.py new file mode 100644 index 000000000..4c27e87fc --- /dev/null +++ b/tests/async/test_browsercontext_cookies.py @@ -0,0 +1,192 @@ +# 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 datetime + +import pytest + + +async def test_should_return_no_cookies_in_pristine_browser_context(context): + assert await context.cookies() == [] + + +async def test_should_get_a_cookie(context, page, server): + await page.goto(server.EMPTY_PAGE) + document_cookie = await page.evaluate( + """() => { + document.cookie = 'username=John Doe'; + return document.cookie; + }""" + ) + assert document_cookie == "username=John Doe" + assert await context.cookies() == [ + { + "name": "username", + "value": "John Doe", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + } + ] + + +async def test_should_get_a_non_session_cookie(context, page, server): + await page.goto(server.EMPTY_PAGE) + # @see https://en.wikipedia.org/wiki/Year_2038_problem + date = int(datetime.datetime(2038, 1, 1).timestamp() * 1000) + document_cookie = await page.evaluate( + """timestamp => { + const date = new Date(timestamp); + document.cookie = `username=John Doe;expires=${date.toUTCString()}`; + return document.cookie; + }""", + date, + ) + assert document_cookie == "username=John Doe" + assert await context.cookies() == [ + { + "name": "username", + "value": "John Doe", + "domain": "localhost", + "path": "/", + "expires": date / 1000, + "httpOnly": False, + "secure": False, + "sameSite": "None", + } + ] + + +async def test_should_properly_report_httponly_cookie(context, page, server): + server.set_route( + "/empty.html", + lambda r: ( + r.setHeader("Set-Cookie", "name=value;HttpOnly; Path=/"), + r.finish(), + ), + ) + + await page.goto(server.EMPTY_PAGE) + cookies = await context.cookies() + assert len(cookies) == 1 + assert cookies[0]["httpOnly"] is True + + +async def test_should_properly_report_strict_samesite_cookie( + context, page, server, is_webkit, is_win +): + if is_webkit and is_win: + pytest.skip() + + server.set_route( + "/empty.html", + lambda r: ( + r.setHeader("Set-Cookie", "name=value;SameSite=Strict"), + r.finish(), + ), + ) + await page.goto(server.EMPTY_PAGE) + cookies = await context.cookies() + assert len(cookies) == 1 + assert cookies[0]["sameSite"] == "Strict" + + +async def test_should_properly_report_lax_samesite_cookie( + context, page, server, is_webkit, is_win +): + if is_webkit and is_win: + pytest.skip() + + server.set_route( + "/empty.html", + lambda r: (r.setHeader("Set-Cookie", "name=value;SameSite=Lax"), r.finish(),), + ) + await page.goto(server.EMPTY_PAGE) + cookies = await context.cookies() + assert len(cookies) == 1 + assert cookies[0]["sameSite"] == "Lax" + + +async def test_should_get_multiple_cookies(context, page, server): + await page.goto(server.EMPTY_PAGE) + document_cookie = await page.evaluate( + """() => { + document.cookie = 'username=John Doe'; + document.cookie = 'password=1234'; + return document.cookie.split('; ').sort().join('; '); + }""" + ) + cookies = await context.cookies() + cookies.sort(key=lambda r: r["name"]) + assert document_cookie == "password=1234; username=John Doe" + assert cookies == [ + { + "name": "password", + "value": "1234", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + }, + { + "name": "username", + "value": "John Doe", + "domain": "localhost", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": False, + "sameSite": "None", + }, + ] + + +async def test_should_get_cookies_from_multiple_urls(context): + await context.addCookies( + [ + {"url": "https://foo.com", "name": "doggo", "value": "woofs"}, + {"url": "https://bar.com", "name": "catto", "value": "purrs"}, + {"url": "https://baz.com", "name": "birdo", "value": "tweets"}, + ] + ) + cookies = await context.cookies(["https://foo.com", "https://baz.com"]) + cookies.sort(key=lambda r: r["name"]) + + assert cookies == [ + { + "name": "birdo", + "value": "tweets", + "domain": "baz.com", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": True, + "sameSite": "None", + }, + { + "name": "doggo", + "value": "woofs", + "domain": "foo.com", + "path": "/", + "expires": -1, + "httpOnly": False, + "secure": True, + "sameSite": "None", + }, + ] From 8697c92de2e695af84d39a47e705f46b61cfc8fa Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 5 Aug 2020 17:24:03 +0200 Subject: [PATCH 3/4] fix: forward driver stderr to main stderr (#145) --- playwright/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/main.py b/playwright/main.py index 6a8451c0a..4726ef4bf 100644 --- a/playwright/main.py +++ b/playwright/main.py @@ -49,7 +49,7 @@ async def run_driver_async() -> Connection: str(driver_executable), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, + stderr=sys.stderr, limit=32768, ) assert proc.stdout From d913f1d2da760c21bf483f26c1ce8c8ea50f2a73 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 5 Aug 2020 18:54:59 +0200 Subject: [PATCH 4/4] tests: added keyboard tests (#146) --- playwright/element_handle.py | 2 +- tests/async/conftest.py | 6 + tests/async/test_headful.py | 1 + tests/async/test_keyboard.py | 489 +++++++++++++++++++++++++++++++++++ tests/conftest.py | 5 - tests/sync/test_sync.py | 10 + 6 files changed, 507 insertions(+), 6 deletions(-) diff --git a/playwright/element_handle.py b/playwright/element_handle.py index c24ae80c9..3986752f6 100644 --- a/playwright/element_handle.py +++ b/playwright/element_handle.py @@ -151,7 +151,7 @@ async def type( timeout: int = None, noWaitAfter: bool = None, ) -> None: - await self._channel.send("text", locals_to_params(locals())) + await self._channel.send("type", locals_to_params(locals())) async def press( self, key: str, delay: int = None, timeout: int = None, noWaitAfter: bool = None diff --git a/tests/async/conftest.py b/tests/async/conftest.py index d563ea315..cbba5d967 100644 --- a/tests/async/conftest.py +++ b/tests/async/conftest.py @@ -17,6 +17,12 @@ from playwright import async_playwright +# Will mark all the tests as async +def pytest_collection_modifyitems(items): + for item in items: + item.add_marker(pytest.mark.asyncio) + + @pytest.fixture(scope="session") async def playwright(): async with async_playwright() as playwright_object: diff --git a/tests/async/test_headful.py b/tests/async/test_headful.py index 43aa2b98d..3bdb50c85 100644 --- a/tests/async/test_headful.py +++ b/tests/async/test_headful.py @@ -34,6 +34,7 @@ async def test_headless_should_be_able_to_read_cookies_written_by_headful( ): if is_chromium and is_win: pytest.skip("see https://github.com/microsoft/playwright/issues/717") + return # Write a cookie in headful chrome headful_context = await browser_type.launchPersistentContext( tmpdir, **{**launch_arguments, "headless": False} diff --git a/tests/async/test_keyboard.py b/tests/async/test_keyboard.py index e6e2d9eea..233c91827 100644 --- a/tests/async/test_keyboard.py +++ b/tests/async/test_keyboard.py @@ -11,6 +11,36 @@ # 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 pytest + +from playwright.async_api import Page +from playwright.helper import Error + + +async def captureLastKeydown(page): + lastEvent = await page.evaluateHandle( + """() => { + const lastEvent = { + repeat: false, + location: -1, + code: '', + key: '', + metaKey: false, + keyIdentifier: 'unsupported' + }; + document.addEventListener('keydown', e => { + lastEvent.repeat = e.repeat; + lastEvent.location = e.location; + lastEvent.key = e.key; + lastEvent.code = e.code; + lastEvent.metaKey = e.metaKey; + // keyIdentifier only exists in WebKit, and isn't in TypeScript's lib. + lastEvent.keyIdentifier = 'keyIdentifier' in e && e.keyIdentifier; + }, true); + return lastEvent; + }""" + ) + return lastEvent async def test_keyboard_type_into_a_textarea(page): @@ -61,3 +91,462 @@ async def test_keyboard_send_a_character_with_elementhandle_press(page, server): ) await textarea.press("b") assert await page.evaluate("document.querySelector('textarea').value") == "a" + + +async def test_should_send_a_character_with_send_character(page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + await page.keyboard.insertText("嗨") + assert await page.evaluate('() => document.querySelector("textarea").value') == "嗨" + await page.evaluate( + '() => window.addEventListener("keydown", e => e.preventDefault(), true)' + ) + await page.keyboard.insertText("a") + assert await page.evaluate('() => document.querySelector("textarea").value') == "嗨a" + + +async def test_should_only_emit_input_event(page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + page.on("console", "m => console.log(m.text())") + events = await page.evaluateHandle( + """() => { + const events = []; + document.addEventListener('keydown', e => events.push(e.type)); + document.addEventListener('keyup', e => events.push(e.type)); + document.addEventListener('keypress', e => events.push(e.type)); + document.addEventListener('input', e => events.push(e.type)); + return events; + }""" + ) + + await page.keyboard.insertText("hello world") + assert await events.jsonValue() == ["input"] + + +async def test_should_report_shiftkey(page: Page, server, is_mac, is_firefox): + if is_firefox and is_mac: + pytest.skip() + await page.goto(server.PREFIX + "/input/keyboard.html") + keyboard = page.keyboard + codeForKey = {"Shift": 16, "Alt": 18, "Control": 17} + for modifierKey in codeForKey.keys(): + await keyboard.down(modifierKey) + assert ( + await page.evaluate("() => getResult()") + == "Keydown: " + + modifierKey + + " " + + modifierKey + + "Left " + + str(codeForKey[modifierKey]) + + " [" + + modifierKey + + "]" + ) + await keyboard.down("!") + # Shift+! will generate a keypress + if modifierKey == "Shift": + assert ( + await page.evaluate("() => getResult()") + == "Keydown: ! Digit1 49 [" + + modifierKey + + "]\nKeypress: ! Digit1 33 33 [" + + modifierKey + + "]" + ) + else: + assert ( + await page.evaluate("() => getResult()") + == "Keydown: ! Digit1 49 [" + modifierKey + "]" + ) + + await keyboard.up("!") + assert ( + await page.evaluate("() => getResult()") + == "Keyup: ! Digit1 49 [" + modifierKey + "]" + ) + await keyboard.up(modifierKey) + assert ( + await page.evaluate("() => getResult()") + == "Keyup: " + + modifierKey + + " " + + modifierKey + + "Left " + + str(codeForKey[modifierKey]) + + " []" + ) + + +async def test_should_report_multiple_modifiers(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + keyboard = page.keyboard + await keyboard.down("Control") + assert ( + await page.evaluate("() => getResult()") + == "Keydown: Control ControlLeft 17 [Control]" + ) + await keyboard.down("Alt") + assert ( + await page.evaluate("() => getResult()") + == "Keydown: Alt AltLeft 18 [Alt Control]" + ) + await keyboard.down(";") + assert ( + await page.evaluate("() => getResult()") + == "Keydown: ; Semicolon 186 [Alt Control]" + ) + await keyboard.up(";") + assert ( + await page.evaluate("() => getResult()") + == "Keyup: ; Semicolon 186 [Alt Control]" + ) + await keyboard.up("Control") + assert ( + await page.evaluate("() => getResult()") + == "Keyup: Control ControlLeft 17 [Alt]" + ) + await keyboard.up("Alt") + assert await page.evaluate("() => getResult()") == "Keyup: Alt AltLeft 18 []" + + +async def test_should_send_proper_codes_while_typing(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.type("!") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: ! Digit1 49 []", + "Keypress: ! Digit1 33 33 []", + "Keyup: ! Digit1 49 []", + ] + ) + await page.keyboard.type("^") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: ^ Digit6 54 []", + "Keypress: ^ Digit6 94 94 []", + "Keyup: ^ Digit6 54 []", + ] + ) + + +async def test_should_send_proper_codes_while_typing_with_shift(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + keyboard = page.keyboard + await keyboard.down("Shift") + await page.keyboard.type("~") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Shift ShiftLeft 16 [Shift]", + "Keydown: ~ Backquote 192 [Shift]", # 192 is ` keyCode + "Keypress: ~ Backquote 126 126 [Shift]", # 126 is ~ charCode + "Keyup: ~ Backquote 192 [Shift]", + ] + ) + await keyboard.up("Shift") + + +async def test_should_not_type_canceled_events(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + await page.evaluate( + """() => { + window.addEventListener('keydown', event => { + event.stopPropagation(); + event.stopImmediatePropagation(); + if (event.key === 'l') + event.preventDefault(); + if (event.key === 'o') + event.preventDefault(); + }, false); + }""" + ) + + await page.keyboard.type("Hello World!") + assert ( + await page.evalOnSelector("textarea", "textarea => textarea.value") == "He Wrd!" + ) + + +async def test_should_press_plus(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("+") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: + Equal 187 []", # 192 is ` keyCode + "Keypress: + Equal 43 43 []", # 126 is ~ charCode + "Keyup: + Equal 187 []", + ] + ) + + +async def test_should_press_shift_plus(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("Shift++") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Shift ShiftLeft 16 [Shift]", + "Keydown: + Equal 187 [Shift]", # 192 is ` keyCode + "Keypress: + Equal 43 43 [Shift]", # 126 is ~ charCode + "Keyup: + Equal 187 [Shift]", + "Keyup: Shift ShiftLeft 16 []", + ] + ) + + +async def test_should_support_plus_separated_modifiers(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("Shift+~") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Shift ShiftLeft 16 [Shift]", + "Keydown: ~ Backquote 192 [Shift]", # 192 is ` keyCode + "Keypress: ~ Backquote 126 126 [Shift]", # 126 is ~ charCode + "Keyup: ~ Backquote 192 [Shift]", + "Keyup: Shift ShiftLeft 16 []", + ] + ) + + +async def test_should_suport_multiple_plus_separated_modifiers(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("Control+Shift+~") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Control ControlLeft 17 [Control]", + "Keydown: Shift ShiftLeft 16 [Control Shift]", + "Keydown: ~ Backquote 192 [Control Shift]", # 192 is ` keyCode + "Keyup: ~ Backquote 192 [Control Shift]", + "Keyup: Shift ShiftLeft 16 [Control]", + "Keyup: Control ControlLeft 17 []", + ] + ) + + +async def test_should_shift_raw_codes(page: Page, server): + await page.goto(server.PREFIX + "/input/keyboard.html") + await page.keyboard.press("Shift+Digit3") + assert await page.evaluate("() => getResult()") == "\n".join( + [ + "Keydown: Shift ShiftLeft 16 [Shift]", + "Keydown: # Digit3 51 [Shift]", # 51 is # keyCode + "Keypress: # Digit3 35 35 [Shift]", # 35 is # charCode + "Keyup: # Digit3 51 [Shift]", + "Keyup: Shift ShiftLeft 16 []", + ] + ) + + +async def test_should_specify_repeat_property(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + lastEvent = await captureLastKeydown(page) + await page.keyboard.down("a") + assert await lastEvent.evaluate("e => e.repeat") is False + await page.keyboard.press("a") + assert await lastEvent.evaluate("e => e.repeat") + + await page.keyboard.down("b") + assert await lastEvent.evaluate("e => e.repeat") is False + await page.keyboard.down("b") + assert await lastEvent.evaluate("e => e.repeat") + + await page.keyboard.up("a") + await page.keyboard.down("a") + assert await lastEvent.evaluate("e => e.repeat") is False + + +async def test_should_type_all_kinds_of_characters(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.focus("textarea") + text = "This text goes onto two lines.\nThis character is 嗨." + await page.keyboard.type(text) + assert await page.evalOnSelector("textarea", "t => t.value") == text + + +async def test_should_specify_location(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + lastEvent = await captureLastKeydown(page) + textarea = await page.querySelector("textarea") + assert textarea + + await textarea.press("Digit5") + assert await lastEvent.evaluate("e => e.location") == 0 + + await textarea.press("ControlLeft") + assert await lastEvent.evaluate("e => e.location") == 1 + + await textarea.press("ControlRight") + assert await lastEvent.evaluate("e => e.location") == 2 + + await textarea.press("NumpadSubtract") + assert await lastEvent.evaluate("e => e.location") == 3 + + +async def test_should_press_enter(page: Page, server): + await page.setContent("") + await page.focus("textarea") + lastEventHandle = await captureLastKeydown(page) + + async def testEnterKey(key, expectedKey, expectedCode): + await page.keyboard.press(key) + lastEvent = await lastEventHandle.jsonValue() + assert lastEvent["key"] == expectedKey + assert lastEvent["code"] == expectedCode + value = await page.evalOnSelector("textarea", "t => t.value") + assert value == "\n" + await page.evalOnSelector("textarea", "t => t.value = ''") + + await testEnterKey("Enter", "Enter", "Enter") + await testEnterKey("NumpadEnter", "Enter", "NumpadEnter") + await testEnterKey("\n", "Enter", "Enter") + await testEnterKey("\r", "Enter", "Enter") + + +async def test_should_throw_unknown_keys(page: Page, server): + with pytest.raises(Error) as exc: + await page.keyboard.press("NotARealKey") + assert exc.value.message == 'Unknown key: "NotARealKey"' + + with pytest.raises(Error) as exc: + await page.keyboard.press("ё") + assert exc.value.message == 'Unknown key: "ё"' + + with pytest.raises(Error) as exc: + await page.keyboard.press("😊") + assert exc.value.message == 'Unknown key: "😊"' + + +async def test_should_type_emoji(page: Page, server): + await page.goto(server.PREFIX + "/input/textarea.html") + await page.type("textarea", "👹 Tokyo street Japan 🇯🇵") + assert ( + await page.evalOnSelector("textarea", "textarea => textarea.value") + == "👹 Tokyo street Japan 🇯🇵" + ) + + +async def test_should_type_emoji_into_an_iframe(page: Page, server, utils): + await page.goto(server.EMPTY_PAGE) + await utils.attach_frame(page, "emoji-test", server.PREFIX + "/input/textarea.html") + frame = page.frames[1] + textarea = await frame.querySelector("textarea") + assert textarea + await textarea.type("👹 Tokyo street Japan 🇯🇵") + assert ( + await frame.evalOnSelector("textarea", "textarea => textarea.value") + == "👹 Tokyo street Japan 🇯🇵" + ) + + +async def test_should_handle_select_all(page: Page, server, is_mac): + await page.goto(server.PREFIX + "/input/textarea.html") + textarea = await page.querySelector("textarea") + assert textarea + await textarea.type("some text") + modifier = "Meta" if is_mac else "Control" + await page.keyboard.down(modifier) + await page.keyboard.press("a") + await page.keyboard.up(modifier) + await page.keyboard.press("Backspace") + assert await page.evalOnSelector("textarea", "textarea => textarea.value") == "" + + +async def test_should_be_able_to_prevent_select_all(page, server, is_mac): + await page.goto(server.PREFIX + "/input/textarea.html") + textarea = await page.querySelector("textarea") + await textarea.type("some text") + await page.evalOnSelector( + "textarea", + """textarea => { + textarea.addEventListener('keydown', event => { + if (event.key === 'a' && (event.metaKey || event.ctrlKey)) + event.preventDefault(); + }, false); + }""", + ) + + modifier = "Meta" if is_mac else "Control" + await page.keyboard.down(modifier) + await page.keyboard.press("a") + await page.keyboard.up(modifier) + await page.keyboard.press("Backspace") + assert ( + await page.evalOnSelector("textarea", "textarea => textarea.value") + == "some tex" + ) + + +@pytest.mark.only_platform("darwin") +async def test_should_support_macos_shortcuts(page, server, is_firefox, is_mac): + await page.goto(server.PREFIX + "/input/textarea.html") + textarea = await page.querySelector("textarea") + await textarea.type("some text") + # select one word backwards + await page.keyboard.press("Shift+Control+Alt+KeyB") + await page.keyboard.press("Backspace") + assert ( + await page.evalOnSelector("textarea", "textarea => textarea.value") == "some " + ) + + +async def test_should_press_the_meta_key(page, server, is_firefox, is_mac): + lastEvent = await captureLastKeydown(page) + await page.keyboard.press("Meta") + v = await lastEvent.jsonValue() + metaKey = v["metaKey"] + key = v["key"] + code = v["code"] + if is_firefox and not is_mac: + assert key == "OS" + else: + assert key == "Meta" + + if is_firefox: + assert code == "OSLeft" + else: + assert code == "MetaLeft" + + if is_firefox and not is_mac: + assert metaKey is False + else: + assert metaKey + + +async def test_should_work_after_a_cross_origin_navigation(page, server): + await page.goto(server.PREFIX + "/empty.html") + await page.goto(server.CROSS_PROCESS_PREFIX + "/empty.html") + lastEvent = await captureLastKeydown(page) + await page.keyboard.press("a") + assert await lastEvent.evaluate("l => l.key") == "a" + + +# event.keyIdentifier has been removed from all browsers except WebKit +@pytest.mark.only_browser("webkit") +async def test_should_expose_keyIdentifier_in_webkit(page, server): + lastEvent = await captureLastKeydown(page) + keyMap = { + "ArrowUp": "Up", + "ArrowDown": "Down", + "ArrowLeft": "Left", + "ArrowRight": "Right", + "Backspace": "U+0008", + "Tab": "U+0009", + "Delete": "U+007F", + "a": "U+0041", + "b": "U+0042", + "F12": "F12", + } + for key, keyIdentifier in keyMap.items(): + await page.keyboard.press(key) + assert await lastEvent.evaluate("e => e.keyIdentifier") == keyIdentifier + + +async def test_should_scroll_with_pagedown(page: Page, server): + await page.goto(server.PREFIX + "/input/scrollable.html") + # A click is required for WebKit to send the event into the body. + await page.click("body") + await page.keyboard.press("PageDown") + # We can't wait for the scroll to finish, so just wait for it to start. + await page.waitForFunction("() => scrollY > 0") diff --git a/tests/conftest.py b/tests/conftest.py index 8e627a325..4225100b1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,11 +28,6 @@ _dirname = get_file_dirname() -# Will mark all the tests as async -def pytest_collection_modifyitems(items): - for item in items: - item.add_marker(pytest.mark.asyncio) - def pytest_generate_tests(metafunc): if "browser_name" in metafunc.fixturenames: diff --git a/tests/sync/test_sync.py b/tests/sync/test_sync.py index d3656c463..926ef4896 100644 --- a/tests/sync/test_sync.py +++ b/tests/sync/test_sync.py @@ -197,3 +197,13 @@ def test_close_should_reject_all_promises(context): lambda: new_page.close(), ) assert "Protocol error" in exc_info.value.message + + +def test_expect_response_should_work(page: Page, server): + with page.expect_response("**/*") as resp: + page.goto(server.EMPTY_PAGE) + assert resp.value + assert resp.value.url == server.EMPTY_PAGE + assert resp.value.status == 200 + assert resp.value.ok + assert resp.value.request