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.
Files changed (296) hide show
  1. {visus_web-0.2.0 → visus_web-0.3.2/.claude}/skills/developing-visus-web/SKILL.md +54 -5
  2. visus_web-0.3.2/.github/dependabot.yml +32 -0
  3. visus_web-0.3.2/.github/workflows/ci.yml +171 -0
  4. visus_web-0.3.2/.github/workflows/publish.yml +109 -0
  5. {visus_web-0.2.0 → visus_web-0.3.2}/.gitignore +14 -1
  6. visus_web-0.3.2/.pre-commit-config.yaml +30 -0
  7. visus_web-0.3.2/CONTRIBUTING.md +74 -0
  8. visus_web-0.2.0/README.md → visus_web-0.3.2/PKG-INFO +223 -34
  9. visus_web-0.2.0/PKG-INFO → visus_web-0.3.2/README.md +160 -73
  10. visus_web-0.3.2/RELEASING.md +92 -0
  11. visus_web-0.3.2/SECURITY.md +41 -0
  12. {visus_web-0.2.0 → visus_web-0.3.2}/TESTING.md +41 -5
  13. visus_web-0.3.2/coverage-floors.json +13 -0
  14. visus_web-0.3.2/docs/agent-skill.md +41 -0
  15. {visus_web-0.2.0 → visus_web-0.3.2}/mkdocs.yml +1 -0
  16. visus_web-0.3.2/page.pdf +0 -0
  17. visus_web-0.3.2/page.png +0 -0
  18. visus_web-0.3.2/pyproject.toml +211 -0
  19. visus_web-0.3.2/scripts/check_coverage.py +177 -0
  20. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/__init__.py +38 -3
  21. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/assertions.py +41 -0
  22. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/browser.py +9 -0
  23. visus_web-0.3.2/src/visus/web/api/devices.py +43 -0
  24. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/fields.py +8 -0
  25. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/input.py +15 -12
  26. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/locator.py +55 -4
  27. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/page.py +124 -19
  28. visus_web-0.3.2/src/visus/web/api/route.py +129 -0
  29. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/async_api/__init__.py +107 -50
  30. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/base.py +20 -6
  31. visus_web-0.3.2/src/visus/web/backends/browsers/chrome.py +112 -0
  32. visus_web-0.3.2/src/visus/web/backends/browsers/edge.py +85 -0
  33. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/browsers/edge_ie.py +35 -2
  34. visus_web-0.3.2/src/visus/web/backends/browsers/firefox.py +77 -0
  35. visus_web-0.3.2/src/visus/web/backends/selenium/cdp_fetch.py +242 -0
  36. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/driver_delegate.py +162 -20
  37. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/js/bundle.js +171 -25
  38. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium_backend.py +80 -4
  39. visus_web-0.3.2/src/visus/web/cli/__main__.py +6 -0
  40. visus_web-0.3.2/src/visus/web/cli/_entry.py +62 -0
  41. visus_web-0.3.2/src/visus/web/cli/codegen.py +206 -0
  42. visus_web-0.3.2/src/visus/web/cli/compact.py +69 -0
  43. visus_web-0.3.2/src/visus/web/cli/config.py +155 -0
  44. visus_web-0.3.2/src/visus/web/cli/console.py +813 -0
  45. visus_web-0.3.2/src/visus/web/cli/fleet.py +63 -0
  46. visus_web-0.3.2/src/visus/web/cli/main.py +3007 -0
  47. visus_web-0.3.2/src/visus/web/cli/output.py +158 -0
  48. visus_web-0.3.2/src/visus/web/cli/session_client.py +818 -0
  49. visus_web-0.3.2/src/visus/web/cli/session_server.py +2584 -0
  50. visus_web-0.3.2/src/visus/web/cli/skill_check.py +97 -0
  51. visus_web-0.3.2/src/visus/web/cli/skills.py +100 -0
  52. visus_web-0.3.2/src/visus/web/mcp/server.py +1609 -0
  53. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/mcp/session.py +73 -21
  54. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/network.py +25 -0
  55. visus_web-0.3.2/src/visus/web/observability/report.py +708 -0
  56. visus_web-0.3.2/src/visus/web/observability/video.py +65 -0
  57. visus_web-0.3.2/src/visus/web/ops/__init__.py +12 -0
  58. visus_web-0.3.2/src/visus/web/ops/captcha.py +371 -0
  59. visus_web-0.3.2/src/visus/web/ops/dates.py +625 -0
  60. visus_web-0.3.2/src/visus/web/ops/registry.py +136 -0
  61. visus_web-0.3.2/src/visus/web/ops/remote.py +86 -0
  62. visus_web-0.3.2/src/visus/web/ops/targeting.py +125 -0
  63. visus_web-0.3.2/src/visus/web/skills/visus-web/SKILL.md +285 -0
  64. visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-commands.md +186 -0
  65. visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-json-and-piping.md +95 -0
  66. visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-repl.md +143 -0
  67. visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-session.md +110 -0
  68. visus_web-0.3.2/src/visus/web/skills/visus-web/references/cli-targeting.md +146 -0
  69. visus_web-0.3.2/src/visus/web/skills/visus-web/references/library-api.md +259 -0
  70. visus_web-0.3.2/src/visus/web/skills/visus-web/references/mcp-tools.md +120 -0
  71. visus_web-0.3.2/src/visus/web/skills/visus-web/references/pytest-plugin.md +58 -0
  72. visus_web-0.3.2/src/visus/web/skills/visus-web/references/tracing-reports.md +96 -0
  73. visus_web-0.3.2/src/visus/web/skills/visus-web/references/troubleshooting.md +53 -0
  74. visus_web-0.3.2/src/visus/web/skills/visus-web/references/vision.md +57 -0
  75. visus_web-0.3.2/src/visus/web/skills/visus-web/references/windows-shell.md +78 -0
  76. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/tracing.py +81 -0
  77. visus_web-0.3.2/src/visus/web/vision/__init__.py +15 -0
  78. visus_web-0.3.2/src/visus/web/vision/ocr.py +116 -0
  79. visus_web-0.3.2/tests/conftest.py +157 -0
  80. visus_web-0.3.2/tests/fixtures/agent_page.html +17 -0
  81. visus_web-0.3.2/tests/fixtures/captcha.html +17 -0
  82. visus_web-0.3.2/tests/fixtures/captcha_turnstile.html +16 -0
  83. visus_web-0.3.2/tests/fixtures/captcha_v3.html +20 -0
  84. visus_web-0.3.2/tests/fixtures/clickables.html +33 -0
  85. visus_web-0.3.2/tests/fixtures/datepicker.html +226 -0
  86. visus_web-0.3.2/tests/fixtures/datepicker_form.html +37 -0
  87. visus_web-0.3.2/tests/fixtures/delayed.html +16 -0
  88. visus_web-0.3.2/tests/fixtures/dropzone.html +26 -0
  89. visus_web-0.3.2/tests/fixtures/duplicates.html +18 -0
  90. visus_web-0.3.2/tests/fixtures/hidden_duplicates.html +12 -0
  91. visus_web-0.3.2/tests/fixtures/imagetarget.html +47 -0
  92. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/input.html +4 -1
  93. visus_web-0.3.2/tests/fixtures/links.html +8 -0
  94. visus_web-0.3.2/tests/fixtures/mouse.html +38 -0
  95. visus_web-0.3.2/tests/fixtures/nullvsempty.html +17 -0
  96. visus_web-0.3.2/tests/fixtures/route_fetch.html +32 -0
  97. visus_web-0.3.2/tests/fixtures/shadow_click.html +15 -0
  98. visus_web-0.3.2/tests/fixtures/snapshot_tree.html +22 -0
  99. visus_web-0.3.2/tests/fixtures/stale_a.html +24 -0
  100. visus_web-0.3.2/tests/fixtures/stale_b.html +18 -0
  101. visus_web-0.3.2/tests/fixtures/storage.html +17 -0
  102. visus_web-0.3.2/tests/fixtures/upload_single.html +20 -0
  103. visus_web-0.3.2/tests/fixtures/visible_text.html +11 -0
  104. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_async_api_unit.py +28 -6
  105. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_backend_protocols.py +15 -5
  106. visus_web-0.3.2/tests/test_browsers_chrome.py +56 -0
  107. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_browsers_edge.py +12 -0
  108. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_browsers_firefox.py +16 -0
  109. visus_web-0.3.2/tests/test_captcha_service.py +266 -0
  110. visus_web-0.3.2/tests/test_ci_workflows.py +158 -0
  111. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_cli.py +93 -2
  112. visus_web-0.3.2/tests/test_cli_agent_loop.py +417 -0
  113. visus_web-0.3.2/tests/test_cli_agent_matrix.py +144 -0
  114. visus_web-0.3.2/tests/test_cli_attach.py +384 -0
  115. visus_web-0.3.2/tests/test_cli_batch.py +87 -0
  116. visus_web-0.3.2/tests/test_cli_compact.py +155 -0
  117. visus_web-0.3.2/tests/test_cli_config.py +228 -0
  118. visus_web-0.3.2/tests/test_cli_devtools.py +448 -0
  119. visus_web-0.3.2/tests/test_cli_drop_upload.py +294 -0
  120. visus_web-0.3.2/tests/test_cli_e2e_practice_login.py +105 -0
  121. visus_web-0.3.2/tests/test_cli_emulation.py +217 -0
  122. visus_web-0.3.2/tests/test_cli_fleet.py +57 -0
  123. visus_web-0.3.2/tests/test_cli_index_resolution.py +114 -0
  124. visus_web-0.3.2/tests/test_cli_input_devices.py +506 -0
  125. visus_web-0.3.2/tests/test_cli_nth.py +109 -0
  126. visus_web-0.3.2/tests/test_cli_output.py +159 -0
  127. visus_web-0.3.2/tests/test_cli_page_ops.py +1282 -0
  128. visus_web-0.3.2/tests/test_cli_positional_targets.py +225 -0
  129. visus_web-0.3.2/tests/test_cli_repl_parity.py +189 -0
  130. visus_web-0.3.2/tests/test_cli_route.py +257 -0
  131. visus_web-0.3.2/tests/test_cli_run_code.py +289 -0
  132. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_cli_session.py +1859 -195
  133. visus_web-0.3.2/tests/test_cli_session_flag_placement.py +77 -0
  134. visus_web-0.3.2/tests/test_cli_sessions_named.py +678 -0
  135. visus_web-0.3.2/tests/test_cli_skills.py +338 -0
  136. visus_web-0.3.2/tests/test_cli_tabs_state.py +337 -0
  137. visus_web-0.3.2/tests/test_cli_tracing.py +253 -0
  138. visus_web-0.3.2/tests/test_cli_visible_text.py +30 -0
  139. visus_web-0.3.2/tests/test_cli_vision_targeting.py +247 -0
  140. visus_web-0.3.2/tests/test_cli_wait.py +49 -0
  141. visus_web-0.3.2/tests/test_codegen.py +199 -0
  142. visus_web-0.3.2/tests/test_console_scripts.py +47 -0
  143. visus_web-0.3.2/tests/test_coverage_floors.py +222 -0
  144. visus_web-0.3.2/tests/test_daemon_command_override.py +67 -0
  145. visus_web-0.3.2/tests/test_download_partial_files.py +82 -0
  146. visus_web-0.3.2/tests/test_driver_handle_inheritance.py +111 -0
  147. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_actions_more.py +32 -0
  148. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_cli_session.py +49 -4
  149. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_contexts.py +24 -0
  150. visus_web-0.3.2/tests/test_e2e_expect.py +124 -0
  151. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_fields.py +84 -0
  152. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_mcp.py +40 -0
  153. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_observability.py +38 -1
  154. visus_web-0.3.2/tests/test_http_site_e2e.py +26 -0
  155. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_input_wiring.py +12 -12
  156. visus_web-0.3.2/tests/test_installed_entry_points.py +167 -0
  157. visus_web-0.3.2/tests/test_list_clickables.py +71 -0
  158. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_locator_steps.py +19 -0
  159. visus_web-0.3.2/tests/test_mcp_attach.py +168 -0
  160. visus_web-0.3.2/tests/test_mcp_index.py +264 -0
  161. visus_web-0.3.2/tests/test_mcp_nth.py +53 -0
  162. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_mcp_server.py +59 -0
  163. visus_web-0.3.2/tests/test_mcp_tracing.py +88 -0
  164. visus_web-0.3.2/tests/test_no_console_window.py +178 -0
  165. visus_web-0.3.2/tests/test_ops_registry.py +88 -0
  166. visus_web-0.3.2/tests/test_ops_remote.py +98 -0
  167. visus_web-0.3.2/tests/test_ops_targeting.py +137 -0
  168. visus_web-0.3.2/tests/test_pick_date.py +220 -0
  169. visus_web-0.3.2/tests/test_pytest_config.py +119 -0
  170. visus_web-0.3.2/tests/test_repo_hygiene.py +152 -0
  171. visus_web-0.3.2/tests/test_repo_metadata.py +185 -0
  172. visus_web-0.3.2/tests/test_report_copy.py +84 -0
  173. visus_web-0.3.2/tests/test_route.py +203 -0
  174. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_selenium_backend_protocol.py +1 -1
  175. visus_web-0.3.2/tests/test_skill_check_binary_rename.py +77 -0
  176. visus_web-0.3.2/tests/test_skill_commands_parse.py +132 -0
  177. visus_web-0.3.2/tests/test_skill_docs.py +271 -0
  178. visus_web-0.3.2/tests/test_unique_locators.py +58 -0
  179. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_vision.py +51 -0
  180. visus_web-0.3.2/tests/test_window_maximized.py +50 -0
  181. visus_web-0.2.0/.github/workflows/publish.yml +0 -51
  182. visus_web-0.2.0/pyproject.toml +0 -102
  183. visus_web-0.2.0/skills/using-visus-web/SKILL.md +0 -161
  184. visus_web-0.2.0/src/visus/web/backends/browsers/chrome.py +0 -45
  185. visus_web-0.2.0/src/visus/web/backends/browsers/edge.py +0 -45
  186. visus_web-0.2.0/src/visus/web/backends/browsers/firefox.py +0 -30
  187. visus_web-0.2.0/src/visus/web/cli/codegen.py +0 -86
  188. visus_web-0.2.0/src/visus/web/cli/console.py +0 -249
  189. visus_web-0.2.0/src/visus/web/cli/main.py +0 -906
  190. visus_web-0.2.0/src/visus/web/cli/session_client.py +0 -231
  191. visus_web-0.2.0/src/visus/web/cli/session_server.py +0 -777
  192. visus_web-0.2.0/src/visus/web/mcp/server.py +0 -761
  193. visus_web-0.2.0/src/visus/web/observability/report.py +0 -689
  194. visus_web-0.2.0/src/visus/web/vision/__init__.py +0 -6
  195. visus_web-0.2.0/src/visus/web/vision/ocr.py +0 -46
  196. visus_web-0.2.0/tests/conftest.py +0 -56
  197. visus_web-0.2.0/tests/test_browsers_chrome.py +0 -22
  198. visus_web-0.2.0/tests/test_codegen.py +0 -93
  199. visus_web-0.2.0/tests/test_e2e_expect.py +0 -59
  200. {visus_web-0.2.0 → visus_web-0.3.2}/LICENSE +0 -0
  201. {visus_web-0.2.0 → visus_web-0.3.2}/NOTICE +0 -0
  202. {visus_web-0.2.0 → visus_web-0.3.2}/examples/demo_rpa.py +0 -0
  203. {visus_web-0.2.0 → visus_web-0.3.2}/examples/dolar.py +0 -0
  204. {visus_web-0.2.0 → visus_web-0.3.2}/examples/login.py +0 -0
  205. {visus_web-0.2.0 → visus_web-0.3.2}/examples/login_mcp.py +0 -0
  206. {visus_web-0.2.0 → visus_web-0.3.2}/examples/login_rpa.py +0 -0
  207. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/__init__.py +0 -0
  208. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/_htmlsel.py +0 -0
  209. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/_steps.py +0 -0
  210. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/context.py +0 -0
  211. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/events.py +0 -0
  212. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/api/frame_locator.py +0 -0
  213. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/__init__.py +0 -0
  214. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/browsers/__init__.py +0 -0
  215. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/browsers/_ie_preflight.py +0 -0
  216. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/__init__.py +0 -0
  217. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/_diagnostics.py +0 -0
  218. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/actionability.py +0 -0
  219. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/expect_engine.py +0 -0
  220. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/js/__init__.py +0 -0
  221. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/backends/selenium/resolver.py +0 -0
  222. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/cli/__init__.py +0 -0
  223. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/config.py +0 -0
  224. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/engine.py +0 -0
  225. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/errors.py +0 -0
  226. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/mcp/__init__.py +0 -0
  227. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/observability/__init__.py +0 -0
  228. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/observability/recorder.py +0 -0
  229. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/py.typed +0 -0
  230. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/pytest_plugin.py +0 -0
  231. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/registry.py +0 -0
  232. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/rpa.py +0 -0
  233. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/vision/_images.py +0 -0
  234. {visus_web-0.2.0 → visus_web-0.3.2}/src/visus/web/vision/imaging.py +0 -0
  235. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/actions.html +0 -0
  236. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/backtrack.html +0 -0
  237. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/backtrack_depth.html +0 -0
  238. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/deep.html +0 -0
  239. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/download.txt +0 -0
  240. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/downloads.html +0 -0
  241. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/expects.html +0 -0
  242. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/fields.html +0 -0
  243. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/forms.html +0 -0
  244. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/frames.html +0 -0
  245. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/index.html +0 -0
  246. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/inner.html +0 -0
  247. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/interactions.html +0 -0
  248. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/locators.html +0 -0
  249. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/network.html +0 -0
  250. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/overlay.html +0 -0
  251. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/page2.html +0 -0
  252. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/popups.html +0 -0
  253. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/rpa.html +0 -0
  254. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/shadow.html +0 -0
  255. {visus_web-0.2.0 → visus_web-0.3.2}/tests/fixtures/smartsel.html +0 -0
  256. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_api_wiring.py +0 -0
  257. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_browsers_edge_ie.py +0 -0
  258. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_config.py +0 -0
  259. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_diagnostics.py +0 -0
  260. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_actions.py +0 -0
  261. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_async.py +0 -0
  262. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_async_more.py +0 -0
  263. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_async_parity.py +0 -0
  264. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_backtrack.py +0 -0
  265. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_downloads.py +0 -0
  266. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_edge.py +0 -0
  267. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_edge_ie.py +0 -0
  268. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_edge_rpa.py +0 -0
  269. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_errors.py +0 -0
  270. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_events.py +0 -0
  271. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_expect_more.py +0 -0
  272. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_firefox.py +0 -0
  273. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_frames.py +0 -0
  274. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_input.py +0 -0
  275. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_launch.py +0 -0
  276. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_locators.py +0 -0
  277. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_locators_more.py +0 -0
  278. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_navigation.py +0 -0
  279. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_network.py +0 -0
  280. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_rpa.py +0 -0
  281. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_rpa_basics.py +0 -0
  282. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_smartsel.py +0 -0
  283. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_e2e_vision.py +0 -0
  284. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_engine.py +0 -0
  285. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_errors.py +0 -0
  286. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_events.py +0 -0
  287. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_expect_engine_unit.py +0 -0
  288. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_htmlsel.py +0 -0
  289. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_mcp_list_fields.py +0 -0
  290. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_network_capture.py +0 -0
  291. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_network_capture_cdp.py +0 -0
  292. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_pytest_plugin.py +0 -0
  293. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_registry.py +0 -0
  294. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_steps_backtrack.py +0 -0
  295. {visus_web-0.2.0 → visus_web-0.3.2}/tests/test_tracing_unit.py +0 -0
  296. {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 `locator(page, role=, name=, text=, selector=, frame=)` helper. Add it to the registration assertion in `tests/test_mcp_server.py` and drive it in `tests/test_e2e_mcp.py`.
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
- `src/visus/web/cli/main.py`: an `@app.command()` Typer function over the public API. Test with `typer.testing.CliRunner` (real browser for browser-touching commands).
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`). Run a subset with `--no-cov` (the `--cov-fail-under=90` gate only makes sense on the full run).
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
- /docs/
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`.