pure-patchright 1.62.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. patchright/__init__.py +5 -0
  2. patchright/__main__.py +18 -0
  3. patchright/_impl/__init__.py +0 -0
  4. patchright/_impl/__pyinstaller/__init__.py +20 -0
  5. patchright/_impl/__pyinstaller/hook-playwright.async_api.py +3 -0
  6. patchright/_impl/__pyinstaller/hook-playwright.sync_api.py +3 -0
  7. patchright/_impl/_api_structures.py +343 -0
  8. patchright/_impl/_artifact.py +46 -0
  9. patchright/_impl/_assertions.py +1014 -0
  10. patchright/_impl/_async_base.py +88 -0
  11. patchright/_impl/_browser.py +267 -0
  12. patchright/_impl/_browser_context.py +759 -0
  13. patchright/_impl/_browser_type.py +336 -0
  14. patchright/_impl/_cdp_session.py +25 -0
  15. patchright/_impl/_clock.py +62 -0
  16. patchright/_impl/_connection.py +686 -0
  17. patchright/_impl/_console_message.py +83 -0
  18. patchright/_impl/_credentials.py +38 -0
  19. patchright/_impl/_debugger.py +37 -0
  20. patchright/_impl/_dialog.py +45 -0
  21. patchright/_impl/_disposable.py +71 -0
  22. patchright/_impl/_download.py +49 -0
  23. patchright/_impl/_driver.py +24 -0
  24. patchright/_impl/_element_handle.py +446 -0
  25. patchright/_impl/_errors.py +46 -0
  26. patchright/_impl/_event_context_manager.py +19 -0
  27. patchright/_impl/_fetch.py +592 -0
  28. patchright/_impl/_file_chooser.py +42 -0
  29. patchright/_impl/_form_data.py +19 -0
  30. patchright/_impl/_frame.py +959 -0
  31. patchright/_impl/_glob.py +47 -0
  32. patchright/_impl/_greenlets.py +35 -0
  33. patchright/_impl/_har_router.py +98 -0
  34. patchright/_impl/_helper.py +551 -0
  35. patchright/_impl/_impl_to_api_mapping.py +141 -0
  36. patchright/_impl/_input.py +85 -0
  37. patchright/_impl/_js_handle.py +297 -0
  38. patchright/_impl/_json_pipe.py +55 -0
  39. patchright/_impl/_local_utils.py +73 -0
  40. patchright/_impl/_locator.py +894 -0
  41. patchright/_impl/_map.py +18 -0
  42. patchright/_impl/_network.py +953 -0
  43. patchright/_impl/_object_factory.py +93 -0
  44. patchright/_impl/_page.py +1601 -0
  45. patchright/_impl/_path_utils.py +13 -0
  46. patchright/_impl/_playwright.py +43 -0
  47. patchright/_impl/_screencast.py +149 -0
  48. patchright/_impl/_selectors.py +48 -0
  49. patchright/_impl/_set_input_files_helpers.py +134 -0
  50. patchright/_impl/_str_utils.py +59 -0
  51. patchright/_impl/_stream.py +31 -0
  52. patchright/_impl/_sync_base.py +130 -0
  53. patchright/_impl/_tracing.py +243 -0
  54. patchright/_impl/_transport.py +151 -0
  55. patchright/_impl/_video.py +41 -0
  56. patchright/_impl/_waiter.py +168 -0
  57. patchright/_impl/_web_error.py +33 -0
  58. patchright/_impl/_web_storage.py +41 -0
  59. patchright/_impl/_writable_stream.py +26 -0
  60. patchright/_repo_version.py +1 -0
  61. patchright/async_api/__init__.py +234 -0
  62. patchright/async_api/_context_manager.py +38 -0
  63. patchright/async_api/_generated.py +23396 -0
  64. patchright/driver/README.md +1 -0
  65. patchright/driver/package/README.md +3 -0
  66. patchright/driver/package/ThirdPartyNotices.txt +13 -0
  67. patchright/driver/package/bin/install_media_pack.ps1 +5 -0
  68. patchright/driver/package/bin/install_webkit_wsl.ps1 +33 -0
  69. patchright/driver/package/bin/reinstall_chrome_beta_linux.sh +42 -0
  70. patchright/driver/package/bin/reinstall_chrome_beta_mac.sh +13 -0
  71. patchright/driver/package/bin/reinstall_chrome_beta_win.ps1 +24 -0
  72. patchright/driver/package/bin/reinstall_chrome_stable_linux.sh +42 -0
  73. patchright/driver/package/bin/reinstall_chrome_stable_mac.sh +12 -0
  74. patchright/driver/package/bin/reinstall_chrome_stable_win.ps1 +24 -0
  75. patchright/driver/package/bin/reinstall_msedge_beta_linux.sh +48 -0
  76. patchright/driver/package/bin/reinstall_msedge_beta_mac.sh +11 -0
  77. patchright/driver/package/bin/reinstall_msedge_beta_win.ps1 +23 -0
  78. patchright/driver/package/bin/reinstall_msedge_dev_linux.sh +48 -0
  79. patchright/driver/package/bin/reinstall_msedge_dev_mac.sh +11 -0
  80. patchright/driver/package/bin/reinstall_msedge_dev_win.ps1 +23 -0
  81. patchright/driver/package/bin/reinstall_msedge_stable_linux.sh +48 -0
  82. patchright/driver/package/bin/reinstall_msedge_stable_mac.sh +11 -0
  83. patchright/driver/package/bin/reinstall_msedge_stable_win.ps1 +24 -0
  84. patchright/driver/package/browsers.json +75 -0
  85. patchright/driver/package/cli.js +21 -0
  86. patchright/driver/package/index.js +17 -0
  87. patchright/driver/package/index.mjs +28 -0
  88. patchright/driver/package/lib/bootstrap.js +88 -0
  89. patchright/driver/package/lib/coreBundle.js +77498 -0
  90. patchright/driver/package/lib/entry/cliDaemon.js +5 -0
  91. patchright/driver/package/lib/entry/dashboardApp.js +3 -0
  92. patchright/driver/package/lib/entry/mcp.js +10 -0
  93. patchright/driver/package/lib/entry/oopBrowserDownload.js +3 -0
  94. patchright/driver/package/lib/package.js +50 -0
  95. patchright/driver/package/lib/server/chromium/appIcon.png +0 -0
  96. patchright/driver/package/lib/server/electron/loader.js +107 -0
  97. patchright/driver/package/lib/serverRegistry.js +7347 -0
  98. patchright/driver/package/lib/serverRegistry.js.LICENSE +354 -0
  99. patchright/driver/package/lib/tools/cli-client/channelSessions.js +141 -0
  100. patchright/driver/package/lib/tools/cli-client/cli.js +6 -0
  101. patchright/driver/package/lib/tools/cli-client/help.json +708 -0
  102. patchright/driver/package/lib/tools/cli-client/minimist.js +128 -0
  103. patchright/driver/package/lib/tools/cli-client/output.js +343 -0
  104. patchright/driver/package/lib/tools/cli-client/program.js +404 -0
  105. patchright/driver/package/lib/tools/cli-client/registry.js +176 -0
  106. patchright/driver/package/lib/tools/cli-client/session.js +258 -0
  107. patchright/driver/package/lib/tools/dashboard/appIcon.png +0 -0
  108. patchright/driver/package/lib/tools/skills/playwright-cli/SKILL.md +420 -0
  109. patchright/driver/package/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
  110. patchright/driver/package/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
  111. patchright/driver/package/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
  112. patchright/driver/package/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
  113. patchright/driver/package/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
  114. patchright/driver/package/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
  115. patchright/driver/package/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
  116. patchright/driver/package/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
  117. patchright/driver/package/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
  118. patchright/driver/package/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  119. patchright/driver/package/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  120. patchright/driver/package/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  121. patchright/driver/package/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  122. patchright/driver/package/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  123. patchright/driver/package/lib/tools/skills/playwright-trace/SKILL.md +171 -0
  124. patchright/driver/package/lib/tools/utils/extension.js +101 -0
  125. patchright/driver/package/lib/tools/utils/socketConnection.js +108 -0
  126. patchright/driver/package/lib/utilsBundle.js +90764 -0
  127. patchright/driver/package/lib/utilsBundle.js.LICENSE +2179 -0
  128. patchright/driver/package/lib/webp_codec.LICENSE +173 -0
  129. patchright/driver/package/lib/webp_codec.wasm +0 -0
  130. patchright/driver/package/lib/xdg-open +1267 -0
  131. patchright/driver/package/package.json +34 -0
  132. patchright/py.typed +0 -0
  133. patchright/sync_api/__init__.py +234 -0
  134. patchright/sync_api/_context_manager.py +73 -0
  135. patchright/sync_api/_generated.py +23590 -0
  136. pure_patchright-1.62.3.dist-info/LICENSE +201 -0
  137. pure_patchright-1.62.3.dist-info/METADATA +96 -0
  138. pure_patchright-1.62.3.dist-info/RECORD +142 -0
  139. pure_patchright-1.62.3.dist-info/WHEEL +4 -0
  140. pure_patchright-1.62.3.dist-info/entry_points.txt +5 -0
  141. pure_patchright-1.62.3.dist-info/licenses/LICENSE +202 -0
  142. pure_patchright-1.62.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,686 @@
1
+ import asyncio
2
+ import collections.abc
3
+ import contextvars
4
+ import datetime
5
+ import inspect
6
+ import sys
7
+ import traceback
8
+ from pathlib import Path
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Any,
12
+ Callable,
13
+ Dict,
14
+ List,
15
+ Mapping,
16
+ Optional,
17
+ Tuple,
18
+ TypedDict,
19
+ Union,
20
+ cast,
21
+ )
22
+ from pyee import EventEmitter
23
+ from pyee.asyncio import AsyncIOEventEmitter
24
+ import patchright
25
+ import patchright._impl._impl_to_api_mapping
26
+ from patchright._impl._errors import TargetClosedError, rewrite_error
27
+ from patchright._impl._greenlets import EventGreenlet
28
+ from patchright._impl._helper import (
29
+ Error,
30
+ ParsedMessagePayload,
31
+ create_task_and_ignore_exception,
32
+ parse_error,
33
+ )
34
+ from patchright._impl._transport import Transport
35
+
36
+ if TYPE_CHECKING:
37
+ from patchright._impl._local_utils import LocalUtils
38
+ from patchright._impl._playwright import Playwright
39
+ TimeoutCalculator = Optional[Callable[[Optional[float]], float]]
40
+ _PLAYWRIGHT_MODULE_PATH = str(Path(patchright.__file__).parents[0])
41
+
42
+
43
+ class Channel(AsyncIOEventEmitter):
44
+ def __init__(self, connection: "Connection", object: "ChannelOwner") -> None:
45
+ super().__init__()
46
+ self._connection = connection
47
+ self._guid = object._guid
48
+ self._object = object
49
+ self.on("error", lambda exc: self._connection._on_event_listener_error(exc))
50
+
51
+ async def send(
52
+ self,
53
+ method: str,
54
+ timeout_calculator: TimeoutCalculator,
55
+ params: Dict = None,
56
+ is_internal: bool = False,
57
+ title: str = None,
58
+ ) -> Any:
59
+ return await self._connection.wrap_api_call(
60
+ lambda: self._inner_send(method, timeout_calculator, params, False),
61
+ is_internal,
62
+ title,
63
+ )
64
+
65
+ def send_may_fail(
66
+ self,
67
+ method: str,
68
+ timeout_calculator: TimeoutCalculator,
69
+ params: Dict = None,
70
+ is_internal: bool = False,
71
+ title: str = None,
72
+ ) -> None:
73
+ create_task_and_ignore_exception(
74
+ self._connection._loop,
75
+ self.send(method, timeout_calculator, params, is_internal, title),
76
+ )
77
+
78
+ async def send_return_as_dict(
79
+ self,
80
+ method: str,
81
+ timeout_calculator: TimeoutCalculator,
82
+ params: Dict = None,
83
+ is_internal: bool = False,
84
+ title: str = None,
85
+ ) -> Any:
86
+ return await self._connection.wrap_api_call(
87
+ lambda: self._inner_send(method, timeout_calculator, params, True),
88
+ is_internal,
89
+ title,
90
+ )
91
+
92
+ def send_no_reply(
93
+ self,
94
+ method: str,
95
+ timeout_calculator: TimeoutCalculator,
96
+ params: Dict = None,
97
+ is_internal: bool = False,
98
+ title: str = None,
99
+ ) -> None:
100
+ augmented_params, timeout = _augment_params(params, timeout_calculator)
101
+ self._connection.wrap_api_call_sync(
102
+ lambda: self._connection._send_message_to_server(
103
+ self._object, method, augmented_params, timeout, True
104
+ ),
105
+ is_internal,
106
+ title,
107
+ )
108
+
109
+ async def _inner_send(
110
+ self,
111
+ method: str,
112
+ timeout_calculator: TimeoutCalculator,
113
+ params: Optional[Dict],
114
+ return_as_dict: bool,
115
+ ) -> Any:
116
+ if self._connection._error:
117
+ error = self._connection._error
118
+ self._connection._error = None
119
+ raise error
120
+ augmented_params, timeout = _augment_params(params, timeout_calculator)
121
+ callback = self._connection._send_message_to_server(
122
+ self._object, method, augmented_params, timeout
123
+ )
124
+ try:
125
+ done, _ = await asyncio.wait(
126
+ {self._connection._transport.on_error_future, callback.future},
127
+ return_when=asyncio.FIRST_COMPLETED,
128
+ )
129
+ except asyncio.CancelledError as exc:
130
+ await self._connection._abort(
131
+ self._object, callback, str(exc) or "Task was cancelled"
132
+ )
133
+ raise
134
+ if not callback.future.done():
135
+ callback.future.cancel()
136
+ result = next(iter(done)).result()
137
+ if not result:
138
+ return None
139
+ assert isinstance(result, dict)
140
+ if return_as_dict:
141
+ return result
142
+ if len(result) == 0:
143
+ return None
144
+ assert len(result) == 1
145
+ key = next(iter(result))
146
+ return result[key]
147
+
148
+
149
+ class ChannelOwner(AsyncIOEventEmitter):
150
+ def __init__(
151
+ self,
152
+ parent: Union["ChannelOwner", "Connection"],
153
+ type: str,
154
+ guid: str,
155
+ initializer: Dict,
156
+ ) -> None:
157
+ super().__init__(loop=parent._loop)
158
+ self._loop: asyncio.AbstractEventLoop = parent._loop
159
+ self._dispatcher_fiber: Any = parent._dispatcher_fiber
160
+ self._type = type
161
+ self._guid: str = guid
162
+ self._connection: Connection = (
163
+ parent._connection if isinstance(parent, ChannelOwner) else parent
164
+ )
165
+ self._parent: Optional[ChannelOwner] = (
166
+ parent if isinstance(parent, ChannelOwner) else None
167
+ )
168
+ self._objects: Dict[str, "ChannelOwner"] = {}
169
+ self._channel: Channel = Channel(self._connection, self)
170
+ self._initializer = initializer
171
+ self._was_collected = False
172
+ self._connection._objects[guid] = self
173
+ if self._parent:
174
+ self._parent._objects[guid] = self
175
+ self._event_to_subscription_mapping: Dict[str, str] = {}
176
+
177
+ def _dispose(self, reason: Optional[str]) -> None:
178
+ if self._parent:
179
+ del self._parent._objects[self._guid]
180
+ del self._connection._objects[self._guid]
181
+ self._was_collected = reason == "gc"
182
+ for object in list(self._objects.values()):
183
+ object._dispose(reason)
184
+ self._objects.clear()
185
+
186
+ def _adopt(self, child: "ChannelOwner") -> None:
187
+ del cast("ChannelOwner", child._parent)._objects[child._guid]
188
+ self._objects[child._guid] = child
189
+ child._parent = self
190
+
191
+ def _set_event_to_subscription_mapping(self, mapping: Dict[str, str]) -> None:
192
+ self._event_to_subscription_mapping = mapping
193
+
194
+ def _update_subscription(self, event: str, enabled: bool) -> None:
195
+ protocol_event = self._event_to_subscription_mapping.get(event)
196
+ if protocol_event:
197
+ self._connection.wrap_api_call_sync(
198
+ lambda: self._channel.send_no_reply(
199
+ "updateSubscription",
200
+ None,
201
+ {"event": protocol_event, "enabled": enabled},
202
+ ),
203
+ True,
204
+ )
205
+
206
+ def _add_event_handler(self, event: str, k: Any, v: Any) -> None:
207
+ if not self.listeners(event):
208
+ self._update_subscription(event, True)
209
+ super()._add_event_handler(event, k, v)
210
+
211
+ def remove_listener(self, event: str, f: Any) -> None:
212
+ super().remove_listener(event, f)
213
+ if not self.listeners(event):
214
+ self._update_subscription(event, False)
215
+
216
+
217
+ class ProtocolCallback:
218
+ def __init__(
219
+ self, loop: asyncio.AbstractEventLoop, id: int, no_reply: bool = False
220
+ ) -> None:
221
+ self.id = id
222
+ self.stack_trace: traceback.StackSummary
223
+ self.no_reply = no_reply
224
+ self.future = loop.create_future()
225
+ if no_reply:
226
+ self.future.set_result(None)
227
+ current_task = asyncio.current_task()
228
+
229
+ def cb(task: asyncio.Task) -> None:
230
+ if current_task:
231
+ current_task.remove_done_callback(cb)
232
+ if task.cancelled():
233
+ self.future.cancel()
234
+
235
+ if current_task:
236
+ current_task.add_done_callback(cb)
237
+ self.future.add_done_callback(
238
+ lambda _: (
239
+ current_task.remove_done_callback(cb) if current_task else None
240
+ )
241
+ )
242
+
243
+
244
+ class RootChannelOwner(ChannelOwner):
245
+ def __init__(self, connection: "Connection") -> None:
246
+ super().__init__(connection, "Root", "", {})
247
+
248
+ async def initialize(self) -> "Playwright":
249
+ return from_channel(
250
+ await self._channel.send("initialize", None, {"sdkLanguage": "python"})
251
+ )
252
+
253
+
254
+ class Connection(EventEmitter):
255
+ def __init__(
256
+ self,
257
+ dispatcher_fiber: Any,
258
+ object_factory: Callable[[ChannelOwner, str, str, Dict], ChannelOwner],
259
+ transport: Transport,
260
+ loop: asyncio.AbstractEventLoop,
261
+ local_utils: Optional["LocalUtils"] = None,
262
+ ) -> None:
263
+ super().__init__()
264
+ self._dispatcher_fiber = dispatcher_fiber
265
+ self._transport = transport
266
+ self._transport.on_message = lambda msg: self.dispatch(msg)
267
+ self._waiting_for_object: Dict[str, Callable[[ChannelOwner], None]] = {}
268
+ self._last_id = 0
269
+ self._objects: Dict[str, ChannelOwner] = {}
270
+ self._callbacks: Dict[int, ProtocolCallback] = {}
271
+ self._object_factory = object_factory
272
+ self._is_sync = False
273
+ self._child_ws_connections: List["Connection"] = []
274
+ self._loop = loop
275
+ self.playwright_future: asyncio.Future["Playwright"] = loop.create_future()
276
+ self._error: Optional[BaseException] = None
277
+ self.is_remote = False
278
+ self._init_task: Optional[asyncio.Task] = None
279
+ self._api_zone: contextvars.ContextVar[Optional[ParsedStackTrace]] = (
280
+ contextvars.ContextVar("ApiZone", default=None)
281
+ )
282
+ self._local_utils: Optional["LocalUtils"] = local_utils
283
+ self._tracing_count = 0
284
+ self._closed_error: Optional[Exception] = None
285
+
286
+ @property
287
+ def local_utils(self) -> "LocalUtils":
288
+ assert self._local_utils
289
+ return self._local_utils
290
+
291
+ def mark_as_remote(self) -> None:
292
+ self.is_remote = True
293
+
294
+ async def run_as_sync(self) -> None:
295
+ self._is_sync = True
296
+ await self.run()
297
+
298
+ async def run(self) -> None:
299
+ self._loop = asyncio.get_running_loop()
300
+ self._root_object = RootChannelOwner(self)
301
+
302
+ async def init() -> None:
303
+ self.playwright_future.set_result(await self._root_object.initialize())
304
+
305
+ await self._transport.connect()
306
+ self._init_task = self._loop.create_task(init())
307
+ await self._transport.run()
308
+
309
+ def stop_sync(self) -> None:
310
+ self._transport.request_stop()
311
+ self._dispatcher_fiber.switch()
312
+ self._loop.run_until_complete(self._transport.wait_until_stopped())
313
+ self.cleanup()
314
+
315
+ async def stop_async(self) -> None:
316
+ self._transport.request_stop()
317
+ await self._transport.wait_until_stopped()
318
+ self.cleanup()
319
+
320
+ def cleanup(self, cause: str = None) -> None:
321
+ self._closed_error = TargetClosedError(cause) if cause else TargetClosedError()
322
+ if self._init_task and (not self._init_task.done()):
323
+ self._init_task.cancel()
324
+ for ws_connection in self._child_ws_connections:
325
+ ws_connection._transport.dispose()
326
+ for callback in self._callbacks.values():
327
+ if callback.no_reply:
328
+ continue
329
+ if callback.future.cancelled():
330
+ continue
331
+ callback.future.set_exception(self._closed_error)
332
+ self._callbacks.clear()
333
+ self.emit("close")
334
+
335
+ def call_on_object_with_known_name(
336
+ self, guid: str, callback: Callable[[ChannelOwner], None]
337
+ ) -> None:
338
+ self._waiting_for_object[guid] = callback
339
+
340
+ def set_is_tracing(self, is_tracing: bool) -> None:
341
+ if is_tracing:
342
+ self._tracing_count += 1
343
+ else:
344
+ self._tracing_count -= 1
345
+
346
+ def _send_message_to_server(
347
+ self,
348
+ object: ChannelOwner,
349
+ method: str,
350
+ params: Dict,
351
+ timeout: float,
352
+ no_reply: bool = False,
353
+ ) -> ProtocolCallback:
354
+ if self._closed_error:
355
+ raise self._closed_error
356
+ if object._was_collected:
357
+ raise Error(
358
+ "The object has been collected to prevent unbounded heap growth."
359
+ )
360
+ self._last_id += 1
361
+ id = self._last_id
362
+ callback = ProtocolCallback(self._loop, id, no_reply=no_reply)
363
+ task = asyncio.current_task(self._loop)
364
+ callback.stack_trace = cast(
365
+ traceback.StackSummary,
366
+ getattr(task, "__pw_stack_trace__", None)
367
+ or traceback.extract_stack(limit=10),
368
+ )
369
+ callback.no_reply = no_reply
370
+ stack_trace_information = cast(ParsedStackTrace, self._api_zone.get())
371
+ frames = stack_trace_information.get("frames", [])
372
+ location = (
373
+ {
374
+ "file": frames[0]["file"],
375
+ "line": frames[0]["line"],
376
+ "column": frames[0]["column"],
377
+ }
378
+ if frames
379
+ else None
380
+ )
381
+ metadata = {
382
+ "wallTime": int(datetime.datetime.now().timestamp() * 1000),
383
+ "apiName": stack_trace_information["apiName"],
384
+ "internal": not stack_trace_information["apiName"],
385
+ "timeout": timeout,
386
+ }
387
+ if location:
388
+ metadata["location"] = location
389
+ title = stack_trace_information["title"]
390
+ if title:
391
+ metadata["title"] = title
392
+ message = {
393
+ "id": id,
394
+ "guid": object._guid,
395
+ "method": method,
396
+ "params": self._replace_channels_with_guids(params),
397
+ "metadata": metadata,
398
+ }
399
+ if self._tracing_count > 0 and frames and (object._guid != "localUtils"):
400
+ self.local_utils.add_stack_to_tracing_no_reply(id, frames)
401
+ self._callbacks[id] = callback
402
+ self._transport.send(message)
403
+ return callback
404
+
405
+ async def _abort(
406
+ self, object: ChannelOwner, callback: ProtocolCallback, reason: str
407
+ ) -> None:
408
+ try:
409
+ self._transport.send(
410
+ {
411
+ "guid": object._guid,
412
+ "method": "__abort__",
413
+ "params": {"id": callback.id, "reason": reason},
414
+ }
415
+ )
416
+ except (Error, OSError):
417
+ pass
418
+ try:
419
+ done, _ = await asyncio.wait(
420
+ {self._transport.on_error_future, callback.future},
421
+ return_when=asyncio.FIRST_COMPLETED,
422
+ )
423
+ finally:
424
+ if not callback.future.done():
425
+ callback.future.cancel()
426
+ for future in done:
427
+ if not future.cancelled():
428
+ future.exception()
429
+
430
+ def dispatch(self, msg: ParsedMessagePayload) -> None:
431
+ if self._closed_error:
432
+ return
433
+ id = msg.get("id")
434
+ if id:
435
+ callback = self._callbacks.pop(id)
436
+ if callback.future.cancelled():
437
+ return
438
+ if callback.no_reply:
439
+ return
440
+ error = msg.get("error")
441
+ if error and (not msg.get("result")):
442
+ parsed_error = parse_error(
443
+ error["error"], format_call_log(msg.get("log"))
444
+ )
445
+ parsed_error._log = msg.get("log")
446
+ parsed_error._details = self._replace_guids_with_channels(
447
+ msg.get("errorDetails")
448
+ )
449
+ parsed_error._stack = "".join(callback.stack_trace.format())
450
+ callback.future.set_exception(parsed_error)
451
+ else:
452
+ result = self._replace_guids_with_channels(msg.get("result"))
453
+ callback.future.set_result(result)
454
+ return
455
+ guid = msg["guid"]
456
+ method = msg["method"]
457
+ params = msg.get("params")
458
+ if method == "__create__":
459
+ assert params
460
+ parent = self._objects[guid]
461
+ self._create_remote_object(
462
+ parent, params["type"], params["guid"], params["initializer"]
463
+ )
464
+ return
465
+ object = self._objects.get(guid)
466
+ if not object:
467
+ raise Exception(f'Cannot find object to "{method}": {guid}')
468
+ if method == "__adopt__":
469
+ child_guid = cast(Dict[str, str], params)["guid"]
470
+ child = self._objects.get(child_guid)
471
+ if not child:
472
+ raise Exception(f"Unknown new child: {child_guid}")
473
+ object._adopt(child)
474
+ return
475
+ if method == "__dispose__":
476
+ assert isinstance(params, dict)
477
+ self._objects[guid]._dispose(cast(Optional[str], params.get("reason")))
478
+ return
479
+ object = self._objects[guid]
480
+ should_replace_guids_with_channels = "jsonPipe@" not in guid
481
+ try:
482
+ if self._is_sync:
483
+ for listener in object._channel.listeners(method):
484
+
485
+ def _done_callback(future: asyncio.Future) -> None:
486
+ exc = future.exception()
487
+ if exc:
488
+ self._on_event_listener_error(exc)
489
+
490
+ def _listener_with_error_handler_attached(params: Any) -> None:
491
+ potential_future = listener(params)
492
+ if asyncio.isfuture(potential_future):
493
+ potential_future.add_done_callback(_done_callback)
494
+
495
+ g = EventGreenlet(_listener_with_error_handler_attached)
496
+ if should_replace_guids_with_channels:
497
+ g.switch(self._replace_guids_with_channels(params))
498
+ else:
499
+ g.switch(params)
500
+ elif should_replace_guids_with_channels:
501
+ object._channel.emit(method, self._replace_guids_with_channels(params))
502
+ else:
503
+ object._channel.emit(method, params)
504
+ except BaseException as exc:
505
+ self._on_event_listener_error(exc)
506
+
507
+ def _on_event_listener_error(self, exc: BaseException) -> None:
508
+ print("Error occurred in event listener", file=sys.stderr)
509
+ traceback.print_exception(type(exc), exc, exc.__traceback__, file=sys.stderr)
510
+ self._error = exc
511
+
512
+ def _create_remote_object(
513
+ self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
514
+ ) -> ChannelOwner:
515
+ initializer = self._replace_guids_with_channels(initializer)
516
+ result = self._object_factory(parent, type, guid, initializer)
517
+ if guid in self._waiting_for_object:
518
+ self._waiting_for_object.pop(guid)(result)
519
+ return result
520
+
521
+ def _replace_channels_with_guids(self, payload: Any) -> Any:
522
+ if payload is None:
523
+ return payload
524
+ if isinstance(payload, Path):
525
+ return str(payload)
526
+ if isinstance(payload, collections.abc.Sequence) and (
527
+ not isinstance(payload, str)
528
+ ):
529
+ return list(map(self._replace_channels_with_guids, payload))
530
+ if isinstance(payload, Channel):
531
+ return dict(guid=payload._guid)
532
+ if isinstance(payload, dict):
533
+ result = {}
534
+ for key, value in payload.items():
535
+ result[key] = self._replace_channels_with_guids(value)
536
+ return result
537
+ return payload
538
+
539
+ def _replace_guids_with_channels(self, payload: Any) -> Any:
540
+ if payload is None:
541
+ return payload
542
+ if isinstance(payload, list):
543
+ return list(map(self._replace_guids_with_channels, payload))
544
+ if isinstance(payload, dict):
545
+ if payload.get("guid") in self._objects:
546
+ return self._objects[payload["guid"]]._channel
547
+ result = {}
548
+ for key, value in payload.items():
549
+ result[key] = self._replace_guids_with_channels(value)
550
+ return result
551
+ return payload
552
+
553
+ async def wrap_api_call(
554
+ self, cb: Callable[[], Any], is_internal: bool = False, title: str = None
555
+ ) -> Any:
556
+ if self._api_zone.get():
557
+ return await cb()
558
+ task = asyncio.current_task(self._loop)
559
+ parsed_st = _attach_api_call_information(
560
+ getattr(task, "__pw_stack__", None) or _capture_stack_trace(),
561
+ is_internal,
562
+ title,
563
+ )
564
+ self._api_zone.set(parsed_st)
565
+ try:
566
+ return await cb()
567
+ except Exception as error:
568
+ raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
569
+ finally:
570
+ self._api_zone.set(None)
571
+
572
+ def wrap_api_call_sync(
573
+ self, cb: Callable[[], Any], is_internal: bool = False, title: str = None
574
+ ) -> Any:
575
+ if self._api_zone.get():
576
+ return cb()
577
+ task = asyncio.current_task(self._loop)
578
+ parsed_st = _attach_api_call_information(
579
+ getattr(task, "__pw_stack__", None) or _capture_stack_trace(),
580
+ is_internal,
581
+ title,
582
+ )
583
+ self._api_zone.set(parsed_st)
584
+ try:
585
+ return cb()
586
+ except Exception as error:
587
+ raise rewrite_error(error, f"{parsed_st['apiName']}: {error}") from None
588
+ finally:
589
+ self._api_zone.set(None)
590
+
591
+
592
+ def from_channel(channel: Channel) -> Any:
593
+ return channel._object
594
+
595
+
596
+ def from_nullable_channel(channel: Optional[Channel]) -> Optional[Any]:
597
+ return channel._object if channel else None
598
+
599
+
600
+ class StackFrame(TypedDict):
601
+ file: str
602
+ line: int
603
+ column: int
604
+ function: Optional[str]
605
+
606
+
607
+ class ParsedStackTrace(TypedDict):
608
+ frames: List[StackFrame]
609
+ apiName: Optional[str]
610
+ title: Optional[str]
611
+
612
+
613
+ def _capture_stack_trace() -> ParsedStackTrace:
614
+ frame = inspect.currentframe()
615
+ frame = frame.f_back.f_back if frame and frame.f_back else None
616
+ last_internal_api_name = ""
617
+ api_name = ""
618
+ parsed_frames: List[StackFrame] = []
619
+ while frame:
620
+ code = frame.f_code
621
+ filename = code.co_filename
622
+ if patchright._impl._impl_to_api_mapping.__file__ == filename:
623
+ frame = frame.f_back
624
+ continue
625
+ is_playwright_internal = filename.startswith(_PLAYWRIGHT_MODULE_PATH)
626
+ method_name = ""
627
+ if "self" in frame.f_locals:
628
+ method_name = frame.f_locals["self"].__class__.__name__ + "."
629
+ method_name += code.co_name
630
+ if not is_playwright_internal:
631
+ parsed_frames.append(
632
+ {
633
+ "file": filename,
634
+ "line": frame.f_lineno,
635
+ "column": 0,
636
+ "function": method_name,
637
+ }
638
+ )
639
+ if is_playwright_internal:
640
+ last_internal_api_name = method_name
641
+ elif last_internal_api_name:
642
+ api_name = last_internal_api_name
643
+ last_internal_api_name = ""
644
+ frame = frame.f_back
645
+ if not api_name:
646
+ api_name = last_internal_api_name
647
+ return {"frames": parsed_frames, "apiName": api_name, "title": None}
648
+
649
+
650
+ def _attach_api_call_information(
651
+ stack_trace_information: ParsedStackTrace, is_internal: bool, title: Optional[str]
652
+ ) -> ParsedStackTrace:
653
+ if is_internal:
654
+ stack_trace_information["apiName"] = ""
655
+ stack_trace_information["title"] = title
656
+ return stack_trace_information
657
+
658
+
659
+ def _augment_params(
660
+ params: Optional[Dict],
661
+ timeout_calculator: Optional[Callable[[Optional[float]], float]],
662
+ ) -> Tuple[Dict, float]:
663
+ if params is None:
664
+ params = {}
665
+ timeout_param = params.pop("timeout", None)
666
+ timeout: float = 0
667
+ if timeout_calculator:
668
+ timeout = timeout_calculator(timeout_param)
669
+ return (_filter_none(params), timeout)
670
+
671
+
672
+ def _filter_none(d: Mapping) -> Dict:
673
+ result = {}
674
+ for k, v in d.items():
675
+ if v is None:
676
+ continue
677
+ result[k] = _filter_none(v) if isinstance(v, dict) else v
678
+ return result
679
+
680
+
681
+ def format_call_log(log: Optional[List[str]]) -> str:
682
+ if not log:
683
+ return ""
684
+ if len(list(filter(lambda x: x.strip(), log))) == 0:
685
+ return ""
686
+ return "\nCall log:\n" + "\n".join(log) + "\n"