visus-web 0.2.0__tar.gz → 0.3.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.
- {visus_web-0.2.0 → visus_web-0.3.2/.claude}/skills/developing-visus-web/SKILL.md +54 -5
- visus_web-0.3.2/.github/dependabot.yml +32 -0
- visus_web-0.3.2/.github/workflows/ci.yml +171 -0
- visus_web-0.3.2/.github/workflows/publish.yml +109 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/.gitignore +14 -1
- visus_web-0.3.2/.pre-commit-config.yaml +30 -0
- visus_web-0.3.2/CONTRIBUTING.md +74 -0
- visus_web-0.2.0/README.md → visus_web-0.3.2/PKG-INFO +223 -34
- visus_web-0.2.0/PKG-INFO → visus_web-0.3.2/README.md +160 -73
- visus_web-0.3.2/RELEASING.md +92 -0
- visus_web-0.3.2/SECURITY.md +41 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/TESTING.md +41 -5
- visus_web-0.3.2/coverage-floors.json +13 -0
- visus_web-0.3.2/docs/agent-skill.md +41 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/mkdocs.yml +1 -0
- visus_web-0.3.2/page.pdf +0 -0
- visus_web-0.3.2/page.png +0 -0
- visus_web-0.3.2/pyproject.toml +211 -0
- visus_web-0.3.2/scripts/check_coverage.py +177 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/__init__.py +38 -3
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/assertions.py +41 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/browser.py +9 -0
- visus_web-0.3.2/src/visus/web/api/devices.py +43 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/fields.py +8 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/input.py +15 -12
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/locator.py +55 -4
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/page.py +124 -19
- visus_web-0.3.2/src/visus/web/api/route.py +129 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/async_api/__init__.py +107 -50
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/base.py +20 -6
- visus_web-0.3.2/src/visus/web/backends/browsers/chrome.py +112 -0
- visus_web-0.3.2/src/visus/web/backends/browsers/edge.py +85 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/browsers/edge_ie.py +35 -2
- visus_web-0.3.2/src/visus/web/backends/browsers/firefox.py +77 -0
- visus_web-0.3.2/src/visus/web/backends/selenium/cdp_fetch.py +242 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/driver_delegate.py +162 -20
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/js/bundle.js +171 -25
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium_backend.py +80 -4
- visus_web-0.3.2/src/visus/web/cli/__main__.py +6 -0
- visus_web-0.3.2/src/visus/web/cli/_entry.py +62 -0
- visus_web-0.3.2/src/visus/web/cli/codegen.py +206 -0
- visus_web-0.3.2/src/visus/web/cli/compact.py +69 -0
- visus_web-0.3.2/src/visus/web/cli/config.py +155 -0
- visus_web-0.3.2/src/visus/web/cli/console.py +813 -0
- visus_web-0.3.2/src/visus/web/cli/fleet.py +63 -0
- visus_web-0.3.2/src/visus/web/cli/main.py +3007 -0
- visus_web-0.3.2/src/visus/web/cli/output.py +158 -0
- visus_web-0.3.2/src/visus/web/cli/session_client.py +818 -0
- visus_web-0.3.2/src/visus/web/cli/session_server.py +2584 -0
- visus_web-0.3.2/src/visus/web/cli/skill_check.py +97 -0
- visus_web-0.3.2/src/visus/web/cli/skills.py +100 -0
- visus_web-0.3.2/src/visus/web/mcp/server.py +1609 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/mcp/session.py +73 -21
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/network.py +25 -0
- visus_web-0.3.2/src/visus/web/observability/report.py +708 -0
- visus_web-0.3.2/src/visus/web/observability/video.py +65 -0
- visus_web-0.3.2/src/visus/web/ops/__init__.py +12 -0
- visus_web-0.3.2/src/visus/web/ops/captcha.py +371 -0
- visus_web-0.3.2/src/visus/web/ops/dates.py +625 -0
- visus_web-0.3.2/src/visus/web/ops/registry.py +136 -0
- visus_web-0.3.2/src/visus/web/ops/remote.py +86 -0
- visus_web-0.3.2/src/visus/web/ops/targeting.py +125 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/SKILL.md +285 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-commands.md +186 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-json-and-piping.md +95 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-repl.md +143 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-session.md +110 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-targeting.md +146 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/library-api.md +259 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/mcp-tools.md +120 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/pytest-plugin.md +58 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/tracing-reports.md +96 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/troubleshooting.md +53 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/vision.md +57 -0
- visus_web-0.3.2/src/visus/web/skills/visus-web/references/windows-shell.md +78 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/tracing.py +81 -0
- visus_web-0.3.2/src/visus/web/vision/__init__.py +15 -0
- visus_web-0.3.2/src/visus/web/vision/ocr.py +116 -0
- visus_web-0.3.2/tests/conftest.py +157 -0
- visus_web-0.3.2/tests/fixtures/agent_page.html +17 -0
- visus_web-0.3.2/tests/fixtures/captcha.html +17 -0
- visus_web-0.3.2/tests/fixtures/captcha_turnstile.html +16 -0
- visus_web-0.3.2/tests/fixtures/captcha_v3.html +20 -0
- visus_web-0.3.2/tests/fixtures/clickables.html +33 -0
- visus_web-0.3.2/tests/fixtures/datepicker.html +226 -0
- visus_web-0.3.2/tests/fixtures/datepicker_form.html +37 -0
- visus_web-0.3.2/tests/fixtures/delayed.html +16 -0
- visus_web-0.3.2/tests/fixtures/dropzone.html +26 -0
- visus_web-0.3.2/tests/fixtures/duplicates.html +18 -0
- visus_web-0.3.2/tests/fixtures/hidden_duplicates.html +12 -0
- visus_web-0.3.2/tests/fixtures/imagetarget.html +47 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/input.html +4 -1
- visus_web-0.3.2/tests/fixtures/links.html +8 -0
- visus_web-0.3.2/tests/fixtures/mouse.html +38 -0
- visus_web-0.3.2/tests/fixtures/nullvsempty.html +17 -0
- visus_web-0.3.2/tests/fixtures/route_fetch.html +32 -0
- visus_web-0.3.2/tests/fixtures/shadow_click.html +15 -0
- visus_web-0.3.2/tests/fixtures/snapshot_tree.html +22 -0
- visus_web-0.3.2/tests/fixtures/stale_a.html +24 -0
- visus_web-0.3.2/tests/fixtures/stale_b.html +18 -0
- visus_web-0.3.2/tests/fixtures/storage.html +17 -0
- visus_web-0.3.2/tests/fixtures/upload_single.html +20 -0
- visus_web-0.3.2/tests/fixtures/visible_text.html +11 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_async_api_unit.py +28 -6
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_backend_protocols.py +15 -5
- visus_web-0.3.2/tests/test_browsers_chrome.py +56 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_browsers_edge.py +12 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_browsers_firefox.py +16 -0
- visus_web-0.3.2/tests/test_captcha_service.py +266 -0
- visus_web-0.3.2/tests/test_ci_workflows.py +158 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_cli.py +93 -2
- visus_web-0.3.2/tests/test_cli_agent_loop.py +417 -0
- visus_web-0.3.2/tests/test_cli_agent_matrix.py +144 -0
- visus_web-0.3.2/tests/test_cli_attach.py +384 -0
- visus_web-0.3.2/tests/test_cli_batch.py +87 -0
- visus_web-0.3.2/tests/test_cli_compact.py +155 -0
- visus_web-0.3.2/tests/test_cli_config.py +228 -0
- visus_web-0.3.2/tests/test_cli_devtools.py +448 -0
- visus_web-0.3.2/tests/test_cli_drop_upload.py +294 -0
- visus_web-0.3.2/tests/test_cli_e2e_practice_login.py +105 -0
- visus_web-0.3.2/tests/test_cli_emulation.py +217 -0
- visus_web-0.3.2/tests/test_cli_fleet.py +57 -0
- visus_web-0.3.2/tests/test_cli_index_resolution.py +114 -0
- visus_web-0.3.2/tests/test_cli_input_devices.py +506 -0
- visus_web-0.3.2/tests/test_cli_nth.py +109 -0
- visus_web-0.3.2/tests/test_cli_output.py +159 -0
- visus_web-0.3.2/tests/test_cli_page_ops.py +1282 -0
- visus_web-0.3.2/tests/test_cli_positional_targets.py +225 -0
- visus_web-0.3.2/tests/test_cli_repl_parity.py +189 -0
- visus_web-0.3.2/tests/test_cli_route.py +257 -0
- visus_web-0.3.2/tests/test_cli_run_code.py +289 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_cli_session.py +1859 -195
- visus_web-0.3.2/tests/test_cli_session_flag_placement.py +77 -0
- visus_web-0.3.2/tests/test_cli_sessions_named.py +678 -0
- visus_web-0.3.2/tests/test_cli_skills.py +338 -0
- visus_web-0.3.2/tests/test_cli_tabs_state.py +337 -0
- visus_web-0.3.2/tests/test_cli_tracing.py +253 -0
- visus_web-0.3.2/tests/test_cli_visible_text.py +30 -0
- visus_web-0.3.2/tests/test_cli_vision_targeting.py +247 -0
- visus_web-0.3.2/tests/test_cli_wait.py +49 -0
- visus_web-0.3.2/tests/test_codegen.py +199 -0
- visus_web-0.3.2/tests/test_console_scripts.py +47 -0
- visus_web-0.3.2/tests/test_coverage_floors.py +222 -0
- visus_web-0.3.2/tests/test_daemon_command_override.py +67 -0
- visus_web-0.3.2/tests/test_download_partial_files.py +82 -0
- visus_web-0.3.2/tests/test_driver_handle_inheritance.py +111 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_actions_more.py +32 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_cli_session.py +49 -4
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_contexts.py +24 -0
- visus_web-0.3.2/tests/test_e2e_expect.py +124 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_fields.py +84 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_mcp.py +40 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_observability.py +38 -1
- visus_web-0.3.2/tests/test_http_site_e2e.py +26 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_input_wiring.py +12 -12
- visus_web-0.3.2/tests/test_installed_entry_points.py +167 -0
- visus_web-0.3.2/tests/test_list_clickables.py +71 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_locator_steps.py +19 -0
- visus_web-0.3.2/tests/test_mcp_attach.py +168 -0
- visus_web-0.3.2/tests/test_mcp_index.py +264 -0
- visus_web-0.3.2/tests/test_mcp_nth.py +53 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_mcp_server.py +59 -0
- visus_web-0.3.2/tests/test_mcp_tracing.py +88 -0
- visus_web-0.3.2/tests/test_no_console_window.py +178 -0
- visus_web-0.3.2/tests/test_ops_registry.py +88 -0
- visus_web-0.3.2/tests/test_ops_remote.py +98 -0
- visus_web-0.3.2/tests/test_ops_targeting.py +137 -0
- visus_web-0.3.2/tests/test_pick_date.py +220 -0
- visus_web-0.3.2/tests/test_pytest_config.py +119 -0
- visus_web-0.3.2/tests/test_repo_hygiene.py +152 -0
- visus_web-0.3.2/tests/test_repo_metadata.py +185 -0
- visus_web-0.3.2/tests/test_report_copy.py +84 -0
- visus_web-0.3.2/tests/test_route.py +203 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_selenium_backend_protocol.py +1 -1
- visus_web-0.3.2/tests/test_skill_check_binary_rename.py +77 -0
- visus_web-0.3.2/tests/test_skill_commands_parse.py +132 -0
- visus_web-0.3.2/tests/test_skill_docs.py +271 -0
- visus_web-0.3.2/tests/test_unique_locators.py +58 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_vision.py +51 -0
- visus_web-0.3.2/tests/test_window_maximized.py +50 -0
- visus_web-0.2.0/.github/workflows/publish.yml +0 -51
- visus_web-0.2.0/pyproject.toml +0 -102
- visus_web-0.2.0/skills/using-visus-web/SKILL.md +0 -161
- visus_web-0.2.0/src/visus/web/backends/browsers/chrome.py +0 -45
- visus_web-0.2.0/src/visus/web/backends/browsers/edge.py +0 -45
- visus_web-0.2.0/src/visus/web/backends/browsers/firefox.py +0 -30
- visus_web-0.2.0/src/visus/web/cli/codegen.py +0 -86
- visus_web-0.2.0/src/visus/web/cli/console.py +0 -249
- visus_web-0.2.0/src/visus/web/cli/main.py +0 -906
- visus_web-0.2.0/src/visus/web/cli/session_client.py +0 -231
- visus_web-0.2.0/src/visus/web/cli/session_server.py +0 -777
- visus_web-0.2.0/src/visus/web/mcp/server.py +0 -761
- visus_web-0.2.0/src/visus/web/observability/report.py +0 -689
- visus_web-0.2.0/src/visus/web/vision/__init__.py +0 -6
- visus_web-0.2.0/src/visus/web/vision/ocr.py +0 -46
- visus_web-0.2.0/tests/conftest.py +0 -56
- visus_web-0.2.0/tests/test_browsers_chrome.py +0 -22
- visus_web-0.2.0/tests/test_codegen.py +0 -93
- visus_web-0.2.0/tests/test_e2e_expect.py +0 -59
- {visus_web-0.2.0 → visus_web-0.3.2}/LICENSE +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/NOTICE +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/examples/demo_rpa.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/examples/dolar.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/examples/login.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/examples/login_mcp.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/examples/login_rpa.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/__init__.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/_htmlsel.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/_steps.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/context.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/events.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/frame_locator.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/__init__.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/browsers/__init__.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/browsers/_ie_preflight.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/__init__.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/_diagnostics.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/actionability.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/expect_engine.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/js/__init__.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/resolver.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/cli/__init__.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/config.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/engine.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/errors.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/mcp/__init__.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/observability/__init__.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/observability/recorder.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/py.typed +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/pytest_plugin.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/registry.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/rpa.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/vision/_images.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/vision/imaging.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/actions.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/backtrack.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/backtrack_depth.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/deep.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/download.txt +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/downloads.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/expects.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/fields.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/forms.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/frames.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/index.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/inner.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/interactions.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/locators.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/network.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/overlay.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/page2.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/popups.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/rpa.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/shadow.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/smartsel.html +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_api_wiring.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_browsers_edge_ie.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_config.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_diagnostics.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_actions.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_async.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_async_more.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_async_parity.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_backtrack.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_downloads.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_edge.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_edge_ie.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_edge_rpa.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_errors.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_events.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_expect_more.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_firefox.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_frames.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_input.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_launch.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_locators.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_locators_more.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_navigation.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_network.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_rpa.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_rpa_basics.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_smartsel.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_vision.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_engine.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_errors.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_events.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_expect_engine_unit.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_htmlsel.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_mcp_list_fields.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_network_capture.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_network_capture_cdp.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_pytest_plugin.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_registry.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_steps_backtrack.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_tracing_unit.py +0 -0
- {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_translation.py +0 -0
|
@@ -58,17 +58,66 @@ The clean-room `bundle.js` (`window.__visus`) is OUR code (NOT Playwright). It c
|
|
|
58
58
|
3. Real-browser smoke test under that engine if available in CI.
|
|
59
59
|
|
|
60
60
|
### Add an MCP tool
|
|
61
|
-
`src/visus/web/mcp/server.py`: a `@mcp.tool()` thin wrapper over the public API, targeting via the shared `
|
|
61
|
+
`src/visus/web/mcp/server.py`: a `@mcp.tool()` thin wrapper over the public API, targeting via the shared `make_locator(page, *, selector=, role=, name=, text=, exact=, frame=)` helper (`mcp/session.py:142-160`; **keyword-only** after `page`). Add the name to `_EXPECTED_TOOLS` in `tests/test_mcp_server.py` and drive the tool in `tests/test_e2e_mcp.py`.
|
|
62
62
|
|
|
63
63
|
### Add a CLI command
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
There are two kinds of command, and only the first is a one-file change.
|
|
66
|
+
|
|
67
|
+
**Daemon-free command** — 11 of the 54 registered commands: `version`, `translate`,
|
|
68
|
+
`doctor`, `install`, `install-skills`, `screenshot`, `pdf`, `open`, `run`, `mcp`,
|
|
69
|
+
`codegen`. These launch (at most) their own throwaway browser and never talk to the
|
|
70
|
+
session daemon. For these, `src/visus/web/cli/main.py` really is the whole change:
|
|
71
|
+
an `@app.command()` Typer function over the public API. Test with
|
|
72
|
+
`typer.testing.CliRunner` (real browser for the browser-touching ones).
|
|
73
|
+
|
|
74
|
+
**Session command** — the other 43. It drives the running daemon (started by
|
|
75
|
+
`visus session start`) instead of launching its own browser. Following only the
|
|
76
|
+
one-file recipe above for one of these ships a command that always fails at
|
|
77
|
+
runtime with `error: ValueError: unknown op '<name>'`, because nothing ever
|
|
78
|
+
answers the dispatch in step 1. Places to touch, in the order you'll hit them:
|
|
79
|
+
|
|
80
|
+
1. **`src/visus/web/cli/session_server.py`** — a `_op_<name>(self, args)` method.
|
|
81
|
+
`SessionHandler.handle()` dispatches with `getattr(self, f"_op_{op}", None)`
|
|
82
|
+
and raises `unknown op '<name>'` if it's missing — this is the one contributors
|
|
83
|
+
skip, and it's the only one that fails silently at authoring time (ruff/mypy
|
|
84
|
+
are clean; it only breaks when someone runs the command). Required.
|
|
85
|
+
2. **`src/visus/web/cli/main.py`** — an `@app.command()` that sends the op via
|
|
86
|
+
`_send("<name>", args)`, never talking to the daemon directly. Required (this
|
|
87
|
+
is the one place the old recipe named).
|
|
88
|
+
3. **`src/visus/web/skills/visus-web/references/cli-commands.md`** — a row in the
|
|
89
|
+
matching table. `tests/test_skill_docs.py::test_every_cli_command_is_documented`
|
|
90
|
+
walks `app.registered_commands` and fails, listing anything undocumented here.
|
|
91
|
+
Required to pass the build.
|
|
92
|
+
4. **`README.md`**'s CLI command tables — a row in the matching table. Not
|
|
93
|
+
test-gated (no test reads `README.md`), but it's the first thing a human
|
|
94
|
+
reads; keep it in sync by hand.
|
|
95
|
+
5. **`src/visus/web/cli/console.py`** — a `dispatch()` branch plus a line in
|
|
96
|
+
`_HELP`, so the interactive REPL can reach it too. Nice-to-have, not
|
|
97
|
+
required: the REPL is a convenience wrapper over the same `_send`, and it is
|
|
98
|
+
currently *incomplete* on purpose/by-omission — `drag`, `upload`,
|
|
99
|
+
`add-cookies`, `clear-cookies`, `read-text`, `solve-captcha`, and
|
|
100
|
+
`find-image` all ship as working CLI commands with no `console.py` branch at
|
|
101
|
+
all. Add one when it's cheap; don't treat its absence as broken.
|
|
102
|
+
|
|
103
|
+
Test the `_op_*` handler with a real-browser test, and the CLI command with
|
|
104
|
+
`typer.testing.CliRunner` asserting the exact op name and payload sent to the
|
|
105
|
+
daemon (pattern: `tests/test_cli_session.py:1152-1642`).
|
|
65
106
|
|
|
66
107
|
## Test conventions (enforced)
|
|
67
108
|
|
|
68
109
|
- **Real browser, no fakes.** Coverage is driven by real-browser integration tests (`@pytest.mark.browser`) against local fixture pages in `tests/fixtures/`, served by the session-scoped `base_url` fixture (an ephemeral `http.server`). Pure-logic units (errors/enum/config/exception-translation/step-encoding/vision-on-generated-images) are the only non-browser tests. Test doubles isolate a unit ONLY when its real behavior is also covered by an integration test — never as the sole proof of a feature. No `skip`/`xfail`/mock-to-pass.
|
|
69
|
-
- **Parallel:** the suite runs under `pytest-xdist` (`-n auto`, `--dist loadfile`).
|
|
70
|
-
- **Gates:** `uv run ruff check src tests`, `uv run mypy` (`--strict`, no weakening — cast `execute_script`/CDP `Any` returns; selenium types only inside backend files), `uv run pytest -q` (≥90% coverage on `visus.web`).
|
|
110
|
+
- **Parallel:** the suite runs under `pytest-xdist` (`-n auto`, `--dist loadfile`). The 90% gate lives in `[tool.coverage.report] fail_under`, not `addopts`, so it is no longer implicit — pass `--cov-fail-under=90` to enforce it, or `--cov-fail-under=0` on a filtered subset (e.g. `-m "not browser"`) to keep coverage measured without failing on a partial run; `--no-cov` drops measurement entirely.
|
|
111
|
+
- **Gates:** `uv run ruff check src tests`, `uv run mypy` (`--strict`, no weakening — cast `execute_script`/CDP `Any` returns; selenium types only inside backend files), `uv run pytest -q --cov-fail-under=90` (≥90% coverage on `visus.web`).
|
|
71
112
|
- **Commits:** small, TDD (failing test → minimal impl → green → commit).
|
|
72
113
|
|
|
114
|
+
### Change the shipped agent skill
|
|
115
|
+
|
|
116
|
+
The skill lives at `src/visus/web/skills/visus-web/` and ships inside the wheel.
|
|
117
|
+
Its frontmatter `version` must equal `visus.web.__version__` — bump both together.
|
|
118
|
+
`tests/test_skill_docs.py` gates command coverage, the MCP tool count, and link
|
|
119
|
+
integrity. The contributor guide you are reading is **not** shipped; it lives in
|
|
120
|
+
`.claude/skills/developing-visus-web/`.
|
|
121
|
+
|
|
73
122
|
## Layout & build
|
|
74
|
-
`src/`-layout namespace package (`visus.web`; no `src/visus/__init__.py`). Build: `uv` + `hatchling`. Optional extras: `[dev]`, `[vision]` (rapidocr/opencv/pillow/numpy), `[mcp]` (mcp), `[cli]` (typer). Entry points: `visus`, `visus-web-mcp`.
|
|
123
|
+
`src/`-layout namespace package (`visus.web`; no `src/visus/__init__.py`). Build: `uv` + `hatchling`. Optional extras: `[dev]`, `[vision]` (rapidocr/opencv/pillow/numpy), `[mcp]` (mcp), `[cli]` (typer + rich + click). Entry points: `visus`, `visus-web-mcp`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
updates:
|
|
4
|
+
# Python dependencies are declared in pyproject.toml (PEP 621 + optional-dependencies).
|
|
5
|
+
# NOTE: `selenium>=4.38,<5` is a range, so Dependabot will only open a PR when a
|
|
6
|
+
# release falls OUTSIDE it. Rolling Selenium within the range is a manual step —
|
|
7
|
+
# see RELEASING.md, "Rolling Selenium".
|
|
8
|
+
- package-ecosystem: "pip"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
12
|
+
day: "monday"
|
|
13
|
+
open-pull-requests-limit: 5
|
|
14
|
+
groups:
|
|
15
|
+
dev-tooling:
|
|
16
|
+
patterns:
|
|
17
|
+
- "pytest*"
|
|
18
|
+
- "ruff"
|
|
19
|
+
- "mypy"
|
|
20
|
+
- "pre-commit"
|
|
21
|
+
- "build"
|
|
22
|
+
- "hatchling"
|
|
23
|
+
- "tomli"
|
|
24
|
+
- "pyyaml"
|
|
25
|
+
|
|
26
|
+
# Pinned action majors in ci.yml and publish.yml.
|
|
27
|
+
- package-ecosystem: "github-actions"
|
|
28
|
+
directory: "/"
|
|
29
|
+
schedule:
|
|
30
|
+
interval: "weekly"
|
|
31
|
+
day: "monday"
|
|
32
|
+
open-pull-requests-limit: 5
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Runs the real gates on every push and PR, and is callable from publish.yml so a
|
|
4
|
+
# release cannot be built before these jobs go green.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [main]
|
|
10
|
+
workflow_call: {}
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
17
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
lint:
|
|
21
|
+
name: Lint + types
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 15
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
- uses: astral-sh/setup-uv@v5
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: true
|
|
32
|
+
- name: Install (mirrors TESTING.md)
|
|
33
|
+
shell: bash
|
|
34
|
+
run: |
|
|
35
|
+
uv venv --python "3.12"
|
|
36
|
+
uv pip install -e ".[dev,vision,mcp,cli]"
|
|
37
|
+
- name: Put the venv on PATH
|
|
38
|
+
shell: bash
|
|
39
|
+
run: |
|
|
40
|
+
if [ -d .venv/bin ]; then
|
|
41
|
+
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
|
|
42
|
+
else
|
|
43
|
+
# Windows: $PWD is a bash path (/d/a/...) that the pwsh steps cannot
|
|
44
|
+
# resolve -- `visus`/`pytest` were "not recognized"
|
|
45
|
+
cygpath -w "$PWD/.venv/Scripts" >> "$GITHUB_PATH"
|
|
46
|
+
fi
|
|
47
|
+
- name: ruff
|
|
48
|
+
run: ruff check src tests scripts
|
|
49
|
+
- name: mypy --strict
|
|
50
|
+
run: mypy
|
|
51
|
+
|
|
52
|
+
fast-tests:
|
|
53
|
+
name: Tests (no browser) ${{ matrix.os }} py${{ matrix.python }}
|
|
54
|
+
strategy:
|
|
55
|
+
fail-fast: false
|
|
56
|
+
matrix:
|
|
57
|
+
os: [ubuntu-latest, windows-latest]
|
|
58
|
+
python: ["3.10", "3.12"]
|
|
59
|
+
runs-on: ${{ matrix.os }}
|
|
60
|
+
timeout-minutes: 30
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v4
|
|
63
|
+
- uses: actions/setup-python@v5
|
|
64
|
+
with:
|
|
65
|
+
python-version: ${{ matrix.python }}
|
|
66
|
+
- uses: astral-sh/setup-uv@v5
|
|
67
|
+
with:
|
|
68
|
+
enable-cache: true
|
|
69
|
+
- name: Install (mirrors TESTING.md)
|
|
70
|
+
shell: bash
|
|
71
|
+
run: |
|
|
72
|
+
uv venv --python "${{ matrix.python }}"
|
|
73
|
+
uv pip install -e ".[dev,vision,mcp,cli]"
|
|
74
|
+
- name: Put the venv on PATH
|
|
75
|
+
shell: bash
|
|
76
|
+
run: |
|
|
77
|
+
if [ -d .venv/bin ]; then
|
|
78
|
+
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
|
|
79
|
+
else
|
|
80
|
+
# Windows: $PWD is a bash path (/d/a/...) that the pwsh steps cannot
|
|
81
|
+
# resolve -- `visus`/`pytest` were "not recognized"
|
|
82
|
+
cygpath -w "$PWD/.venv/Scripts" >> "$GITHUB_PATH"
|
|
83
|
+
fi
|
|
84
|
+
- name: pytest (no browser, no packaging)
|
|
85
|
+
run: pytest -q -m "not browser and not packaging" --cov-fail-under=0
|
|
86
|
+
|
|
87
|
+
packaging-tests:
|
|
88
|
+
name: Installed console scripts (${{ matrix.os }})
|
|
89
|
+
strategy:
|
|
90
|
+
fail-fast: false
|
|
91
|
+
matrix:
|
|
92
|
+
os: [ubuntu-latest, windows-latest]
|
|
93
|
+
runs-on: ${{ matrix.os }}
|
|
94
|
+
timeout-minutes: 30
|
|
95
|
+
steps:
|
|
96
|
+
- uses: actions/checkout@v4
|
|
97
|
+
- uses: actions/setup-python@v5
|
|
98
|
+
with:
|
|
99
|
+
python-version: "3.12"
|
|
100
|
+
- uses: astral-sh/setup-uv@v5
|
|
101
|
+
with:
|
|
102
|
+
enable-cache: true
|
|
103
|
+
- name: Install (mirrors TESTING.md)
|
|
104
|
+
shell: bash
|
|
105
|
+
run: |
|
|
106
|
+
uv venv --python "3.12"
|
|
107
|
+
uv pip install -e ".[dev,vision,mcp,cli]"
|
|
108
|
+
- name: Put the venv on PATH
|
|
109
|
+
shell: bash
|
|
110
|
+
run: |
|
|
111
|
+
if [ -d .venv/bin ]; then
|
|
112
|
+
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
|
|
113
|
+
else
|
|
114
|
+
# Windows: $PWD is a bash path (/d/a/...) that the pwsh steps cannot
|
|
115
|
+
# resolve -- `visus`/`pytest` were "not recognized"
|
|
116
|
+
cygpath -w "$PWD/.venv/Scripts" >> "$GITHUB_PATH"
|
|
117
|
+
fi
|
|
118
|
+
- name: pytest -m packaging
|
|
119
|
+
run: pytest -q -m packaging --cov-fail-under=0
|
|
120
|
+
|
|
121
|
+
browser-tests:
|
|
122
|
+
name: Full suite + coverage gates (windows-latest)
|
|
123
|
+
runs-on: windows-latest
|
|
124
|
+
timeout-minutes: 75
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@v4
|
|
127
|
+
- uses: actions/setup-python@v5
|
|
128
|
+
with:
|
|
129
|
+
python-version: "3.12"
|
|
130
|
+
- uses: astral-sh/setup-uv@v5
|
|
131
|
+
with:
|
|
132
|
+
enable-cache: true
|
|
133
|
+
- name: Install (mirrors TESTING.md)
|
|
134
|
+
shell: bash
|
|
135
|
+
run: |
|
|
136
|
+
uv venv --python "3.12"
|
|
137
|
+
uv pip install -e ".[dev,vision,mcp,cli]"
|
|
138
|
+
- name: Put the venv on PATH
|
|
139
|
+
shell: bash
|
|
140
|
+
run: |
|
|
141
|
+
if [ -d .venv/bin ]; then
|
|
142
|
+
echo "$PWD/.venv/bin" >> "$GITHUB_PATH"
|
|
143
|
+
else
|
|
144
|
+
# Windows: $PWD is a bash path (/d/a/...) that the pwsh steps cannot
|
|
145
|
+
# resolve -- `visus`/`pytest` were "not recognized"
|
|
146
|
+
cygpath -w "$PWD/.venv/Scripts" >> "$GITHUB_PATH"
|
|
147
|
+
fi
|
|
148
|
+
# Chrome ships with the runner image; Selenium Manager resolves chromedriver
|
|
149
|
+
# on first launch (backends/browsers/chrome.py). Fail loudly here rather than
|
|
150
|
+
# 298 times below.
|
|
151
|
+
- name: Smoke-test browser + driver
|
|
152
|
+
run: visus doctor
|
|
153
|
+
# -n 4 (not the addopts default -n auto): a measured full-suite run on a
|
|
154
|
+
# many-core dev machine showed -n auto spawning one xdist worker per core,
|
|
155
|
+
# each loading its own ONNX model for the vision tests (test_e2e_vision.py
|
|
156
|
+
# is `browser`-marked and runs here), and workers dying with
|
|
157
|
+
# `_ArrayMemoryError`. -n 4 was the exact configuration measured safe:
|
|
158
|
+
# 935 passed, 7 skipped, 0 failed in 14m52s. GitHub-hosted windows-latest
|
|
159
|
+
# runners are not guaranteed to stay small-core forever, so this is pinned
|
|
160
|
+
# explicitly rather than left to whatever `os.cpu_count()` reports.
|
|
161
|
+
- name: pytest (full suite, real browsers) + global coverage gate
|
|
162
|
+
run: pytest -q -n 4 -m "not packaging" --cov-fail-under=90 --cov-report=json
|
|
163
|
+
- name: Per-package coverage floors
|
|
164
|
+
run: python scripts/check_coverage.py
|
|
165
|
+
- name: Upload coverage.json
|
|
166
|
+
if: always()
|
|
167
|
+
uses: actions/upload-artifact@v4
|
|
168
|
+
with:
|
|
169
|
+
name: coverage-json
|
|
170
|
+
path: coverage.json
|
|
171
|
+
if-no-files-found: warn
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when you cut a GitHub Release (tag vX.Y.Z).
|
|
4
|
+
#
|
|
5
|
+
# A plain `git push` does NOT publish. Neither does `workflow_dispatch`: EVERY job
|
|
6
|
+
# below is guarded, so a manual run from the Actions tab — on any branch — builds
|
|
7
|
+
# nothing and publishes nothing. The dispatch trigger is kept only so the guard
|
|
8
|
+
# stays visible and testable.
|
|
9
|
+
#
|
|
10
|
+
# The guard has two halves:
|
|
11
|
+
# github.event_name == 'release' -> not a dispatch
|
|
12
|
+
# github.event.release.target_commitish == 'main' -> cut from main
|
|
13
|
+
#
|
|
14
|
+
# It is `target_commitish`, NOT `github.ref`: on a release event GITHUB_REF is the
|
|
15
|
+
# TAG ref (refs/tags/vX.Y.Z), so `github.ref == 'refs/heads/main'` is never true
|
|
16
|
+
# and would silently block every release. `target_commitish` carries the branch
|
|
17
|
+
# the release was created from.
|
|
18
|
+
#
|
|
19
|
+
# Caveat: if a Release is cut from a pre-existing tag rather than a branch, GitHub
|
|
20
|
+
# sets target_commitish to a commit SHA and this guard rejects it. Cut releases
|
|
21
|
+
# from the `main` branch, as RELEASING.md describes.
|
|
22
|
+
#
|
|
23
|
+
# Nothing is built until the full CI matrix (lint, types, unit, packaging and the
|
|
24
|
+
# real-browser suite with both coverage gates) is green on the released commit.
|
|
25
|
+
#
|
|
26
|
+
# Auth is via PyPI Trusted Publishing (OIDC): no tokens, no secrets.
|
|
27
|
+
|
|
28
|
+
on:
|
|
29
|
+
release:
|
|
30
|
+
types: [published]
|
|
31
|
+
workflow_dispatch: # deliberately inert — see the guard on every job
|
|
32
|
+
|
|
33
|
+
permissions:
|
|
34
|
+
contents: read
|
|
35
|
+
|
|
36
|
+
jobs:
|
|
37
|
+
test:
|
|
38
|
+
name: CI gates
|
|
39
|
+
if: github.event_name == 'release' &&
|
|
40
|
+
github.event.release.target_commitish == 'main'
|
|
41
|
+
uses: ./.github/workflows/ci.yml
|
|
42
|
+
|
|
43
|
+
build:
|
|
44
|
+
name: Build distribution
|
|
45
|
+
if: github.event_name == 'release' &&
|
|
46
|
+
github.event.release.target_commitish == 'main'
|
|
47
|
+
needs: [test]
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
|
|
52
|
+
- uses: actions/setup-python@v5
|
|
53
|
+
with:
|
|
54
|
+
python-version: "3.12"
|
|
55
|
+
|
|
56
|
+
- name: Build sdist + wheel and validate metadata
|
|
57
|
+
run: |
|
|
58
|
+
python -m pip install --upgrade pip build twine
|
|
59
|
+
python -m build
|
|
60
|
+
twine check dist/*
|
|
61
|
+
|
|
62
|
+
# `twine check` validates metadata and never opens the wheel's payload.
|
|
63
|
+
# The agent skill and the Selenium JS bundle both live inside the package
|
|
64
|
+
# directory, so a packaging regression would silently ship a CLI that
|
|
65
|
+
# cannot install its own skill or drive a browser.
|
|
66
|
+
- name: Assert the wheel ships the agent skill and the runtime data files
|
|
67
|
+
run: |
|
|
68
|
+
python - "$(ls dist/*.whl)" <<'PY'
|
|
69
|
+
import sys, zipfile
|
|
70
|
+
wheel = sys.argv[1]
|
|
71
|
+
names = set(zipfile.ZipFile(wheel).namelist())
|
|
72
|
+
required = [
|
|
73
|
+
"visus/web/skills/visus-web/SKILL.md",
|
|
74
|
+
"visus/web/backends/selenium/js/bundle.js",
|
|
75
|
+
"visus/web/py.typed",
|
|
76
|
+
]
|
|
77
|
+
print(f"{wheel}: {len(names)} entries")
|
|
78
|
+
for item in required:
|
|
79
|
+
print((" OK " if item in names else " MISS ") + item)
|
|
80
|
+
missing = [item for item in required if item not in names]
|
|
81
|
+
if missing:
|
|
82
|
+
sys.exit(f"wheel is missing required files: {missing}")
|
|
83
|
+
PY
|
|
84
|
+
|
|
85
|
+
- uses: actions/upload-artifact@v4
|
|
86
|
+
with:
|
|
87
|
+
name: dist
|
|
88
|
+
path: dist/
|
|
89
|
+
|
|
90
|
+
publish:
|
|
91
|
+
name: Publish to PyPI
|
|
92
|
+
if: github.event_name == 'release' &&
|
|
93
|
+
github.event.release.target_commitish == 'main'
|
|
94
|
+
needs: [build]
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
# The environment name MUST match what you set on PyPI's trusted publisher.
|
|
97
|
+
environment:
|
|
98
|
+
name: pypi
|
|
99
|
+
url: https://pypi.org/p/visus-web
|
|
100
|
+
permissions:
|
|
101
|
+
id-token: write # required for OIDC / trusted publishing
|
|
102
|
+
steps:
|
|
103
|
+
- uses: actions/download-artifact@v4
|
|
104
|
+
with:
|
|
105
|
+
name: dist
|
|
106
|
+
path: dist/
|
|
107
|
+
|
|
108
|
+
- name: Publish to PyPI
|
|
109
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
/botcity-framework-web-python/
|
|
5
5
|
|
|
6
6
|
# ---- Local-only planning docs (specs/plans kept on disk, NOT tracked) ----
|
|
7
|
-
|
|
7
|
+
# `/docs/*` (not `/docs/`) so a single tracked page can be re-included below —
|
|
8
|
+
# git cannot un-ignore a file whose parent directory is excluded.
|
|
9
|
+
/docs/*
|
|
10
|
+
!/docs/agent-skill.md
|
|
8
11
|
|
|
9
12
|
# ---- Workflow research temp files ----
|
|
10
13
|
_synthesis.tmp.md
|
|
@@ -37,6 +40,9 @@ uv.lock
|
|
|
37
40
|
.coverage.*
|
|
38
41
|
htmlcov/
|
|
39
42
|
coverage.xml
|
|
43
|
+
# machine-readable report consumed by scripts/check_coverage.py — regenerated,
|
|
44
|
+
# never committed (coverage-floors.json IS committed)
|
|
45
|
+
coverage.json
|
|
40
46
|
.tox/
|
|
41
47
|
.nox/
|
|
42
48
|
|
|
@@ -58,3 +64,10 @@ IEDriverServer.exe
|
|
|
58
64
|
*.swp
|
|
59
65
|
.DS_Store
|
|
60
66
|
Thumbs.db
|
|
67
|
+
|
|
68
|
+
# ---- Agent skills installed into this repo (build artifacts) ----
|
|
69
|
+
# `visus install --skills` writes the shipped skill here; the source of truth is
|
|
70
|
+
# src/visus/web/skills/visus-web/. The contributor skill under
|
|
71
|
+
# .claude/skills/developing-visus-web/ IS tracked — do not widen this to `.claude/`.
|
|
72
|
+
.claude/skills/visus-web/
|
|
73
|
+
.agents/
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Local-only hooks. Every hook shells out to the exact command CI runs, so
|
|
2
|
+
# `pre-commit run --all-files` and the `lint` job in .github/workflows/ci.yml
|
|
3
|
+
# cannot disagree, and no hook needs a network clone or a second pinned copy of
|
|
4
|
+
# ruff/mypy.
|
|
5
|
+
#
|
|
6
|
+
# Setup (see TESTING.md):
|
|
7
|
+
# uv venv && uv pip install -e ".[dev,vision,mcp,cli]"
|
|
8
|
+
# uv run pre-commit install
|
|
9
|
+
#
|
|
10
|
+
# Requires `uv` on PATH.
|
|
11
|
+
repos:
|
|
12
|
+
- repo: local
|
|
13
|
+
hooks:
|
|
14
|
+
- id: ruff
|
|
15
|
+
name: ruff check (src, tests, scripts)
|
|
16
|
+
entry: uv run ruff check --force-exclude
|
|
17
|
+
language: system
|
|
18
|
+
types_or: [python, pyi]
|
|
19
|
+
files: ^(src|tests|scripts)/
|
|
20
|
+
require_serial: true
|
|
21
|
+
|
|
22
|
+
- id: mypy
|
|
23
|
+
name: mypy --strict (visus.web)
|
|
24
|
+
entry: uv run mypy
|
|
25
|
+
language: system
|
|
26
|
+
types_or: [python, pyi]
|
|
27
|
+
files: ^src/
|
|
28
|
+
# [tool.mypy] packages = ["visus.web"] — mypy selects its own inputs.
|
|
29
|
+
pass_filenames: false
|
|
30
|
+
require_serial: true
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Contributing to visus.web
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv venv && uv pip install -e ".[dev,vision,mcp,cli]"
|
|
7
|
+
uv run pre-commit install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`[vision]` (RapidOCR + OpenCV) is required for the OCR/image tests; `[mcp]` and `[cli]`
|
|
11
|
+
for the MCP/CLI tests. See `TESTING.md` for what the suite covers and how it is split.
|
|
12
|
+
|
|
13
|
+
## The gate
|
|
14
|
+
|
|
15
|
+
Everything below must pass before a change is proposed. CI runs the same commands
|
|
16
|
+
(`.github/workflows/ci.yml`), so a green local run is a green CI run.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv run ruff check src tests scripts
|
|
20
|
+
uv run mypy
|
|
21
|
+
uv run pytest -q --cov-fail-under=90 --cov-report=json
|
|
22
|
+
uv run python scripts/check_coverage.py
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- The **global** 90% floor lives in `[tool.coverage.report] fail_under` and is passed
|
|
26
|
+
explicitly on the pytest command. It is deliberately *not* in `addopts`: a filtered
|
|
27
|
+
run (`-m "not browser"` selects most, but not all, of the suite) can never reach it.
|
|
28
|
+
- `scripts/check_coverage.py` adds **per-package** floors from `coverage-floors.json`,
|
|
29
|
+
so `cli/` and `mcp/` — roughly 3,200 of 11,300 lines — cannot rot behind a healthy
|
|
30
|
+
total. Floors ratchet up only: after coverage improves, re-run
|
|
31
|
+
`uv run python scripts/check_coverage.py --freeze` and commit the new numbers.
|
|
32
|
+
|
|
33
|
+
While iterating on one file, drop the gate but keep the table:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv run pytest tests/test_e2e_locators.py -m browser --cov-fail-under=0
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The suite runs under `pytest-xdist` and takes roughly 15 minutes end to end with the
|
|
40
|
+
real-browser lane (currently 965+ collected items — it grows, so treat any specific
|
|
41
|
+
count as a snapshot, not a target); markers are `browser` and `packaging`.
|
|
42
|
+
|
|
43
|
+
## How to write a change
|
|
44
|
+
|
|
45
|
+
**TDD is not optional.** Write the failing test, run it and watch it fail for the right
|
|
46
|
+
reason, write the smallest implementation that makes it pass, run it again, commit.
|
|
47
|
+
|
|
48
|
+
**Real browser, no fakes.** Anything that touches a page is marked `@pytest.mark.browser`
|
|
49
|
+
and drives a real headless Chromium against a local fixture page in `tests/fixtures/*.html`,
|
|
50
|
+
served by the session-scoped `base_url` fixture in `tests/conftest.py`. A test double may
|
|
51
|
+
isolate a unit only when the same behaviour is *also* proven by an integration test.
|
|
52
|
+
|
|
53
|
+
**No `skip`, no `xfail`, no mock-to-pass.** `tests/test_repo_hygiene.py` enforces this;
|
|
54
|
+
the two pre-existing conditional skips are allowlisted there by name with their reason.
|
|
55
|
+
|
|
56
|
+
**The hidden-engine rule.** No `selenium.*` type may appear in any public signature,
|
|
57
|
+
return type, exception or docstring. Selenium imports are allowed only in
|
|
58
|
+
`src/visus/web/backends/browsers/*.py`, `src/visus/web/backends/selenium/*.py` and
|
|
59
|
+
`src/visus/web/backends/selenium_backend.py`.
|
|
60
|
+
|
|
61
|
+
## Commits
|
|
62
|
+
|
|
63
|
+
Small and semantic: `label(scope): description`, where `label` is one of
|
|
64
|
+
`fix`, `feat`, `chore`, `docs`, `test`, `devops`.
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
fix(session): invalidate cached field indexes on navigation
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Never add `Co-Authored-By` trailers.
|
|
71
|
+
|
|
72
|
+
## Releasing
|
|
73
|
+
|
|
74
|
+
See `RELEASING.md`.
|