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,1017 @@
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
+
15
+ import asyncio
16
+ import base64
17
+ import json
18
+ import json as json_utils
19
+ import mimetypes
20
+ import re
21
+ from collections import defaultdict
22
+ from pathlib import Path
23
+ from types import SimpleNamespace
24
+ from typing import (
25
+ TYPE_CHECKING,
26
+ Any,
27
+ Callable,
28
+ Coroutine,
29
+ Dict,
30
+ List,
31
+ Optional,
32
+ TypedDict,
33
+ Union,
34
+ cast,
35
+ )
36
+ from urllib import parse
37
+
38
+ from playwright._impl._api_structures import (
39
+ ClientCertificate,
40
+ Headers,
41
+ HeadersArray,
42
+ RemoteAddr,
43
+ RequestSizes,
44
+ ResourceTiming,
45
+ SecurityDetails,
46
+ )
47
+ from playwright._impl._connection import (
48
+ ChannelOwner,
49
+ _capture_stack_trace,
50
+ from_channel,
51
+ from_nullable_channel,
52
+ )
53
+ from playwright._impl._errors import Error
54
+ from playwright._impl._event_context_manager import EventContextManagerImpl
55
+ from playwright._impl._helper import (
56
+ URLMatch,
57
+ WebSocketRouteHandlerCallback,
58
+ async_readfile,
59
+ locals_to_params,
60
+ url_matches,
61
+ )
62
+ from playwright._impl._str_utils import escape_regex_flags
63
+ from playwright._impl._waiter import Waiter
64
+
65
+ if TYPE_CHECKING: # pragma: no cover
66
+ from playwright._impl._browser_context import BrowserContext
67
+ from playwright._impl._fetch import APIResponse
68
+ from playwright._impl._frame import Frame
69
+ from playwright._impl._page import Page, Worker
70
+
71
+
72
+ class FallbackOverrideParameters(TypedDict, total=False):
73
+ url: Optional[str]
74
+ method: Optional[str]
75
+ headers: Optional[Dict[str, str]]
76
+ postData: Optional[Union[str, bytes]]
77
+
78
+
79
+ class SerializedFallbackOverrides:
80
+ def __init__(self) -> None:
81
+ self.url: Optional[str] = None
82
+ self.method: Optional[str] = None
83
+ self.headers: Optional[Dict[str, str]] = None
84
+ self.post_data_buffer: Optional[bytes] = None
85
+
86
+
87
+ def serialize_headers(headers: Dict[str, str]) -> HeadersArray:
88
+ return [
89
+ {"name": name, "value": value}
90
+ for name, value in headers.items()
91
+ if value is not None
92
+ ]
93
+
94
+
95
+ async def to_client_certificates_protocol(
96
+ clientCertificates: Optional[List[ClientCertificate]],
97
+ ) -> Optional[List[Dict[str, str]]]:
98
+ if not clientCertificates:
99
+ return None
100
+ out = []
101
+ for clientCertificate in clientCertificates:
102
+ out_record = {
103
+ "origin": clientCertificate["origin"],
104
+ }
105
+ if passphrase := clientCertificate.get("passphrase"):
106
+ out_record["passphrase"] = passphrase
107
+ if pfx := clientCertificate.get("pfx"):
108
+ out_record["pfx"] = base64.b64encode(pfx).decode()
109
+ if pfx_path := clientCertificate.get("pfxPath"):
110
+ out_record["pfx"] = base64.b64encode(
111
+ await async_readfile(pfx_path)
112
+ ).decode()
113
+ if cert := clientCertificate.get("cert"):
114
+ out_record["cert"] = base64.b64encode(cert).decode()
115
+ if cert_path := clientCertificate.get("certPath"):
116
+ out_record["cert"] = base64.b64encode(
117
+ await async_readfile(cert_path)
118
+ ).decode()
119
+ if key := clientCertificate.get("key"):
120
+ out_record["key"] = base64.b64encode(key).decode()
121
+ if key_path := clientCertificate.get("keyPath"):
122
+ out_record["key"] = base64.b64encode(
123
+ await async_readfile(key_path)
124
+ ).decode()
125
+ out.append(out_record)
126
+ return out
127
+
128
+
129
+ class Request(ChannelOwner):
130
+ def __init__(
131
+ self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
132
+ ) -> None:
133
+ super().__init__(parent, type, guid, initializer)
134
+ self._redirected_from: Optional["Request"] = from_nullable_channel(
135
+ initializer.get("redirectedFrom")
136
+ )
137
+ self._redirected_to: Optional["Request"] = None
138
+ if self._redirected_from:
139
+ self._redirected_from._redirected_to = self
140
+ self._failure_text: Optional[str] = None
141
+ self._timing: ResourceTiming = {
142
+ "startTime": 0,
143
+ "domainLookupStart": -1,
144
+ "domainLookupEnd": -1,
145
+ "connectStart": -1,
146
+ "secureConnectionStart": -1,
147
+ "connectEnd": -1,
148
+ "requestStart": -1,
149
+ "responseStart": -1,
150
+ "responseEnd": -1,
151
+ }
152
+ self._provisional_headers = RawHeaders(self._initializer["headers"])
153
+ self._all_headers_future: Optional[asyncio.Future[RawHeaders]] = None
154
+ self._fallback_overrides: SerializedFallbackOverrides = (
155
+ SerializedFallbackOverrides()
156
+ )
157
+ self._response: Optional["Response"] = None
158
+
159
+ def __repr__(self) -> str:
160
+ return f"<Request url={self.url!r} method={self.method!r}>"
161
+
162
+ def _apply_fallback_overrides(self, overrides: FallbackOverrideParameters) -> None:
163
+ self._fallback_overrides.url = overrides.get(
164
+ "url", self._fallback_overrides.url
165
+ )
166
+ self._fallback_overrides.method = overrides.get(
167
+ "method", self._fallback_overrides.method
168
+ )
169
+ self._fallback_overrides.headers = overrides.get(
170
+ "headers", self._fallback_overrides.headers
171
+ )
172
+ post_data = overrides.get("postData")
173
+ if isinstance(post_data, str):
174
+ self._fallback_overrides.post_data_buffer = post_data.encode()
175
+ elif isinstance(post_data, bytes):
176
+ self._fallback_overrides.post_data_buffer = post_data
177
+ elif post_data is not None:
178
+ self._fallback_overrides.post_data_buffer = json.dumps(post_data).encode()
179
+
180
+ @property
181
+ def url(self) -> str:
182
+ return cast(str, self._fallback_overrides.url or self._initializer["url"])
183
+
184
+ @property
185
+ def resource_type(self) -> str:
186
+ return self._initializer["resourceType"]
187
+
188
+ @property
189
+ def service_worker(self) -> Optional["Worker"]:
190
+ return cast(
191
+ Optional["Worker"],
192
+ from_nullable_channel(self._initializer.get("serviceWorker")),
193
+ )
194
+
195
+ @property
196
+ def method(self) -> str:
197
+ return cast(str, self._fallback_overrides.method or self._initializer["method"])
198
+
199
+ async def sizes(self) -> RequestSizes:
200
+ response = await self.response()
201
+ if not response:
202
+ raise Error("Unable to fetch sizes for failed request")
203
+ return await response._channel.send(
204
+ "sizes",
205
+ None,
206
+ )
207
+
208
+ @property
209
+ def post_data(self) -> Optional[str]:
210
+ data = self._fallback_overrides.post_data_buffer
211
+ if data is not None:
212
+ return data.decode()
213
+ base64_post_data = self._initializer.get("postData")
214
+ if base64_post_data is not None:
215
+ return base64.b64decode(base64_post_data).decode()
216
+ return None
217
+
218
+ @property
219
+ def post_data_json(self) -> Optional[Any]:
220
+ post_data = self.post_data
221
+ if not post_data:
222
+ return None
223
+ content_type = self.headers.get("content-type")
224
+ if content_type and "application/x-www-form-urlencoded" in content_type:
225
+ return dict(parse.parse_qsl(post_data))
226
+ try:
227
+ return json.loads(post_data)
228
+ except Exception:
229
+ raise Error(f"POST data is not a valid JSON object: {post_data}")
230
+
231
+ @property
232
+ def post_data_buffer(self) -> Optional[bytes]:
233
+ if self._fallback_overrides.post_data_buffer:
234
+ return self._fallback_overrides.post_data_buffer
235
+ if self._initializer.get("postData"):
236
+ return base64.b64decode(self._initializer["postData"])
237
+ return None
238
+
239
+ async def response(self) -> Optional["Response"]:
240
+ return from_nullable_channel(
241
+ await self._channel.send(
242
+ "response",
243
+ None,
244
+ )
245
+ )
246
+
247
+ @property
248
+ def existing_response(self) -> Optional["Response"]:
249
+ return self._response
250
+
251
+ @property
252
+ def frame(self) -> "Frame":
253
+ if not self._initializer.get("frame"):
254
+ raise Error("Service Worker requests do not have an associated frame.")
255
+ frame = cast("Frame", from_channel(self._initializer["frame"]))
256
+ if not frame._page:
257
+ raise Error(
258
+ "\n".join(
259
+ [
260
+ "Frame for this navigation request is not available, because the request",
261
+ "was issued before the frame is created. You can check whether the request",
262
+ "is a navigation request by calling isNavigationRequest() method.",
263
+ ]
264
+ )
265
+ )
266
+ return frame
267
+
268
+ def is_navigation_request(self) -> bool:
269
+ return self._initializer["isNavigationRequest"]
270
+
271
+ @property
272
+ def redirected_from(self) -> Optional["Request"]:
273
+ return self._redirected_from
274
+
275
+ @property
276
+ def redirected_to(self) -> Optional["Request"]:
277
+ return self._redirected_to
278
+
279
+ @property
280
+ def failure(self) -> Optional[str]:
281
+ return self._failure_text
282
+
283
+ @property
284
+ def timing(self) -> ResourceTiming:
285
+ return self._timing
286
+
287
+ def _set_response_end_timing(self, response_end_timing: float) -> None:
288
+ self._timing["responseEnd"] = response_end_timing
289
+ if self._timing["responseStart"] == -1:
290
+ self._timing["responseStart"] = response_end_timing
291
+
292
+ @property
293
+ def headers(self) -> Headers:
294
+ override = self._fallback_overrides.headers
295
+ if override:
296
+ return RawHeaders._from_headers_dict_lossy(override).headers()
297
+ return self._provisional_headers.headers()
298
+
299
+ async def all_headers(self) -> Headers:
300
+ return (await self._actual_headers()).headers()
301
+
302
+ async def headers_array(self) -> HeadersArray:
303
+ return (await self._actual_headers()).headers_array()
304
+
305
+ async def header_value(self, name: str) -> Optional[str]:
306
+ return (await self._actual_headers()).get(name)
307
+
308
+ async def _actual_headers(self) -> "RawHeaders":
309
+ override = self._fallback_overrides.headers
310
+ if override:
311
+ return RawHeaders(serialize_headers(override))
312
+ if not self._all_headers_future:
313
+ self._all_headers_future = asyncio.Future()
314
+ headers = await self._channel.send(
315
+ "rawRequestHeaders", None, is_internal=True
316
+ )
317
+ self._all_headers_future.set_result(RawHeaders(headers))
318
+ return await self._all_headers_future
319
+
320
+ def _target_closed_future(self) -> asyncio.Future:
321
+ frame = cast(
322
+ Optional["Frame"], from_nullable_channel(self._initializer.get("frame"))
323
+ )
324
+ if not frame:
325
+ return asyncio.Future()
326
+ page = frame._page
327
+ if not page:
328
+ return asyncio.Future()
329
+ return page._closed_or_crashed_future
330
+
331
+ def _safe_page(self) -> "Optional[Page]":
332
+ frame = from_nullable_channel(self._initializer.get("frame"))
333
+ if not frame:
334
+ return None
335
+ return cast("Frame", frame)._page
336
+
337
+
338
+ class Route(ChannelOwner):
339
+ def __init__(
340
+ self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
341
+ ) -> None:
342
+ super().__init__(parent, type, guid, initializer)
343
+ self._handling_future: Optional[asyncio.Future["bool"]] = None
344
+ self._context: "BrowserContext" = cast("BrowserContext", None)
345
+ self._did_throw = False
346
+
347
+ def _start_handling(self) -> "asyncio.Future[bool]":
348
+ self._handling_future = asyncio.Future()
349
+ return self._handling_future
350
+
351
+ def _report_handled(self, done: bool) -> None:
352
+ chain = self._handling_future
353
+ assert chain
354
+ self._handling_future = None
355
+ chain.set_result(done)
356
+
357
+ def _check_not_handled(self) -> None:
358
+ if not self._handling_future:
359
+ raise Error("Route is already handled!")
360
+
361
+ def __repr__(self) -> str:
362
+ return f"<Route request={self.request}>"
363
+
364
+ @property
365
+ def request(self) -> Request:
366
+ return from_channel(self._initializer["request"])
367
+
368
+ async def abort(self, errorCode: str = None) -> None:
369
+ await self._handle_route(
370
+ lambda: self._race_with_page_close(
371
+ self._channel.send(
372
+ "abort",
373
+ None,
374
+ {
375
+ "errorCode": errorCode,
376
+ },
377
+ )
378
+ )
379
+ )
380
+
381
+ async def fulfill(
382
+ self,
383
+ status: int = None,
384
+ headers: Dict[str, str] = None,
385
+ body: Union[str, bytes] = None,
386
+ json: Any = None,
387
+ path: Union[str, Path] = None,
388
+ contentType: str = None,
389
+ response: "APIResponse" = None,
390
+ ) -> None:
391
+ await self._handle_route(
392
+ lambda: self._inner_fulfill(
393
+ status, headers, body, json, path, contentType, response
394
+ )
395
+ )
396
+
397
+ async def _inner_fulfill(
398
+ self,
399
+ status: int = None,
400
+ headers: Dict[str, str] = None,
401
+ body: Union[str, bytes] = None,
402
+ json: Any = None,
403
+ path: Union[str, Path] = None,
404
+ contentType: str = None,
405
+ response: "APIResponse" = None,
406
+ ) -> None:
407
+ params = locals_to_params(locals())
408
+
409
+ if json is not None:
410
+ if body is not None:
411
+ raise Error("Can specify either body or json parameters")
412
+ body = json_utils.dumps(json)
413
+
414
+ if response:
415
+ del params["response"]
416
+ params["status"] = (
417
+ params["status"] if params.get("status") else response.status
418
+ )
419
+ params["headers"] = (
420
+ params["headers"] if params.get("headers") else response.headers
421
+ )
422
+ from playwright._impl._fetch import APIResponse
423
+
424
+ if body is None and path is None and isinstance(response, APIResponse):
425
+ if response._request._connection is self._connection:
426
+ params["fetchResponseUid"] = response._fetch_uid
427
+ else:
428
+ body = await response.body()
429
+
430
+ length = 0
431
+ if isinstance(body, str):
432
+ params["body"] = body
433
+ params["isBase64"] = False
434
+ length = len(body.encode())
435
+ elif isinstance(body, bytes):
436
+ params["body"] = base64.b64encode(body).decode()
437
+ params["isBase64"] = True
438
+ length = len(body)
439
+ elif path:
440
+ del params["path"]
441
+ file_content = Path(path).read_bytes()
442
+ params["body"] = base64.b64encode(file_content).decode()
443
+ params["isBase64"] = True
444
+ length = len(file_content)
445
+
446
+ headers = {k.lower(): str(v) for k, v in params.get("headers", {}).items()}
447
+ if params.get("contentType"):
448
+ headers["content-type"] = params["contentType"]
449
+ elif json:
450
+ headers["content-type"] = "application/json"
451
+ elif path:
452
+ headers["content-type"] = (
453
+ mimetypes.guess_type(str(Path(path)))[0] or "application/octet-stream"
454
+ )
455
+ if length and "content-length" not in headers:
456
+ headers["content-length"] = str(length)
457
+ params["headers"] = serialize_headers(headers)
458
+
459
+ await self._race_with_page_close(self._channel.send("fulfill", None, params))
460
+
461
+ async def _handle_route(self, callback: Callable) -> None:
462
+ self._check_not_handled()
463
+ try:
464
+ await callback()
465
+ self._report_handled(True)
466
+ except Exception as e:
467
+ self._did_throw = True
468
+ raise e
469
+
470
+ async def fetch(
471
+ self,
472
+ url: str = None,
473
+ method: str = None,
474
+ headers: Dict[str, str] = None,
475
+ postData: Union[Any, str, bytes] = None,
476
+ maxRedirects: int = None,
477
+ maxRetries: int = None,
478
+ timeout: float = None,
479
+ ) -> "APIResponse":
480
+ return await self._connection.wrap_api_call(
481
+ lambda: self._context.request._inner_fetch(
482
+ self.request,
483
+ url,
484
+ method,
485
+ headers,
486
+ postData,
487
+ maxRedirects=maxRedirects,
488
+ maxRetries=maxRetries,
489
+ timeout=timeout,
490
+ )
491
+ )
492
+
493
+ async def fallback(
494
+ self,
495
+ url: str = None,
496
+ method: str = None,
497
+ headers: Dict[str, str] = None,
498
+ postData: Union[Any, str, bytes] = None,
499
+ ) -> None:
500
+ overrides = cast(FallbackOverrideParameters, locals_to_params(locals()))
501
+ self._check_not_handled()
502
+ self.request._apply_fallback_overrides(overrides)
503
+ self._report_handled(False)
504
+
505
+ async def continue_(
506
+ self,
507
+ url: str = None,
508
+ method: str = None,
509
+ headers: Dict[str, str] = None,
510
+ postData: Union[Any, str, bytes] = None,
511
+ ) -> None:
512
+ overrides = cast(FallbackOverrideParameters, locals_to_params(locals()))
513
+
514
+ async def _inner() -> None:
515
+ self.request._apply_fallback_overrides(overrides)
516
+ await self._inner_continue(False)
517
+
518
+ return await self._handle_route(_inner)
519
+
520
+ async def _inner_continue(self, is_fallback: bool = False) -> None:
521
+ options = self.request._fallback_overrides
522
+ await self._race_with_page_close(
523
+ self._channel.send(
524
+ "continue",
525
+ None,
526
+ {
527
+ "url": options.url,
528
+ "method": options.method,
529
+ "headers": (
530
+ serialize_headers(options.headers) if options.headers else None
531
+ ),
532
+ "postData": (
533
+ base64.b64encode(options.post_data_buffer).decode()
534
+ if options.post_data_buffer is not None
535
+ else None
536
+ ),
537
+ "isFallback": is_fallback,
538
+ },
539
+ )
540
+ )
541
+
542
+ async def _redirected_navigation_request(self, url: str) -> None:
543
+ await self._handle_route(
544
+ lambda: self._race_with_page_close(
545
+ self._channel.send("redirectNavigationRequest", None, {"url": url})
546
+ )
547
+ )
548
+
549
+ async def _race_with_page_close(self, future: Coroutine) -> None:
550
+ fut = asyncio.create_task(future)
551
+ # Rewrite the user's stack to the new task which runs in the background.
552
+ setattr(
553
+ fut,
554
+ "__pw_stack__",
555
+ getattr(asyncio.current_task(self._loop), "__pw_stack__", None)
556
+ or _capture_stack_trace(),
557
+ )
558
+ target_closed_future = self.request._target_closed_future()
559
+ await asyncio.wait(
560
+ [fut, target_closed_future],
561
+ return_when=asyncio.FIRST_COMPLETED,
562
+ )
563
+ if fut.done() and fut.exception():
564
+ raise cast(BaseException, fut.exception())
565
+ if target_closed_future.done():
566
+ await asyncio.gather(fut, return_exceptions=True)
567
+
568
+
569
+ class ServerWebSocketRoute:
570
+ def __init__(self, ws: "WebSocketRoute"):
571
+ self._ws = ws
572
+
573
+ def on_message(self, handler: Callable[[Union[str, bytes]], Any]) -> None:
574
+ self._ws._on_server_message = handler
575
+
576
+ def on_close(self, handler: Callable[[Optional[int], Optional[str]], Any]) -> None:
577
+ self._ws._on_server_close = handler
578
+
579
+ def connect_to_server(self) -> None:
580
+ raise NotImplementedError(
581
+ "connectToServer must be called on the page-side WebSocketRoute"
582
+ )
583
+
584
+ @property
585
+ def url(self) -> str:
586
+ return self._ws._initializer["url"]
587
+
588
+ @property
589
+ def protocols(self) -> List[str]:
590
+ return list(self._ws._initializer.get("protocols", []))
591
+
592
+ def close(self, code: int = None, reason: str = None) -> None:
593
+ self._ws._channel.send_may_fail(
594
+ "closeServer",
595
+ None,
596
+ {
597
+ "code": code,
598
+ "reason": reason,
599
+ "wasClean": True,
600
+ },
601
+ )
602
+
603
+ def send(self, message: Union[str, bytes]) -> None:
604
+ if isinstance(message, str):
605
+ self._ws._channel.send_may_fail(
606
+ "sendToServer", None, {"message": message, "isBase64": False}
607
+ )
608
+ else:
609
+ self._ws._channel.send_may_fail(
610
+ "sendToServer",
611
+ None,
612
+ {"message": base64.b64encode(message).decode(), "isBase64": True},
613
+ )
614
+
615
+
616
+ class WebSocketRoute(ChannelOwner):
617
+ def __init__(
618
+ self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
619
+ ) -> None:
620
+ super().__init__(parent, type, guid, initializer)
621
+ self._on_page_message: Optional[Callable[[Union[str, bytes]], Any]] = None
622
+ self._on_page_close: Optional[Callable[[Optional[int], Optional[str]], Any]] = (
623
+ None
624
+ )
625
+ self._on_server_message: Optional[Callable[[Union[str, bytes]], Any]] = None
626
+ self._on_server_close: Optional[
627
+ Callable[[Optional[int], Optional[str]], Any]
628
+ ] = None
629
+ self._server = ServerWebSocketRoute(self)
630
+ self._connected = False
631
+
632
+ self._channel.on("messageFromPage", self._channel_message_from_page)
633
+ self._channel.on("messageFromServer", self._channel_message_from_server)
634
+ self._channel.on("closePage", self._channel_close_page)
635
+ self._channel.on("closeServer", self._channel_close_server)
636
+
637
+ def _channel_message_from_page(self, event: Dict) -> None:
638
+ if self._on_page_message:
639
+ self._on_page_message(
640
+ base64.b64decode(event["message"])
641
+ if event["isBase64"]
642
+ else event["message"]
643
+ )
644
+ elif self._connected:
645
+ self._channel.send_may_fail("sendToServer", None, event)
646
+
647
+ def _channel_message_from_server(self, event: Dict) -> None:
648
+ if self._on_server_message:
649
+ self._on_server_message(
650
+ base64.b64decode(event["message"])
651
+ if event["isBase64"]
652
+ else event["message"]
653
+ )
654
+ else:
655
+ self._channel.send_may_fail("sendToPage", None, event)
656
+
657
+ def _channel_close_page(self, event: Dict) -> None:
658
+ if self._on_page_close:
659
+ self._on_page_close(event["code"], event["reason"])
660
+ else:
661
+ self._channel.send_may_fail("closeServer", None, event)
662
+
663
+ def _channel_close_server(self, event: Dict) -> None:
664
+ if self._on_server_close:
665
+ self._on_server_close(event["code"], event["reason"])
666
+ else:
667
+ self._channel.send_may_fail("closePage", None, event)
668
+
669
+ @property
670
+ def url(self) -> str:
671
+ return self._initializer["url"]
672
+
673
+ @property
674
+ def protocols(self) -> List[str]:
675
+ return list(self._initializer.get("protocols", []))
676
+
677
+ async def close(self, code: int = None, reason: str = None) -> None:
678
+ try:
679
+ await self._channel.send(
680
+ "closePage", None, {"code": code, "reason": reason, "wasClean": True}
681
+ )
682
+ except Exception:
683
+ pass
684
+
685
+ def connect_to_server(self) -> "WebSocketRoute":
686
+ if self._connected:
687
+ raise Error("Already connected to the server")
688
+ self._connected = True
689
+ self._channel.send_may_fail("connect", None)
690
+ return cast("WebSocketRoute", self._server)
691
+
692
+ def send(self, message: Union[str, bytes]) -> None:
693
+ if isinstance(message, str):
694
+ self._channel.send_may_fail(
695
+ "sendToPage", None, {"message": message, "isBase64": False}
696
+ )
697
+ else:
698
+ self._channel.send_may_fail(
699
+ "sendToPage",
700
+ None,
701
+ {
702
+ "message": base64.b64encode(message).decode(),
703
+ "isBase64": True,
704
+ },
705
+ )
706
+
707
+ def on_message(self, handler: Callable[[Union[str, bytes]], Any]) -> None:
708
+ self._on_page_message = handler
709
+
710
+ def on_close(self, handler: Callable[[Optional[int], Optional[str]], Any]) -> None:
711
+ self._on_page_close = handler
712
+
713
+ async def _after_handle(self) -> None:
714
+ if self._connected:
715
+ return
716
+ # Ensure that websocket is "open" and can send messages without an actual server connection.
717
+ try:
718
+ await self._channel.send(
719
+ "ensureOpened",
720
+ None,
721
+ )
722
+ except Exception:
723
+ pass
724
+
725
+
726
+ class WebSocketRouteHandler:
727
+ def __init__(
728
+ self,
729
+ base_url: Optional[str],
730
+ url: URLMatch,
731
+ handler: WebSocketRouteHandlerCallback,
732
+ ):
733
+ self._base_url = base_url
734
+ self.url = url
735
+ self.handler = handler
736
+
737
+ @staticmethod
738
+ def prepare_interception_patterns(
739
+ handlers: List["WebSocketRouteHandler"],
740
+ ) -> List[dict]:
741
+ patterns = []
742
+ all_urls = False
743
+ for handler in handlers:
744
+ if isinstance(handler.url, str):
745
+ patterns.append({"glob": handler.url})
746
+ elif isinstance(handler.url, re.Pattern):
747
+ patterns.append(
748
+ {
749
+ "regexSource": handler.url.pattern,
750
+ "regexFlags": escape_regex_flags(handler.url),
751
+ }
752
+ )
753
+ else:
754
+ all_urls = True
755
+
756
+ if all_urls:
757
+ return [{"glob": "**/*"}]
758
+ return patterns
759
+
760
+ def matches(self, ws_url: str) -> bool:
761
+ return url_matches(self._base_url, ws_url, self.url, True)
762
+
763
+ async def handle(self, websocket_route: "WebSocketRoute") -> None:
764
+ coro_or_future = self.handler(websocket_route)
765
+ if asyncio.iscoroutine(coro_or_future):
766
+ await coro_or_future
767
+ await websocket_route._after_handle()
768
+
769
+
770
+ class Response(ChannelOwner):
771
+ def __init__(
772
+ self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
773
+ ) -> None:
774
+ super().__init__(parent, type, guid, initializer)
775
+ self._request: Request = from_channel(self._initializer["request"])
776
+ self._request._response = self
777
+ timing = self._initializer["timing"]
778
+ self._request._timing["startTime"] = timing["startTime"]
779
+ self._request._timing["domainLookupStart"] = timing["domainLookupStart"]
780
+ self._request._timing["domainLookupEnd"] = timing["domainLookupEnd"]
781
+ self._request._timing["connectStart"] = timing["connectStart"]
782
+ self._request._timing["secureConnectionStart"] = timing["secureConnectionStart"]
783
+ self._request._timing["connectEnd"] = timing["connectEnd"]
784
+ self._request._timing["requestStart"] = timing["requestStart"]
785
+ self._request._timing["responseStart"] = timing["responseStart"]
786
+ self._provisional_headers = RawHeaders(
787
+ cast(HeadersArray, self._initializer["headers"])
788
+ )
789
+ self._raw_headers_future: Optional[asyncio.Future[RawHeaders]] = None
790
+ self._finished_future: asyncio.Future[bool] = asyncio.Future()
791
+
792
+ def __repr__(self) -> str:
793
+ return f"<Response url={self.url!r} request={self.request}>"
794
+
795
+ @property
796
+ def url(self) -> str:
797
+ return self._initializer["url"]
798
+
799
+ @property
800
+ def ok(self) -> bool:
801
+ # Status 0 is for file:// URLs
802
+ return self._initializer["status"] == 0 or (
803
+ self._initializer["status"] >= 200 and self._initializer["status"] <= 299
804
+ )
805
+
806
+ @property
807
+ def status(self) -> int:
808
+ return self._initializer["status"]
809
+
810
+ @property
811
+ def status_text(self) -> str:
812
+ return self._initializer["statusText"]
813
+
814
+ @property
815
+ def headers(self) -> Headers:
816
+ return self._provisional_headers.headers()
817
+
818
+ @property
819
+ def from_service_worker(self) -> bool:
820
+ return self._initializer["fromServiceWorker"]
821
+
822
+ async def all_headers(self) -> Headers:
823
+ return (await self._actual_headers()).headers()
824
+
825
+ async def headers_array(self) -> HeadersArray:
826
+ return (await self._actual_headers()).headers_array()
827
+
828
+ async def header_value(self, name: str) -> Optional[str]:
829
+ return (await self._actual_headers()).get(name)
830
+
831
+ async def header_values(self, name: str) -> List[str]:
832
+ return (await self._actual_headers()).get_all(name)
833
+
834
+ async def _actual_headers(self) -> "RawHeaders":
835
+ if not self._raw_headers_future:
836
+ self._raw_headers_future = asyncio.Future()
837
+ headers = cast(
838
+ HeadersArray,
839
+ await self._channel.send(
840
+ "rawResponseHeaders",
841
+ None,
842
+ ),
843
+ )
844
+ self._raw_headers_future.set_result(RawHeaders(headers))
845
+ return await self._raw_headers_future
846
+
847
+ async def server_addr(self) -> Optional[RemoteAddr]:
848
+ return await self._channel.send(
849
+ "serverAddr",
850
+ None,
851
+ )
852
+
853
+ async def security_details(self) -> Optional[SecurityDetails]:
854
+ return await self._channel.send(
855
+ "securityDetails",
856
+ None,
857
+ )
858
+
859
+ async def http_version(self) -> str:
860
+ return await self._channel.send(
861
+ "httpVersion",
862
+ None,
863
+ )
864
+
865
+ async def finished(self) -> None:
866
+ async def on_finished() -> None:
867
+ await self._request._target_closed_future()
868
+ raise Error("Target closed")
869
+
870
+ on_finished_task = asyncio.create_task(on_finished())
871
+ await asyncio.wait(
872
+ cast(
873
+ List[Union[asyncio.Task, asyncio.Future]],
874
+ [self._finished_future, on_finished_task],
875
+ ),
876
+ return_when=asyncio.FIRST_COMPLETED,
877
+ )
878
+ if on_finished_task.done():
879
+ await on_finished_task
880
+
881
+ async def body(self) -> bytes:
882
+ binary = await self._channel.send(
883
+ "body",
884
+ None,
885
+ )
886
+ return base64.b64decode(binary)
887
+
888
+ async def text(self) -> str:
889
+ content = await self.body()
890
+ return content.decode()
891
+
892
+ async def json(self) -> Any:
893
+ return json.loads(await self.text())
894
+
895
+ @property
896
+ def request(self) -> Request:
897
+ return self._request
898
+
899
+ @property
900
+ def frame(self) -> "Frame":
901
+ return self._request.frame
902
+
903
+
904
+ class WebSocket(ChannelOwner):
905
+ Events = SimpleNamespace(
906
+ Close="close",
907
+ FrameReceived="framereceived",
908
+ FrameSent="framesent",
909
+ Error="socketerror",
910
+ )
911
+
912
+ def __init__(
913
+ self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
914
+ ) -> None:
915
+ super().__init__(parent, type, guid, initializer)
916
+ self._is_closed = False
917
+ self._page = cast("Page", parent)
918
+ self._channel.on(
919
+ "frameSent",
920
+ lambda params: self._on_frame_sent(params["opcode"], params["data"]),
921
+ )
922
+ self._channel.on(
923
+ "frameReceived",
924
+ lambda params: self._on_frame_received(params["opcode"], params["data"]),
925
+ )
926
+ self._channel.on(
927
+ "socketError",
928
+ lambda params: self.emit(WebSocket.Events.Error, params["error"]),
929
+ )
930
+ self._channel.on("close", lambda params: self._on_close())
931
+
932
+ def __repr__(self) -> str:
933
+ return f"<WebSocket url={self.url!r}>"
934
+
935
+ @property
936
+ def url(self) -> str:
937
+ return self._initializer["url"]
938
+
939
+ def expect_event(
940
+ self,
941
+ event: str,
942
+ predicate: Callable = None,
943
+ timeout: float = None,
944
+ ) -> EventContextManagerImpl:
945
+ if timeout is None:
946
+ timeout = cast(Any, self._parent)._timeout_settings.timeout()
947
+ waiter = Waiter(self, f"web_socket.expect_event({event})")
948
+ waiter.reject_on_timeout(
949
+ cast(float, timeout),
950
+ f'Timeout {timeout}ms exceeded while waiting for event "{event}"',
951
+ )
952
+ if event != WebSocket.Events.Close:
953
+ waiter.reject_on_event(self, WebSocket.Events.Close, Error("Socket closed"))
954
+ if event != WebSocket.Events.Error:
955
+ waiter.reject_on_event(self, WebSocket.Events.Error, Error("Socket error"))
956
+ waiter.reject_on_event(
957
+ self._page, "close", lambda: self._page._close_error_with_reason()
958
+ )
959
+ waiter.wait_for_event(self, event, predicate)
960
+ return EventContextManagerImpl(waiter.result())
961
+
962
+ async def wait_for_event(
963
+ self, event: str, predicate: Callable = None, timeout: float = None
964
+ ) -> Any:
965
+ async with self.expect_event(event, predicate, timeout) as event_info:
966
+ pass
967
+ return await event_info
968
+
969
+ def _on_frame_sent(self, opcode: int, data: str) -> None:
970
+ if opcode == 2:
971
+ self.emit(WebSocket.Events.FrameSent, base64.b64decode(data))
972
+ elif opcode == 1:
973
+ self.emit(WebSocket.Events.FrameSent, data)
974
+
975
+ def _on_frame_received(self, opcode: int, data: str) -> None:
976
+ if opcode == 2:
977
+ self.emit(WebSocket.Events.FrameReceived, base64.b64decode(data))
978
+ elif opcode == 1:
979
+ self.emit(WebSocket.Events.FrameReceived, data)
980
+
981
+ def is_closed(self) -> bool:
982
+ return self._is_closed
983
+
984
+ def _on_close(self) -> None:
985
+ self._is_closed = True
986
+ self.emit(WebSocket.Events.Close, self)
987
+
988
+
989
+ class RawHeaders:
990
+ def __init__(self, headers: HeadersArray) -> None:
991
+ self._headers_array = headers
992
+ self._headers_map: Dict[str, Dict[str, bool]] = defaultdict(dict)
993
+ for header in headers:
994
+ self._headers_map[header["name"].lower()][header["value"]] = True
995
+
996
+ @staticmethod
997
+ def _from_headers_dict_lossy(headers: Dict[str, str]) -> "RawHeaders":
998
+ return RawHeaders(serialize_headers(headers))
999
+
1000
+ def get(self, name: str) -> Optional[str]:
1001
+ values = self.get_all(name)
1002
+ if not values:
1003
+ return None
1004
+ separator = "\n" if name.lower() == "set-cookie" else ", "
1005
+ return separator.join(values)
1006
+
1007
+ def get_all(self, name: str) -> List[str]:
1008
+ return list(self._headers_map[name.lower()].keys())
1009
+
1010
+ def headers(self) -> Dict[str, str]:
1011
+ result = {}
1012
+ for name in self._headers_map.keys():
1013
+ result[name] = cast(str, self.get(name))
1014
+ return result
1015
+
1016
+ def headers_array(self) -> HeadersArray:
1017
+ return self._headers_array