From beb0f22b1feab1d1fa1db933a1deca1d905cd5bf Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 29 Jul 2020 22:41:50 -0700 Subject: [PATCH 01/27] feat(roll): roll Playwright to 1.2.0-next.1596080705875 (#107) --- README.md | 4 +- driver/package.json | 2 +- playwright/accessibility.py | 10 +-- playwright/browser.py | 11 +-- playwright/browser_context.py | 9 ++- playwright/browser_server.py | 8 +- playwright/browser_type.py | 8 +- playwright/connection.py | 124 +++++++++++++------------------ playwright/console_message.py | 8 +- playwright/dialog.py | 8 +- playwright/download.py | 8 +- playwright/drivers/browsers.json | 9 ++- playwright/element_handle.py | 8 +- playwright/frame.py | 15 ++-- playwright/input.py | 4 +- playwright/js_handle.py | 8 +- playwright/network.py | 25 ++++--- playwright/object_factory.py | 48 ++++++------ playwright/page.py | 31 ++++---- playwright/playwright.py | 8 +- playwright/selectors.py | 8 +- playwright/worker.py | 8 +- tests/test_navigation.py | 6 -- tests/test_network.py | 5 +- tests/test_page.py | 2 +- 25 files changed, 192 insertions(+), 193 deletions(-) diff --git a/README.md b/README.md index 0d0b90593..8d5eadb58 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 🎭 Playwright for Python -[![PyPI version](https://badge.fury.io/py/playwright.svg)](https://pypi.python.org/pypi/playwright/) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg) [![Chromium version](https://img.shields.io/badge/chromium-86.0.4211.0-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-78.0b5-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-14.0-blue.svg?logo=safari)](https://webkit.org/) +[![PyPI version](https://badge.fury.io/py/playwright.svg)](https://pypi.python.org/pypi/playwright/) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg) [![Chromium version](https://img.shields.io/badge/chromium-86.0.4217.0-blue.svg?logo=google-chrome)](https://www.chromium.org/Home) [![Firefox version](https://img.shields.io/badge/firefox-78.0b5-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/) [![WebKit version](https://img.shields.io/badge/webkit-14.0-blue.svg?logo=safari)](https://webkit.org/) [![Coverage Status](https://coveralls.io/repos/github/microsoft/playwright-python/badge.svg?branch=master)](https://coveralls.io/github/microsoft/playwright-python?branch=master) ##### [Docs](https://github.com/microsoft/playwright/blob/master/docs/README.md) | [API reference](https://github.com/microsoft/playwright/blob/master/docs/api.md) @@ -9,7 +9,7 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H | | Linux | macOS | Windows | | :--- | :---: | :---: | :---: | -| Chromium 86.0.4211.0 | ✅ | ✅ | ✅ | +| Chromium 86.0.4217.0 | ✅ | ✅ | ✅ | | WebKit 14.0 | ✅ | ✅ | ✅ | | Firefox 78.0b5 | ✅ | ✅ | ✅ | diff --git a/driver/package.json b/driver/package.json index 91da02606..088bec259 100644 --- a/driver/package.json +++ b/driver/package.json @@ -13,7 +13,7 @@ }, "license": "Apache-2.0", "dependencies": { - "playwright": "1.2.0-next.1595625808301" + "playwright": "1.2.0-next.1596080705875" }, "devDependencies": { "pkg": "^4.4.9" diff --git a/playwright/accessibility.py b/playwright/accessibility.py index e7173a730..efda033fe 100644 --- a/playwright/accessibility.py +++ b/playwright/accessibility.py @@ -55,13 +55,13 @@ def _ax_node_from_protocol(axNode: Dict[str, Any]) -> Dict[str, Any]: class Accessibility: def __init__(self, channel: Channel) -> None: self._channel = channel - self._loop = channel._scope._loop + self._loop = channel._connection._loop async def snapshot( self, interestingOnly: bool = True, root: ElementHandle = None ) -> Optional[Dict[str, Any]]: - root = root._channel if root else None - result = await self._channel.send( - "accessibilitySnapshot", dict(root=root, interestingOnly=interestingOnly), - ) + params = {"interestingOnly": interestingOnly} + if root: + params["root"] = root._channel + result = await self._channel.send("accessibilitySnapshot", params,) return _ax_node_from_protocol(result) if result else None diff --git a/playwright/browser.py b/playwright/browser.py index 92f38bef8..2320a7590 100644 --- a/playwright/browser.py +++ b/playwright/browser.py @@ -17,7 +17,7 @@ from typing import Dict, List, Union from playwright.browser_context import BrowserContext -from playwright.connection import ChannelOwner, ConnectionScope, from_channel +from playwright.connection import ChannelOwner, from_channel from playwright.helper import ColorScheme, locals_to_params from playwright.network import serialize_headers from playwright.page import Page @@ -32,8 +32,10 @@ class Browser(ChannelOwner): Events = SimpleNamespace(Disconnected="disconnected",) - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer, True) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self._is_connected = True self._is_closed_or_closing = False @@ -44,7 +46,6 @@ def _on_close(self) -> None: self._is_connected = False self.emit(Browser.Events.Disconnected) self._is_closed_or_closing = True - self._scope.dispose() @property def contexts(self) -> List[BrowserContext]: @@ -75,7 +76,7 @@ async def newContext( ) -> BrowserContext: params = locals_to_params(locals()) if viewport == 0: - params["viewport"] = None + del params["viewport"] params["noDefaultViewport"] = True if extraHTTPHeaders: params["extraHTTPHeaders"] = serialize_headers(extraHTTPHeaders) diff --git a/playwright/browser_context.py b/playwright/browser_context.py index 96646ed2d..acf22f396 100644 --- a/playwright/browser_context.py +++ b/playwright/browser_context.py @@ -16,7 +16,7 @@ from types import SimpleNamespace from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union -from playwright.connection import ChannelOwner, ConnectionScope, from_channel +from playwright.connection import ChannelOwner, from_channel from playwright.event_context_manager import EventContextManagerImpl from playwright.helper import ( Cookie, @@ -41,8 +41,10 @@ class BrowserContext(ChannelOwner): Events = SimpleNamespace(Close="close", Page="page",) - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer, True) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self._pages: List[Page] = [] self._routes: List[RouteHandlerEntry] = [] self._bindings: Dict[str, Any] = {} @@ -205,7 +207,6 @@ def _on_close(self) -> None: pending_event.reject(False, "Context") self.emit(BrowserContext.Events.Close) - self._scope.dispose() async def close(self) -> None: if self._is_closed_or_closing: diff --git a/playwright/browser_server.py b/playwright/browser_server.py index afa48e146..a1c55803d 100644 --- a/playwright/browser_server.py +++ b/playwright/browser_server.py @@ -15,15 +15,17 @@ from types import SimpleNamespace from typing import Dict -from playwright.connection import ChannelOwner, ConnectionScope +from playwright.connection import ChannelOwner class BrowserServer(ChannelOwner): Events = SimpleNamespace(Close="close",) - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self._channel.on("close", lambda _: self.emit(BrowserServer.Events.Close)) @property diff --git a/playwright/browser_type.py b/playwright/browser_type.py index d6d5eb12e..23d20c525 100644 --- a/playwright/browser_type.py +++ b/playwright/browser_type.py @@ -16,13 +16,15 @@ from playwright.browser import Browser from playwright.browser_context import BrowserContext -from playwright.connection import ChannelOwner, ConnectionScope, from_channel +from playwright.connection import ChannelOwner, from_channel from playwright.helper import ColorScheme, locals_to_params, not_installed_error class BrowserType(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer, True) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) @property def name(self) -> str: diff --git a/playwright/connection.py b/playwright/connection.py index eaf836043..c275a11ea 100644 --- a/playwright/connection.py +++ b/playwright/connection.py @@ -15,7 +15,7 @@ import asyncio import sys import traceback -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Callable, Dict, Optional, Union from greenlet import greenlet from pyee import BaseEventEmitter @@ -26,16 +26,17 @@ class Channel(BaseEventEmitter): - def __init__(self, scope: "ConnectionScope", guid: str) -> None: + def __init__(self, connection: "Connection", guid: str) -> None: super().__init__() - self._scope: ConnectionScope = scope + self._connection: Connection = connection self._guid = guid self._object: Optional[ChannelOwner] = None async def send(self, method: str, params: dict = None) -> Any: if params is None: params = {} - result = await self._scope.send_message_to_server(self._guid, method, params) + callback = self._connection._send_message_to_server(self._guid, method, params) + result = await callback.future # Protocol now has named return values, assume result is one level deeper unless # there is explicit ambiguity. if not result: @@ -50,79 +51,46 @@ async def send(self, method: str, params: dict = None) -> Any: def send_no_reply(self, method: str, params: dict = None) -> None: if params is None: params = {} - self._scope.send_message_to_server_no_reply(self._guid, method, params) + self._connection._send_message_to_server(self._guid, method, params) class ChannelOwner(BaseEventEmitter): def __init__( self, - scope: "ConnectionScope", + parent: Union["ChannelOwner", "Connection"], + type: str, guid: str, initializer: Dict, - is_scope: bool = False, ) -> None: super().__init__() + self._loop: asyncio.AbstractEventLoop = parent._loop + self._type = type self._guid = guid - self._scope = scope.create_child(guid) if is_scope else scope - self._loop = self._scope._loop - self._channel = Channel(self._scope, guid) + self._connection: Connection = parent._connection if isinstance( + parent, ChannelOwner + ) else parent + self._parent: Optional[ChannelOwner] = parent if isinstance( + parent, ChannelOwner + ) else None + self._objects: Dict[str, "ChannelOwner"] = {} + self._channel = Channel(self._connection, guid) self._channel._object = self self._initializer = initializer - -class ConnectionScope: - def __init__( - self, connection: "Connection", guid: str, parent: Optional["ConnectionScope"] - ) -> None: - self._connection: "Connection" = connection - self._loop: asyncio.AbstractEventLoop = connection._loop - self._guid: str = guid - self._children: List["ConnectionScope"] = [] - self._objects: Dict[str, ChannelOwner] = {} - self._parent = parent - - def create_child(self, guid: str) -> "ConnectionScope": - scope = self._connection.create_scope(guid, self) - self._children.append(scope) - return scope - - def dispose(self) -> None: - # Take care of hierarchy. - for child in self._children: - child.dispose() - self._children.clear() - - # Delete self from scopes and objects. - self._connection._scopes.pop(self._guid) - self._connection._objects.pop(self._guid) - - # Delete all of the objects from connection. - for guid in self._objects: - self._connection._objects.pop(guid) - - # Clean up from parent. + self._connection._objects[guid] = self if self._parent: - self._parent._objects.pop(self._guid) - self._parent._children.remove(self) + self._parent._objects[guid] = self - async def send_message_to_server(self, guid: str, method: str, params: Dict) -> Any: - callback = self._connection._send_message_to_server(guid, method, params) - return await callback.future - - def send_message_to_server_no_reply( - self, guid: str, method: str, params: Dict - ) -> Any: - self._connection._send_message_to_server(guid, method, params) + def _dispose(self) -> None: + # Clean up from parent and connection. + if self._parent: + del self._parent._objects[self._guid] + del self._connection._objects[self._guid] - def create_remote_object(self, type: str, guid: str, initializer: Dict) -> Any: - result: ChannelOwner - initializer = self._connection._replace_guids_with_channels(initializer) - result = self._connection._object_factory(self, type, guid, initializer) - self._connection._objects[guid] = result - self._objects[guid] = result - if guid in self._connection._waiting_for_object: - self._connection._waiting_for_object.pop(guid)(result) - return result + # Dispose all children. + for object in list(self._objects.values()): + object._dispose() + self._objects.clear() class ProtocolCallback: @@ -131,6 +99,11 @@ def __init__(self, loop: asyncio.AbstractEventLoop) -> None: self.future = loop.create_future() +class RootChannelOwner(ChannelOwner): + def __init__(self, connection: "Connection") -> None: + super().__init__(connection, "", "", {}) + + class Connection: def __init__( self, @@ -145,9 +118,8 @@ def __init__( self._last_id = 0 self._loop = loop self._objects: Dict[str, ChannelOwner] = {} - self._scopes: Dict[str, ConnectionScope] = {} self._callbacks: Dict[int, ProtocolCallback] = {} - self._root_scope = self.create_scope("", None) + self._root_object = RootChannelOwner(self) self._object_factory = object_factory self._is_sync = False @@ -215,11 +187,14 @@ def _dispatch(self, msg: ParsedMessagePayload) -> None: method = msg.get("method") params = msg["params"] if method == "__create__": - scope = self._scopes[guid] - scope.create_remote_object( - params["type"], params["guid"], params["initializer"] + parent = self._objects[guid] + self._create_remote_object( + parent, params["type"], params["guid"], params["initializer"] ) return + if method == "__dispose__": + self._objects[guid]._dispose() + return object = self._objects[guid] try: @@ -235,6 +210,16 @@ def _dispatch(self, msg: ParsedMessagePayload) -> None: "".join(traceback.format_exception(*sys.exc_info())), ) + def _create_remote_object( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> Any: + result: ChannelOwner + initializer = self._replace_guids_with_channels(initializer) + result = self._object_factory(parent, type, guid, initializer) + if guid in self._waiting_for_object: + self._waiting_for_object.pop(guid)(result) + return result + def _replace_channels_with_guids(self, payload: Any) -> Any: if payload is None: return payload @@ -263,13 +248,6 @@ def _replace_guids_with_channels(self, payload: Any) -> Any: return result return payload - def create_scope( - self, guid: str, parent: Optional[ConnectionScope] - ) -> ConnectionScope: - scope = ConnectionScope(self, guid, parent) - self._scopes[guid] = scope - return scope - def from_channel(channel: Channel) -> Any: return channel._object diff --git a/playwright/console_message.py b/playwright/console_message.py index 3026726c0..e7dd8c015 100644 --- a/playwright/console_message.py +++ b/playwright/console_message.py @@ -14,14 +14,16 @@ from typing import Dict, List -from playwright.connection import ChannelOwner, ConnectionScope, from_channel +from playwright.connection import ChannelOwner, from_channel from playwright.helper import ConsoleMessageLocation from playwright.js_handle import JSHandle class ConsoleMessage(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) def __str__(self) -> str: return self.text diff --git a/playwright/dialog.py b/playwright/dialog.py index 4156ceace..99f8b7d15 100644 --- a/playwright/dialog.py +++ b/playwright/dialog.py @@ -14,13 +14,15 @@ from typing import Dict -from playwright.connection import ChannelOwner, ConnectionScope +from playwright.connection import ChannelOwner from playwright.helper import locals_to_params class Dialog(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) @property def type(self) -> str: diff --git a/playwright/download.py b/playwright/download.py index 873c7508b..e0eca58ec 100644 --- a/playwright/download.py +++ b/playwright/download.py @@ -14,12 +14,14 @@ from typing import Dict, Optional -from playwright.connection import ChannelOwner, ConnectionScope +from playwright.connection import ChannelOwner class Download(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) @property def url(self) -> str: diff --git a/playwright/drivers/browsers.json b/playwright/drivers/browsers.json index e9a780d02..376dad895 100644 --- a/playwright/drivers/browsers.json +++ b/playwright/drivers/browsers.json @@ -3,15 +3,18 @@ "browsers": [ { "name": "chromium", - "revision": "791201" + "revision": "792639", + "download": true }, { "name": "firefox", - "revision": "1140" + "revision": "1144", + "download": true }, { "name": "webkit", - "revision": "1317" + "revision": "1319", + "download": true } ] } \ No newline at end of file diff --git a/playwright/element_handle.py b/playwright/element_handle.py index bedb1752c..b8edc359c 100644 --- a/playwright/element_handle.py +++ b/playwright/element_handle.py @@ -18,7 +18,7 @@ import sys from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union, cast -from playwright.connection import ConnectionScope, from_nullable_channel +from playwright.connection import ChannelOwner, from_nullable_channel from playwright.helper import ( FilePayload, KeyboardModifier, @@ -38,8 +38,10 @@ class ElementHandle(JSHandle): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) def asElement(self) -> Optional["ElementHandle"]: return self diff --git a/playwright/frame.py b/playwright/frame.py index 32e567639..ad143e90e 100644 --- a/playwright/frame.py +++ b/playwright/frame.py @@ -18,12 +18,7 @@ from pyee import BaseEventEmitter -from playwright.connection import ( - ChannelOwner, - ConnectionScope, - from_channel, - from_nullable_channel, -) +from playwright.connection import ChannelOwner, from_channel, from_nullable_channel from playwright.element_handle import ( ElementHandle, ValuesToSelect, @@ -56,8 +51,10 @@ class Frame(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self._parent_frame = from_nullable_channel(initializer.get("parentFrame")) if self._parent_frame: self._parent_frame._child_frames.append(self) @@ -416,7 +413,7 @@ async def uncheck( await self._channel.send("uncheck", locals_to_params(locals())) async def waitForTimeout(self, timeout: int) -> Awaitable[None]: - return self._scope._loop.create_task(asyncio.sleep(timeout / 1000)) + return self._connection._loop.create_task(asyncio.sleep(timeout / 1000)) async def waitForFunction( self, diff --git a/playwright/input.py b/playwright/input.py index bba0b0474..6fdac66f5 100644 --- a/playwright/input.py +++ b/playwright/input.py @@ -19,7 +19,7 @@ class Keyboard: def __init__(self, channel: Channel) -> None: self._channel = channel - self._loop = channel._scope._loop + self._loop = channel._connection._loop async def down(self, key: str) -> None: await self._channel.send("keyboardDown", locals_to_params(locals())) @@ -40,7 +40,7 @@ async def press(self, key: str, delay: int = None) -> None: class Mouse: def __init__(self, channel: Channel) -> None: self._channel = channel - self._loop = channel._scope._loop + self._loop = channel._connection._loop async def move(self, x: float, y: float, steps: int = None) -> None: await self._channel.send("mouseMove", locals_to_params(locals())) diff --git a/playwright/js_handle.py b/playwright/js_handle.py index 538ae8d1b..e57e319e2 100644 --- a/playwright/js_handle.py +++ b/playwright/js_handle.py @@ -16,7 +16,7 @@ from datetime import datetime from typing import TYPE_CHECKING, Any, Dict, List, Optional -from playwright.connection import ChannelOwner, ConnectionScope, from_channel +from playwright.connection import ChannelOwner, from_channel from playwright.helper import Error, is_function_body if TYPE_CHECKING: # pragma: no cover @@ -24,8 +24,10 @@ class JSHandle(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self._preview = self._initializer["preview"] self._channel.on( "previewUpdated", lambda params: self._on_preview_updated(params["preview"]) diff --git a/playwright/network.py b/playwright/network.py index 4f3fecc1d..2f378fa10 100644 --- a/playwright/network.py +++ b/playwright/network.py @@ -16,12 +16,7 @@ import json from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast -from playwright.connection import ( - ChannelOwner, - ConnectionScope, - from_channel, - from_nullable_channel, -) +from playwright.connection import ChannelOwner, from_channel, from_nullable_channel from playwright.helper import ContinueParameters, Error, Header if TYPE_CHECKING: # pragma: no cover @@ -29,8 +24,10 @@ class Request(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self._redirected_from: Optional["Request"] = from_nullable_channel( initializer.get("redirectedFrom") ) @@ -86,8 +83,10 @@ def failure(self) -> Optional[str]: class Route(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) @property def request(self) -> Request: @@ -133,8 +132,10 @@ async def continue_( class Response(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) @property def url(self) -> str: diff --git a/playwright/object_factory.py b/playwright/object_factory.py index f7671b468..be9531566 100644 --- a/playwright/object_factory.py +++ b/playwright/object_factory.py @@ -18,7 +18,7 @@ from playwright.browser_context import BrowserContext from playwright.browser_server import BrowserServer from playwright.browser_type import BrowserType -from playwright.connection import ChannelOwner, ConnectionScope +from playwright.connection import ChannelOwner from playwright.console_message import ConsoleMessage from playwright.dialog import Dialog from playwright.download import Download @@ -33,47 +33,49 @@ class DummyObject(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) def create_remote_object( - scope: ConnectionScope, type: str, guid: str, initializer: Dict + parent: ChannelOwner, type: str, guid: str, initializer: Dict ) -> Any: if type == "BindingCall": - return BindingCall(scope, guid, initializer) + return BindingCall(parent, type, guid, initializer) if type == "Browser": - return Browser(scope, guid, initializer) + return Browser(parent, type, guid, initializer) if type == "BrowserServer": - return BrowserServer(scope, guid, initializer) + return BrowserServer(parent, type, guid, initializer) if type == "BrowserType": - return BrowserType(scope, guid, initializer) + return BrowserType(parent, type, guid, initializer) if type == "BrowserContext": - return BrowserContext(scope, guid, initializer) + return BrowserContext(parent, type, guid, initializer) if type == "ConsoleMessage": - return ConsoleMessage(scope, guid, initializer) + return ConsoleMessage(parent, type, guid, initializer) if type == "Dialog": - return Dialog(scope, guid, initializer) + return Dialog(parent, type, guid, initializer) if type == "Download": - return Download(scope, guid, initializer) + return Download(parent, type, guid, initializer) if type == "ElementHandle": - return ElementHandle(scope, guid, initializer) + return ElementHandle(parent, type, guid, initializer) if type == "Frame": - return Frame(scope, guid, initializer) + return Frame(parent, type, guid, initializer) if type == "JSHandle": - return JSHandle(scope, guid, initializer) + return JSHandle(parent, type, guid, initializer) if type == "Page": - return Page(scope, guid, initializer) + return Page(parent, type, guid, initializer) if type == "Playwright": - return Playwright(scope, guid, initializer) + return Playwright(parent, type, guid, initializer) if type == "Request": - return Request(scope, guid, initializer) + return Request(parent, type, guid, initializer) if type == "Response": - return Response(scope, guid, initializer) + return Response(parent, type, guid, initializer) if type == "Route": - return Route(scope, guid, initializer) + return Route(parent, type, guid, initializer) if type == "Worker": - return Worker(scope, guid, initializer) + return Worker(parent, type, guid, initializer) if type == "Selectors": - return Selectors(scope, guid, initializer) - return DummyObject(scope, guid, initializer) + return Selectors(parent, type, guid, initializer) + return DummyObject(parent, type, guid, initializer) diff --git a/playwright/page.py b/playwright/page.py index fe1e788e5..65642c4ba 100644 --- a/playwright/page.py +++ b/playwright/page.py @@ -19,12 +19,7 @@ from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Union, cast from playwright.accessibility import Accessibility -from playwright.connection import ( - ChannelOwner, - ConnectionScope, - from_channel, - from_nullable_channel, -) +from playwright.connection import ChannelOwner, from_channel, from_nullable_channel from playwright.console_message import ConsoleMessage from playwright.dialog import Dialog from playwright.download import Download @@ -94,8 +89,10 @@ class Page(ChannelOwner): keyboard: Keyboard mouse: Mouse - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self.accessibility = Accessibility(self._channel) self.keyboard = Keyboard(self._channel) self.mouse = Mouse(self._channel) @@ -110,7 +107,7 @@ def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None self._pending_wait_for_events: List[PendingWaitEvent] = [] self._routes: List[RouteHandlerEntry] = [] self._owned_context: Optional["BrowserContext"] = None - self._timeout_settings = TimeoutSettings(None) + self._timeout_settings: TimeoutSettings = TimeoutSettings(None) self._channel.on( "bindingCall", @@ -705,8 +702,14 @@ async def pdf( margin: Dict = None, path: str = None, ) -> bytes: - binary = await self._channel.send("pdf", locals_to_params(locals())) - return base64.b64decode(binary) + params = locals_to_params(locals()) + del params["path"] + encoded_binary = await self._channel.send("pdf", params) + decoded_binary = base64.b64decode(encoded_binary) + if path: + with open(path, "wb") as fd: + fd.write(decoded_binary) + return decoded_binary def expect_event( self, event: str, predicate: Callable[[Any], bool] = None, timeout: int = None, @@ -755,8 +758,10 @@ def expect_worker( class BindingCall(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) async def call(self, func: FunctionWithSource) -> None: try: diff --git a/playwright/playwright.py b/playwright/playwright.py index b295a1454..f2c561390 100644 --- a/playwright/playwright.py +++ b/playwright/playwright.py @@ -15,7 +15,7 @@ from typing import Dict from playwright.browser_type import BrowserType -from playwright.connection import ChannelOwner, ConnectionScope, from_channel +from playwright.connection import ChannelOwner, from_channel from playwright.helper import Devices from playwright.selectors import Selectors @@ -27,8 +27,10 @@ class Playwright(ChannelOwner): selectors: Selectors devices: Devices - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self.chromium = from_channel(initializer["chromium"]) self.firefox = from_channel(initializer["firefox"]) self.webkit = from_channel(initializer["webkit"]) diff --git a/playwright/selectors.py b/playwright/selectors.py index 8605d24a7..5306d474e 100644 --- a/playwright/selectors.py +++ b/playwright/selectors.py @@ -14,13 +14,15 @@ from typing import Dict, Optional -from playwright.connection import ChannelOwner, ConnectionScope +from playwright.connection import ChannelOwner from playwright.element_handle import ElementHandle class Selectors(ChannelOwner): - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) async def register( self, name: str, source: str = "", path: str = None, contentScript: bool = False diff --git a/playwright/worker.py b/playwright/worker.py index 421c7657c..3eec643c8 100644 --- a/playwright/worker.py +++ b/playwright/worker.py @@ -15,7 +15,7 @@ from types import SimpleNamespace from typing import Any, Dict -from playwright.connection import ChannelOwner, ConnectionScope, from_channel +from playwright.connection import ChannelOwner, from_channel from playwright.helper import is_function_body from playwright.js_handle import JSHandle, parse_result, serialize_argument @@ -23,8 +23,10 @@ class Worker(ChannelOwner): Events = SimpleNamespace(Close="close") - def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None: - super().__init__(scope, guid, initializer) + def __init__( + self, parent: ChannelOwner, type: str, guid: str, initializer: Dict + ) -> None: + super().__init__(parent, type, guid, initializer) self._channel.on("close", lambda _: self._on_close()) def _on_close(self) -> None: diff --git a/tests/test_navigation.py b/tests/test_navigation.py index f7655bda5..589cac006 100644 --- a/tests/test_navigation.py +++ b/tests/test_navigation.py @@ -225,12 +225,6 @@ async def test_goto_should_not_crash_when_navigating_to_bad_ssl_after_a_cross_or await page.goto(https_server.EMPTY_PAGE) -async def test_goto_should_not_throw_if_networkidle0_is_passed_as_an_option( - page, server -): - await page.goto(server.EMPTY_PAGE, waitUntil="networkidle0") - - async def test_goto_should_throw_if_networkidle2_is_passed_as_an_option(page, server): with pytest.raises(Error) as exc_info: await page.goto(server.EMPTY_PAGE, waitUntil="networkidle2") diff --git a/tests/test_network.py b/tests/test_network.py index 33446ddc3..be5f1e541 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -502,7 +502,4 @@ async def test_set_extra_http_headers_should_throw_for_non_string_header_values( await page.setExtraHTTPHeaders({"foo": 1}) except Error as exc: error = exc - assert ( - error.message - == 'Expected value of header "foo" to be String, but "number" is found.' - ) + assert error.message == "headers[0].value: expected string, got number" diff --git a/tests/test_page.py b/tests/test_page.py index cf22c57b8..276bf7524 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -762,7 +762,7 @@ async def test_select_option_should_not_select_single_option_when_some_attribute async def test_select_option_should_select_only_first_option(page, server): await page.goto(server.PREFIX + "/input/select.html") - await page.selectOption("select", "blue", "green", "red") + await page.selectOption("select", ["blue", "green", "red"]) assert await page.evaluate("result.onInput") == ["blue"] assert await page.evaluate("result.onChange") == ["blue"] From 02b6ea04dcb822894220c684ebce995e2c34a33d Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 29 Jul 2020 22:42:11 -0700 Subject: [PATCH 02/27] docs: leave async examples in README.md (#108) --- README.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8d5eadb58..f2879c072 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,10 @@ with sync_playwright() as p: iphone_11 = p.devices['iPhone 11 Pro'] browser = p.webkit.launch(headless=False) context = browser.newContext( - **iphone_11, - locale='en-US', - geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 }, - permissions=['geolocation'] + **iphone_11, + locale='en-US', + geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 }, + permissions=['geolocation'] ) page = context.newPage() page.goto('https://maps.google.com') @@ -83,6 +83,32 @@ with sync_playwright() as p: browser.close() ``` +... or, if you are comfortable using asyncio, you can do the following: + +```py +import asyncio +from playwright import async_playwright + +async def main(): + async with async_playwright() as p: + iphone_11 = p.devices['iPhone 11 Pro'] + browser = await p.webkit.launch(headless=False) + context = await browser.newContext( + **iphone_11, + locale='en-US', + geolocation={ 'longitude': 12.492507, 'latitude': 41.889938 }, + permissions=['geolocation'] + ) + page = await context.newPage() + await page.goto('https://maps.google.com') + await page.click('text="Your location"') + await page.waitForRequest('*preview/pwa') + await page.screenshot(path='colosseum-iphone.png') + await browser.close() + +asyncio.get_event_loop().run_until_complete(main()) +``` + #### Evaluate in browser context This code snippet navigates to example.com in Firefox, and executes a script in the page context. @@ -105,6 +131,30 @@ with sync_playwright() as p: browser.close() ``` +... and again, async version: + +```py +import asyncio +from playwright import async_playwright + +async def main(): + async with async_playwright() as p: + browser = await p.firefox.launch() + page = await browser.newPage() + await page.goto('https://www.example.com/') + dimensions = await page.evaluate('''() => { + return { + width: document.documentElement.clientWidth, + height: document.documentElement.clientHeight, + deviceScaleFactor: window.devicePixelRatio + } + }''') + print(dimensions) + await browser.close() + +asyncio.get_event_loop().run_until_complete(main()) +``` + #### Intercept network requests This code snippet sets up request routing for a Chromium page to log all network requests. @@ -127,6 +177,30 @@ with sync_playwright() as p: browser.close() ``` +... async version: + +```py +import asyncio +from playwright import async_playwright + +async def main(): + async with async_playwright() as p: + browser = await p.chromium.launch() + page = await browser.newPage() + + def log_and_continue_request(route, request): + print(request.url) + asyncio.create_task(route.continue_()) + + # Log and continue all network requests + await page.route('**', lambda route, request: log_and_continue_request(route, request)) + + await page.goto('http://todomvc.com') + await browser.close() + +asyncio.get_event_loop().run_until_complete(main()) +``` + # Is Playwright for Python ready? We are ready for your feedback, but we are still covering Playwright Python with the tests, so expect a bumpy ride and don't use for production. From 3c7a9babe83c723b586e7506f1ef842f82aea969 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 30 Jul 2020 09:20:22 +0200 Subject: [PATCH 03/27] fix: multiple Playwright instances (#106) --- playwright/main.py | 7 +++++-- tests/test_sync.py | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/playwright/main.py b/playwright/main.py index 0c7f2a4d1..abfa7776d 100644 --- a/playwright/main.py +++ b/playwright/main.py @@ -25,7 +25,7 @@ from playwright.async_api import Playwright as AsyncPlaywright from playwright.connection import Connection -from playwright.helper import not_installed_error +from playwright.helper import Error, not_installed_error from playwright.object_factory import create_remote_object from playwright.playwright import Playwright from playwright.sync_api import Playwright as SyncPlaywright @@ -72,7 +72,10 @@ async def run_driver_async() -> Connection: def run_driver() -> Connection: - return asyncio.get_event_loop().run_until_complete(run_driver_async()) + loop = asyncio.get_event_loop() + if loop.is_running(): + raise Error("Can only run one Playwright at a time.") + return loop.run_until_complete(run_driver_async()) class SyncPlaywrightContextManager: diff --git a/tests/test_sync.py b/tests/test_sync.py index 60e56d95f..1cfa3d79a 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -205,3 +205,12 @@ def test_sync_workers_page_workers(page: Page, server): page.goto(server.EMPTY_PAGE) assert len(page.workers) == 0 + + +def test_sync_playwright_multiple_times(): + with sync_playwright() as pw1: + assert pw1.chromium + with pytest.raises(Error) as exc: + with sync_playwright() as pw2: + assert pw1.chromium == pw2.chromium + assert "Can only run one Playwright at a time." in exc.value.message From de036673afe73c63f411fb928795a0c7b8f4561e Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 30 Jul 2020 17:55:33 +0200 Subject: [PATCH 04/27] chore(docs): use bundled api.md for docs generation (#109) --- scripts/documentation_provider.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/scripts/documentation_provider.py b/scripts/documentation_provider.py index b1919c8a3..adce58351 100644 --- a/scripts/documentation_provider.py +++ b/scripts/documentation_provider.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os import re -from sys import stderr +import sys +from pathlib import Path from typing import Any, Dict, List, cast -import requests +_dirname = Path(os.path.dirname(os.path.abspath(__file__))) class DocumentationProvider: @@ -33,9 +35,9 @@ def __init__(self) -> None: } def load(self) -> None: - api_md = requests.get( - "https://raw.githubusercontent.com/microsoft/playwright/master/docs/api.md" - ).text + api_md = ( + _dirname / ".." / "driver" / "node_modules" / "playwright" / "api.md" + ).read_text() class_name = None method_name = None @@ -160,7 +162,7 @@ def print_entry( if name not in signature: print( f"Not implemented parameter {class_name}.{method_name}({name}=)", - file=stderr, + file=sys.stderr, ) continue else: @@ -179,7 +181,7 @@ def print_entry( if signature: print( f"Not documented parameters: {class_name}.{method_name}({signature.keys()})", - file=stderr, + file=sys.stderr, ) From f81221c6b1e6463ec801b1d7b93d2637cb6634fc Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 30 Jul 2020 18:12:41 +0200 Subject: [PATCH 05/27] chore: added tests for generation scripts (#99) --- .github/workflows/ci.yml | 2 +- buildbots/test-sync-generation.sh | 22 +++++++++++++--------- playwright/transport.py | 5 +++-- tests/test_generation.py | 27 +++++++++++++++++++++++++++ 4 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 tests/test_generation.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c96b20612..97332f68a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,7 +75,7 @@ jobs: - name: Install run: python -m playwright install - name: Test - run: pytest -vv --browser=${{ matrix.browser }} --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}.xml --cov=playwright --cov-report xml + run: pytest -vv --browser=${{ matrix.browser }} --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.browser }}.xml --cov=playwright --cov=scripts --cov-report xml - name: Coveralls run: coveralls env: diff --git a/buildbots/test-sync-generation.sh b/buildbots/test-sync-generation.sh index e442a2852..012217ecc 100644 --- a/buildbots/test-sync-generation.sh +++ b/buildbots/test-sync-generation.sh @@ -1,15 +1,19 @@ #!/bin/bash -newfile="sync_api.py" -oldfile="playwright/sync_api.py" +function assert_script { + newfile="$2" + oldfile="playwright/$2" + echo "Testing $newfile against $oldfile" -python scripts/generate_sync_api.py > $newfile + python $1 > $newfile -pre-commit run --files $newfile + pre-commit run --files $newfile -cmp $oldfile $newfile -exit_code=$? + cmp $oldfile $newfile + exit_code=$? + rm $newfile + return $exit_code +} -rm $newfile - -exit $exit_code +assert_script "scripts/generate_sync_api.py" "sync_api.py" +assert_script "scripts/generate_async_api.py" "async_api.py" diff --git a/playwright/transport.py b/playwright/transport.py index 01a7b31d8..e8c1631ac 100644 --- a/playwright/transport.py +++ b/playwright/transport.py @@ -71,5 +71,6 @@ def send(self, message: Dict) -> None: if "DEBUGP" in os.environ: # pragma: no cover print("\x1b[32mSEND>\x1b[0m", json.dumps(message, indent=2)) data = bytes(msg, "utf-8") - self._output.write(len(data).to_bytes(4, byteorder="little", signed=False)) - self._output.write(data) + self._output.write( + len(data).to_bytes(4, byteorder="little", signed=False) + data + ) diff --git a/tests/test_generation.py b/tests/test_generation.py new file mode 100644 index 000000000..33a3d1b73 --- /dev/null +++ b/tests/test_generation.py @@ -0,0 +1,27 @@ +import sys +from io import StringIO +from unittest.mock import patch + +import pytest + +CAN_RUN_GENERATION_SCRIPT = sys.version_info >= (3, 8) + +if CAN_RUN_GENERATION_SCRIPT: + from scripts.generate_async_api import main as generate_async_api + from scripts.generate_sync_api import main as generate_sync_api + + +@pytest.mark.skipif( + not CAN_RUN_GENERATION_SCRIPT, reason="requires python3.8 or higher" +) +def test_generate_sync_api(): + with patch("sys.stdout", new_callable=StringIO): + generate_sync_api() + + +@pytest.mark.skipif( + not CAN_RUN_GENERATION_SCRIPT, reason="requires python3.8 or higher" +) +def test_generate_async_api(): + with patch("sys.stdout", new_callable=StringIO): + generate_async_api() From 735c3a26cd083306afade0d21c8912d40eb92c5a Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 30 Jul 2020 21:45:58 +0200 Subject: [PATCH 06/27] feat: accept pathlib in the API (#110) --- build_driver.py | 5 +++-- build_package.py | 20 ++++++++++++-------- playwright/async_api.py | 15 +++++++++++++-- playwright/element_handle.py | 9 +++++---- playwright/frame.py | 9 ++++++--- playwright/main.py | 19 ++++++++++--------- playwright/path_utils.py | 10 ++++++++++ playwright/sync_api.py | 15 +++++++++++++-- scripts/documentation_provider.py | 6 +++--- scripts/generate_api.py | 1 + tests/conftest.py | 4 ++-- tests/server.py | 8 ++++---- tests/test_add_init_script.py | 17 +++++------------ tests/test_input.py | 16 ++++++---------- tests/test_launcher.py | 7 +++---- tests/test_navigation.py | 9 +++------ tests/test_network.py | 14 +++++--------- tests/test_page.py | 13 +++++++------ tests/test_pdf.py | 3 ++- tests/test_queryselector.py | 11 ++++------- 20 files changed, 117 insertions(+), 94 deletions(-) create mode 100644 playwright/path_utils.py diff --git a/build_driver.py b/build_driver.py index 2a2c89b4c..10b48a7e4 100644 --- a/build_driver.py +++ b/build_driver.py @@ -17,9 +17,10 @@ import re import shutil import subprocess -from pathlib import Path -_dirname = Path(os.path.dirname(os.path.abspath(__file__))) +from playwright.path_utils import get_file_dirname + +_dirname = get_file_dirname() driver_path = _dirname / "driver" package_path = _dirname / "playwright" diff --git a/build_package.py b/build_package.py index f4c1ca965..f1813413c 100644 --- a/build_package.py +++ b/build_package.py @@ -12,16 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import shutil import subprocess -folder = os.path.dirname(os.path.abspath(__file__)) -if os.path.exists(os.path.join(folder, "build")): - shutil.rmtree(os.path.join(folder, "build")) -if os.path.exists(os.path.join(folder, "dist")): - shutil.rmtree(os.path.join(folder, "dist")) -if os.path.exists(os.path.join(folder, "playwright.egg-info")): - shutil.rmtree(os.path.join(folder, "playwright.egg-info")) +from playwright.path_utils import get_file_dirname + +_dirname = get_file_dirname() +_build_dir = _dirname / "build" +if _build_dir.exists(): + shutil.rmtree(_build_dir) +_dist_dir = _dirname / "dist" +if _dist_dir.exists(): + shutil.rmtree(_dist_dir) +_egg_dir = _dirname / "playwright.egg-info" +if _egg_dir.exists(): + shutil.rmtree(_egg_dir) subprocess.run("python setup.py sdist bdist_wheel", shell=True) diff --git a/playwright/async_api.py b/playwright/async_api.py index 8e7b03004..cb3cd30c1 100644 --- a/playwright/async_api.py +++ b/playwright/async_api.py @@ -13,6 +13,7 @@ # limitations under the License. +import pathlib import sys import typing @@ -870,7 +871,12 @@ async def selectText(self, timeout: int = None) -> NoneType: async def setInputFiles( self, files: typing.Union[ - str, FilePayload, typing.List[str], typing.List[FilePayload] + str, + pathlib.Path, + FilePayload, + typing.List[str], + typing.List[pathlib.Path], + typing.List[FilePayload], ], timeout: int = None, noWaitAfter: bool = None, @@ -1877,7 +1883,12 @@ async def setInputFiles( self, selector: str, files: typing.Union[ - str, FilePayload, typing.List[str], typing.List[FilePayload] + str, + pathlib.Path, + FilePayload, + typing.List[str], + typing.List[pathlib.Path], + typing.List[FilePayload], ], timeout: int = None, noWaitAfter: bool = None, diff --git a/playwright/element_handle.py b/playwright/element_handle.py index b8edc359c..5e2069d8d 100644 --- a/playwright/element_handle.py +++ b/playwright/element_handle.py @@ -16,6 +16,7 @@ import mimetypes import os import sys +from pathlib import Path from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union, cast from playwright.connection import ChannelOwner, from_nullable_channel @@ -123,7 +124,7 @@ async def selectText(self, timeout: int = None) -> None: async def setInputFiles( self, - files: Union[str, FilePayload, List[str], List[FilePayload]], + files: Union[str, Path, FilePayload, List[str], List[Path], List[FilePayload]], timeout: int = None, noWaitAfter: bool = None, ) -> None: @@ -242,16 +243,16 @@ def convert_select_option_values(arg: ValuesToSelect) -> Any: def normalize_file_payloads( - files: Union[str, FilePayload, List[str], List[FilePayload]] + files: Union[str, Path, FilePayload, List[str], List[Path], List[FilePayload]] ) -> List[FilePayload]: file_list = files if isinstance(files, list) else [files] file_payloads: List[FilePayload] = [] for item in file_list: - if isinstance(item, str): + if isinstance(item, str) or isinstance(item, Path): with open(item, mode="rb") as fd: file: FilePayload = { "name": os.path.basename(item), - "mimeType": mimetypes.guess_type(item)[0] + "mimeType": mimetypes.guess_type(str(Path(item)))[0] or "application/octet-stream", "buffer": base64.b64encode(fd.read()).decode(), } diff --git a/playwright/frame.py b/playwright/frame.py index ad143e90e..7739d8ec1 100644 --- a/playwright/frame.py +++ b/playwright/frame.py @@ -14,6 +14,7 @@ import asyncio import sys +from pathlib import Path from typing import TYPE_CHECKING, Any, Awaitable, Dict, List, Optional, Set, Union, cast from pyee import BaseEventEmitter @@ -280,7 +281,7 @@ async def addScriptTag( params = locals_to_params(locals()) if path: with open(path, "r") as file: - params["content"] = file.read() + "\n//# sourceURL=" + path + params["content"] = file.read() + "\n//# sourceURL=" + str(Path(path)) del params["path"] return from_channel(await self._channel.send("addScriptTag", params)) @@ -290,7 +291,9 @@ async def addStyleTag( params = locals_to_params(locals()) if path: with open(path, "r") as file: - params["content"] = file.read() + "\n/*# sourceURL=" + path + "*/" + params["content"] = ( + file.read() + "\n/*# sourceURL=" + str(Path(path)) + "*/" + ) del params["path"] return from_channel(await self._channel.send("addStyleTag", params)) @@ -366,7 +369,7 @@ async def selectOption( async def setInputFiles( self, selector: str, - files: Union[str, FilePayload, List[str], List[FilePayload]], + files: Union[str, Path, FilePayload, List[str], List[Path], List[FilePayload]], timeout: int = None, noWaitAfter: bool = None, ) -> None: diff --git a/playwright/main.py b/playwright/main.py index abfa7776d..20cb2c3ff 100644 --- a/playwright/main.py +++ b/playwright/main.py @@ -27,6 +27,7 @@ from playwright.connection import Connection from playwright.helper import Error, not_installed_error from playwright.object_factory import create_remote_object +from playwright.path_utils import get_file_dirname from playwright.playwright import Playwright from playwright.sync_api import Playwright as SyncPlaywright from playwright.sync_base import dispatcher_fiber, set_dispatcher_fiber @@ -44,12 +45,12 @@ def compute_driver_name() -> str: async def run_driver_async() -> Connection: - package_path = os.path.dirname(os.path.abspath(__file__)) + package_path = get_file_dirname() driver_name = compute_driver_name() - driver_executable = os.path.join(package_path, driver_name) - archive_name = os.path.join(package_path, "drivers", driver_name + ".gz") + driver_executable = package_path / driver_name + archive_name = package_path / "drivers" / (driver_name + ".gz") - if not os.path.exists(driver_executable) or os.path.getmtime( + if not driver_executable.exists() or os.path.getmtime( driver_executable ) < os.path.getmtime(archive_name): raise not_installed_error( @@ -57,7 +58,7 @@ async def run_driver_async() -> Connection: ) proc = await asyncio.create_subprocess_exec( - driver_executable, + str(driver_executable), stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, @@ -124,12 +125,12 @@ def main() -> None: if "install" not in sys.argv: print('Run "python -m playwright install" to complete installation') return - package_path = os.path.dirname(os.path.abspath(__file__)) + package_path = get_file_dirname() driver_name = compute_driver_name() - driver_executable = os.path.join(package_path, driver_name) - archive_name = os.path.join(package_path, "drivers", driver_name + ".gz") + driver_executable = package_path / driver_name + archive_name = package_path / "drivers" / (driver_name + ".gz") - if not os.path.exists(driver_executable) or os.path.getmtime( + if not driver_executable.exists() or os.path.getmtime( driver_executable ) < os.path.getmtime(archive_name): print(f"Extracting {archive_name} into {driver_executable}...") diff --git a/playwright/path_utils.py b/playwright/path_utils.py new file mode 100644 index 000000000..ee901c860 --- /dev/null +++ b/playwright/path_utils.py @@ -0,0 +1,10 @@ +import inspect +from pathlib import Path + + +def get_file_dirname() -> Path: + """Returns the callee (`__file__`) directory name""" + frame = inspect.stack()[1] + module = inspect.getmodule(frame[0]) + assert module + return Path(module.__file__).parent.absolute() diff --git a/playwright/sync_api.py b/playwright/sync_api.py index d765e40ce..58a28c4f5 100644 --- a/playwright/sync_api.py +++ b/playwright/sync_api.py @@ -13,6 +13,7 @@ # limitations under the License. +import pathlib import sys import typing @@ -906,7 +907,12 @@ def selectText(self, timeout: int = None) -> NoneType: def setInputFiles( self, files: typing.Union[ - str, FilePayload, typing.List[str], typing.List[FilePayload] + str, + pathlib.Path, + FilePayload, + typing.List[str], + typing.List[pathlib.Path], + typing.List[FilePayload], ], timeout: int = None, noWaitAfter: bool = None, @@ -1968,7 +1974,12 @@ def setInputFiles( self, selector: str, files: typing.Union[ - str, FilePayload, typing.List[str], typing.List[FilePayload] + str, + pathlib.Path, + FilePayload, + typing.List[str], + typing.List[pathlib.Path], + typing.List[FilePayload], ], timeout: int = None, noWaitAfter: bool = None, diff --git a/scripts/documentation_provider.py b/scripts/documentation_provider.py index adce58351..d0efbfa73 100644 --- a/scripts/documentation_provider.py +++ b/scripts/documentation_provider.py @@ -12,13 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import re import sys -from pathlib import Path from typing import Any, Dict, List, cast -_dirname = Path(os.path.dirname(os.path.abspath(__file__))) +from playwright.path_utils import get_file_dirname + +_dirname = get_file_dirname() class DocumentationProvider: diff --git a/scripts/generate_api.py b/scripts/generate_api.py index c90a51717..41a9214cb 100644 --- a/scripts/generate_api.py +++ b/scripts/generate_api.py @@ -153,6 +153,7 @@ def return_value(value: Any) -> List[str]: import typing import sys +import pathlib if sys.version_info >= (3, 8): # pragma: no cover from typing import Literal diff --git a/tests/conftest.py b/tests/conftest.py index 0cb58283e..c248c9819 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -186,7 +186,7 @@ def skip_by_browser(request, browser_name): ) if browser_name in skip_browsers_names: - pytest.skip("skipped for this browser: {}".format(browser_name)) + pytest.skip(f"skipped for this browser: {browser_name}") @pytest.fixture(autouse=True) @@ -196,7 +196,7 @@ def skip_by_platform(request): ) if sys.platform in skip_platform_names: - pytest.skip("skipped on this platform: {}".format(sys.platform)) + pytest.skip(f"skipped on this platform: {sys.platform}") def pytest_addoption(parser): diff --git a/tests/server.py b/tests/server.py index 694cdc093..fe34be6f2 100644 --- a/tests/server.py +++ b/tests/server.py @@ -16,18 +16,18 @@ import asyncio import gzip import mimetypes -import os import socket import threading from contextlib import closing from http import HTTPStatus -from pathlib import Path from OpenSSL import crypto from twisted.internet import reactor, ssl from twisted.web import http -_dirname = Path(os.path.join(os.path.dirname(__file__))) +from playwright.path_utils import get_file_dirname + +_dirname = get_file_dirname() def _find_free_port(): @@ -107,7 +107,7 @@ def process(self): file_content = None try: file_content = open( - os.path.join(static_path, request.path.decode()[1:]), "rb" + static_path / request.path.decode()[1:], "rb" ).read() except (FileNotFoundError, IsADirectoryError): request.setResponseCode(HTTPStatus.NOT_FOUND) diff --git a/tests/test_add_init_script.py b/tests/test_add_init_script.py index 47e575959..f9438fb6a 100644 --- a/tests/test_add_init_script.py +++ b/tests/test_add_init_script.py @@ -12,9 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os - from playwright import Error +from playwright.path_utils import get_file_dirname + +_dirname = get_file_dirname() async def test_add_init_script_evaluate_before_anything_else_on_the_page(page): @@ -24,11 +25,7 @@ async def test_add_init_script_evaluate_before_anything_else_on_the_page(page): async def test_add_init_script_work_with_a_path(page): - await page.addInitScript( - path=os.path.join( - os.path.dirname(os.path.abspath(__file__)), "assets/injectedfile.js" - ) - ) + await page.addInitScript(path=_dirname / "assets/injectedfile.js") await page.goto("data:text/html,") assert await page.evaluate("window.result") == 123 @@ -59,11 +56,7 @@ async def test_add_init_script_work_with_browser_context_scripts(page, context): async def test_add_init_script_work_with_browser_context_scripts_with_a_path( page, context ): - await context.addInitScript( - path=os.path.join( - os.path.dirname(os.path.abspath(__file__)), "assets/injectedfile.js" - ) - ) + await context.addInitScript(path=_dirname / "assets/injectedfile.js") page = await context.newPage() await page.goto("data:text/html,") assert await page.evaluate("window.result") == 123 diff --git a/tests/test_input.py b/tests/test_input.py index 1f852b3b8..8890c89a4 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -16,12 +16,10 @@ import os from playwright.page import Page +from playwright.path_utils import get_file_dirname -FILE_TO_UPLOAD = os.path.join( - os.path.dirname(os.path.realpath(__file__)), "assets/file-to-upload.txt" -) - -__dirname = os.path.dirname(os.path.realpath(__file__)) +_dirname = get_file_dirname() +FILE_TO_UPLOAD = _dirname / "assets/file-to-upload.txt" async def test_should_upload_the_file(page, server): @@ -46,9 +44,7 @@ async def test_should_upload_the_file(page, server): async def test_should_work(page): await page.setContent("") - await page.setInputFiles( - "input", os.path.join(__dirname, "assets/file-to-upload.txt") - ) + await page.setInputFiles("input", _dirname / "assets/file-to-upload.txt") assert await page.evalOnSelector("input", "input => input.files.length") == 1 assert ( await page.evalOnSelector("input", "input => input.files[0].name") @@ -197,8 +193,8 @@ async def test_should_not_accept_multiple_files_for_single_file_input(page, serv try: await file_chooser.setFiles( [ - os.path.realpath(os.path.join(__dirname, "assets/file-to-upload.txt")), - os.path.realpath(os.path.join(__dirname, "assets/pptr.png")), + os.path.realpath(_dirname / "assets/file-to-upload.txt"), + os.path.realpath(_dirname / "assets/pptr.png"), ] ) except Exception as exc: diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 4fb0f496d..97ef245c1 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -19,8 +19,9 @@ from playwright.browser_type import BrowserType from playwright.helper import Error +from playwright.path_utils import get_file_dirname -__dirname = os.path.dirname(os.path.abspath(__file__)) +_dirname = get_file_dirname() async def test_browser_type_launch_should_reject_all_promises_when_browser_is_closed( @@ -51,9 +52,7 @@ async def test_browser_type_launch_should_reject_if_launched_browser_fails_immed with pytest.raises(Error): await browser_type.launch( **launch_arguments, - executablePath=os.path.join( - __dirname, "assets", "dummy_bad_browser_executable.js" - ) + executablePath=_dirname / "assets" / "dummy_bad_browser_executable.js" ) diff --git a/tests/test_navigation.py b/tests/test_navigation.py index 589cac006..5778c864c 100644 --- a/tests/test_navigation.py +++ b/tests/test_navigation.py @@ -13,8 +13,6 @@ # limitations under the License. import asyncio -import os -import pathlib import sys import pytest @@ -22,8 +20,9 @@ from playwright import Error from playwright.helper import TimeoutError from playwright.network import Request +from playwright.path_utils import get_file_dirname -__dirname = os.path.dirname(os.path.realpath(__file__)) +_dirname = get_file_dirname() async def test_goto_should_work(page, server): @@ -32,9 +31,7 @@ async def test_goto_should_work(page, server): async def test_goto_should_work_with_file_URL(page, server): - fileurl = pathlib.Path( - os.path.join(__dirname, "assets", "frames", "two-frames.html") - ).as_uri() + fileurl = (_dirname / "assets" / "frames" / "two-frames.html").as_uri() await page.goto(fileurl) assert page.url.lower() == fileurl.lower() assert len(page.frames) == 3 diff --git a/tests/test_network.py b/tests/test_network.py index be5f1e541..122861de6 100644 --- a/tests/test_network.py +++ b/tests/test_network.py @@ -14,7 +14,6 @@ import asyncio import json -import os from asyncio.futures import Future from typing import Dict, List @@ -23,6 +22,9 @@ from playwright.helper import Error from playwright.network import Request from playwright.page import Page +from playwright.path_utils import get_file_dirname + +_dirname = get_file_dirname() async def test_request_fulfill(page): @@ -245,20 +247,14 @@ async def test_response_json_should_work(page, server): async def test_response_body_should_work(page, server): response = await page.goto(server.PREFIX + "/pptr.png") - with open( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets/pptr.png"), - "rb", - ) as fd: + with open(_dirname / "assets/pptr.png", "rb",) as fd: assert fd.read() == await response.body() async def test_response_body_should_work_with_compression(page, server): server.enable_gzip("/pptr.png") response = await page.goto(server.PREFIX + "/pptr.png") - with open( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets/pptr.png"), - "rb", - ) as fd: + with open(_dirname / "assets/pptr.png", "rb",) as fd: assert fd.read() == await response.body() diff --git a/tests/test_page.py b/tests/test_page.py index 276bf7524..92babb5ed 100644 --- a/tests/test_page.py +++ b/tests/test_page.py @@ -19,8 +19,9 @@ import pytest from playwright import Error, TimeoutError +from playwright.path_utils import get_file_dirname -__dirname = os.path.dirname(os.path.realpath(__file__)) +_dirname = get_file_dirname() async def test_close_should_reject_all_promises(context): @@ -533,7 +534,7 @@ async def test_add_script_tag_should_work_with_a_url_and_type_module(page, serve async def test_add_script_tag_should_work_with_a_path_and_type_module(page, server): await page.goto(server.EMPTY_PAGE) await page.addScriptTag( - path=os.path.join(__dirname, "assets", "es6", "es6pathimport.js"), type="module" + path=_dirname / "assets" / "es6" / "es6pathimport.js", type="module" ) await page.waitForFunction("window.__es6injected") assert await page.evaluate("__es6injected") == 42 @@ -561,7 +562,7 @@ async def test_add_script_tag_should_throw_an_error_if_loading_from_url_fail( async def test_add_script_tag_should_work_with_a_path(page, server): await page.goto(server.EMPTY_PAGE) script_handle = await page.addScriptTag( - path=os.path.join(__dirname, "assets", "injectedfile.js") + path=_dirname / "assets" / "injectedfile.js" ) assert script_handle.asElement() assert await page.evaluate("__injected") == 42 @@ -573,7 +574,7 @@ async def test_add_script_tag_should_include_source_url_when_path_is_provided( ): # Lacking sourceURL support in WebKit await page.goto(server.EMPTY_PAGE) - await page.addScriptTag(path=os.path.join(__dirname, "assets", "injectedfile.js")) + await page.addScriptTag(path=_dirname / "assets" / "injectedfile.js") result = await page.evaluate("__injectedError.stack") assert os.path.join("assets", "injectedfile.js") in result @@ -639,7 +640,7 @@ async def test_add_style_tag_should_throw_an_error_if_loading_from_url_fail( async def test_add_style_tag_should_work_with_a_path(page, server): await page.goto(server.EMPTY_PAGE) style_handle = await page.addStyleTag( - path=os.path.join(__dirname, "assets", "injectedstyle.css") + path=_dirname / "assets" / "injectedstyle.css" ) assert style_handle.asElement() assert ( @@ -654,7 +655,7 @@ async def test_add_style_tag_should_include_source_url_when_path_is_provided( page, server ): await page.goto(server.EMPTY_PAGE) - await page.addStyleTag(path=os.path.join(__dirname, "assets", "injectedstyle.css")) + await page.addStyleTag(path=_dirname / "assets" / "injectedstyle.css") style_handle = await page.querySelector("style") style_content = await page.evaluate("style => style.innerHTML", style_handle) assert os.path.join("assets", "injectedstyle.css") in style_content diff --git a/tests/test_pdf.py b/tests/test_pdf.py index 3601b3eec..92c6ece14 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -13,6 +13,7 @@ # limitations under the License. import os +from pathlib import Path import pytest @@ -20,7 +21,7 @@ @pytest.mark.only_browser("chromium") -async def test_should_be_able_to_save_pdf_file(page: Page, server, tmpdir): +async def test_should_be_able_to_save_pdf_file(page: Page, server, tmpdir: Path): output_file = tmpdir / "foo.png" await page.pdf(path=str(output_file)) assert os.path.getsize(output_file) > 0 diff --git a/tests/test_queryselector.py b/tests/test_queryselector.py index 1d92f754d..abe612ee1 100644 --- a/tests/test_queryselector.py +++ b/tests/test_queryselector.py @@ -1,10 +1,12 @@ -import os from typing import Any, cast import pytest from playwright.helper import Error from playwright.page import Page +from playwright.path_utils import get_file_dirname + +_dirname = get_file_dirname() async def test_selectors_register_should_work(selectors, page: Page, utils): @@ -42,12 +44,7 @@ async def test_selectors_register_should_work(selectors, page: Page, utils): async def test_selectors_register_should_work_with_path(selectors, page: Page, utils): await utils.register_selector_engine( - selectors, - "foo", - path=os.path.join( - os.path.dirname(os.path.abspath(__file__)), - "assets/sectionselectorengine.js", - ), + selectors, "foo", path=_dirname / "assets/sectionselectorengine.js" ) await page.setContent("
") assert await page.evalOnSelector("foo=whatever", "e => e.nodeName") == "SECTION" From d4ccf6a18de97e7ca3ceeaa41f9d1572b4164e67 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 30 Jul 2020 18:36:05 -0700 Subject: [PATCH 07/27] chore: better type validation (#112) Co-authored-by: Max Schmitt --- .pre-commit-config.yaml | 1 - api.json | 16493 +++++++++++++++++ driver/package.json | 2 +- playwright/accessibility.py | 7 +- playwright/async_api.py | 4355 +++-- playwright/browser.py | 3 + playwright/browser_context.py | 7 +- playwright/browser_type.py | 2 +- playwright/element_handle.py | 4 +- playwright/frame.py | 27 +- playwright/helper.py | 3 +- playwright/network.py | 27 +- playwright/page.py | 38 +- playwright/sync_api.py | 4355 +++-- scripts/documentation_provider.py | 363 +- scripts/generate_async_api.py | 11 +- scripts/generate_sync_api.py | 7 +- tests/test_scripts_documentation_provider.py | 26 - 18 files changed, 22259 insertions(+), 3472 deletions(-) create mode 100644 api.json delete mode 100644 tests/test_scripts_documentation_provider.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2513fc3ff..3c8055c74 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,6 @@ repos: - id: end-of-file-fixer exclude: ^playwright/drivers/browsers.json$ - id: check-yaml - - id: check-added-large-files - repo: https://github.com/psf/black rev: 19.10b0 hooks: diff --git a/api.json b/api.json new file mode 100644 index 000000000..df57be420 --- /dev/null +++ b/api.json @@ -0,0 +1,16493 @@ +{ + "Browser": { + "name": "Browser", + "members": { + "disconnected": { + "kind": "event", + "name": "disconnected", + "type": null, + "comment": "", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "close": { + "kind": "method", + "name": "close", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "In case this browser is obtained using browserType.launch, closes the browser and all of its pages (if any were opened).\nIn case this browser is obtained using browserType.connect, clears all created contexts belonging to this browser and disconnects from the browser server.\nThe Browser object itself is considered to be disposed and cannot be used anymore.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "contexts": { + "kind": "method", + "name": "contexts", + "type": { + "name": "Array", + "properties": {} + }, + "comment": "Returns an array of all open browser contexts. In a newly created browser, this will return zero\nbrowser contexts.\n```js\nconst browser = await pw.webkit.launch();\nconsole.log(browser.contexts().length); // prints `0`\n\nconst context = await browser.newContext();\nconsole.log(browser.contexts().length); // prints `1`\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "isConnected": { + "kind": "method", + "name": "isConnected", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Indicates that the browser is connected.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "newContext": { + "kind": "method", + "name": "newContext", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Creates a new browser context. It won't share cookies/cache with other browser contexts.\n```js\n(async () => {\n const browser = await playwright.firefox.launch(); // Or 'chromium' or 'webkit'.\n // Create a new incognito browser context.\n const context = await browser.newContext();\n // Create a new page in a pristine context.\n const page = await context.newPage();\n await page.goto('https://example.com');\n})();\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "options": { + "kind": "property", + "name": "options", + "type": { + "name": "Object", + "properties": { + "acceptDownloads": { + "kind": "property", + "name": "acceptDownloads", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled.", + "returnComment": "", + "required": false, + "templates": [] + }, + "ignoreHTTPSErrors": { + "kind": "property", + "name": "ignoreHTTPSErrors", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether to ignore HTTPS errors during navigation. Defaults to `false`.", + "returnComment": "", + "required": false, + "templates": [] + }, + "bypassCSP": { + "kind": "property", + "name": "bypassCSP", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Toggles bypassing page's Content-Security-Policy.", + "returnComment": "", + "required": false, + "templates": [] + }, + "viewport": { + "kind": "property", + "name": "viewport", + "type": { + "name": "?Object", + "properties": { + "width": { + "kind": "property", + "name": "width", + "type": { + "name": "number", + "properties": {} + }, + "comment": "page width in pixels.", + "returnComment": "", + "required": true, + "templates": [] + }, + "height": { + "kind": "property", + "name": "height", + "type": { + "name": "number", + "properties": {} + }, + "comment": "page height in pixels.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "comment": "Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport.", + "returnComment": "", + "required": false, + "templates": [] + }, + "userAgent": { + "kind": "property", + "name": "userAgent", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Specific user agent to use in this context.", + "returnComment": "", + "required": false, + "templates": [] + }, + "deviceScaleFactor": { + "kind": "property", + "name": "deviceScaleFactor", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Specify device scale factor (can be thought of as dpr). Defaults to `1`.", + "returnComment": "", + "required": false, + "templates": [] + }, + "isMobile": { + "kind": "property", + "name": "isMobile", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether the `meta viewport` tag is taken into account and touch events are enabled. Defaults to `false`. Not supported in Firefox.", + "returnComment": "", + "required": false, + "templates": [] + }, + "hasTouch": { + "kind": "property", + "name": "hasTouch", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Specifies if viewport supports touch events. Defaults to false.", + "returnComment": "", + "required": false, + "templates": [] + }, + "javaScriptEnabled": { + "kind": "property", + "name": "javaScriptEnabled", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether or not to enable JavaScript in the context. Defaults to true.", + "returnComment": "", + "required": false, + "templates": [] + }, + "timezoneId": { + "kind": "property", + "name": "timezoneId", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Changes the timezone of the context. See ICU’s `metaZones.txt` for a list of supported timezone IDs.", + "returnComment": "", + "required": false, + "templates": [] + }, + "geolocation": { + "kind": "property", + "name": "geolocation", + "type": { + "name": "Object", + "properties": { + "latitude": { + "kind": "property", + "name": "latitude", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Latitude between -90 and 90.", + "returnComment": "", + "required": true, + "templates": [] + }, + "longitude": { + "kind": "property", + "name": "longitude", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Longitude between -180 and 180.", + "returnComment": "", + "required": true, + "templates": [] + }, + "accuracy": { + "kind": "property", + "name": "accuracy", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Non-negative accuracy value. Defaults to `0`.", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + }, + "locale": { + "kind": "property", + "name": "locale", + "type": { + "name": "string", + "properties": {} + }, + "comment": "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.", + "returnComment": "", + "required": false, + "templates": [] + }, + "permissions": { + "kind": "property", + "name": "permissions", + "type": { + "name": "Array", + "properties": {} + }, + "comment": "A list of permissions to grant to all pages in this context. See browserContext.grantPermissions for more details.", + "returnComment": "", + "required": false, + "templates": [] + }, + "extraHTTPHeaders": { + "kind": "property", + "name": "extraHTTPHeaders", + "type": { + "name": "Object", + "properties": {} + }, + "comment": "An object containing additional HTTP headers to be sent with every request. All header values must be strings.", + "returnComment": "", + "required": false, + "templates": [] + }, + "offline": { + "kind": "property", + "name": "offline", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether to emulate network being offline. Defaults to `false`.", + "returnComment": "", + "required": false, + "templates": [] + }, + "httpCredentials": { + "kind": "property", + "name": "httpCredentials", + "type": { + "name": "Object", + "properties": { + "username": { + "kind": "property", + "name": "username", + "type": { + "name": "string", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + }, + "password": { + "kind": "property", + "name": "password", + "type": { + "name": "string", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "comment": "Credentials for HTTP authentication.", + "returnComment": "", + "required": false, + "templates": [] + }, + "colorScheme": { + "kind": "property", + "name": "colorScheme", + "type": { + "name": "\"light\"|\"dark\"|\"no-preference\"", + "properties": {} + }, + "comment": "Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'.", + "returnComment": "", + "required": false, + "templates": [] + }, + "logger": { + "kind": "property", + "name": "logger", + "type": { + "name": "Logger", + "properties": {} + }, + "comment": "Logger sink for Playwright logging.", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "newPage": { + "kind": "method", + "name": "newPage", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Creates a new page in a new browser context. Closing this page will close the context as well.\nThis is a convenience API that should only be used for the single-page scenarios and short snippets. Production code and testing frameworks should explicitly create browser.newContext followed by the browserContext.newPage to control their exact life times.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "options": { + "kind": "property", + "name": "options", + "type": { + "name": "Object", + "properties": { + "acceptDownloads": { + "kind": "property", + "name": "acceptDownloads", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled.", + "returnComment": "", + "required": false, + "templates": [] + }, + "ignoreHTTPSErrors": { + "kind": "property", + "name": "ignoreHTTPSErrors", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether to ignore HTTPS errors during navigation. Defaults to `false`.", + "returnComment": "", + "required": false, + "templates": [] + }, + "bypassCSP": { + "kind": "property", + "name": "bypassCSP", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Toggles bypassing page's Content-Security-Policy.", + "returnComment": "", + "required": false, + "templates": [] + }, + "viewport": { + "kind": "property", + "name": "viewport", + "type": { + "name": "?Object", + "properties": { + "width": { + "kind": "property", + "name": "width", + "type": { + "name": "number", + "properties": {} + }, + "comment": "page width in pixels.", + "returnComment": "", + "required": true, + "templates": [] + }, + "height": { + "kind": "property", + "name": "height", + "type": { + "name": "number", + "properties": {} + }, + "comment": "page height in pixels.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "comment": "Sets a consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport.", + "returnComment": "", + "required": false, + "templates": [] + }, + "userAgent": { + "kind": "property", + "name": "userAgent", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Specific user agent to use in this context.", + "returnComment": "", + "required": false, + "templates": [] + }, + "deviceScaleFactor": { + "kind": "property", + "name": "deviceScaleFactor", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Specify device scale factor (can be thought of as dpr). Defaults to `1`.", + "returnComment": "", + "required": false, + "templates": [] + }, + "isMobile": { + "kind": "property", + "name": "isMobile", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether the `meta viewport` tag is taken into account and touch events are enabled. Defaults to `false`. Not supported in Firefox.", + "returnComment": "", + "required": false, + "templates": [] + }, + "hasTouch": { + "kind": "property", + "name": "hasTouch", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Specifies if viewport supports touch events. Defaults to false.", + "returnComment": "", + "required": false, + "templates": [] + }, + "javaScriptEnabled": { + "kind": "property", + "name": "javaScriptEnabled", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether or not to enable JavaScript in the context. Defaults to `true`.", + "returnComment": "", + "required": false, + "templates": [] + }, + "timezoneId": { + "kind": "property", + "name": "timezoneId", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Changes the timezone of the context. See ICU’s `metaZones.txt` for a list of supported timezone IDs.", + "returnComment": "", + "required": false, + "templates": [] + }, + "geolocation": { + "kind": "property", + "name": "geolocation", + "type": { + "name": "Object", + "properties": { + "latitude": { + "kind": "property", + "name": "latitude", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Latitude between -90 and 90.", + "returnComment": "", + "required": true, + "templates": [] + }, + "longitude": { + "kind": "property", + "name": "longitude", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Longitude between -180 and 180.", + "returnComment": "", + "required": true, + "templates": [] + }, + "accuracy": { + "kind": "property", + "name": "accuracy", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Non-negative accuracy value. Defaults to `0`.", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + }, + "locale": { + "kind": "property", + "name": "locale", + "type": { + "name": "string", + "properties": {} + }, + "comment": "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.", + "returnComment": "", + "required": false, + "templates": [] + }, + "permissions": { + "kind": "property", + "name": "permissions", + "type": { + "name": "Array", + "properties": {} + }, + "comment": "A list of permissions to grant to all pages in this context. See browserContext.grantPermissions for more details.", + "returnComment": "", + "required": false, + "templates": [] + }, + "extraHTTPHeaders": { + "kind": "property", + "name": "extraHTTPHeaders", + "type": { + "name": "Object", + "properties": {} + }, + "comment": "An object containing additional HTTP headers to be sent with every request. All header values must be strings.", + "returnComment": "", + "required": false, + "templates": [] + }, + "offline": { + "kind": "property", + "name": "offline", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether to emulate network being offline. Defaults to `false`.", + "returnComment": "", + "required": false, + "templates": [] + }, + "httpCredentials": { + "kind": "property", + "name": "httpCredentials", + "type": { + "name": "Object", + "properties": { + "username": { + "kind": "property", + "name": "username", + "type": { + "name": "string", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + }, + "password": { + "kind": "property", + "name": "password", + "type": { + "name": "string", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "comment": "Credentials for HTTP authentication.", + "returnComment": "", + "required": false, + "templates": [] + }, + "colorScheme": { + "kind": "property", + "name": "colorScheme", + "type": { + "name": "\"light\"|\"dark\"|\"no-preference\"", + "properties": {} + }, + "comment": "Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See page.emulateMedia(options) for more details. Defaults to '`light`'.", + "returnComment": "", + "required": false, + "templates": [] + }, + "logger": { + "kind": "property", + "name": "logger", + "type": { + "name": "Logger", + "properties": {} + }, + "comment": "Logger sink for Playwright logging.", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "version": { + "kind": "method", + "name": "version", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Returns the browser version.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + } + } + }, + "BrowserContext": { + "name": "BrowserContext", + "members": { + "close": { + "kind": "method", + "name": "close", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Closes the browser context. All the pages that belong to the browser context\nwill be closed.\n\n**NOTE** the default browser context cannot be closed.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "page": { + "kind": "event", + "name": "page", + "type": { + "name": "Page", + "properties": {} + }, + "comment": "The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event will also fire for popup pages. See also `Page.on('popup')` to receive events about popups relevant to a specific page.\nThe earliest moment that page is available is when it has navigated to the initial url. For example, when opening a popup with `window.open('http://example.com')`, this event will fire when the network request to \"http://example.com\" is done and its response has started loading in the popup.\n```js\nconst [page] = await Promise.all([\n context.waitForEvent('page'),\n page.click('a[target=_blank]'),\n]);\nconsole.log(await page.evaluate('location.href'));\n```\n\n**NOTE** Use `page.waitForLoadState([state[, options]])` to wait until the page gets to a particular state (you should not need it in most cases).", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "addCookies": { + "kind": "method", + "name": "addCookies", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "```js\nawait browserContext.addCookies([cookieObject1, cookieObject2]);\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "cookies": { + "kind": "property", + "name": "cookies", + "type": { + "name": "Array", + "properties": { + "name": { + "kind": "property", + "name": "name", + "type": { + "name": "string", + "properties": {} + }, + "comment": "**required**", + "returnComment": "", + "required": true, + "templates": [] + }, + "value": { + "kind": "property", + "name": "value", + "type": { + "name": "string", + "properties": {} + }, + "comment": "**required**", + "returnComment": "", + "required": true, + "templates": [] + }, + "url": { + "kind": "property", + "name": "url", + "type": { + "name": "string", + "properties": {} + }, + "comment": "either url or domain / path are required", + "returnComment": "", + "required": false, + "templates": [] + }, + "domain": { + "kind": "property", + "name": "domain", + "type": { + "name": "string", + "properties": {} + }, + "comment": "either url or domain / path are required", + "returnComment": "", + "required": false, + "templates": [] + }, + "path": { + "kind": "property", + "name": "path", + "type": { + "name": "string", + "properties": {} + }, + "comment": "either url or domain / path are required", + "returnComment": "", + "required": false, + "templates": [] + }, + "expires": { + "kind": "property", + "name": "expires", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Unix time in seconds.", + "returnComment": "", + "required": false, + "templates": [] + }, + "httpOnly": { + "kind": "property", + "name": "httpOnly", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + }, + "secure": { + "kind": "property", + "name": "secure", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + }, + "sameSite": { + "kind": "property", + "name": "sameSite", + "type": { + "name": "\"Strict\"|\"Lax\"|\"None\"", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "addInitScript": { + "kind": "method", + "name": "addInitScript", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Adds a script which would be evaluated in one of the following scenarios:\n\nWhenever a page is created in the browser context or is navigated.\nWhenever a child frame is attached or navigated in any page in the browser context. In this case, the script is evaluated in the context of the newly attached frame.\n\nThe script is evaluated after the document was created but before any of its scripts were run. This is useful to amend the JavaScript environment, e.g. to seed `Math.random`.\nAn example of overriding `Math.random` before the page loads:\n```js\n// preload.js\nMath.random = () => 42;\n```\n```js\n// In your playwright script, assuming the preload.js file is in same folder.\nawait browserContext.addInitScript({\n path: 'preload.js'\n});\n```\n\n**NOTE** The order of evaluation of multiple scripts installed via browserContext.addInitScript(script[, arg]) and page.addInitScript(script[, arg]) is not defined.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "script": { + "kind": "property", + "name": "script", + "type": { + "name": "function|string|Object", + "properties": { + "path": { + "kind": "property", + "name": "path", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to current working directory.", + "returnComment": "", + "required": false, + "templates": [] + }, + "content": { + "kind": "property", + "name": "content", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Raw script content.", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "Script to be evaluated in all pages in the browser context.", + "returnComment": "", + "required": true, + "templates": [] + }, + "arg": { + "kind": "property", + "name": "arg", + "type": { + "name": "Serializable", + "properties": {} + }, + "comment": "Optional argument to pass to `script` (only supported when passing a function).", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "clearCookies": { + "kind": "method", + "name": "clearCookies", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Clears context cookies.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "clearPermissions": { + "kind": "method", + "name": "clearPermissions", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Clears all permission overrides for the browser context.\n```js\nconst context = await browser.newContext();\nawait context.grantPermissions(['clipboard-read']);\n// do stuff ..\ncontext.clearPermissions();\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "cookies": { + "kind": "method", + "name": "cookies", + "type": { + "name": "Promise>", + "properties": { + "name": { + "kind": "property", + "name": "name", + "type": { + "name": "string", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + }, + "value": { + "kind": "property", + "name": "value", + "type": { + "name": "string", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + }, + "domain": { + "kind": "property", + "name": "domain", + "type": { + "name": "string", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + }, + "path": { + "kind": "property", + "name": "path", + "type": { + "name": "string", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + }, + "expires": { + "kind": "property", + "name": "expires", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Unix time in seconds.", + "returnComment": "", + "required": true, + "templates": [] + }, + "httpOnly": { + "kind": "property", + "name": "httpOnly", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + }, + "secure": { + "kind": "property", + "name": "secure", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + }, + "sameSite": { + "kind": "property", + "name": "sameSite", + "type": { + "name": "\"Strict\"|\"Lax\"|\"None\"", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "comment": "If no URLs are specified, this method returns all cookies.\nIf URLs are specified, only cookies that affect those URLs are returned.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "urls": { + "kind": "property", + "name": "urls", + "type": { + "name": "string|Array", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "exposeBinding": { + "kind": "method", + "name": "exposeBinding", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "The method adds a function called `name` on the `window` object of every frame in every page in the context.\nWhen called, the function executes `playwrightBinding` in Node.js and returns a Promise which resolves to the return value of `playwrightBinding`.\nIf the `playwrightBinding` returns a Promise, it will be awaited.\nThe first argument of the `playwrightBinding` function contains information about the caller:\n`{ browserContext: BrowserContext, page: Page, frame: Frame }`.\nSee page.exposeBinding(name, playwrightBinding) for page-only version.\nAn example of exposing page URL to all frames in all pages in the context:\n```js\nconst { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.\n\n(async () => {\n const browser = await webkit.launch({ headless: false });\n const context = await browser.newContext();\n await context.exposeBinding('pageURL', ({ page }) => page.url());\n const page = await context.newPage();\n await page.setContent(`\n \n \n
\n `);\n await page.click('button');\n})();\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "name": { + "kind": "property", + "name": "name", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Name of the function on the window object.", + "returnComment": "", + "required": true, + "templates": [] + }, + "playwrightBinding": { + "kind": "property", + "name": "playwrightBinding", + "type": { + "name": "function", + "properties": {} + }, + "comment": "Callback function that will be called in the Playwright's context.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "exposeFunction": { + "kind": "method", + "name": "exposeFunction", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "The method adds a function called `name` on the `window` object of every frame in every page in the context.\nWhen called, the function executes `playwrightFunction` in Node.js and returns a Promise which resolves to the return value of `playwrightFunction`.\nIf the `playwrightFunction` returns a Promise, it will be awaited.\nSee page.exposeFunction(name, playwrightFunction) for page-only version.\nAn example of adding an `md5` function to all pages in the context:\n```js\nconst { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.\nconst crypto = require('crypto');\n\n(async () => {\n const browser = await webkit.launch({ headless: false });\n const context = await browser.newContext();\n await context.exposeFunction('md5', text => crypto.createHash('md5').update(text).digest('hex'));\n const page = await context.newPage();\n await page.setContent(`\n \n \n
\n `);\n await page.click('button');\n})();\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "name": { + "kind": "property", + "name": "name", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Name of the function on the window object.", + "returnComment": "", + "required": true, + "templates": [] + }, + "playwrightFunction": { + "kind": "property", + "name": "playwrightFunction", + "type": { + "name": "function", + "properties": {} + }, + "comment": "Callback function that will be called in the Playwright's context.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "grantPermissions": { + "kind": "method", + "name": "grantPermissions", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if specified.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "permissions": { + "kind": "property", + "name": "permissions", + "type": { + "name": "Array", + "properties": {} + }, + "comment": "A permission or an array of permissions to grant. Permissions can be one of the following values:\n - `'*'`\n - `'geolocation'`\n - `'midi'`\n - `'midi-sysex'` (system-exclusive midi)\n - `'notifications'`\n - `'push'`\n - `'camera'`\n - `'microphone'`\n - `'background-sync'`\n - `'ambient-light-sensor'`\n - `'accelerometer'`\n - `'gyroscope'`\n - `'magnetometer'`\n - `'accessibility-events'`\n - `'clipboard-read'`\n - `'clipboard-write'`\n - `'payment-handler'`", + "returnComment": "", + "required": true, + "templates": [] + }, + "options": { + "kind": "property", + "name": "options", + "type": { + "name": "Object", + "properties": { + "origin": { + "kind": "property", + "name": "origin", + "type": { + "name": "string", + "properties": {} + }, + "comment": "The origin to grant permissions to, e.g. \"https://example.com\".", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "newPage": { + "kind": "method", + "name": "newPage", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Creates a new page in the browser context.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "pages": { + "kind": "method", + "name": "pages", + "type": { + "name": "Array", + "properties": {} + }, + "comment": "", + "returnComment": "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().", + "required": true, + "templates": [], + "args": {} + }, + "route": { + "kind": "method", + "name": "route", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Routing provides the capability to modify network requests that are made by any page in the browser context.\nOnce route is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.\nAn example of a naïve handler that aborts all image requests:\n```js\nconst context = await browser.newContext();\nawait context.route('**/*.{png,jpg,jpeg}', route => route.abort());\nconst page = await context.newPage();\nawait page.goto('https://example.com');\nawait browser.close();\n```\nor the same snippet using a regex pattern instead:\n```js\nconst context = await browser.newContext();\nawait context.route(/(\\.png$)|(\\.jpg$)/, route => route.abort());\nconst page = await context.newPage();\nawait page.goto('https://example.com');\nawait browser.close();\n```\nPage routes (set up with page.route(url, handler)) take precedence over browser context routes when request matches both handlers.\n\n**NOTE** Enabling routing disables http cache.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "url": { + "kind": "property", + "name": "url", + "type": { + "name": "string|RegExp|function(URL):boolean", + "properties": {} + }, + "comment": "A glob pattern, regex pattern or predicate receiving URL to match while routing.", + "returnComment": "", + "required": true, + "templates": [] + }, + "handler": { + "kind": "property", + "name": "handler", + "type": { + "name": "function(Route, Request)", + "properties": {} + }, + "comment": "handler function to route the request.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "setDefaultNavigationTimeout": { + "kind": "method", + "name": "setDefaultNavigationTimeout", + "type": null, + "comment": "This setting will change the default maximum navigation time for the following methods and related shortcuts:\n\npage.goBack([options])\npage.goForward([options])\npage.goto(url[, options])\npage.reload([options])\npage.setContent(html[, options])\npage.waitForNavigation([options])\n\n\n**NOTE** `page.setDefaultNavigationTimeout` and `page.setDefaultTimeout` take priority over `browserContext.setDefaultNavigationTimeout`.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "timeout": { + "kind": "property", + "name": "timeout", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Maximum navigation time in milliseconds", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "setDefaultTimeout": { + "kind": "method", + "name": "setDefaultTimeout", + "type": null, + "comment": "This setting will change the default maximum time for all the methods accepting `timeout` option.\n\n**NOTE** `page.setDefaultNavigationTimeout`, `page.setDefaultTimeout` and `browserContext.setDefaultNavigationTimeout` take priority over `browserContext.setDefaultTimeout`.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "timeout": { + "kind": "property", + "name": "timeout", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Maximum time in milliseconds", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "setExtraHTTPHeaders": { + "kind": "method", + "name": "setExtraHTTPHeaders", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "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.\n\n**NOTE** `browserContext.setExtraHTTPHeaders` does not guarantee the order of headers in the outgoing requests.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "headers": { + "kind": "property", + "name": "headers", + "type": { + "name": "Object", + "properties": {} + }, + "comment": "An object containing additional HTTP headers to be sent with every request. All header values must be strings.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "setGeolocation": { + "kind": "method", + "name": "setGeolocation", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Sets the context's geolocation. Passing `null` or `undefined` emulates position unavailable.\n```js\nawait browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667});\n```\n\n**NOTE** Consider using browserContext.grantPermissions to grant permissions for the browser context pages to read its geolocation.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "geolocation": { + "kind": "property", + "name": "geolocation", + "type": { + "name": "?Object", + "properties": { + "latitude": { + "kind": "property", + "name": "latitude", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Latitude between -90 and 90. **required**", + "returnComment": "", + "required": true, + "templates": [] + }, + "longitude": { + "kind": "property", + "name": "longitude", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Longitude between -180 and 180. **required**", + "returnComment": "", + "required": true, + "templates": [] + }, + "accuracy": { + "kind": "property", + "name": "accuracy", + "type": { + "name": "number", + "properties": {} + }, + "comment": "Non-negative accuracy value. Defaults to `0`.", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "setHTTPCredentials": { + "kind": "method", + "name": "setHTTPCredentials", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Provide credentials for HTTP authentication.\n\n**NOTE** Browsers may cache credentials that resulted in successful auth. That means passing different credentials after successful authentication or passing `null` to disable authentication is unreliable. Instead, create a separate browser context that will not have previous credentials cached.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "httpCredentials": { + "kind": "property", + "name": "httpCredentials", + "type": { + "name": "?Object", + "properties": { + "username": { + "kind": "property", + "name": "username", + "type": { + "name": "string", + "properties": {} + }, + "comment": "**required**", + "returnComment": "", + "required": true, + "templates": [] + }, + "password": { + "kind": "property", + "name": "password", + "type": { + "name": "string", + "properties": {} + }, + "comment": "**required**", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "setOffline": { + "kind": "method", + "name": "setOffline", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "offline": { + "kind": "property", + "name": "offline", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Whether to emulate network being offline for the browser context.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "unroute": { + "kind": "method", + "name": "unroute", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Removes a route created with browserContext.route(url, handler). When `handler` is not specified, removes all routes for the `url`.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "url": { + "kind": "property", + "name": "url", + "type": { + "name": "string|RegExp|function(URL):boolean", + "properties": {} + }, + "comment": "A glob pattern, regex pattern or predicate receiving URL used to register a routing with browserContext.route(url, handler).", + "returnComment": "", + "required": true, + "templates": [] + }, + "handler": { + "kind": "property", + "name": "handler", + "type": { + "name": "function(Route, Request)", + "properties": {} + }, + "comment": "Handler function used to register a routing with browserContext.route(url, handler).", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "waitForEvent": { + "kind": "method", + "name": "waitForEvent", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Waits for event to fire and passes its value into the predicate function. Resolves when the predicate returns truthy value. Will throw an error if the context closes before the event\nis fired.\n```js\nconst context = await browser.newContext();\nawait context.grantPermissions(['geolocation']);\n```", + "returnComment": "Promise which resolves to the event data value.", + "required": true, + "templates": [], + "args": { + "event": { + "kind": "property", + "name": "event", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Event name, same one would pass into `browserContext.on(event)`.", + "returnComment": "", + "required": true, + "templates": [] + }, + "optionsOrPredicate": { + "kind": "property", + "name": "optionsOrPredicate", + "type": { + "name": "Function|Object", + "properties": { + "predicate": { + "kind": "property", + "name": "predicate", + "type": { + "name": "Function", + "properties": {} + }, + "comment": "receives the event data and resolves to truthy value when the waiting should resolve.", + "returnComment": "", + "required": false, + "templates": [] + }, + "timeout": { + "kind": "property", + "name": "timeout", + "type": { + "name": "number", + "properties": {} + }, + "comment": "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).", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "Either a predicate that receives an event or an options object.", + "returnComment": "", + "required": false, + "templates": [] + } + } + } + } + }, + "Page": { + "name": "Page", + "members": { + "close": { + "kind": "method", + "name": "close", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "By default, `page.close()` **does not** run beforeunload handlers.\n\n**NOTE** if `runBeforeUnload` is passed as true, a `beforeunload` dialog might be summoned\nand should be handled manually via page's 'dialog' event.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "options": { + "kind": "property", + "name": "options", + "type": { + "name": "Object", + "properties": { + "runBeforeUnload": { + "kind": "property", + "name": "runBeforeUnload", + "type": { + "name": "boolean", + "properties": {} + }, + "comment": "Defaults to `false`. Whether to run the\nbefore unload\npage handlers.", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "console": { + "kind": "event", + "name": "console", + "type": { + "name": "ConsoleMessage", + "properties": {} + }, + "comment": "Emitted when JavaScript within the page calls one of console API methods, e.g. `console.log` or `console.dir`. Also emitted if the page throws an error or a warning.\nThe arguments passed into `console.log` appear as arguments on the event handler.\nAn example of handling `console` event:\n```js\npage.on('console', msg => {\n for (let i = 0; i < msg.args().length; ++i)\n console.log(`${i}: ${msg.args()[i]}`);\n});\npage.evaluate(() => console.log('hello', 5, {foo: 'bar'}));\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "crash": { + "kind": "event", + "name": "crash", + "type": null, + "comment": "Emitted when the page crashes. Browser pages might crash if they try to allocate too much memory. When the page crashes, ongoing and subsequent operations will throw.\nThe most common way to deal with crashes is to catch an exception:\n```js\ntry {\n // Crash might happen during a click.\n await page.click('button');\n // Or while waiting for an event.\n await page.waitForEvent('popup');\n} catch (e) {\n // When the page crashes, exception message contains 'crash'.\n}\n```\nHowever, when manually listening to events, it might be useful to avoid stalling when the page crashes. In this case, handling `crash` event helps:\n```js\nawait new Promise((resolve, reject) => {\n page.on('requestfinished', async request => {\n if (await someProcessing(request))\n resolve(request);\n });\n page.on('crash', error => reject(error));\n});\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "dialog": { + "kind": "event", + "name": "dialog", + "type": { + "name": "Dialog", + "properties": {} + }, + "comment": "Emitted when a JavaScript dialog appears, such as `alert`, `prompt`, `confirm` or `beforeunload`. Playwright can respond to the dialog via Dialog's accept or dismiss methods.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "domcontentloaded": { + "kind": "event", + "name": "domcontentloaded", + "type": null, + "comment": "Emitted when the JavaScript `DOMContentLoaded` event is dispatched.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "download": { + "kind": "event", + "name": "download", + "type": { + "name": "Download", + "properties": {} + }, + "comment": "Emitted when attachment download started. User can access basic file operations on downloaded content via the passed Download instance.\n\n**NOTE** Browser context **must** be created with the `acceptDownloads` set to `true` when user needs access to the downloaded content. If `acceptDownloads` is not set or set to `false`, download events are emitted, but the actual download is not performed and user has no access to the downloaded files.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "filechooser": { + "kind": "event", + "name": "filechooser", + "type": { + "name": "FileChooser", + "properties": {} + }, + "comment": "Emitted when a file chooser is supposed to appear, such as after clicking the ``. Playwright can respond to it via setting the input files using `fileChooser.setFiles` that can be uploaded after that.\n```js\npage.on('filechooser', async (fileChooser) => {\n await fileChooser.setFiles('/tmp/myfile.pdf');\n});\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "frameattached": { + "kind": "event", + "name": "frameattached", + "type": { + "name": "Frame", + "properties": {} + }, + "comment": "Emitted when a frame is attached.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "framedetached": { + "kind": "event", + "name": "framedetached", + "type": { + "name": "Frame", + "properties": {} + }, + "comment": "Emitted when a frame is detached.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "framenavigated": { + "kind": "event", + "name": "framenavigated", + "type": { + "name": "Frame", + "properties": {} + }, + "comment": "Emitted when a frame is navigated to a new url.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "load": { + "kind": "event", + "name": "load", + "type": null, + "comment": "Emitted when the JavaScript `load` event is dispatched.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "pageerror": { + "kind": "event", + "name": "pageerror", + "type": { + "name": "Error", + "properties": {} + }, + "comment": "Emitted when an uncaught exception happens within the page.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "popup": { + "kind": "event", + "name": "popup", + "type": { + "name": "Page", + "properties": {} + }, + "comment": "Emitted when the page opens a new tab or window. This event is emitted in addition to the `browserContext.on('page')`, but only for popups relevant to this page.\nThe earliest moment that page is available is when it has navigated to the initial url. For example, when opening a popup with `window.open('http://example.com')`, this event will fire when the network request to \"http://example.com\" is done and its response has started loading in the popup.\n```js\nconst [popup] = await Promise.all([\n page.waitForEvent('popup'),\n page.evaluate(() => window.open('https://example.com')),\n]);\nconsole.log(await popup.evaluate('location.href'));\n```\n\n**NOTE** Use `page.waitForLoadState([state[, options]])` to wait until the page gets to a particular state (you should not need it in most cases).", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "request": { + "kind": "event", + "name": "request", + "type": { + "name": "Request", + "properties": {} + }, + "comment": "Emitted when a page issues a request. The request object is read-only.\nIn order to intercept and mutate requests, see `page.route()` or `browserContext.route()`.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "requestfailed": { + "kind": "event", + "name": "requestfailed", + "type": { + "name": "Request", + "properties": {} + }, + "comment": "Emitted when a request fails, for example by timing out.\n\n**NOTE** HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete with `'requestfinished'` event and not with `'requestfailed'`.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "requestfinished": { + "kind": "event", + "name": "requestfinished", + "type": { + "name": "Request", + "properties": {} + }, + "comment": "Emitted when a request finishes successfully.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "response": { + "kind": "event", + "name": "response", + "type": { + "name": "Response", + "properties": {} + }, + "comment": "Emitted when a response is received.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "worker": { + "kind": "event", + "name": "worker", + "type": { + "name": "Worker", + "properties": {} + }, + "comment": "Emitted when a dedicated WebWorker is spawned by the page.", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "$": { + "kind": "method", + "name": "$", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "The method finds an element matching the specified selector within the page. If no elements match the selector, the return value resolves to `null`.\nShortcut for page.mainFrame().$(selector).", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "selector": { + "kind": "property", + "name": "selector", + "type": { + "name": "string", + "properties": {} + }, + "comment": "A selector to query page for. See working with selectors for more details.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "$$": { + "kind": "method", + "name": "$$", + "type": { + "name": "Promise>", + "properties": {} + }, + "comment": "The method finds all elements matching the specified selector within the page. If no elements match the selector, the return value resolves to `[]`.\nShortcut for page.mainFrame().$$(selector).", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "selector": { + "kind": "property", + "name": "selector", + "type": { + "name": "string", + "properties": {} + }, + "comment": "A selector to query page for. See working with selectors for more details.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "$eval": { + "kind": "method", + "name": "$eval", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "The method finds an element matching the specified selector within the page and passes it as a first argument to `pageFunction`. If no elements match the selector, the method throws an error.\nIf `pageFunction` returns a Promise, then `page.$eval` would wait for the promise to resolve and return its value.\nExamples:\n```js\nconst searchValue = await page.$eval('#search', el => el.value);\nconst preloadHref = await page.$eval('link[rel=preload]', el => el.href);\nconst html = await page.$eval('.main-container', (e, suffix) => e.outerHTML + suffix, 'hello');\n```\nShortcut for page.mainFrame().$eval(selector, pageFunction).", + "returnComment": "Promise which resolves to the return value of `pageFunction`", + "required": true, + "templates": [], + "args": { + "selector": { + "kind": "property", + "name": "selector", + "type": { + "name": "string", + "properties": {} + }, + "comment": "A selector to query page for. See working with selectors for more details.", + "returnComment": "", + "required": true, + "templates": [] + }, + "pageFunction": { + "kind": "property", + "name": "pageFunction", + "type": { + "name": "function(Element)", + "properties": {} + }, + "comment": "Function to be evaluated in browser context", + "returnComment": "", + "required": true, + "templates": [] + }, + "arg": { + "kind": "property", + "name": "arg", + "type": { + "name": "Serializable|JSHandle", + "properties": {} + }, + "comment": "Optional argument to pass to `pageFunction`", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "$$eval": { + "kind": "method", + "name": "$$eval", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "The method finds all elements matching the specified selector within the page and passes an array of matched elements as a first argument to `pageFunction`.\nIf `pageFunction` returns a Promise, then `page.$$eval` would wait for the promise to resolve and return its value.\nExamples:\n```js\nconst divsCounts = await page.$$eval('div', (divs, min) => divs.length >= min, 10);\n```", + "returnComment": "Promise which resolves to the return value of `pageFunction`", + "required": true, + "templates": [], + "args": { + "selector": { + "kind": "property", + "name": "selector", + "type": { + "name": "string", + "properties": {} + }, + "comment": "A selector to query page for. See working with selectors for more details.", + "returnComment": "", + "required": true, + "templates": [] + }, + "pageFunction": { + "kind": "property", + "name": "pageFunction", + "type": { + "name": "function(Array)", + "properties": {} + }, + "comment": "Function to be evaluated in browser context", + "returnComment": "", + "required": true, + "templates": [] + }, + "arg": { + "kind": "property", + "name": "arg", + "type": { + "name": "Serializable|JSHandle", + "properties": {} + }, + "comment": "Optional argument to pass to `pageFunction`", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "accessibility": { + "kind": "property", + "name": "accessibility", + "type": { + "name": "Accessibility", + "properties": {} + }, + "comment": "", + "returnComment": "", + "required": true, + "templates": [], + "args": {} + }, + "addInitScript": { + "kind": "method", + "name": "addInitScript", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Adds a script which would be evaluated in one of the following scenarios:\n\nWhenever the page is navigated.\nWhenever the child frame is attached or navigated. In this case, the scritp is evaluated in the context of the newly attached frame.\n\nThe script is evaluated after the document was created but before any of its scripts were run. This is useful to amend the JavaScript environment, e.g. to seed `Math.random`.\nAn example of overriding `Math.random` before the page loads:\n```js\n// preload.js\nMath.random = () => 42;\n\n// In your playwright script, assuming the preload.js file is in same folder\nconst preloadFile = fs.readFileSync('./preload.js', 'utf8');\nawait page.addInitScript(preloadFile);\n```\n\n**NOTE** The order of evaluation of multiple scripts installed via browserContext.addInitScript(script[, arg]) and page.addInitScript(script[, arg]) is not defined.", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "script": { + "kind": "property", + "name": "script", + "type": { + "name": "function|string|Object", + "properties": { + "path": { + "kind": "property", + "name": "path", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Path to the JavaScript file. If `path` is a relative path, then it is resolved relative to current working directory.", + "returnComment": "", + "required": false, + "templates": [] + }, + "content": { + "kind": "property", + "name": "content", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Raw script content.", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "comment": "Script to be evaluated in the page.", + "returnComment": "", + "required": true, + "templates": [] + }, + "arg": { + "kind": "property", + "name": "arg", + "type": { + "name": "Serializable", + "properties": {} + }, + "comment": "Optional argument to pass to `script` (only supported when passing a function).", + "returnComment": "", + "required": false, + "templates": [] + } + } + }, + "addScriptTag": { + "kind": "method", + "name": "addScriptTag", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "Adds a `\n \n
\n `);\n await page.click('button');\n})();\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "name": { + "kind": "property", + "name": "name", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Name of the function on the window object.", + "returnComment": "", + "required": true, + "templates": [] + }, + "playwrightBinding": { + "kind": "property", + "name": "playwrightBinding", + "type": { + "name": "function", + "properties": {} + }, + "comment": "Callback function that will be called in the Playwright's context.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "exposeFunction": { + "kind": "method", + "name": "exposeFunction", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "The method adds a function called `name` on the `window` object of every frame in the page.\nWhen called, the function executes `playwrightFunction` in Node.js and returns a Promise which resolves to the return value of `playwrightFunction`.\nIf the `playwrightFunction` returns a Promise, it will be awaited.\nSee browserContext.exposeFunction(name, playwrightFunction) for context-wide exposed function.\n\n**NOTE** Functions installed via `page.exposeFunction` survive navigations.\n\nAn example of adding an `md5` function to the page:\n```js\nconst { webkit } = require('playwright'); // Or 'chromium' or 'firefox'.\nconst crypto = require('crypto');\n\n(async () => {\n const browser = await webkit.launch({ headless: false });\n const page = await browser.newPage();\n await page.exposeFunction('md5', text => crypto.createHash('md5').update(text).digest('hex'));\n await page.setContent(`\n \n \n
\n `);\n await page.click('button');\n})();\n```\nAn example of adding a `window.readfile` function to the page:\n```js\nconst { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.\nconst fs = require('fs');\n\n(async () => {\n const browser = await chromium.launch();\n const page = await browser.newPage();\n page.on('console', msg => console.log(msg.text()));\n await page.exposeFunction('readfile', async filePath => {\n return new Promise((resolve, reject) => {\n fs.readFile(filePath, 'utf8', (err, text) => {\n if (err)\n reject(err);\n else\n resolve(text);\n });\n });\n });\n await page.evaluate(async () => {\n // use window.readfile to read contents of a file\n const content = await window.readfile('/etc/hosts');\n console.log(content);\n });\n await browser.close();\n})();\n```", + "returnComment": "", + "required": true, + "templates": [], + "args": { + "name": { + "kind": "property", + "name": "name", + "type": { + "name": "string", + "properties": {} + }, + "comment": "Name of the function on the window object", + "returnComment": "", + "required": true, + "templates": [] + }, + "playwrightFunction": { + "kind": "property", + "name": "playwrightFunction", + "type": { + "name": "function", + "properties": {} + }, + "comment": "Callback function which will be called in Playwright's context.", + "returnComment": "", + "required": true, + "templates": [] + } + } + }, + "fill": { + "kind": "method", + "name": "fill", + "type": { + "name": "Promise", + "properties": {} + }, + "comment": "This method waits for an element matching `selector`, waits for actionability checks, focuses the element, fills it and triggers an `input` event after filling.\nIf the element matching `selector` is not an ``, `