wavexis 2.1.2__tar.gz → 2.2.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.
- {wavexis-2.1.2 → wavexis-2.2.0}/.gitignore +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/CHANGELOG.md +18 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/PKG-INFO +1 -1
- {wavexis-2.1.2 → wavexis-2.2.0}/pyproject.toml +1 -1
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_global_options.py +19 -1
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/__init__.py +1 -1
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/backend/cdp.py +1 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_shared.py +9 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_workflow.py +3 -2
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/config.py +2 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/plugins.py +1 -1
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/serve.py +21 -3
- {wavexis-2.1.2 → wavexis-2.2.0}/.dockerignore +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/.github/workflows/ci.yml +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/.github/workflows/docs.yml +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/.github/workflows/release.yml +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/CODE_OF_CONDUCT.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/CONTRIBUTING.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/Dockerfile +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/LICENSE +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/README.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/SECURITY.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/api/actions.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/api/backends.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/api/cli.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/api/config.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/api/exceptions.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/api/multi.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/api/plugins.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/api/serve.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/cookbook/auth.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/cookbook/ci-cd.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/cookbook/css.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/cookbook/debug.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/cookbook/record-replay.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/cookbook/scraping.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/cookbook/screenshots.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/cookbook/serve-mode.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/assert.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/backends.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/commands.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/init.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/installation.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/multi.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/perf.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/plugins.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/quickstart.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/raw.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/repl.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/guide/troubleshooting.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/docs/index.md +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/mkdocs.yml +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/stubs/bidiwave/__init__.pyi +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/stubs/cdpwave/__init__.pyi +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/__init__.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/conftest.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/__init__.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_a11y.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_animation.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_backend_selection.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_browser.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_console.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_css.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_debug.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_dialog.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_dom.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_dom_snapshot.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_emulation.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_emulation_advanced.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_eval.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_har.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_input.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_media.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_multi.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_navigate.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_network.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_network_advanced.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_overlay.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_pdf.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_perf.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_permissions.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_raw.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_record.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_scrape.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_screenshot.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_security.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_serve.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_service_worker.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_storage.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_tabs.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_webaudio.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/integration/test_webauthn.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/__init__.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_abstract_backend_phase5.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_actions.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_actions_phase5.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_animation_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_assert_multi.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_auth.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_backend_manager.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_bidi_backend.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_bidi_phase5.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_bluetooth_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_cast_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_cli_phase5.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_config.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_config_phase5.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_cookies_headers_actions.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_css_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_debug_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_dom_snapshot_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_download_screencast.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_emulation_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_error_handling.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_exceptions.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_format_record.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_init.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_input_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_interactions_crawl.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_manager.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_media_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_multi.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_multi_improvements.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_new_features.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_output.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_overlay_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_perf_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_perf_console.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_plugins.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_raw.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_record.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_repl.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_serve.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_service_worker_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_storage_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_webaudio_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/tests/unit/test_webauthn_action.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/__main__.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/__init__.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/accessibility.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/animation.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/base.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/bluetooth.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/browser.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/cast.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/console.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/cookies.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/crawl.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/css.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/debug.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/dialog.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/dom.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/dom_snapshot.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/download.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/emulation.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/eval.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/extract.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/form.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/har.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/headers.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/input.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/lighthouse.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/media.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/multi.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/navigate.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/network.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/overlay.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/pdf.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/performance.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/permissions.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/record.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/scrape.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/screencast.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/screenshot.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/security.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/service_worker.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/session.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/storage.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/tabs.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/wait.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/webaudio.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/webauthn.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/actions/websocket.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/auth.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/backend/__init__.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/backend/base.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/backend/bidi.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/backend/manager.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/__init__.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_advanced.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_capture.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_config.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_debug.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_emulation.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_experimental.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_input.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_navigation.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_network.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_perf.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_serve.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/_session.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/cli/app.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/exceptions.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/init.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/multi.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/output.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/record.py +0 -0
- {wavexis-2.1.2 → wavexis-2.2.0}/wavexis/repl.py +0 -0
|
Binary file
|
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to wavexis are documented in this file.
|
|
4
4
|
|
|
5
|
+
## v2.2.0 — 2026-07-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `--user-data-dir` global flag for persistent browser profiles (cookies, login, extensions)
|
|
10
|
+
- `user_data_dir` field in `BrowserOptions` dataclass
|
|
11
|
+
- `user_data_dir` support in `config.yml` (`user_data_dir: /path/to/profile`)
|
|
12
|
+
- `user_data_dir` passed to `CDPClient.launch()` for CDP backend
|
|
13
|
+
|
|
14
|
+
## v2.1.3 — 2026-07-07
|
|
15
|
+
|
|
16
|
+
### Improved
|
|
17
|
+
|
|
18
|
+
- Fixed `_multi_watch()` bypassing `_run_async()` error handling — now uses `_run_async()` for proper `WavexisError` propagation
|
|
19
|
+
- Narrowed 3 `except Exception` handlers in `serve.py` WebSocket streams to `WavexisError` and `(ConnectionError, OSError)`
|
|
20
|
+
- Narrowed `except Exception` in `plugins.py` to `(ImportError, AttributeError, ValueError, TypeError)` for plugin loading isolation
|
|
21
|
+
- Verified `.gitignore` excludes `screenshot.png`
|
|
22
|
+
|
|
5
23
|
## v2.1.2 — 2026-07-07
|
|
6
24
|
|
|
7
25
|
### Refactored
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavexis
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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/wavexis
|
|
6
6
|
Project-URL: Repository, https://github.com/MathiasPaulenko/wavexis
|
|
@@ -30,6 +30,11 @@ class TestBrowserOptions:
|
|
|
30
30
|
assert opts.headless is True
|
|
31
31
|
assert opts.proxy is None
|
|
32
32
|
assert opts.timeout == 30000
|
|
33
|
+
assert opts.user_data_dir is None
|
|
34
|
+
|
|
35
|
+
def test_with_user_data_dir(self) -> None:
|
|
36
|
+
opts = BrowserOptions(user_data_dir="/tmp/wavexis-profile")
|
|
37
|
+
assert opts.user_data_dir == "/tmp/wavexis-profile"
|
|
33
38
|
|
|
34
39
|
def test_with_proxy(self) -> None:
|
|
35
40
|
opts = BrowserOptions(proxy="http://proxy:8080")
|
|
@@ -57,6 +62,13 @@ class TestBrowserOptionsHelper:
|
|
|
57
62
|
assert opts.headless is True
|
|
58
63
|
assert opts.timeout == 30000
|
|
59
64
|
assert opts.proxy is None
|
|
65
|
+
assert opts.user_data_dir is None
|
|
66
|
+
|
|
67
|
+
def test_user_data_dir_options(self) -> None:
|
|
68
|
+
ctx = _fresh_ctx()
|
|
69
|
+
ctx.user_data_dir = "/tmp/wavexis-profile"
|
|
70
|
+
opts = _cli._browser_options()
|
|
71
|
+
assert opts.user_data_dir == "/tmp/wavexis-profile"
|
|
60
72
|
|
|
61
73
|
def test_headed_options(self) -> None:
|
|
62
74
|
ctx = _fresh_ctx()
|
|
@@ -88,13 +100,18 @@ class TestLoadGlobalConfig:
|
|
|
88
100
|
assert ctx.headless is True
|
|
89
101
|
assert ctx.timeout == 30000
|
|
90
102
|
assert ctx.proxy is None
|
|
103
|
+
assert ctx.user_data_dir is None
|
|
91
104
|
|
|
92
105
|
def test_loads_config(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
93
106
|
config_dir = tmp_path / ".wavexis"
|
|
94
107
|
config_dir.mkdir()
|
|
95
108
|
config_file = config_dir / "config.yml"
|
|
96
109
|
config_file.write_text(
|
|
97
|
-
"backend: bidi\
|
|
110
|
+
"backend: bidi\n"
|
|
111
|
+
"headless: false\n"
|
|
112
|
+
"timeout: 60000\n"
|
|
113
|
+
"proxy: http://proxy:9090\n"
|
|
114
|
+
"user_data_dir: /tmp/profile\n",
|
|
98
115
|
encoding="utf-8",
|
|
99
116
|
)
|
|
100
117
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
@@ -106,6 +123,7 @@ class TestLoadGlobalConfig:
|
|
|
106
123
|
assert ctx.headless is False
|
|
107
124
|
assert ctx.timeout == 60000
|
|
108
125
|
assert ctx.proxy == "http://proxy:9090"
|
|
126
|
+
assert ctx.user_data_dir == "/tmp/profile"
|
|
109
127
|
|
|
110
128
|
def test_cli_overrides_config(
|
|
111
129
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
@@ -85,6 +85,7 @@ class CDPBackend(AbstractBackend):
|
|
|
85
85
|
|
|
86
86
|
self._client = await CDPClient.launch(
|
|
87
87
|
headless=options.headless,
|
|
88
|
+
user_data_dir=options.user_data_dir, # type: ignore[call-arg]
|
|
88
89
|
extra_args=extra_args if extra_args else None,
|
|
89
90
|
)
|
|
90
91
|
self._session = await self._client.new_page()
|
|
@@ -75,6 +75,7 @@ class CLIContext:
|
|
|
75
75
|
headless: bool = True
|
|
76
76
|
timeout: int = 30000
|
|
77
77
|
proxy: str | None = None
|
|
78
|
+
user_data_dir: str | None = None
|
|
78
79
|
|
|
79
80
|
|
|
80
81
|
_ctx: contextvars.ContextVar[CLIContext | None] = contextvars.ContextVar(
|
|
@@ -113,6 +114,8 @@ def _load_global_config() -> None:
|
|
|
113
114
|
ctx.timeout = int(raw["timeout"])
|
|
114
115
|
if "proxy" in raw:
|
|
115
116
|
ctx.proxy = str(raw["proxy"])
|
|
117
|
+
if "user_data_dir" in raw:
|
|
118
|
+
ctx.user_data_dir = str(raw["user_data_dir"])
|
|
116
119
|
except (OSError, ValueError, TypeError, yaml.YAMLError) as exc:
|
|
117
120
|
if ctx.verbose:
|
|
118
121
|
_echo(f"Warning: failed to load config from {config_path}: {exc}")
|
|
@@ -138,6 +141,9 @@ def main_callback(
|
|
|
138
141
|
proxy: str | None = typer.Option(
|
|
139
142
|
None, "--proxy", help="Proxy server URL (e.g. http://proxy:8080)"
|
|
140
143
|
),
|
|
144
|
+
user_data_dir: str | None = typer.Option(
|
|
145
|
+
None, "--user-data-dir", help="Path to persistent browser profile directory"
|
|
146
|
+
),
|
|
141
147
|
version: bool = typer.Option(
|
|
142
148
|
False, "--version", help="Print wavexis version and exit"
|
|
143
149
|
),
|
|
@@ -154,6 +160,8 @@ def main_callback(
|
|
|
154
160
|
ctx.timeout = timeout
|
|
155
161
|
if proxy:
|
|
156
162
|
ctx.proxy = proxy
|
|
163
|
+
if user_data_dir:
|
|
164
|
+
ctx.user_data_dir = user_data_dir
|
|
157
165
|
if version:
|
|
158
166
|
from wavexis import __version__
|
|
159
167
|
|
|
@@ -227,6 +235,7 @@ def _browser_options() -> BrowserOptions:
|
|
|
227
235
|
headless=ctx.headless,
|
|
228
236
|
timeout=ctx.timeout,
|
|
229
237
|
proxy=ctx.proxy,
|
|
238
|
+
user_data_dir=ctx.user_data_dir,
|
|
230
239
|
)
|
|
231
240
|
|
|
232
241
|
|
|
@@ -97,8 +97,9 @@ def _multi_watch(config_path: Any, parallel: bool = False) -> None:
|
|
|
97
97
|
if mtime != last_mtime:
|
|
98
98
|
last_mtime = mtime
|
|
99
99
|
typer.echo(f"\n[{time.strftime('%H:%M:%S')}] Re-running actions…")
|
|
100
|
-
results =
|
|
101
|
-
|
|
100
|
+
results = _run_async(_multi(config_path, parallel=parallel))
|
|
101
|
+
if results is not None:
|
|
102
|
+
typer.echo(f"Completed {len(results)} actions")
|
|
102
103
|
time.sleep(1)
|
|
103
104
|
except KeyboardInterrupt:
|
|
104
105
|
typer.echo("\nStopped.")
|
|
@@ -146,6 +146,7 @@ class BrowserOptions:
|
|
|
146
146
|
extra_headers: Extra HTTP headers to send with every request.
|
|
147
147
|
proxy: Proxy server URL (e.g. http://proxy:8080 or socks5://proxy:1080).
|
|
148
148
|
timeout: Default navigation timeout in milliseconds.
|
|
149
|
+
user_data_dir: Path to a persistent user data directory for browser profiles.
|
|
149
150
|
"""
|
|
150
151
|
|
|
151
152
|
headless: bool = True
|
|
@@ -155,6 +156,7 @@ class BrowserOptions:
|
|
|
155
156
|
extra_headers: dict[str, str] = field(default_factory=dict)
|
|
156
157
|
proxy: str | None = None
|
|
157
158
|
timeout: int = 30000
|
|
159
|
+
user_data_dir: str | None = None
|
|
158
160
|
|
|
159
161
|
|
|
160
162
|
@dataclass
|
|
@@ -198,7 +198,7 @@ def _discover_entry_points() -> PluginRegistry:
|
|
|
198
198
|
for ep in eps:
|
|
199
199
|
try:
|
|
200
200
|
obj = ep.load()
|
|
201
|
-
except
|
|
201
|
+
except (ImportError, AttributeError, ValueError, TypeError) as exc:
|
|
202
202
|
logger.warning("Failed to load plugin %s: %s", ep.name, exc)
|
|
203
203
|
continue
|
|
204
204
|
|
|
@@ -389,7 +389,13 @@ async def _stream_screenshots(
|
|
|
389
389
|
"data": b64,
|
|
390
390
|
"timestamp": time.time(),
|
|
391
391
|
})
|
|
392
|
-
except
|
|
392
|
+
except WavexisError as exc:
|
|
393
|
+
await ws.send_json({
|
|
394
|
+
"type": "error",
|
|
395
|
+
"source": "screenshot",
|
|
396
|
+
"message": str(exc),
|
|
397
|
+
})
|
|
398
|
+
except (ConnectionError, OSError) as exc:
|
|
393
399
|
await ws.send_json({
|
|
394
400
|
"type": "error",
|
|
395
401
|
"source": "screenshot",
|
|
@@ -415,7 +421,13 @@ async def _stream_console(
|
|
|
415
421
|
"data": msg,
|
|
416
422
|
"timestamp": time.time(),
|
|
417
423
|
})
|
|
418
|
-
except
|
|
424
|
+
except WavexisError as exc:
|
|
425
|
+
await ws.send_json({
|
|
426
|
+
"type": "error",
|
|
427
|
+
"source": "console",
|
|
428
|
+
"message": str(exc),
|
|
429
|
+
})
|
|
430
|
+
except (ConnectionError, OSError) as exc:
|
|
419
431
|
await ws.send_json({
|
|
420
432
|
"type": "error",
|
|
421
433
|
"source": "console",
|
|
@@ -440,7 +452,13 @@ async def _stream_navigation(
|
|
|
440
452
|
"url": current_url,
|
|
441
453
|
"timestamp": time.time(),
|
|
442
454
|
})
|
|
443
|
-
except
|
|
455
|
+
except WavexisError as exc:
|
|
456
|
+
await ws.send_json({
|
|
457
|
+
"type": "error",
|
|
458
|
+
"source": "navigation",
|
|
459
|
+
"message": str(exc),
|
|
460
|
+
})
|
|
461
|
+
except (ConnectionError, OSError) as exc:
|
|
444
462
|
await ws.send_json({
|
|
445
463
|
"type": "error",
|
|
446
464
|
"source": "navigation",
|
|
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
|
|
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
|