wavexis 2.0.2__tar.gz → 2.0.4__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.0.2 → wavexis-2.0.4}/.github/workflows/release.yml +32 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/CHANGELOG.md +21 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/PKG-INFO +1 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/pyproject.toml +1 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_global_options.py +28 -34
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_interactions_crawl.py +6 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_new_features.py +3 -2
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/__init__.py +1 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/crawl.py +4 -3
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/eval.py +5 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/form.py +3 -2
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/lighthouse.py +2 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/record.py +3 -2
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/scrape.py +5 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/screencast.py +7 -5
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/session.py +2 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/backend/bidi.py +9 -18
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/backend/cdp.py +13 -14
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/cli/app.py +245 -447
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/plugins.py +1 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/repl.py +9 -4
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/serve.py +1 -1
- {wavexis-2.0.2 → wavexis-2.0.4}/.dockerignore +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/.github/workflows/ci.yml +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/.github/workflows/docs.yml +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/.gitignore +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/CODE_OF_CONDUCT.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/CONTRIBUTING.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/Dockerfile +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/LICENSE +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/README.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/SECURITY.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/api/actions.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/api/backends.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/api/cli.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/api/config.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/api/exceptions.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/api/multi.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/api/plugins.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/api/serve.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/cookbook/auth.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/cookbook/ci-cd.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/cookbook/css.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/cookbook/debug.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/cookbook/record-replay.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/cookbook/scraping.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/cookbook/screenshots.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/cookbook/serve-mode.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/assert.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/backends.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/commands.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/init.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/installation.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/multi.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/perf.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/plugins.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/quickstart.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/raw.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/repl.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/guide/troubleshooting.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/docs/index.md +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/mkdocs.yml +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/stubs/bidiwave/__init__.pyi +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/stubs/cdpwave/__init__.pyi +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/__init__.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/conftest.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/__init__.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_a11y.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_animation.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_backend_selection.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_browser.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_console.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_css.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_debug.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_dialog.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_dom.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_dom_snapshot.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_emulation.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_emulation_advanced.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_eval.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_har.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_input.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_media.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_multi.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_navigate.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_network.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_network_advanced.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_overlay.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_pdf.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_perf.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_permissions.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_raw.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_record.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_scrape.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_screenshot.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_security.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_serve.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_service_worker.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_storage.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_tabs.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_webaudio.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/integration/test_webauthn.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/__init__.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_abstract_backend_phase5.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_actions.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_actions_phase5.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_animation_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_assert_multi.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_auth.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_backend_manager.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_bidi_backend.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_bidi_phase5.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_bluetooth_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_cast_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_cli_phase5.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_config.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_config_phase5.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_cookies_headers_actions.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_css_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_debug_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_dom_snapshot_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_download_screencast.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_emulation_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_error_handling.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_exceptions.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_format_record.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_init.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_input_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_manager.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_media_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_multi.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_multi_improvements.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_output.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_overlay_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_perf_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_perf_console.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_plugins.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_raw.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_record.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_repl.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_serve.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_service_worker_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_storage_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_webaudio_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/tests/unit/test_webauthn_action.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/__main__.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/__init__.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/accessibility.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/animation.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/base.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/bluetooth.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/browser.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/cast.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/console.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/cookies.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/css.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/debug.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/dialog.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/dom.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/dom_snapshot.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/download.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/emulation.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/extract.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/har.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/headers.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/input.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/media.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/multi.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/navigate.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/network.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/overlay.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/pdf.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/performance.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/permissions.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/screenshot.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/security.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/service_worker.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/storage.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/tabs.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/wait.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/webaudio.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/webauthn.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/actions/websocket.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/auth.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/backend/__init__.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/backend/base.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/backend/manager.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/cli/__init__.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/config.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/exceptions.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/init.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/multi.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/output.py +0 -0
- {wavexis-2.0.2 → wavexis-2.0.4}/wavexis/record.py +0 -0
|
@@ -6,7 +6,7 @@ on:
|
|
|
6
6
|
- "v*.*.*"
|
|
7
7
|
|
|
8
8
|
permissions:
|
|
9
|
-
contents:
|
|
9
|
+
contents: write
|
|
10
10
|
packages: write
|
|
11
11
|
|
|
12
12
|
concurrency:
|
|
@@ -76,3 +76,34 @@ jobs:
|
|
|
76
76
|
cache-from: type=gha
|
|
77
77
|
cache-to: type=gha,mode=max
|
|
78
78
|
continue-on-error: true
|
|
79
|
+
|
|
80
|
+
github-release:
|
|
81
|
+
name: Create GitHub Release
|
|
82
|
+
needs: build
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
permissions:
|
|
85
|
+
contents: write
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
- uses: actions/download-artifact@v4
|
|
89
|
+
with:
|
|
90
|
+
name: dist
|
|
91
|
+
path: dist/
|
|
92
|
+
- name: Extract changelog for this version
|
|
93
|
+
id: changelog
|
|
94
|
+
run: |
|
|
95
|
+
version="${GITHUB_REF_NAME#v}"
|
|
96
|
+
awk -v ver="## v${version}" '
|
|
97
|
+
$0 ~ ver { found=1; next }
|
|
98
|
+
/^## v/ && found { exit }
|
|
99
|
+
found { print }
|
|
100
|
+
' CHANGELOG.md > release_notes.md
|
|
101
|
+
- uses: softprops/action-gh-release@v2
|
|
102
|
+
with:
|
|
103
|
+
name: ${{ github.ref_name }}
|
|
104
|
+
body_path: release_notes.md
|
|
105
|
+
draft: false
|
|
106
|
+
prerelease: false
|
|
107
|
+
files: |
|
|
108
|
+
dist/*.whl
|
|
109
|
+
dist/*.tar.gz
|
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to wavexis are documented in this file.
|
|
4
4
|
|
|
5
|
+
## v2.0.4 — 2026-07-07
|
|
6
|
+
|
|
7
|
+
### Refactored
|
|
8
|
+
|
|
9
|
+
- Extracted `_run_async` helper to eliminate ~60 `try/except asyncio.run` boilerplate patterns in `cli/app.py` (−229 lines net)
|
|
10
|
+
- Added `github-release` job to `release.yml` workflow — automatically creates GitHub Release with changelog notes and dist artifacts on tag push
|
|
11
|
+
- Narrowed `contextlib.suppress(Exception)` to `contextlib.suppress(WavexisError, OSError)` in `repl.py`
|
|
12
|
+
- Narrowed `contextlib.suppress(Exception)` to `contextlib.suppress(WavexisError)` in `actions/lighthouse.py`
|
|
13
|
+
- Removed 9 local `import json as _json` in `backend/bidi.py` — uses top-level `json` import
|
|
14
|
+
- Fixed mypy type errors in `cdp.py`, `scrape.py`, `eval.py`, `cli/app.py`
|
|
15
|
+
|
|
16
|
+
## v2.0.3 — 2026-07-06
|
|
17
|
+
|
|
18
|
+
### Refactored
|
|
19
|
+
|
|
20
|
+
- Removed all `# noqa` lint suppressions — converted Typer argument annotations to `Annotated` types (B008), replaced sync file I/O in async functions with `asyncio.to_thread` (ASYNC230, ASYNC240), fixed mutable default argument (B006)
|
|
21
|
+
- Narrowed broad `except Exception` clauses to specific exception types (`WavexisError`, `OSError`, `json.JSONDecodeError`, `KeyError`, `TypeError`, `TimeoutError`, `zipfile.BadZipFile`, `yaml.YAMLError`) across `cli/app.py`, `repl.py`, `serve.py`, `plugins.py`, `actions/record.py`, `actions/form.py`, `actions/crawl.py`, `actions/session.py`, `backend/cdp.py`
|
|
22
|
+
- Replaced 6 module-level mutable globals (`_preferred_backend`, `_verbose`, `_quiet`, `_headless`, `_timeout`, `_proxy`) with `CLIContext` dataclass + `contextvars.ContextVar` for thread-safe state management
|
|
23
|
+
- Removed unused `os` import in `backend/cdp.py` download handler
|
|
24
|
+
- Replaced local `import json as _json` with top-level `json` import in `backend/cdp.py`
|
|
25
|
+
|
|
5
26
|
## v2.0.2 — 2026-07-06
|
|
6
27
|
|
|
7
28
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavexis
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.4
|
|
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
|
|
@@ -15,6 +15,13 @@ pytestmark = pytest.mark.unit
|
|
|
15
15
|
_cli = sys.modules["wavexis.cli.app"]
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
def _fresh_ctx() -> _cli.CLIContext:
|
|
19
|
+
"""Create a fresh CLIContext and set it as the current context."""
|
|
20
|
+
ctx = _cli.CLIContext()
|
|
21
|
+
_cli._ctx.set(ctx)
|
|
22
|
+
return ctx
|
|
23
|
+
|
|
24
|
+
|
|
18
25
|
class TestBrowserOptions:
|
|
19
26
|
"""Tests for BrowserOptions with proxy and timeout fields."""
|
|
20
27
|
|
|
@@ -45,32 +52,27 @@ class TestBrowserOptionsHelper:
|
|
|
45
52
|
"""Tests for _browser_options() CLI helper."""
|
|
46
53
|
|
|
47
54
|
def test_default_options(self) -> None:
|
|
48
|
-
|
|
49
|
-
_cli._timeout = 30000
|
|
50
|
-
_cli._proxy = None
|
|
55
|
+
_fresh_ctx()
|
|
51
56
|
opts = _cli._browser_options()
|
|
52
57
|
assert opts.headless is True
|
|
53
58
|
assert opts.timeout == 30000
|
|
54
59
|
assert opts.proxy is None
|
|
55
60
|
|
|
56
61
|
def test_headed_options(self) -> None:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
_cli._proxy = None
|
|
62
|
+
ctx = _fresh_ctx()
|
|
63
|
+
ctx.headless = False
|
|
60
64
|
opts = _cli._browser_options()
|
|
61
65
|
assert opts.headless is False
|
|
62
66
|
|
|
63
67
|
def test_proxy_options(self) -> None:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
_cli._proxy = "http://proxy:8080"
|
|
68
|
+
ctx = _fresh_ctx()
|
|
69
|
+
ctx.proxy = "http://proxy:8080"
|
|
67
70
|
opts = _cli._browser_options()
|
|
68
71
|
assert opts.proxy == "http://proxy:8080"
|
|
69
72
|
|
|
70
73
|
def test_timeout_options(self) -> None:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
_cli._proxy = None
|
|
74
|
+
ctx = _fresh_ctx()
|
|
75
|
+
ctx.timeout = 60000
|
|
74
76
|
opts = _cli._browser_options()
|
|
75
77
|
assert opts.timeout == 60000
|
|
76
78
|
|
|
@@ -80,15 +82,12 @@ class TestLoadGlobalConfig:
|
|
|
80
82
|
|
|
81
83
|
def test_no_config_file(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
82
84
|
monkeypatch.setattr(Path, "home", lambda: Path("/nonexistent_home_12345"))
|
|
83
|
-
|
|
84
|
-
_cli._headless = True
|
|
85
|
-
_cli._timeout = 30000
|
|
86
|
-
_cli._proxy = None
|
|
85
|
+
ctx = _fresh_ctx()
|
|
87
86
|
_cli._load_global_config()
|
|
88
|
-
assert
|
|
89
|
-
assert
|
|
90
|
-
assert
|
|
91
|
-
assert
|
|
87
|
+
assert ctx.preferred_backend is None
|
|
88
|
+
assert ctx.headless is True
|
|
89
|
+
assert ctx.timeout == 30000
|
|
90
|
+
assert ctx.proxy is None
|
|
92
91
|
|
|
93
92
|
def test_loads_config(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|
94
93
|
config_dir = tmp_path / ".wavexis"
|
|
@@ -100,16 +99,13 @@ class TestLoadGlobalConfig:
|
|
|
100
99
|
)
|
|
101
100
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
102
101
|
|
|
103
|
-
|
|
104
|
-
_cli._headless = True
|
|
105
|
-
_cli._timeout = 30000
|
|
106
|
-
_cli._proxy = None
|
|
102
|
+
ctx = _fresh_ctx()
|
|
107
103
|
_cli._load_global_config()
|
|
108
104
|
|
|
109
|
-
assert
|
|
110
|
-
assert
|
|
111
|
-
assert
|
|
112
|
-
assert
|
|
105
|
+
assert ctx.preferred_backend == "bidi"
|
|
106
|
+
assert ctx.headless is False
|
|
107
|
+
assert ctx.timeout == 60000
|
|
108
|
+
assert ctx.proxy == "http://proxy:9090"
|
|
113
109
|
|
|
114
110
|
def test_cli_overrides_config(
|
|
115
111
|
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
@@ -120,14 +116,12 @@ class TestLoadGlobalConfig:
|
|
|
120
116
|
config_file.write_text("backend: bidi\ntimeout: 60000\n", encoding="utf-8")
|
|
121
117
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
122
118
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
_cli._timeout = 30000
|
|
126
|
-
_cli._proxy = None
|
|
119
|
+
ctx = _fresh_ctx()
|
|
120
|
+
ctx.preferred_backend = "cdp"
|
|
127
121
|
_cli._load_global_config()
|
|
128
122
|
|
|
129
|
-
assert
|
|
130
|
-
assert
|
|
123
|
+
assert ctx.preferred_backend == "cdp"
|
|
124
|
+
assert ctx.timeout == 60000
|
|
131
125
|
|
|
132
126
|
|
|
133
127
|
class TestConfigCommand:
|
|
@@ -9,6 +9,7 @@ import pytest
|
|
|
9
9
|
from wavexis.actions.crawl import CrawlAction, CrawlParams
|
|
10
10
|
from wavexis.actions.input import InputAction
|
|
11
11
|
from wavexis.config import InputParams, WaitStrategy
|
|
12
|
+
from wavexis.exceptions import WavexisError
|
|
12
13
|
|
|
13
14
|
pytestmark = pytest.mark.unit
|
|
14
15
|
|
|
@@ -182,6 +183,8 @@ class TestCrawlAction:
|
|
|
182
183
|
side_effect=[
|
|
183
184
|
"Page 1",
|
|
184
185
|
["https://example.com/about", "https://other.com/page"],
|
|
186
|
+
"About",
|
|
187
|
+
[],
|
|
185
188
|
]
|
|
186
189
|
)
|
|
187
190
|
|
|
@@ -205,6 +208,8 @@ class TestCrawlAction:
|
|
|
205
208
|
side_effect=[
|
|
206
209
|
"Blog",
|
|
207
210
|
["https://example.com/blog/post1", "https://example.com/about"],
|
|
211
|
+
"Blog Post 1",
|
|
212
|
+
[],
|
|
208
213
|
]
|
|
209
214
|
)
|
|
210
215
|
|
|
@@ -242,7 +247,7 @@ class TestCrawlAction:
|
|
|
242
247
|
|
|
243
248
|
async def test_crawl_handles_navigation_error(self) -> None:
|
|
244
249
|
backend = MagicMock()
|
|
245
|
-
backend.navigate = AsyncMock(side_effect=
|
|
250
|
+
backend.navigate = AsyncMock(side_effect=WavexisError("Network error"))
|
|
246
251
|
backend.eval = AsyncMock()
|
|
247
252
|
|
|
248
253
|
params = CrawlParams(
|
|
@@ -20,6 +20,7 @@ from wavexis.actions.session import (
|
|
|
20
20
|
)
|
|
21
21
|
from wavexis.actions.websocket import WebSocketInterceptAction, WebSocketParams
|
|
22
22
|
from wavexis.config import WaitStrategy
|
|
23
|
+
from wavexis.exceptions import WavexisError
|
|
23
24
|
|
|
24
25
|
pytestmark = pytest.mark.unit
|
|
25
26
|
|
|
@@ -68,7 +69,7 @@ class TestSessionSave:
|
|
|
68
69
|
backend.close = AsyncMock()
|
|
69
70
|
backend.get_cookies = AsyncMock(return_value=[])
|
|
70
71
|
backend.storage_list = AsyncMock(side_effect=[{}, {}])
|
|
71
|
-
backend.eval = AsyncMock(side_effect=
|
|
72
|
+
backend.eval = AsyncMock(side_effect=WavexisError("no page"))
|
|
72
73
|
|
|
73
74
|
with tempfile.NamedTemporaryFile(
|
|
74
75
|
suffix=".json", delete=False, mode="w"
|
|
@@ -273,7 +274,7 @@ class TestFormAction:
|
|
|
273
274
|
backend.launch = AsyncMock()
|
|
274
275
|
backend.close = AsyncMock()
|
|
275
276
|
backend.navigate = AsyncMock()
|
|
276
|
-
backend.fill = AsyncMock(side_effect=[None,
|
|
277
|
+
backend.fill = AsyncMock(side_effect=[None, WavexisError("not found")])
|
|
277
278
|
|
|
278
279
|
params = FormParams(
|
|
279
280
|
url="https://app.com/register",
|
|
@@ -11,6 +11,7 @@ from urllib.parse import urljoin, urlparse
|
|
|
11
11
|
from wavexis.actions.base import BaseAction
|
|
12
12
|
from wavexis.backend.base import AbstractBackend
|
|
13
13
|
from wavexis.config import WaitStrategy
|
|
14
|
+
from wavexis.exceptions import WavexisError
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@dataclass
|
|
@@ -63,16 +64,16 @@ class CrawlAction(BaseAction[CrawlParams, list[dict[str, Any]]]):
|
|
|
63
64
|
|
|
64
65
|
try:
|
|
65
66
|
await backend.navigate(url, self.params.wait)
|
|
66
|
-
except
|
|
67
|
+
except WavexisError:
|
|
67
68
|
continue
|
|
68
69
|
|
|
69
70
|
title = ""
|
|
70
71
|
links: list[str] = []
|
|
71
|
-
with contextlib.suppress(
|
|
72
|
+
with contextlib.suppress(WavexisError):
|
|
72
73
|
title = await backend.eval(
|
|
73
74
|
"document.title", await_promise=False
|
|
74
75
|
)
|
|
75
|
-
with contextlib.suppress(
|
|
76
|
+
with contextlib.suppress(WavexisError):
|
|
76
77
|
raw_links = await backend.eval(
|
|
77
78
|
"Array.from(document.querySelectorAll('a[href]')).map(a => a.href)",
|
|
78
79
|
await_promise=False,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
@@ -31,6 +32,9 @@ class EvalAction(BaseAction[EvalParams, Any]):
|
|
|
31
32
|
|
|
32
33
|
expression = params.expression
|
|
33
34
|
if params.file:
|
|
34
|
-
|
|
35
|
+
file_path = params.file
|
|
36
|
+
expression = await asyncio.to_thread(
|
|
37
|
+
lambda: Path(file_path).read_text(encoding="utf-8")
|
|
38
|
+
)
|
|
35
39
|
|
|
36
40
|
return await backend.eval(expression, await_promise=params.await_promise)
|
|
@@ -8,6 +8,7 @@ from typing import Any
|
|
|
8
8
|
from wavexis.actions.base import BaseAction
|
|
9
9
|
from wavexis.backend.base import AbstractBackend
|
|
10
10
|
from wavexis.config import BrowserOptions, WaitStrategy
|
|
11
|
+
from wavexis.exceptions import WavexisError
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
@dataclass
|
|
@@ -52,7 +53,7 @@ class FormAction(BaseAction[FormParams, dict[str, Any]]):
|
|
|
52
53
|
try:
|
|
53
54
|
await backend.fill(selector, value)
|
|
54
55
|
filled += 1
|
|
55
|
-
except
|
|
56
|
+
except WavexisError:
|
|
56
57
|
pass
|
|
57
58
|
|
|
58
59
|
submitted = False
|
|
@@ -60,7 +61,7 @@ class FormAction(BaseAction[FormParams, dict[str, Any]]):
|
|
|
60
61
|
try:
|
|
61
62
|
await backend.click(self.params.submit)
|
|
62
63
|
submitted = True
|
|
63
|
-
except
|
|
64
|
+
except WavexisError:
|
|
64
65
|
pass
|
|
65
66
|
|
|
66
67
|
return {
|
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
|
9
9
|
from wavexis.actions.base import BaseAction
|
|
10
10
|
from wavexis.backend.base import AbstractBackend
|
|
11
11
|
from wavexis.config import BrowserOptions, WaitStrategy
|
|
12
|
+
from wavexis.exceptions import WavexisError
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
@dataclass
|
|
@@ -290,7 +291,7 @@ class LighthouseAction(BaseAction[LighthouseParams, dict[str, Any]]):
|
|
|
290
291
|
})()
|
|
291
292
|
"""
|
|
292
293
|
console_errors: list[dict[str, Any]] = []
|
|
293
|
-
with contextlib.suppress(
|
|
294
|
+
with contextlib.suppress(WavexisError):
|
|
294
295
|
console_errors = await backend.capture_console(level="error")
|
|
295
296
|
|
|
296
297
|
bp = await backend.eval(js, await_promise=False)
|
|
@@ -11,6 +11,7 @@ import yaml
|
|
|
11
11
|
|
|
12
12
|
from wavexis.backend.base import AbstractBackend
|
|
13
13
|
from wavexis.config import BrowserOptions, WaitStrategy
|
|
14
|
+
from wavexis.exceptions import WavexisError
|
|
14
15
|
|
|
15
16
|
_RECORD_SCRIPT = """
|
|
16
17
|
(function() {
|
|
@@ -142,10 +143,10 @@ async def record_session(
|
|
|
142
143
|
events = json.loads(raw)
|
|
143
144
|
elif isinstance(raw, list):
|
|
144
145
|
events = raw
|
|
145
|
-
except
|
|
146
|
+
except (json.JSONDecodeError, TypeError, WavexisError):
|
|
146
147
|
pass
|
|
147
148
|
|
|
148
|
-
with contextlib.suppress(
|
|
149
|
+
with contextlib.suppress(WavexisError):
|
|
149
150
|
await backend.close()
|
|
150
151
|
|
|
151
152
|
return events_to_yaml(events, url)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import asyncio
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
@@ -32,7 +33,10 @@ class ScrapeAction(BaseAction[ScrapeParams, list[dict[str, Any]]]):
|
|
|
32
33
|
expression = params.expression
|
|
33
34
|
|
|
34
35
|
if params.file:
|
|
35
|
-
|
|
36
|
+
file_path = params.file
|
|
37
|
+
expression = await asyncio.to_thread(
|
|
38
|
+
lambda: Path(file_path).read_text(encoding="utf-8")
|
|
39
|
+
)
|
|
36
40
|
|
|
37
41
|
if not expression:
|
|
38
42
|
expression = "document.title"
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import asyncio
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
from wavexis.actions.base import BaseAction
|
|
8
9
|
from wavexis.backend.base import AbstractBackend
|
|
@@ -38,13 +39,14 @@ class ScreencastAction(BaseAction[ScreencastParams, list[str]]):
|
|
|
38
39
|
finally:
|
|
39
40
|
await backend.close()
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
await asyncio.to_thread(
|
|
43
|
+
lambda: Path(self._output_dir).mkdir(parents=True, exist_ok=True)
|
|
44
|
+
)
|
|
42
45
|
saved: list[str] = []
|
|
43
46
|
for i, frame in enumerate(frames):
|
|
44
47
|
ext = "png" if self.params.format == "png" else "jpg"
|
|
45
48
|
fname = f"frame_{i:05d}.{ext}"
|
|
46
|
-
fpath =
|
|
47
|
-
|
|
48
|
-
f.write(frame)
|
|
49
|
+
fpath = str(Path(self._output_dir) / fname)
|
|
50
|
+
await asyncio.to_thread(Path(fpath).write_bytes, frame)
|
|
49
51
|
saved.append(fpath)
|
|
50
52
|
return saved
|
|
@@ -11,6 +11,7 @@ from typing import Any
|
|
|
11
11
|
from wavexis.actions.base import BaseAction
|
|
12
12
|
from wavexis.backend.base import AbstractBackend
|
|
13
13
|
from wavexis.config import CookieParams
|
|
14
|
+
from wavexis.exceptions import WavexisError
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@dataclass
|
|
@@ -69,7 +70,7 @@ class SessionSaveAction(BaseAction[Path, str]):
|
|
|
69
70
|
local_storage = await backend.storage_list("local")
|
|
70
71
|
session_storage = await backend.storage_list("session")
|
|
71
72
|
url = ""
|
|
72
|
-
with contextlib.suppress(
|
|
73
|
+
with contextlib.suppress(WavexisError):
|
|
73
74
|
url = await backend.eval("window.location.href", await_promise=False)
|
|
74
75
|
|
|
75
76
|
data = SessionData(
|
|
@@ -1209,9 +1209,8 @@ class BiDiBackend(AbstractBackend):
|
|
|
1209
1209
|
"})"
|
|
1210
1210
|
)
|
|
1211
1211
|
result = await self._client.script.evaluate(self._context, js)
|
|
1212
|
-
import json as _json
|
|
1213
1212
|
val = result.value if hasattr(result, "value") else result
|
|
1214
|
-
return
|
|
1213
|
+
return json.loads(val) if isinstance(val, str) else dict(val)
|
|
1215
1214
|
|
|
1216
1215
|
async def perf_trace(self, duration_ms: int = 3000) -> dict[str, Any]:
|
|
1217
1216
|
"""Capture a performance trace via CDP Tracing.
|
|
@@ -1346,8 +1345,7 @@ class BiDiBackend(AbstractBackend):
|
|
|
1346
1345
|
val = result.value if hasattr(result, "value") else result
|
|
1347
1346
|
if not val:
|
|
1348
1347
|
raise RuntimeError(f"Element not found: {selector}")
|
|
1349
|
-
|
|
1350
|
-
return _json.loads(val) if isinstance(val, str) else dict(val)
|
|
1348
|
+
return json.loads(val) if isinstance(val, str) else dict(val)
|
|
1351
1349
|
|
|
1352
1350
|
async def css_get_stylesheets(self) -> list[dict[str, Any]]:
|
|
1353
1351
|
"""List all stylesheets in the page via JS.
|
|
@@ -1369,9 +1367,8 @@ class BiDiBackend(AbstractBackend):
|
|
|
1369
1367
|
"}}))"
|
|
1370
1368
|
)
|
|
1371
1369
|
result = await self._client.script.evaluate(self._context, js)
|
|
1372
|
-
import json as _json
|
|
1373
1370
|
val = result.value if hasattr(result, "value") else result
|
|
1374
|
-
return
|
|
1371
|
+
return json.loads(val) if isinstance(val, str) else list(val)
|
|
1375
1372
|
|
|
1376
1373
|
async def css_get_rules(self, stylesheet_id: str) -> list[dict[str, Any]]:
|
|
1377
1374
|
"""Get CSS rules from a stylesheet by index via JS.
|
|
@@ -1396,9 +1393,8 @@ class BiDiBackend(AbstractBackend):
|
|
|
1396
1393
|
f"}})()"
|
|
1397
1394
|
)
|
|
1398
1395
|
result = await self._client.script.evaluate(self._context, js)
|
|
1399
|
-
import json as _json
|
|
1400
1396
|
val = result.value if hasattr(result, "value") else result
|
|
1401
|
-
return
|
|
1397
|
+
return json.loads(val) if isinstance(val, str) else list(val)
|
|
1402
1398
|
|
|
1403
1399
|
async def css_get_computed(self, selector: str) -> dict[str, Any]:
|
|
1404
1400
|
"""Get computed styles for an element via JS getComputedStyle.
|
|
@@ -1429,8 +1425,7 @@ class BiDiBackend(AbstractBackend):
|
|
|
1429
1425
|
val = result.value if hasattr(result, "value") else result
|
|
1430
1426
|
if not val:
|
|
1431
1427
|
raise RuntimeError(f"Element not found: {selector}")
|
|
1432
|
-
|
|
1433
|
-
return _json.loads(val) if isinstance(val, str) else dict(val)
|
|
1428
|
+
return json.loads(val) if isinstance(val, str) else dict(val)
|
|
1434
1429
|
|
|
1435
1430
|
# ── Debugging ──────────────────────────────────────────
|
|
1436
1431
|
|
|
@@ -1721,9 +1716,8 @@ class BiDiBackend(AbstractBackend):
|
|
|
1721
1716
|
"})"
|
|
1722
1717
|
)
|
|
1723
1718
|
result = await self._client.script.evaluate(self._context, js)
|
|
1724
|
-
import json as _json
|
|
1725
1719
|
val = result.value if hasattr(result, "value") else result
|
|
1726
|
-
return
|
|
1720
|
+
return json.loads(val) if isinstance(val, str) else list(val)
|
|
1727
1721
|
|
|
1728
1722
|
async def cache_storage_entries(
|
|
1729
1723
|
self, cache_name: str,
|
|
@@ -1749,9 +1743,8 @@ class BiDiBackend(AbstractBackend):
|
|
|
1749
1743
|
f"}})"
|
|
1750
1744
|
)
|
|
1751
1745
|
result = await self._client.script.evaluate(self._context, js)
|
|
1752
|
-
import json as _json
|
|
1753
1746
|
val = result.value if hasattr(result, "value") else result
|
|
1754
|
-
return
|
|
1747
|
+
return json.loads(val) if isinstance(val, str) else list(val)
|
|
1755
1748
|
|
|
1756
1749
|
async def cache_storage_delete(self, cache_name: str) -> None:
|
|
1757
1750
|
"""Delete a cache by name via JS Cache API.
|
|
@@ -1845,9 +1838,8 @@ class BiDiBackend(AbstractBackend):
|
|
|
1845
1838
|
"})"
|
|
1846
1839
|
)
|
|
1847
1840
|
result = await self._client.script.evaluate(self._context, js)
|
|
1848
|
-
import json as _json
|
|
1849
1841
|
val = result.value if hasattr(result, "value") else result
|
|
1850
|
-
return
|
|
1842
|
+
return json.loads(val) if isinstance(val, str) else list(val)
|
|
1851
1843
|
|
|
1852
1844
|
async def sw_unregister(self, registration_id: str) -> None:
|
|
1853
1845
|
"""Unregister a service worker by scope via JS.
|
|
@@ -1910,9 +1902,8 @@ class BiDiBackend(AbstractBackend):
|
|
|
1910
1902
|
"})"
|
|
1911
1903
|
)
|
|
1912
1904
|
result = await self._client.script.evaluate(self._context, js)
|
|
1913
|
-
import json as _json
|
|
1914
1905
|
val = result.value if hasattr(result, "value") else result
|
|
1915
|
-
return
|
|
1906
|
+
return json.loads(val) if isinstance(val, str) else list(val)
|
|
1916
1907
|
|
|
1917
1908
|
async def animation_pause(self, animation_id: str) -> None:
|
|
1918
1909
|
"""Pause an animation by index via JS.
|
|
@@ -4,7 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import base64
|
|
7
|
+
import json
|
|
7
8
|
import time
|
|
9
|
+
from pathlib import Path
|
|
8
10
|
from typing import Any
|
|
9
11
|
|
|
10
12
|
from wavexis.backend.base import AbstractBackend
|
|
@@ -1267,11 +1269,10 @@ class CDPBackend(AbstractBackend):
|
|
|
1267
1269
|
"""
|
|
1268
1270
|
if self._session is None:
|
|
1269
1271
|
raise NavigationError("", "Session not initialized.")
|
|
1270
|
-
import json as _json
|
|
1271
1272
|
|
|
1272
1273
|
body = response.get("body", "")
|
|
1273
1274
|
if isinstance(body, (dict, list)):
|
|
1274
|
-
body =
|
|
1275
|
+
body = json.dumps(body)
|
|
1275
1276
|
body_b64 = base64.b64encode(body.encode("utf-8")).decode("ascii")
|
|
1276
1277
|
|
|
1277
1278
|
fulfilled: list[bool] = [False]
|
|
@@ -1374,7 +1375,6 @@ class CDPBackend(AbstractBackend):
|
|
|
1374
1375
|
"""
|
|
1375
1376
|
if self._session is None:
|
|
1376
1377
|
raise NavigationError("", "Session not initialized.")
|
|
1377
|
-
import os
|
|
1378
1378
|
import tempfile
|
|
1379
1379
|
|
|
1380
1380
|
download_dir = tempfile.mkdtemp()
|
|
@@ -1385,11 +1385,12 @@ class CDPBackend(AbstractBackend):
|
|
|
1385
1385
|
|
|
1386
1386
|
await asyncio.sleep(2)
|
|
1387
1387
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1388
|
+
def _list_files() -> list[Path]:
|
|
1389
|
+
return list(Path(download_dir).iterdir())
|
|
1390
|
+
|
|
1391
|
+
for fpath in await asyncio.to_thread(_list_files):
|
|
1392
|
+
if await asyncio.to_thread(Path(fpath).is_file):
|
|
1393
|
+
return await asyncio.to_thread(Path(fpath).read_bytes)
|
|
1393
1394
|
|
|
1394
1395
|
return b""
|
|
1395
1396
|
|
|
@@ -1585,10 +1586,8 @@ class CDPBackend(AbstractBackend):
|
|
|
1585
1586
|
zf = zipfile.ZipFile(io.BytesIO(raw))
|
|
1586
1587
|
for name in zf.namelist():
|
|
1587
1588
|
content = zf.read(name).decode("utf-8", errors="replace")
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
trace_events.extend(_json.loads(content).get("traceEvents", []))
|
|
1591
|
-
except Exception:
|
|
1589
|
+
trace_events.extend(json.loads(content).get("traceEvents", []))
|
|
1590
|
+
except (zipfile.BadZipFile, json.JSONDecodeError, KeyError, ValueError):
|
|
1592
1591
|
trace_events.append({"raw_size": len(raw)})
|
|
1593
1592
|
else:
|
|
1594
1593
|
trace_events.append({"error": "No stream handle in tracingComplete"})
|
|
@@ -2323,7 +2322,7 @@ class CDPBackend(AbstractBackend):
|
|
|
2323
2322
|
)
|
|
2324
2323
|
for ev in events:
|
|
2325
2324
|
players.append(dict(ev.get("player", ev)))
|
|
2326
|
-
except
|
|
2325
|
+
except (TimeoutError, KeyError, TypeError):
|
|
2327
2326
|
pass
|
|
2328
2327
|
return players
|
|
2329
2328
|
|
|
@@ -2340,7 +2339,7 @@ class CDPBackend(AbstractBackend):
|
|
|
2340
2339
|
for ev in events:
|
|
2341
2340
|
if ev.get("playerId") == player_id:
|
|
2342
2341
|
messages.append(dict(ev))
|
|
2343
|
-
except
|
|
2342
|
+
except (TimeoutError, KeyError, TypeError):
|
|
2344
2343
|
pass
|
|
2345
2344
|
return messages
|
|
2346
2345
|
|