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.
Files changed (162) hide show
  1. {browsix-1.2.0 → browsix-1.3.0}/PKG-INFO +71 -4
  2. browsix-1.3.0/README.md +150 -0
  3. {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/bidi.py +140 -11
  4. {browsix-1.2.0 → browsix-1.3.0}/pyproject.toml +1 -1
  5. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_bidi_backend.py +10 -0
  6. browsix-1.2.0/README.md +0 -83
  7. {browsix-1.2.0 → browsix-1.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  8. {browsix-1.2.0 → browsix-1.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  9. {browsix-1.2.0 → browsix-1.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  10. {browsix-1.2.0 → browsix-1.3.0}/.github/workflows/ci.yml +0 -0
  11. {browsix-1.2.0 → browsix-1.3.0}/.github/workflows/docs.yml +0 -0
  12. {browsix-1.2.0 → browsix-1.3.0}/.github/workflows/release.yml +0 -0
  13. {browsix-1.2.0 → browsix-1.3.0}/.github/workflows/serve-test.yml +0 -0
  14. {browsix-1.2.0 → browsix-1.3.0}/.gitignore +0 -0
  15. {browsix-1.2.0 → browsix-1.3.0}/CHANGELOG.md +0 -0
  16. {browsix-1.2.0 → browsix-1.3.0}/CODE_OF_CONDUCT.md +0 -0
  17. {browsix-1.2.0 → browsix-1.3.0}/CONTRIBUTING.md +0 -0
  18. {browsix-1.2.0 → browsix-1.3.0}/Dockerfile +0 -0
  19. {browsix-1.2.0 → browsix-1.3.0}/LICENSE +0 -0
  20. {browsix-1.2.0 → browsix-1.3.0}/SECURITY.md +0 -0
  21. {browsix-1.2.0 → browsix-1.3.0}/browsix/__init__.py +0 -0
  22. {browsix-1.2.0 → browsix-1.3.0}/browsix/__main__.py +0 -0
  23. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/__init__.py +0 -0
  24. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/accessibility.py +0 -0
  25. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/animation.py +0 -0
  26. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/base.py +0 -0
  27. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/bluetooth.py +0 -0
  28. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/browser.py +0 -0
  29. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/cast.py +0 -0
  30. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/console.py +0 -0
  31. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/css.py +0 -0
  32. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/debug.py +0 -0
  33. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/dialog.py +0 -0
  34. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/dom.py +0 -0
  35. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/dom_snapshot.py +0 -0
  36. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/download.py +0 -0
  37. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/emulation.py +0 -0
  38. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/eval.py +0 -0
  39. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/har.py +0 -0
  40. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/input.py +0 -0
  41. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/media.py +0 -0
  42. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/multi.py +0 -0
  43. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/navigate.py +0 -0
  44. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/network.py +0 -0
  45. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/overlay.py +0 -0
  46. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/pdf.py +0 -0
  47. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/performance.py +0 -0
  48. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/permissions.py +0 -0
  49. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/scrape.py +0 -0
  50. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/screencast.py +0 -0
  51. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/screenshot.py +0 -0
  52. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/security.py +0 -0
  53. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/service_worker.py +0 -0
  54. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/storage.py +0 -0
  55. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/tabs.py +0 -0
  56. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/webaudio.py +0 -0
  57. {browsix-1.2.0 → browsix-1.3.0}/browsix/actions/webauthn.py +0 -0
  58. {browsix-1.2.0 → browsix-1.3.0}/browsix/auth.py +0 -0
  59. {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/__init__.py +0 -0
  60. {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/base.py +0 -0
  61. {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/cdp.py +0 -0
  62. {browsix-1.2.0 → browsix-1.3.0}/browsix/backend/manager.py +0 -0
  63. {browsix-1.2.0 → browsix-1.3.0}/browsix/cli/__init__.py +0 -0
  64. {browsix-1.2.0 → browsix-1.3.0}/browsix/cli/app.py +0 -0
  65. {browsix-1.2.0 → browsix-1.3.0}/browsix/config.py +0 -0
  66. {browsix-1.2.0 → browsix-1.3.0}/browsix/exceptions.py +0 -0
  67. {browsix-1.2.0 → browsix-1.3.0}/browsix/multi.py +0 -0
  68. {browsix-1.2.0 → browsix-1.3.0}/browsix/output.py +0 -0
  69. {browsix-1.2.0 → browsix-1.3.0}/browsix/plugins.py +0 -0
  70. {browsix-1.2.0 → browsix-1.3.0}/browsix/record.py +0 -0
  71. {browsix-1.2.0 → browsix-1.3.0}/browsix/serve.py +0 -0
  72. {browsix-1.2.0 → browsix-1.3.0}/docs/api/actions.md +0 -0
  73. {browsix-1.2.0 → browsix-1.3.0}/docs/api/backends.md +0 -0
  74. {browsix-1.2.0 → browsix-1.3.0}/docs/api/cli.md +0 -0
  75. {browsix-1.2.0 → browsix-1.3.0}/docs/api/exceptions.md +0 -0
  76. {browsix-1.2.0 → browsix-1.3.0}/docs/api/serve.md +0 -0
  77. {browsix-1.2.0 → browsix-1.3.0}/docs/cookbook/ci-cd.md +0 -0
  78. {browsix-1.2.0 → browsix-1.3.0}/docs/cookbook/scraping.md +0 -0
  79. {browsix-1.2.0 → browsix-1.3.0}/docs/cookbook/screenshots.md +0 -0
  80. {browsix-1.2.0 → browsix-1.3.0}/docs/cookbook/serve-mode.md +0 -0
  81. {browsix-1.2.0 → browsix-1.3.0}/docs/guide/backends.md +0 -0
  82. {browsix-1.2.0 → browsix-1.3.0}/docs/guide/commands.md +0 -0
  83. {browsix-1.2.0 → browsix-1.3.0}/docs/guide/installation.md +0 -0
  84. {browsix-1.2.0 → browsix-1.3.0}/docs/guide/multi.md +0 -0
  85. {browsix-1.2.0 → browsix-1.3.0}/docs/guide/quickstart.md +0 -0
  86. {browsix-1.2.0 → browsix-1.3.0}/docs/guide/raw.md +0 -0
  87. {browsix-1.2.0 → browsix-1.3.0}/docs/guide/troubleshooting.md +0 -0
  88. {browsix-1.2.0 → browsix-1.3.0}/docs/index.md +0 -0
  89. {browsix-1.2.0 → browsix-1.3.0}/mkdocs.yml +0 -0
  90. {browsix-1.2.0 → browsix-1.3.0}/tests/__init__.py +0 -0
  91. {browsix-1.2.0 → browsix-1.3.0}/tests/conftest.py +0 -0
  92. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/__init__.py +0 -0
  93. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_a11y.py +0 -0
  94. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_animation.py +0 -0
  95. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_backend_selection.py +0 -0
  96. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_browser.py +0 -0
  97. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_console.py +0 -0
  98. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_css.py +0 -0
  99. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_debug.py +0 -0
  100. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_dialog.py +0 -0
  101. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_dom.py +0 -0
  102. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_dom_snapshot.py +0 -0
  103. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_emulation.py +0 -0
  104. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_emulation_advanced.py +0 -0
  105. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_eval.py +0 -0
  106. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_har.py +0 -0
  107. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_input.py +0 -0
  108. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_media.py +0 -0
  109. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_multi.py +0 -0
  110. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_navigate.py +0 -0
  111. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_network.py +0 -0
  112. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_network_advanced.py +0 -0
  113. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_overlay.py +0 -0
  114. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_pdf.py +0 -0
  115. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_perf.py +0 -0
  116. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_permissions.py +0 -0
  117. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_raw.py +0 -0
  118. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_record.py +0 -0
  119. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_scrape.py +0 -0
  120. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_screenshot.py +0 -0
  121. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_security.py +0 -0
  122. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_serve.py +0 -0
  123. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_service_worker.py +0 -0
  124. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_storage.py +0 -0
  125. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_tabs.py +0 -0
  126. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_webaudio.py +0 -0
  127. {browsix-1.2.0 → browsix-1.3.0}/tests/integration/test_webauthn.py +0 -0
  128. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/__init__.py +0 -0
  129. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_abstract_backend_phase5.py +0 -0
  130. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_actions.py +0 -0
  131. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_actions_phase5.py +0 -0
  132. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_animation_action.py +0 -0
  133. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_auth.py +0 -0
  134. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_backend_manager.py +0 -0
  135. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_bidi_phase5.py +0 -0
  136. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_bluetooth_action.py +0 -0
  137. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_cast_action.py +0 -0
  138. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_cli_phase5.py +0 -0
  139. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_config.py +0 -0
  140. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_config_phase5.py +0 -0
  141. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_css_action.py +0 -0
  142. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_debug_action.py +0 -0
  143. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_dom_snapshot_action.py +0 -0
  144. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_download_screencast.py +0 -0
  145. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_emulation_action.py +0 -0
  146. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_error_handling.py +0 -0
  147. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_exceptions.py +0 -0
  148. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_input_action.py +0 -0
  149. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_manager.py +0 -0
  150. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_media_action.py +0 -0
  151. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_multi.py +0 -0
  152. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_output.py +0 -0
  153. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_overlay_action.py +0 -0
  154. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_perf_action.py +0 -0
  155. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_plugins.py +0 -0
  156. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_raw.py +0 -0
  157. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_record.py +0 -0
  158. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_serve.py +0 -0
  159. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_service_worker_action.py +0 -0
  160. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_storage_action.py +0 -0
  161. {browsix-1.2.0 → browsix-1.3.0}/tests/unit/test_webaudio_action.py +0 -0
  162. {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.2.0
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
  [![PyPI](https://img.shields.io/pypi/v/browsix.svg)](https://pypi.org/project/browsix/)
50
50
  [![Python](https://img.shields.io/pypi/pyversions/browsix.svg)](https://pypi.org/project/browsix/)
51
51
  [![License](https://img.shields.io/github/license/MathiasPaulenko/browsix.svg)](https://github.com/MathiasPaulenko/browsix/blob/main/LICENSE)
52
+ [![Docs](https://img.shields.io/badge/docs-mkdocs-blue.svg)](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) — minimal, WebDriver BiDi protocol. `pip install browsix[bidi]`
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 cdp screenshot https://example.com -o out.png
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
@@ -0,0 +1,150 @@
1
+ # browsix
2
+
3
+ [![CI](https://github.com/MathiasPaulenko/browsix/actions/workflows/ci.yml/badge.svg)](https://github.com/MathiasPaulenko/browsix/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/browsix.svg)](https://pypi.org/project/browsix/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/browsix.svg)](https://pypi.org/project/browsix/)
6
+ [![License](https://img.shields.io/github/license/MathiasPaulenko/browsix.svg)](https://github.com/MathiasPaulenko/browsix/blob/main/LICENSE)
7
+ [![Docs](https://img.shields.io/badge/docs-mkdocs-blue.svg)](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, cookies (get/set/delete/clear),
42
- set_headers, set_user_agent, browser_version, emulate_device, set_viewport,
43
- set_geolocation, set_timezone, set_dark_mode, set_locale, set_touch_emulation,
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, performance,
52
- CSS, debug, DOM snapshot, overlay, cache storage, IndexedDB,
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
- raise NotImplementedError("perf_metrics is not supported by BiDiBackend")
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
- raise NotImplementedError("perf_coverage is not supported by BiDiBackend")
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
- raise NotImplementedError("css_get_styles is not supported by BiDiBackend")
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
- raise NotImplementedError(
1052
- "css_get_computed is not supported by BiDiBackend"
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
- raise NotImplementedError("dom_snapshot is not supported by BiDiBackend")
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "browsix"
7
- version = "1.2.0"
7
+ version = "1.3.0"
8
8
  description = "Browser automation CLI — wraps cdpwave and bidiwave, no Node.js, no Chromium download"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -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
- [![CI](https://github.com/MathiasPaulenko/browsix/actions/workflows/ci.yml/badge.svg)](https://github.com/MathiasPaulenko/browsix/actions/workflows/ci.yml)
4
- [![PyPI](https://img.shields.io/pypi/v/browsix.svg)](https://pypi.org/project/browsix/)
5
- [![Python](https://img.shields.io/pypi/pyversions/browsix.svg)](https://pypi.org/project/browsix/)
6
- [![License](https://img.shields.io/github/license/MathiasPaulenko/browsix.svg)](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