wavexis 2.2.1__tar.gz → 2.2.2__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.1 → wavexis-2.2.2}/CHANGELOG.md +7 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/PKG-INFO +1 -1
- {wavexis-2.2.1 → wavexis-2.2.2}/pyproject.toml +1 -1
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/__init__.py +1 -1
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/backend/bidi.py +22 -9
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/backend/cdp.py +20 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/.dockerignore +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/.github/workflows/ci.yml +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/.github/workflows/docs.yml +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/.github/workflows/release.yml +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/.gitignore +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/CODE_OF_CONDUCT.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/CONTRIBUTING.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/Dockerfile +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/LICENSE +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/README.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/SECURITY.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/api/actions.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/api/backends.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/api/cli.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/api/config.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/api/exceptions.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/api/multi.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/api/plugins.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/api/serve.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/cookbook/auth.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/cookbook/ci-cd.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/cookbook/css.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/cookbook/debug.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/cookbook/record-replay.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/cookbook/scraping.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/cookbook/screenshots.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/cookbook/serve-mode.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/assert.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/backends.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/commands.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/init.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/installation.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/multi.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/perf.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/plugins.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/quickstart.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/raw.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/repl.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/guide/troubleshooting.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/docs/index.md +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/mkdocs.yml +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/stubs/bidiwave/__init__.pyi +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/stubs/cdpwave/__init__.pyi +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/__init__.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/conftest.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/__init__.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_a11y.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_animation.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_backend_selection.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_browser.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_console.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_css.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_debug.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_dialog.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_dom.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_dom_snapshot.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_emulation.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_emulation_advanced.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_eval.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_har.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_input.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_media.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_multi.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_navigate.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_network.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_network_advanced.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_overlay.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_pdf.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_perf.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_permissions.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_raw.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_record.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_scrape.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_screenshot.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_security.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_serve.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_service_worker.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_storage.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_tabs.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_webaudio.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/integration/test_webauthn.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/__init__.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_abstract_backend_phase5.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_actions.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_actions_phase5.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_animation_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_assert_multi.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_auth.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_backend_manager.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_bidi_backend.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_bidi_phase5.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_bluetooth_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_cast_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_cli_phase5.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_config.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_config_phase5.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_cookies_headers_actions.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_css_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_debug_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_dom_snapshot_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_download_screencast.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_emulation_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_error_handling.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_exceptions.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_format_record.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_global_options.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_init.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_input_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_interactions_crawl.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_manager.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_media_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_multi.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_multi_improvements.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_new_features.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_output.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_overlay_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_perf_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_perf_console.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_plugins.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_raw.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_record.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_repl.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_serve.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_service_worker_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_storage_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_webaudio_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/tests/unit/test_webauthn_action.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/__main__.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/__init__.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/accessibility.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/animation.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/base.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/bluetooth.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/browser.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/cast.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/console.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/cookies.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/crawl.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/css.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/debug.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/dialog.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/dom.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/dom_snapshot.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/download.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/emulation.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/eval.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/extract.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/form.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/har.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/headers.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/input.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/lighthouse.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/media.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/multi.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/navigate.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/network.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/overlay.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/pdf.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/performance.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/permissions.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/record.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/scrape.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/screencast.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/screenshot.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/security.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/service_worker.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/session.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/storage.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/tabs.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/wait.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/webaudio.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/webauthn.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/actions/websocket.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/auth.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/backend/__init__.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/backend/base.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/backend/manager.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/__init__.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_advanced.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_capture.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_config.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_debug.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_emulation.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_experimental.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_input.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_navigation.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_network.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_perf.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_serve.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_session.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_shared.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/_workflow.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/cli/app.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/config.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/exceptions.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/init.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/multi.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/output.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/plugins.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/record.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/repl.py +0 -0
- {wavexis-2.2.1 → wavexis-2.2.2}/wavexis/serve.py +0 -0
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to wavexis are documented in this file.
|
|
4
4
|
|
|
5
|
+
## v2.2.2 — 2026-07-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Auto scroll-into-view before `click`, `fill`, and `hover` in both CDP and BiDi backends
|
|
10
|
+
- `_scroll_into_view_if_needed` helper checks if element is in viewport and scrolls if needed
|
|
11
|
+
|
|
5
12
|
## v2.2.1 — 2026-07-07
|
|
6
13
|
|
|
7
14
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wavexis
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
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
|
|
@@ -807,19 +807,32 @@ class BiDiBackend(AbstractBackend):
|
|
|
807
807
|
|
|
808
808
|
# ── Input ──────────────────────────────────────────────
|
|
809
809
|
|
|
810
|
+
async def _scroll_into_view_if_needed(self, selector: str) -> None:
|
|
811
|
+
"""Scroll element into view if it's not visible in the viewport."""
|
|
812
|
+
client = self._require_client()
|
|
813
|
+
escaped = selector.replace("'", "\\'")
|
|
814
|
+
js = (
|
|
815
|
+
f"(function(){{var el=document.querySelector('{escaped}');"
|
|
816
|
+
f"if(!el)return;var rect=el.getBoundingClientRect();"
|
|
817
|
+
f"if(rect.top<0||rect.bottom>window.innerHeight||"
|
|
818
|
+
f"rect.left<0||rect.right>window.innerWidth)"
|
|
819
|
+
f"el.scrollIntoView({{block:'center',behavior:'instant'}});}})()"
|
|
820
|
+
)
|
|
821
|
+
await client.script.evaluate(self._context, js)
|
|
822
|
+
|
|
810
823
|
async def click(
|
|
811
824
|
self, selector: str, button: str = "left", click_count: int = 1
|
|
812
825
|
) -> None:
|
|
813
826
|
"""Click an element via BiDi script.evaluate."""
|
|
814
|
-
|
|
815
|
-
|
|
827
|
+
client = self._require_client()
|
|
828
|
+
await self._scroll_into_view_if_needed(selector)
|
|
816
829
|
escaped = selector.replace("'", "\\'")
|
|
817
830
|
js = (
|
|
818
831
|
f"document.querySelector('{escaped}')"
|
|
819
832
|
f".dispatchEvent(new MouseEvent('click',{{bubbles:true}}))"
|
|
820
833
|
)
|
|
821
834
|
for _ in range(click_count):
|
|
822
|
-
await
|
|
835
|
+
await client.script.evaluate(self._context, js)
|
|
823
836
|
|
|
824
837
|
async def type_text(self, selector: str, text: str, delay: int = 0) -> None:
|
|
825
838
|
"""Type text into an element via BiDi."""
|
|
@@ -843,12 +856,12 @@ class BiDiBackend(AbstractBackend):
|
|
|
843
856
|
|
|
844
857
|
async def fill(self, selector: str, value: str) -> None:
|
|
845
858
|
"""Fill an input element with a value via BiDi."""
|
|
846
|
-
|
|
847
|
-
|
|
859
|
+
client = self._require_client()
|
|
860
|
+
await self._scroll_into_view_if_needed(selector)
|
|
848
861
|
escaped = selector.replace("'", "\\'")
|
|
849
862
|
escaped_val = value.replace("\\", "\\\\").replace("'", "\\'")
|
|
850
863
|
js = f"document.querySelector('{escaped}').value = '{escaped_val}'"
|
|
851
|
-
await
|
|
864
|
+
await client.script.evaluate(self._context, js)
|
|
852
865
|
|
|
853
866
|
async def select_option(self, selector: str, value: str) -> None:
|
|
854
867
|
"""Select an option in a <select> element by value via BiDi."""
|
|
@@ -861,15 +874,15 @@ class BiDiBackend(AbstractBackend):
|
|
|
861
874
|
|
|
862
875
|
async def hover(self, selector: str) -> None:
|
|
863
876
|
"""Hover over an element via BiDi script.evaluate."""
|
|
864
|
-
|
|
865
|
-
|
|
877
|
+
client = self._require_client()
|
|
878
|
+
await self._scroll_into_view_if_needed(selector)
|
|
866
879
|
escaped = selector.replace("'", "\\'")
|
|
867
880
|
js = (
|
|
868
881
|
f"var el=document.querySelector('{escaped}');"
|
|
869
882
|
f"el.dispatchEvent(new MouseEvent('mouseover',{{bubbles:true}}));"
|
|
870
883
|
f"el.dispatchEvent(new MouseEvent('mousemove',{{bubbles:true}}))"
|
|
871
884
|
)
|
|
872
|
-
await
|
|
885
|
+
await client.script.evaluate(self._context, js)
|
|
873
886
|
|
|
874
887
|
async def key_press(self, key: str) -> None:
|
|
875
888
|
"""Press a keyboard key via BiDi script.evaluate."""
|
|
@@ -1026,6 +1026,23 @@ class CDPBackend(AbstractBackend):
|
|
|
1026
1026
|
cy = (min(ys) + max(ys)) / 2
|
|
1027
1027
|
return cx, cy
|
|
1028
1028
|
|
|
1029
|
+
async def _scroll_into_view_if_needed(self, selector: str) -> None:
|
|
1030
|
+
"""Scroll element into view if it's not visible in the viewport.
|
|
1031
|
+
|
|
1032
|
+
Args:
|
|
1033
|
+
selector: CSS selector for the target element.
|
|
1034
|
+
"""
|
|
1035
|
+
session = self._require_session()
|
|
1036
|
+
escaped = selector.replace("'", "\\'")
|
|
1037
|
+
js = (
|
|
1038
|
+
f"(function(){{var el=document.querySelector('{escaped}');"
|
|
1039
|
+
f"if(!el)return;var rect=el.getBoundingClientRect();"
|
|
1040
|
+
f"if(rect.top<0||rect.bottom>window.innerHeight||"
|
|
1041
|
+
f"rect.left<0||rect.right>window.innerWidth)"
|
|
1042
|
+
f"el.scrollIntoView({{block:'center',behavior:'instant'}});}})()"
|
|
1043
|
+
)
|
|
1044
|
+
await session.runtime.evaluate(js)
|
|
1045
|
+
|
|
1029
1046
|
async def click(
|
|
1030
1047
|
self, selector: str, button: str = "left", click_count: int = 1
|
|
1031
1048
|
) -> None:
|
|
@@ -1037,6 +1054,7 @@ class CDPBackend(AbstractBackend):
|
|
|
1037
1054
|
click_count: Number of clicks to dispatch.
|
|
1038
1055
|
"""
|
|
1039
1056
|
session = self._require_session()
|
|
1057
|
+
await self._scroll_into_view_if_needed(selector)
|
|
1040
1058
|
x, y = await self._get_box_center(selector)
|
|
1041
1059
|
btn_map = {"left": "left", "right": "right", "middle": "middle"}
|
|
1042
1060
|
btn = btn_map.get(button, "left")
|
|
@@ -1074,6 +1092,7 @@ class CDPBackend(AbstractBackend):
|
|
|
1074
1092
|
value: Value to set in the input field.
|
|
1075
1093
|
"""
|
|
1076
1094
|
session = self._require_session()
|
|
1095
|
+
await self._scroll_into_view_if_needed(selector)
|
|
1077
1096
|
escaped = selector.replace("'", "\\'")
|
|
1078
1097
|
js = (
|
|
1079
1098
|
f"(function(){{var el=document.querySelector('{escaped}');"
|
|
@@ -1113,6 +1132,7 @@ class CDPBackend(AbstractBackend):
|
|
|
1113
1132
|
selector: CSS selector for the target element.
|
|
1114
1133
|
"""
|
|
1115
1134
|
session = self._require_session()
|
|
1135
|
+
await self._scroll_into_view_if_needed(selector)
|
|
1116
1136
|
x, y = await self._get_box_center(selector)
|
|
1117
1137
|
await session.input.dispatch_mouse_event(
|
|
1118
1138
|
type_="mouseMoved", x=x, y=y
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|