pure-patchright 1.62.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. patchright/__init__.py +5 -0
  2. patchright/__main__.py +18 -0
  3. patchright/_impl/__init__.py +0 -0
  4. patchright/_impl/__pyinstaller/__init__.py +20 -0
  5. patchright/_impl/__pyinstaller/hook-playwright.async_api.py +3 -0
  6. patchright/_impl/__pyinstaller/hook-playwright.sync_api.py +3 -0
  7. patchright/_impl/_api_structures.py +343 -0
  8. patchright/_impl/_artifact.py +46 -0
  9. patchright/_impl/_assertions.py +1014 -0
  10. patchright/_impl/_async_base.py +88 -0
  11. patchright/_impl/_browser.py +267 -0
  12. patchright/_impl/_browser_context.py +759 -0
  13. patchright/_impl/_browser_type.py +336 -0
  14. patchright/_impl/_cdp_session.py +25 -0
  15. patchright/_impl/_clock.py +62 -0
  16. patchright/_impl/_connection.py +686 -0
  17. patchright/_impl/_console_message.py +83 -0
  18. patchright/_impl/_credentials.py +38 -0
  19. patchright/_impl/_debugger.py +37 -0
  20. patchright/_impl/_dialog.py +45 -0
  21. patchright/_impl/_disposable.py +71 -0
  22. patchright/_impl/_download.py +49 -0
  23. patchright/_impl/_driver.py +24 -0
  24. patchright/_impl/_element_handle.py +446 -0
  25. patchright/_impl/_errors.py +46 -0
  26. patchright/_impl/_event_context_manager.py +19 -0
  27. patchright/_impl/_fetch.py +592 -0
  28. patchright/_impl/_file_chooser.py +42 -0
  29. patchright/_impl/_form_data.py +19 -0
  30. patchright/_impl/_frame.py +959 -0
  31. patchright/_impl/_glob.py +47 -0
  32. patchright/_impl/_greenlets.py +35 -0
  33. patchright/_impl/_har_router.py +98 -0
  34. patchright/_impl/_helper.py +551 -0
  35. patchright/_impl/_impl_to_api_mapping.py +141 -0
  36. patchright/_impl/_input.py +85 -0
  37. patchright/_impl/_js_handle.py +297 -0
  38. patchright/_impl/_json_pipe.py +55 -0
  39. patchright/_impl/_local_utils.py +73 -0
  40. patchright/_impl/_locator.py +894 -0
  41. patchright/_impl/_map.py +18 -0
  42. patchright/_impl/_network.py +953 -0
  43. patchright/_impl/_object_factory.py +93 -0
  44. patchright/_impl/_page.py +1601 -0
  45. patchright/_impl/_path_utils.py +13 -0
  46. patchright/_impl/_playwright.py +43 -0
  47. patchright/_impl/_screencast.py +149 -0
  48. patchright/_impl/_selectors.py +48 -0
  49. patchright/_impl/_set_input_files_helpers.py +134 -0
  50. patchright/_impl/_str_utils.py +59 -0
  51. patchright/_impl/_stream.py +31 -0
  52. patchright/_impl/_sync_base.py +130 -0
  53. patchright/_impl/_tracing.py +243 -0
  54. patchright/_impl/_transport.py +151 -0
  55. patchright/_impl/_video.py +41 -0
  56. patchright/_impl/_waiter.py +168 -0
  57. patchright/_impl/_web_error.py +33 -0
  58. patchright/_impl/_web_storage.py +41 -0
  59. patchright/_impl/_writable_stream.py +26 -0
  60. patchright/_repo_version.py +1 -0
  61. patchright/async_api/__init__.py +234 -0
  62. patchright/async_api/_context_manager.py +38 -0
  63. patchright/async_api/_generated.py +23396 -0
  64. patchright/driver/README.md +1 -0
  65. patchright/driver/package/README.md +3 -0
  66. patchright/driver/package/ThirdPartyNotices.txt +13 -0
  67. patchright/driver/package/bin/install_media_pack.ps1 +5 -0
  68. patchright/driver/package/bin/install_webkit_wsl.ps1 +33 -0
  69. patchright/driver/package/bin/reinstall_chrome_beta_linux.sh +42 -0
  70. patchright/driver/package/bin/reinstall_chrome_beta_mac.sh +13 -0
  71. patchright/driver/package/bin/reinstall_chrome_beta_win.ps1 +24 -0
  72. patchright/driver/package/bin/reinstall_chrome_stable_linux.sh +42 -0
  73. patchright/driver/package/bin/reinstall_chrome_stable_mac.sh +12 -0
  74. patchright/driver/package/bin/reinstall_chrome_stable_win.ps1 +24 -0
  75. patchright/driver/package/bin/reinstall_msedge_beta_linux.sh +48 -0
  76. patchright/driver/package/bin/reinstall_msedge_beta_mac.sh +11 -0
  77. patchright/driver/package/bin/reinstall_msedge_beta_win.ps1 +23 -0
  78. patchright/driver/package/bin/reinstall_msedge_dev_linux.sh +48 -0
  79. patchright/driver/package/bin/reinstall_msedge_dev_mac.sh +11 -0
  80. patchright/driver/package/bin/reinstall_msedge_dev_win.ps1 +23 -0
  81. patchright/driver/package/bin/reinstall_msedge_stable_linux.sh +48 -0
  82. patchright/driver/package/bin/reinstall_msedge_stable_mac.sh +11 -0
  83. patchright/driver/package/bin/reinstall_msedge_stable_win.ps1 +24 -0
  84. patchright/driver/package/browsers.json +75 -0
  85. patchright/driver/package/cli.js +21 -0
  86. patchright/driver/package/index.js +17 -0
  87. patchright/driver/package/index.mjs +28 -0
  88. patchright/driver/package/lib/bootstrap.js +88 -0
  89. patchright/driver/package/lib/coreBundle.js +77498 -0
  90. patchright/driver/package/lib/entry/cliDaemon.js +5 -0
  91. patchright/driver/package/lib/entry/dashboardApp.js +3 -0
  92. patchright/driver/package/lib/entry/mcp.js +10 -0
  93. patchright/driver/package/lib/entry/oopBrowserDownload.js +3 -0
  94. patchright/driver/package/lib/package.js +50 -0
  95. patchright/driver/package/lib/server/chromium/appIcon.png +0 -0
  96. patchright/driver/package/lib/server/electron/loader.js +107 -0
  97. patchright/driver/package/lib/serverRegistry.js +7347 -0
  98. patchright/driver/package/lib/serverRegistry.js.LICENSE +354 -0
  99. patchright/driver/package/lib/tools/cli-client/channelSessions.js +141 -0
  100. patchright/driver/package/lib/tools/cli-client/cli.js +6 -0
  101. patchright/driver/package/lib/tools/cli-client/help.json +708 -0
  102. patchright/driver/package/lib/tools/cli-client/minimist.js +128 -0
  103. patchright/driver/package/lib/tools/cli-client/output.js +343 -0
  104. patchright/driver/package/lib/tools/cli-client/program.js +404 -0
  105. patchright/driver/package/lib/tools/cli-client/registry.js +176 -0
  106. patchright/driver/package/lib/tools/cli-client/session.js +258 -0
  107. patchright/driver/package/lib/tools/dashboard/appIcon.png +0 -0
  108. patchright/driver/package/lib/tools/skills/playwright-cli/SKILL.md +420 -0
  109. patchright/driver/package/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
  110. patchright/driver/package/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
  111. patchright/driver/package/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
  112. patchright/driver/package/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
  113. patchright/driver/package/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
  114. patchright/driver/package/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
  115. patchright/driver/package/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
  116. patchright/driver/package/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
  117. patchright/driver/package/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
  118. patchright/driver/package/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  119. patchright/driver/package/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  120. patchright/driver/package/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  121. patchright/driver/package/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  122. patchright/driver/package/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  123. patchright/driver/package/lib/tools/skills/playwright-trace/SKILL.md +171 -0
  124. patchright/driver/package/lib/tools/utils/extension.js +101 -0
  125. patchright/driver/package/lib/tools/utils/socketConnection.js +108 -0
  126. patchright/driver/package/lib/utilsBundle.js +90764 -0
  127. patchright/driver/package/lib/utilsBundle.js.LICENSE +2179 -0
  128. patchright/driver/package/lib/webp_codec.LICENSE +173 -0
  129. patchright/driver/package/lib/webp_codec.wasm +0 -0
  130. patchright/driver/package/lib/xdg-open +1267 -0
  131. patchright/driver/package/package.json +34 -0
  132. patchright/py.typed +0 -0
  133. patchright/sync_api/__init__.py +234 -0
  134. patchright/sync_api/_context_manager.py +73 -0
  135. patchright/sync_api/_generated.py +23590 -0
  136. pure_patchright-1.62.3.dist-info/LICENSE +201 -0
  137. pure_patchright-1.62.3.dist-info/METADATA +96 -0
  138. pure_patchright-1.62.3.dist-info/RECORD +142 -0
  139. pure_patchright-1.62.3.dist-info/WHEEL +4 -0
  140. pure_patchright-1.62.3.dist-info/entry_points.txt +5 -0
  141. pure_patchright-1.62.3.dist-info/licenses/LICENSE +202 -0
  142. pure_patchright-1.62.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,759 @@
1
+ import asyncio
2
+ import json
3
+ from pathlib import Path
4
+ from types import SimpleNamespace
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ Callable,
9
+ Dict,
10
+ List,
11
+ Literal,
12
+ Optional,
13
+ Pattern,
14
+ Sequence,
15
+ Set,
16
+ Union,
17
+ cast,
18
+ )
19
+ from patchright._impl._api_structures import (
20
+ Cookie,
21
+ Geolocation,
22
+ SetCookieParam,
23
+ StorageState,
24
+ WebErrorLocation,
25
+ )
26
+ from patchright._impl._cdp_session import CDPSession
27
+ from patchright._impl._clock import Clock
28
+ from patchright._impl._connection import (
29
+ ChannelOwner,
30
+ from_channel,
31
+ from_nullable_channel,
32
+ )
33
+ from patchright._impl._console_message import ConsoleMessage
34
+ from patchright._impl._credentials import Credentials
35
+ from patchright._impl._debugger import Debugger
36
+ from patchright._impl._dialog import Dialog
37
+ from patchright._impl._disposable import Disposable, DisposableStub
38
+ from patchright._impl._errors import Error, TargetClosedError
39
+ from patchright._impl._event_context_manager import EventContextManagerImpl
40
+ from patchright._impl._fetch import APIRequestContext
41
+ from patchright._impl._frame import Frame
42
+ from patchright._impl._har_router import HarRouter
43
+ from patchright._impl._helper import (
44
+ HarContentPolicy,
45
+ HarMode,
46
+ RouteFromHarNotFoundPolicy,
47
+ RouteHandler,
48
+ RouteHandlerCallback,
49
+ TimeoutSettings,
50
+ URLMatch,
51
+ WebSocketRouteHandlerCallback,
52
+ async_readfile,
53
+ async_writefile,
54
+ create_task_and_ignore_exception,
55
+ locals_to_params,
56
+ parse_error,
57
+ to_impl,
58
+ )
59
+ from patchright._impl._network import (
60
+ Request,
61
+ Response,
62
+ Route,
63
+ WebSocketRoute,
64
+ WebSocketRouteHandler,
65
+ serialize_headers,
66
+ )
67
+ from patchright._impl._page import BindingCall, Page, Worker
68
+ from patchright._impl._str_utils import escape_regex_flags
69
+ from patchright._impl._tracing import Tracing
70
+ from patchright._impl._waiter import Waiter
71
+ from patchright._impl._web_error import WebError
72
+
73
+ if TYPE_CHECKING:
74
+ from patchright._impl._browser import Browser
75
+
76
+
77
+ class BrowserContext(ChannelOwner):
78
+ Events = SimpleNamespace(
79
+ BackgroundPage="backgroundpage",
80
+ Close="close",
81
+ Console="console",
82
+ Dialog="dialog",
83
+ Download="download",
84
+ FrameAttached="frameattached",
85
+ FrameDetached="framedetached",
86
+ FrameNavigated="framenavigated",
87
+ Page="page",
88
+ PageClose="pageclose",
89
+ PageLoad="pageload",
90
+ WebError="weberror",
91
+ ServiceWorker="serviceworker",
92
+ Request="request",
93
+ Response="response",
94
+ RequestFailed="requestfailed",
95
+ RequestFinished="requestfinished",
96
+ )
97
+
98
+ def __init__(
99
+ self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
100
+ ) -> None:
101
+ super().__init__(parent, type, guid, initializer)
102
+ self._browser: Optional["Browser"] = None
103
+ if parent.__class__.__name__ == "Browser":
104
+ self._browser = cast("Browser", parent)
105
+ self._pages: List[Page] = []
106
+ self._routes: List[RouteHandler] = []
107
+ self._web_socket_routes: List[WebSocketRouteHandler] = []
108
+ self._bindings: Dict[str, Any] = {}
109
+ self._timeout_settings = TimeoutSettings(None)
110
+ self._owner_page: Optional[Page] = None
111
+ self._options: Dict[str, Any] = initializer["options"]
112
+ self._service_workers: Set[Worker] = set()
113
+ self._base_url: Optional[str] = self._options.get("baseURL")
114
+ self._videos_dir: Optional[str] = self._options.get("recordVideo")
115
+ self._tracing = cast(Tracing, from_channel(initializer["tracing"]))
116
+ self._debugger: Debugger = cast(Debugger, from_channel(initializer["debugger"]))
117
+ self._request: APIRequestContext = from_channel(initializer["requestContext"])
118
+ self._request._timeout_settings = self._timeout_settings
119
+ self._clock = Clock(self)
120
+ self._credentials = Credentials(self)
121
+ self._channel.on(
122
+ "bindingCall",
123
+ lambda params: self._on_binding(from_channel(params["binding"])),
124
+ )
125
+ self._channel.on("close", lambda _: self._on_close())
126
+ self._channel.on(
127
+ "page", lambda params: self._on_page(from_channel(params["page"]))
128
+ )
129
+ self._channel.on(
130
+ "route",
131
+ lambda params: self._loop.create_task(
132
+ self._on_route(from_channel(params.get("route")))
133
+ ),
134
+ )
135
+ self._channel.on(
136
+ "webSocketRoute",
137
+ lambda params: self._loop.create_task(
138
+ self._on_web_socket_route(from_channel(params["webSocketRoute"]))
139
+ ),
140
+ )
141
+ self._channel.on(
142
+ "serviceWorker",
143
+ lambda params: self._on_service_worker(from_channel(params["worker"])),
144
+ )
145
+ self._channel.on("console", lambda event: self._on_console_message(event))
146
+ self._channel.on(
147
+ "dialog", lambda params: self._on_dialog(from_channel(params["dialog"]))
148
+ )
149
+ self._channel.on(
150
+ "pageError",
151
+ lambda params: self._on_page_error(
152
+ parse_error(params["error"]["error"]),
153
+ from_nullable_channel(params["page"]),
154
+ cast(
155
+ WebErrorLocation,
156
+ params.get("location") or {"url": "", "line": 0, "column": 0},
157
+ ),
158
+ ),
159
+ )
160
+ self._channel.on(
161
+ "request",
162
+ lambda params: self._on_request(
163
+ from_channel(params["request"]),
164
+ from_nullable_channel(params.get("page")),
165
+ ),
166
+ )
167
+ self._channel.on(
168
+ "response",
169
+ lambda params: self._on_response(
170
+ from_channel(params["response"]),
171
+ from_nullable_channel(params.get("page")),
172
+ ),
173
+ )
174
+ self._channel.on(
175
+ "requestFailed",
176
+ lambda params: self._on_request_failed(
177
+ from_channel(params["request"]),
178
+ params["responseEndTiming"],
179
+ params.get("failureText"),
180
+ from_nullable_channel(params.get("page")),
181
+ ),
182
+ )
183
+ self._channel.on(
184
+ "requestFinished",
185
+ lambda params: self._on_request_finished(
186
+ from_channel(params["request"]),
187
+ from_nullable_channel(params.get("response")),
188
+ params["responseEndTiming"],
189
+ from_nullable_channel(params.get("page")),
190
+ ),
191
+ )
192
+ self._closed_future: asyncio.Future = asyncio.Future()
193
+ self.once(
194
+ self.Events.Close, lambda context: self._closed_future.set_result(True)
195
+ )
196
+ self._close_reason: Optional[str] = None
197
+ self._har_routers: List[HarRouter] = []
198
+ self._set_event_to_subscription_mapping(
199
+ {
200
+ BrowserContext.Events.Console: "console",
201
+ BrowserContext.Events.Dialog: "dialog",
202
+ BrowserContext.Events.Request: "request",
203
+ BrowserContext.Events.Response: "response",
204
+ BrowserContext.Events.RequestFinished: "requestFinished",
205
+ BrowserContext.Events.RequestFailed: "requestFailed",
206
+ }
207
+ )
208
+ self._closing_or_closed = False
209
+
210
+ def __repr__(self) -> str:
211
+ return f"<BrowserContext browser={self.browser}>"
212
+
213
+ def _on_page(self, page: Page) -> None:
214
+ self._pages.append(page)
215
+ self.emit(BrowserContext.Events.Page, page)
216
+ if page._opener and (not page._opener.is_closed()):
217
+ page._opener.emit(Page.Events.Popup, page)
218
+
219
+ async def _on_route(self, route: Route) -> None:
220
+ route._context = self
221
+ page = route.request._safe_page()
222
+ route_handlers = self._routes.copy()
223
+ for route_handler in route_handlers:
224
+ if page and page._close_was_called or self._closing_or_closed:
225
+ return
226
+ if not route_handler.matches(route.request.url):
227
+ continue
228
+ if route_handler not in self._routes:
229
+ continue
230
+ if route_handler.will_expire:
231
+ self._routes.remove(route_handler)
232
+ try:
233
+ handled = await route_handler.handle(route)
234
+ finally:
235
+ if len(self._routes) == 0:
236
+ create_task_and_ignore_exception(
237
+ self._loop,
238
+ self._connection.wrap_api_call(
239
+ lambda: self._update_interception_patterns(), True
240
+ ),
241
+ )
242
+ if handled:
243
+ return
244
+ try:
245
+ await route._inner_continue(True)
246
+ except Exception:
247
+ pass
248
+
249
+ async def _on_web_socket_route(self, web_socket_route: WebSocketRoute) -> None:
250
+ route_handler = next(
251
+ (
252
+ route_handler
253
+ for route_handler in self._web_socket_routes
254
+ if route_handler.matches(web_socket_route.url)
255
+ ),
256
+ None,
257
+ )
258
+ if route_handler:
259
+ await route_handler.handle(web_socket_route)
260
+ else:
261
+ web_socket_route.connect_to_server()
262
+
263
+ def _on_binding(self, binding_call: BindingCall) -> None:
264
+ func = self._bindings.get(binding_call._initializer["name"])
265
+ if func is None:
266
+ return
267
+ asyncio.create_task(binding_call.call(func))
268
+
269
+ def set_default_navigation_timeout(self, timeout: float) -> None:
270
+ return self._set_default_navigation_timeout_impl(timeout)
271
+
272
+ def _set_default_navigation_timeout_impl(self, timeout: Optional[float]) -> None:
273
+ self._timeout_settings.set_default_navigation_timeout(timeout)
274
+
275
+ def set_default_timeout(self, timeout: float) -> None:
276
+ return self._set_default_timeout_impl(timeout)
277
+
278
+ def _set_default_timeout_impl(self, timeout: Optional[float]) -> None:
279
+ self._timeout_settings.set_default_timeout(timeout)
280
+
281
+ @property
282
+ def pages(self) -> List[Page]:
283
+ return self._pages.copy()
284
+
285
+ @property
286
+ def browser(self) -> Optional["Browser"]:
287
+ return self._browser
288
+
289
+ async def _initialize_har_from_options(
290
+ self,
291
+ record_har_path: Optional[Union[Path, str]],
292
+ record_har_content: Optional[HarContentPolicy],
293
+ record_har_omit_content: Optional[bool],
294
+ record_har_url_filter: Optional[Union[Pattern[str], str]],
295
+ record_har_mode: Optional[HarMode],
296
+ ) -> None:
297
+ if not record_har_path:
298
+ return
299
+ record_har_path = str(record_har_path)
300
+ default_policy: HarContentPolicy = (
301
+ "attach" if record_har_path.endswith(".zip") else "embed"
302
+ )
303
+ content_policy: HarContentPolicy = record_har_content or (
304
+ "omit" if record_har_omit_content is True else default_policy
305
+ )
306
+ await self._tracing._record_into_har(
307
+ har=record_har_path,
308
+ page=None,
309
+ url=record_har_url_filter,
310
+ update_content=content_policy,
311
+ update_mode=record_har_mode or "full",
312
+ )
313
+
314
+ async def new_page(self) -> Page:
315
+ if self._owner_page:
316
+ raise Error("Please use browser.new_context()")
317
+ return from_channel(await self._channel.send("newPage", None))
318
+
319
+ async def cookies(self, urls: Union[str, Sequence[str]] = None) -> List[Cookie]:
320
+ if urls is None:
321
+ urls = []
322
+ if isinstance(urls, str):
323
+ urls = [urls]
324
+ return await self._channel.send("cookies", None, dict(urls=urls))
325
+
326
+ async def add_cookies(self, cookies: Sequence[SetCookieParam]) -> None:
327
+ await self._channel.send("addCookies", None, dict(cookies=cookies))
328
+
329
+ async def clear_cookies(
330
+ self,
331
+ name: Union[str, Pattern[str]] = None,
332
+ domain: Union[str, Pattern[str]] = None,
333
+ path: Union[str, Pattern[str]] = None,
334
+ ) -> None:
335
+ await self._channel.send(
336
+ "clearCookies",
337
+ None,
338
+ {
339
+ "name": name if isinstance(name, str) else None,
340
+ "nameRegexSource": name.pattern if isinstance(name, Pattern) else None,
341
+ "nameRegexFlags": escape_regex_flags(name)
342
+ if isinstance(name, Pattern)
343
+ else None,
344
+ "domain": domain if isinstance(domain, str) else None,
345
+ "domainRegexSource": domain.pattern
346
+ if isinstance(domain, Pattern)
347
+ else None,
348
+ "domainRegexFlags": escape_regex_flags(domain)
349
+ if isinstance(domain, Pattern)
350
+ else None,
351
+ "path": path if isinstance(path, str) else None,
352
+ "pathRegexSource": path.pattern if isinstance(path, Pattern) else None,
353
+ "pathRegexFlags": escape_regex_flags(path)
354
+ if isinstance(path, Pattern)
355
+ else None,
356
+ },
357
+ )
358
+
359
+ async def grant_permissions(
360
+ self, permissions: Sequence[str], origin: str = None
361
+ ) -> None:
362
+ await self._channel.send("grantPermissions", None, locals_to_params(locals()))
363
+
364
+ async def clear_permissions(self) -> None:
365
+ await self._channel.send("clearPermissions", None)
366
+
367
+ async def set_geolocation(self, geolocation: Geolocation = None) -> None:
368
+ await self._channel.send("setGeolocation", None, locals_to_params(locals()))
369
+
370
+ async def set_extra_http_headers(self, headers: Dict[str, str]) -> None:
371
+ await self._channel.send(
372
+ "setExtraHTTPHeaders", None, dict(headers=serialize_headers(headers))
373
+ )
374
+
375
+ async def set_offline(self, offline: bool) -> None:
376
+ await self._channel.send("setOffline", None, dict(offline=offline))
377
+
378
+ async def add_init_script(
379
+ self, script: str = None, path: Union[str, Path] = None
380
+ ) -> Disposable:
381
+ await self.install_inject_route()
382
+ if path:
383
+ script = (await async_readfile(path)).decode()
384
+ if not isinstance(script, str):
385
+ raise Error("Either path or script parameter must be specified")
386
+ return from_channel(
387
+ await self._channel.send("addInitScript", None, dict(source=script))
388
+ )
389
+
390
+ async def expose_binding(self, name: str, callback: Callable) -> Disposable:
391
+ await self.install_inject_route()
392
+ for page in self._pages:
393
+ if name in page._bindings:
394
+ raise Error(
395
+ f'Function "{name}" has been already registered in one of the pages'
396
+ )
397
+ if name in self._bindings:
398
+ raise Error(f'Function "{name}" has been already registered')
399
+ self._bindings[name] = callback
400
+ return from_channel(
401
+ await self._channel.send("exposeBinding", None, dict(name=name))
402
+ )
403
+
404
+ async def expose_function(self, name: str, callback: Callable) -> Disposable:
405
+ return await self.expose_binding(name, lambda source, *args: callback(*args))
406
+
407
+ async def route(
408
+ self, url: URLMatch, handler: RouteHandlerCallback, times: int = None
409
+ ) -> DisposableStub:
410
+ self._routes.insert(
411
+ 0,
412
+ RouteHandler(
413
+ self._base_url,
414
+ url,
415
+ handler,
416
+ True if self._dispatcher_fiber else False,
417
+ times,
418
+ ),
419
+ )
420
+ await self._update_interception_patterns()
421
+ return DisposableStub(lambda: self.unroute(url, handler), self)
422
+
423
+ async def unroute(
424
+ self, url: URLMatch, handler: Optional[RouteHandlerCallback] = None
425
+ ) -> None:
426
+ removed = []
427
+ remaining = []
428
+ for route in self._routes:
429
+ if route.url != url or (handler and route.handler != handler):
430
+ remaining.append(route)
431
+ else:
432
+ removed.append(route)
433
+ await self._unroute_internal(removed, remaining, "default")
434
+
435
+ async def _unroute_internal(
436
+ self,
437
+ removed: List[RouteHandler],
438
+ remaining: List[RouteHandler],
439
+ behavior: Literal["default", "ignoreErrors", "wait"] = None,
440
+ ) -> None:
441
+ self._routes = remaining
442
+ if behavior is not None and behavior != "default":
443
+ await asyncio.gather(*map(lambda router: router.stop(behavior), removed))
444
+ await self._update_interception_patterns()
445
+
446
+ async def route_web_socket(
447
+ self, url: URLMatch, handler: WebSocketRouteHandlerCallback
448
+ ) -> None:
449
+ self._web_socket_routes.insert(
450
+ 0, WebSocketRouteHandler(self._base_url, url, handler)
451
+ )
452
+ await self._update_web_socket_interception_patterns()
453
+
454
+ def _dispose_har_routers(self) -> None:
455
+ for router in self._har_routers:
456
+ router.dispose()
457
+ self._har_routers = []
458
+
459
+ async def unroute_all(
460
+ self, behavior: Literal["default", "ignoreErrors", "wait"] = None
461
+ ) -> None:
462
+ await self._unroute_internal(self._routes, [], behavior)
463
+ self._dispose_har_routers()
464
+
465
+ async def route_from_har(
466
+ self,
467
+ har: Union[Path, str],
468
+ url: Union[Pattern[str], str] = None,
469
+ notFound: RouteFromHarNotFoundPolicy = None,
470
+ update: bool = None,
471
+ updateContent: Literal["attach", "embed"] = None,
472
+ updateMode: HarMode = None,
473
+ ) -> None:
474
+ if update:
475
+ await self._tracing._record_into_har(
476
+ har=har,
477
+ page=None,
478
+ url=url,
479
+ update_content=updateContent,
480
+ update_mode=updateMode,
481
+ )
482
+ return
483
+ router = await HarRouter.create(
484
+ local_utils=self._connection.local_utils,
485
+ file=str(har),
486
+ not_found_action=notFound or "abort",
487
+ url_matcher=url,
488
+ )
489
+ self._har_routers.append(router)
490
+ await router.add_context_route(self)
491
+
492
+ async def _update_interception_patterns(self) -> None:
493
+ patterns = RouteHandler.prepare_interception_patterns(self._routes)
494
+ await self._channel.send(
495
+ "setNetworkInterceptionPatterns", None, {"patterns": patterns}
496
+ )
497
+
498
+ async def _update_web_socket_interception_patterns(self) -> None:
499
+ patterns = WebSocketRouteHandler.prepare_interception_patterns(
500
+ self._web_socket_routes
501
+ )
502
+ await self._channel.send(
503
+ "setWebSocketInterceptionPatterns", None, {"patterns": patterns}
504
+ )
505
+
506
+ def expect_event(
507
+ self, event: str, predicate: Callable = None, timeout: float = None
508
+ ) -> EventContextManagerImpl:
509
+ if timeout is None:
510
+ timeout = self._timeout_settings.timeout()
511
+ waiter = Waiter(self, f"browser_context.expect_event({event})")
512
+ waiter.reject_on_timeout(
513
+ timeout, f'Timeout {timeout}ms exceeded while waiting for event "{event}"'
514
+ )
515
+ if event != BrowserContext.Events.Close:
516
+ waiter.reject_on_event(
517
+ self, BrowserContext.Events.Close, lambda: TargetClosedError()
518
+ )
519
+ waiter.wait_for_event(self, event, predicate)
520
+ return EventContextManagerImpl(waiter.result())
521
+
522
+ def _on_close(self) -> None:
523
+ self._closing_or_closed = True
524
+ if self._browser:
525
+ if self in self._browser._contexts:
526
+ self._browser._contexts.remove(self)
527
+ assert self._browser._browser_type is not None
528
+ if (
529
+ self
530
+ in self._browser._browser_type._playwright.selectors._contexts_for_selectors
531
+ ):
532
+ self._browser._browser_type._playwright.selectors._contexts_for_selectors.remove(
533
+ self
534
+ )
535
+ self._dispose_har_routers()
536
+ self._tracing._reset_stack_counter()
537
+ self.emit(BrowserContext.Events.Close, self)
538
+
539
+ def is_closed(self) -> bool:
540
+ return self._closing_or_closed
541
+
542
+ async def close(self, reason: str = None) -> None:
543
+ if self._closing_or_closed:
544
+ return
545
+ self._close_reason = reason
546
+ self._closing_or_closed = True
547
+ await self.request.dispose(reason=reason)
548
+
549
+ async def _inner_close() -> None:
550
+ await self._tracing._export_all_hars()
551
+
552
+ await self._channel._connection.wrap_api_call(_inner_close, True)
553
+ await self._channel.send("close", None, {"reason": reason})
554
+ await self._closed_future
555
+
556
+ async def storage_state(
557
+ self,
558
+ path: Union[str, Path] = None,
559
+ indexedDB: bool = None,
560
+ credentials: bool = None,
561
+ ) -> StorageState:
562
+ result = await self._channel.send_return_as_dict(
563
+ "storageState", None, {"indexedDB": indexedDB, "credentials": credentials}
564
+ )
565
+ if path:
566
+ await async_writefile(path, json.dumps(result))
567
+ return result
568
+
569
+ async def set_storage_state(
570
+ self, storageState: Union[StorageState, str, Path]
571
+ ) -> None:
572
+ if isinstance(storageState, (str, Path)):
573
+ state = json.loads(await async_readfile(storageState))
574
+ else:
575
+ state = storageState
576
+ await self._channel.send("setStorageState", None, {"storageState": state})
577
+
578
+ def _effective_close_reason(self) -> Optional[str]:
579
+ if self._close_reason:
580
+ return self._close_reason
581
+ if self._browser:
582
+ return self._browser._close_reason
583
+ return None
584
+
585
+ async def wait_for_event(
586
+ self, event: str, predicate: Callable = None, timeout: float = None
587
+ ) -> Any:
588
+ async with self.expect_event(event, predicate, timeout) as event_info:
589
+ pass
590
+ return await event_info
591
+
592
+ def expect_console_message(
593
+ self, predicate: Callable[[ConsoleMessage], bool] = None, timeout: float = None
594
+ ) -> EventContextManagerImpl[ConsoleMessage]:
595
+ return self.expect_event(Page.Events.Console, predicate, timeout)
596
+
597
+ def expect_page(
598
+ self, predicate: Callable[[Page], bool] = None, timeout: float = None
599
+ ) -> EventContextManagerImpl[Page]:
600
+ return self.expect_event(BrowserContext.Events.Page, predicate, timeout)
601
+
602
+ def _on_service_worker(self, worker: Worker) -> None:
603
+ worker._context = self
604
+ self._service_workers.add(worker)
605
+ self.emit(BrowserContext.Events.ServiceWorker, worker)
606
+
607
+ def _on_request_failed(
608
+ self,
609
+ request: Request,
610
+ response_end_timing: float,
611
+ failure_text: Optional[str],
612
+ page: Optional[Page],
613
+ ) -> None:
614
+ request._failure_text = failure_text
615
+ request._set_response_end_timing(response_end_timing)
616
+ self.emit(BrowserContext.Events.RequestFailed, request)
617
+ if page:
618
+ page.emit(Page.Events.RequestFailed, request)
619
+
620
+ def _on_request_finished(
621
+ self,
622
+ request: Request,
623
+ response: Optional[Response],
624
+ response_end_timing: float,
625
+ page: Optional[Page],
626
+ ) -> None:
627
+ request._set_response_end_timing(response_end_timing)
628
+ self.emit(BrowserContext.Events.RequestFinished, request)
629
+ if page:
630
+ page.emit(Page.Events.RequestFinished, request)
631
+ if response:
632
+ response._finished_future.set_result(True)
633
+
634
+ def _on_console_message(self, event: Dict) -> None:
635
+ message = ConsoleMessage(event, self._loop, self._dispatcher_fiber)
636
+ worker = message.worker
637
+ if worker:
638
+ worker.emit(Worker.Events.Console, message)
639
+ page = message.page
640
+ if page:
641
+ page.emit(Page.Events.Console, message)
642
+ self.emit(BrowserContext.Events.Console, message)
643
+
644
+ def _on_dialog(self, dialog: Dialog) -> None:
645
+ has_listeners = self.emit(BrowserContext.Events.Dialog, dialog)
646
+ page = dialog.page
647
+ if page:
648
+ has_listeners = page.emit(Page.Events.Dialog, dialog) or has_listeners
649
+ if not has_listeners:
650
+
651
+ async def handle_dialog() -> None:
652
+ try:
653
+ if dialog.type == "beforeunload":
654
+ await self._connection.wrap_api_call(
655
+ lambda: dialog.accept(), is_internal=True
656
+ )
657
+ else:
658
+ await self._connection.wrap_api_call(
659
+ lambda: dialog.dismiss(), is_internal=True
660
+ )
661
+ except Exception:
662
+ pass
663
+
664
+ asyncio.create_task(handle_dialog())
665
+
666
+ def _on_page_error(
667
+ self, error: Error, page: Optional[Page], location: WebErrorLocation
668
+ ) -> None:
669
+ self.emit(
670
+ BrowserContext.Events.WebError,
671
+ WebError(self._loop, self._dispatcher_fiber, page, error, location),
672
+ )
673
+ if page:
674
+ page.emit(Page.Events.PageError, error)
675
+
676
+ def _on_request(self, request: Request, page: Optional[Page]) -> None:
677
+ self.emit(BrowserContext.Events.Request, request)
678
+ if page:
679
+ page.emit(Page.Events.Request, request)
680
+
681
+ def _on_response(self, response: Response, page: Optional[Page]) -> None:
682
+ self.emit(BrowserContext.Events.Response, response)
683
+ if page:
684
+ page.emit(Page.Events.Response, response)
685
+
686
+ @property
687
+ def background_pages(self) -> List[Page]:
688
+ return []
689
+
690
+ @property
691
+ def service_workers(self) -> List[Worker]:
692
+ return list(self._service_workers)
693
+
694
+ async def new_cdp_session(self, page: Union[Page, Frame]) -> CDPSession:
695
+ page = to_impl(page)
696
+ params = {}
697
+ if isinstance(page, Page):
698
+ params["page"] = page._channel
699
+ elif isinstance(page, Frame):
700
+ params["frame"] = page._channel
701
+ else:
702
+ raise Error("page: expected Page or Frame")
703
+ return from_channel(await self._channel.send("newCDPSession", None, params))
704
+
705
+ @property
706
+ def tracing(self) -> Tracing:
707
+ return self._tracing
708
+
709
+ @property
710
+ def debugger(self) -> Debugger:
711
+ return self._debugger
712
+
713
+ @property
714
+ def request(self) -> "APIRequestContext":
715
+ return self._request
716
+
717
+ @property
718
+ def clock(self) -> Clock:
719
+ return self._clock
720
+
721
+ @property
722
+ def credentials(self) -> Credentials:
723
+ return self._credentials
724
+
725
+ route_injecting = False
726
+
727
+ async def install_inject_route(self) -> None:
728
+ from patchright._impl._impl_to_api_mapping import ImplToApiMapping
729
+
730
+ mapping = ImplToApiMapping()
731
+
732
+ async def route_handler(route: Route) -> None:
733
+ try:
734
+ if (
735
+ route.request.resource_type == "document"
736
+ and route.request.url.startswith("http")
737
+ ):
738
+ await route.fallback(patchrightInitScript=True)
739
+ else:
740
+ await route.fallback()
741
+ except:
742
+ await route.fallback()
743
+
744
+ if not self.route_injecting:
745
+ if self._connection._is_sync:
746
+ self._routes.insert(
747
+ 0,
748
+ RouteHandler(
749
+ self._options.get("baseURL"),
750
+ "**/*",
751
+ mapping.wrap_handler(route_handler),
752
+ False,
753
+ None,
754
+ ),
755
+ )
756
+ await self._update_interception_patterns()
757
+ else:
758
+ await self.route("**/*", mapping.wrap_handler(route_handler))
759
+ self.route_injecting = True