class BrowserContext(ChannelOwner): Events = SimpleNamespace( Close="close", Page="page", ) 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] = {} self._timeout_settings = TimeoutSettings(None) self._browser: Optional["Browser"] = None self._owner_page: Optional[Page] = None self._is_closed_or_closing = False self._options: Dict[str, Any] = {} self._channel.on( "bindingCall", lambda params: self._on_binding(from_channel(params["binding"])), ) self._channel.on("close", lambda _: self._on_close()) self._channel.on( "page", lambda params: self._on_page(from_channel(params["page"])) ) self._channel.on( "route", lambda params: self._on_route( from_channel(params.get("route")), from_channel(params.get("request")) ), ) def _on_page(self, page: Page) -> None: page._set_browser_context(self) self._pages.append(page) self.emit(BrowserContext.Events.Page, page) def _on_route(self, route: Route, request: Request) -> None: for handler_entry in self._routes: if handler_entry.matcher.matches(request.url): result = cast(Any, handler_entry.handler)(route, request) if inspect.iscoroutine(result): asyncio.create_task(result) return asyncio.create_task(route.continue_()) def _on_binding(self, binding_call: BindingCall) -> None: func = self._bindings.get(binding_call._initializer["name"]) if func is None: return asyncio.create_task(binding_call.call(func)) def set_default_navigation_timeout(self, timeout: float) -> None: self._timeout_settings.set_navigation_timeout(timeout) self._channel.send_no_reply( "setDefaultNavigationTimeoutNoReply", dict(timeout=timeout) ) def set_default_timeout(self, timeout: float) -> None: self._timeout_settings.set_timeout(timeout) self._channel.send_no_reply("setDefaultTimeoutNoReply", dict(timeout=timeout)) @property def pages(self) -> List[Page]: return self._pages.copy() @property def browser(self) -> Optional["Browser"]: return self._browser 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")) async def cookies(self, urls: Union[str, List[str]] = None) -> List[Cookie]: if urls is None: urls = [] if not isinstance(urls, list): urls = [urls] return await self._channel.send("cookies", dict(urls=urls)) async def add_cookies(self, cookies: List[Cookie]) -> None: await self._channel.send("addCookies", dict(cookies=cookies)) async def clear_cookies(self) -> None: await self._channel.send("clearCookies") async def grant_permissions( self, permissions: List[str], origin: str = None ) -> None: await self._channel.send("grantPermissions", locals_to_params(locals())) async def clear_permissions(self) -> None: await self._channel.send("clearPermissions") async def set_geolocation(self, geolocation: Geolocation = None) -> None: await self._channel.send("setGeolocation", locals_to_params(locals())) async def set_extra_http_headers(self, headers: Dict[str, str]) -> None: await self._channel.send( "setExtraHTTPHeaders", dict(headers=serialize_headers(headers)) ) async def set_offline(self, offline: bool) -> None: await self._channel.send("setOffline", dict(offline=offline)) async def add_init_script( self, script: str = None, path: Union[str, Path] = None ) -> None: if path: with open(path, "r") as file: script = file.read() if not isinstance(script, str): raise Error("Either path or source parameter must be specified") await self._channel.send("addInitScript", dict(source=script)) async def expose_binding( self, name: str, callback: Callable, handle: bool = None ) -> None: for page in self._pages: if name in page._bindings: raise Error( f'Function "{name}" has been already registered in one of the pages' ) if name in self._bindings: raise Error(f'Function "{name}" has been already registered') self._bindings[name] = callback await self._channel.send( "exposeBinding", dict(name=name, needsHandle=handle or False) ) async def expose_function(self, name: str, callback: Callable) -> None: await self.expose_binding(name, lambda source, *args: callback(*args)) async def route(self, url: URLMatch, handler: RouteHandler) -> None: self._routes.append(RouteHandlerEntry(URLMatcher(url), handler)) if len(self._routes) == 1: await self._channel.send( "setNetworkInterceptionEnabled", dict(enabled=True) ) async def unroute( self, url: URLMatch, handler: Optional[RouteHandler] = None ) -> None: self._routes = list( filter( lambda r: r.matcher.match != url or (handler and r.handler != handler), self._routes, ) ) if len(self._routes) == 0: await self._channel.send( "setNetworkInterceptionEnabled", dict(enabled=False) ) 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, f"browser_context.expect_event({event})") 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 _on_close(self) -> None: self._is_closed_or_closing = True if self._browser: self._browser._contexts.remove(self) self.emit(BrowserContext.Events.Close) async def close(self) -> None: if self._is_closed_or_closing: return self._is_closed_or_closing = True try: await self._channel.send("close") except Exception as e: if not is_safe_close_error(e): raise e async def _pause(self) -> None: await self._channel.send("pause") async def storage_state(self, path: Union[str, Path] = None) -> StorageState: result = await self._channel.send_return_as_dict("storageState") if path: with open(path, "w") as f: json.dump(result, f) return result async def wait_for_event( self, event: str, predicate: Callable = None, timeout: float = None ) -> Any: async with self.expect_event(event, predicate, timeout) as event_info: pass return await event_info def expect_page( self, predicate: Callable[[Page], bool] = None, timeout: float = None, ) -> EventContextManagerImpl[Page]: return self.expect_event(BrowserContext.Events.Page, predicate, timeout)
class Page(ChannelOwner): Events = SimpleNamespace( Close="close", Crash="crash", Console="console", Dialog="dialog", Download="download", FileChooser="filechooser", DOMContentLoaded="domcontentloaded", PageError="pageerror", Request="request", Response="response", RequestFailed="requestfailed", RequestFinished="requestfinished", FrameAttached="frameattached", FrameDetached="framedetached", FrameNavigated="framenavigated", Load="load", Popup="popup", WebSocket="websocket", Worker="worker", ) accessibility: Accessibility keyboard: Keyboard mouse: Mouse touchscreen: Touchscreen 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) self.touchscreen = Touchscreen(self._channel) self._main_frame: Frame = from_channel(initializer["mainFrame"]) self._main_frame._page = self self._frames = [self._main_frame] self._viewport_size: Optional[ViewportSize] = initializer.get( "viewportSize") self._is_closed = False self._workers: List["Worker"] = [] self._bindings: Dict[str, Any] = {} self._routes: List[RouteHandlerEntry] = [] self._owned_context: Optional["BrowserContext"] = None self._timeout_settings: TimeoutSettings = TimeoutSettings(None) self._video: Optional[Video] = None self._opener = cast("Page", from_nullable_channel(initializer.get("opener"))) self._channel.on( "bindingCall", lambda params: self._on_binding(from_channel(params["binding"])), ) self._channel.on("close", lambda _: self._on_close()) self._channel.on( "console", lambda params: self.emit(Page.Events.Console, from_channel(params["message"])), ) self._channel.on("crash", lambda _: self._on_crash()) self._channel.on("dialog", lambda params: self._on_dialog(params)) self._channel.on("domcontentloaded", lambda _: self.emit(Page.Events.DOMContentLoaded)) self._channel.on("download", lambda params: self._on_download(params)) self._channel.on( "fileChooser", lambda params: self.emit( Page.Events.FileChooser, FileChooser(self, from_channel(params["element"]), params[ "isMultiple"]), ), ) self._channel.on( "frameAttached", lambda params: self._on_frame_attached( from_channel(params["frame"])), ) self._channel.on( "frameDetached", lambda params: self._on_frame_detached( from_channel(params["frame"])), ) self._channel.on("load", lambda _: self.emit(Page.Events.Load)) self._channel.on( "pageError", lambda params: self.emit(Page.Events.PageError, parse_error(params["error"]["error"])), ) self._channel.on( "request", lambda params: self.emit(Page.Events.Request, from_channel(params["request"])), ) self._channel.on( "requestFailed", lambda params: self._on_request_failed( from_channel(params["request"]), params["responseEndTiming"], params["failureText"], ), ) self._channel.on( "requestFinished", lambda params: self._on_request_finished( from_channel(params["request"]), params["responseEndTiming"]), ) self._channel.on( "response", lambda params: self.emit(Page.Events.Response, from_channel(params["response"])), ) self._channel.on( "route", lambda params: self._on_route(from_channel(params["route"]), from_channel(params["request"])), ) self._channel.on("video", lambda params: self._on_video(params)) self._channel.on( "webSocket", lambda params: self.emit(Page.Events.WebSocket, from_channel(params["webSocket"])), ) self._channel.on( "worker", lambda params: self._on_worker(from_channel(params["worker"]))) def __repr__(self) -> str: return f"<Page url={self.url!r}>" def _set_browser_context(self, context: "BrowserContext") -> None: self._browser_context = context self._timeout_settings = TimeoutSettings(context._timeout_settings) def _on_request_failed( self, request: Request, response_end_timing: float, failure_text: str = None, ) -> None: request._failure_text = failure_text if request._timing: request._timing["responseEnd"] = response_end_timing self.emit(Page.Events.RequestFailed, request) def _on_request_finished(self, request: Request, response_end_timing: float) -> None: if request._timing: request._timing["responseEnd"] = response_end_timing self.emit(Page.Events.RequestFinished, request) def _on_frame_attached(self, frame: Frame) -> None: frame._page = self self._frames.append(frame) self.emit(Page.Events.FrameAttached, frame) def _on_frame_detached(self, frame: Frame) -> None: self._frames.remove(frame) frame._detached = True self.emit(Page.Events.FrameDetached, frame) def _on_route(self, route: Route, request: Request) -> None: for handler_entry in self._routes: if handler_entry.matcher.matches(request.url): result = cast(Any, handler_entry.handler)(route, request) if inspect.iscoroutine(result): asyncio.create_task(result) return self._browser_context._on_route(route, request) def _on_binding(self, binding_call: "BindingCall") -> None: func = self._bindings.get(binding_call._initializer["name"]) if func: asyncio.create_task(binding_call.call(func)) self._browser_context._on_binding(binding_call) def _on_worker(self, worker: "Worker") -> None: self._workers.append(worker) worker._page = self self.emit(Page.Events.Worker, worker) def _on_close(self) -> None: self._is_closed = True self._browser_context._pages.remove(self) self.emit(Page.Events.Close) def _on_crash(self) -> None: self.emit(Page.Events.Crash) def _on_dialog(self, params: Any) -> None: dialog = from_channel(params["dialog"]) if self.listeners(Page.Events.Dialog): self.emit(Page.Events.Dialog, dialog) else: asyncio.create_task(dialog.dismiss()) def _on_download(self, params: Any) -> None: url = params["url"] suggested_filename = params["suggestedFilename"] artifact = from_channel(params["artifact"]) self.emit(Page.Events.Download, Download(self, url, suggested_filename, artifact)) def _on_video(self, params: Any) -> None: artifact = from_channel(params["artifact"]) cast(Video, self.video)._artifact_ready(artifact) def _add_event_handler(self, event: str, k: Any, v: Any) -> None: if event == Page.Events.FileChooser and len( self.listeners(event)) == 0: self._channel.send_no_reply("setFileChooserInterceptedNoReply", {"intercepted": True}) super()._add_event_handler(event, k, v) def remove_listener(self, event: str, f: Any) -> None: super().remove_listener(event, f) if event == Page.Events.FileChooser and len( self.listeners(event)) == 0: self._channel.send_no_reply("setFileChooserInterceptedNoReply", {"intercepted": False}) @property def context(self) -> "BrowserContext": return self._browser_context async def opener(self) -> Optional["Page"]: if self._opener and self._opener.is_closed(): return None return self._opener @property def main_frame(self) -> Frame: return self._main_frame def frame(self, name: str = None, url: URLMatch = None) -> Optional[Frame]: matcher = URLMatcher(url) if url else None for frame in self._frames: if name and frame.name == name: return frame if url and matcher and matcher.matches(frame.url): return frame return None @property def frames(self) -> List[Frame]: return self._frames.copy() def set_default_navigation_timeout(self, timeout: float) -> None: self._timeout_settings.set_navigation_timeout(timeout) self._channel.send_no_reply("setDefaultNavigationTimeoutNoReply", dict(timeout=timeout)) def set_default_timeout(self, timeout: float) -> None: self._timeout_settings.set_timeout(timeout) self._channel.send_no_reply("setDefaultTimeoutNoReply", dict(timeout=timeout)) async def query_selector(self, selector: str) -> Optional[ElementHandle]: return await self._main_frame.query_selector(selector) async def query_selector_all(self, selector: str) -> List[ElementHandle]: return await self._main_frame.query_selector_all(selector) async def wait_for_selector( self, selector: str, timeout: float = None, state: Literal["attached", "detached", "hidden", "visible"] = None, ) -> Optional[ElementHandle]: return await self._main_frame.wait_for_selector( **locals_to_params(locals())) async def is_checked(self, selector: str, timeout: float = None) -> bool: return await self._main_frame.is_checked(**locals_to_params(locals())) async def is_disabled(self, selector: str, timeout: float = None) -> bool: return await self._main_frame.is_disabled(**locals_to_params(locals())) async def is_editable(self, selector: str, timeout: float = None) -> bool: return await self._main_frame.is_editable(**locals_to_params(locals())) async def is_enabled(self, selector: str, timeout: float = None) -> bool: return await self._main_frame.is_enabled(**locals_to_params(locals())) async def is_hidden(self, selector: str, timeout: float = None) -> bool: return await self._main_frame.is_hidden(**locals_to_params(locals())) async def is_visible(self, selector: str, timeout: float = None) -> bool: return await self._main_frame.is_visible(**locals_to_params(locals())) async def dispatch_event(self, selector: str, type: str, eventInit: Dict = None, timeout: float = None) -> None: return await self._main_frame.dispatch_event( **locals_to_params(locals())) async def evaluate(self, expression: str, arg: Serializable = None) -> Any: return await self._main_frame.evaluate(expression, arg) async def evaluate_handle(self, expression: str, arg: Serializable = None) -> JSHandle: return await self._main_frame.evaluate_handle(expression, arg) async def eval_on_selector( self, selector: str, expression: str, arg: Serializable = None, ) -> Any: return await self._main_frame.eval_on_selector(selector, expression, arg) async def eval_on_selector_all( self, selector: str, expression: str, arg: Serializable = None, ) -> Any: return await self._main_frame.eval_on_selector_all( selector, expression, arg) async def add_script_tag( self, url: str = None, path: Union[str, Path] = None, content: str = None, type: str = None, ) -> ElementHandle: return await self._main_frame.add_script_tag( **locals_to_params(locals())) async def add_style_tag(self, url: str = None, path: Union[str, Path] = None, content: str = None) -> ElementHandle: return await self._main_frame.add_style_tag( **locals_to_params(locals())) async def expose_function(self, name: str, callback: Callable) -> None: await self.expose_binding(name, lambda source, *args: callback(*args)) async def expose_binding(self, name: str, callback: Callable, handle: bool = None) -> None: if name in self._bindings: raise Error(f'Function "{name}" has been already registered') if name in self._browser_context._bindings: raise Error( f'Function "{name}" has been already registered in the browser context' ) self._bindings[name] = callback await self._channel.send("exposeBinding", dict(name=name, needsHandle=handle or False)) async def set_extra_http_headers(self, headers: Dict[str, str]) -> None: await self._channel.send("setExtraHTTPHeaders", dict(headers=serialize_headers(headers))) @property def url(self) -> str: return self._main_frame.url async def content(self) -> str: return await self._main_frame.content() async def set_content( self, html: str, timeout: float = None, waitUntil: DocumentLoadState = None, ) -> None: return await self._main_frame.set_content(**locals_to_params(locals())) async def goto( self, url: str, timeout: float = None, waitUntil: DocumentLoadState = None, referer: str = None, ) -> Optional[Response]: return await self._main_frame.goto(**locals_to_params(locals())) async def reload( self, timeout: float = None, waitUntil: DocumentLoadState = None, ) -> Optional[Response]: return from_nullable_channel(await self._channel.send( "reload", locals_to_params(locals()))) async def wait_for_load_state(self, state: DocumentLoadState = None, timeout: float = None) -> None: return await self._main_frame.wait_for_load_state( **locals_to_params(locals())) async def wait_for_url( self, url: URLMatch, wait_until: DocumentLoadState = None, timeout: float = None, ) -> None: return await self._main_frame.wait_for_url(**locals_to_params(locals()) ) async def wait_for_event(self, event: str, predicate: Callable = None, timeout: float = None) -> Any: async with self.expect_event(event, predicate, timeout) as event_info: pass return await event_info async def go_back( self, timeout: float = None, waitUntil: DocumentLoadState = None, ) -> Optional[Response]: return from_nullable_channel(await self._channel.send( "goBack", locals_to_params(locals()))) async def go_forward( self, timeout: float = None, waitUntil: DocumentLoadState = None, ) -> Optional[Response]: return from_nullable_channel(await self._channel.send( "goForward", locals_to_params(locals()))) async def emulate_media( self, media: Literal["print", "screen"] = None, colorScheme: ColorScheme = None, ) -> None: await self._channel.send("emulateMedia", locals_to_params(locals())) async def set_viewport_size(self, viewportSize: ViewportSize) -> None: self._viewport_size = viewportSize await self._channel.send("setViewportSize", locals_to_params(locals())) @property def viewport_size(self) -> Optional[ViewportSize]: return self._viewport_size async def bring_to_front(self) -> None: await self._channel.send("bringToFront") async def add_init_script(self, script: str = None, path: Union[str, Path] = None) -> None: if path: with open(path, "r") as file: script = file.read() if not isinstance(script, str): raise Error("Either path or script parameter must be specified") await self._channel.send("addInitScript", dict(source=script)) async def route(self, url: URLMatch, handler: RouteHandler) -> None: self._routes.append(RouteHandlerEntry(URLMatcher(url), handler)) if len(self._routes) == 1: await self._channel.send("setNetworkInterceptionEnabled", dict(enabled=True)) async def unroute(self, url: URLMatch, handler: Optional[RouteHandler] = None) -> None: self._routes = list( filter( lambda r: r.matcher.match != url or (handler and r.handler != handler), self._routes, )) if len(self._routes) == 0: await self._channel.send("setNetworkInterceptionEnabled", dict(enabled=False)) async def screenshot( self, timeout: float = None, type: Literal["jpeg", "png"] = None, path: Union[str, Path] = None, quality: int = None, omitBackground: bool = None, fullPage: bool = None, clip: FloatRect = None, ) -> bytes: params = locals_to_params(locals()) if "path" in params: del params["path"] encoded_binary = await self._channel.send("screenshot", params) decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) with open(path, "wb") as fd: fd.write(decoded_binary) return decoded_binary async def title(self) -> str: return await self._main_frame.title() async def close(self, runBeforeUnload: bool = None) -> None: try: await self._channel.send("close", locals_to_params(locals())) if self._owned_context: await self._owned_context.close() except Exception as e: if not is_safe_close_error(e): raise e def is_closed(self) -> bool: return self._is_closed async def click( self, selector: str, modifiers: List[KeyboardModifier] = None, position: Position = None, delay: float = None, button: MouseButton = None, clickCount: int = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, ) -> None: return await self._main_frame.click(**locals_to_params(locals())) async def dblclick( self, selector: str, modifiers: List[KeyboardModifier] = None, position: Position = None, delay: float = None, button: MouseButton = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, ) -> None: return await self._main_frame.dblclick(**locals_to_params(locals())) async def tap( self, selector: str, modifiers: List[KeyboardModifier] = None, position: Position = None, timeout: float = None, force: bool = None, noWaitAfter: bool = None, ) -> None: return await self._main_frame.tap(**locals_to_params(locals())) async def fill(self, selector: str, value: str, timeout: float = None, noWaitAfter: bool = None) -> None: return await self._main_frame.fill(**locals_to_params(locals())) async def focus(self, selector: str, timeout: float = None) -> None: return await self._main_frame.focus(**locals_to_params(locals())) async def text_content(self, selector: str, timeout: float = None) -> Optional[str]: return await self._main_frame.text_content(**locals_to_params(locals()) ) async def inner_text(self, selector: str, timeout: float = None) -> str: return await self._main_frame.inner_text(**locals_to_params(locals())) async def inner_html(self, selector: str, timeout: float = None) -> str: return await self._main_frame.inner_html(**locals_to_params(locals())) async def get_attribute(self, selector: str, name: str, timeout: float = None) -> Optional[str]: return await self._main_frame.get_attribute( **locals_to_params(locals())) async def hover( self, selector: str, modifiers: List[KeyboardModifier] = None, position: Position = None, timeout: float = None, force: bool = None, ) -> None: return await self._main_frame.hover(**locals_to_params(locals())) async def select_option( self, selector: str, value: Union[str, List[str]] = None, index: Union[int, List[int]] = None, label: Union[str, List[str]] = None, element: Union["ElementHandle", List["ElementHandle"]] = None, timeout: float = None, noWaitAfter: bool = None, ) -> List[str]: params = locals_to_params(locals()) return await self._main_frame.select_option(**params) async def set_input_files( self, selector: str, files: Union[str, Path, FilePayload, List[Union[str, Path]], List[FilePayload]], timeout: float = None, noWaitAfter: bool = None, ) -> None: return await self._main_frame.set_input_files( **locals_to_params(locals())) async def type( self, selector: str, text: str, delay: float = None, timeout: float = None, noWaitAfter: bool = None, ) -> None: return await self._main_frame.type(**locals_to_params(locals())) async def press( self, selector: str, key: str, delay: float = None, timeout: float = None, noWaitAfter: bool = None, ) -> None: return await self._main_frame.press(**locals_to_params(locals())) async def check( self, selector: str, timeout: float = None, force: bool = None, noWaitAfter: bool = None, ) -> None: return await self._main_frame.check(**locals_to_params(locals())) async def uncheck( self, selector: str, timeout: float = None, force: bool = None, noWaitAfter: bool = None, ) -> None: return await self._main_frame.uncheck(**locals_to_params(locals())) async def wait_for_timeout(self, timeout: float) -> None: await self._main_frame.wait_for_timeout(timeout) async def wait_for_function( self, expression: str, arg: Serializable = None, timeout: float = None, polling: Union[float, Literal["raf"]] = None, ) -> JSHandle: return await self._main_frame.wait_for_function( **locals_to_params(locals())) @property def workers(self) -> List["Worker"]: return self._workers.copy() async def pause(self) -> None: await self._browser_context._pause() async def pdf( self, scale: float = None, displayHeaderFooter: bool = None, headerTemplate: str = None, footerTemplate: str = None, printBackground: bool = None, landscape: bool = None, pageRanges: str = None, format: str = None, width: Union[str, float] = None, height: Union[str, float] = None, preferCSSPageSize: bool = None, margin: PdfMargins = None, path: Union[str, Path] = None, ) -> bytes: params = locals_to_params(locals()) if "path" in params: del params["path"] encoded_binary = await self._channel.send("pdf", params) decoded_binary = base64.b64decode(encoded_binary) if path: make_dirs_for_file(path) with open(path, "wb") as fd: fd.write(decoded_binary) return decoded_binary @property def video(self, ) -> Optional[Video]: if "recordVideo" not in self._browser_context._options: return None if not self._video: self._video = Video(self) return self._video 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, f"page.expect_event({event})") wait_helper.reject_on_timeout( timeout, f'Timeout while waiting for event "{event}"') 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()) def expect_console_message( self, predicate: Callable[[ConsoleMessage], bool] = None, timeout: float = None, ) -> EventContextManagerImpl[ConsoleMessage]: return self.expect_event(Page.Events.Console, predicate, timeout) def expect_download( self, predicate: Callable[[Download], bool] = None, timeout: float = None, ) -> EventContextManagerImpl[Download]: return self.expect_event(Page.Events.Download, predicate, timeout) def expect_file_chooser( self, predicate: Callable[[FileChooser], bool] = None, timeout: float = None, ) -> EventContextManagerImpl[FileChooser]: return self.expect_event(Page.Events.FileChooser, predicate, timeout) def expect_navigation( self, url: URLMatch = None, wait_until: DocumentLoadState = None, timeout: float = None, ) -> EventContextManagerImpl[Response]: return self.main_frame.expect_navigation(url, wait_until, timeout) def expect_popup( self, predicate: Callable[["Page"], bool] = None, timeout: float = None, ) -> EventContextManagerImpl["Page"]: return self.expect_event(Page.Events.Popup, predicate, timeout) def expect_request( self, url_or_predicate: URLMatchRequest, timeout: float = None, ) -> EventContextManagerImpl[Request]: matcher = None if callable(url_or_predicate) else URLMatcher( url_or_predicate) predicate = url_or_predicate if callable(url_or_predicate) else None def my_predicate(request: Request) -> bool: if matcher: return matcher.matches(request.url) if predicate: return url_or_predicate(request) return True return self.expect_event(Page.Events.Request, predicate=my_predicate, timeout=timeout) def expect_response( self, url_or_predicate: URLMatchResponse, timeout: float = None, ) -> EventContextManagerImpl[Response]: matcher = None if callable(url_or_predicate) else URLMatcher( url_or_predicate) predicate = url_or_predicate if callable(url_or_predicate) else None def my_predicate(response: Response) -> bool: if matcher: return matcher.matches(response.url) if predicate: return predicate(response) return True return self.expect_event(Page.Events.Response, predicate=my_predicate, timeout=timeout) def expect_worker( self, predicate: Callable[["Worker"], bool] = None, timeout: float = None, ) -> EventContextManagerImpl["Worker"]: return self.expect_event("worker", predicate, timeout)
class BrowserContext(ChannelOwner): Events = SimpleNamespace( BackgroundPage="backgroundpage", Close="close", Page="page", ServiceWorker="serviceworker", Request="request", Response="response", RequestFailed="requestfailed", RequestFinished="requestfinished", ) 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[RouteHandler] = [] self._bindings: Dict[str, Any] = {} self._timeout_settings = TimeoutSettings(None) self._browser: Optional["Browser"] = None self._owner_page: Optional[Page] = None self._options: Dict[str, Any] = {} self._background_pages: Set[Page] = set() self._service_workers: Set[Worker] = set() self._tracing = cast(Tracing, from_channel(initializer["tracing"])) self._har_recorders: Dict[str, HarRecordingMetadata] = {} self._request: APIRequestContext = from_channel( initializer["requestContext"]) self._channel.on( "bindingCall", lambda params: self._on_binding(from_channel(params["binding"])), ) self._channel.on("close", lambda _: self._on_close()) self._channel.on( "page", lambda params: self._on_page(from_channel(params["page"]))) self._channel.on( "route", lambda params: asyncio.create_task( self._on_route( from_channel(params.get("route")), from_channel(params.get("request")), )), ) self._channel.on( "backgroundPage", lambda params: self._on_background_page( from_channel(params["page"])), ) self._channel.on( "serviceWorker", lambda params: self._on_service_worker( from_channel(params["worker"])), ) self._channel.on( "request", lambda params: self._on_request( from_channel(params["request"]), from_nullable_channel(params.get("page")), ), ) self._channel.on( "response", lambda params: self._on_response( from_channel(params["response"]), from_nullable_channel(params.get("page")), ), ) self._channel.on( "requestFailed", lambda params: self._on_request_failed( from_channel(params["request"]), params["responseEndTiming"], params.get("failureText"), from_nullable_channel(params.get("page")), ), ) self._channel.on( "requestFinished", lambda params: self._on_request_finished( from_channel(params["request"]), from_nullable_channel(params.get("response")), params["responseEndTiming"], from_nullable_channel(params.get("page")), ), ) self._closed_future: asyncio.Future = asyncio.Future() self.once(self.Events.Close, lambda context: self._closed_future.set_result(True)) def __repr__(self) -> str: return f"<BrowserContext browser={self.browser}>" def _on_page(self, page: Page) -> None: self._pages.append(page) self.emit(BrowserContext.Events.Page, page) if page._opener and not page._opener.is_closed(): page._opener.emit(Page.Events.Popup, page) async def _on_route(self, route: Route, request: Request) -> None: route_handlers = self._routes.copy() for route_handler in route_handlers: if not route_handler.matches(request.url): continue if route_handler.will_expire: self._routes.remove(route_handler) try: handled = await route_handler.handle(route, request) finally: if len(self._routes) == 0: asyncio.create_task(self._disable_interception()) if handled: return await route._internal_continue(is_internal=True) def _on_binding(self, binding_call: BindingCall) -> None: func = self._bindings.get(binding_call._initializer["name"]) if func is None: return asyncio.create_task(binding_call.call(func)) def set_default_navigation_timeout(self, timeout: float) -> None: self._timeout_settings.set_navigation_timeout(timeout) self._channel.send_no_reply("setDefaultNavigationTimeoutNoReply", dict(timeout=timeout)) def set_default_timeout(self, timeout: float) -> None: self._timeout_settings.set_timeout(timeout) self._channel.send_no_reply("setDefaultTimeoutNoReply", dict(timeout=timeout)) @property def pages(self) -> List[Page]: return self._pages.copy() @property def browser(self) -> Optional["Browser"]: return self._browser def _set_browser_type(self, browser_type: "BrowserType") -> None: self._browser_type = browser_type if self._options.get("recordHar"): self._har_recorders[""] = { "path": self._options["recordHar"]["path"], "content": self._options["recordHar"].get("content"), } 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")) async def cookies(self, urls: Union[str, List[str]] = None) -> List[Cookie]: if urls is None: urls = [] if not isinstance(urls, list): urls = [urls] return await self._channel.send("cookies", dict(urls=urls)) async def add_cookies(self, cookies: List[SetCookieParam]) -> None: await self._channel.send("addCookies", dict(cookies=cookies)) async def clear_cookies(self) -> None: await self._channel.send("clearCookies") async def grant_permissions(self, permissions: List[str], origin: str = None) -> None: await self._channel.send("grantPermissions", locals_to_params(locals())) async def clear_permissions(self) -> None: await self._channel.send("clearPermissions") async def set_geolocation(self, geolocation: Geolocation = None) -> None: await self._channel.send("setGeolocation", locals_to_params(locals())) async def set_extra_http_headers(self, headers: Dict[str, str]) -> None: await self._channel.send("setExtraHTTPHeaders", dict(headers=serialize_headers(headers))) async def set_offline(self, offline: bool) -> None: await self._channel.send("setOffline", dict(offline=offline)) async def add_init_script(self, script: str = None, path: Union[str, Path] = None) -> None: if path: script = (await async_readfile(path)).decode() if not isinstance(script, str): raise Error("Either path or script parameter must be specified") await self._channel.send("addInitScript", dict(source=script)) async def expose_binding(self, name: str, callback: Callable, handle: bool = None) -> None: for page in self._pages: if name in page._bindings: raise Error( f'Function "{name}" has been already registered in one of the pages' ) if name in self._bindings: raise Error(f'Function "{name}" has been already registered') self._bindings[name] = callback await self._channel.send("exposeBinding", dict(name=name, needsHandle=handle or False)) async def expose_function(self, name: str, callback: Callable) -> None: await self.expose_binding(name, lambda source, *args: callback(*args)) async def route(self, url: URLMatch, handler: RouteHandlerCallback, times: int = None) -> None: self._routes.insert( 0, RouteHandler( URLMatcher(self._options.get("baseURL"), url), handler, True if self._dispatcher_fiber else False, times, ), ) if len(self._routes) == 1: await self._channel.send("setNetworkInterceptionEnabled", dict(enabled=True)) async def unroute(self, url: URLMatch, handler: Optional[RouteHandlerCallback] = None) -> None: self._routes = list( filter( lambda r: r.matcher.match != url or (handler and r.handler != handler), self._routes, )) if len(self._routes) == 0: await self._disable_interception() async def _record_into_har( self, har: Union[Path, str], page: Optional[Page] = None, url: Union[Pattern[str], str] = None, ) -> None: params = { "options": prepare_record_har_options({ "recordHarPath": har, "recordHarContent": "attach", "recordHarMode": "minimal", "recordHarUrlFilter": url, }) } if page: params["page"] = page._channel har_id = await self._channel.send("harStart", params) self._har_recorders[har_id] = {"path": str(har), "content": "attach"} async def route_from_har( self, har: Union[Path, str], url: Union[Pattern[str], str] = None, not_found: RouteFromHarNotFoundPolicy = None, update: bool = None, ) -> None: if update: await self._record_into_har(har=har, page=None, url=url) return router = await HarRouter.create( local_utils=self._connection.local_utils, file=str(har), not_found_action=not_found or "abort", url_matcher=url, ) await router.add_context_route(self) async def _disable_interception(self) -> None: await self._channel.send("setNetworkInterceptionEnabled", dict(enabled=False)) 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, f"browser_context.expect_event({event})") wait_helper.reject_on_timeout( timeout, f'Timeout {timeout}ms exceeded 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 _on_close(self) -> None: if self._browser: self._browser._contexts.remove(self) self.emit(BrowserContext.Events.Close, self) async def close(self) -> None: try: for har_id, params in self._har_recorders.items(): har = cast( Artifact, from_channel(await self._channel.send("harExport", {"harId": har_id})), ) # Server side will compress artifact if content is attach or if file is .zip. is_compressed = params.get( "content") == "attach" or params["path"].endswith(".zip") need_compressed = params["path"].endswith(".zip") if is_compressed and not need_compressed: tmp_path = params["path"] + ".tmp" await har.save_as(tmp_path) await self._connection.local_utils.har_unzip( zipFile=tmp_path, harFile=params["path"]) else: await har.save_as(params["path"]) await har.delete() await self._channel.send("close") await self._closed_future except Exception as e: if not is_safe_close_error(e): raise e async def _pause(self) -> None: await self._channel.send("pause") async def storage_state(self, path: Union[str, Path] = None) -> StorageState: result = await self._channel.send_return_as_dict("storageState") if path: await async_writefile(path, json.dumps(result)) return result async def wait_for_event(self, event: str, predicate: Callable = None, timeout: float = None) -> Any: async with self.expect_event(event, predicate, timeout) as event_info: pass return await event_info def expect_page( self, predicate: Callable[[Page], bool] = None, timeout: float = None, ) -> EventContextManagerImpl[Page]: return self.expect_event(BrowserContext.Events.Page, predicate, timeout) def _on_background_page(self, page: Page) -> None: self._background_pages.add(page) self.emit(BrowserContext.Events.BackgroundPage, page) def _on_service_worker(self, worker: Worker) -> None: worker._context = self self._service_workers.add(worker) self.emit(BrowserContext.Events.ServiceWorker, worker) def _on_request_failed( self, request: Request, response_end_timing: float, failure_text: Optional[str], page: Optional[Page], ) -> None: request._failure_text = failure_text if request._timing: request._timing["responseEnd"] = response_end_timing self.emit(BrowserContext.Events.RequestFailed, request) if page: page.emit(Page.Events.RequestFailed, request) def _on_request_finished( self, request: Request, response: Optional[Response], response_end_timing: float, page: Optional[Page], ) -> None: if request._timing: request._timing["responseEnd"] = response_end_timing self.emit(BrowserContext.Events.RequestFinished, request) if page: page.emit(Page.Events.RequestFinished, request) if response: response._finished_future.set_result(True) def _on_request(self, request: Request, page: Optional[Page]) -> None: self.emit(BrowserContext.Events.Request, request) if page: page.emit(Page.Events.Request, request) def _on_response(self, response: Response, page: Optional[Page]) -> None: self.emit(BrowserContext.Events.Response, response) if page: page.emit(Page.Events.Response, response) @property def background_pages(self) -> List[Page]: return list(self._background_pages) @property def service_workers(self) -> List[Worker]: return list(self._service_workers) async def new_cdp_session(self, page: Union[Page, Frame]) -> CDPSession: page = to_impl(page) params = {} if isinstance(page, Page): params["page"] = page._channel elif isinstance(page, Frame): params["frame"] = page._channel else: raise Error("page: expected Page or Frame") return from_channel(await self._channel.send("newCDPSession", params)) @property def tracing(self) -> Tracing: return self._tracing @property def request(self) -> "APIRequestContext": return self._request