browsix 1.2.0__tar.gz → 1.3.0__tar.gz
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.
- {browsix-1.2.0 → browsix-1.3.0}/PKG-INFO +71 -4
- browsix-1.3.0/README.md +150 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/bidi.py +140 -11
- {browsix-1.2.0 → browsix-1.3.0}/pyproject.toml +1 -1
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_bidi_backend.py +10 -0
- browsix-1.2.0/README.md +0 -83
- {browsix-1.2.0 → browsix-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/.github/workflows/ci.yml +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/.github/workflows/docs.yml +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/.github/workflows/release.yml +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/.github/workflows/serve-test.yml +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/.gitignore +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/CHANGELOG.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/CODE_OF_CONDUCT.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/CONTRIBUTING.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/Dockerfile +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/LICENSE +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/SECURITY.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/__init__.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/__main__.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/__init__.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/accessibility.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/animation.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/base.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/bluetooth.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/browser.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/cast.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/console.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/css.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/debug.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/dialog.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/dom.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/dom_snapshot.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/download.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/emulation.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/eval.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/har.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/input.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/media.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/multi.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/navigate.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/network.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/overlay.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/pdf.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/performance.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/permissions.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/scrape.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/screencast.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/screenshot.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/security.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/service_worker.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/storage.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/tabs.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/webaudio.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/webauthn.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/auth.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/__init__.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/base.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/cdp.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/manager.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/cli/__init__.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/cli/app.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/config.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/exceptions.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/multi.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/output.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/plugins.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/record.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/browsix/serve.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/api/actions.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/api/backends.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/api/cli.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/api/exceptions.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/api/serve.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/cookbook/ci-cd.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/cookbook/scraping.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/cookbook/screenshots.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/cookbook/serve-mode.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/guide/backends.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/guide/commands.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/guide/installation.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/guide/multi.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/guide/quickstart.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/guide/raw.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/guide/troubleshooting.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/docs/index.md +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/mkdocs.yml +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/__init__.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/conftest.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/__init__.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_a11y.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_animation.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_backend_selection.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_browser.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_console.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_css.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_debug.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_dialog.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_dom.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_dom_snapshot.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_emulation.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_emulation_advanced.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_eval.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_har.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_input.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_media.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_multi.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_navigate.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_network.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_network_advanced.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_overlay.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_pdf.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_perf.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_permissions.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_raw.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_record.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_scrape.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_screenshot.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_security.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_serve.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_service_worker.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_storage.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_tabs.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_webaudio.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_webauthn.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/__init__.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_abstract_backend_phase5.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_actions.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_actions_phase5.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_animation_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_auth.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_backend_manager.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_bidi_phase5.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_bluetooth_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_cast_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_cli_phase5.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_config.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_config_phase5.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_css_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_debug_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_dom_snapshot_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_download_screencast.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_emulation_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_error_handling.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_exceptions.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_input_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_manager.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_media_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_multi.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_output.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_overlay_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_perf_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_plugins.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_raw.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_record.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_serve.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_service_worker_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_storage_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_webaudio_action.py +0 -0
- {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_webauthn_action.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: browsix
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Browser automation CLI — wraps cdpwave and bidiwave, no Node.js, no Chromium download
|
|
5
5
|
Project-URL: Homepage, https://github.com/MathiasPaulenko/browsix
|
|
6
6
|
Project-URL: Repository, https://github.com/MathiasPaulenko/browsix
|
|
@@ -49,8 +49,9 @@ Description-Content-Type: text/markdown
|
|
|
49
49
|
[](https://pypi.org/project/browsix/)
|
|
50
50
|
[](https://pypi.org/project/browsix/)
|
|
51
51
|
[](https://github.com/MathiasPaulenko/browsix/blob/main/LICENSE)
|
|
52
|
+
[](https://mathiaspaulenko.github.io/browsix/)
|
|
52
53
|
|
|
53
|
-
> Browser automation CLI — wraps cdpwave and bidiwave. No Node.js, no Chromium download. Uses your existing Chrome/Edge.
|
|
54
|
+
> Browser automation CLI — wraps cdpwave and bidiwave. No Node.js, no Chromium download. Uses your existing Chrome/Edge. 80+ commands across CDP and BiDi backends.
|
|
54
55
|
|
|
55
56
|
## Install
|
|
56
57
|
|
|
@@ -64,11 +65,23 @@ pip install browsix[cdp]
|
|
|
64
65
|
# Take a screenshot
|
|
65
66
|
browsix screenshot https://example.com -o out.png
|
|
66
67
|
|
|
68
|
+
# Full-page screenshot
|
|
69
|
+
browsix screenshot https://example.com -o full.png --full-page
|
|
70
|
+
|
|
71
|
+
# Screenshot of a specific element
|
|
72
|
+
browsix screenshot https://example.com -o el.png --selector "h1"
|
|
73
|
+
|
|
67
74
|
# Generate a PDF
|
|
68
75
|
browsix pdf https://example.com -o out.pdf --paper a4
|
|
69
76
|
|
|
70
77
|
# Evaluate JavaScript
|
|
71
78
|
browsix eval https://example.com -e "document.title"
|
|
79
|
+
|
|
80
|
+
# Scrape page content
|
|
81
|
+
browsix scrape https://example.com --selector "article"
|
|
82
|
+
|
|
83
|
+
# Emulate a device
|
|
84
|
+
browsix device https://example.com --preset iphone-15 -o shot.png
|
|
72
85
|
```
|
|
73
86
|
|
|
74
87
|
## Multi-action
|
|
@@ -84,6 +97,9 @@ actions:
|
|
|
84
97
|
- eval:
|
|
85
98
|
url: https://example.com
|
|
86
99
|
expression: document.title
|
|
100
|
+
- pdf:
|
|
101
|
+
url: https://example.com
|
|
102
|
+
paper: a4
|
|
87
103
|
```
|
|
88
104
|
|
|
89
105
|
```bash
|
|
@@ -95,14 +111,61 @@ browsix multi actions.yml
|
|
|
95
111
|
browsix supports two backends:
|
|
96
112
|
|
|
97
113
|
- **CDP** (cdpwave) — default, full feature support. `pip install browsix[cdp]`
|
|
98
|
-
- **BiDi** (bidiwave) —
|
|
114
|
+
- **BiDi** (bidiwave) — WebDriver BiDi protocol, 23+ methods implemented. `pip install browsix[bidi]`
|
|
99
115
|
|
|
100
116
|
Select with `--backend`:
|
|
101
117
|
|
|
102
118
|
```bash
|
|
103
|
-
browsix --backend
|
|
119
|
+
browsix --backend bidi screenshot https://example.com -o out.png
|
|
104
120
|
```
|
|
105
121
|
|
|
122
|
+
### BiDi backend support
|
|
123
|
+
|
|
124
|
+
The BiDi backend now supports 23+ methods via bidiwave:
|
|
125
|
+
|
|
126
|
+
| Category | Methods |
|
|
127
|
+
|----------|---------|
|
|
128
|
+
| Navigation | `navigate`, `go_back`, `go_forward`, `reload`, `stop_loading`, `wait_for` |
|
|
129
|
+
| Screenshots | `screenshot`, `screenshot_selector`, `pdf` |
|
|
130
|
+
| Tabs | `list_tabs`, `new_tab`, `close_tab`, `activate_tab` |
|
|
131
|
+
| DOM | `dom_get`, `dom_query`, `dom_set_attr`, `dom_get_attr`, `dom_remove_attr`, `dom_remove`, `dom_focus`, `dom_scroll` |
|
|
132
|
+
| Cookies | `get_cookies`, `set_cookie`, `delete_cookie`, `clear_cookies` |
|
|
133
|
+
| Network | `set_headers`, `set_user_agent`, `block_requests`, `throttle_network`, `set_cache_disabled`, `intercept_requests`, `mock_response` |
|
|
134
|
+
| Emulation | `emulate_device`, `set_viewport`, `set_geolocation`, `set_timezone`, `set_dark_mode`, `set_locale`, `set_touch_emulation`, `set_cpu_throttle`, `set_sensors` |
|
|
135
|
+
| Browser | `browser_version`, `eval`, `raw`, `capture_console`, `capture_logs` |
|
|
136
|
+
| Security | `get_security_state`, `ignore_cert_errors` |
|
|
137
|
+
| Contexts | `new_context`, `list_contexts`, `close_context`, `get_window_bounds`, `set_window_bounds` |
|
|
138
|
+
| Input | `click`, `type_text`, `fill`, `select_option`, `hover`, `key_press`, `drag`, `tap` |
|
|
139
|
+
| Storage | `storage_get`, `storage_set`, `storage_clear`, `storage_list` |
|
|
140
|
+
| Dialogs | `dialog_accept`, `dialog_dismiss`, `grant_permission`, `reset_permissions` |
|
|
141
|
+
|
|
142
|
+
CDP-only features (HAR, screencast, a11y, downloads, performance profiling,
|
|
143
|
+
CSS inspection, debugging, DOM snapshot, overlay, cache storage, IndexedDB,
|
|
144
|
+
service workers, animations, WebAuthn, WebAudio, Media, Cast, Bluetooth)
|
|
145
|
+
require `--backend cdp`.
|
|
146
|
+
|
|
147
|
+
## Commands
|
|
148
|
+
|
|
149
|
+
browsix provides 80+ CLI commands organized into categories:
|
|
150
|
+
|
|
151
|
+
| Category | Commands |
|
|
152
|
+
|----------|----------|
|
|
153
|
+
| Capture | `screenshot`, `pdf`, `screencast`, `scrape` |
|
|
154
|
+
| Navigate | `navigate`, `back`, `forward`, `reload`, `stop`, `tabs` |
|
|
155
|
+
| Console | `console`, `logs`, `har` |
|
|
156
|
+
| Cookies | `cookies` (get/set/delete/clear) |
|
|
157
|
+
| Network | `headers`, `user-agent`, `block`, `throttle`, `cache`, `intercept`, `mock` |
|
|
158
|
+
| Browser | `open`, `close`, `version` |
|
|
159
|
+
| Emulation | `device`, `viewport`, `geolocation`, `timezone`, `dark-mode` |
|
|
160
|
+
| Input | `click`, `type`, `fill`, `select`, `hover`, `key`, `drag`, `tap` |
|
|
161
|
+
| CSS | `css-styles`, `css-computed`, `css-rules` |
|
|
162
|
+
| Debug | `debug-break`, `debug-step`, `debug-pause`, `debug-resume` |
|
|
163
|
+
| Performance | `perf-metrics`, `perf-trace`, `perf-profile`, `perf-coverage` |
|
|
164
|
+
| Storage | `storage` (get/set/clear/list), `indexeddb` |
|
|
165
|
+
| Advanced | `sw`, `animation`, `record`, `replay`, `webauthn`, `cast`, `bluetooth` |
|
|
166
|
+
|
|
167
|
+
Run `browsix --help` for the full list.
|
|
168
|
+
|
|
106
169
|
## Comparison
|
|
107
170
|
|
|
108
171
|
| Feature | browsix | shot-scraper | Playwright |
|
|
@@ -117,6 +180,10 @@ browsix --backend cdp screenshot https://example.com -o out.png
|
|
|
117
180
|
| Device emulation | Yes | Yes | Yes |
|
|
118
181
|
| HAR capture | Yes | No | Yes |
|
|
119
182
|
| PDF generation | Yes | Yes | Yes |
|
|
183
|
+
| Network throttling | Yes | No | Yes |
|
|
184
|
+
| Cookie management | Yes | No | Yes |
|
|
185
|
+
| Session recording | Yes | No | No |
|
|
186
|
+
| Serve mode (HTTP API) | Yes | No | No |
|
|
120
187
|
| Shell completions | Yes | No | No |
|
|
121
188
|
|
|
122
189
|
## Documentation
|
browsix-1.3.0/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# browsix
|
|
2
|
+
|
|
3
|
+
[](https://github.com/MathiasPaulenko/browsix/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/browsix/)
|
|
5
|
+
[](https://pypi.org/project/browsix/)
|
|
6
|
+
[](https://github.com/MathiasPaulenko/browsix/blob/main/LICENSE)
|
|
7
|
+
[](https://mathiaspaulenko.github.io/browsix/)
|
|
8
|
+
|
|
9
|
+
> Browser automation CLI — wraps cdpwave and bidiwave. No Node.js, no Chromium download. Uses your existing Chrome/Edge. 80+ commands across CDP and BiDi backends.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install browsix[cdp]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Take a screenshot
|
|
21
|
+
browsix screenshot https://example.com -o out.png
|
|
22
|
+
|
|
23
|
+
# Full-page screenshot
|
|
24
|
+
browsix screenshot https://example.com -o full.png --full-page
|
|
25
|
+
|
|
26
|
+
# Screenshot of a specific element
|
|
27
|
+
browsix screenshot https://example.com -o el.png --selector "h1"
|
|
28
|
+
|
|
29
|
+
# Generate a PDF
|
|
30
|
+
browsix pdf https://example.com -o out.pdf --paper a4
|
|
31
|
+
|
|
32
|
+
# Evaluate JavaScript
|
|
33
|
+
browsix eval https://example.com -e "document.title"
|
|
34
|
+
|
|
35
|
+
# Scrape page content
|
|
36
|
+
browsix scrape https://example.com --selector "article"
|
|
37
|
+
|
|
38
|
+
# Emulate a device
|
|
39
|
+
browsix device https://example.com --preset iphone-15 -o shot.png
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Multi-action
|
|
43
|
+
|
|
44
|
+
Create a YAML config and run multiple actions in sequence:
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
# actions.yml
|
|
48
|
+
actions:
|
|
49
|
+
- screenshot:
|
|
50
|
+
url: https://example.com
|
|
51
|
+
full_page: true
|
|
52
|
+
- eval:
|
|
53
|
+
url: https://example.com
|
|
54
|
+
expression: document.title
|
|
55
|
+
- pdf:
|
|
56
|
+
url: https://example.com
|
|
57
|
+
paper: a4
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
browsix multi actions.yml
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Backends
|
|
65
|
+
|
|
66
|
+
browsix supports two backends:
|
|
67
|
+
|
|
68
|
+
- **CDP** (cdpwave) — default, full feature support. `pip install browsix[cdp]`
|
|
69
|
+
- **BiDi** (bidiwave) — WebDriver BiDi protocol, 23+ methods implemented. `pip install browsix[bidi]`
|
|
70
|
+
|
|
71
|
+
Select with `--backend`:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
browsix --backend bidi screenshot https://example.com -o out.png
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### BiDi backend support
|
|
78
|
+
|
|
79
|
+
The BiDi backend now supports 23+ methods via bidiwave:
|
|
80
|
+
|
|
81
|
+
| Category | Methods |
|
|
82
|
+
|----------|---------|
|
|
83
|
+
| Navigation | `navigate`, `go_back`, `go_forward`, `reload`, `stop_loading`, `wait_for` |
|
|
84
|
+
| Screenshots | `screenshot`, `screenshot_selector`, `pdf` |
|
|
85
|
+
| Tabs | `list_tabs`, `new_tab`, `close_tab`, `activate_tab` |
|
|
86
|
+
| DOM | `dom_get`, `dom_query`, `dom_set_attr`, `dom_get_attr`, `dom_remove_attr`, `dom_remove`, `dom_focus`, `dom_scroll` |
|
|
87
|
+
| Cookies | `get_cookies`, `set_cookie`, `delete_cookie`, `clear_cookies` |
|
|
88
|
+
| Network | `set_headers`, `set_user_agent`, `block_requests`, `throttle_network`, `set_cache_disabled`, `intercept_requests`, `mock_response` |
|
|
89
|
+
| Emulation | `emulate_device`, `set_viewport`, `set_geolocation`, `set_timezone`, `set_dark_mode`, `set_locale`, `set_touch_emulation`, `set_cpu_throttle`, `set_sensors` |
|
|
90
|
+
| Browser | `browser_version`, `eval`, `raw`, `capture_console`, `capture_logs` |
|
|
91
|
+
| Security | `get_security_state`, `ignore_cert_errors` |
|
|
92
|
+
| Contexts | `new_context`, `list_contexts`, `close_context`, `get_window_bounds`, `set_window_bounds` |
|
|
93
|
+
| Input | `click`, `type_text`, `fill`, `select_option`, `hover`, `key_press`, `drag`, `tap` |
|
|
94
|
+
| Storage | `storage_get`, `storage_set`, `storage_clear`, `storage_list` |
|
|
95
|
+
| Dialogs | `dialog_accept`, `dialog_dismiss`, `grant_permission`, `reset_permissions` |
|
|
96
|
+
|
|
97
|
+
CDP-only features (HAR, screencast, a11y, downloads, performance profiling,
|
|
98
|
+
CSS inspection, debugging, DOM snapshot, overlay, cache storage, IndexedDB,
|
|
99
|
+
service workers, animations, WebAuthn, WebAudio, Media, Cast, Bluetooth)
|
|
100
|
+
require `--backend cdp`.
|
|
101
|
+
|
|
102
|
+
## Commands
|
|
103
|
+
|
|
104
|
+
browsix provides 80+ CLI commands organized into categories:
|
|
105
|
+
|
|
106
|
+
| Category | Commands |
|
|
107
|
+
|----------|----------|
|
|
108
|
+
| Capture | `screenshot`, `pdf`, `screencast`, `scrape` |
|
|
109
|
+
| Navigate | `navigate`, `back`, `forward`, `reload`, `stop`, `tabs` |
|
|
110
|
+
| Console | `console`, `logs`, `har` |
|
|
111
|
+
| Cookies | `cookies` (get/set/delete/clear) |
|
|
112
|
+
| Network | `headers`, `user-agent`, `block`, `throttle`, `cache`, `intercept`, `mock` |
|
|
113
|
+
| Browser | `open`, `close`, `version` |
|
|
114
|
+
| Emulation | `device`, `viewport`, `geolocation`, `timezone`, `dark-mode` |
|
|
115
|
+
| Input | `click`, `type`, `fill`, `select`, `hover`, `key`, `drag`, `tap` |
|
|
116
|
+
| CSS | `css-styles`, `css-computed`, `css-rules` |
|
|
117
|
+
| Debug | `debug-break`, `debug-step`, `debug-pause`, `debug-resume` |
|
|
118
|
+
| Performance | `perf-metrics`, `perf-trace`, `perf-profile`, `perf-coverage` |
|
|
119
|
+
| Storage | `storage` (get/set/clear/list), `indexeddb` |
|
|
120
|
+
| Advanced | `sw`, `animation`, `record`, `replay`, `webauthn`, `cast`, `bluetooth` |
|
|
121
|
+
|
|
122
|
+
Run `browsix --help` for the full list.
|
|
123
|
+
|
|
124
|
+
## Comparison
|
|
125
|
+
|
|
126
|
+
| Feature | browsix | shot-scraper | Playwright |
|
|
127
|
+
|---------|---------|--------------|------------|
|
|
128
|
+
| Language | Python | Python | Multi |
|
|
129
|
+
| Node.js required | No | Yes | Yes |
|
|
130
|
+
| Chromium download | No | Yes | Yes |
|
|
131
|
+
| CDP backend | Yes | Yes | Yes |
|
|
132
|
+
| BiDi backend | Yes | No | No |
|
|
133
|
+
| CLI-first | Yes | Yes | No |
|
|
134
|
+
| Multi-action YAML | Yes | No | No |
|
|
135
|
+
| Device emulation | Yes | Yes | Yes |
|
|
136
|
+
| HAR capture | Yes | No | Yes |
|
|
137
|
+
| PDF generation | Yes | Yes | Yes |
|
|
138
|
+
| Network throttling | Yes | No | Yes |
|
|
139
|
+
| Cookie management | Yes | No | Yes |
|
|
140
|
+
| Session recording | Yes | No | No |
|
|
141
|
+
| Serve mode (HTTP API) | Yes | No | No |
|
|
142
|
+
| Shell completions | Yes | No | No |
|
|
143
|
+
|
|
144
|
+
## Documentation
|
|
145
|
+
|
|
146
|
+
Full docs at [mathiaspaulenko.github.io/browsix](https://mathiaspaulenko.github.io/browsix/).
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
|
@@ -38,18 +38,21 @@ class BiDiBackend(AbstractBackend):
|
|
|
38
38
|
Supports: launch, navigate, screenshot, screenshot_selector, pdf, eval,
|
|
39
39
|
raw, close, go_back, go_forward, reload, stop_loading, wait_for,
|
|
40
40
|
list_tabs, new_tab, close_tab, activate_tab, capture_console,
|
|
41
|
-
capture_logs, DOM methods, storage methods,
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
capture_logs, DOM methods, dom_snapshot, storage methods,
|
|
42
|
+
cookies (get/set/delete/clear), set_headers, set_user_agent,
|
|
43
|
+
browser_version, emulate_device, set_viewport, set_geolocation,
|
|
44
|
+
set_timezone, set_dark_mode, set_locale, set_touch_emulation,
|
|
44
45
|
set_cpu_throttle, set_sensors, new_context, list_contexts, close_context,
|
|
45
46
|
get_window_bounds, set_window_bounds, get_security_state, ignore_cert_errors,
|
|
47
|
+
perf_metrics, perf_coverage, css_get_styles, css_get_computed,
|
|
46
48
|
dialog_accept, dialog_dismiss, grant_permission, reset_permissions,
|
|
47
49
|
click, type_text, fill, select_option, hover, key_press, drag, tap,
|
|
48
50
|
block_requests, throttle_network, set_cache_disabled, intercept_requests,
|
|
49
51
|
mock_response.
|
|
50
52
|
|
|
51
|
-
CDP-only features (HAR, screencast, a11y, downloads,
|
|
52
|
-
|
|
53
|
+
CDP-only features (HAR, screencast, a11y, downloads, perf_trace,
|
|
54
|
+
perf_profile, perf_heap_snapshot, perf_css_coverage, css_get_stylesheets,
|
|
55
|
+
css_get_rules, debug, overlay, cache storage, IndexedDB,
|
|
53
56
|
service workers, animations, WebAuthn, WebAudio, Media, Cast, Bluetooth)
|
|
54
57
|
raise NotImplementedError.
|
|
55
58
|
"""
|
|
@@ -1013,7 +1016,37 @@ class BiDiBackend(AbstractBackend):
|
|
|
1013
1016
|
# ── Performance ───────────────────────────────────────
|
|
1014
1017
|
|
|
1015
1018
|
async def perf_metrics(self) -> dict[str, Any]:
|
|
1016
|
-
|
|
1019
|
+
"""Get performance metrics via JS Performance API.
|
|
1020
|
+
|
|
1021
|
+
Uses script.evaluate to collect performance.timing,
|
|
1022
|
+
performance.memory, and navigation timing data.
|
|
1023
|
+
|
|
1024
|
+
Returns:
|
|
1025
|
+
Dict mapping metric names to values.
|
|
1026
|
+
"""
|
|
1027
|
+
if self._client is None or self._context is None:
|
|
1028
|
+
raise RuntimeError("BiDiBackend not launched. Call launch() first.")
|
|
1029
|
+
js = (
|
|
1030
|
+
"JSON.stringify({"
|
|
1031
|
+
" navigationStart: performance.timing.navigationStart,"
|
|
1032
|
+
" loadEventEnd: performance.timing.loadEventEnd,"
|
|
1033
|
+
" domContentLoadedEventEnd: performance.timing.domContentLoadedEventEnd,"
|
|
1034
|
+
" responseEnd: performance.timing.responseEnd,"
|
|
1035
|
+
" domInteractive: performance.timing.domInteractive,"
|
|
1036
|
+
" domComplete: performance.timing.domComplete,"
|
|
1037
|
+
" loadEventStart: performance.timing.loadEventStart,"
|
|
1038
|
+
" jsHeapUsedSize: performance.memory ? performance.memory.usedJSHeapSize : null,"
|
|
1039
|
+
" jsHeapTotalSize: performance.memory ? performance.memory.totalJSHeapSize : null,"
|
|
1040
|
+
" jsHeapLimit: performance.memory ? performance.memory.jsHeapSizeLimit : null,"
|
|
1041
|
+
" resourceCount: performance.getEntriesByType('resource').length,"
|
|
1042
|
+
" transferSize: performance.getEntriesByType('resource')"
|
|
1043
|
+
" .reduce((s,e)=>s+(e.transferSize||0),0)"
|
|
1044
|
+
"})"
|
|
1045
|
+
)
|
|
1046
|
+
result = await self._client.script.evaluate(self._context, js)
|
|
1047
|
+
import json as _json
|
|
1048
|
+
val = result.value if hasattr(result, "value") else result
|
|
1049
|
+
return _json.loads(val) if isinstance(val, str) else dict(val)
|
|
1017
1050
|
|
|
1018
1051
|
async def perf_trace(self, duration_ms: int = 3000) -> dict[str, Any]:
|
|
1019
1052
|
raise NotImplementedError("perf_trace is not supported by BiDiBackend")
|
|
@@ -1027,7 +1060,22 @@ class BiDiBackend(AbstractBackend):
|
|
|
1027
1060
|
)
|
|
1028
1061
|
|
|
1029
1062
|
async def perf_coverage(self) -> dict[str, Any]:
|
|
1030
|
-
|
|
1063
|
+
"""Get JS coverage data via CDP Profiler.
|
|
1064
|
+
|
|
1065
|
+
Uses CDP bridge to enable profiler and take precise coverage.
|
|
1066
|
+
|
|
1067
|
+
Returns:
|
|
1068
|
+
Dict with 'result' key containing script coverage entries.
|
|
1069
|
+
"""
|
|
1070
|
+
if self._client is None:
|
|
1071
|
+
raise RuntimeError("BiDiBackend not launched. Call launch() first.")
|
|
1072
|
+
await self._client.cdp.send_command("Profiler.enable", {})
|
|
1073
|
+
await self._client.cdp.send_command(
|
|
1074
|
+
"Profiler.startPreciseCoverage",
|
|
1075
|
+
{"callCount": True, "detailed": True},
|
|
1076
|
+
)
|
|
1077
|
+
result = await self._client.cdp.send_command("Profiler.takePreciseCoverage", {})
|
|
1078
|
+
return dict(result) if result else {}
|
|
1031
1079
|
|
|
1032
1080
|
async def perf_css_coverage(self) -> dict[str, Any]:
|
|
1033
1081
|
raise NotImplementedError(
|
|
@@ -1037,7 +1085,49 @@ class BiDiBackend(AbstractBackend):
|
|
|
1037
1085
|
# ── CSS ────────────────────────────────────────────────
|
|
1038
1086
|
|
|
1039
1087
|
async def css_get_styles(self, selector: str) -> dict[str, Any]:
|
|
1040
|
-
|
|
1088
|
+
"""Get inline and matched styles for an element via JS.
|
|
1089
|
+
|
|
1090
|
+
Uses script.evaluate to extract inline styles and matched
|
|
1091
|
+
CSS rules from document.styleSheets for the given selector.
|
|
1092
|
+
|
|
1093
|
+
Args:
|
|
1094
|
+
selector: CSS selector for the target element.
|
|
1095
|
+
|
|
1096
|
+
Returns:
|
|
1097
|
+
Dict containing inlineStyles and matchedStyles.
|
|
1098
|
+
"""
|
|
1099
|
+
if self._client is None or self._context is None:
|
|
1100
|
+
raise RuntimeError("BiDiBackend not launched. Call launch() first.")
|
|
1101
|
+
escaped = selector.replace("'", "\\'")
|
|
1102
|
+
js = (
|
|
1103
|
+
f"(function(){{"
|
|
1104
|
+
f" var el=document.querySelector('{escaped}');"
|
|
1105
|
+
f" if(!el) return null;"
|
|
1106
|
+
f" var inline=el.getAttribute('style')||'';"
|
|
1107
|
+
f" var matched=[];"
|
|
1108
|
+
f" for(var i=0;i<document.styleSheets.length;i++){{"
|
|
1109
|
+
f" try{{"
|
|
1110
|
+
f" var sheet=document.styleSheets[i];"
|
|
1111
|
+
f" var rules=sheet.cssRules||sheet.rules;"
|
|
1112
|
+
f" for(var j=0;j<rules.length;j++){{"
|
|
1113
|
+
f" if(el.matches(rules[j].selectorText)){{"
|
|
1114
|
+
f" matched.push({{"
|
|
1115
|
+
f" selectorText:rules[j].selectorText,"
|
|
1116
|
+
f" cssText:rules[j].cssText"
|
|
1117
|
+
f" }});"
|
|
1118
|
+
f" }}"
|
|
1119
|
+
f" }}"
|
|
1120
|
+
f" }}catch(e){{}}"
|
|
1121
|
+
f" }}"
|
|
1122
|
+
f" return JSON.stringify({{inlineStyles:inline,matchedStyles:matched}});"
|
|
1123
|
+
f"}})()"
|
|
1124
|
+
)
|
|
1125
|
+
result = await self._client.script.evaluate(self._context, js)
|
|
1126
|
+
val = result.value if hasattr(result, "value") else result
|
|
1127
|
+
if not val:
|
|
1128
|
+
raise RuntimeError(f"Element not found: {selector}")
|
|
1129
|
+
import json as _json
|
|
1130
|
+
return _json.loads(val) if isinstance(val, str) else dict(val)
|
|
1041
1131
|
|
|
1042
1132
|
async def css_get_stylesheets(self) -> list[dict[str, Any]]:
|
|
1043
1133
|
raise NotImplementedError(
|
|
@@ -1048,9 +1138,36 @@ class BiDiBackend(AbstractBackend):
|
|
|
1048
1138
|
raise NotImplementedError("css_get_rules is not supported by BiDiBackend")
|
|
1049
1139
|
|
|
1050
1140
|
async def css_get_computed(self, selector: str) -> dict[str, Any]:
|
|
1051
|
-
|
|
1052
|
-
|
|
1141
|
+
"""Get computed styles for an element via JS getComputedStyle.
|
|
1142
|
+
|
|
1143
|
+
Args:
|
|
1144
|
+
selector: CSS selector for the target element.
|
|
1145
|
+
|
|
1146
|
+
Returns:
|
|
1147
|
+
Dict mapping CSS property names to computed values.
|
|
1148
|
+
"""
|
|
1149
|
+
if self._client is None or self._context is None:
|
|
1150
|
+
raise RuntimeError("BiDiBackend not launched. Call launch() first.")
|
|
1151
|
+
escaped = selector.replace("'", "\\'")
|
|
1152
|
+
js = (
|
|
1153
|
+
f"(function(){{"
|
|
1154
|
+
f" var el=document.querySelector('{escaped}');"
|
|
1155
|
+
f" if(!el) return null;"
|
|
1156
|
+
f" var cs=getComputedStyle(el);"
|
|
1157
|
+
f" var result={{}};"
|
|
1158
|
+
f" for(var i=0;i<cs.length;i++){{"
|
|
1159
|
+
f" var prop=cs.item(i);"
|
|
1160
|
+
f" result[prop]=cs.getPropertyValue(prop);"
|
|
1161
|
+
f" }}"
|
|
1162
|
+
f" return JSON.stringify(result);"
|
|
1163
|
+
f"}})()"
|
|
1053
1164
|
)
|
|
1165
|
+
result = await self._client.script.evaluate(self._context, js)
|
|
1166
|
+
val = result.value if hasattr(result, "value") else result
|
|
1167
|
+
if not val:
|
|
1168
|
+
raise RuntimeError(f"Element not found: {selector}")
|
|
1169
|
+
import json as _json
|
|
1170
|
+
return _json.loads(val) if isinstance(val, str) else dict(val)
|
|
1054
1171
|
|
|
1055
1172
|
# ── Debugging ──────────────────────────────────────────
|
|
1056
1173
|
|
|
@@ -1094,7 +1211,19 @@ class BiDiBackend(AbstractBackend):
|
|
|
1094
1211
|
# ── DOM Snapshot ───────────────────────────────────────
|
|
1095
1212
|
|
|
1096
1213
|
async def dom_snapshot(self) -> dict[str, Any]:
|
|
1097
|
-
|
|
1214
|
+
"""Capture a DOM snapshot via JS.
|
|
1215
|
+
|
|
1216
|
+
Uses script.evaluate to serialize the full DOM tree.
|
|
1217
|
+
|
|
1218
|
+
Returns:
|
|
1219
|
+
Dict containing 'html' with the full outerHTML.
|
|
1220
|
+
"""
|
|
1221
|
+
if self._client is None or self._context is None:
|
|
1222
|
+
raise RuntimeError("BiDiBackend not launched. Call launch() first.")
|
|
1223
|
+
js = "document.documentElement.outerHTML"
|
|
1224
|
+
result = await self._client.script.evaluate(self._context, js)
|
|
1225
|
+
html = result.value if hasattr(result, "value") else result
|
|
1226
|
+
return {"html": str(html)}
|
|
1098
1227
|
|
|
1099
1228
|
# ── Overlay ────────────────────────────────────────────
|
|
1100
1229
|
|
|
@@ -95,6 +95,16 @@ class TestBiDiBackend:
|
|
|
95
95
|
await backend.set_cpu_throttle(4.0)
|
|
96
96
|
with pytest.raises(RuntimeError, match="not launched"):
|
|
97
97
|
await backend.set_sensors(MagicMock())
|
|
98
|
+
with pytest.raises(RuntimeError, match="not launched"):
|
|
99
|
+
await backend.perf_metrics()
|
|
100
|
+
with pytest.raises(RuntimeError, match="not launched"):
|
|
101
|
+
await backend.perf_coverage()
|
|
102
|
+
with pytest.raises(RuntimeError, match="not launched"):
|
|
103
|
+
await backend.css_get_styles("h1")
|
|
104
|
+
with pytest.raises(RuntimeError, match="not launched"):
|
|
105
|
+
await backend.css_get_computed("h1")
|
|
106
|
+
with pytest.raises(RuntimeError, match="not launched"):
|
|
107
|
+
await backend.dom_snapshot()
|
|
98
108
|
|
|
99
109
|
async def test_bidi_paridad_methods_raise_runtime_without_launch(self) -> None:
|
|
100
110
|
with patch("browsix.backend.bidi.BiDiClient", MagicMock()):
|
browsix-1.2.0/README.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# browsix
|
|
2
|
-
|
|
3
|
-
[](https://github.com/MathiasPaulenko/browsix/actions/workflows/ci.yml)
|
|
4
|
-
[](https://pypi.org/project/browsix/)
|
|
5
|
-
[](https://pypi.org/project/browsix/)
|
|
6
|
-
[](https://github.com/MathiasPaulenko/browsix/blob/main/LICENSE)
|
|
7
|
-
|
|
8
|
-
> Browser automation CLI — wraps cdpwave and bidiwave. No Node.js, no Chromium download. Uses your existing Chrome/Edge.
|
|
9
|
-
|
|
10
|
-
## Install
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
pip install browsix[cdp]
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Quick start
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
# Take a screenshot
|
|
20
|
-
browsix screenshot https://example.com -o out.png
|
|
21
|
-
|
|
22
|
-
# Generate a PDF
|
|
23
|
-
browsix pdf https://example.com -o out.pdf --paper a4
|
|
24
|
-
|
|
25
|
-
# Evaluate JavaScript
|
|
26
|
-
browsix eval https://example.com -e "document.title"
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Multi-action
|
|
30
|
-
|
|
31
|
-
Create a YAML config and run multiple actions in sequence:
|
|
32
|
-
|
|
33
|
-
```yaml
|
|
34
|
-
# actions.yml
|
|
35
|
-
actions:
|
|
36
|
-
- screenshot:
|
|
37
|
-
url: https://example.com
|
|
38
|
-
full_page: true
|
|
39
|
-
- eval:
|
|
40
|
-
url: https://example.com
|
|
41
|
-
expression: document.title
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
browsix multi actions.yml
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Backends
|
|
49
|
-
|
|
50
|
-
browsix supports two backends:
|
|
51
|
-
|
|
52
|
-
- **CDP** (cdpwave) — default, full feature support. `pip install browsix[cdp]`
|
|
53
|
-
- **BiDi** (bidiwave) — minimal, WebDriver BiDi protocol. `pip install browsix[bidi]`
|
|
54
|
-
|
|
55
|
-
Select with `--backend`:
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
browsix --backend cdp screenshot https://example.com -o out.png
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Comparison
|
|
62
|
-
|
|
63
|
-
| Feature | browsix | shot-scraper | Playwright |
|
|
64
|
-
|---------|---------|--------------|------------|
|
|
65
|
-
| Language | Python | Python | Multi |
|
|
66
|
-
| Node.js required | No | Yes | Yes |
|
|
67
|
-
| Chromium download | No | Yes | Yes |
|
|
68
|
-
| CDP backend | Yes | Yes | Yes |
|
|
69
|
-
| BiDi backend | Yes | No | No |
|
|
70
|
-
| CLI-first | Yes | Yes | No |
|
|
71
|
-
| Multi-action YAML | Yes | No | No |
|
|
72
|
-
| Device emulation | Yes | Yes | Yes |
|
|
73
|
-
| HAR capture | Yes | No | Yes |
|
|
74
|
-
| PDF generation | Yes | Yes | Yes |
|
|
75
|
-
| Shell completions | Yes | No | No |
|
|
76
|
-
|
|
77
|
-
## Documentation
|
|
78
|
-
|
|
79
|
-
Full docs at [mathiaspaulenko.github.io/browsix](https://mathiaspaulenko.github.io/browsix/).
|
|
80
|
-
|
|
81
|
-
## License
|
|
82
|
-
|
|
83
|
-
MIT
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|