playwright-browserstack 1.62.0__py3-none-win32.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 (192) hide show
  1. playwright/__init__.py +19 -0
  2. playwright/__main__.py +33 -0
  3. playwright/_impl/__init__.py +0 -0
  4. playwright/_impl/__pyinstaller/__init__.py +20 -0
  5. playwright/_impl/__pyinstaller/hook-playwright.async_api.py +17 -0
  6. playwright/_impl/__pyinstaller/hook-playwright.sync_api.py +17 -0
  7. playwright/_impl/_android.py +704 -0
  8. playwright/_impl/_api_structures.py +365 -0
  9. playwright/_impl/_artifact.py +87 -0
  10. playwright/_impl/_assertions.py +1124 -0
  11. playwright/_impl/_async_base.py +105 -0
  12. playwright/_impl/_browser.py +293 -0
  13. playwright/_impl/_browser_context.py +754 -0
  14. playwright/_impl/_browser_type.py +368 -0
  15. playwright/_impl/_cdp_session.py +46 -0
  16. playwright/_impl/_clock.py +104 -0
  17. playwright/_impl/_connection.py +764 -0
  18. playwright/_impl/_console_message.py +98 -0
  19. playwright/_impl/_credentials.py +57 -0
  20. playwright/_impl/_debugger.py +54 -0
  21. playwright/_impl/_dialog.py +63 -0
  22. playwright/_impl/_disposable.py +96 -0
  23. playwright/_impl/_download.py +64 -0
  24. playwright/_impl/_driver.py +41 -0
  25. playwright/_impl/_element_handle.py +480 -0
  26. playwright/_impl/_errors.py +64 -0
  27. playwright/_impl/_event_context_manager.py +33 -0
  28. playwright/_impl/_fetch.py +626 -0
  29. playwright/_impl/_file_chooser.py +57 -0
  30. playwright/_impl/_form_data.py +34 -0
  31. playwright/_impl/_frame.py +985 -0
  32. playwright/_impl/_glob.py +65 -0
  33. playwright/_impl/_greenlets.py +49 -0
  34. playwright/_impl/_har_router.py +122 -0
  35. playwright/_impl/_helper.py +601 -0
  36. playwright/_impl/_impl_to_api_mapping.py +158 -0
  37. playwright/_impl/_input.py +111 -0
  38. playwright/_impl/_js_handle.py +336 -0
  39. playwright/_impl/_json_pipe.py +97 -0
  40. playwright/_impl/_local_utils.py +95 -0
  41. playwright/_impl/_locator.py +1024 -0
  42. playwright/_impl/_map.py +31 -0
  43. playwright/_impl/_network.py +1017 -0
  44. playwright/_impl/_object_factory.py +116 -0
  45. playwright/_impl/_page.py +1637 -0
  46. playwright/_impl/_path_utils.py +27 -0
  47. playwright/_impl/_playwright.py +68 -0
  48. playwright/_impl/_screencast.py +177 -0
  49. playwright/_impl/_selectors.py +67 -0
  50. playwright/_impl/_set_input_files_helpers.py +158 -0
  51. playwright/_impl/_str_utils.py +76 -0
  52. playwright/_impl/_stream.py +46 -0
  53. playwright/_impl/_sync_base.py +151 -0
  54. playwright/_impl/_tracing.py +281 -0
  55. playwright/_impl/_transport.py +184 -0
  56. playwright/_impl/_video.py +56 -0
  57. playwright/_impl/_waiter.py +197 -0
  58. playwright/_impl/_web_error.py +48 -0
  59. playwright/_impl/_web_storage.py +58 -0
  60. playwright/_impl/_writable_stream.py +42 -0
  61. playwright/_repo_version.py +24 -0
  62. playwright/async_api/__init__.py +275 -0
  63. playwright/async_api/_android_generated.py +462 -0
  64. playwright/async_api/_context_manager.py +57 -0
  65. playwright/async_api/_generated.py +23875 -0
  66. playwright/driver/LICENSE +2946 -0
  67. playwright/driver/README.md +1 -0
  68. playwright/driver/node.exe +0 -0
  69. playwright/driver/package/LICENSE +202 -0
  70. playwright/driver/package/NOTICE +5 -0
  71. playwright/driver/package/README.md +3 -0
  72. playwright/driver/package/ThirdPartyNotices.txt +13 -0
  73. playwright/driver/package/bin/install_media_pack.ps1 +5 -0
  74. playwright/driver/package/bin/install_webkit_wsl.ps1 +33 -0
  75. playwright/driver/package/bin/reinstall_chrome_beta_linux.sh +42 -0
  76. playwright/driver/package/bin/reinstall_chrome_beta_mac.sh +13 -0
  77. playwright/driver/package/bin/reinstall_chrome_beta_win.ps1 +24 -0
  78. playwright/driver/package/bin/reinstall_chrome_stable_linux.sh +42 -0
  79. playwright/driver/package/bin/reinstall_chrome_stable_mac.sh +12 -0
  80. playwright/driver/package/bin/reinstall_chrome_stable_win.ps1 +24 -0
  81. playwright/driver/package/bin/reinstall_msedge_beta_linux.sh +48 -0
  82. playwright/driver/package/bin/reinstall_msedge_beta_mac.sh +11 -0
  83. playwright/driver/package/bin/reinstall_msedge_beta_win.ps1 +23 -0
  84. playwright/driver/package/bin/reinstall_msedge_dev_linux.sh +48 -0
  85. playwright/driver/package/bin/reinstall_msedge_dev_mac.sh +11 -0
  86. playwright/driver/package/bin/reinstall_msedge_dev_win.ps1 +23 -0
  87. playwright/driver/package/bin/reinstall_msedge_stable_linux.sh +48 -0
  88. playwright/driver/package/bin/reinstall_msedge_stable_mac.sh +11 -0
  89. playwright/driver/package/bin/reinstall_msedge_stable_win.ps1 +24 -0
  90. playwright/driver/package/browsers.json +75 -0
  91. playwright/driver/package/cli.js +21 -0
  92. playwright/driver/package/index.d.ts +17 -0
  93. playwright/driver/package/index.js +17 -0
  94. playwright/driver/package/index.mjs +28 -0
  95. playwright/driver/package/lib/bootstrap.js +88 -0
  96. playwright/driver/package/lib/coreBundle.js +74830 -0
  97. playwright/driver/package/lib/entry/cliDaemon.js +5 -0
  98. playwright/driver/package/lib/entry/dashboardApp.js +3 -0
  99. playwright/driver/package/lib/entry/mcp.js +10 -0
  100. playwright/driver/package/lib/entry/oopBrowserDownload.js +3 -0
  101. playwright/driver/package/lib/package.js +50 -0
  102. playwright/driver/package/lib/server/chromium/appIcon.png +0 -0
  103. playwright/driver/package/lib/server/electron/loader.js +118 -0
  104. playwright/driver/package/lib/serverRegistry.js +7347 -0
  105. playwright/driver/package/lib/serverRegistry.js.LICENSE +354 -0
  106. playwright/driver/package/lib/tools/cli-client/channelSessions.js +141 -0
  107. playwright/driver/package/lib/tools/cli-client/cli.js +6 -0
  108. playwright/driver/package/lib/tools/cli-client/help.json +708 -0
  109. playwright/driver/package/lib/tools/cli-client/minimist.js +128 -0
  110. playwright/driver/package/lib/tools/cli-client/output.js +343 -0
  111. playwright/driver/package/lib/tools/cli-client/program.js +404 -0
  112. playwright/driver/package/lib/tools/cli-client/registry.js +176 -0
  113. playwright/driver/package/lib/tools/cli-client/session.js +258 -0
  114. playwright/driver/package/lib/tools/dashboard/appIcon.png +0 -0
  115. playwright/driver/package/lib/tools/skills/playwright-cli/SKILL.md +420 -0
  116. playwright/driver/package/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
  117. playwright/driver/package/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
  118. playwright/driver/package/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
  119. playwright/driver/package/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
  120. playwright/driver/package/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
  121. playwright/driver/package/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
  122. playwright/driver/package/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
  123. playwright/driver/package/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
  124. playwright/driver/package/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
  125. playwright/driver/package/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  126. playwright/driver/package/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  127. playwright/driver/package/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  128. playwright/driver/package/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  129. playwright/driver/package/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  130. playwright/driver/package/lib/tools/skills/playwright-trace/SKILL.md +171 -0
  131. playwright/driver/package/lib/tools/utils/extension.js +101 -0
  132. playwright/driver/package/lib/tools/utils/socketConnection.js +108 -0
  133. playwright/driver/package/lib/utilsBundle.js +90764 -0
  134. playwright/driver/package/lib/utilsBundle.js.LICENSE +2179 -0
  135. playwright/driver/package/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
  136. playwright/driver/package/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
  137. playwright/driver/package/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
  138. playwright/driver/package/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
  139. playwright/driver/package/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
  140. playwright/driver/package/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
  141. playwright/driver/package/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
  142. playwright/driver/package/lib/vite/dashboard/index.html +29 -0
  143. playwright/driver/package/lib/vite/dashboard/playwright-logo.svg +24 -0
  144. playwright/driver/package/lib/vite/htmlReport/index.html +16 -0
  145. playwright/driver/package/lib/vite/htmlReport/report.css +2 -0
  146. playwright/driver/package/lib/vite/htmlReport/report.js +32 -0
  147. playwright/driver/package/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  148. playwright/driver/package/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
  149. playwright/driver/package/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
  150. playwright/driver/package/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
  151. playwright/driver/package/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
  152. playwright/driver/package/lib/vite/recorder/index.html +29 -0
  153. playwright/driver/package/lib/vite/recorder/playwright-logo.svg +9 -0
  154. playwright/driver/package/lib/vite/traceViewer/assets/codeMirrorModule-By56iMx7.js +32 -0
  155. playwright/driver/package/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +181 -0
  156. playwright/driver/package/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  157. playwright/driver/package/lib/vite/traceViewer/assets/xtermModule-COQkjINf.js +7 -0
  158. playwright/driver/package/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  159. playwright/driver/package/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
  160. playwright/driver/package/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
  161. playwright/driver/package/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  162. playwright/driver/package/lib/vite/traceViewer/index.Dl36UVQT.js +1 -0
  163. playwright/driver/package/lib/vite/traceViewer/index.html +44 -0
  164. playwright/driver/package/lib/vite/traceViewer/manifest.webmanifest +16 -0
  165. playwright/driver/package/lib/vite/traceViewer/playwright-logo.svg +9 -0
  166. playwright/driver/package/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  167. playwright/driver/package/lib/vite/traceViewer/snapshot.html +10 -0
  168. playwright/driver/package/lib/vite/traceViewer/sw.bundle.js +4 -0
  169. playwright/driver/package/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
  170. playwright/driver/package/lib/vite/traceViewer/uiMode.D962mr9b.js +5 -0
  171. playwright/driver/package/lib/vite/traceViewer/uiMode.html +18 -0
  172. playwright/driver/package/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  173. playwright/driver/package/lib/webp_codec.LICENSE +173 -0
  174. playwright/driver/package/lib/webp_codec.wasm +0 -0
  175. playwright/driver/package/lib/xdg-open +1267 -0
  176. playwright/driver/package/package.json +34 -0
  177. playwright/driver/package/types/protocol.d.ts +24842 -0
  178. playwright/driver/package/types/structs.d.ts +47 -0
  179. playwright/driver/package/types/types.d.ts +26188 -0
  180. playwright/py.typed +0 -0
  181. playwright/sync_api/__init__.py +275 -0
  182. playwright/sync_api/_android_generated.py +494 -0
  183. playwright/sync_api/_context_manager.py +98 -0
  184. playwright/sync_api/_generated.py +24045 -0
  185. playwright_browserstack-1.62.0.dist-info/METADATA +82 -0
  186. playwright_browserstack-1.62.0.dist-info/RECORD +192 -0
  187. playwright_browserstack-1.62.0.dist-info/WHEEL +5 -0
  188. playwright_browserstack-1.62.0.dist-info/entry_points.txt +5 -0
  189. playwright_browserstack-1.62.0.dist-info/licenses/LICENSE +202 -0
  190. playwright_browserstack-1.62.0.dist-info/licenses/LICENSE.txt +51 -0
  191. playwright_browserstack-1.62.0.dist-info/licenses/NOTICE +18 -0
  192. playwright_browserstack-1.62.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,601 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import asyncio
15
+ import datetime
16
+ import math
17
+ import os
18
+ import re
19
+ import time
20
+ import traceback
21
+ from pathlib import Path
22
+ from types import TracebackType
23
+ from typing import (
24
+ TYPE_CHECKING,
25
+ Any,
26
+ Awaitable,
27
+ Callable,
28
+ Dict,
29
+ List,
30
+ Literal,
31
+ Optional,
32
+ Pattern,
33
+ Set,
34
+ Tuple,
35
+ TypedDict,
36
+ TypeVar,
37
+ Union,
38
+ cast,
39
+ )
40
+ from urllib.parse import ParseResult, urljoin, urlparse, urlunparse
41
+
42
+ from playwright._impl._api_structures import NameValue
43
+ from playwright._impl._errors import (
44
+ Error,
45
+ TargetClosedError,
46
+ TimeoutError,
47
+ is_target_closed_error,
48
+ rewrite_error,
49
+ )
50
+ from playwright._impl._glob import glob_to_regex_pattern
51
+ from playwright._impl._greenlets import RouteGreenlet
52
+ from playwright._impl._str_utils import escape_regex_flags
53
+
54
+ if TYPE_CHECKING: # pragma: no cover
55
+ from playwright._impl._api_structures import HeadersArray
56
+ from playwright._impl._network import Request, Response, Route, WebSocketRoute
57
+
58
+ URLMatch = Union[str, Pattern[str], Callable[[str], bool]]
59
+ URLMatchRequest = Union[
60
+ str, Pattern[str], Callable[["Request"], Union[bool, Awaitable[bool]]]
61
+ ]
62
+ URLMatchResponse = Union[
63
+ str, Pattern[str], Callable[["Response"], Union[bool, Awaitable[bool]]]
64
+ ]
65
+ RouteHandlerCallback = Union[
66
+ Callable[["Route"], Any], Callable[["Route", "Request"], Any]
67
+ ]
68
+ WebSocketRouteHandlerCallback = Callable[["WebSocketRoute"], Any]
69
+
70
+ ColorScheme = Literal["dark", "light", "no-preference", "null"]
71
+ ForcedColors = Literal["active", "none", "null"]
72
+ Contrast = Literal["more", "no-preference", "null"]
73
+ ReducedMotion = Literal["no-preference", "null", "reduce"]
74
+ DocumentLoadState = Literal["commit", "domcontentloaded", "load", "networkidle"]
75
+ KeyboardModifier = Literal["Alt", "Control", "ControlOrMeta", "Meta", "Shift"]
76
+ MouseButton = Literal["left", "middle", "right"]
77
+ ServiceWorkersPolicy = Literal["allow", "block"]
78
+ HarMode = Literal["full", "minimal"]
79
+ HarContentPolicy = Literal["attach", "embed", "omit"]
80
+ RouteFromHarNotFoundPolicy = Literal["abort", "fallback"]
81
+
82
+
83
+ class ErrorPayload(TypedDict, total=False):
84
+ message: str
85
+ name: str
86
+ stack: str
87
+ value: Optional[Any]
88
+
89
+
90
+ class HarRecordingMetadata(TypedDict, total=False):
91
+ path: str
92
+ content: Optional[HarContentPolicy]
93
+
94
+
95
+ def prepare_record_har_options(params: Dict) -> Dict[str, Any]:
96
+ out_params: Dict[str, Any] = {"path": str(params["recordHarPath"])}
97
+ if "recordHarUrlFilter" in params:
98
+ opt = params["recordHarUrlFilter"]
99
+ if isinstance(opt, str):
100
+ out_params["urlGlob"] = opt
101
+ if isinstance(opt, Pattern):
102
+ out_params["urlRegexSource"] = opt.pattern
103
+ out_params["urlRegexFlags"] = escape_regex_flags(opt)
104
+ del params["recordHarUrlFilter"]
105
+ if "recordHarMode" in params:
106
+ out_params["mode"] = params["recordHarMode"]
107
+ del params["recordHarMode"]
108
+
109
+ new_content_api = None
110
+ old_content_api = None
111
+ if "recordHarContent" in params:
112
+ new_content_api = params["recordHarContent"]
113
+ del params["recordHarContent"]
114
+ if "recordHarOmitContent" in params:
115
+ old_content_api = params["recordHarOmitContent"]
116
+ del params["recordHarOmitContent"]
117
+ content = new_content_api or ("omit" if old_content_api else None)
118
+ if content:
119
+ out_params["content"] = content
120
+
121
+ return out_params
122
+
123
+
124
+ class ParsedMessageParams(TypedDict):
125
+ type: str
126
+ guid: str
127
+ initializer: Dict
128
+
129
+
130
+ class ParsedMessagePayload(TypedDict, total=False):
131
+ id: int
132
+ guid: str
133
+ method: str
134
+ params: ParsedMessageParams
135
+ result: Any
136
+ error: ErrorPayload
137
+ errorDetails: Any
138
+ log: List[str]
139
+
140
+
141
+ class Document(TypedDict):
142
+ request: Optional[Any]
143
+
144
+
145
+ class FrameNavigatedEvent(TypedDict):
146
+ url: str
147
+ name: str
148
+ newDocument: Optional[Document]
149
+ error: Optional[str]
150
+
151
+
152
+ Env = Dict[str, Union[str, float, bool]]
153
+
154
+
155
+ def url_matches(
156
+ base_url: Optional[str],
157
+ url_string: str,
158
+ match: Optional[URLMatch],
159
+ websocket_url: bool = None,
160
+ ) -> bool:
161
+ if not match:
162
+ return True
163
+ if isinstance(match, str):
164
+ match = re.compile(
165
+ resolve_glob_to_regex_pattern(base_url, match, websocket_url)
166
+ )
167
+ if isinstance(match, Pattern):
168
+ return bool(match.search(url_string))
169
+ return match(url_string)
170
+
171
+
172
+ def resolve_glob_to_regex_pattern(
173
+ base_url: Optional[str], glob: str, websocket_url: bool = None
174
+ ) -> str:
175
+ if websocket_url:
176
+ base_url = to_websocket_base_url(base_url)
177
+ glob = resolve_glob_base(base_url, glob)
178
+ return glob_to_regex_pattern(glob)
179
+
180
+
181
+ def to_websocket_base_url(base_url: Optional[str]) -> Optional[str]:
182
+ if base_url is not None and re.match(r"^https?://", base_url):
183
+ base_url = re.sub(r"^http", "ws", base_url)
184
+ return base_url
185
+
186
+
187
+ def resolve_glob_base(base_url: Optional[str], match: str) -> str:
188
+ if match[0] == "*":
189
+ return match
190
+
191
+ token_map: Dict[str, str] = {}
192
+
193
+ def map_token(original: str, replacement: str) -> str:
194
+ if len(original) == 0:
195
+ return ""
196
+ token_map[replacement] = original
197
+ return replacement
198
+
199
+ # Escaped `\\?` behaves the same as `?` in our glob patterns.
200
+ match = match.replace(r"\\?", "?")
201
+ # Special case about: URLs as they are not relative to base_url
202
+ if (
203
+ match.startswith("about:")
204
+ or match.startswith("data:")
205
+ or match.startswith("chrome:")
206
+ or match.startswith("edge:")
207
+ or match.startswith("file:")
208
+ ):
209
+ # about: and data: URLs are not relative to base_url, so we return them as is.
210
+ return match
211
+ # Glob symbols may be escaped in the URL and some of them such as ? affect resolution,
212
+ # so we replace them with safe components first.
213
+ processed_parts = []
214
+ for index, token in enumerate(match.split("/")):
215
+ if token in (".", "..", ""):
216
+ processed_parts.append(token)
217
+ continue
218
+ # Handle special case of http*://, note that the new schema has to be
219
+ # a web schema so that slashes are properly inserted after domain.
220
+ if index == 0 and token.endswith(":"):
221
+ # Replace any pattern with http:
222
+ if "*" in token or "{" in token:
223
+ processed_parts.append(map_token(token, "http:"))
224
+ else:
225
+ # Preserve explicit schema as is as it may affect trailing slashes after domain.
226
+ processed_parts.append(token)
227
+ continue
228
+ question_index = token.find("?")
229
+ if question_index == -1:
230
+ processed_parts.append(map_token(token, f"$_{index}_$"))
231
+ else:
232
+ new_prefix = map_token(token[:question_index], f"$_{index}_$")
233
+ new_suffix = map_token(token[question_index:], f"?$_{index}_$")
234
+ processed_parts.append(new_prefix + new_suffix)
235
+
236
+ relative_path = "/".join(processed_parts)
237
+ resolved, case_insensitive_part = resolve_base_url(base_url, relative_path)
238
+
239
+ for token, original in token_map.items():
240
+ normalize = case_insensitive_part and token in case_insensitive_part
241
+ resolved = resolved.replace(
242
+ token, original.lower() if normalize else original, 1
243
+ )
244
+
245
+ return resolved
246
+
247
+
248
+ def resolve_base_url(
249
+ base_url: Optional[str], given_url: str
250
+ ) -> Tuple[str, Optional[str]]:
251
+ try:
252
+ url = nodelike_urlparse(
253
+ urljoin(base_url if base_url is not None else "", given_url)
254
+ )
255
+ resolved = urlunparse(url)
256
+ # Schema and domain are case-insensitive.
257
+ hostname_port = (
258
+ url.hostname or ""
259
+ ) # can't use parsed.netloc because it includes userinfo (username:password)
260
+ if url.port:
261
+ hostname_port += f":{url.port}"
262
+ case_insensitive_prefix = f"{url.scheme}://{hostname_port}"
263
+ return resolved, case_insensitive_prefix
264
+ except Exception:
265
+ return given_url, None
266
+
267
+
268
+ def nodelike_urlparse(url: str) -> ParseResult:
269
+ parsed = urlparse(url, allow_fragments=True)
270
+
271
+ # https://url.spec.whatwg.org/#special-scheme
272
+ is_special_url = parsed.scheme in ["http", "https", "ws", "wss", "ftp", "file"]
273
+ if is_special_url:
274
+ # special urls have a list path, list paths are serialized as follows: https://url.spec.whatwg.org/#url-path-serializer
275
+ # urllib diverges, so we patch it here
276
+ if parsed.path == "":
277
+ parsed = parsed._replace(path="/")
278
+
279
+ return parsed
280
+
281
+
282
+ class HarLookupResult(TypedDict, total=False):
283
+ action: Literal["error", "redirect", "fulfill", "noentry"]
284
+ message: Optional[str]
285
+ redirectURL: Optional[str]
286
+ status: Optional[int]
287
+ headers: Optional["HeadersArray"]
288
+ body: Optional[str]
289
+
290
+
291
+ DEFAULT_PLAYWRIGHT_TIMEOUT_IN_MILLISECONDS = 30000
292
+ DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT_IN_MILLISECONDS = 180000
293
+ PLAYWRIGHT_MAX_DEADLINE = 2147483647 # 2^31-1
294
+
295
+
296
+ class TimeoutSettings:
297
+
298
+ @staticmethod
299
+ def launch_timeout(timeout: Optional[float] = None) -> float:
300
+ return (
301
+ timeout
302
+ if timeout is not None
303
+ else DEFAULT_PLAYWRIGHT_LAUNCH_TIMEOUT_IN_MILLISECONDS
304
+ )
305
+
306
+ def __init__(self, parent: Optional["TimeoutSettings"]) -> None:
307
+ self._parent = parent
308
+ self._default_timeout: Optional[float] = None
309
+ self._default_navigation_timeout: Optional[float] = None
310
+
311
+ def set_default_timeout(self, timeout: Optional[float]) -> None:
312
+ self._default_timeout = timeout
313
+
314
+ def timeout(self, timeout: float = None) -> float:
315
+ if timeout is not None:
316
+ return timeout
317
+ if self._default_timeout is not None:
318
+ return self._default_timeout
319
+ if self._parent:
320
+ return self._parent.timeout()
321
+ return DEFAULT_PLAYWRIGHT_TIMEOUT_IN_MILLISECONDS
322
+
323
+ def set_default_navigation_timeout(
324
+ self, navigation_timeout: Optional[float]
325
+ ) -> None:
326
+ self._default_navigation_timeout = navigation_timeout
327
+
328
+ def default_navigation_timeout(self) -> Optional[float]:
329
+ return self._default_navigation_timeout
330
+
331
+ def default_timeout(self) -> Optional[float]:
332
+ return self._default_timeout
333
+
334
+ def navigation_timeout(self, timeout: float = None) -> float:
335
+ if timeout is not None:
336
+ return timeout
337
+ if self._default_navigation_timeout is not None:
338
+ return self._default_navigation_timeout
339
+ if self._default_timeout is not None:
340
+ return self._default_timeout
341
+ if self._parent:
342
+ return self._parent.navigation_timeout()
343
+ return DEFAULT_PLAYWRIGHT_TIMEOUT_IN_MILLISECONDS
344
+
345
+
346
+ def serialize_error(ex: Exception, tb: Optional[TracebackType]) -> ErrorPayload:
347
+ return ErrorPayload(
348
+ message=str(ex), name="Error", stack="".join(traceback.format_tb(tb))
349
+ )
350
+
351
+
352
+ def parse_error(error: ErrorPayload, log: Optional[str] = None) -> Error:
353
+ base_error_class = Error
354
+ if error.get("name") == "TimeoutError":
355
+ base_error_class = TimeoutError
356
+ if error.get("name") == "TargetClosedError":
357
+ base_error_class = TargetClosedError
358
+ if not log:
359
+ log = ""
360
+ exc = base_error_class(patch_error_message(error["message"]) + log)
361
+ exc._name = error["name"]
362
+ exc._stack = error["stack"]
363
+ return exc
364
+
365
+
366
+ def patch_error_message(message: str) -> str:
367
+ match = re.match(r"(\w+)(: expected .*)", message)
368
+ if match:
369
+ message = to_snake_case(match.group(1)) + match.group(2)
370
+ message = message.replace(
371
+ "Pass { acceptDownloads: true }", "Pass 'accept_downloads=True'"
372
+ )
373
+ return message
374
+
375
+
376
+ def locals_to_params(args: Dict) -> Dict:
377
+ copy = {}
378
+ for key in args:
379
+ if key == "self":
380
+ continue
381
+ if args[key] is not None:
382
+ copy[key] = (
383
+ args[key]
384
+ if not isinstance(args[key], Dict)
385
+ else locals_to_params(args[key])
386
+ )
387
+ return copy
388
+
389
+
390
+ def monotonic_time() -> int:
391
+ return math.floor(time.monotonic() * 1000)
392
+
393
+
394
+ def create_task_and_ignore_exception(
395
+ loop: asyncio.AbstractEventLoop, coro: Awaitable[Any]
396
+ ) -> None:
397
+ async def _ignore_exception() -> None:
398
+ try:
399
+ await coro
400
+ except Exception:
401
+ pass
402
+
403
+ loop.create_task(_ignore_exception())
404
+
405
+
406
+ class RouteHandlerInvocation:
407
+ complete: "asyncio.Future"
408
+ route: "Route"
409
+
410
+ def __init__(self, complete: "asyncio.Future", route: "Route") -> None:
411
+ self.complete = complete
412
+ self.route = route
413
+
414
+
415
+ class RouteHandler:
416
+ def __init__(
417
+ self,
418
+ base_url: Optional[str],
419
+ url: URLMatch,
420
+ handler: RouteHandlerCallback,
421
+ is_sync: bool,
422
+ times: Optional[int] = None,
423
+ ):
424
+ self._base_url = base_url
425
+ self.url = url
426
+ self.handler = handler
427
+ self._times = times if times else math.inf
428
+ self._handled_count = 0
429
+ self._is_sync = is_sync
430
+ self._ignore_exception = False
431
+ self._active_invocations: Set[RouteHandlerInvocation] = set()
432
+
433
+ def matches(self, request_url: str) -> bool:
434
+ return url_matches(self._base_url, request_url, self.url)
435
+
436
+ async def handle(self, route: "Route") -> bool:
437
+ handler_invocation = RouteHandlerInvocation(
438
+ asyncio.get_running_loop().create_future(), route
439
+ )
440
+ self._active_invocations.add(handler_invocation)
441
+ try:
442
+ return await self._handle_internal(route)
443
+ except Exception as e:
444
+ # If the handler was stopped (without waiting for completion), we ignore all exceptions.
445
+ if self._ignore_exception:
446
+ return False
447
+ if is_target_closed_error(e):
448
+ # We are failing in the handler because the target has closed.
449
+ # Give user a hint!
450
+ optional_async_prefix = "await " if not self._is_sync else ""
451
+ raise rewrite_error(
452
+ e,
453
+ f"\"{str(e)}\" while running route callback.\nConsider awaiting `{optional_async_prefix}page.unroute_all(behavior='ignoreErrors')`\nbefore the end of the test to ignore remaining routes in flight.",
454
+ )
455
+ raise e
456
+ finally:
457
+ handler_invocation.complete.set_result(None)
458
+ self._active_invocations.remove(handler_invocation)
459
+
460
+ async def _handle_internal(self, route: "Route") -> bool:
461
+ handled_future = route._start_handling()
462
+
463
+ self._handled_count += 1
464
+ if self._is_sync:
465
+ handler_finished_future = route._loop.create_future()
466
+
467
+ def _handler() -> None:
468
+ try:
469
+ self.handler(route, route.request) # type: ignore
470
+ handler_finished_future.set_result(None)
471
+ except Exception as e:
472
+ handler_finished_future.set_exception(e)
473
+
474
+ # As with event handlers, each route handler is a potentially blocking context
475
+ # so it needs a fiber.
476
+ g = RouteGreenlet(_handler)
477
+ g.switch()
478
+ await handler_finished_future
479
+ else:
480
+ coro_or_future = self.handler(route, route.request) # type: ignore
481
+ if coro_or_future:
482
+ # separate task so that we get a proper stack trace for exceptions / tracing api_name extraction
483
+ await asyncio.ensure_future(coro_or_future)
484
+ return await handled_future
485
+
486
+ async def stop(self, behavior: Literal["ignoreErrors", "wait"]) -> None:
487
+ # When a handler is manually unrouted or its page/context is closed we either
488
+ # - wait for the current handler invocations to finish
489
+ # - or do not wait, if the user opted out of it, but swallow all exceptions
490
+ # that happen after the unroute/close.
491
+ if behavior == "ignoreErrors":
492
+ self._ignore_exception = True
493
+ else:
494
+ tasks = []
495
+ for activation in self._active_invocations:
496
+ if not activation.route._did_throw:
497
+ tasks.append(activation.complete)
498
+ await asyncio.gather(*tasks)
499
+
500
+ @property
501
+ def will_expire(self) -> bool:
502
+ return self._handled_count + 1 >= self._times
503
+
504
+ @staticmethod
505
+ def prepare_interception_patterns(
506
+ handlers: List["RouteHandler"],
507
+ ) -> List[Dict[str, str]]:
508
+ patterns = []
509
+ all = False
510
+ for handler in handlers:
511
+ if isinstance(handler.url, str):
512
+ patterns.append({"glob": handler.url})
513
+ elif isinstance(handler.url, re.Pattern):
514
+ patterns.append(
515
+ {
516
+ "regexSource": handler.url.pattern,
517
+ "regexFlags": escape_regex_flags(handler.url),
518
+ }
519
+ )
520
+ else:
521
+ all = True
522
+ if all:
523
+ return [{"glob": "**/*"}]
524
+ return patterns
525
+
526
+
527
+ to_snake_case_regex = re.compile("((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))")
528
+
529
+
530
+ def to_snake_case(name: str) -> str:
531
+ return to_snake_case_regex.sub(r"_\1", name).lower()
532
+
533
+
534
+ def make_dirs_for_file(path: Union[Path, str]) -> None:
535
+ if not os.path.isabs(path):
536
+ path = Path.cwd() / path
537
+ os.makedirs(os.path.dirname(path), exist_ok=True)
538
+
539
+
540
+ async def async_writefile(file: Union[str, Path], data: Union[str, bytes]) -> None:
541
+ def inner() -> None:
542
+ with open(file, "w" if isinstance(data, str) else "wb") as fh:
543
+ fh.write(data)
544
+
545
+ loop = asyncio.get_running_loop()
546
+ await loop.run_in_executor(None, inner)
547
+
548
+
549
+ async def async_readfile(file: Union[str, Path]) -> bytes:
550
+ def inner() -> bytes:
551
+ with open(file, "rb") as fh:
552
+ return fh.read()
553
+
554
+ loop = asyncio.get_running_loop()
555
+ return await loop.run_in_executor(None, inner)
556
+
557
+
558
+ T = TypeVar("T")
559
+
560
+
561
+ def to_impl(obj: T) -> T:
562
+ if hasattr(obj, "_impl_obj"):
563
+ return cast(Any, obj)._impl_obj
564
+ return obj
565
+
566
+
567
+ def object_to_array(obj: Optional[Dict]) -> Optional[List[NameValue]]:
568
+ if not obj:
569
+ return None
570
+ result = []
571
+ for key, value in obj.items():
572
+ result.append(NameValue(name=key, value=str(value)))
573
+ return result
574
+
575
+
576
+ def is_file_payload(value: Optional[Any]) -> bool:
577
+ return (
578
+ isinstance(value, dict)
579
+ and "name" in value
580
+ and "mimeType" in value
581
+ and "buffer" in value
582
+ )
583
+
584
+
585
+ TEXTUAL_MIME_TYPE = re.compile(
586
+ r"^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$"
587
+ )
588
+
589
+
590
+ def is_textual_mime_type(mime_type: str) -> bool:
591
+ return bool(TEXTUAL_MIME_TYPE.match(mime_type))
592
+
593
+
594
+ def to_milliseconds(
595
+ value: Union[float, datetime.timedelta, None],
596
+ ) -> Optional[float]:
597
+ if value is None:
598
+ return None
599
+ if isinstance(value, datetime.timedelta):
600
+ return value / datetime.timedelta(milliseconds=1)
601
+ return value