playwright-browserstack 1.62.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. playwright/__init__.py +19 -0
  2. playwright/__main__.py +33 -0
  3. playwright/_impl/__init__.py +0 -0
  4. playwright/_impl/__pyinstaller/__init__.py +20 -0
  5. playwright/_impl/__pyinstaller/hook-playwright.async_api.py +17 -0
  6. playwright/_impl/__pyinstaller/hook-playwright.sync_api.py +17 -0
  7. playwright/_impl/_android.py +704 -0
  8. playwright/_impl/_api_structures.py +365 -0
  9. playwright/_impl/_artifact.py +87 -0
  10. playwright/_impl/_assertions.py +1124 -0
  11. playwright/_impl/_async_base.py +105 -0
  12. playwright/_impl/_browser.py +293 -0
  13. playwright/_impl/_browser_context.py +754 -0
  14. playwright/_impl/_browser_type.py +368 -0
  15. playwright/_impl/_cdp_session.py +46 -0
  16. playwright/_impl/_clock.py +104 -0
  17. playwright/_impl/_connection.py +764 -0
  18. playwright/_impl/_console_message.py +98 -0
  19. playwright/_impl/_credentials.py +57 -0
  20. playwright/_impl/_debugger.py +54 -0
  21. playwright/_impl/_dialog.py +63 -0
  22. playwright/_impl/_disposable.py +96 -0
  23. playwright/_impl/_download.py +64 -0
  24. playwright/_impl/_driver.py +41 -0
  25. playwright/_impl/_element_handle.py +480 -0
  26. playwright/_impl/_errors.py +64 -0
  27. playwright/_impl/_event_context_manager.py +33 -0
  28. playwright/_impl/_fetch.py +626 -0
  29. playwright/_impl/_file_chooser.py +57 -0
  30. playwright/_impl/_form_data.py +34 -0
  31. playwright/_impl/_frame.py +985 -0
  32. playwright/_impl/_glob.py +65 -0
  33. playwright/_impl/_greenlets.py +49 -0
  34. playwright/_impl/_har_router.py +122 -0
  35. playwright/_impl/_helper.py +601 -0
  36. playwright/_impl/_impl_to_api_mapping.py +158 -0
  37. playwright/_impl/_input.py +111 -0
  38. playwright/_impl/_js_handle.py +336 -0
  39. playwright/_impl/_json_pipe.py +97 -0
  40. playwright/_impl/_local_utils.py +95 -0
  41. playwright/_impl/_locator.py +1024 -0
  42. playwright/_impl/_map.py +31 -0
  43. playwright/_impl/_network.py +1017 -0
  44. playwright/_impl/_object_factory.py +116 -0
  45. playwright/_impl/_page.py +1637 -0
  46. playwright/_impl/_path_utils.py +27 -0
  47. playwright/_impl/_playwright.py +68 -0
  48. playwright/_impl/_screencast.py +177 -0
  49. playwright/_impl/_selectors.py +67 -0
  50. playwright/_impl/_set_input_files_helpers.py +158 -0
  51. playwright/_impl/_str_utils.py +76 -0
  52. playwright/_impl/_stream.py +46 -0
  53. playwright/_impl/_sync_base.py +151 -0
  54. playwright/_impl/_tracing.py +281 -0
  55. playwright/_impl/_transport.py +184 -0
  56. playwright/_impl/_video.py +56 -0
  57. playwright/_impl/_waiter.py +197 -0
  58. playwright/_impl/_web_error.py +48 -0
  59. playwright/_impl/_web_storage.py +58 -0
  60. playwright/_impl/_writable_stream.py +42 -0
  61. playwright/_repo_version.py +24 -0
  62. playwright/async_api/__init__.py +275 -0
  63. playwright/async_api/_android_generated.py +462 -0
  64. playwright/async_api/_context_manager.py +57 -0
  65. playwright/async_api/_generated.py +23875 -0
  66. playwright/driver/LICENSE +2946 -0
  67. playwright/driver/README.md +1 -0
  68. playwright/driver/node.exe +0 -0
  69. playwright/driver/package/LICENSE +202 -0
  70. playwright/driver/package/NOTICE +5 -0
  71. playwright/driver/package/README.md +3 -0
  72. playwright/driver/package/ThirdPartyNotices.txt +13 -0
  73. playwright/driver/package/bin/install_media_pack.ps1 +5 -0
  74. playwright/driver/package/bin/install_webkit_wsl.ps1 +33 -0
  75. playwright/driver/package/bin/reinstall_chrome_beta_linux.sh +42 -0
  76. playwright/driver/package/bin/reinstall_chrome_beta_mac.sh +13 -0
  77. playwright/driver/package/bin/reinstall_chrome_beta_win.ps1 +24 -0
  78. playwright/driver/package/bin/reinstall_chrome_stable_linux.sh +42 -0
  79. playwright/driver/package/bin/reinstall_chrome_stable_mac.sh +12 -0
  80. playwright/driver/package/bin/reinstall_chrome_stable_win.ps1 +24 -0
  81. playwright/driver/package/bin/reinstall_msedge_beta_linux.sh +48 -0
  82. playwright/driver/package/bin/reinstall_msedge_beta_mac.sh +11 -0
  83. playwright/driver/package/bin/reinstall_msedge_beta_win.ps1 +23 -0
  84. playwright/driver/package/bin/reinstall_msedge_dev_linux.sh +48 -0
  85. playwright/driver/package/bin/reinstall_msedge_dev_mac.sh +11 -0
  86. playwright/driver/package/bin/reinstall_msedge_dev_win.ps1 +23 -0
  87. playwright/driver/package/bin/reinstall_msedge_stable_linux.sh +48 -0
  88. playwright/driver/package/bin/reinstall_msedge_stable_mac.sh +11 -0
  89. playwright/driver/package/bin/reinstall_msedge_stable_win.ps1 +24 -0
  90. playwright/driver/package/browsers.json +75 -0
  91. playwright/driver/package/cli.js +21 -0
  92. playwright/driver/package/index.d.ts +17 -0
  93. playwright/driver/package/index.js +17 -0
  94. playwright/driver/package/index.mjs +28 -0
  95. playwright/driver/package/lib/bootstrap.js +88 -0
  96. playwright/driver/package/lib/coreBundle.js +74830 -0
  97. playwright/driver/package/lib/entry/cliDaemon.js +5 -0
  98. playwright/driver/package/lib/entry/dashboardApp.js +3 -0
  99. playwright/driver/package/lib/entry/mcp.js +10 -0
  100. playwright/driver/package/lib/entry/oopBrowserDownload.js +3 -0
  101. playwright/driver/package/lib/package.js +50 -0
  102. playwright/driver/package/lib/server/chromium/appIcon.png +0 -0
  103. playwright/driver/package/lib/server/electron/loader.js +118 -0
  104. playwright/driver/package/lib/serverRegistry.js +7347 -0
  105. playwright/driver/package/lib/serverRegistry.js.LICENSE +354 -0
  106. playwright/driver/package/lib/tools/cli-client/channelSessions.js +141 -0
  107. playwright/driver/package/lib/tools/cli-client/cli.js +6 -0
  108. playwright/driver/package/lib/tools/cli-client/help.json +708 -0
  109. playwright/driver/package/lib/tools/cli-client/minimist.js +128 -0
  110. playwright/driver/package/lib/tools/cli-client/output.js +343 -0
  111. playwright/driver/package/lib/tools/cli-client/program.js +404 -0
  112. playwright/driver/package/lib/tools/cli-client/registry.js +176 -0
  113. playwright/driver/package/lib/tools/cli-client/session.js +258 -0
  114. playwright/driver/package/lib/tools/dashboard/appIcon.png +0 -0
  115. playwright/driver/package/lib/tools/skills/playwright-cli/SKILL.md +420 -0
  116. playwright/driver/package/lib/tools/skills/playwright-cli/references/element-attributes.md +23 -0
  117. playwright/driver/package/lib/tools/skills/playwright-cli/references/playwright-tests.md +39 -0
  118. playwright/driver/package/lib/tools/skills/playwright-cli/references/request-mocking.md +87 -0
  119. playwright/driver/package/lib/tools/skills/playwright-cli/references/running-code.md +241 -0
  120. playwright/driver/package/lib/tools/skills/playwright-cli/references/session-management.md +225 -0
  121. playwright/driver/package/lib/tools/skills/playwright-cli/references/storage-state.md +275 -0
  122. playwright/driver/package/lib/tools/skills/playwright-cli/references/test-generation.md +433 -0
  123. playwright/driver/package/lib/tools/skills/playwright-cli/references/tracing.md +139 -0
  124. playwright/driver/package/lib/tools/skills/playwright-cli/references/video-recording.md +143 -0
  125. playwright/driver/package/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  126. playwright/driver/package/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  127. playwright/driver/package/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  128. playwright/driver/package/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  129. playwright/driver/package/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  130. playwright/driver/package/lib/tools/skills/playwright-trace/SKILL.md +171 -0
  131. playwright/driver/package/lib/tools/utils/extension.js +101 -0
  132. playwright/driver/package/lib/tools/utils/socketConnection.js +108 -0
  133. playwright/driver/package/lib/utilsBundle.js +90764 -0
  134. playwright/driver/package/lib/utilsBundle.js.LICENSE +2179 -0
  135. playwright/driver/package/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
  136. playwright/driver/package/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +1 -0
  137. playwright/driver/package/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +1 -0
  138. playwright/driver/package/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +1 -0
  139. playwright/driver/package/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
  140. playwright/driver/package/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
  141. playwright/driver/package/lib/vite/dashboard/assets/safari-na3_-uQk.svg +1 -0
  142. playwright/driver/package/lib/vite/dashboard/index.html +29 -0
  143. playwright/driver/package/lib/vite/dashboard/playwright-logo.svg +24 -0
  144. playwright/driver/package/lib/vite/htmlReport/index.html +16 -0
  145. playwright/driver/package/lib/vite/htmlReport/report.css +2 -0
  146. playwright/driver/package/lib/vite/htmlReport/report.js +32 -0
  147. playwright/driver/package/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  148. playwright/driver/package/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
  149. playwright/driver/package/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
  150. playwright/driver/package/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
  151. playwright/driver/package/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
  152. playwright/driver/package/lib/vite/recorder/index.html +29 -0
  153. playwright/driver/package/lib/vite/recorder/playwright-logo.svg +9 -0
  154. playwright/driver/package/lib/vite/traceViewer/assets/codeMirrorModule-By56iMx7.js +32 -0
  155. playwright/driver/package/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +181 -0
  156. playwright/driver/package/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  157. playwright/driver/package/lib/vite/traceViewer/assets/xtermModule-COQkjINf.js +7 -0
  158. playwright/driver/package/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  159. playwright/driver/package/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
  160. playwright/driver/package/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
  161. playwright/driver/package/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  162. playwright/driver/package/lib/vite/traceViewer/index.Dl36UVQT.js +1 -0
  163. playwright/driver/package/lib/vite/traceViewer/index.html +44 -0
  164. playwright/driver/package/lib/vite/traceViewer/manifest.webmanifest +16 -0
  165. playwright/driver/package/lib/vite/traceViewer/playwright-logo.svg +9 -0
  166. playwright/driver/package/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  167. playwright/driver/package/lib/vite/traceViewer/snapshot.html +10 -0
  168. playwright/driver/package/lib/vite/traceViewer/sw.bundle.js +4 -0
  169. playwright/driver/package/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
  170. playwright/driver/package/lib/vite/traceViewer/uiMode.D962mr9b.js +5 -0
  171. playwright/driver/package/lib/vite/traceViewer/uiMode.html +18 -0
  172. playwright/driver/package/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  173. playwright/driver/package/lib/webp_codec.LICENSE +173 -0
  174. playwright/driver/package/lib/webp_codec.wasm +0 -0
  175. playwright/driver/package/lib/xdg-open +1267 -0
  176. playwright/driver/package/package.json +34 -0
  177. playwright/driver/package/types/protocol.d.ts +24842 -0
  178. playwright/driver/package/types/structs.d.ts +47 -0
  179. playwright/driver/package/types/types.d.ts +26188 -0
  180. playwright/py.typed +0 -0
  181. playwright/sync_api/__init__.py +275 -0
  182. playwright/sync_api/_android_generated.py +494 -0
  183. playwright/sync_api/_context_manager.py +98 -0
  184. playwright/sync_api/_generated.py +24045 -0
  185. playwright_browserstack-1.62.0.dist-info/METADATA +82 -0
  186. playwright_browserstack-1.62.0.dist-info/RECORD +192 -0
  187. playwright_browserstack-1.62.0.dist-info/WHEEL +5 -0
  188. playwright_browserstack-1.62.0.dist-info/entry_points.txt +5 -0
  189. playwright_browserstack-1.62.0.dist-info/licenses/LICENSE +202 -0
  190. playwright_browserstack-1.62.0.dist-info/licenses/LICENSE.txt +51 -0
  191. playwright_browserstack-1.62.0.dist-info/licenses/NOTICE +18 -0
  192. playwright_browserstack-1.62.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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()