playwright-browserstack 1.62.0__py3-none-win_amd64.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,764 @@
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 collections.abc
17
+ import contextvars
18
+ import datetime
19
+ import inspect
20
+ import sys
21
+ import traceback
22
+ from pathlib import Path
23
+ from typing import (
24
+ TYPE_CHECKING,
25
+ Any,
26
+ Callable,
27
+ Dict,
28
+ List,
29
+ Mapping,
30
+ Optional,
31
+ Tuple,
32
+ TypedDict,
33
+ Union,
34
+ cast,
35
+ )
36
+
37
+ from pyee import EventEmitter
38
+ from pyee.asyncio import AsyncIOEventEmitter
39
+
40
+ import playwright
41
+ import playwright._impl._impl_to_api_mapping
42
+ from playwright._impl._errors import TargetClosedError, rewrite_error
43
+ from playwright._impl._greenlets import EventGreenlet
44
+ from playwright._impl._helper import (
45
+ Error,
46
+ ParsedMessagePayload,
47
+ create_task_and_ignore_exception,
48
+ parse_error,
49
+ )
50
+ from playwright._impl._transport import Transport
51
+
52
+ if TYPE_CHECKING:
53
+ from playwright._impl._local_utils import LocalUtils
54
+ from playwright._impl._playwright import Playwright
55
+
56
+ TimeoutCalculator = Optional[Callable[[Optional[float]], float]]
57
+ _PLAYWRIGHT_MODULE_PATH = str(Path(playwright.__file__).parents[0])
58
+
59
+
60
+ class Channel(AsyncIOEventEmitter):
61
+ def __init__(self, connection: "Connection", object: "ChannelOwner") -> None:
62
+ super().__init__()
63
+ self._connection = connection
64
+ self._guid = object._guid
65
+ self._object = object
66
+ self.on("error", lambda exc: self._connection._on_event_listener_error(exc))
67
+
68
+ async def send(
69
+ self,
70
+ method: str,
71
+ timeout_calculator: TimeoutCalculator,
72
+ params: Dict = None,
73
+ is_internal: bool = False,
74
+ title: str = None,
75
+ ) -> Any:
76
+ return await self._connection.wrap_api_call(
77
+ lambda: self._inner_send(method, timeout_calculator, params, False),
78
+ is_internal,
79
+ title,
80
+ )
81
+
82
+ def send_may_fail(
83
+ self,
84
+ method: str,
85
+ timeout_calculator: TimeoutCalculator,
86
+ params: Dict = None,
87
+ is_internal: bool = False,
88
+ title: str = None,
89
+ ) -> None:
90
+ create_task_and_ignore_exception(
91
+ self._connection._loop,
92
+ self.send(method, timeout_calculator, params, is_internal, title),
93
+ )
94
+
95
+ async def send_return_as_dict(
96
+ self,
97
+ method: str,
98
+ timeout_calculator: TimeoutCalculator,
99
+ params: Dict = None,
100
+ is_internal: bool = False,
101
+ title: str = None,
102
+ ) -> Any:
103
+ return await self._connection.wrap_api_call(
104
+ lambda: self._inner_send(method, timeout_calculator, params, True),
105
+ is_internal,
106
+ title,
107
+ )
108
+
109
+ def send_no_reply(
110
+ self,
111
+ method: str,
112
+ timeout_calculator: TimeoutCalculator,
113
+ params: Dict = None,
114
+ is_internal: bool = False,
115
+ title: str = None,
116
+ ) -> None:
117
+ # No reply messages are used to e.g. __waitInfo__(after).
118
+ augmented_params, timeout = _augment_params(params, timeout_calculator)
119
+ self._connection.wrap_api_call_sync(
120
+ lambda: self._connection._send_message_to_server(
121
+ self._object,
122
+ method,
123
+ augmented_params,
124
+ timeout,
125
+ True,
126
+ ),
127
+ is_internal,
128
+ title,
129
+ )
130
+
131
+ async def _inner_send(
132
+ self,
133
+ method: str,
134
+ timeout_calculator: TimeoutCalculator,
135
+ params: Optional[Dict],
136
+ return_as_dict: bool,
137
+ ) -> Any:
138
+ if self._connection._error:
139
+ error = self._connection._error
140
+ self._connection._error = None
141
+ raise error
142
+ augmented_params, timeout = _augment_params(params, timeout_calculator)
143
+ callback = self._connection._send_message_to_server(
144
+ self._object, method, augmented_params, timeout
145
+ )
146
+ try:
147
+ done, _ = await asyncio.wait(
148
+ {
149
+ self._connection._transport.on_error_future,
150
+ callback.future,
151
+ },
152
+ return_when=asyncio.FIRST_COMPLETED,
153
+ )
154
+ except asyncio.CancelledError as exc:
155
+ await self._connection._abort(
156
+ self._object,
157
+ callback,
158
+ str(exc) or "Task was cancelled",
159
+ )
160
+ raise
161
+ if not callback.future.done():
162
+ callback.future.cancel()
163
+ result = next(iter(done)).result()
164
+ # Protocol now has named return values, assume result is one level deeper unless
165
+ # there is explicit ambiguity.
166
+ if not result:
167
+ return None
168
+ assert isinstance(result, dict)
169
+ if return_as_dict:
170
+ return result
171
+ if len(result) == 0:
172
+ return None
173
+ assert len(result) == 1
174
+ key = next(iter(result))
175
+ return result[key]
176
+
177
+
178
+ class ChannelOwner(AsyncIOEventEmitter):
179
+ def __init__(
180
+ self,
181
+ parent: Union["ChannelOwner", "Connection"],
182
+ type: str,
183
+ guid: str,
184
+ initializer: Dict,
185
+ ) -> None:
186
+ super().__init__(loop=parent._loop)
187
+ self._loop: asyncio.AbstractEventLoop = parent._loop
188
+ self._dispatcher_fiber: Any = parent._dispatcher_fiber
189
+ self._type = type
190
+ self._guid: str = guid
191
+ self._connection: Connection = (
192
+ parent._connection if isinstance(parent, ChannelOwner) else parent
193
+ )
194
+ self._parent: Optional[ChannelOwner] = (
195
+ parent if isinstance(parent, ChannelOwner) else None
196
+ )
197
+ self._objects: Dict[str, "ChannelOwner"] = {}
198
+ self._channel: Channel = Channel(self._connection, self)
199
+ self._initializer = initializer
200
+ self._was_collected = False
201
+
202
+ self._connection._objects[guid] = self
203
+ if self._parent:
204
+ self._parent._objects[guid] = self
205
+
206
+ self._event_to_subscription_mapping: Dict[str, str] = {}
207
+
208
+ def _dispose(self, reason: Optional[str]) -> None:
209
+ # Clean up from parent and connection.
210
+ if self._parent:
211
+ del self._parent._objects[self._guid]
212
+ del self._connection._objects[self._guid]
213
+ self._was_collected = reason == "gc"
214
+
215
+ # Dispose all children.
216
+ for object in list(self._objects.values()):
217
+ object._dispose(reason)
218
+ self._objects.clear()
219
+
220
+ def _adopt(self, child: "ChannelOwner") -> None:
221
+ del cast("ChannelOwner", child._parent)._objects[child._guid]
222
+ self._objects[child._guid] = child
223
+ child._parent = self
224
+
225
+ def _set_event_to_subscription_mapping(self, mapping: Dict[str, str]) -> None:
226
+ self._event_to_subscription_mapping = mapping
227
+
228
+ def _update_subscription(self, event: str, enabled: bool) -> None:
229
+ protocol_event = self._event_to_subscription_mapping.get(event)
230
+ if protocol_event:
231
+ self._connection.wrap_api_call_sync(
232
+ lambda: self._channel.send_no_reply(
233
+ "updateSubscription",
234
+ None,
235
+ {"event": protocol_event, "enabled": enabled},
236
+ ),
237
+ True,
238
+ )
239
+
240
+ def _add_event_handler(self, event: str, k: Any, v: Any) -> None:
241
+ if not self.listeners(event):
242
+ self._update_subscription(event, True)
243
+ super()._add_event_handler(event, k, v)
244
+
245
+ def remove_listener(self, event: str, f: Any) -> None:
246
+ super().remove_listener(event, f)
247
+ if not self.listeners(event):
248
+ self._update_subscription(event, False)
249
+
250
+
251
+ class ProtocolCallback:
252
+ def __init__(
253
+ self, loop: asyncio.AbstractEventLoop, id: int, no_reply: bool = False
254
+ ) -> None:
255
+ self.id = id
256
+ self.stack_trace: traceback.StackSummary
257
+ self.no_reply = no_reply
258
+ self.future = loop.create_future()
259
+ if no_reply:
260
+ self.future.set_result(None)
261
+ # The outer task can get cancelled by the user, this forwards the cancellation to the inner task.
262
+ current_task = asyncio.current_task()
263
+
264
+ def cb(task: asyncio.Task) -> None:
265
+ if current_task:
266
+ current_task.remove_done_callback(cb)
267
+ if task.cancelled():
268
+ self.future.cancel()
269
+
270
+ if current_task:
271
+ current_task.add_done_callback(cb)
272
+ self.future.add_done_callback(
273
+ lambda _: (
274
+ current_task.remove_done_callback(cb) if current_task else None
275
+ )
276
+ )
277
+
278
+
279
+ class RootChannelOwner(ChannelOwner):
280
+ def __init__(self, connection: "Connection") -> None:
281
+ super().__init__(connection, "Root", "", {})
282
+
283
+ async def initialize(self) -> "Playwright":
284
+ return from_channel(
285
+ await self._channel.send(
286
+ "initialize",
287
+ None,
288
+ {
289
+ "sdkLanguage": "python",
290
+ },
291
+ )
292
+ )
293
+
294
+
295
+ class Connection(EventEmitter):
296
+ def __init__(
297
+ self,
298
+ dispatcher_fiber: Any,
299
+ object_factory: Callable[[ChannelOwner, str, str, Dict], ChannelOwner],
300
+ transport: Transport,
301
+ loop: asyncio.AbstractEventLoop,
302
+ local_utils: Optional["LocalUtils"] = None,
303
+ ) -> None:
304
+ super().__init__()
305
+ self._dispatcher_fiber = dispatcher_fiber
306
+ self._transport = transport
307
+ self._transport.on_message = lambda msg: self.dispatch(msg)
308
+ self._waiting_for_object: Dict[str, Callable[[ChannelOwner], None]] = {}
309
+ self._last_id = 0
310
+ self._objects: Dict[str, ChannelOwner] = {}
311
+ self._callbacks: Dict[int, ProtocolCallback] = {}
312
+ self._object_factory = object_factory
313
+ self._is_sync = False
314
+ self._child_ws_connections: List["Connection"] = []
315
+ self._loop = loop
316
+ self.playwright_future: asyncio.Future["Playwright"] = loop.create_future()
317
+ self._error: Optional[BaseException] = None
318
+ self.is_remote = False
319
+ self._init_task: Optional[asyncio.Task] = None
320
+ self._api_zone: contextvars.ContextVar[Optional[ParsedStackTrace]] = (
321
+ contextvars.ContextVar("ApiZone", default=None)
322
+ )
323
+ self._local_utils: Optional["LocalUtils"] = local_utils
324
+ self._tracing_count = 0
325
+ self._closed_error: Optional[Exception] = None
326
+
327
+ @property
328
+ def local_utils(self) -> "LocalUtils":
329
+ assert self._local_utils
330
+ return self._local_utils
331
+
332
+ def mark_as_remote(self) -> None:
333
+ self.is_remote = True
334
+
335
+ async def run_as_sync(self) -> None:
336
+ self._is_sync = True
337
+ await self.run()
338
+
339
+ async def run(self) -> None:
340
+ self._loop = asyncio.get_running_loop()
341
+ self._root_object = RootChannelOwner(self)
342
+
343
+ async def init() -> None:
344
+ self.playwright_future.set_result(await self._root_object.initialize())
345
+
346
+ await self._transport.connect()
347
+ self._init_task = self._loop.create_task(init())
348
+ await self._transport.run()
349
+
350
+ def stop_sync(self) -> None:
351
+ self._transport.request_stop()
352
+ self._dispatcher_fiber.switch()
353
+ self._loop.run_until_complete(self._transport.wait_until_stopped())
354
+ self.cleanup()
355
+
356
+ async def stop_async(self) -> None:
357
+ self._transport.request_stop()
358
+ await self._transport.wait_until_stopped()
359
+ self.cleanup()
360
+
361
+ def cleanup(self, cause: str = None) -> None:
362
+ self._closed_error = TargetClosedError(cause) if cause else TargetClosedError()
363
+ if self._init_task and not self._init_task.done():
364
+ self._init_task.cancel()
365
+ for ws_connection in self._child_ws_connections:
366
+ ws_connection._transport.dispose()
367
+ for callback in self._callbacks.values():
368
+ # To prevent 'Future exception was never retrieved' we ignore all callbacks that are no_reply.
369
+ if callback.no_reply:
370
+ continue
371
+ if callback.future.cancelled():
372
+ continue
373
+ callback.future.set_exception(self._closed_error)
374
+ self._callbacks.clear()
375
+ self.emit("close")
376
+
377
+ def call_on_object_with_known_name(
378
+ self, guid: str, callback: Callable[[ChannelOwner], None]
379
+ ) -> None:
380
+ self._waiting_for_object[guid] = callback
381
+
382
+ def set_is_tracing(self, is_tracing: bool) -> None:
383
+ if is_tracing:
384
+ self._tracing_count += 1
385
+ else:
386
+ self._tracing_count -= 1
387
+
388
+ def _send_message_to_server(
389
+ self,
390
+ object: ChannelOwner,
391
+ method: str,
392
+ params: Dict,
393
+ timeout: float,
394
+ no_reply: bool = False,
395
+ ) -> ProtocolCallback:
396
+ if self._closed_error:
397
+ raise self._closed_error
398
+ if object._was_collected:
399
+ raise Error(
400
+ "The object has been collected to prevent unbounded heap growth."
401
+ )
402
+ self._last_id += 1
403
+ id = self._last_id
404
+ callback = ProtocolCallback(self._loop, id, no_reply=no_reply)
405
+ task = asyncio.current_task(self._loop)
406
+ callback.stack_trace = cast(
407
+ traceback.StackSummary,
408
+ getattr(task, "__pw_stack_trace__", None)
409
+ or traceback.extract_stack(limit=10),
410
+ )
411
+ callback.no_reply = no_reply
412
+ stack_trace_information = cast(ParsedStackTrace, self._api_zone.get())
413
+ frames = stack_trace_information.get("frames", [])
414
+ location = (
415
+ {
416
+ "file": frames[0]["file"],
417
+ "line": frames[0]["line"],
418
+ "column": frames[0]["column"],
419
+ }
420
+ if frames
421
+ else None
422
+ )
423
+ metadata = {
424
+ "wallTime": int(datetime.datetime.now().timestamp() * 1000),
425
+ "apiName": stack_trace_information["apiName"],
426
+ "internal": not stack_trace_information["apiName"],
427
+ "timeout": timeout,
428
+ }
429
+ if location:
430
+ metadata["location"] = location # type: ignore
431
+ title = stack_trace_information["title"]
432
+ if title:
433
+ metadata["title"] = title
434
+ message = {
435
+ "id": id,
436
+ "guid": object._guid,
437
+ "method": method,
438
+ "params": self._replace_channels_with_guids(params),
439
+ "metadata": metadata,
440
+ }
441
+ if self._tracing_count > 0 and frames and object._guid != "localUtils":
442
+ self.local_utils.add_stack_to_tracing_no_reply(id, frames)
443
+
444
+ self._callbacks[id] = callback
445
+ self._transport.send(message)
446
+
447
+ return callback
448
+
449
+ async def _abort(
450
+ self, object: ChannelOwner, callback: ProtocolCallback, reason: str
451
+ ) -> None:
452
+ try:
453
+ self._transport.send(
454
+ {
455
+ "guid": object._guid,
456
+ "method": "__abort__",
457
+ "params": {"id": callback.id, "reason": reason},
458
+ }
459
+ )
460
+ except (Error, OSError):
461
+ pass
462
+ try:
463
+ done, _ = await asyncio.wait(
464
+ {
465
+ self._transport.on_error_future,
466
+ callback.future,
467
+ },
468
+ return_when=asyncio.FIRST_COMPLETED,
469
+ )
470
+ finally:
471
+ if not callback.future.done():
472
+ callback.future.cancel()
473
+ for future in done:
474
+ if not future.cancelled():
475
+ future.exception()
476
+
477
+ def dispatch(self, msg: ParsedMessagePayload) -> None:
478
+ if self._closed_error:
479
+ return
480
+ id = msg.get("id")
481
+ if id:
482
+ callback = self._callbacks.pop(id)
483
+ if callback.future.cancelled():
484
+ return
485
+ # No reply messages are used to e.g. __waitInfo__(after) which returns exceptions on page close.
486
+ # To prevent 'Future exception was never retrieved' we just ignore such messages.
487
+ if callback.no_reply:
488
+ return
489
+ error = msg.get("error")
490
+ if error and not msg.get("result"):
491
+ parsed_error = parse_error(
492
+ error["error"], format_call_log(msg.get("log")) # type: ignore
493
+ )
494
+ parsed_error._log = msg.get("log")
495
+ parsed_error._details = self._replace_guids_with_channels(
496
+ msg.get("errorDetails")
497
+ )
498
+ parsed_error._stack = "".join(callback.stack_trace.format())
499
+ callback.future.set_exception(parsed_error)
500
+ else:
501
+ result = self._replace_guids_with_channels(msg.get("result"))
502
+ callback.future.set_result(result)
503
+ return
504
+
505
+ guid = msg["guid"]
506
+ method = msg["method"]
507
+ params = msg.get("params")
508
+ if method == "__create__":
509
+ assert params
510
+ parent = self._objects[guid]
511
+ self._create_remote_object(
512
+ parent, params["type"], params["guid"], params["initializer"]
513
+ )
514
+ return
515
+
516
+ object = self._objects.get(guid)
517
+ if not object:
518
+ raise Exception(f'Cannot find object to "{method}": {guid}')
519
+
520
+ if method == "__adopt__":
521
+ child_guid = cast(Dict[str, str], params)["guid"]
522
+ child = self._objects.get(child_guid)
523
+ if not child:
524
+ raise Exception(f"Unknown new child: {child_guid}")
525
+ object._adopt(child)
526
+ return
527
+
528
+ if method == "__dispose__":
529
+ assert isinstance(params, dict)
530
+ self._objects[guid]._dispose(cast(Optional[str], params.get("reason")))
531
+ return
532
+ object = self._objects[guid]
533
+ should_replace_guids_with_channels = "jsonPipe@" not in guid
534
+ try:
535
+ if self._is_sync:
536
+ for listener in object._channel.listeners(method):
537
+ # Event handlers like route/locatorHandlerTriggered require us to perform async work.
538
+ # In order to report their potential errors to the user, we need to catch it and store it in the connection
539
+ def _done_callback(future: asyncio.Future) -> None:
540
+ exc = future.exception()
541
+ if exc:
542
+ self._on_event_listener_error(exc)
543
+
544
+ def _listener_with_error_handler_attached(params: Any) -> None:
545
+ potential_future = listener(params)
546
+ if asyncio.isfuture(potential_future):
547
+ potential_future.add_done_callback(_done_callback)
548
+
549
+ # Each event handler is a potentilly blocking context, create a fiber for each
550
+ # and switch to them in order, until they block inside and pass control to each
551
+ # other and then eventually back to dispatcher as listener functions return.
552
+ g = EventGreenlet(_listener_with_error_handler_attached)
553
+ if should_replace_guids_with_channels:
554
+ g.switch(self._replace_guids_with_channels(params))
555
+ else:
556
+ g.switch(params)
557
+ else:
558
+ if should_replace_guids_with_channels:
559
+ object._channel.emit(
560
+ method, self._replace_guids_with_channels(params)
561
+ )
562
+ else:
563
+ object._channel.emit(method, params)
564
+ except BaseException as exc:
565
+ self._on_event_listener_error(exc)
566
+
567
+ def _on_event_listener_error(self, exc: BaseException) -> None:
568
+ print("Error occurred in event listener", file=sys.stderr)
569
+ traceback.print_exception(type(exc), exc, exc.__traceback__, file=sys.stderr)
570
+ # Save the error to throw at the next API call. This "replicates" unhandled rejection in Node.js.
571
+ self._error = exc
572
+
573
+ def _create_remote_object(
574
+ self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
575
+ ) -> ChannelOwner:
576
+ initializer = self._replace_guids_with_channels(initializer)
577
+ result = self._object_factory(parent, type, guid, initializer)
578
+ if guid in self._waiting_for_object:
579
+ self._waiting_for_object.pop(guid)(result)
580
+ return result
581
+
582
+ def _replace_channels_with_guids(
583
+ self,
584
+ payload: Any,
585
+ ) -> Any:
586
+ if payload is None:
587
+ return payload
588
+ if isinstance(payload, Path):
589
+ return str(payload)
590
+ if isinstance(payload, collections.abc.Sequence) and not isinstance(
591
+ payload, str
592
+ ):
593
+ return list(map(self._replace_channels_with_guids, payload))
594
+ if isinstance(payload, Channel):
595
+ return dict(guid=payload._guid)
596
+ if isinstance(payload, dict):
597
+ result = {}
598
+ for key, value in payload.items():
599
+ result[key] = self._replace_channels_with_guids(value)
600
+ return result
601
+ return payload
602
+
603
+ def _replace_guids_with_channels(self, payload: Any) -> Any:
604
+ if payload is None:
605
+ return payload
606
+ if isinstance(payload, list):
607
+ return list(map(self._replace_guids_with_channels, payload))
608
+ if isinstance(payload, dict):
609
+ if payload.get("guid") in self._objects:
610
+ return self._objects[payload["guid"]]._channel
611
+ result = {}
612
+ for key, value in payload.items():
613
+ result[key] = self._replace_guids_with_channels(value)
614
+ return result
615
+ return payload
616
+
617
+ async def wrap_api_call(
618
+ self, cb: Callable[[], Any], is_internal: bool = False, title: str = None
619
+ ) -> Any:
620
+ if self._api_zone.get():
621
+ return await cb()
622
+ task = asyncio.current_task(self._loop)
623
+ parsed_st = _attach_api_call_information(
624
+ getattr(task, "__pw_stack__", None) or _capture_stack_trace(),
625
+ is_internal,
626
+ title,
627
+ )
628
+ self._api_zone.set(parsed_st)
629
+ try:
630
+ return await cb()
631
+ except Exception as error:
632
+ raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
633
+ finally:
634
+ self._api_zone.set(None)
635
+
636
+ def wrap_api_call_sync(
637
+ self, cb: Callable[[], Any], is_internal: bool = False, title: str = None
638
+ ) -> Any:
639
+ if self._api_zone.get():
640
+ return cb()
641
+ task = asyncio.current_task(self._loop)
642
+ parsed_st = _attach_api_call_information(
643
+ getattr(task, "__pw_stack__", None) or _capture_stack_trace(),
644
+ is_internal,
645
+ title,
646
+ )
647
+ self._api_zone.set(parsed_st)
648
+ try:
649
+ return cb()
650
+ except Exception as error:
651
+ raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
652
+ finally:
653
+ self._api_zone.set(None)
654
+
655
+
656
+ def from_channel(channel: Channel) -> Any:
657
+ return channel._object
658
+
659
+
660
+ def from_nullable_channel(channel: Optional[Channel]) -> Optional[Any]:
661
+ return channel._object if channel else None
662
+
663
+
664
+ class StackFrame(TypedDict):
665
+ file: str
666
+ line: int
667
+ column: int
668
+ function: Optional[str]
669
+
670
+
671
+ class ParsedStackTrace(TypedDict):
672
+ frames: List[StackFrame]
673
+ apiName: Optional[str]
674
+ title: Optional[str]
675
+
676
+
677
+ def _capture_stack_trace() -> ParsedStackTrace:
678
+ frame = inspect.currentframe()
679
+ # Skip this helper and the caller that only captures the stack.
680
+ frame = frame.f_back.f_back if frame and frame.f_back else None
681
+ last_internal_api_name = ""
682
+ api_name = ""
683
+ parsed_frames: List[StackFrame] = []
684
+ while frame:
685
+ code = frame.f_code
686
+ filename = code.co_filename
687
+ # Sync and Async implementations can have event handlers. When these are sync, they
688
+ # get evaluated in the context of the event loop, so they contain the stack trace of when
689
+ # the message was received. _impl_to_api_mapping is glue between the user-code and internal
690
+ # code to translate impl classes to api classes. We want to ignore these frames.
691
+ if playwright._impl._impl_to_api_mapping.__file__ == filename:
692
+ frame = frame.f_back
693
+ continue
694
+ is_playwright_internal = filename.startswith(_PLAYWRIGHT_MODULE_PATH)
695
+
696
+ method_name = ""
697
+ if "self" in frame.f_locals:
698
+ method_name = frame.f_locals["self"].__class__.__name__ + "."
699
+ method_name += code.co_name
700
+
701
+ if not is_playwright_internal:
702
+ parsed_frames.append(
703
+ {
704
+ "file": filename,
705
+ "line": frame.f_lineno,
706
+ "column": 0,
707
+ "function": method_name,
708
+ }
709
+ )
710
+ if is_playwright_internal:
711
+ last_internal_api_name = method_name
712
+ elif last_internal_api_name:
713
+ api_name = last_internal_api_name
714
+ last_internal_api_name = ""
715
+ frame = frame.f_back
716
+ if not api_name:
717
+ api_name = last_internal_api_name
718
+
719
+ return {
720
+ "frames": parsed_frames,
721
+ "apiName": api_name,
722
+ "title": None,
723
+ }
724
+
725
+
726
+ def _attach_api_call_information(
727
+ stack_trace_information: ParsedStackTrace,
728
+ is_internal: bool,
729
+ title: Optional[str],
730
+ ) -> ParsedStackTrace:
731
+ if is_internal:
732
+ stack_trace_information["apiName"] = ""
733
+ stack_trace_information["title"] = title
734
+ return stack_trace_information
735
+
736
+
737
+ def _augment_params(
738
+ params: Optional[Dict],
739
+ timeout_calculator: Optional[Callable[[Optional[float]], float]],
740
+ ) -> Tuple[Dict, float]:
741
+ if params is None:
742
+ params = {}
743
+ timeout_param = params.pop("timeout", None)
744
+ timeout: float = 0
745
+ if timeout_calculator:
746
+ timeout = timeout_calculator(timeout_param)
747
+ return _filter_none(params), timeout
748
+
749
+
750
+ def _filter_none(d: Mapping) -> Dict:
751
+ result = {}
752
+ for k, v in d.items():
753
+ if v is None:
754
+ continue
755
+ result[k] = _filter_none(v) if isinstance(v, dict) else v
756
+ return result
757
+
758
+
759
+ def format_call_log(log: Optional[List[str]]) -> str:
760
+ if not log:
761
+ return ""
762
+ if len(list(filter(lambda x: x.strip(), log))) == 0:
763
+ return ""
764
+ return "\nCall log:\n" + "\n".join(log) + "\n"