wavexis 2.2.4__tar.gz → 2.2.6__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.2.4 → wavexis-2.2.6}/CHANGELOG.md +21 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/PKG-INFO +1 -1
- {wavexis-2.2.4 → wavexis-2.2.6}/pyproject.toml +1 -1
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/__init__.py +1 -1
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_session.py +52 -6
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_shared.py +15 -5
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/exceptions.py +28 -3
- {wavexis-2.2.4 → wavexis-2.2.6}/.dockerignore +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/.github/workflows/ci.yml +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/.github/workflows/docs.yml +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/.github/workflows/release.yml +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/.gitignore +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/CODE_OF_CONDUCT.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/CONTRIBUTING.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/Dockerfile +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/LICENSE +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/README.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/SECURITY.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/api/actions.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/api/backends.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/api/cli.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/api/config.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/api/exceptions.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/api/multi.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/api/plugins.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/api/serve.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/cookbook/auth.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/cookbook/ci-cd.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/cookbook/css.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/cookbook/debug.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/cookbook/record-replay.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/cookbook/scraping.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/cookbook/screenshots.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/cookbook/serve-mode.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/assert.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/backends.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/commands.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/init.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/installation.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/multi.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/perf.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/plugins.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/quickstart.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/raw.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/repl.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/guide/troubleshooting.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/docs/index.md +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/mkdocs.yml +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/stubs/bidiwave/__init__.pyi +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/stubs/cdpwave/__init__.pyi +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/__init__.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/conftest.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/__init__.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_a11y.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_animation.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_backend_selection.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_browser.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_console.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_css.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_debug.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_dialog.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_dom.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_dom_snapshot.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_emulation.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_emulation_advanced.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_eval.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_har.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_input.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_media.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_multi.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_navigate.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_network.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_network_advanced.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_overlay.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_pdf.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_perf.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_permissions.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_raw.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_record.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_scrape.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_screenshot.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_security.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_serve.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_service_worker.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_storage.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_tabs.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_webaudio.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/integration/test_webauthn.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/__init__.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_abstract_backend_phase5.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_actions.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_actions_phase5.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_animation_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_assert_multi.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_auth.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_backend_manager.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_bidi_backend.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_bidi_phase5.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_bluetooth_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_cast_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_cleanup.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_cli_phase5.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_config.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_config_phase5.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_cookies_headers_actions.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_css_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_debug_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_dom_snapshot_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_download_screencast.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_emulation_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_error_handling.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_exceptions.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_format_record.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_global_options.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_init.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_input_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_interactions_crawl.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_manager.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_media_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_multi.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_multi_improvements.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_new_features.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_output.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_overlay_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_perf_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_perf_console.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_plugins.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_raw.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_record.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_repl.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_serve.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_service_worker_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_storage_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_webaudio_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/tests/unit/test_webauthn_action.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/__main__.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/__init__.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/accessibility.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/animation.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/base.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/bluetooth.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/browser.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/cast.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/console.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/cookies.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/crawl.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/css.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/debug.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/dialog.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/dom.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/dom_snapshot.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/download.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/emulation.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/eval.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/extract.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/form.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/har.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/headers.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/input.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/lighthouse.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/media.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/multi.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/navigate.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/network.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/overlay.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/pdf.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/performance.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/permissions.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/record.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/scrape.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/screencast.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/screenshot.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/security.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/service_worker.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/session.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/storage.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/tabs.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/wait.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/webaudio.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/webauthn.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/actions/websocket.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/auth.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/backend/__init__.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/backend/base.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/backend/bidi.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/backend/cdp.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/backend/manager.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cleanup.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/__init__.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_advanced.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_capture.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_config.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_debug.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_emulation.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_experimental.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_input.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_navigation.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_network.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_perf.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_serve.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/_workflow.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/cli/app.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/config.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/init.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/multi.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/output.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/plugins.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/record.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/repl.py +0 -0
- {wavexis-2.2.4 → wavexis-2.2.6}/wavexis/serve.py +0 -0
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to wavexis are documented in this file.
|
|
4
4
|
|
|
5
|
+
## v2.2.6 — 2026-07-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Named sessions: `--name` flag stores sessions in `~/.wavexis/sessions/`
|
|
10
|
+
- `wavexis session list` — list all saved named sessions with size and modification date
|
|
11
|
+
- `wavexis session delete --name <name>` — delete a named session
|
|
12
|
+
- Named sessions persist between runs, enabling stateful workflows
|
|
13
|
+
|
|
14
|
+
## v2.2.5 — 2026-07-07
|
|
15
|
+
|
|
16
|
+
### Improved
|
|
17
|
+
|
|
18
|
+
- Error messages now include actionable hints for common failures
|
|
19
|
+
- `ElementNotFoundError` suggests `wavexis dom` and `wavexis screenshot` for debugging
|
|
20
|
+
- `WaitTimeoutError` suggests increasing `--timeout` and alternative wait strategies
|
|
21
|
+
- `NavigationError` suggests checking URL, `--timeout`, and `--proxy`
|
|
22
|
+
- `SessionNotInitializedError` suggests `--headed` and checking Chromium installation
|
|
23
|
+
- `BackendNotAvailableError` shows install commands for both backends
|
|
24
|
+
- `MultiConfigError` suggests `--dry-run` to validate config
|
|
25
|
+
|
|
5
26
|
## v2.2.4 — 2026-07-07
|
|
6
27
|
|
|
7
28
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavexis
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.6
|
|
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
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import time
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any
|
|
8
9
|
|
|
@@ -21,7 +22,7 @@ from wavexis.config import WaitStrategy
|
|
|
21
22
|
@app.command()
|
|
22
23
|
def session(
|
|
23
24
|
action: str = typer.Argument(
|
|
24
|
-
..., help="Session action: save, load"
|
|
25
|
+
..., help="Session action: save, load, list, delete"
|
|
25
26
|
),
|
|
26
27
|
url: str = typer.Argument(
|
|
27
28
|
"", help="URL to navigate to (for save) or load before navigating (for load)"
|
|
@@ -29,18 +30,60 @@ def session(
|
|
|
29
30
|
output: str = typer.Option(
|
|
30
31
|
"session.json", "--output", "-o", help="Session file path"
|
|
31
32
|
),
|
|
33
|
+
name: str = typer.Option(
|
|
34
|
+
"", "--name", "-n", help="Named session (stored in ~/.wavexis/sessions/)"
|
|
35
|
+
),
|
|
32
36
|
) -> None:
|
|
33
37
|
"""Save or load browser session state (cookies + localStorage + sessionStorage).
|
|
34
38
|
|
|
35
39
|
\b
|
|
36
40
|
Save: wavexis session save https://app.com -o mysession.json
|
|
37
41
|
Load: wavexis session load mysession.json https://app.com
|
|
42
|
+
Named: wavexis session save https://app.com --name mysession
|
|
43
|
+
List: wavexis session list
|
|
44
|
+
Delete: wavexis session delete --name mysession
|
|
38
45
|
"""
|
|
39
46
|
from wavexis.actions.session import SessionLoadAction, SessionSaveAction
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
sessions_dir = Path.home() / ".wavexis" / "sessions"
|
|
49
|
+
|
|
50
|
+
def _session_path() -> Path:
|
|
51
|
+
if name:
|
|
52
|
+
return sessions_dir / f"{name}.json"
|
|
53
|
+
return Path(output)
|
|
54
|
+
|
|
55
|
+
if action == "list":
|
|
56
|
+
if not sessions_dir.exists():
|
|
57
|
+
typer.echo("No saved sessions found.")
|
|
58
|
+
return
|
|
59
|
+
sessions = list(sessions_dir.glob("*.json"))
|
|
60
|
+
if not sessions:
|
|
61
|
+
typer.echo("No saved sessions found.")
|
|
62
|
+
return
|
|
63
|
+
typer.echo(f"Saved sessions ({len(sessions)}):")
|
|
64
|
+
for s in sorted(sessions):
|
|
65
|
+
stat = s.stat()
|
|
66
|
+
modified = time.strftime("%Y-%m-%d %H:%M", time.localtime(stat.st_mtime))
|
|
67
|
+
typer.echo(f" {s.stem} ({stat.st_size} bytes, {modified})")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
if action == "delete":
|
|
71
|
+
if not name:
|
|
72
|
+
typer.echo("Error: --name required for delete", err=True)
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
target = _session_path()
|
|
75
|
+
if not target.exists():
|
|
76
|
+
typer.echo(f"Error: session '{name}' not found", err=True)
|
|
77
|
+
raise typer.Exit(1)
|
|
78
|
+
target.unlink()
|
|
79
|
+
typer.echo(f"Deleted session '{name}'")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
session_path = _session_path()
|
|
42
83
|
|
|
43
84
|
if action == "save":
|
|
85
|
+
if name:
|
|
86
|
+
sessions_dir.mkdir(parents=True, exist_ok=True)
|
|
44
87
|
if not url:
|
|
45
88
|
typer.echo("Error: URL required for session save", err=True)
|
|
46
89
|
raise typer.Exit(1)
|
|
@@ -56,11 +99,11 @@ def session(
|
|
|
56
99
|
await backend.close()
|
|
57
100
|
|
|
58
101
|
_run_async(_save_session())
|
|
59
|
-
typer.echo(f"Session saved to {
|
|
102
|
+
typer.echo(f"Session saved to {session_path}")
|
|
60
103
|
|
|
61
104
|
elif action == "load":
|
|
62
105
|
if not session_path.exists():
|
|
63
|
-
typer.echo(f"Error: session file not found: {
|
|
106
|
+
typer.echo(f"Error: session file not found: {session_path}", err=True)
|
|
64
107
|
raise typer.Exit(1)
|
|
65
108
|
|
|
66
109
|
async def _load_session() -> Any:
|
|
@@ -80,10 +123,13 @@ def session(
|
|
|
80
123
|
result = _run_async(_load_session())
|
|
81
124
|
if result is None:
|
|
82
125
|
return
|
|
83
|
-
typer.echo(f"Session loaded from {
|
|
126
|
+
typer.echo(f"Session loaded from {session_path}: {result}")
|
|
84
127
|
|
|
85
128
|
else:
|
|
86
|
-
typer.echo(
|
|
129
|
+
typer.echo(
|
|
130
|
+
f"Error: unknown session action '{action}'. Use save, load, list, or delete.",
|
|
131
|
+
err=True,
|
|
132
|
+
)
|
|
87
133
|
raise typer.Exit(1)
|
|
88
134
|
|
|
89
135
|
|
|
@@ -21,6 +21,7 @@ from wavexis.exceptions import (
|
|
|
21
21
|
ElementNotFoundError,
|
|
22
22
|
MultiConfigError,
|
|
23
23
|
NavigationError,
|
|
24
|
+
SessionNotInitializedError,
|
|
24
25
|
WaitTimeoutError,
|
|
25
26
|
WavexisError,
|
|
26
27
|
)
|
|
@@ -39,6 +40,7 @@ __all__ = [
|
|
|
39
40
|
"MultiConfigError",
|
|
40
41
|
"NavigationError",
|
|
41
42
|
"Output",
|
|
43
|
+
"SessionNotInitializedError",
|
|
42
44
|
"WaitTimeoutError",
|
|
43
45
|
"WavexisError",
|
|
44
46
|
"_browser_options",
|
|
@@ -209,20 +211,28 @@ def _handle_error(e: Exception) -> None:
|
|
|
209
211
|
"""
|
|
210
212
|
if isinstance(e, BackendNotAvailableError):
|
|
211
213
|
Output.error(
|
|
212
|
-
"No backend available
|
|
214
|
+
"No backend available.\n"
|
|
215
|
+
"Hint: Install cdpwave with `pip install wavexis[cdp]`, "
|
|
216
|
+
"or bidiwave with `pip install wavexis[bidi]`."
|
|
213
217
|
)
|
|
214
218
|
raise typer.Exit(EXIT_BACKEND_ERROR) from e
|
|
219
|
+
if isinstance(e, SessionNotInitializedError):
|
|
220
|
+
Output.error(str(e))
|
|
221
|
+
raise typer.Exit(EXIT_BROWSER_ERROR) from e
|
|
215
222
|
if isinstance(e, NavigationError):
|
|
216
|
-
Output.error(
|
|
223
|
+
Output.error(str(e))
|
|
217
224
|
raise typer.Exit(EXIT_BROWSER_ERROR) from e
|
|
218
225
|
if isinstance(e, WaitTimeoutError):
|
|
219
|
-
Output.error(
|
|
226
|
+
Output.error(str(e))
|
|
220
227
|
raise typer.Exit(EXIT_BROWSER_ERROR) from e
|
|
221
228
|
if isinstance(e, ElementNotFoundError):
|
|
222
|
-
Output.error(
|
|
229
|
+
Output.error(str(e))
|
|
223
230
|
raise typer.Exit(EXIT_BROWSER_ERROR) from e
|
|
224
231
|
if isinstance(e, MultiConfigError):
|
|
225
|
-
Output.error(
|
|
232
|
+
Output.error(
|
|
233
|
+
f"Invalid multi config: {e}\n"
|
|
234
|
+
"Hint: Run `wavexis multi <config> --dry-run` to validate the config."
|
|
235
|
+
)
|
|
226
236
|
raise typer.Exit(EXIT_CONFIG_ERROR) from e
|
|
227
237
|
if isinstance(e, WavexisError):
|
|
228
238
|
Output.error(str(e))
|
|
@@ -41,6 +41,21 @@ class BackendNotSupportedError(WavexisError):
|
|
|
41
41
|
class SessionNotInitializedError(WavexisError):
|
|
42
42
|
"""Raised when a backend method is called before launch()."""
|
|
43
43
|
|
|
44
|
+
def __init__(self, msg: str | None = None) -> None:
|
|
45
|
+
"""Initialize the error.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
msg: Optional custom message.
|
|
49
|
+
"""
|
|
50
|
+
if msg:
|
|
51
|
+
super().__init__(msg)
|
|
52
|
+
else:
|
|
53
|
+
super().__init__(
|
|
54
|
+
"Browser session not initialized. Call launch() first.\n"
|
|
55
|
+
"Hint: This usually means the browser failed to start. "
|
|
56
|
+
"Try --headed to see the browser window, or check if Chromium is installed."
|
|
57
|
+
)
|
|
58
|
+
|
|
44
59
|
|
|
45
60
|
class NavigationError(WavexisError):
|
|
46
61
|
"""Raised when navigation fails or times out."""
|
|
@@ -52,7 +67,11 @@ class NavigationError(WavexisError):
|
|
|
52
67
|
url: The URL that failed to navigate to.
|
|
53
68
|
reason: The failure reason.
|
|
54
69
|
"""
|
|
55
|
-
super().__init__(
|
|
70
|
+
super().__init__(
|
|
71
|
+
f"Navigation to '{url}' failed: {reason}\n"
|
|
72
|
+
f"Hint: Check the URL is reachable, try --timeout 60000, "
|
|
73
|
+
f"or use --proxy if behind a firewall."
|
|
74
|
+
)
|
|
56
75
|
|
|
57
76
|
|
|
58
77
|
class WaitTimeoutError(WavexisError):
|
|
@@ -66,7 +85,9 @@ class WaitTimeoutError(WavexisError):
|
|
|
66
85
|
timeout_ms: The timeout duration in milliseconds.
|
|
67
86
|
"""
|
|
68
87
|
super().__init__(
|
|
69
|
-
f"Wait strategy '{strategy}' timed out after {timeout_ms}ms"
|
|
88
|
+
f"Wait strategy '{strategy}' timed out after {timeout_ms}ms\n"
|
|
89
|
+
f"Hint: Increase timeout with --timeout {max(timeout_ms * 2, 60000)}, "
|
|
90
|
+
f"or use a different wait strategy (load, domcontentloaded, networkidle)."
|
|
70
91
|
)
|
|
71
92
|
|
|
72
93
|
|
|
@@ -79,7 +100,11 @@ class ElementNotFoundError(WavexisError):
|
|
|
79
100
|
Args:
|
|
80
101
|
selector: The CSS selector that matched no elements.
|
|
81
102
|
"""
|
|
82
|
-
super().__init__(
|
|
103
|
+
super().__init__(
|
|
104
|
+
f"No element found matching selector: {selector}\n"
|
|
105
|
+
f"Hint: Verify the selector with `wavexis dom --selector '{selector}'`, "
|
|
106
|
+
f"or use `wavexis screenshot` to inspect the page visually."
|
|
107
|
+
)
|
|
83
108
|
|
|
84
109
|
|
|
85
110
|
class ActionError(WavexisError):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|