async def wait_for_load_state(self, state: DocumentLoadState = None, timeout: float = None) -> None: if not state: state = "load" if state not in ("load", "domcontentloaded", "networkidle"): raise Error( "state: expected one of (load|domcontentloaded|networkidle)") if state in self._load_states: return wait_helper = self._setup_navigation_wait_helper(timeout) wait_helper.wait_for_event(self._event_emitter, "loadstate", lambda s: s == state) await wait_helper.result()
def expect_event( self, event: str, predicate: Callable = None, timeout: float = None, ) -> EventContextManagerImpl: if timeout is None: timeout = cast(Any, self._parent)._timeout_settings.timeout() wait_helper = WaitHelper(self, f"web_socket.expect_event({event})") wait_helper.reject_on_timeout( cast(float, timeout), f'Timeout {timeout}ms exceeded while waiting for event "{event}"', ) if event != WebSocket.Events.Close: wait_helper.reject_on_event(self, WebSocket.Events.Close, Error("Socket closed")) if event != WebSocket.Events.Error: wait_helper.reject_on_event(self, WebSocket.Events.Error, Error("Socket error")) wait_helper.reject_on_event(self._parent, "close", Error("Page closed")) wait_helper.wait_for_event(self, event, predicate) return EventContextManagerImpl(wait_helper.result())
def _expect_event( self, event: str, predicate: Callable = None, timeout: float = None, log_line: str = None, ) -> EventContextManagerImpl: if timeout is None: timeout = self._timeout_settings.timeout() wait_helper = WaitHelper(self, f"page.expect_event({event})") wait_helper.reject_on_timeout( timeout, f'Timeout {timeout}ms exceeded while waiting for event "{event}"') if log_line: wait_helper.log(log_line) if event != Page.Events.Crash: wait_helper.reject_on_event(self, Page.Events.Crash, Error("Page crashed")) if event != Page.Events.Close: wait_helper.reject_on_event(self, Page.Events.Close, Error("Page closed")) wait_helper.wait_for_event(self, event, predicate) return EventContextManagerImpl(wait_helper.result())
async def waitForEvent( self, event: str, predicate: Callable[[Any], bool] = None, timeout: int = None ) -> Any: if timeout is None: timeout = self._timeout_settings.timeout() wait_helper = WaitHelper(self._loop) wait_helper.reject_on_timeout( timeout, f'Timeout while waiting for event "{event}"' ) if event != BrowserContext.Events.Close: wait_helper.reject_on_event( self, BrowserContext.Events.Close, Error("Context closed") ) return await wait_helper.wait_for_event(self, event, predicate)
def __enter__(self) -> SyncPlaywright: try: self._loop = asyncio.get_running_loop() except RuntimeError: self._loop = asyncio.new_event_loop() self._own_loop = True if self._loop.is_running(): raise Error( """It looks like you are using Playwright Sync API inside the asyncio loop. Please use the Async API instead.""" ) # In Python 3.7, asyncio.Process.wait() hangs because it does not use ThreadedChildWatcher # which is used in Python 3.8+. This is unix specific and also takes care about # cleaning up zombie processes. See https://bugs.python.org/issue35621 if ( sys.version_info[0] == 3 and sys.version_info[1] == 7 and sys.platform != "win32" and isinstance(asyncio.get_child_watcher(), asyncio.SafeChildWatcher) ): from ._py37ThreadedChildWatcher import ThreadedChildWatcher # type: ignore self._watcher = ThreadedChildWatcher() asyncio.set_child_watcher(self._watcher) # type: ignore def greenlet_main() -> None: self._loop.run_until_complete(self._connection.run_as_sync()) dispatcher_fiber = greenlet(greenlet_main) self._connection = Connection( dispatcher_fiber, create_remote_object, PipeTransport(self._loop, compute_driver_executable()), self._loop, ) g_self = greenlet.getcurrent() def callback_wrapper(playwright_impl: Playwright) -> None: self._playwright = SyncPlaywright(playwright_impl) g_self.switch() self._connection.call_on_object_with_known_name("Playwright", callback_wrapper) dispatcher_fiber.switch() playwright = self._playwright playwright.stop = self.__exit__ # type: ignore return playwright
async def register( self, name: str, script: str = None, path: Union[str, Path] = None, contentScript: bool = None, ) -> None: if not script and not path: raise Error("Either source or path should be specified") if path: script = (await async_readfile(path)).decode() params: Dict[str, Any] = dict(name=name, source=script) if contentScript: params["contentScript"] = True await self._channel.send("register", params)
async def run(self) -> None: while not self._stopped: try: message = await self._connection.recv() if self.slow_mo is not None: await asyncio.sleep(self.slow_mo / 1000) if self._stopped: self.on_error_future.set_exception( Error("Playwright connection closed")) break obj = self.deserialize_message(message) self.on_message(obj) except ( websockets.exceptions.ConnectionClosed, websockets.exceptions.ConnectionClosedError, ): if not self._stopped: self.emit("close") self.on_error_future.set_exception( Error("Playwright connection closed")) break except Exception as exc: self.on_error_future.set_exception(exc) break
async def run(self) -> None: await self.start() self._stopped_future: asyncio.Future = asyncio.Future() try: self._proc = proc = await asyncio.create_subprocess_exec( str(self._driver_executable), "run-driver", stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=_get_stderr_fileno(), limit=32768, ) except FileNotFoundError: self.on_error_future.set_exception( Error( "playwright's driver is not found, You can read the contributing guide " "for some guidance on how to get everything setup for working on the code " "https://github.com/microsoft/playwright-python/blob/master/CONTRIBUTING.md" )) return assert proc.stdout assert proc.stdin self._output = proc.stdin while not self._stopped: try: buffer = await proc.stdout.readexactly(4) length = int.from_bytes(buffer, byteorder="little", signed=False) buffer = bytes(0) while length: to_read = min(length, 32768) data = await proc.stdout.readexactly(to_read) length -= to_read if len(buffer): buffer = buffer + data else: buffer = data obj = self.deserialize_message(buffer) self.on_message(obj) except asyncio.IncompleteReadError: break await asyncio.sleep(0) self._stopped_future.set_result(None)
def to_impl(self, obj: Any) -> Any: try: if not obj: return obj if isinstance(obj, dict): return { name: self.to_impl(value) for name, value in obj.items() } if isinstance(obj, list): return [self.to_impl(item) for item in obj] if isinstance(obj, ImplWrapper): return obj._impl_obj return obj except RecursionError: raise Error("Maximum argument depth exceeded")
async def register( self, name: str, source: str = None, path: Union[str, Path] = None, contentScript: bool = None, ) -> None: if not source and not path: raise Error("Either source or path should be specified") if path: with open(path, "r") as file: source = file.read() params: Dict = dict(name=name, source=source) if contentScript: params["contentScript"] = True await self._channel.send("register", params)
def expect_event( self, event: str, predicate: Callable = None, timeout: float = None, ) -> EventContextManagerImpl: if timeout is None: timeout = self._timeout_settings.timeout() wait_helper = WaitHelper(self._loop) wait_helper.reject_on_timeout( timeout, f'Timeout while waiting for event "{event}"') if event != BrowserContext.Events.Close: wait_helper.reject_on_event(self, BrowserContext.Events.Close, Error("Context closed")) wait_helper.wait_for_event(self, event, predicate) return EventContextManagerImpl(wait_helper.result())
def greenlet_main() -> None: loop = None own_loop = None try: loop = asyncio.get_running_loop() except RuntimeError: loop = asyncio.new_event_loop() own_loop = loop if loop.is_running(): raise Error("Can only run one Playwright at a time.") loop.run_until_complete(self._connection.run_as_sync()) if own_loop: loop.run_until_complete(loop.shutdown_asyncgens()) loop.close()
async def connect( self, ws_endpoint: str, timeout: float = None, slow_mo: float = None, headers: Dict[str, str] = None, ) -> Browser: if timeout is None: timeout = 30000 transport = WebSocketTransport(self._connection._loop, ws_endpoint, headers, slow_mo) connection = Connection( self._connection._dispatcher_fiber, self._connection._object_factory, transport, ) connection._is_sync = self._connection._is_sync connection._loop = self._connection._loop connection._loop.create_task(connection.run()) future = connection._loop.create_task( connection.wait_for_object_with_known_name("Playwright")) timeout_future = throw_on_timeout(timeout, Error("Connection timed out")) done, pending = await asyncio.wait( {transport.on_error_future, future, timeout_future}, return_when=asyncio.FIRST_COMPLETED, ) if not future.done(): future.cancel() if not timeout_future.done(): timeout_future.cancel() playwright = next(iter(done)).result() self._connection._child_ws_connections.append(connection) pre_launched_browser = playwright._initializer.get( "preLaunchedBrowser") assert pre_launched_browser browser = cast(Browser, from_channel(pre_launched_browser)) browser._is_remote = True browser._is_connected_over_websocket = True transport.once("close", browser._on_close) return browser
def greenlet_main() -> None: loop = None own_loop = None try: loop = asyncio.get_running_loop() except RuntimeError: loop = asyncio.new_event_loop() own_loop = loop if loop.is_running(): raise Error( """It looks like you are using Playwright Sync API inside the asyncio loop. Please use the Async API instead.""") loop.run_until_complete(self._connection.run_as_sync()) if own_loop: loop.run_until_complete(loop.shutdown_asyncgens()) loop.close()
def __enter__(self) -> SyncPlaywright: loop: asyncio.AbstractEventLoop own_loop = None try: loop = asyncio.get_running_loop() except RuntimeError: loop = asyncio.new_event_loop() own_loop = loop if loop.is_running(): raise Error( """It looks like you are using Playwright Sync API inside the asyncio loop. Please use the Async API instead.""") def greenlet_main() -> None: loop.run_until_complete(self._connection.run_as_sync()) if own_loop: loop.run_until_complete(loop.shutdown_asyncgens()) loop.close() global dispatcher_fiber dispatcher_fiber = greenlet(greenlet_main) self._connection = Connection( dispatcher_fiber, create_remote_object, PipeTransport(loop, compute_driver_executable()), loop, ) g_self = greenlet.getcurrent() def callback_wrapper(playwright_impl: Playwright) -> None: self._playwright = SyncPlaywright(playwright_impl) g_self.switch() self._connection.call_on_object_with_known_name( "Playwright", callback_wrapper) dispatcher_fiber.switch() playwright = self._playwright playwright.stop = self.__exit__ # type: ignore return playwright
def serialize_value(value: Any, handles: List[JSHandle], depth: int) -> Any: if isinstance(value, JSHandle): h = len(handles) handles.append(value._channel) return dict(h=h) if depth > 100: raise Error("Maximum argument depth exceeded") if value is None: return dict(v="undefined") if isinstance(value, float): if value == float("inf"): return dict(v="Infinity") if value == float("-inf"): return dict(v="-Infinity") if value == float("-0"): return dict(v="-0") if math.isnan(value): return dict(v="NaN") if isinstance(value, datetime): return dict(d=value.isoformat() + "Z") if isinstance(value, bool): return {"b": value} if isinstance(value, (int, float)): return {"n": value} if isinstance(value, str): return {"s": value} if isinstance(value, list): result = list( map(lambda a: serialize_value(a, handles, depth + 1), value)) return dict(a=result) if isinstance(value, dict): result = [] # type: ignore for name in value: result.append({ "k": name, "v": serialize_value(value[name], handles, depth + 1) }) return dict(o=result) return dict(v="undefined")
async def wait_for_navigation( self, url: URLMatch = None, waitUntil: DocumentLoadState = None, timeout: float = None, ) -> Optional[Response]: if not waitUntil: waitUntil = "load" if timeout is None: timeout = self._page._timeout_settings.navigation_timeout() deadline = monotonic_time() + timeout wait_helper = self._setup_navigation_wait_helper(timeout) matcher = URLMatcher(url) if url else None def predicate(event: Any) -> bool: # Any failed navigation results in a rejection. if event.get("error"): return True return not matcher or matcher.matches(event["url"]) event = await wait_helper.wait_for_event( self._event_emitter, "navigated", predicate=predicate, ) if "error" in event: raise Error(event["error"]) if waitUntil not in self._load_states: t = deadline - monotonic_time() if t > 0: await self.wait_for_load_state(state=waitUntil, timeout=t) if "newDocument" in event and "request" in event["newDocument"]: request = from_channel(event["newDocument"]["request"]) return await request.response() return None
async def _wait_for_load_state_impl( self, state: DocumentLoadState = None, timeout: float = None ) -> None: if not state: state = "load" if state not in ("load", "domcontentloaded", "networkidle", "commit"): raise Error( "state: expected one of (load|domcontentloaded|networkidle|commit)" ) if state in self._load_states: return wait_helper = self._setup_navigation_wait_helper("wait_for_load_state", timeout) def handle_load_state_event(actual_state: str) -> bool: wait_helper.log(f'"{actual_state}" event fired') return actual_state == state wait_helper.wait_for_event( self._event_emitter, "loadstate", handle_load_state_event, ) await wait_helper.result()
async def new_page(self) -> Page: if self._owner_page: raise Error("Please use browser.new_context()") return from_channel(await self._channel.send("newPage"))
def send(self, message: Dict) -> None: if self._stopped or (hasattr(self, "_connection") and self._connection.closed): raise Error("Playwright connection closed") data = self.serialize_message(message) self._loop.create_task(self._connection.send(data))
async def sizes(self) -> RequestSizes: response = await self.response() if not response: raise Error("Unable to fetch sizes for failed request") return await response._channel.send("sizes")
def _check_not_handled(self) -> None: if not self._handling_future: raise Error("Route is already handled!")