playwright-browserstack 1.62.0__py3-none-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- playwright/__init__.py +19 -0
- playwright/__main__.py +33 -0
- playwright/_impl/__init__.py +0 -0
- playwright/_impl/__pyinstaller/__init__.py +20 -0
- playwright/_impl/__pyinstaller/hook-playwright.async_api.py +17 -0
- playwright/_impl/__pyinstaller/hook-playwright.sync_api.py +17 -0
- playwright/_impl/_android.py +704 -0
- playwright/_impl/_api_structures.py +365 -0
- playwright/_impl/_artifact.py +87 -0
- playwright/_impl/_assertions.py +1124 -0
- playwright/_impl/_async_base.py +105 -0
- playwright/_impl/_browser.py +293 -0
- playwright/_impl/_browser_context.py +754 -0
- playwright/_impl/_browser_type.py +368 -0
- playwright/_impl/_cdp_session.py +46 -0
- playwright/_impl/_clock.py +104 -0
- playwright/_impl/_connection.py +764 -0
- playwright/_impl/_console_message.py +98 -0
- playwright/_impl/_credentials.py +57 -0
- playwright/_impl/_debugger.py +54 -0
- playwright/_impl/_dialog.py +63 -0
- playwright/_impl/_disposable.py +96 -0
- playwright/_impl/_download.py +64 -0
- playwright/_impl/_driver.py +41 -0
- playwright/_impl/_element_handle.py +480 -0
- playwright/_impl/_errors.py +64 -0
- playwright/_impl/_event_context_manager.py +33 -0
- playwright/_impl/_fetch.py +626 -0
- playwright/_impl/_file_chooser.py +57 -0
- playwright/_impl/_form_data.py +34 -0
- playwright/_impl/_frame.py +985 -0
- playwright/_impl/_glob.py +65 -0
- playwright/_impl/_greenlets.py +49 -0
- playwright/_impl/_har_router.py +122 -0
- playwright/_impl/_helper.py +601 -0
- playwright/_impl/_impl_to_api_mapping.py +158 -0
- playwright/_impl/_input.py +111 -0
- playwright/_impl/_js_handle.py +336 -0
- playwright/_impl/_json_pipe.py +97 -0
- playwright/_impl/_local_utils.py +95 -0
- playwright/_impl/_locator.py +1024 -0
- playwright/_impl/_map.py +31 -0
- playwright/_impl/_network.py +1017 -0
- playwright/_impl/_object_factory.py +116 -0
- playwright/_impl/_page.py +1637 -0
- playwright/_impl/_path_utils.py +27 -0
- playwright/_impl/_playwright.py +68 -0
- playwright/_impl/_screencast.py +177 -0
- playwright/_impl/_selectors.py +67 -0
- playwright/_impl/_set_input_files_helpers.py +158 -0
- playwright/_impl/_str_utils.py +76 -0
- playwright/_impl/_stream.py +46 -0
- playwright/_impl/_sync_base.py +151 -0
- playwright/_impl/_tracing.py +281 -0
- playwright/_impl/_transport.py +184 -0
- playwright/_impl/_video.py +56 -0
- playwright/_impl/_waiter.py +197 -0
- playwright/_impl/_web_error.py +48 -0
- playwright/_impl/_web_storage.py +58 -0
- playwright/_impl/_writable_stream.py +42 -0
- playwright/_repo_version.py +24 -0
- playwright/async_api/__init__.py +275 -0
- playwright/async_api/_android_generated.py +462 -0
- playwright/async_api/_context_manager.py +57 -0
- playwright/async_api/_generated.py +23875 -0
- playwright/driver/LICENSE +2946 -0
- playwright/driver/README.md +1 -0
- playwright/driver/node.exe +0 -0
- playwright/driver/package/LICENSE +202 -0
- playwright/driver/package/NOTICE +5 -0
- playwright/driver/package/README.md +3 -0
- playwright/driver/package/ThirdPartyNotices.txt +13 -0
- playwright/driver/package/bin/install_media_pack.ps1 +5 -0
- playwright/driver/package/bin/install_webkit_wsl.ps1 +33 -0
- playwright/driver/package/bin/reinstall_chrome_beta_linux.sh +42 -0
- playwright/driver/package/bin/reinstall_chrome_beta_mac.sh +13 -0
- playwright/driver/package/bin/reinstall_chrome_beta_win.ps1 +24 -0
- playwright/driver/package/bin/reinstall_chrome_stable_linux.sh +42 -0
- playwright/driver/package/bin/reinstall_chrome_stable_mac.sh +12 -0
- playwright/driver/package/bin/reinstall_chrome_stable_win.ps1 +24 -0
- playwright/driver/package/bin/reinstall_msedge_beta_linux.sh +48 -0
- playwright/driver/package/bin/reinstall_msedge_beta_mac.sh +11 -0
- playwright/driver/package/bin/reinstall_msedge_beta_win.ps1 +23 -0
- playwright/driver/package/bin/reinstall_msedge_dev_linux.sh +48 -0
- playwright/driver/package/bin/reinstall_msedge_dev_mac.sh +11 -0
- playwright/driver/package/bin/reinstall_msedge_dev_win.ps1 +23 -0
- playwright/driver/package/bin/reinstall_msedge_stable_linux.sh +48 -0
- playwright/driver/package/bin/reinstall_msedge_stable_mac.sh +11 -0
- playwright/driver/package/bin/reinstall_msedge_stable_win.ps1 +24 -0
- playwright/driver/package/browsers.json +75 -0
- playwright/driver/package/cli.js +21 -0
- playwright/driver/package/index.d.ts +17 -0
- playwright/driver/package/index.js +17 -0
- playwright/driver/package/index.mjs +28 -0
- playwright/driver/package/lib/bootstrap.js +88 -0
- playwright/driver/package/lib/coreBundle.js +74830 -0
- playwright/driver/package/lib/entry/cliDaemon.js +5 -0
- playwright/driver/package/lib/entry/dashboardApp.js +3 -0
- playwright/driver/package/lib/entry/mcp.js +10 -0
- playwright/driver/package/lib/entry/oopBrowserDownload.js +3 -0
- playwright/driver/package/lib/package.js +50 -0
- playwright/driver/package/lib/server/chromium/appIcon.png +0 -0
- playwright/driver/package/lib/server/electron/loader.js +118 -0
- playwright/driver/package/lib/serverRegistry.js +7347 -0
- playwright/driver/package/lib/serverRegistry.js.LICENSE +354 -0
- playwright/driver/package/lib/tools/cli-client/channelSessions.js +141 -0
- playwright/driver/package/lib/tools/cli-client/cli.js +6 -0
- playwright/driver/package/lib/tools/cli-client/help.json +708 -0
- playwright/driver/package/lib/tools/cli-client/minimist.js +128 -0
- playwright/driver/package/lib/tools/cli-client/output.js +343 -0
- playwright/driver/package/lib/tools/cli-client/program.js +404 -0
- playwright/driver/package/lib/tools/cli-client/registry.js +176 -0
- playwright/driver/package/lib/tools/cli-client/session.js +258 -0
- playwright/driver/package/lib/tools/dashboard/appIcon.png +0 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/SKILL.md +420 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
- playwright/driver/package/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
- playwright/driver/package/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
- playwright/driver/package/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
- playwright/driver/package/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
- playwright/driver/package/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
- playwright/driver/package/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
- playwright/driver/package/lib/tools/skills/playwright-trace/SKILL.md +171 -0
- playwright/driver/package/lib/tools/utils/extension.js +101 -0
- playwright/driver/package/lib/tools/utils/socketConnection.js +108 -0
- playwright/driver/package/lib/utilsBundle.js +90764 -0
- playwright/driver/package/lib/utilsBundle.js.LICENSE +2179 -0
- playwright/driver/package/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
- playwright/driver/package/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
- playwright/driver/package/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
- playwright/driver/package/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
- playwright/driver/package/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
- playwright/driver/package/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
- playwright/driver/package/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
- playwright/driver/package/lib/vite/dashboard/index.html +29 -0
- playwright/driver/package/lib/vite/dashboard/playwright-logo.svg +24 -0
- playwright/driver/package/lib/vite/htmlReport/index.html +16 -0
- playwright/driver/package/lib/vite/htmlReport/report.css +2 -0
- playwright/driver/package/lib/vite/htmlReport/report.js +32 -0
- playwright/driver/package/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
- playwright/driver/package/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
- playwright/driver/package/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
- playwright/driver/package/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
- playwright/driver/package/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
- playwright/driver/package/lib/vite/recorder/index.html +29 -0
- playwright/driver/package/lib/vite/recorder/playwright-logo.svg +9 -0
- playwright/driver/package/lib/vite/traceViewer/assets/codeMirrorModule-By56iMx7.js +32 -0
- playwright/driver/package/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +181 -0
- playwright/driver/package/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
- playwright/driver/package/lib/vite/traceViewer/assets/xtermModule-COQkjINf.js +7 -0
- playwright/driver/package/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
- playwright/driver/package/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
- playwright/driver/package/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
- playwright/driver/package/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
- playwright/driver/package/lib/vite/traceViewer/index.Dl36UVQT.js +1 -0
- playwright/driver/package/lib/vite/traceViewer/index.html +44 -0
- playwright/driver/package/lib/vite/traceViewer/manifest.webmanifest +16 -0
- playwright/driver/package/lib/vite/traceViewer/playwright-logo.svg +9 -0
- playwright/driver/package/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
- playwright/driver/package/lib/vite/traceViewer/snapshot.html +10 -0
- playwright/driver/package/lib/vite/traceViewer/sw.bundle.js +4 -0
- playwright/driver/package/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
- playwright/driver/package/lib/vite/traceViewer/uiMode.D962mr9b.js +5 -0
- playwright/driver/package/lib/vite/traceViewer/uiMode.html +18 -0
- playwright/driver/package/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
- playwright/driver/package/lib/webp_codec.LICENSE +173 -0
- playwright/driver/package/lib/webp_codec.wasm +0 -0
- playwright/driver/package/lib/xdg-open +1267 -0
- playwright/driver/package/package.json +34 -0
- playwright/driver/package/types/protocol.d.ts +24842 -0
- playwright/driver/package/types/structs.d.ts +47 -0
- playwright/driver/package/types/types.d.ts +26188 -0
- playwright/py.typed +0 -0
- playwright/sync_api/__init__.py +275 -0
- playwright/sync_api/_android_generated.py +494 -0
- playwright/sync_api/_context_manager.py +98 -0
- playwright/sync_api/_generated.py +24045 -0
- playwright_browserstack-1.62.0.dist-info/METADATA +82 -0
- playwright_browserstack-1.62.0.dist-info/RECORD +192 -0
- playwright_browserstack-1.62.0.dist-info/WHEEL +5 -0
- playwright_browserstack-1.62.0.dist-info/entry_points.txt +5 -0
- playwright_browserstack-1.62.0.dist-info/licenses/LICENSE +202 -0
- playwright_browserstack-1.62.0.dist-info/licenses/LICENSE.txt +51 -0
- playwright_browserstack-1.62.0.dist-info/licenses/NOTICE +18 -0
- 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"
|