wavexis 2.3.4__tar.gz → 2.4.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.3.4 → wavexis-2.4.0}/CHANGELOG.md +30 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/PKG-INFO +1 -1
- {wavexis-2.3.4 → wavexis-2.4.0}/pyproject.toml +1 -1
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_backend_manager.py +61 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_manager.py +31 -0
- wavexis-2.4.0/tests/unit/test_network_inspect.py +349 -0
- wavexis-2.4.0/tests/unit/test_nl.py +180 -0
- wavexis-2.4.0/tests/unit/test_w3_w12_enhancements.py +434 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/__init__.py +1 -1
- wavexis-2.4.0/wavexis/actions/axe_audit.py +50 -0
- wavexis-2.4.0/wavexis/actions/combined_trace.py +78 -0
- wavexis-2.4.0/wavexis/actions/har_replay.py +55 -0
- wavexis-2.4.0/wavexis/actions/modify_request.py +56 -0
- wavexis-2.4.0/wavexis/actions/visual_diff.py +126 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/base.py +148 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/bidi.py +488 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/cdp.py +473 -0
- wavexis-2.4.0/wavexis/cli/_network_inspect.py +327 -0
- wavexis-2.4.0/wavexis/cli/_nl.py +102 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/app.py +2 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/serve.py +13 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/.dockerignore +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/.github/workflows/ci.yml +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/.github/workflows/docs.yml +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/.github/workflows/release.yml +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/.gitignore +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/CODE_OF_CONDUCT.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/CONTRIBUTING.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/Dockerfile +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/LICENSE +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/README.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/SECURITY.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/actions.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/backends.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/cli.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/config.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/exceptions.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/multi.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/plugins.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/serve.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/auth.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/ci-cd.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/css.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/debug.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/record-replay.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/scraping.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/screenshots.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/serve-mode.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/assert.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/backends.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/commands.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/init.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/installation.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/multi.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/perf.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/plugins.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/quickstart.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/raw.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/repl.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/troubleshooting.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/docs/index.md +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/mkdocs.yml +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/stubs/bidiwave/__init__.pyi +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/stubs/cdpwave/__init__.pyi +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/__init__.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/conftest.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/__init__.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_a11y.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_animation.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_backend_selection.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_browser.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_console.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_css.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_debug.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_dialog.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_dom.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_dom_snapshot.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_emulation.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_emulation_advanced.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_eval.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_har.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_input.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_media.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_multi.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_navigate.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_network.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_network_advanced.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_overlay.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_pdf.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_perf.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_permissions.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_raw.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_record.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_scrape.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_screenshot.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_security.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_serve.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_service_worker.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_storage.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_tabs.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_webaudio.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_webauthn.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/__init__.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_abstract_backend_phase5.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_actions.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_actions_phase5.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_animation_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_assert_multi.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_auth.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_auto_wait.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_bidi_backend.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_bidi_phase5.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_bluetooth_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_cast_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_cleanup.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_cli_phase5.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_config.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_config_phase5.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_cookies_headers_actions.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_css_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_debug_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_dom_snapshot_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_download_screencast.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_emulation_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_error_handling.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_exceptions.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_format_record.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_global_options.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_iframe.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_init.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_input_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_interactions_crawl.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_media_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_multi.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_multi_improvements.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_new_features.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_output.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_overlay_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_perf_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_perf_console.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_plugins.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_raw.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_record.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_repl.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_serve.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_service_worker_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_shadow.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_storage_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_suggest_locator.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_webaudio_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_webauthn_action.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/__main__.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/__init__.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/accessibility.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/animation.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/base.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/bluetooth.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/browser.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/cast.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/console.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/cookies.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/crawl.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/css.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/debug.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/dialog.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/dom.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/dom_snapshot.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/download.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/emulation.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/eval.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/extract.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/form.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/har.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/headers.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/input.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/lighthouse.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/media.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/multi.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/navigate.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/network.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/overlay.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/pdf.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/performance.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/permissions.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/record.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/scrape.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/screencast.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/screenshot.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/security.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/service_worker.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/session.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/storage.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/tabs.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/wait.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/webaudio.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/webauthn.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/websocket.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/auth.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/__init__.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/manager.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cleanup.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/__init__.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_advanced.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_capture.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_config.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_debug.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_emulation.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_experimental.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_iframe.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_input.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_navigation.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_network.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_perf.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_serve.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_session.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_shadow.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_shared.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_workflow.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/config.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/exceptions.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/init.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/multi.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/output.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/plugins.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/record.py +0 -0
- {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/repl.py +0 -0
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to wavexis are documented in this file.
|
|
4
4
|
|
|
5
|
+
## v2.4.0 — 2026-07-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Request/response body inspection: `get_request_body`, `get_response_body` in both CDP and BiDi backends
|
|
10
|
+
- `wavexis inspect <url> -r <request_id> -t request|response` CLI command
|
|
11
|
+
- Request interception and modification: `modify_request` via CDP Fetch domain in both backends
|
|
12
|
+
- `wavexis modify <url> -p <pattern> -h <header> -m <method>` CLI command
|
|
13
|
+
- HAR replay: `replay_har` reads HAR file and replays requests via browser fetch API
|
|
14
|
+
- `wavexis har-replay <har_path> -u <url> -f <filter>` CLI command
|
|
15
|
+
- Combined tracing: `start_combined_trace`/`stop_combined_trace` capturing screenshots, network, console, and trace events
|
|
16
|
+
- `wavexis trace start|stop` CLI command with `--no-screenshots`, `--no-network`, `--no-console` flags
|
|
17
|
+
- axe-core accessibility audit: `axe_audit` injects axe-core JS and returns violations/passes/incomplete/inapplicable
|
|
18
|
+
- `wavexis axe <url> -o <output>` CLI command
|
|
19
|
+
- Event subscription: `subscribe_events`/`unsubscribe_events` for real-time console, network, dialog, and navigation events
|
|
20
|
+
- `wavexis events subscribe <url> -t <types> -d <duration>` CLI command
|
|
21
|
+
- WebSocket event streaming in serve.py for network_request, network_response, and dialog events
|
|
22
|
+
|
|
23
|
+
## v2.3.5 — 2026-07-07
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Natural language selector: `find_by_text`, `nl_click`, `nl_fill` in both CDP and BiDi backends
|
|
28
|
+
- `wavexis nl click/fill/find` CLI command with natural language queries
|
|
29
|
+
- Fuzzy text matching scoring: exact match (100), contains query (80), query contains text (60), word overlap (up to 50)
|
|
30
|
+
- Searches textContent, aria-label, placeholder, title, alt, and value attributes
|
|
31
|
+
- Returns best CSS selector for the matched element
|
|
32
|
+
- `--all` flag on `nl find` returns all matches ranked by score
|
|
33
|
+
- `--no-wait` flag to skip auto-waiting
|
|
34
|
+
|
|
5
35
|
## v2.3.4 — 2026-07-07
|
|
6
36
|
|
|
7
37
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavexis
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -138,6 +138,22 @@ class DummyBackend(AbstractBackend):
|
|
|
138
138
|
"""Suggest locator."""
|
|
139
139
|
return selector
|
|
140
140
|
|
|
141
|
+
async def find_by_text(
|
|
142
|
+
self, query: str, all: bool = False
|
|
143
|
+
) -> list[str] | str:
|
|
144
|
+
"""Find by text."""
|
|
145
|
+
return query
|
|
146
|
+
|
|
147
|
+
async def nl_click(self, query: str, auto_wait: bool = True) -> None:
|
|
148
|
+
"""NL click."""
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
async def nl_fill(
|
|
152
|
+
self, query: str, value: str, auto_wait: bool = True
|
|
153
|
+
) -> None:
|
|
154
|
+
"""NL fill."""
|
|
155
|
+
pass
|
|
156
|
+
|
|
141
157
|
async def capture_har(self, params: object) -> dict:
|
|
142
158
|
"""Capture har."""
|
|
143
159
|
return {}
|
|
@@ -588,6 +604,51 @@ class DummyBackend(AbstractBackend):
|
|
|
588
604
|
"""Bluetooth stop."""
|
|
589
605
|
pass
|
|
590
606
|
|
|
607
|
+
async def get_request_body(self, request_id: str) -> str | None:
|
|
608
|
+
"""Get request body."""
|
|
609
|
+
return None
|
|
610
|
+
|
|
611
|
+
async def get_response_body(self, request_id: str) -> str | None:
|
|
612
|
+
"""Get response body."""
|
|
613
|
+
return None
|
|
614
|
+
|
|
615
|
+
async def modify_request(
|
|
616
|
+
self, pattern: dict[str, Any], modifications: dict[str, Any]
|
|
617
|
+
) -> None:
|
|
618
|
+
"""Modify request."""
|
|
619
|
+
pass
|
|
620
|
+
|
|
621
|
+
async def replay_har(self, har_path: str, url_filter: str = "") -> None:
|
|
622
|
+
"""Replay har."""
|
|
623
|
+
pass
|
|
624
|
+
|
|
625
|
+
async def start_combined_trace(
|
|
626
|
+
self,
|
|
627
|
+
capture_screenshots: bool = True,
|
|
628
|
+
capture_network: bool = True,
|
|
629
|
+
capture_console: bool = True,
|
|
630
|
+
) -> str:
|
|
631
|
+
"""Start combined trace."""
|
|
632
|
+
return "trace-dummy"
|
|
633
|
+
|
|
634
|
+
async def stop_combined_trace(self, trace_id: str) -> dict[str, Any]:
|
|
635
|
+
"""Stop combined trace."""
|
|
636
|
+
return {}
|
|
637
|
+
|
|
638
|
+
async def axe_audit(self) -> dict[str, Any]:
|
|
639
|
+
"""Axe audit."""
|
|
640
|
+
return {}
|
|
641
|
+
|
|
642
|
+
async def subscribe_events(
|
|
643
|
+
self, event_types: list[str], callback: Any
|
|
644
|
+
) -> str:
|
|
645
|
+
"""Subscribe events."""
|
|
646
|
+
return "sub-dummy"
|
|
647
|
+
|
|
648
|
+
async def unsubscribe_events(self, subscription_id: str) -> None:
|
|
649
|
+
"""Unsubscribe events."""
|
|
650
|
+
pass
|
|
651
|
+
|
|
591
652
|
|
|
592
653
|
@pytest.mark.unit
|
|
593
654
|
class TestBackendManager:
|
|
@@ -69,6 +69,13 @@ class FakeBackend(AbstractBackend):
|
|
|
69
69
|
async def suggest_locator(self, selector, all=False):
|
|
70
70
|
"""Suggest locator."""
|
|
71
71
|
return selector
|
|
72
|
+
async def find_by_text(self, query, all=False):
|
|
73
|
+
"""Find by text."""
|
|
74
|
+
return query
|
|
75
|
+
async def nl_click(self, query, auto_wait=True):
|
|
76
|
+
"""NL click."""
|
|
77
|
+
async def nl_fill(self, query, value, auto_wait=True):
|
|
78
|
+
"""NL fill."""
|
|
72
79
|
async def capture_har(self, params):
|
|
73
80
|
"""Capture har."""
|
|
74
81
|
async def get_cookies(self):
|
|
@@ -279,6 +286,30 @@ class FakeBackend(AbstractBackend):
|
|
|
279
286
|
"""Shadow click."""
|
|
280
287
|
async def shadow_fill(self, selectors, value, auto_wait=True):
|
|
281
288
|
"""Shadow fill."""
|
|
289
|
+
async def get_request_body(self, request_id):
|
|
290
|
+
"""Get request body."""
|
|
291
|
+
async def get_response_body(self, request_id):
|
|
292
|
+
"""Get response body."""
|
|
293
|
+
async def modify_request(self, pattern, modifications):
|
|
294
|
+
"""Modify request."""
|
|
295
|
+
async def replay_har(self, har_path, url_filter=""):
|
|
296
|
+
"""Replay har."""
|
|
297
|
+
async def start_combined_trace(
|
|
298
|
+
self, capture_screenshots=True, capture_network=True, capture_console=True
|
|
299
|
+
):
|
|
300
|
+
"""Start combined trace."""
|
|
301
|
+
return "trace-fake"
|
|
302
|
+
async def stop_combined_trace(self, trace_id):
|
|
303
|
+
"""Stop combined trace."""
|
|
304
|
+
return {}
|
|
305
|
+
async def axe_audit(self):
|
|
306
|
+
"""Axe audit."""
|
|
307
|
+
return {}
|
|
308
|
+
async def subscribe_events(self, event_types, callback):
|
|
309
|
+
"""Subscribe events."""
|
|
310
|
+
return "sub-fake"
|
|
311
|
+
async def unsubscribe_events(self, subscription_id):
|
|
312
|
+
"""Unsubscribe events."""
|
|
282
313
|
|
|
283
314
|
|
|
284
315
|
class TestBackendManager:
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""Unit tests for v2.4.x network inspection, tracing, axe, and events."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any
|
|
8
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _make_cdp_backend() -> Any:
|
|
14
|
+
"""Create a CDPBackend with mocked session."""
|
|
15
|
+
from wavexis.backend.cdp import CDPBackend
|
|
16
|
+
|
|
17
|
+
backend = CDPBackend()
|
|
18
|
+
backend._session = MagicMock()
|
|
19
|
+
backend._session.runtime = MagicMock()
|
|
20
|
+
backend._session.runtime.evaluate = AsyncMock(
|
|
21
|
+
return_value={"result": {"value": None}}
|
|
22
|
+
)
|
|
23
|
+
backend._session.runtime.enable = AsyncMock()
|
|
24
|
+
backend._session.send = AsyncMock(return_value={})
|
|
25
|
+
backend._session.network = MagicMock()
|
|
26
|
+
backend._session.network.enable = AsyncMock()
|
|
27
|
+
backend._session.page = MagicMock()
|
|
28
|
+
backend._session.page.capture_screenshot = AsyncMock(return_value="")
|
|
29
|
+
return backend
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _make_bidi_backend() -> Any:
|
|
33
|
+
"""Create a BiDiBackend with mocked client."""
|
|
34
|
+
from wavexis.backend.bidi import BiDiBackend
|
|
35
|
+
|
|
36
|
+
backend = BiDiBackend()
|
|
37
|
+
backend._client = MagicMock()
|
|
38
|
+
backend._context = MagicMock()
|
|
39
|
+
backend._client.script = MagicMock()
|
|
40
|
+
result = MagicMock()
|
|
41
|
+
result.value = None
|
|
42
|
+
backend._client.script.evaluate = AsyncMock(return_value=result)
|
|
43
|
+
backend._client.cdp = MagicMock()
|
|
44
|
+
backend._client.cdp.send_command = AsyncMock(return_value={})
|
|
45
|
+
backend._client.cdp.on = MagicMock()
|
|
46
|
+
backend._client.cdp.off = MagicMock()
|
|
47
|
+
return backend
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.unit
|
|
51
|
+
class TestGetRequestBodyCDP:
|
|
52
|
+
"""Tests for get_request_body in CDP backend."""
|
|
53
|
+
|
|
54
|
+
def test_returns_body(self) -> None:
|
|
55
|
+
"""Test that get_request_body returns the post data."""
|
|
56
|
+
backend = _make_cdp_backend()
|
|
57
|
+
backend._session.send = AsyncMock(
|
|
58
|
+
return_value={"postData": '{"key": "value"}'}
|
|
59
|
+
)
|
|
60
|
+
result = asyncio.run(backend.get_request_body("req-123"))
|
|
61
|
+
assert result == '{"key": "value"}'
|
|
62
|
+
|
|
63
|
+
def test_returns_none_on_error(self) -> None:
|
|
64
|
+
"""Test that get_request_body returns None on error."""
|
|
65
|
+
backend = _make_cdp_backend()
|
|
66
|
+
backend._session.send = AsyncMock(side_effect=Exception("fail"))
|
|
67
|
+
result = asyncio.run(backend.get_request_body("req-123"))
|
|
68
|
+
assert result is None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@pytest.mark.unit
|
|
72
|
+
class TestGetResponseBodyCDP:
|
|
73
|
+
"""Tests for get_response_body in CDP backend."""
|
|
74
|
+
|
|
75
|
+
def test_returns_body(self) -> None:
|
|
76
|
+
"""Test that get_response_body returns the body."""
|
|
77
|
+
backend = _make_cdp_backend()
|
|
78
|
+
backend._session.send = AsyncMock(
|
|
79
|
+
return_value={"body": "<html>response</html>"}
|
|
80
|
+
)
|
|
81
|
+
result = asyncio.run(backend.get_response_body("req-123"))
|
|
82
|
+
assert result == "<html>response</html>"
|
|
83
|
+
|
|
84
|
+
def test_returns_none_on_error(self) -> None:
|
|
85
|
+
"""Test that get_response_body returns None on error."""
|
|
86
|
+
backend = _make_cdp_backend()
|
|
87
|
+
backend._session.send = AsyncMock(side_effect=Exception("fail"))
|
|
88
|
+
result = asyncio.run(backend.get_response_body("req-123"))
|
|
89
|
+
assert result is None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.unit
|
|
93
|
+
class TestGetRequestBodyBiDi:
|
|
94
|
+
"""Tests for get_request_body in BiDi backend."""
|
|
95
|
+
|
|
96
|
+
def test_returns_body(self) -> None:
|
|
97
|
+
"""Test that get_request_body returns the post data."""
|
|
98
|
+
backend = _make_bidi_backend()
|
|
99
|
+
backend._client.cdp.send_command = AsyncMock(
|
|
100
|
+
return_value={"postData": '{"key": "value"}'}
|
|
101
|
+
)
|
|
102
|
+
result = asyncio.run(backend.get_request_body("req-123"))
|
|
103
|
+
assert result == '{"key": "value"}'
|
|
104
|
+
|
|
105
|
+
def test_returns_none_on_error(self) -> None:
|
|
106
|
+
"""Test that get_request_body returns None on error."""
|
|
107
|
+
backend = _make_bidi_backend()
|
|
108
|
+
backend._client.cdp.send_command = AsyncMock(
|
|
109
|
+
side_effect=Exception("fail")
|
|
110
|
+
)
|
|
111
|
+
result = asyncio.run(backend.get_request_body("req-123"))
|
|
112
|
+
assert result is None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.unit
|
|
116
|
+
class TestGetResponseBodyBiDi:
|
|
117
|
+
"""Tests for get_response_body in BiDi backend."""
|
|
118
|
+
|
|
119
|
+
def test_returns_body(self) -> None:
|
|
120
|
+
"""Test that get_response_body returns the body."""
|
|
121
|
+
backend = _make_bidi_backend()
|
|
122
|
+
backend._client.cdp.send_command = AsyncMock(
|
|
123
|
+
return_value={"body": "<html>response</html>"}
|
|
124
|
+
)
|
|
125
|
+
result = asyncio.run(backend.get_response_body("req-123"))
|
|
126
|
+
assert result == "<html>response</html>"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.mark.unit
|
|
130
|
+
class TestModifyRequestCDP:
|
|
131
|
+
"""Tests for modify_request in CDP backend."""
|
|
132
|
+
|
|
133
|
+
def test_calls_fetch_enable(self) -> None:
|
|
134
|
+
"""Test that modify_request enables Fetch domain."""
|
|
135
|
+
backend = _make_cdp_backend()
|
|
136
|
+
backend._session.on = MagicMock()
|
|
137
|
+
asyncio.run(
|
|
138
|
+
backend.modify_request(
|
|
139
|
+
{"urlPattern": "*/api/*"},
|
|
140
|
+
{"method": "POST"},
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
backend._session.send.assert_any_call(
|
|
144
|
+
"Fetch.enable",
|
|
145
|
+
{"patterns": [{"urlPattern": "*/api/*"}]},
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@pytest.mark.unit
|
|
150
|
+
class TestReplayHarCDP:
|
|
151
|
+
"""Tests for replay_har in CDP backend."""
|
|
152
|
+
|
|
153
|
+
def test_replays_entries(self, tmp_path: Any) -> None:
|
|
154
|
+
"""Test that replay_har reads and replays HAR entries."""
|
|
155
|
+
backend = _make_cdp_backend()
|
|
156
|
+
har_data = {
|
|
157
|
+
"log": {
|
|
158
|
+
"entries": [
|
|
159
|
+
{
|
|
160
|
+
"request": {
|
|
161
|
+
"url": "https://api.example.com/data",
|
|
162
|
+
"method": "GET",
|
|
163
|
+
"headers": [
|
|
164
|
+
{"name": "Accept", "value": "application/json"}
|
|
165
|
+
],
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
har_path = tmp_path / "test.har"
|
|
172
|
+
har_path.write_text(json.dumps(har_data))
|
|
173
|
+
|
|
174
|
+
asyncio.run(backend.replay_har(str(har_path)))
|
|
175
|
+
backend._session.runtime.evaluate.assert_called_once()
|
|
176
|
+
|
|
177
|
+
def test_filters_by_url(self, tmp_path: Any) -> None:
|
|
178
|
+
"""Test that replay_har filters entries by URL."""
|
|
179
|
+
backend = _make_cdp_backend()
|
|
180
|
+
har_data = {
|
|
181
|
+
"log": {
|
|
182
|
+
"entries": [
|
|
183
|
+
{
|
|
184
|
+
"request": {
|
|
185
|
+
"url": "https://api.example.com/data",
|
|
186
|
+
"method": "GET",
|
|
187
|
+
"headers": [],
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"request": {
|
|
192
|
+
"url": "https://other.example.com/data",
|
|
193
|
+
"method": "GET",
|
|
194
|
+
"headers": [],
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
har_path = tmp_path / "test.har"
|
|
201
|
+
har_path.write_text(json.dumps(har_data))
|
|
202
|
+
|
|
203
|
+
asyncio.run(backend.replay_har(str(har_path), url_filter="api.example"))
|
|
204
|
+
backend._session.runtime.evaluate.assert_called_once()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@pytest.mark.unit
|
|
208
|
+
class TestCombinedTraceCDP:
|
|
209
|
+
"""Tests for start/stop_combined_trace in CDP backend."""
|
|
210
|
+
|
|
211
|
+
def test_start_returns_trace_id(self) -> None:
|
|
212
|
+
"""Test that start_combined_trace returns a trace ID."""
|
|
213
|
+
backend = _make_cdp_backend()
|
|
214
|
+
backend._session.on = MagicMock()
|
|
215
|
+
trace_id = asyncio.run(backend.start_combined_trace())
|
|
216
|
+
assert trace_id.startswith("trace-")
|
|
217
|
+
|
|
218
|
+
def test_stop_returns_data(self) -> None:
|
|
219
|
+
"""Test that stop_combined_trace returns collected data."""
|
|
220
|
+
backend = _make_cdp_backend()
|
|
221
|
+
backend._session.on = MagicMock()
|
|
222
|
+
trace_id = asyncio.run(backend.start_combined_trace())
|
|
223
|
+
result = asyncio.run(backend.stop_combined_trace(trace_id))
|
|
224
|
+
assert "trace_events" in result
|
|
225
|
+
assert "screenshots" in result
|
|
226
|
+
assert "network" in result
|
|
227
|
+
assert "console" in result
|
|
228
|
+
|
|
229
|
+
def test_stop_unknown_trace(self) -> None:
|
|
230
|
+
"""Test that stop_combined_trace returns error for unknown ID."""
|
|
231
|
+
backend = _make_cdp_backend()
|
|
232
|
+
result = asyncio.run(backend.stop_combined_trace("unknown"))
|
|
233
|
+
assert "error" in result
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@pytest.mark.unit
|
|
237
|
+
class TestAxeAuditCDP:
|
|
238
|
+
"""Tests for axe_audit in CDP backend."""
|
|
239
|
+
|
|
240
|
+
def test_returns_violations(self) -> None:
|
|
241
|
+
"""Test that axe_audit returns audit results."""
|
|
242
|
+
backend = _make_cdp_backend()
|
|
243
|
+
audit_result = {
|
|
244
|
+
"violations": [{"id": "color-contrast"}],
|
|
245
|
+
"passes": [],
|
|
246
|
+
"incomplete": [],
|
|
247
|
+
"inapplicable": [],
|
|
248
|
+
}
|
|
249
|
+
backend._session.runtime.evaluate = AsyncMock(
|
|
250
|
+
return_value={"result": {"value": audit_result}}
|
|
251
|
+
)
|
|
252
|
+
result = asyncio.run(backend.axe_audit())
|
|
253
|
+
assert "violations" in result
|
|
254
|
+
assert len(result["violations"]) == 1
|
|
255
|
+
|
|
256
|
+
def test_returns_error_on_failure(self) -> None:
|
|
257
|
+
"""Test that axe_audit returns error dict on failure."""
|
|
258
|
+
backend = _make_cdp_backend()
|
|
259
|
+
backend._session.runtime.evaluate = AsyncMock(
|
|
260
|
+
return_value={"result": {"value": None}}
|
|
261
|
+
)
|
|
262
|
+
result = asyncio.run(backend.axe_audit())
|
|
263
|
+
assert "error" in result
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@pytest.mark.unit
|
|
267
|
+
class TestSubscribeEventsCDP:
|
|
268
|
+
"""Tests for subscribe/unsubscribe_events in CDP backend."""
|
|
269
|
+
|
|
270
|
+
def test_subscribe_returns_id(self) -> None:
|
|
271
|
+
"""Test that subscribe_events returns a subscription ID."""
|
|
272
|
+
backend = _make_cdp_backend()
|
|
273
|
+
backend._session.on = MagicMock()
|
|
274
|
+
sub_id = asyncio.run(
|
|
275
|
+
backend.subscribe_events(["console"], lambda e: None)
|
|
276
|
+
)
|
|
277
|
+
assert sub_id.startswith("sub-")
|
|
278
|
+
|
|
279
|
+
def test_unsubscribe_removes_handlers(self) -> None:
|
|
280
|
+
"""Test that unsubscribe_events removes handlers."""
|
|
281
|
+
backend = _make_cdp_backend()
|
|
282
|
+
backend._session.on = MagicMock()
|
|
283
|
+
backend._session.off = MagicMock()
|
|
284
|
+
sub_id = asyncio.run(
|
|
285
|
+
backend.subscribe_events(["console"], lambda e: None)
|
|
286
|
+
)
|
|
287
|
+
asyncio.run(backend.unsubscribe_events(sub_id))
|
|
288
|
+
backend._session.off.assert_called()
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@pytest.mark.unit
|
|
292
|
+
class TestCombinedTraceBiDi:
|
|
293
|
+
"""Tests for combined trace in BiDi backend."""
|
|
294
|
+
|
|
295
|
+
def test_start_returns_trace_id(self) -> None:
|
|
296
|
+
"""Test that start_combined_trace returns a trace ID."""
|
|
297
|
+
backend = _make_bidi_backend()
|
|
298
|
+
trace_id = asyncio.run(backend.start_combined_trace())
|
|
299
|
+
assert trace_id.startswith("trace-")
|
|
300
|
+
|
|
301
|
+
def test_stop_returns_data(self) -> None:
|
|
302
|
+
"""Test that stop_combined_trace returns collected data."""
|
|
303
|
+
backend = _make_bidi_backend()
|
|
304
|
+
trace_id = asyncio.run(backend.start_combined_trace())
|
|
305
|
+
result = asyncio.run(backend.stop_combined_trace(trace_id))
|
|
306
|
+
assert "trace_events" in result
|
|
307
|
+
assert "network" in result
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@pytest.mark.unit
|
|
311
|
+
class TestAxeAuditBiDi:
|
|
312
|
+
"""Tests for axe_audit in BiDi backend."""
|
|
313
|
+
|
|
314
|
+
def test_returns_violations(self) -> None:
|
|
315
|
+
"""Test that axe_audit returns audit results."""
|
|
316
|
+
backend = _make_bidi_backend()
|
|
317
|
+
audit_result = {
|
|
318
|
+
"violations": [{"id": "color-contrast"}],
|
|
319
|
+
"passes": [],
|
|
320
|
+
"incomplete": [],
|
|
321
|
+
"inapplicable": [],
|
|
322
|
+
}
|
|
323
|
+
result_mock = MagicMock()
|
|
324
|
+
result_mock.value = audit_result
|
|
325
|
+
backend._client.script.evaluate = AsyncMock(return_value=result_mock)
|
|
326
|
+
result = asyncio.run(backend.axe_audit())
|
|
327
|
+
assert "violations" in result
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@pytest.mark.unit
|
|
331
|
+
class TestSubscribeEventsBiDi:
|
|
332
|
+
"""Tests for subscribe_events in BiDi backend."""
|
|
333
|
+
|
|
334
|
+
def test_subscribe_returns_id(self) -> None:
|
|
335
|
+
"""Test that subscribe_events returns a subscription ID."""
|
|
336
|
+
backend = _make_bidi_backend()
|
|
337
|
+
sub_id = asyncio.run(
|
|
338
|
+
backend.subscribe_events(["console"], lambda e: None)
|
|
339
|
+
)
|
|
340
|
+
assert sub_id.startswith("sub-")
|
|
341
|
+
|
|
342
|
+
def test_unsubscribe_removes_handlers(self) -> None:
|
|
343
|
+
"""Test that unsubscribe_events removes handlers."""
|
|
344
|
+
backend = _make_bidi_backend()
|
|
345
|
+
sub_id = asyncio.run(
|
|
346
|
+
backend.subscribe_events(["console"], lambda e: None)
|
|
347
|
+
)
|
|
348
|
+
asyncio.run(backend.unsubscribe_events(sub_id))
|
|
349
|
+
backend._client.cdp.off.assert_called()
|