wavexis 2.2.3__tar.gz → 2.2.5__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 (213) hide show
  1. {wavexis-2.2.3 → wavexis-2.2.5}/CHANGELOG.md +19 -0
  2. {wavexis-2.2.3 → wavexis-2.2.5}/PKG-INFO +1 -1
  3. {wavexis-2.2.3 → wavexis-2.2.5}/pyproject.toml +1 -1
  4. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/__init__.py +1 -1
  5. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_capture.py +18 -9
  6. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_shared.py +31 -5
  7. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_workflow.py +22 -2
  8. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/exceptions.py +28 -3
  9. {wavexis-2.2.3 → wavexis-2.2.5}/.dockerignore +0 -0
  10. {wavexis-2.2.3 → wavexis-2.2.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {wavexis-2.2.3 → wavexis-2.2.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  12. {wavexis-2.2.3 → wavexis-2.2.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {wavexis-2.2.3 → wavexis-2.2.5}/.github/workflows/ci.yml +0 -0
  14. {wavexis-2.2.3 → wavexis-2.2.5}/.github/workflows/docs.yml +0 -0
  15. {wavexis-2.2.3 → wavexis-2.2.5}/.github/workflows/release.yml +0 -0
  16. {wavexis-2.2.3 → wavexis-2.2.5}/.gitignore +0 -0
  17. {wavexis-2.2.3 → wavexis-2.2.5}/CODE_OF_CONDUCT.md +0 -0
  18. {wavexis-2.2.3 → wavexis-2.2.5}/CONTRIBUTING.md +0 -0
  19. {wavexis-2.2.3 → wavexis-2.2.5}/Dockerfile +0 -0
  20. {wavexis-2.2.3 → wavexis-2.2.5}/LICENSE +0 -0
  21. {wavexis-2.2.3 → wavexis-2.2.5}/README.md +0 -0
  22. {wavexis-2.2.3 → wavexis-2.2.5}/SECURITY.md +0 -0
  23. {wavexis-2.2.3 → wavexis-2.2.5}/docs/api/actions.md +0 -0
  24. {wavexis-2.2.3 → wavexis-2.2.5}/docs/api/backends.md +0 -0
  25. {wavexis-2.2.3 → wavexis-2.2.5}/docs/api/cli.md +0 -0
  26. {wavexis-2.2.3 → wavexis-2.2.5}/docs/api/config.md +0 -0
  27. {wavexis-2.2.3 → wavexis-2.2.5}/docs/api/exceptions.md +0 -0
  28. {wavexis-2.2.3 → wavexis-2.2.5}/docs/api/multi.md +0 -0
  29. {wavexis-2.2.3 → wavexis-2.2.5}/docs/api/plugins.md +0 -0
  30. {wavexis-2.2.3 → wavexis-2.2.5}/docs/api/serve.md +0 -0
  31. {wavexis-2.2.3 → wavexis-2.2.5}/docs/cookbook/auth.md +0 -0
  32. {wavexis-2.2.3 → wavexis-2.2.5}/docs/cookbook/ci-cd.md +0 -0
  33. {wavexis-2.2.3 → wavexis-2.2.5}/docs/cookbook/css.md +0 -0
  34. {wavexis-2.2.3 → wavexis-2.2.5}/docs/cookbook/debug.md +0 -0
  35. {wavexis-2.2.3 → wavexis-2.2.5}/docs/cookbook/record-replay.md +0 -0
  36. {wavexis-2.2.3 → wavexis-2.2.5}/docs/cookbook/scraping.md +0 -0
  37. {wavexis-2.2.3 → wavexis-2.2.5}/docs/cookbook/screenshots.md +0 -0
  38. {wavexis-2.2.3 → wavexis-2.2.5}/docs/cookbook/serve-mode.md +0 -0
  39. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/assert.md +0 -0
  40. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/backends.md +0 -0
  41. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/commands.md +0 -0
  42. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/init.md +0 -0
  43. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/installation.md +0 -0
  44. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/multi.md +0 -0
  45. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/perf.md +0 -0
  46. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/plugins.md +0 -0
  47. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/quickstart.md +0 -0
  48. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/raw.md +0 -0
  49. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/repl.md +0 -0
  50. {wavexis-2.2.3 → wavexis-2.2.5}/docs/guide/troubleshooting.md +0 -0
  51. {wavexis-2.2.3 → wavexis-2.2.5}/docs/index.md +0 -0
  52. {wavexis-2.2.3 → wavexis-2.2.5}/mkdocs.yml +0 -0
  53. {wavexis-2.2.3 → wavexis-2.2.5}/stubs/bidiwave/__init__.pyi +0 -0
  54. {wavexis-2.2.3 → wavexis-2.2.5}/stubs/cdpwave/__init__.pyi +0 -0
  55. {wavexis-2.2.3 → wavexis-2.2.5}/tests/__init__.py +0 -0
  56. {wavexis-2.2.3 → wavexis-2.2.5}/tests/conftest.py +0 -0
  57. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/__init__.py +0 -0
  58. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_a11y.py +0 -0
  59. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_animation.py +0 -0
  60. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_backend_selection.py +0 -0
  61. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_browser.py +0 -0
  62. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_console.py +0 -0
  63. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_css.py +0 -0
  64. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_debug.py +0 -0
  65. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_dialog.py +0 -0
  66. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_dom.py +0 -0
  67. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_dom_snapshot.py +0 -0
  68. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_emulation.py +0 -0
  69. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_emulation_advanced.py +0 -0
  70. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_eval.py +0 -0
  71. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_har.py +0 -0
  72. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_input.py +0 -0
  73. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_media.py +0 -0
  74. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_multi.py +0 -0
  75. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_navigate.py +0 -0
  76. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_network.py +0 -0
  77. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_network_advanced.py +0 -0
  78. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_overlay.py +0 -0
  79. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_pdf.py +0 -0
  80. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_perf.py +0 -0
  81. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_permissions.py +0 -0
  82. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_raw.py +0 -0
  83. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_record.py +0 -0
  84. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_scrape.py +0 -0
  85. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_screenshot.py +0 -0
  86. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_security.py +0 -0
  87. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_serve.py +0 -0
  88. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_service_worker.py +0 -0
  89. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_storage.py +0 -0
  90. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_tabs.py +0 -0
  91. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_webaudio.py +0 -0
  92. {wavexis-2.2.3 → wavexis-2.2.5}/tests/integration/test_webauthn.py +0 -0
  93. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/__init__.py +0 -0
  94. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_abstract_backend_phase5.py +0 -0
  95. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_actions.py +0 -0
  96. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_actions_phase5.py +0 -0
  97. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_animation_action.py +0 -0
  98. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_assert_multi.py +0 -0
  99. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_auth.py +0 -0
  100. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_backend_manager.py +0 -0
  101. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_bidi_backend.py +0 -0
  102. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_bidi_phase5.py +0 -0
  103. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_bluetooth_action.py +0 -0
  104. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_cast_action.py +0 -0
  105. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_cleanup.py +0 -0
  106. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_cli_phase5.py +0 -0
  107. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_config.py +0 -0
  108. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_config_phase5.py +0 -0
  109. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_cookies_headers_actions.py +0 -0
  110. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_css_action.py +0 -0
  111. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_debug_action.py +0 -0
  112. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_dom_snapshot_action.py +0 -0
  113. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_download_screencast.py +0 -0
  114. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_emulation_action.py +0 -0
  115. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_error_handling.py +0 -0
  116. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_exceptions.py +0 -0
  117. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_format_record.py +0 -0
  118. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_global_options.py +0 -0
  119. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_init.py +0 -0
  120. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_input_action.py +0 -0
  121. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_interactions_crawl.py +0 -0
  122. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_manager.py +0 -0
  123. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_media_action.py +0 -0
  124. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_multi.py +0 -0
  125. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_multi_improvements.py +0 -0
  126. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_new_features.py +0 -0
  127. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_output.py +0 -0
  128. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_overlay_action.py +0 -0
  129. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_perf_action.py +0 -0
  130. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_perf_console.py +0 -0
  131. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_plugins.py +0 -0
  132. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_raw.py +0 -0
  133. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_record.py +0 -0
  134. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_repl.py +0 -0
  135. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_serve.py +0 -0
  136. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_service_worker_action.py +0 -0
  137. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_storage_action.py +0 -0
  138. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_webaudio_action.py +0 -0
  139. {wavexis-2.2.3 → wavexis-2.2.5}/tests/unit/test_webauthn_action.py +0 -0
  140. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/__main__.py +0 -0
  141. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/__init__.py +0 -0
  142. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/accessibility.py +0 -0
  143. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/animation.py +0 -0
  144. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/base.py +0 -0
  145. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/bluetooth.py +0 -0
  146. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/browser.py +0 -0
  147. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/cast.py +0 -0
  148. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/console.py +0 -0
  149. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/cookies.py +0 -0
  150. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/crawl.py +0 -0
  151. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/css.py +0 -0
  152. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/debug.py +0 -0
  153. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/dialog.py +0 -0
  154. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/dom.py +0 -0
  155. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/dom_snapshot.py +0 -0
  156. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/download.py +0 -0
  157. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/emulation.py +0 -0
  158. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/eval.py +0 -0
  159. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/extract.py +0 -0
  160. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/form.py +0 -0
  161. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/har.py +0 -0
  162. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/headers.py +0 -0
  163. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/input.py +0 -0
  164. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/lighthouse.py +0 -0
  165. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/media.py +0 -0
  166. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/multi.py +0 -0
  167. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/navigate.py +0 -0
  168. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/network.py +0 -0
  169. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/overlay.py +0 -0
  170. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/pdf.py +0 -0
  171. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/performance.py +0 -0
  172. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/permissions.py +0 -0
  173. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/record.py +0 -0
  174. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/scrape.py +0 -0
  175. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/screencast.py +0 -0
  176. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/screenshot.py +0 -0
  177. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/security.py +0 -0
  178. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/service_worker.py +0 -0
  179. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/session.py +0 -0
  180. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/storage.py +0 -0
  181. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/tabs.py +0 -0
  182. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/wait.py +0 -0
  183. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/webaudio.py +0 -0
  184. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/webauthn.py +0 -0
  185. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/actions/websocket.py +0 -0
  186. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/auth.py +0 -0
  187. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/backend/__init__.py +0 -0
  188. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/backend/base.py +0 -0
  189. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/backend/bidi.py +0 -0
  190. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/backend/cdp.py +0 -0
  191. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/backend/manager.py +0 -0
  192. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cleanup.py +0 -0
  193. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/__init__.py +0 -0
  194. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_advanced.py +0 -0
  195. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_config.py +0 -0
  196. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_debug.py +0 -0
  197. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_emulation.py +0 -0
  198. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_experimental.py +0 -0
  199. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_input.py +0 -0
  200. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_navigation.py +0 -0
  201. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_network.py +0 -0
  202. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_perf.py +0 -0
  203. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_serve.py +0 -0
  204. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/_session.py +0 -0
  205. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/cli/app.py +0 -0
  206. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/config.py +0 -0
  207. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/init.py +0 -0
  208. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/multi.py +0 -0
  209. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/output.py +0 -0
  210. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/plugins.py +0 -0
  211. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/record.py +0 -0
  212. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/repl.py +0 -0
  213. {wavexis-2.2.3 → wavexis-2.2.5}/wavexis/serve.py +0 -0
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to wavexis are documented in this file.
4
4
 
5
+ ## v2.2.5 — 2026-07-07
6
+
7
+ ### Improved
8
+
9
+ - Error messages now include actionable hints for common failures
10
+ - `ElementNotFoundError` suggests `wavexis dom` and `wavexis screenshot` for debugging
11
+ - `WaitTimeoutError` suggests increasing `--timeout` and alternative wait strategies
12
+ - `NavigationError` suggests checking URL, `--timeout`, and `--proxy`
13
+ - `SessionNotInitializedError` suggests `--headed` and checking Chromium installation
14
+ - `BackendNotAvailableError` shows install commands for both backends
15
+ - `MultiConfigError` suggests `--dry-run` to validate config
16
+
17
+ ## v2.2.4 — 2026-07-07
18
+
19
+ ### Added
20
+
21
+ - Progress reporting for long-running operations (`batch`, `multi`, `scrape`)
22
+ - `_progress(current, total, label)` helper shows `[n/total] — label` unless `--quiet`
23
+
5
24
  ## v2.2.3 — 2026-07-07
6
25
 
7
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavexis
3
- Version: 2.2.3
3
+ Version: 2.2.5
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wavexis"
7
- version = "2.2.3"
7
+ version = "2.2.5"
8
8
  description = "Browser automation CLI — wraps cdpwave and bidiwave, no Node.js, no Chromium download"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -1,3 +1,3 @@
1
1
  """wavexis — CLI de automatización de navegador."""
2
2
 
3
- __version__ = "2.2.3"
3
+ __version__ = "2.2.5"
@@ -18,7 +18,9 @@ from wavexis.actions.screenshot import ScreenshotAction
18
18
  from wavexis.cli._shared import (
19
19
  Output,
20
20
  _browser_options,
21
+ _echo,
21
22
  _get_backend,
23
+ _progress,
22
24
  _run_async,
23
25
  _write_json_output,
24
26
  app,
@@ -376,17 +378,24 @@ async def _scrape(
376
378
  ) -> list[dict[str, Any]]:
377
379
  """Async helper for scraping."""
378
380
  backend = _get_backend()
381
+ total = len(urls)
382
+ _echo(f"Scraping {total} URL(s)…")
379
383
  try:
380
384
  await backend.launch(_browser_options())
381
- params = ScrapeParams(
382
- urls=urls,
383
- expression=expression,
384
- file=file,
385
- output_format="json",
386
- selector=selector,
387
- wait=WaitStrategy(strategy="load"),
388
- )
389
- return await ScrapeAction(params).execute(backend)
385
+ results: list[dict[str, Any]] = []
386
+ for i, url in enumerate(urls):
387
+ _progress(i + 1, total, url)
388
+ params = ScrapeParams(
389
+ urls=[url],
390
+ expression=expression,
391
+ file=file,
392
+ output_format="json",
393
+ selector=selector,
394
+ wait=WaitStrategy(strategy="load"),
395
+ )
396
+ result = await ScrapeAction(params).execute(backend)
397
+ results.extend(result)
398
+ return results
390
399
  finally:
391
400
  await backend.close()
392
401
 
@@ -21,6 +21,7 @@ from wavexis.exceptions import (
21
21
  ElementNotFoundError,
22
22
  MultiConfigError,
23
23
  NavigationError,
24
+ SessionNotInitializedError,
24
25
  WaitTimeoutError,
25
26
  WavexisError,
26
27
  )
@@ -39,6 +40,7 @@ __all__ = [
39
40
  "MultiConfigError",
40
41
  "NavigationError",
41
42
  "Output",
43
+ "SessionNotInitializedError",
42
44
  "WaitTimeoutError",
43
45
  "WavexisError",
44
46
  "_browser_options",
@@ -48,6 +50,7 @@ __all__ = [
48
50
  "_get_ctx",
49
51
  "_handle_error",
50
52
  "_load_global_config",
53
+ "_progress",
51
54
  "_run_async",
52
55
  "_write_json_output",
53
56
  "app",
@@ -185,6 +188,21 @@ def _echo(msg: str) -> None:
185
188
  typer.echo(msg)
186
189
 
187
190
 
191
+ def _progress(current: int, total: int, label: str = "") -> None:
192
+ """Print a progress indicator unless quiet mode is active.
193
+
194
+ Args:
195
+ current: Current item index (1-based).
196
+ total: Total number of items.
197
+ label: Optional label to prepend (e.g. URL or action name).
198
+ """
199
+ ctx = _get_ctx()
200
+ if ctx.quiet:
201
+ return
202
+ suffix = f" — {label}" if label else ""
203
+ typer.echo(f"[{current}/{total}]{suffix}")
204
+
205
+
188
206
  def _handle_error(e: Exception) -> None:
189
207
  """Handle a WavexisError with the correct exit code and message.
190
208
 
@@ -193,20 +211,28 @@ def _handle_error(e: Exception) -> None:
193
211
  """
194
212
  if isinstance(e, BackendNotAvailableError):
195
213
  Output.error(
196
- "No backend available. Install cdpwave: pip install wavexis[cdp]"
214
+ "No backend available.\n"
215
+ "Hint: Install cdpwave with `pip install wavexis[cdp]`, "
216
+ "or bidiwave with `pip install wavexis[bidi]`."
197
217
  )
198
218
  raise typer.Exit(EXIT_BACKEND_ERROR) from e
219
+ if isinstance(e, SessionNotInitializedError):
220
+ Output.error(str(e))
221
+ raise typer.Exit(EXIT_BROWSER_ERROR) from e
199
222
  if isinstance(e, NavigationError):
200
- Output.error(f"Navigation failed: {e}")
223
+ Output.error(str(e))
201
224
  raise typer.Exit(EXIT_BROWSER_ERROR) from e
202
225
  if isinstance(e, WaitTimeoutError):
203
- Output.error(f"Timeout waiting: {e}")
226
+ Output.error(str(e))
204
227
  raise typer.Exit(EXIT_BROWSER_ERROR) from e
205
228
  if isinstance(e, ElementNotFoundError):
206
- Output.error(f"Element not found: {e}")
229
+ Output.error(str(e))
207
230
  raise typer.Exit(EXIT_BROWSER_ERROR) from e
208
231
  if isinstance(e, MultiConfigError):
209
- Output.error(f"Invalid multi config: {e}")
232
+ Output.error(
233
+ f"Invalid multi config: {e}\n"
234
+ "Hint: Run `wavexis multi <config> --dry-run` to validate the config."
235
+ )
210
236
  raise typer.Exit(EXIT_CONFIG_ERROR) from e
211
237
  if isinstance(e, WavexisError):
212
238
  Output.error(str(e))
@@ -19,6 +19,7 @@ from wavexis.cli._shared import (
19
19
  _echo,
20
20
  _get_backend,
21
21
  _handle_error,
22
+ _progress,
22
23
  _run_async,
23
24
  app,
24
25
  )
@@ -118,10 +119,20 @@ async def _multi(config_path: Any, parallel: bool = False) -> list[Any]:
118
119
  from wavexis.multi import execute_actions, parse_yaml
119
120
 
120
121
  actions = parse_yaml(config_path)
122
+ total = len(actions)
123
+ _echo(f"Executing {total} action(s)…")
121
124
  backend = _get_backend()
122
125
  await backend.launch(_browser_options())
123
126
  try:
124
- return await execute_actions(actions, backend, parallel=parallel)
127
+ results: list[Any] = []
128
+ if parallel:
129
+ results = await execute_actions(actions, backend, parallel=True)
130
+ else:
131
+ for i, action in enumerate(actions):
132
+ _progress(i + 1, total, str(action))
133
+ result = await execute_actions([action], backend, parallel=False)
134
+ results.extend(result)
135
+ return results
125
136
  finally:
126
137
  await backend.close()
127
138
 
@@ -215,13 +226,22 @@ async def _batch(
215
226
  List of results (or exceptions) in the same order as urls.
216
227
  """
217
228
  semaphore = asyncio.Semaphore(parallel)
229
+ total = len(urls)
230
+ completed = 0
231
+ lock = asyncio.Lock()
218
232
 
219
233
  async def _run_one(url: str) -> Any:
234
+ nonlocal completed
220
235
  async with semaphore:
221
236
  try:
222
- return await _batch_single(url, action, out_dir, expression)
237
+ result = await _batch_single(url, action, out_dir, expression)
238
+ return result
223
239
  except (WavexisError, OSError) as exc:
224
240
  return exc
241
+ finally:
242
+ async with lock:
243
+ completed += 1
244
+ _progress(completed, total, url)
225
245
 
226
246
  tasks = [_run_one(u) for u in urls]
227
247
  return await asyncio.gather(*tasks)
@@ -41,6 +41,21 @@ class BackendNotSupportedError(WavexisError):
41
41
  class SessionNotInitializedError(WavexisError):
42
42
  """Raised when a backend method is called before launch()."""
43
43
 
44
+ def __init__(self, msg: str | None = None) -> None:
45
+ """Initialize the error.
46
+
47
+ Args:
48
+ msg: Optional custom message.
49
+ """
50
+ if msg:
51
+ super().__init__(msg)
52
+ else:
53
+ super().__init__(
54
+ "Browser session not initialized. Call launch() first.\n"
55
+ "Hint: This usually means the browser failed to start. "
56
+ "Try --headed to see the browser window, or check if Chromium is installed."
57
+ )
58
+
44
59
 
45
60
  class NavigationError(WavexisError):
46
61
  """Raised when navigation fails or times out."""
@@ -52,7 +67,11 @@ class NavigationError(WavexisError):
52
67
  url: The URL that failed to navigate to.
53
68
  reason: The failure reason.
54
69
  """
55
- super().__init__(f"Navigation to '{url}' failed: {reason}")
70
+ super().__init__(
71
+ f"Navigation to '{url}' failed: {reason}\n"
72
+ f"Hint: Check the URL is reachable, try --timeout 60000, "
73
+ f"or use --proxy if behind a firewall."
74
+ )
56
75
 
57
76
 
58
77
  class WaitTimeoutError(WavexisError):
@@ -66,7 +85,9 @@ class WaitTimeoutError(WavexisError):
66
85
  timeout_ms: The timeout duration in milliseconds.
67
86
  """
68
87
  super().__init__(
69
- f"Wait strategy '{strategy}' timed out after {timeout_ms}ms"
88
+ f"Wait strategy '{strategy}' timed out after {timeout_ms}ms\n"
89
+ f"Hint: Increase timeout with --timeout {max(timeout_ms * 2, 60000)}, "
90
+ f"or use a different wait strategy (load, domcontentloaded, networkidle)."
70
91
  )
71
92
 
72
93
 
@@ -79,7 +100,11 @@ class ElementNotFoundError(WavexisError):
79
100
  Args:
80
101
  selector: The CSS selector that matched no elements.
81
102
  """
82
- super().__init__(f"No element found matching selector: {selector}")
103
+ super().__init__(
104
+ f"No element found matching selector: {selector}\n"
105
+ f"Hint: Verify the selector with `wavexis dom --selector '{selector}'`, "
106
+ f"or use `wavexis screenshot` to inspect the page visually."
107
+ )
83
108
 
84
109
 
85
110
  class ActionError(WavexisError):
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