browser-control-and-automation-cli 0.1.1.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- browser_cli/__init__.py +7 -0
- browser_cli/actions/__init__.py +5 -0
- browser_cli/actions/cli_specs.py +868 -0
- browser_cli/agent_scope/__init__.py +14 -0
- browser_cli/automation/__init__.py +11 -0
- browser_cli/automation/api/__init__.py +5 -0
- browser_cli/automation/api/server.py +321 -0
- browser_cli/automation/hooks.py +42 -0
- browser_cli/automation/loader.py +134 -0
- browser_cli/automation/models.py +127 -0
- browser_cli/automation/persistence/__init__.py +5 -0
- browser_cli/automation/persistence/store.py +682 -0
- browser_cli/automation/projections.py +279 -0
- browser_cli/automation/publisher.py +153 -0
- browser_cli/automation/scheduler/__init__.py +5 -0
- browser_cli/automation/scheduler/schedule.py +113 -0
- browser_cli/automation/service/__init__.py +8 -0
- browser_cli/automation/service/__main__.py +64 -0
- browser_cli/automation/service/client.py +232 -0
- browser_cli/automation/service/runtime.py +388 -0
- browser_cli/automation/toml.py +35 -0
- browser_cli/automation/web/__init__.py +5 -0
- browser_cli/automation/web/assets.py +377 -0
- browser_cli/browser/__init__.py +1 -0
- browser_cli/browser/models.py +44 -0
- browser_cli/browser/network_capture.py +392 -0
- browser_cli/browser/service.py +1519 -0
- browser_cli/browser/session.py +158 -0
- browser_cli/browser/snapshot.py +35 -0
- browser_cli/browser/stealth.py +450 -0
- browser_cli/cli/__init__.py +1 -0
- browser_cli/cli/error_hints.py +26 -0
- browser_cli/cli/main.py +298 -0
- browser_cli/commands/__init__.py +1 -0
- browser_cli/commands/action.py +25 -0
- browser_cli/commands/automation.py +304 -0
- browser_cli/commands/doctor.py +336 -0
- browser_cli/commands/install_skills.py +138 -0
- browser_cli/commands/paths.py +31 -0
- browser_cli/commands/read.py +33 -0
- browser_cli/commands/reload.py +51 -0
- browser_cli/commands/status.py +444 -0
- browser_cli/commands/task.py +71 -0
- browser_cli/constants.py +97 -0
- browser_cli/daemon/__init__.py +1 -0
- browser_cli/daemon/__main__.py +34 -0
- browser_cli/daemon/app.py +941 -0
- browser_cli/daemon/browser_service.py +998 -0
- browser_cli/daemon/client.py +280 -0
- browser_cli/daemon/models.py +63 -0
- browser_cli/daemon/runtime_presentation.py +167 -0
- browser_cli/daemon/server.py +103 -0
- browser_cli/daemon/state.py +15 -0
- browser_cli/daemon/transport.py +77 -0
- browser_cli/drivers/__init__.py +6 -0
- browser_cli/drivers/_extension/__init__.py +1 -0
- browser_cli/drivers/_extension/artifact_actions.py +154 -0
- browser_cli/drivers/_extension/dialog_actions.py +35 -0
- browser_cli/drivers/_extension/input_actions.py +63 -0
- browser_cli/drivers/_extension/locator_actions.py +100 -0
- browser_cli/drivers/_extension/observe_actions.py +285 -0
- browser_cli/drivers/_extension/page_actions.py +210 -0
- browser_cli/drivers/_extension/state_actions.py +134 -0
- browser_cli/drivers/base.py +391 -0
- browser_cli/drivers/extension_driver.py +33 -0
- browser_cli/drivers/models.py +21 -0
- browser_cli/drivers/playwright_driver.py +499 -0
- browser_cli/error_codes.py +27 -0
- browser_cli/errors.py +146 -0
- browser_cli/exit_codes.py +9 -0
- browser_cli/extension/__init__.py +32 -0
- browser_cli/extension/protocol.py +222 -0
- browser_cli/extension/session.py +393 -0
- browser_cli/network.py +147 -0
- browser_cli/outputs/__init__.py +1 -0
- browser_cli/outputs/json.py +10 -0
- browser_cli/outputs/render.py +7 -0
- browser_cli/packaged_skills/__init__.py +1 -0
- browser_cli/packaged_skills/browser-cli-converge/SKILL.md +62 -0
- browser_cli/packaged_skills/browser-cli-delivery/SKILL.md +61 -0
- browser_cli/packaged_skills/browser-cli-explore/SKILL.md +77 -0
- browser_cli/profiles/__init__.py +1 -0
- browser_cli/profiles/discovery.py +185 -0
- browser_cli/refs/__init__.py +17 -0
- browser_cli/refs/generator.py +386 -0
- browser_cli/refs/models.py +127 -0
- browser_cli/refs/registry.py +51 -0
- browser_cli/refs/resolver.py +214 -0
- browser_cli/tabs/__init__.py +5 -0
- browser_cli/tabs/registry.py +283 -0
- browser_cli/task_runtime/__init__.py +31 -0
- browser_cli/task_runtime/client.py +107 -0
- browser_cli/task_runtime/entrypoint.py +110 -0
- browser_cli/task_runtime/errors.py +17 -0
- browser_cli/task_runtime/flow.py +171 -0
- browser_cli/task_runtime/models.py +97 -0
- browser_cli/task_runtime/read.py +71 -0
- browser_cli/task_runtime/templates.py +58 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/METADATA +340 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/RECORD +104 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/WHEEL +5 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/entry_points.txt +2 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/licenses/LICENSE +21 -0
- browser_control_and_automation_cli-0.1.1.dev3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"""Playwright-backed driver adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from browser_cli.browser.service import BrowserService as PlaywrightBrowserService
|
|
8
|
+
from browser_cli.profiles.discovery import ChromeEnvironment
|
|
9
|
+
from browser_cli.refs.models import LocatorSpec, SnapshotInput
|
|
10
|
+
|
|
11
|
+
from .base import BrowserDriver
|
|
12
|
+
from .models import DriverHealth, TabState
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PlaywrightDriver(BrowserDriver):
|
|
16
|
+
name = "playwright"
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
chrome_environment: ChromeEnvironment | None = None,
|
|
21
|
+
*,
|
|
22
|
+
headless: bool | None = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
self._service = PlaywrightBrowserService(
|
|
25
|
+
chrome_environment=chrome_environment, headless=headless
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def chrome_environment(self) -> ChromeEnvironment | None:
|
|
30
|
+
return self._service.chrome_environment
|
|
31
|
+
|
|
32
|
+
def configure_environment(self, chrome_environment: ChromeEnvironment) -> None:
|
|
33
|
+
self._service.configure_environment(chrome_environment)
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def build_search_url(query: str, engine: str = "duckduckgo") -> str:
|
|
37
|
+
return PlaywrightBrowserService._build_search_url(query, engine)
|
|
38
|
+
|
|
39
|
+
async def ensure_started(self) -> None:
|
|
40
|
+
await self._service.ensure_started()
|
|
41
|
+
|
|
42
|
+
async def stop(self) -> dict[str, Any]:
|
|
43
|
+
return await self._service.stop()
|
|
44
|
+
|
|
45
|
+
async def health(self) -> DriverHealth:
|
|
46
|
+
environment = self.chrome_environment
|
|
47
|
+
return DriverHealth(
|
|
48
|
+
name=self.name,
|
|
49
|
+
available=True,
|
|
50
|
+
details={
|
|
51
|
+
"chrome_environment": (environment.source if environment is not None else None),
|
|
52
|
+
"user_data_dir": str(environment.user_data_dir)
|
|
53
|
+
if environment is not None
|
|
54
|
+
else None,
|
|
55
|
+
"profile_directory": environment.profile_directory
|
|
56
|
+
if environment is not None
|
|
57
|
+
else None,
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
async def new_tab(
|
|
62
|
+
self,
|
|
63
|
+
*,
|
|
64
|
+
page_id: str,
|
|
65
|
+
url: str | None = None,
|
|
66
|
+
wait_until: str = "load",
|
|
67
|
+
timeout_seconds: float | None = None,
|
|
68
|
+
) -> dict[str, Any]:
|
|
69
|
+
return await self._service.new_tab(
|
|
70
|
+
page_id=page_id,
|
|
71
|
+
url=url,
|
|
72
|
+
wait_until=wait_until,
|
|
73
|
+
timeout_seconds=timeout_seconds,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def close_tab(self, page_id: str) -> dict[str, Any]:
|
|
77
|
+
return await self._service.close_tab(page_id)
|
|
78
|
+
|
|
79
|
+
async def list_tabs(self) -> list[TabState]:
|
|
80
|
+
results: list[TabState] = []
|
|
81
|
+
for page_id in sorted(self._service._pages.keys()): # noqa: SLF001
|
|
82
|
+
summary = await self._service.get_page_summary(page_id)
|
|
83
|
+
results.append(
|
|
84
|
+
TabState(
|
|
85
|
+
page_id=page_id,
|
|
86
|
+
url=str(summary["url"]),
|
|
87
|
+
title=str(summary["title"]),
|
|
88
|
+
active=False,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
return results
|
|
92
|
+
|
|
93
|
+
async def switch_tab(self, page_id: str) -> dict[str, Any]:
|
|
94
|
+
page = self._service._require_page(page_id) # noqa: SLF001
|
|
95
|
+
await page.bring_to_front()
|
|
96
|
+
return await self._service.get_page_summary(page_id)
|
|
97
|
+
|
|
98
|
+
async def get_page_summary(self, page_id: str) -> dict[str, Any]:
|
|
99
|
+
return await self._service.get_page_summary(page_id)
|
|
100
|
+
|
|
101
|
+
async def get_page_info(self, page_id: str) -> dict[str, Any]:
|
|
102
|
+
return await self._service.get_page_info(page_id)
|
|
103
|
+
|
|
104
|
+
async def capture_html(self, page_id: str) -> dict[str, Any]:
|
|
105
|
+
return await self._service.capture_html(page_id)
|
|
106
|
+
|
|
107
|
+
async def capture_snapshot_input(
|
|
108
|
+
self,
|
|
109
|
+
page_id: str,
|
|
110
|
+
*,
|
|
111
|
+
interactive: bool = False,
|
|
112
|
+
full_page: bool = True,
|
|
113
|
+
) -> SnapshotInput:
|
|
114
|
+
return await self._service.capture_snapshot_input(
|
|
115
|
+
page_id,
|
|
116
|
+
interactive=interactive,
|
|
117
|
+
full_page=full_page,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
async def navigate(
|
|
121
|
+
self,
|
|
122
|
+
page_id: str,
|
|
123
|
+
url: str,
|
|
124
|
+
*,
|
|
125
|
+
wait_until: str = "load",
|
|
126
|
+
timeout_seconds: float = 30.0,
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
return await self._service.navigate(
|
|
129
|
+
page_id,
|
|
130
|
+
url,
|
|
131
|
+
wait_until=wait_until,
|
|
132
|
+
timeout_seconds=timeout_seconds,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
async def reload(
|
|
136
|
+
self,
|
|
137
|
+
page_id: str,
|
|
138
|
+
*,
|
|
139
|
+
wait_until: str = "load",
|
|
140
|
+
timeout_seconds: float = 30.0,
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
return await self._service.reload(
|
|
143
|
+
page_id, wait_until=wait_until, timeout_seconds=timeout_seconds
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
async def go_back(self, page_id: str) -> dict[str, Any]:
|
|
147
|
+
return await self._service.go_back(page_id)
|
|
148
|
+
|
|
149
|
+
async def go_forward(self, page_id: str) -> dict[str, Any]:
|
|
150
|
+
return await self._service.go_forward(page_id)
|
|
151
|
+
|
|
152
|
+
async def resize(self, page_id: str, *, width: int, height: int) -> dict[str, Any]:
|
|
153
|
+
return await self._service.resize(page_id, width=width, height=height)
|
|
154
|
+
|
|
155
|
+
async def click(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
|
|
156
|
+
return await self._service.click_locator(page_id, locator)
|
|
157
|
+
|
|
158
|
+
async def double_click(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
|
|
159
|
+
return await self._service.double_click_locator(page_id, locator)
|
|
160
|
+
|
|
161
|
+
async def hover(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
|
|
162
|
+
return await self._service.hover_locator(page_id, locator)
|
|
163
|
+
|
|
164
|
+
async def focus(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
|
|
165
|
+
return await self._service.focus_locator(page_id, locator)
|
|
166
|
+
|
|
167
|
+
async def fill(
|
|
168
|
+
self,
|
|
169
|
+
page_id: str,
|
|
170
|
+
locator: LocatorSpec,
|
|
171
|
+
text: str,
|
|
172
|
+
*,
|
|
173
|
+
submit: bool = False,
|
|
174
|
+
) -> dict[str, Any]:
|
|
175
|
+
return await self._service.fill_locator(page_id, locator, text, submit=submit)
|
|
176
|
+
|
|
177
|
+
async def select_option(self, page_id: str, locator: LocatorSpec, text: str) -> dict[str, Any]:
|
|
178
|
+
return await self._service.select_locator(page_id, locator, text)
|
|
179
|
+
|
|
180
|
+
async def list_options(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
|
|
181
|
+
return await self._service.list_locator_options(page_id, locator)
|
|
182
|
+
|
|
183
|
+
async def check(self, page_id: str, locator: LocatorSpec, *, checked: bool) -> dict[str, Any]:
|
|
184
|
+
return await self._service.set_checked_locator(page_id, locator, checked=checked)
|
|
185
|
+
|
|
186
|
+
async def scroll_to(self, page_id: str, locator: LocatorSpec) -> dict[str, Any]:
|
|
187
|
+
return await self._service.scroll_to_locator(page_id, locator)
|
|
188
|
+
|
|
189
|
+
async def drag(
|
|
190
|
+
self, page_id: str, start_locator: LocatorSpec, end_locator: LocatorSpec
|
|
191
|
+
) -> dict[str, Any]:
|
|
192
|
+
return await self._service.drag_between_locators(page_id, start_locator, end_locator)
|
|
193
|
+
|
|
194
|
+
async def upload(self, page_id: str, locator: LocatorSpec, file_path: str) -> dict[str, Any]:
|
|
195
|
+
return await self._service.upload_to_locator(page_id, locator, file_path)
|
|
196
|
+
|
|
197
|
+
async def evaluate(self, page_id: str, code: str) -> dict[str, Any]:
|
|
198
|
+
return await self._service.evaluate(page_id, code)
|
|
199
|
+
|
|
200
|
+
async def evaluate_on(self, page_id: str, locator: LocatorSpec, code: str) -> dict[str, Any]:
|
|
201
|
+
return await self._service.evaluate_on_locator(page_id, locator, code)
|
|
202
|
+
|
|
203
|
+
async def wait(
|
|
204
|
+
self,
|
|
205
|
+
page_id: str,
|
|
206
|
+
*,
|
|
207
|
+
seconds: float | None = None,
|
|
208
|
+
text: str | None = None,
|
|
209
|
+
gone: bool = False,
|
|
210
|
+
exact: bool = False,
|
|
211
|
+
) -> dict[str, Any]:
|
|
212
|
+
return await self._service.wait(
|
|
213
|
+
page_id,
|
|
214
|
+
seconds=seconds,
|
|
215
|
+
text=text,
|
|
216
|
+
gone=gone,
|
|
217
|
+
exact=exact,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
async def wait_for_network_idle(
|
|
221
|
+
self, page_id: str, *, timeout_seconds: float = 30.0
|
|
222
|
+
) -> dict[str, Any]:
|
|
223
|
+
return await self._service.wait_for_network_idle(page_id, timeout_seconds=timeout_seconds)
|
|
224
|
+
|
|
225
|
+
async def start_console_capture(self, page_id: str) -> dict[str, Any]:
|
|
226
|
+
return await self._service.start_console_capture(page_id)
|
|
227
|
+
|
|
228
|
+
async def get_console_messages(
|
|
229
|
+
self,
|
|
230
|
+
page_id: str,
|
|
231
|
+
*,
|
|
232
|
+
message_type: str | None = None,
|
|
233
|
+
clear: bool = True,
|
|
234
|
+
) -> dict[str, Any]:
|
|
235
|
+
return await self._service.get_console_messages(
|
|
236
|
+
page_id, message_type=message_type, clear=clear
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
async def stop_console_capture(self, page_id: str) -> dict[str, Any]:
|
|
240
|
+
return await self._service.stop_console_capture(page_id)
|
|
241
|
+
|
|
242
|
+
async def start_network_capture(self, page_id: str) -> dict[str, Any]:
|
|
243
|
+
return await self._service.start_network_capture(page_id)
|
|
244
|
+
|
|
245
|
+
async def wait_for_network_record(
|
|
246
|
+
self,
|
|
247
|
+
page_id: str,
|
|
248
|
+
*,
|
|
249
|
+
url_contains: str | None = None,
|
|
250
|
+
url_regex: str | None = None,
|
|
251
|
+
method: str | None = None,
|
|
252
|
+
status: int | None = None,
|
|
253
|
+
resource_type: str | None = None,
|
|
254
|
+
mime_contains: str | None = None,
|
|
255
|
+
include_static: bool = False,
|
|
256
|
+
timeout_seconds: float = 30.0,
|
|
257
|
+
) -> dict[str, Any]:
|
|
258
|
+
return await self._service.wait_for_network_record(
|
|
259
|
+
page_id,
|
|
260
|
+
url_contains=url_contains,
|
|
261
|
+
url_regex=url_regex,
|
|
262
|
+
method=method,
|
|
263
|
+
status=status,
|
|
264
|
+
resource_type=resource_type,
|
|
265
|
+
mime_contains=mime_contains,
|
|
266
|
+
include_static=include_static,
|
|
267
|
+
timeout_seconds=timeout_seconds,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
async def get_network_records(
|
|
271
|
+
self,
|
|
272
|
+
page_id: str,
|
|
273
|
+
*,
|
|
274
|
+
url_contains: str | None = None,
|
|
275
|
+
url_regex: str | None = None,
|
|
276
|
+
method: str | None = None,
|
|
277
|
+
status: int | None = None,
|
|
278
|
+
resource_type: str | None = None,
|
|
279
|
+
mime_contains: str | None = None,
|
|
280
|
+
include_static: bool = False,
|
|
281
|
+
clear: bool = True,
|
|
282
|
+
) -> dict[str, Any]:
|
|
283
|
+
return await self._service.get_network_records(
|
|
284
|
+
page_id,
|
|
285
|
+
url_contains=url_contains,
|
|
286
|
+
url_regex=url_regex,
|
|
287
|
+
method=method,
|
|
288
|
+
status=status,
|
|
289
|
+
resource_type=resource_type,
|
|
290
|
+
mime_contains=mime_contains,
|
|
291
|
+
include_static=include_static,
|
|
292
|
+
clear=clear,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
async def stop_network_capture(self, page_id: str) -> dict[str, Any]:
|
|
296
|
+
return await self._service.stop_network_capture(page_id)
|
|
297
|
+
|
|
298
|
+
async def get_cookies(
|
|
299
|
+
self,
|
|
300
|
+
page_id: str,
|
|
301
|
+
*,
|
|
302
|
+
name: str | None = None,
|
|
303
|
+
domain: str | None = None,
|
|
304
|
+
path: str | None = None,
|
|
305
|
+
) -> dict[str, Any]:
|
|
306
|
+
return await self._service.get_cookies(page_id, name=name, domain=domain, path=path)
|
|
307
|
+
|
|
308
|
+
async def set_cookie(
|
|
309
|
+
self,
|
|
310
|
+
page_id: str,
|
|
311
|
+
*,
|
|
312
|
+
name: str,
|
|
313
|
+
value: str,
|
|
314
|
+
domain: str | None = None,
|
|
315
|
+
path: str = "/",
|
|
316
|
+
expires: float | None = None,
|
|
317
|
+
http_only: bool = False,
|
|
318
|
+
secure: bool = False,
|
|
319
|
+
same_site: str | None = None,
|
|
320
|
+
) -> dict[str, Any]:
|
|
321
|
+
return await self._service.set_cookie(
|
|
322
|
+
page_id,
|
|
323
|
+
name=name,
|
|
324
|
+
value=value,
|
|
325
|
+
domain=domain,
|
|
326
|
+
path=path,
|
|
327
|
+
expires=expires,
|
|
328
|
+
http_only=http_only,
|
|
329
|
+
secure=secure,
|
|
330
|
+
same_site=same_site,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
async def clear_cookies(
|
|
334
|
+
self,
|
|
335
|
+
page_id: str,
|
|
336
|
+
*,
|
|
337
|
+
name: str | None = None,
|
|
338
|
+
domain: str | None = None,
|
|
339
|
+
path: str | None = None,
|
|
340
|
+
) -> dict[str, Any]:
|
|
341
|
+
return await self._service.clear_cookies(page_id, name=name, domain=domain, path=path)
|
|
342
|
+
|
|
343
|
+
async def save_storage_state(self, page_id: str, *, path: str | None = None) -> dict[str, Any]:
|
|
344
|
+
return await self._service.save_storage_state(page_id, path=path)
|
|
345
|
+
|
|
346
|
+
async def load_storage_state(self, page_id: str, *, path: str) -> dict[str, Any]:
|
|
347
|
+
return await self._service.load_storage_state(page_id, path=path)
|
|
348
|
+
|
|
349
|
+
async def verify_text(
|
|
350
|
+
self,
|
|
351
|
+
page_id: str,
|
|
352
|
+
*,
|
|
353
|
+
text: str,
|
|
354
|
+
exact: bool = False,
|
|
355
|
+
timeout_seconds: float = 5.0,
|
|
356
|
+
) -> dict[str, Any]:
|
|
357
|
+
return await self._service.verify_text(
|
|
358
|
+
page_id,
|
|
359
|
+
text=text,
|
|
360
|
+
exact=exact,
|
|
361
|
+
timeout_seconds=timeout_seconds,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
async def verify_url(
|
|
365
|
+
self, page_id: str, *, expected: str, exact: bool = False
|
|
366
|
+
) -> dict[str, Any]:
|
|
367
|
+
return await self._service.verify_url(page_id, expected=expected, exact=exact)
|
|
368
|
+
|
|
369
|
+
async def verify_title(
|
|
370
|
+
self, page_id: str, *, expected: str, exact: bool = False
|
|
371
|
+
) -> dict[str, Any]:
|
|
372
|
+
return await self._service.verify_title(page_id, expected=expected, exact=exact)
|
|
373
|
+
|
|
374
|
+
async def verify_visible(
|
|
375
|
+
self,
|
|
376
|
+
page_id: str,
|
|
377
|
+
*,
|
|
378
|
+
role: str,
|
|
379
|
+
name: str,
|
|
380
|
+
timeout_seconds: float = 5.0,
|
|
381
|
+
) -> dict[str, Any]:
|
|
382
|
+
return await self._service.verify_visible(
|
|
383
|
+
page_id,
|
|
384
|
+
role=role,
|
|
385
|
+
name=name,
|
|
386
|
+
timeout_seconds=timeout_seconds,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
async def verify_state(
|
|
390
|
+
self, page_id: str, *, locator: LocatorSpec, state: str
|
|
391
|
+
) -> dict[str, Any]:
|
|
392
|
+
return await self._service.verify_state_locator(page_id, locator, state=state)
|
|
393
|
+
|
|
394
|
+
async def verify_value(
|
|
395
|
+
self, page_id: str, *, locator: LocatorSpec, expected: str
|
|
396
|
+
) -> dict[str, Any]:
|
|
397
|
+
return await self._service.verify_value_locator(page_id, locator, expected=expected)
|
|
398
|
+
|
|
399
|
+
async def type_text(self, page_id: str, text: str, *, submit: bool = False) -> dict[str, Any]:
|
|
400
|
+
return await self._service.type_text(page_id, text, submit=submit)
|
|
401
|
+
|
|
402
|
+
async def press_key(self, page_id: str, key: str) -> dict[str, Any]:
|
|
403
|
+
return await self._service.press_key(page_id, key)
|
|
404
|
+
|
|
405
|
+
async def key_down(self, page_id: str, key: str) -> dict[str, Any]:
|
|
406
|
+
return await self._service.key_down(page_id, key)
|
|
407
|
+
|
|
408
|
+
async def key_up(self, page_id: str, key: str) -> dict[str, Any]:
|
|
409
|
+
return await self._service.key_up(page_id, key)
|
|
410
|
+
|
|
411
|
+
async def wheel(self, page_id: str, *, dx: int = 0, dy: int = 700) -> dict[str, Any]:
|
|
412
|
+
return await self._service.wheel(page_id, dx=dx, dy=dy)
|
|
413
|
+
|
|
414
|
+
async def mouse_move(self, page_id: str, *, x: int, y: int) -> dict[str, Any]:
|
|
415
|
+
return await self._service.mouse_move(page_id, x=x, y=y)
|
|
416
|
+
|
|
417
|
+
async def mouse_click(
|
|
418
|
+
self,
|
|
419
|
+
page_id: str,
|
|
420
|
+
*,
|
|
421
|
+
x: int,
|
|
422
|
+
y: int,
|
|
423
|
+
button: str = "left",
|
|
424
|
+
count: int = 1,
|
|
425
|
+
) -> dict[str, Any]:
|
|
426
|
+
return await self._service.mouse_click(page_id, x=x, y=y, button=button, count=count)
|
|
427
|
+
|
|
428
|
+
async def mouse_drag(
|
|
429
|
+
self, page_id: str, *, x1: int, y1: int, x2: int, y2: int
|
|
430
|
+
) -> dict[str, Any]:
|
|
431
|
+
return await self._service.mouse_drag(page_id, x1=x1, y1=y1, x2=x2, y2=y2)
|
|
432
|
+
|
|
433
|
+
async def mouse_down(self, page_id: str, *, button: str = "left") -> dict[str, Any]:
|
|
434
|
+
return await self._service.mouse_down(page_id, button=button)
|
|
435
|
+
|
|
436
|
+
async def mouse_up(self, page_id: str, *, button: str = "left") -> dict[str, Any]:
|
|
437
|
+
return await self._service.mouse_up(page_id, button=button)
|
|
438
|
+
|
|
439
|
+
async def screenshot(
|
|
440
|
+
self, page_id: str, *, path: str, full_page: bool = False
|
|
441
|
+
) -> dict[str, Any]:
|
|
442
|
+
return await self._service.screenshot(page_id, path=path, full_page=full_page)
|
|
443
|
+
|
|
444
|
+
async def save_pdf(self, page_id: str, *, path: str) -> dict[str, Any]:
|
|
445
|
+
return await self._service.save_pdf(page_id, path=path)
|
|
446
|
+
|
|
447
|
+
async def setup_dialog_handler(
|
|
448
|
+
self,
|
|
449
|
+
page_id: str,
|
|
450
|
+
*,
|
|
451
|
+
default_action: str = "accept",
|
|
452
|
+
default_prompt_text: str | None = None,
|
|
453
|
+
) -> dict[str, Any]:
|
|
454
|
+
return await self._service.setup_dialog_handler(
|
|
455
|
+
page_id,
|
|
456
|
+
default_action=default_action,
|
|
457
|
+
default_prompt_text=default_prompt_text,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
async def handle_dialog(
|
|
461
|
+
self,
|
|
462
|
+
page_id: str,
|
|
463
|
+
*,
|
|
464
|
+
accept: bool,
|
|
465
|
+
prompt_text: str | None = None,
|
|
466
|
+
) -> dict[str, Any]:
|
|
467
|
+
return await self._service.handle_dialog(page_id, accept=accept, prompt_text=prompt_text)
|
|
468
|
+
|
|
469
|
+
async def remove_dialog_handler(self, page_id: str) -> dict[str, Any]:
|
|
470
|
+
return await self._service.remove_dialog_handler(page_id)
|
|
471
|
+
|
|
472
|
+
async def start_tracing(
|
|
473
|
+
self,
|
|
474
|
+
page_id: str,
|
|
475
|
+
*,
|
|
476
|
+
screenshots: bool = True,
|
|
477
|
+
snapshots: bool = True,
|
|
478
|
+
sources: bool = False,
|
|
479
|
+
) -> dict[str, Any]:
|
|
480
|
+
return await self._service.start_tracing(
|
|
481
|
+
page_id,
|
|
482
|
+
screenshots=screenshots,
|
|
483
|
+
snapshots=snapshots,
|
|
484
|
+
sources=sources,
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
async def add_trace_chunk(self, page_id: str, *, title: str | None = None) -> dict[str, Any]:
|
|
488
|
+
return await self._service.add_trace_chunk(page_id, title=title)
|
|
489
|
+
|
|
490
|
+
async def stop_tracing(self, page_id: str, *, path: str | None = None) -> dict[str, Any]:
|
|
491
|
+
return await self._service.stop_tracing(page_id, path=path)
|
|
492
|
+
|
|
493
|
+
async def start_video(
|
|
494
|
+
self, page_id: str, *, width: int | None = None, height: int | None = None
|
|
495
|
+
) -> dict[str, Any]:
|
|
496
|
+
return await self._service.start_video(page_id, width=width, height=height)
|
|
497
|
+
|
|
498
|
+
async def stop_video(self, page_id: str, *, path: str | None = None) -> dict[str, Any]:
|
|
499
|
+
return await self._service.stop_video(page_id, path=path)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Stable machine-readable error codes."""
|
|
2
|
+
|
|
3
|
+
DAEMON_NOT_AVAILABLE = "DAEMON_NOT_AVAILABLE"
|
|
4
|
+
NO_ACTIVE_TAB = "NO_ACTIVE_TAB"
|
|
5
|
+
NO_VISIBLE_TABS = "NO_VISIBLE_TABS"
|
|
6
|
+
AGENT_ACTIVE_TAB_BUSY = "AGENT_ACTIVE_TAB_BUSY"
|
|
7
|
+
TAB_NOT_FOUND = "TAB_NOT_FOUND"
|
|
8
|
+
REF_NOT_FOUND = "REF_NOT_FOUND"
|
|
9
|
+
STALE_SNAPSHOT = "STALE_SNAPSHOT"
|
|
10
|
+
NO_SNAPSHOT_CONTEXT = "NO_SNAPSHOT_CONTEXT"
|
|
11
|
+
AMBIGUOUS_REF = "AMBIGUOUS_REF"
|
|
12
|
+
INVALID_INPUT = "INVALID_INPUT"
|
|
13
|
+
OPERATION_FAILED = "OPERATION_FAILED"
|
|
14
|
+
INTERNAL_ERROR = "INTERNAL_ERROR"
|
|
15
|
+
BROWSER_UNAVAILABLE = "BROWSER_UNAVAILABLE"
|
|
16
|
+
PROFILE_UNAVAILABLE = "PROFILE_UNAVAILABLE"
|
|
17
|
+
TEMPORARY_FAILURE = "TEMPORARY_FAILURE"
|
|
18
|
+
EMPTY_CONTENT = "EMPTY_CONTENT"
|
|
19
|
+
DRIVER_UNSUPPORTED = "DRIVER_UNSUPPORTED"
|
|
20
|
+
EXTENSION_UNAVAILABLE = "EXTENSION_UNAVAILABLE"
|
|
21
|
+
EXTENSION_CAPABILITY_INCOMPLETE = "EXTENSION_CAPABILITY_INCOMPLETE"
|
|
22
|
+
EXTENSION_REQUEST_FAILED = "EXTENSION_REQUEST_FAILED"
|
|
23
|
+
EXTENSION_DISCONNECTED = "EXTENSION_DISCONNECTED"
|
|
24
|
+
AUTOMATION_SERVICE_NOT_AVAILABLE = "AUTOMATION_SERVICE_NOT_AVAILABLE"
|
|
25
|
+
AUTOMATION_INVALID = "AUTOMATION_INVALID"
|
|
26
|
+
AUTOMATION_RUN_FAILED = "AUTOMATION_RUN_FAILED"
|
|
27
|
+
AUTOMATION_RUN_TIMEOUT = "AUTOMATION_RUN_TIMEOUT"
|
browser_cli/errors.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Project-level error types."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from . import error_codes, exit_codes
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class BrowserCliError(Exception):
|
|
12
|
+
"""Base typed error surfaced to the CLI."""
|
|
13
|
+
|
|
14
|
+
message: str
|
|
15
|
+
exit_code: int = exit_codes.INTERNAL_ERROR
|
|
16
|
+
error_code: str = error_codes.INTERNAL_ERROR
|
|
17
|
+
|
|
18
|
+
def __str__(self) -> str:
|
|
19
|
+
return self.message
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BrowserUnavailableError(BrowserCliError):
|
|
23
|
+
def __init__(self, message: str) -> None:
|
|
24
|
+
super().__init__(message, exit_codes.BROWSER_UNAVAILABLE, error_codes.BROWSER_UNAVAILABLE)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProfileUnavailableError(BrowserCliError):
|
|
28
|
+
def __init__(self, message: str) -> None:
|
|
29
|
+
super().__init__(message, exit_codes.PROFILE_UNAVAILABLE, error_codes.PROFILE_UNAVAILABLE)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TemporaryReadError(BrowserCliError):
|
|
33
|
+
def __init__(self, message: str) -> None:
|
|
34
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.TEMPORARY_FAILURE)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EmptyContentError(BrowserCliError):
|
|
38
|
+
def __init__(self, message: str = "Read completed but produced no content.") -> None:
|
|
39
|
+
super().__init__(message, exit_codes.EMPTY_CONTENT, error_codes.EMPTY_CONTENT)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class InvalidInputError(BrowserCliError):
|
|
43
|
+
def __init__(self, message: str, *, error_code: str = error_codes.INVALID_INPUT) -> None:
|
|
44
|
+
super().__init__(message, exit_codes.USAGE_ERROR, error_code)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DaemonNotAvailableError(BrowserCliError):
|
|
48
|
+
def __init__(self, message: str = "Browser daemon is not available.") -> None:
|
|
49
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.DAEMON_NOT_AVAILABLE)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class NoActiveTabError(BrowserCliError):
|
|
53
|
+
def __init__(self, message: str = "No active tab is available for this agent.") -> None:
|
|
54
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.NO_ACTIVE_TAB)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class NoVisibleTabsError(BrowserCliError):
|
|
58
|
+
def __init__(self, message: str = "No visible tabs are available for this agent.") -> None:
|
|
59
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.NO_VISIBLE_TABS)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class BusyTabError(BrowserCliError):
|
|
63
|
+
def __init__(self, message: str | None = None) -> None:
|
|
64
|
+
super().__init__(
|
|
65
|
+
message
|
|
66
|
+
or "The current active tab is already being operated on. Create a new tab or retry after the other command finishes.",
|
|
67
|
+
exit_codes.TEMPORARY_FAILURE,
|
|
68
|
+
error_codes.AGENT_ACTIVE_TAB_BUSY,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TabNotFoundError(BrowserCliError):
|
|
73
|
+
def __init__(
|
|
74
|
+
self, message: str = "The requested tab is not visible or does not exist."
|
|
75
|
+
) -> None:
|
|
76
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.TAB_NOT_FOUND)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class RefNotFoundError(BrowserCliError):
|
|
80
|
+
def __init__(
|
|
81
|
+
self, message: str = "The requested ref is not known in the latest snapshot."
|
|
82
|
+
) -> None:
|
|
83
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.REF_NOT_FOUND)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class NoSnapshotContextError(BrowserCliError):
|
|
87
|
+
def __init__(
|
|
88
|
+
self,
|
|
89
|
+
message: str = "No snapshot context is available for the active tab. Capture a snapshot first.",
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.NO_SNAPSHOT_CONTEXT)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class StaleSnapshotError(BrowserCliError):
|
|
95
|
+
def __init__(
|
|
96
|
+
self, message: str = "The requested ref is stale. Capture a new snapshot and retry."
|
|
97
|
+
) -> None:
|
|
98
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.STALE_SNAPSHOT)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class AmbiguousRefError(BrowserCliError):
|
|
102
|
+
def __init__(
|
|
103
|
+
self,
|
|
104
|
+
message: str = "The requested ref resolved to multiple elements. Capture a new snapshot and retry.",
|
|
105
|
+
) -> None:
|
|
106
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_codes.AMBIGUOUS_REF)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class OperationFailedError(BrowserCliError):
|
|
110
|
+
def __init__(self, message: str, *, error_code: str = error_codes.OPERATION_FAILED) -> None:
|
|
111
|
+
super().__init__(message, exit_codes.TEMPORARY_FAILURE, error_code)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AutomationServiceNotAvailableError(BrowserCliError):
|
|
115
|
+
def __init__(self, message: str = "Automation service is not available.") -> None:
|
|
116
|
+
super().__init__(
|
|
117
|
+
message,
|
|
118
|
+
exit_codes.TEMPORARY_FAILURE,
|
|
119
|
+
error_codes.AUTOMATION_SERVICE_NOT_AVAILABLE,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class AutomationServiceError(BrowserCliError):
|
|
124
|
+
def __init__(self, payload: dict[str, object]) -> None:
|
|
125
|
+
error_code = str(payload.get("error_code") or error_codes.INTERNAL_ERROR)
|
|
126
|
+
exit_code = (
|
|
127
|
+
exit_codes.USAGE_ERROR
|
|
128
|
+
if error_code in {error_codes.INVALID_INPUT, error_codes.AUTOMATION_INVALID}
|
|
129
|
+
else exit_codes.TEMPORARY_FAILURE
|
|
130
|
+
)
|
|
131
|
+
super().__init__(
|
|
132
|
+
str(payload.get("error_message") or "Automation service request failed."),
|
|
133
|
+
exit_code,
|
|
134
|
+
error_code,
|
|
135
|
+
)
|
|
136
|
+
self.payload = payload
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class AutomationInvalidError(BrowserCliError):
|
|
140
|
+
def __init__(self, message: str) -> None:
|
|
141
|
+
super().__init__(message, exit_codes.USAGE_ERROR, error_codes.AUTOMATION_INVALID)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class AutomationRunTimeoutError(OperationFailedError):
|
|
145
|
+
def __init__(self, message: str = "Automation run timed out.") -> None:
|
|
146
|
+
super().__init__(message, error_code=error_codes.AUTOMATION_RUN_TIMEOUT)
|