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.
- patchright/__init__.py +5 -0
- patchright/__main__.py +18 -0
- patchright/_impl/__init__.py +0 -0
- patchright/_impl/__pyinstaller/__init__.py +20 -0
- patchright/_impl/__pyinstaller/hook-playwright.async_api.py +3 -0
- patchright/_impl/__pyinstaller/hook-playwright.sync_api.py +3 -0
- patchright/_impl/_api_structures.py +343 -0
- patchright/_impl/_artifact.py +46 -0
- patchright/_impl/_assertions.py +1014 -0
- patchright/_impl/_async_base.py +88 -0
- patchright/_impl/_browser.py +267 -0
- patchright/_impl/_browser_context.py +759 -0
- patchright/_impl/_browser_type.py +336 -0
- patchright/_impl/_cdp_session.py +25 -0
- patchright/_impl/_clock.py +62 -0
- patchright/_impl/_connection.py +686 -0
- patchright/_impl/_console_message.py +83 -0
- patchright/_impl/_credentials.py +38 -0
- patchright/_impl/_debugger.py +37 -0
- patchright/_impl/_dialog.py +45 -0
- patchright/_impl/_disposable.py +71 -0
- patchright/_impl/_download.py +49 -0
- patchright/_impl/_driver.py +24 -0
- patchright/_impl/_element_handle.py +446 -0
- patchright/_impl/_errors.py +46 -0
- patchright/_impl/_event_context_manager.py +19 -0
- patchright/_impl/_fetch.py +592 -0
- patchright/_impl/_file_chooser.py +42 -0
- patchright/_impl/_form_data.py +19 -0
- patchright/_impl/_frame.py +959 -0
- patchright/_impl/_glob.py +47 -0
- patchright/_impl/_greenlets.py +35 -0
- patchright/_impl/_har_router.py +98 -0
- patchright/_impl/_helper.py +551 -0
- patchright/_impl/_impl_to_api_mapping.py +141 -0
- patchright/_impl/_input.py +85 -0
- patchright/_impl/_js_handle.py +297 -0
- patchright/_impl/_json_pipe.py +55 -0
- patchright/_impl/_local_utils.py +73 -0
- patchright/_impl/_locator.py +894 -0
- patchright/_impl/_map.py +18 -0
- patchright/_impl/_network.py +953 -0
- patchright/_impl/_object_factory.py +93 -0
- patchright/_impl/_page.py +1601 -0
- patchright/_impl/_path_utils.py +13 -0
- patchright/_impl/_playwright.py +43 -0
- patchright/_impl/_screencast.py +149 -0
- patchright/_impl/_selectors.py +48 -0
- patchright/_impl/_set_input_files_helpers.py +134 -0
- patchright/_impl/_str_utils.py +59 -0
- patchright/_impl/_stream.py +31 -0
- patchright/_impl/_sync_base.py +130 -0
- patchright/_impl/_tracing.py +243 -0
- patchright/_impl/_transport.py +151 -0
- patchright/_impl/_video.py +41 -0
- patchright/_impl/_waiter.py +168 -0
- patchright/_impl/_web_error.py +33 -0
- patchright/_impl/_web_storage.py +41 -0
- patchright/_impl/_writable_stream.py +26 -0
- patchright/_repo_version.py +1 -0
- patchright/async_api/__init__.py +234 -0
- patchright/async_api/_context_manager.py +38 -0
- patchright/async_api/_generated.py +23396 -0
- patchright/driver/README.md +1 -0
- patchright/driver/package/README.md +3 -0
- patchright/driver/package/ThirdPartyNotices.txt +13 -0
- patchright/driver/package/bin/install_media_pack.ps1 +5 -0
- patchright/driver/package/bin/install_webkit_wsl.ps1 +33 -0
- patchright/driver/package/bin/reinstall_chrome_beta_linux.sh +42 -0
- patchright/driver/package/bin/reinstall_chrome_beta_mac.sh +13 -0
- patchright/driver/package/bin/reinstall_chrome_beta_win.ps1 +24 -0
- patchright/driver/package/bin/reinstall_chrome_stable_linux.sh +42 -0
- patchright/driver/package/bin/reinstall_chrome_stable_mac.sh +12 -0
- patchright/driver/package/bin/reinstall_chrome_stable_win.ps1 +24 -0
- patchright/driver/package/bin/reinstall_msedge_beta_linux.sh +48 -0
- patchright/driver/package/bin/reinstall_msedge_beta_mac.sh +11 -0
- patchright/driver/package/bin/reinstall_msedge_beta_win.ps1 +23 -0
- patchright/driver/package/bin/reinstall_msedge_dev_linux.sh +48 -0
- patchright/driver/package/bin/reinstall_msedge_dev_mac.sh +11 -0
- patchright/driver/package/bin/reinstall_msedge_dev_win.ps1 +23 -0
- patchright/driver/package/bin/reinstall_msedge_stable_linux.sh +48 -0
- patchright/driver/package/bin/reinstall_msedge_stable_mac.sh +11 -0
- patchright/driver/package/bin/reinstall_msedge_stable_win.ps1 +24 -0
- patchright/driver/package/browsers.json +75 -0
- patchright/driver/package/cli.js +21 -0
- patchright/driver/package/index.js +17 -0
- patchright/driver/package/index.mjs +28 -0
- patchright/driver/package/lib/bootstrap.js +88 -0
- patchright/driver/package/lib/coreBundle.js +77498 -0
- patchright/driver/package/lib/entry/cliDaemon.js +5 -0
- patchright/driver/package/lib/entry/dashboardApp.js +3 -0
- patchright/driver/package/lib/entry/mcp.js +10 -0
- patchright/driver/package/lib/entry/oopBrowserDownload.js +3 -0
- patchright/driver/package/lib/package.js +50 -0
- patchright/driver/package/lib/server/chromium/appIcon.png +0 -0
- patchright/driver/package/lib/server/electron/loader.js +107 -0
- patchright/driver/package/lib/serverRegistry.js +7347 -0
- patchright/driver/package/lib/serverRegistry.js.LICENSE +354 -0
- patchright/driver/package/lib/tools/cli-client/channelSessions.js +141 -0
- patchright/driver/package/lib/tools/cli-client/cli.js +6 -0
- patchright/driver/package/lib/tools/cli-client/help.json +708 -0
- patchright/driver/package/lib/tools/cli-client/minimist.js +128 -0
- patchright/driver/package/lib/tools/cli-client/output.js +343 -0
- patchright/driver/package/lib/tools/cli-client/program.js +404 -0
- patchright/driver/package/lib/tools/cli-client/registry.js +176 -0
- patchright/driver/package/lib/tools/cli-client/session.js +258 -0
- patchright/driver/package/lib/tools/dashboard/appIcon.png +0 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/SKILL.md +420 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
- patchright/driver/package/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
- patchright/driver/package/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
- patchright/driver/package/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
- patchright/driver/package/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
- patchright/driver/package/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
- patchright/driver/package/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
- patchright/driver/package/lib/tools/skills/playwright-trace/SKILL.md +171 -0
- patchright/driver/package/lib/tools/utils/extension.js +101 -0
- patchright/driver/package/lib/tools/utils/socketConnection.js +108 -0
- patchright/driver/package/lib/utilsBundle.js +90764 -0
- patchright/driver/package/lib/utilsBundle.js.LICENSE +2179 -0
- patchright/driver/package/lib/webp_codec.LICENSE +173 -0
- patchright/driver/package/lib/webp_codec.wasm +0 -0
- patchright/driver/package/lib/xdg-open +1267 -0
- patchright/driver/package/package.json +34 -0
- patchright/py.typed +0 -0
- patchright/sync_api/__init__.py +234 -0
- patchright/sync_api/_context_manager.py +73 -0
- patchright/sync_api/_generated.py +23590 -0
- pure_patchright-1.62.3.dist-info/LICENSE +201 -0
- pure_patchright-1.62.3.dist-info/METADATA +96 -0
- pure_patchright-1.62.3.dist-info/RECORD +142 -0
- pure_patchright-1.62.3.dist-info/WHEEL +4 -0
- pure_patchright-1.62.3.dist-info/entry_points.txt +5 -0
- pure_patchright-1.62.3.dist-info/licenses/LICENSE +202 -0
- 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"
|