playwright-browserstack 1.62.0__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,151 @@
|
|
|
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 traceback
|
|
17
|
+
from contextlib import AbstractContextManager
|
|
18
|
+
from types import TracebackType
|
|
19
|
+
from typing import (
|
|
20
|
+
Any,
|
|
21
|
+
Callable,
|
|
22
|
+
Coroutine,
|
|
23
|
+
Generator,
|
|
24
|
+
Generic,
|
|
25
|
+
Optional,
|
|
26
|
+
Type,
|
|
27
|
+
TypeVar,
|
|
28
|
+
Union,
|
|
29
|
+
cast,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
import greenlet
|
|
33
|
+
|
|
34
|
+
from playwright._impl._connection import _capture_stack_trace
|
|
35
|
+
from playwright._impl._helper import Error
|
|
36
|
+
from playwright._impl._impl_to_api_mapping import ImplToApiMapping, ImplWrapper
|
|
37
|
+
|
|
38
|
+
mapping = ImplToApiMapping()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
T = TypeVar("T")
|
|
42
|
+
Self = TypeVar("Self", bound="SyncContextManager")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EventInfo(Generic[T]):
|
|
46
|
+
def __init__(self, sync_base: "SyncBase", future: "asyncio.Future[T]") -> None:
|
|
47
|
+
self._sync_base = sync_base
|
|
48
|
+
self._future = future
|
|
49
|
+
g_self = greenlet.getcurrent()
|
|
50
|
+
self._future.add_done_callback(lambda _: g_self.switch())
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def value(self) -> T:
|
|
54
|
+
while not self._future.done():
|
|
55
|
+
self._sync_base._dispatcher_fiber.switch()
|
|
56
|
+
asyncio._set_running_loop(self._sync_base._loop)
|
|
57
|
+
exception = self._future.exception()
|
|
58
|
+
if exception:
|
|
59
|
+
raise exception
|
|
60
|
+
return cast(T, mapping.from_maybe_impl(self._future.result()))
|
|
61
|
+
|
|
62
|
+
def _cancel(self) -> None:
|
|
63
|
+
self._future.cancel()
|
|
64
|
+
|
|
65
|
+
def is_done(self) -> bool:
|
|
66
|
+
return self._future.done()
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class EventContextManager(Generic[T], AbstractContextManager):
|
|
70
|
+
def __init__(self, sync_base: "SyncBase", future: "asyncio.Future[T]") -> None:
|
|
71
|
+
self._event = EventInfo[T](sync_base, future)
|
|
72
|
+
|
|
73
|
+
def __enter__(self) -> EventInfo[T]:
|
|
74
|
+
return self._event
|
|
75
|
+
|
|
76
|
+
def __exit__(
|
|
77
|
+
self,
|
|
78
|
+
exc_type: Optional[Type[BaseException]],
|
|
79
|
+
exc_val: Optional[BaseException],
|
|
80
|
+
exc_tb: Optional[TracebackType],
|
|
81
|
+
) -> None:
|
|
82
|
+
if exc_val:
|
|
83
|
+
self._event._cancel()
|
|
84
|
+
else:
|
|
85
|
+
self._event.value
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SyncBase(ImplWrapper):
|
|
89
|
+
def __init__(self, impl_obj: Any) -> None:
|
|
90
|
+
super().__init__(impl_obj)
|
|
91
|
+
self._loop: asyncio.AbstractEventLoop = impl_obj._loop
|
|
92
|
+
self._dispatcher_fiber = impl_obj._dispatcher_fiber
|
|
93
|
+
|
|
94
|
+
def __str__(self) -> str:
|
|
95
|
+
return self._impl_obj.__str__()
|
|
96
|
+
|
|
97
|
+
def _sync(
|
|
98
|
+
self,
|
|
99
|
+
coro: Union[Coroutine[Any, Any, Any], Generator[Any, Any, Any]],
|
|
100
|
+
) -> Any:
|
|
101
|
+
__tracebackhide__ = True
|
|
102
|
+
if self._loop.is_closed():
|
|
103
|
+
coro.close()
|
|
104
|
+
raise Error("Event loop is closed! Is Playwright already stopped?")
|
|
105
|
+
|
|
106
|
+
g_self = greenlet.getcurrent()
|
|
107
|
+
task: asyncio.tasks.Task[Any] = self._loop.create_task(coro)
|
|
108
|
+
setattr(task, "__pw_stack__", _capture_stack_trace())
|
|
109
|
+
setattr(task, "__pw_stack_trace__", traceback.extract_stack(limit=10))
|
|
110
|
+
|
|
111
|
+
task.add_done_callback(lambda _: g_self.switch())
|
|
112
|
+
while not task.done():
|
|
113
|
+
self._dispatcher_fiber.switch()
|
|
114
|
+
asyncio._set_running_loop(self._loop)
|
|
115
|
+
return task.result()
|
|
116
|
+
|
|
117
|
+
def _wrap_handler(
|
|
118
|
+
self, handler: Union[Callable[..., Any], Any]
|
|
119
|
+
) -> Callable[..., None]:
|
|
120
|
+
if callable(handler):
|
|
121
|
+
return mapping.wrap_handler(handler)
|
|
122
|
+
return handler
|
|
123
|
+
|
|
124
|
+
def on(self, event: Any, f: Any) -> None:
|
|
125
|
+
"""Registers the function ``f`` to the event name ``event``."""
|
|
126
|
+
self._impl_obj.on(event, self._wrap_handler(f))
|
|
127
|
+
|
|
128
|
+
def once(self, event: Any, f: Any) -> None:
|
|
129
|
+
"""The same as ``self.on``, except that the listener is automatically
|
|
130
|
+
removed after being called.
|
|
131
|
+
"""
|
|
132
|
+
self._impl_obj.once(event, self._wrap_handler(f))
|
|
133
|
+
|
|
134
|
+
def remove_listener(self, event: Any, f: Any) -> None:
|
|
135
|
+
"""Removes the function ``f`` from ``event``."""
|
|
136
|
+
self._impl_obj.remove_listener(event, self._wrap_handler(f))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class SyncContextManager(SyncBase):
|
|
140
|
+
def __enter__(self: Self) -> Self:
|
|
141
|
+
return self
|
|
142
|
+
|
|
143
|
+
def __exit__(
|
|
144
|
+
self,
|
|
145
|
+
exc_type: Optional[Type[BaseException]],
|
|
146
|
+
exc_val: Optional[BaseException],
|
|
147
|
+
_traceback: Optional[TracebackType],
|
|
148
|
+
) -> None:
|
|
149
|
+
self.close()
|
|
150
|
+
|
|
151
|
+
def close(self) -> None: ...
|
|
@@ -0,0 +1,281 @@
|
|
|
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 pathlib
|
|
16
|
+
from typing import Any, Dict, Literal, Optional, Pattern, Union, cast
|
|
17
|
+
|
|
18
|
+
from playwright._impl._api_structures import TracingGroupLocation
|
|
19
|
+
from playwright._impl._artifact import Artifact
|
|
20
|
+
from playwright._impl._connection import (
|
|
21
|
+
ChannelOwner,
|
|
22
|
+
from_channel,
|
|
23
|
+
from_nullable_channel,
|
|
24
|
+
)
|
|
25
|
+
from playwright._impl._disposable import DisposableStub
|
|
26
|
+
from playwright._impl._helper import Error, locals_to_params
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Tracing(ChannelOwner):
|
|
30
|
+
def __init__(
|
|
31
|
+
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(parent, type, guid, initializer)
|
|
34
|
+
self._include_sources: bool = False
|
|
35
|
+
self._is_live: bool = False
|
|
36
|
+
self._stacks_id: Optional[str] = None
|
|
37
|
+
self._is_tracing: bool = False
|
|
38
|
+
self._traces_dir: Optional[str] = None
|
|
39
|
+
self._har_id: Optional[str] = None
|
|
40
|
+
self._har_recorders: Dict[str, Dict[str, str]] = {}
|
|
41
|
+
|
|
42
|
+
async def start(
|
|
43
|
+
self,
|
|
44
|
+
name: str = None,
|
|
45
|
+
title: str = None,
|
|
46
|
+
snapshots: bool = None,
|
|
47
|
+
screenshots: bool = None,
|
|
48
|
+
sources: bool = None,
|
|
49
|
+
live: bool = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
params = locals_to_params(locals())
|
|
52
|
+
self._include_sources = bool(sources)
|
|
53
|
+
self._is_live = bool(live)
|
|
54
|
+
|
|
55
|
+
await self._channel.send(
|
|
56
|
+
"tracingStart",
|
|
57
|
+
None,
|
|
58
|
+
{
|
|
59
|
+
"name": name,
|
|
60
|
+
"snapshots": snapshots,
|
|
61
|
+
"screenshots": screenshots,
|
|
62
|
+
"live": live,
|
|
63
|
+
},
|
|
64
|
+
)
|
|
65
|
+
trace_name = await self._channel.send(
|
|
66
|
+
"tracingStartChunk", None, {"title": title, "name": name}
|
|
67
|
+
)
|
|
68
|
+
await self._start_collecting_stacks(trace_name)
|
|
69
|
+
|
|
70
|
+
async def start_chunk(self, title: str = None, name: str = None) -> None:
|
|
71
|
+
params = locals_to_params(locals())
|
|
72
|
+
trace_name = await self._channel.send("tracingStartChunk", None, params)
|
|
73
|
+
await self._start_collecting_stacks(trace_name)
|
|
74
|
+
|
|
75
|
+
async def _start_collecting_stacks(self, trace_name: str) -> None:
|
|
76
|
+
if not self._is_tracing:
|
|
77
|
+
self._is_tracing = True
|
|
78
|
+
self._connection.set_is_tracing(True)
|
|
79
|
+
self._stacks_id = await self._connection.local_utils.tracing_started(
|
|
80
|
+
self._traces_dir, trace_name, self._is_live
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def stop_chunk(self, path: Union[pathlib.Path, str] = None) -> None:
|
|
84
|
+
await self._do_stop_chunk(path)
|
|
85
|
+
|
|
86
|
+
async def stop(self, path: Union[pathlib.Path, str] = None) -> None:
|
|
87
|
+
await self._do_stop_chunk(path)
|
|
88
|
+
await self._channel.send(
|
|
89
|
+
"tracingStop",
|
|
90
|
+
None,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
async def _do_stop_chunk(self, file_path: Union[pathlib.Path, str] = None) -> None:
|
|
94
|
+
self._reset_stack_counter()
|
|
95
|
+
|
|
96
|
+
if not file_path:
|
|
97
|
+
# Not interested in any artifacts
|
|
98
|
+
await self._channel.send("tracingStopChunk", None, {"mode": "discard"})
|
|
99
|
+
if self._stacks_id:
|
|
100
|
+
await self._connection.local_utils.trace_discarded(self._stacks_id)
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
is_local = not self._connection.is_remote
|
|
104
|
+
|
|
105
|
+
if is_local:
|
|
106
|
+
result = await self._channel.send_return_as_dict(
|
|
107
|
+
"tracingStopChunk", None, {"mode": "entries"}
|
|
108
|
+
)
|
|
109
|
+
await self._connection.local_utils.zip(
|
|
110
|
+
{
|
|
111
|
+
"zipFile": str(file_path),
|
|
112
|
+
"entries": result["entries"],
|
|
113
|
+
"stacksId": self._stacks_id,
|
|
114
|
+
"mode": "write",
|
|
115
|
+
"includeSources": self._include_sources,
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
result = await self._channel.send_return_as_dict(
|
|
121
|
+
"tracingStopChunk",
|
|
122
|
+
None,
|
|
123
|
+
{
|
|
124
|
+
"mode": "archive",
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
artifact = cast(
|
|
129
|
+
Optional[Artifact],
|
|
130
|
+
from_nullable_channel(result.get("artifact")),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# The artifact may be missing if the browser closed while stopping tracing.
|
|
134
|
+
if not artifact:
|
|
135
|
+
if self._stacks_id:
|
|
136
|
+
await self._connection.local_utils.trace_discarded(self._stacks_id)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
# Save trace to the final local file.
|
|
140
|
+
await artifact.save_as(file_path)
|
|
141
|
+
await artifact.delete()
|
|
142
|
+
|
|
143
|
+
await self._connection.local_utils.zip(
|
|
144
|
+
{
|
|
145
|
+
"zipFile": str(file_path),
|
|
146
|
+
"entries": [],
|
|
147
|
+
"stacksId": self._stacks_id,
|
|
148
|
+
"mode": "append",
|
|
149
|
+
"includeSources": self._include_sources,
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def _reset_stack_counter(self) -> None:
|
|
154
|
+
if self._is_tracing:
|
|
155
|
+
self._is_tracing = False
|
|
156
|
+
self._connection.set_is_tracing(False)
|
|
157
|
+
|
|
158
|
+
async def group(
|
|
159
|
+
self, name: str, location: TracingGroupLocation = None
|
|
160
|
+
) -> DisposableStub:
|
|
161
|
+
await self._channel.send("tracingGroup", None, locals_to_params(locals()))
|
|
162
|
+
return DisposableStub(lambda: self.group_end(), self)
|
|
163
|
+
|
|
164
|
+
async def group_end(self) -> None:
|
|
165
|
+
await self._channel.send(
|
|
166
|
+
"tracingGroupEnd",
|
|
167
|
+
None,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
async def start_har(
|
|
171
|
+
self,
|
|
172
|
+
path: Union[pathlib.Path, str],
|
|
173
|
+
content: Literal["attach", "embed", "omit"] = None,
|
|
174
|
+
mode: Literal["full", "minimal"] = None,
|
|
175
|
+
urlFilter: Union[str, Pattern[str]] = None,
|
|
176
|
+
) -> DisposableStub:
|
|
177
|
+
if self._har_id:
|
|
178
|
+
raise Error("HAR recording has already been started")
|
|
179
|
+
is_zip = str(path).endswith(".zip")
|
|
180
|
+
default_content: Literal["attach", "embed", "omit"] = (
|
|
181
|
+
"attach" if is_zip else "embed"
|
|
182
|
+
)
|
|
183
|
+
self._har_id = await self._record_into_har(
|
|
184
|
+
har=path,
|
|
185
|
+
page=None,
|
|
186
|
+
url=urlFilter,
|
|
187
|
+
update_content=content or default_content,
|
|
188
|
+
update_mode=mode or "full",
|
|
189
|
+
)
|
|
190
|
+
return DisposableStub(lambda: self.stop_har(), self)
|
|
191
|
+
|
|
192
|
+
async def _record_into_har(
|
|
193
|
+
self,
|
|
194
|
+
har: Union[pathlib.Path, str],
|
|
195
|
+
page: Optional[ChannelOwner],
|
|
196
|
+
url: Union[str, Pattern[str]] = None,
|
|
197
|
+
update_content: Literal["attach", "embed", "omit"] = None,
|
|
198
|
+
update_mode: Literal["full", "minimal"] = None,
|
|
199
|
+
resourcesDir: Optional[str] = None,
|
|
200
|
+
) -> str:
|
|
201
|
+
is_zip = str(har).endswith(".zip")
|
|
202
|
+
url_glob: Optional[str] = None
|
|
203
|
+
url_regex_source: Optional[str] = None
|
|
204
|
+
url_regex_flags: Optional[str] = None
|
|
205
|
+
if isinstance(url, str):
|
|
206
|
+
url_glob = url
|
|
207
|
+
elif url is not None:
|
|
208
|
+
url_regex_source = url.pattern
|
|
209
|
+
url_regex_flags = "".join(
|
|
210
|
+
flag
|
|
211
|
+
for flag, mask in (("i", 2), ("m", 8), ("s", 16))
|
|
212
|
+
if url.flags & mask
|
|
213
|
+
)
|
|
214
|
+
options: Dict[str, object] = {
|
|
215
|
+
"content": update_content or "attach",
|
|
216
|
+
"mode": update_mode or "minimal",
|
|
217
|
+
"harPath": None if is_zip else str(har),
|
|
218
|
+
}
|
|
219
|
+
if url_glob is not None:
|
|
220
|
+
options["urlGlob"] = url_glob
|
|
221
|
+
if url_regex_source is not None:
|
|
222
|
+
options["urlRegexSource"] = url_regex_source
|
|
223
|
+
if url_regex_flags is not None:
|
|
224
|
+
options["urlRegexFlags"] = url_regex_flags
|
|
225
|
+
if resourcesDir is not None:
|
|
226
|
+
options["resourcesDir"] = resourcesDir
|
|
227
|
+
params: Dict[str, Any] = {"options": options}
|
|
228
|
+
if page is not None:
|
|
229
|
+
params["page"] = page._channel
|
|
230
|
+
result = await self._channel.send_return_as_dict("harStart", None, params)
|
|
231
|
+
har_id = result["harId"]
|
|
232
|
+
self._har_recorders[har_id] = {"path": str(har)}
|
|
233
|
+
return har_id
|
|
234
|
+
|
|
235
|
+
async def _export_all_hars(self) -> None:
|
|
236
|
+
for har_id in list(self._har_recorders.keys()):
|
|
237
|
+
await self._export_har(har_id)
|
|
238
|
+
self._har_id = None
|
|
239
|
+
|
|
240
|
+
async def stop_har(self) -> None:
|
|
241
|
+
har_id = self._har_id
|
|
242
|
+
if not har_id:
|
|
243
|
+
return
|
|
244
|
+
self._har_id = None
|
|
245
|
+
await self._export_har(har_id)
|
|
246
|
+
|
|
247
|
+
async def _export_har(self, har_id: str) -> None:
|
|
248
|
+
params = self._har_recorders.pop(har_id, None)
|
|
249
|
+
if not params:
|
|
250
|
+
return
|
|
251
|
+
is_local = not self._connection.is_remote
|
|
252
|
+
is_zip = params["path"].endswith(".zip")
|
|
253
|
+
|
|
254
|
+
if is_local:
|
|
255
|
+
result = await self._channel.send_return_as_dict(
|
|
256
|
+
"harExport", None, {"harId": har_id, "mode": "entries"}
|
|
257
|
+
)
|
|
258
|
+
if not is_zip:
|
|
259
|
+
# Server wrote HAR and resources to the user's chosen paths.
|
|
260
|
+
return
|
|
261
|
+
await self._connection.local_utils.zip(
|
|
262
|
+
{
|
|
263
|
+
"zipFile": params["path"],
|
|
264
|
+
"entries": result["entries"],
|
|
265
|
+
"mode": "write",
|
|
266
|
+
"includeSources": False,
|
|
267
|
+
}
|
|
268
|
+
)
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
result = await self._channel.send_return_as_dict(
|
|
272
|
+
"harExport", None, {"harId": har_id, "mode": "archive"}
|
|
273
|
+
)
|
|
274
|
+
artifact = cast(Artifact, from_channel(result["artifact"]))
|
|
275
|
+
if is_zip:
|
|
276
|
+
await artifact.save_as(params["path"])
|
|
277
|
+
await artifact.delete()
|
|
278
|
+
return
|
|
279
|
+
# Uncompressed har is not supported in thin clients
|
|
280
|
+
await artifact.save_as(params["path"] + ".tmp")
|
|
281
|
+
await artifact.delete()
|
|
@@ -0,0 +1,184 @@
|
|
|
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 io
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from abc import ABC, abstractmethod
|
|
22
|
+
from typing import Callable, Dict, Optional, Union
|
|
23
|
+
|
|
24
|
+
from playwright._impl._driver import compute_driver_executable, get_driver_env
|
|
25
|
+
from playwright._impl._helper import ParsedMessagePayload
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Sourced from: https://github.com/pytest-dev/pytest/blob/da01ee0a4bb0af780167ecd228ab3ad249511302/src/_pytest/faulthandler.py#L69-L77
|
|
29
|
+
def _get_stderr_fileno() -> Optional[int]:
|
|
30
|
+
try:
|
|
31
|
+
# when using pythonw, sys.stderr is None.
|
|
32
|
+
# when Pyinstaller is used, there is no closed attribute because Pyinstaller monkey-patches it with a NullWriter class
|
|
33
|
+
if sys.stderr is None or not hasattr(sys.stderr, "closed"):
|
|
34
|
+
return None
|
|
35
|
+
if sys.stderr.closed:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
return sys.stderr.fileno()
|
|
39
|
+
except (NotImplementedError, AttributeError, io.UnsupportedOperation):
|
|
40
|
+
# pytest-xdist monkeypatches sys.stderr with an object that is not an actual file.
|
|
41
|
+
# https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors
|
|
42
|
+
# This is potentially dangerous, but the best we can do.
|
|
43
|
+
if not hasattr(sys, "__stderr__") or not sys.__stderr__:
|
|
44
|
+
return None
|
|
45
|
+
return sys.__stderr__.fileno()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Transport(ABC):
|
|
49
|
+
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
50
|
+
self._loop = loop
|
|
51
|
+
self.on_message: Callable[[ParsedMessagePayload], None] = lambda _: None
|
|
52
|
+
self.on_error_future: asyncio.Future = loop.create_future()
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def request_stop(self) -> None:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def dispose(self) -> None:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
async def wait_until_stopped(self) -> None:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
async def connect(self) -> None:
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
async def run(self) -> None:
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def send(self, message: Dict) -> None:
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def serialize_message(self, message: Dict) -> bytes:
|
|
78
|
+
msg = json.dumps(message)
|
|
79
|
+
if "DEBUGP" in os.environ: # pragma: no cover
|
|
80
|
+
print("\x1b[32mSEND>\x1b[0m", json.dumps(message, indent=2))
|
|
81
|
+
return msg.encode()
|
|
82
|
+
|
|
83
|
+
def deserialize_message(self, data: Union[str, bytes]) -> ParsedMessagePayload:
|
|
84
|
+
obj = json.loads(data)
|
|
85
|
+
|
|
86
|
+
if "DEBUGP" in os.environ: # pragma: no cover
|
|
87
|
+
print("\x1b[33mRECV>\x1b[0m", json.dumps(obj, indent=2))
|
|
88
|
+
return obj
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class PipeTransport(Transport):
|
|
92
|
+
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
|
|
93
|
+
super().__init__(loop)
|
|
94
|
+
self._stopped = False
|
|
95
|
+
|
|
96
|
+
def request_stop(self) -> None:
|
|
97
|
+
assert self._output
|
|
98
|
+
self._stopped = True
|
|
99
|
+
self._output.close()
|
|
100
|
+
|
|
101
|
+
async def wait_until_stopped(self) -> None:
|
|
102
|
+
await self._stopped_future
|
|
103
|
+
|
|
104
|
+
async def connect(self) -> None:
|
|
105
|
+
self._stopped_future: asyncio.Future = asyncio.Future()
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
# For pyinstaller and Nuitka
|
|
109
|
+
env = get_driver_env()
|
|
110
|
+
if getattr(sys, "frozen", False) or globals().get("__compiled__"):
|
|
111
|
+
env.setdefault("PLAYWRIGHT_BROWSERS_PATH", "0")
|
|
112
|
+
|
|
113
|
+
startupinfo = None
|
|
114
|
+
if sys.platform == "win32":
|
|
115
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
116
|
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
117
|
+
startupinfo.wShowWindow = subprocess.SW_HIDE
|
|
118
|
+
|
|
119
|
+
executable_path, entrypoint_path = compute_driver_executable()
|
|
120
|
+
self._proc = await asyncio.create_subprocess_exec(
|
|
121
|
+
executable_path,
|
|
122
|
+
entrypoint_path,
|
|
123
|
+
"run-driver",
|
|
124
|
+
stdin=asyncio.subprocess.PIPE,
|
|
125
|
+
stdout=asyncio.subprocess.PIPE,
|
|
126
|
+
stderr=_get_stderr_fileno(),
|
|
127
|
+
limit=32768,
|
|
128
|
+
env=env,
|
|
129
|
+
startupinfo=startupinfo,
|
|
130
|
+
)
|
|
131
|
+
except Exception as exc:
|
|
132
|
+
self.on_error_future.set_exception(exc)
|
|
133
|
+
raise exc
|
|
134
|
+
|
|
135
|
+
self._output = self._proc.stdin
|
|
136
|
+
|
|
137
|
+
async def run(self) -> None:
|
|
138
|
+
assert self._proc.stdout
|
|
139
|
+
assert self._proc.stdin
|
|
140
|
+
try:
|
|
141
|
+
while not self._stopped:
|
|
142
|
+
try:
|
|
143
|
+
buffer = await self._proc.stdout.readexactly(4)
|
|
144
|
+
if self._stopped:
|
|
145
|
+
break
|
|
146
|
+
length = int.from_bytes(buffer, byteorder="little", signed=False)
|
|
147
|
+
buffer = bytes(0)
|
|
148
|
+
while length:
|
|
149
|
+
to_read = min(length, 32768)
|
|
150
|
+
data = await self._proc.stdout.readexactly(to_read)
|
|
151
|
+
if self._stopped:
|
|
152
|
+
break
|
|
153
|
+
length -= to_read
|
|
154
|
+
if len(buffer):
|
|
155
|
+
buffer = buffer + data
|
|
156
|
+
else:
|
|
157
|
+
buffer = data
|
|
158
|
+
if self._stopped:
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
obj = self.deserialize_message(buffer)
|
|
162
|
+
self.on_message(obj)
|
|
163
|
+
except asyncio.IncompleteReadError:
|
|
164
|
+
if not self._stopped:
|
|
165
|
+
self.on_error_future.set_exception(
|
|
166
|
+
Exception("Connection closed while reading from the driver")
|
|
167
|
+
)
|
|
168
|
+
break
|
|
169
|
+
await asyncio.sleep(0)
|
|
170
|
+
|
|
171
|
+
await self._proc.communicate()
|
|
172
|
+
finally:
|
|
173
|
+
# Release waiters on wait_until_stopped() even if this task was
|
|
174
|
+
# cancelled before reaching the end (e.g. by asyncio.run()'s
|
|
175
|
+
# task-cancellation phase that runs before asyncio-atexit hooks).
|
|
176
|
+
if not self._stopped_future.done():
|
|
177
|
+
self._stopped_future.set_result(None)
|
|
178
|
+
|
|
179
|
+
def send(self, message: Dict) -> None:
|
|
180
|
+
assert self._output
|
|
181
|
+
data = self.serialize_message(message)
|
|
182
|
+
self._output.write(
|
|
183
|
+
len(data).to_bytes(4, byteorder="little", signed=False) + data
|
|
184
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
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 pathlib
|
|
16
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
17
|
+
|
|
18
|
+
from playwright._impl._artifact import Artifact
|
|
19
|
+
from playwright._impl._helper import Error
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
22
|
+
from playwright._impl._page import Page
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Video:
|
|
26
|
+
def __init__(self, page: "Page", artifact: Optional[Artifact]) -> None:
|
|
27
|
+
self._loop = page._loop
|
|
28
|
+
self._dispatcher_fiber = page._dispatcher_fiber
|
|
29
|
+
self._page = page
|
|
30
|
+
self._is_remote = page._connection.is_remote
|
|
31
|
+
self._artifact = artifact
|
|
32
|
+
|
|
33
|
+
def __repr__(self) -> str:
|
|
34
|
+
return f"<Video page={self._page}>"
|
|
35
|
+
|
|
36
|
+
async def path(self) -> pathlib.Path:
|
|
37
|
+
if self._is_remote:
|
|
38
|
+
raise Error(
|
|
39
|
+
"Path is not available when using browserType.connect(). Use save_as() to save a local copy."
|
|
40
|
+
)
|
|
41
|
+
if not self._artifact:
|
|
42
|
+
raise Error("Video recording has not been started.")
|
|
43
|
+
return self._artifact.absolute_path
|
|
44
|
+
|
|
45
|
+
async def save_as(self, path: Union[str, pathlib.Path]) -> None:
|
|
46
|
+
if self._page._connection._is_sync and not self._page._is_closed:
|
|
47
|
+
raise Error(
|
|
48
|
+
"Page is not yet closed. Close the page prior to calling save_as"
|
|
49
|
+
)
|
|
50
|
+
if not self._artifact:
|
|
51
|
+
raise Error("Video recording has not been started.")
|
|
52
|
+
await self._artifact.save_as(path)
|
|
53
|
+
|
|
54
|
+
async def delete(self) -> None:
|
|
55
|
+
if self._artifact:
|
|
56
|
+
await self._artifact.delete()
|