wavexis 2.3.4__tar.gz → 2.4.0__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 (229) hide show
  1. {wavexis-2.3.4 → wavexis-2.4.0}/CHANGELOG.md +30 -0
  2. {wavexis-2.3.4 → wavexis-2.4.0}/PKG-INFO +1 -1
  3. {wavexis-2.3.4 → wavexis-2.4.0}/pyproject.toml +1 -1
  4. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_backend_manager.py +61 -0
  5. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_manager.py +31 -0
  6. wavexis-2.4.0/tests/unit/test_network_inspect.py +349 -0
  7. wavexis-2.4.0/tests/unit/test_nl.py +180 -0
  8. wavexis-2.4.0/tests/unit/test_w3_w12_enhancements.py +434 -0
  9. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/__init__.py +1 -1
  10. wavexis-2.4.0/wavexis/actions/axe_audit.py +50 -0
  11. wavexis-2.4.0/wavexis/actions/combined_trace.py +78 -0
  12. wavexis-2.4.0/wavexis/actions/har_replay.py +55 -0
  13. wavexis-2.4.0/wavexis/actions/modify_request.py +56 -0
  14. wavexis-2.4.0/wavexis/actions/visual_diff.py +126 -0
  15. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/base.py +148 -0
  16. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/bidi.py +488 -0
  17. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/cdp.py +473 -0
  18. wavexis-2.4.0/wavexis/cli/_network_inspect.py +327 -0
  19. wavexis-2.4.0/wavexis/cli/_nl.py +102 -0
  20. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/app.py +2 -0
  21. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/serve.py +13 -0
  22. {wavexis-2.3.4 → wavexis-2.4.0}/.dockerignore +0 -0
  23. {wavexis-2.3.4 → wavexis-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  24. {wavexis-2.3.4 → wavexis-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  25. {wavexis-2.3.4 → wavexis-2.4.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  26. {wavexis-2.3.4 → wavexis-2.4.0}/.github/workflows/ci.yml +0 -0
  27. {wavexis-2.3.4 → wavexis-2.4.0}/.github/workflows/docs.yml +0 -0
  28. {wavexis-2.3.4 → wavexis-2.4.0}/.github/workflows/release.yml +0 -0
  29. {wavexis-2.3.4 → wavexis-2.4.0}/.gitignore +0 -0
  30. {wavexis-2.3.4 → wavexis-2.4.0}/CODE_OF_CONDUCT.md +0 -0
  31. {wavexis-2.3.4 → wavexis-2.4.0}/CONTRIBUTING.md +0 -0
  32. {wavexis-2.3.4 → wavexis-2.4.0}/Dockerfile +0 -0
  33. {wavexis-2.3.4 → wavexis-2.4.0}/LICENSE +0 -0
  34. {wavexis-2.3.4 → wavexis-2.4.0}/README.md +0 -0
  35. {wavexis-2.3.4 → wavexis-2.4.0}/SECURITY.md +0 -0
  36. {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/actions.md +0 -0
  37. {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/backends.md +0 -0
  38. {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/cli.md +0 -0
  39. {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/config.md +0 -0
  40. {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/exceptions.md +0 -0
  41. {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/multi.md +0 -0
  42. {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/plugins.md +0 -0
  43. {wavexis-2.3.4 → wavexis-2.4.0}/docs/api/serve.md +0 -0
  44. {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/auth.md +0 -0
  45. {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/ci-cd.md +0 -0
  46. {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/css.md +0 -0
  47. {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/debug.md +0 -0
  48. {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/record-replay.md +0 -0
  49. {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/scraping.md +0 -0
  50. {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/screenshots.md +0 -0
  51. {wavexis-2.3.4 → wavexis-2.4.0}/docs/cookbook/serve-mode.md +0 -0
  52. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/assert.md +0 -0
  53. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/backends.md +0 -0
  54. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/commands.md +0 -0
  55. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/init.md +0 -0
  56. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/installation.md +0 -0
  57. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/multi.md +0 -0
  58. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/perf.md +0 -0
  59. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/plugins.md +0 -0
  60. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/quickstart.md +0 -0
  61. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/raw.md +0 -0
  62. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/repl.md +0 -0
  63. {wavexis-2.3.4 → wavexis-2.4.0}/docs/guide/troubleshooting.md +0 -0
  64. {wavexis-2.3.4 → wavexis-2.4.0}/docs/index.md +0 -0
  65. {wavexis-2.3.4 → wavexis-2.4.0}/mkdocs.yml +0 -0
  66. {wavexis-2.3.4 → wavexis-2.4.0}/stubs/bidiwave/__init__.pyi +0 -0
  67. {wavexis-2.3.4 → wavexis-2.4.0}/stubs/cdpwave/__init__.pyi +0 -0
  68. {wavexis-2.3.4 → wavexis-2.4.0}/tests/__init__.py +0 -0
  69. {wavexis-2.3.4 → wavexis-2.4.0}/tests/conftest.py +0 -0
  70. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/__init__.py +0 -0
  71. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_a11y.py +0 -0
  72. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_animation.py +0 -0
  73. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_backend_selection.py +0 -0
  74. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_browser.py +0 -0
  75. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_console.py +0 -0
  76. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_css.py +0 -0
  77. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_debug.py +0 -0
  78. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_dialog.py +0 -0
  79. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_dom.py +0 -0
  80. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_dom_snapshot.py +0 -0
  81. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_emulation.py +0 -0
  82. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_emulation_advanced.py +0 -0
  83. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_eval.py +0 -0
  84. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_har.py +0 -0
  85. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_input.py +0 -0
  86. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_media.py +0 -0
  87. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_multi.py +0 -0
  88. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_navigate.py +0 -0
  89. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_network.py +0 -0
  90. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_network_advanced.py +0 -0
  91. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_overlay.py +0 -0
  92. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_pdf.py +0 -0
  93. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_perf.py +0 -0
  94. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_permissions.py +0 -0
  95. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_raw.py +0 -0
  96. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_record.py +0 -0
  97. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_scrape.py +0 -0
  98. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_screenshot.py +0 -0
  99. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_security.py +0 -0
  100. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_serve.py +0 -0
  101. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_service_worker.py +0 -0
  102. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_storage.py +0 -0
  103. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_tabs.py +0 -0
  104. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_webaudio.py +0 -0
  105. {wavexis-2.3.4 → wavexis-2.4.0}/tests/integration/test_webauthn.py +0 -0
  106. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/__init__.py +0 -0
  107. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_abstract_backend_phase5.py +0 -0
  108. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_actions.py +0 -0
  109. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_actions_phase5.py +0 -0
  110. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_animation_action.py +0 -0
  111. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_assert_multi.py +0 -0
  112. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_auth.py +0 -0
  113. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_auto_wait.py +0 -0
  114. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_bidi_backend.py +0 -0
  115. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_bidi_phase5.py +0 -0
  116. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_bluetooth_action.py +0 -0
  117. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_cast_action.py +0 -0
  118. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_cleanup.py +0 -0
  119. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_cli_phase5.py +0 -0
  120. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_config.py +0 -0
  121. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_config_phase5.py +0 -0
  122. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_cookies_headers_actions.py +0 -0
  123. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_css_action.py +0 -0
  124. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_debug_action.py +0 -0
  125. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_dom_snapshot_action.py +0 -0
  126. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_download_screencast.py +0 -0
  127. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_emulation_action.py +0 -0
  128. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_error_handling.py +0 -0
  129. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_exceptions.py +0 -0
  130. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_format_record.py +0 -0
  131. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_global_options.py +0 -0
  132. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_iframe.py +0 -0
  133. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_init.py +0 -0
  134. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_input_action.py +0 -0
  135. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_interactions_crawl.py +0 -0
  136. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_media_action.py +0 -0
  137. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_multi.py +0 -0
  138. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_multi_improvements.py +0 -0
  139. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_new_features.py +0 -0
  140. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_output.py +0 -0
  141. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_overlay_action.py +0 -0
  142. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_perf_action.py +0 -0
  143. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_perf_console.py +0 -0
  144. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_plugins.py +0 -0
  145. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_raw.py +0 -0
  146. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_record.py +0 -0
  147. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_repl.py +0 -0
  148. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_serve.py +0 -0
  149. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_service_worker_action.py +0 -0
  150. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_shadow.py +0 -0
  151. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_storage_action.py +0 -0
  152. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_suggest_locator.py +0 -0
  153. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_webaudio_action.py +0 -0
  154. {wavexis-2.3.4 → wavexis-2.4.0}/tests/unit/test_webauthn_action.py +0 -0
  155. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/__main__.py +0 -0
  156. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/__init__.py +0 -0
  157. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/accessibility.py +0 -0
  158. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/animation.py +0 -0
  159. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/base.py +0 -0
  160. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/bluetooth.py +0 -0
  161. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/browser.py +0 -0
  162. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/cast.py +0 -0
  163. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/console.py +0 -0
  164. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/cookies.py +0 -0
  165. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/crawl.py +0 -0
  166. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/css.py +0 -0
  167. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/debug.py +0 -0
  168. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/dialog.py +0 -0
  169. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/dom.py +0 -0
  170. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/dom_snapshot.py +0 -0
  171. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/download.py +0 -0
  172. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/emulation.py +0 -0
  173. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/eval.py +0 -0
  174. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/extract.py +0 -0
  175. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/form.py +0 -0
  176. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/har.py +0 -0
  177. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/headers.py +0 -0
  178. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/input.py +0 -0
  179. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/lighthouse.py +0 -0
  180. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/media.py +0 -0
  181. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/multi.py +0 -0
  182. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/navigate.py +0 -0
  183. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/network.py +0 -0
  184. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/overlay.py +0 -0
  185. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/pdf.py +0 -0
  186. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/performance.py +0 -0
  187. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/permissions.py +0 -0
  188. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/record.py +0 -0
  189. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/scrape.py +0 -0
  190. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/screencast.py +0 -0
  191. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/screenshot.py +0 -0
  192. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/security.py +0 -0
  193. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/service_worker.py +0 -0
  194. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/session.py +0 -0
  195. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/storage.py +0 -0
  196. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/tabs.py +0 -0
  197. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/wait.py +0 -0
  198. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/webaudio.py +0 -0
  199. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/webauthn.py +0 -0
  200. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/actions/websocket.py +0 -0
  201. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/auth.py +0 -0
  202. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/__init__.py +0 -0
  203. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/backend/manager.py +0 -0
  204. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cleanup.py +0 -0
  205. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/__init__.py +0 -0
  206. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_advanced.py +0 -0
  207. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_capture.py +0 -0
  208. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_config.py +0 -0
  209. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_debug.py +0 -0
  210. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_emulation.py +0 -0
  211. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_experimental.py +0 -0
  212. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_iframe.py +0 -0
  213. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_input.py +0 -0
  214. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_navigation.py +0 -0
  215. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_network.py +0 -0
  216. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_perf.py +0 -0
  217. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_serve.py +0 -0
  218. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_session.py +0 -0
  219. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_shadow.py +0 -0
  220. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_shared.py +0 -0
  221. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/cli/_workflow.py +0 -0
  222. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/config.py +0 -0
  223. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/exceptions.py +0 -0
  224. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/init.py +0 -0
  225. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/multi.py +0 -0
  226. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/output.py +0 -0
  227. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/plugins.py +0 -0
  228. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/record.py +0 -0
  229. {wavexis-2.3.4 → wavexis-2.4.0}/wavexis/repl.py +0 -0
@@ -2,6 +2,36 @@
2
2
 
3
3
  All notable changes to wavexis are documented in this file.
4
4
 
5
+ ## v2.4.0 — 2026-07-07
6
+
7
+ ### Added
8
+
9
+ - Request/response body inspection: `get_request_body`, `get_response_body` in both CDP and BiDi backends
10
+ - `wavexis inspect <url> -r <request_id> -t request|response` CLI command
11
+ - Request interception and modification: `modify_request` via CDP Fetch domain in both backends
12
+ - `wavexis modify <url> -p <pattern> -h <header> -m <method>` CLI command
13
+ - HAR replay: `replay_har` reads HAR file and replays requests via browser fetch API
14
+ - `wavexis har-replay <har_path> -u <url> -f <filter>` CLI command
15
+ - Combined tracing: `start_combined_trace`/`stop_combined_trace` capturing screenshots, network, console, and trace events
16
+ - `wavexis trace start|stop` CLI command with `--no-screenshots`, `--no-network`, `--no-console` flags
17
+ - axe-core accessibility audit: `axe_audit` injects axe-core JS and returns violations/passes/incomplete/inapplicable
18
+ - `wavexis axe <url> -o <output>` CLI command
19
+ - Event subscription: `subscribe_events`/`unsubscribe_events` for real-time console, network, dialog, and navigation events
20
+ - `wavexis events subscribe <url> -t <types> -d <duration>` CLI command
21
+ - WebSocket event streaming in serve.py for network_request, network_response, and dialog events
22
+
23
+ ## v2.3.5 — 2026-07-07
24
+
25
+ ### Added
26
+
27
+ - Natural language selector: `find_by_text`, `nl_click`, `nl_fill` in both CDP and BiDi backends
28
+ - `wavexis nl click/fill/find` CLI command with natural language queries
29
+ - Fuzzy text matching scoring: exact match (100), contains query (80), query contains text (60), word overlap (up to 50)
30
+ - Searches textContent, aria-label, placeholder, title, alt, and value attributes
31
+ - Returns best CSS selector for the matched element
32
+ - `--all` flag on `nl find` returns all matches ranked by score
33
+ - `--no-wait` flag to skip auto-waiting
34
+
5
35
  ## v2.3.4 — 2026-07-07
6
36
 
7
37
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wavexis
3
- Version: 2.3.4
3
+ Version: 2.4.0
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.3.4"
7
+ version = "2.4.0"
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" }
@@ -138,6 +138,22 @@ class DummyBackend(AbstractBackend):
138
138
  """Suggest locator."""
139
139
  return selector
140
140
 
141
+ async def find_by_text(
142
+ self, query: str, all: bool = False
143
+ ) -> list[str] | str:
144
+ """Find by text."""
145
+ return query
146
+
147
+ async def nl_click(self, query: str, auto_wait: bool = True) -> None:
148
+ """NL click."""
149
+ pass
150
+
151
+ async def nl_fill(
152
+ self, query: str, value: str, auto_wait: bool = True
153
+ ) -> None:
154
+ """NL fill."""
155
+ pass
156
+
141
157
  async def capture_har(self, params: object) -> dict:
142
158
  """Capture har."""
143
159
  return {}
@@ -588,6 +604,51 @@ class DummyBackend(AbstractBackend):
588
604
  """Bluetooth stop."""
589
605
  pass
590
606
 
607
+ async def get_request_body(self, request_id: str) -> str | None:
608
+ """Get request body."""
609
+ return None
610
+
611
+ async def get_response_body(self, request_id: str) -> str | None:
612
+ """Get response body."""
613
+ return None
614
+
615
+ async def modify_request(
616
+ self, pattern: dict[str, Any], modifications: dict[str, Any]
617
+ ) -> None:
618
+ """Modify request."""
619
+ pass
620
+
621
+ async def replay_har(self, har_path: str, url_filter: str = "") -> None:
622
+ """Replay har."""
623
+ pass
624
+
625
+ async def start_combined_trace(
626
+ self,
627
+ capture_screenshots: bool = True,
628
+ capture_network: bool = True,
629
+ capture_console: bool = True,
630
+ ) -> str:
631
+ """Start combined trace."""
632
+ return "trace-dummy"
633
+
634
+ async def stop_combined_trace(self, trace_id: str) -> dict[str, Any]:
635
+ """Stop combined trace."""
636
+ return {}
637
+
638
+ async def axe_audit(self) -> dict[str, Any]:
639
+ """Axe audit."""
640
+ return {}
641
+
642
+ async def subscribe_events(
643
+ self, event_types: list[str], callback: Any
644
+ ) -> str:
645
+ """Subscribe events."""
646
+ return "sub-dummy"
647
+
648
+ async def unsubscribe_events(self, subscription_id: str) -> None:
649
+ """Unsubscribe events."""
650
+ pass
651
+
591
652
 
592
653
  @pytest.mark.unit
593
654
  class TestBackendManager:
@@ -69,6 +69,13 @@ class FakeBackend(AbstractBackend):
69
69
  async def suggest_locator(self, selector, all=False):
70
70
  """Suggest locator."""
71
71
  return selector
72
+ async def find_by_text(self, query, all=False):
73
+ """Find by text."""
74
+ return query
75
+ async def nl_click(self, query, auto_wait=True):
76
+ """NL click."""
77
+ async def nl_fill(self, query, value, auto_wait=True):
78
+ """NL fill."""
72
79
  async def capture_har(self, params):
73
80
  """Capture har."""
74
81
  async def get_cookies(self):
@@ -279,6 +286,30 @@ class FakeBackend(AbstractBackend):
279
286
  """Shadow click."""
280
287
  async def shadow_fill(self, selectors, value, auto_wait=True):
281
288
  """Shadow fill."""
289
+ async def get_request_body(self, request_id):
290
+ """Get request body."""
291
+ async def get_response_body(self, request_id):
292
+ """Get response body."""
293
+ async def modify_request(self, pattern, modifications):
294
+ """Modify request."""
295
+ async def replay_har(self, har_path, url_filter=""):
296
+ """Replay har."""
297
+ async def start_combined_trace(
298
+ self, capture_screenshots=True, capture_network=True, capture_console=True
299
+ ):
300
+ """Start combined trace."""
301
+ return "trace-fake"
302
+ async def stop_combined_trace(self, trace_id):
303
+ """Stop combined trace."""
304
+ return {}
305
+ async def axe_audit(self):
306
+ """Axe audit."""
307
+ return {}
308
+ async def subscribe_events(self, event_types, callback):
309
+ """Subscribe events."""
310
+ return "sub-fake"
311
+ async def unsubscribe_events(self, subscription_id):
312
+ """Unsubscribe events."""
282
313
 
283
314
 
284
315
  class TestBackendManager:
@@ -0,0 +1,349 @@
1
+ """Unit tests for v2.4.x network inspection, tracing, axe, and events."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from typing import Any
8
+ from unittest.mock import AsyncMock, MagicMock
9
+
10
+ import pytest
11
+
12
+
13
+ def _make_cdp_backend() -> Any:
14
+ """Create a CDPBackend with mocked session."""
15
+ from wavexis.backend.cdp import CDPBackend
16
+
17
+ backend = CDPBackend()
18
+ backend._session = MagicMock()
19
+ backend._session.runtime = MagicMock()
20
+ backend._session.runtime.evaluate = AsyncMock(
21
+ return_value={"result": {"value": None}}
22
+ )
23
+ backend._session.runtime.enable = AsyncMock()
24
+ backend._session.send = AsyncMock(return_value={})
25
+ backend._session.network = MagicMock()
26
+ backend._session.network.enable = AsyncMock()
27
+ backend._session.page = MagicMock()
28
+ backend._session.page.capture_screenshot = AsyncMock(return_value="")
29
+ return backend
30
+
31
+
32
+ def _make_bidi_backend() -> Any:
33
+ """Create a BiDiBackend with mocked client."""
34
+ from wavexis.backend.bidi import BiDiBackend
35
+
36
+ backend = BiDiBackend()
37
+ backend._client = MagicMock()
38
+ backend._context = MagicMock()
39
+ backend._client.script = MagicMock()
40
+ result = MagicMock()
41
+ result.value = None
42
+ backend._client.script.evaluate = AsyncMock(return_value=result)
43
+ backend._client.cdp = MagicMock()
44
+ backend._client.cdp.send_command = AsyncMock(return_value={})
45
+ backend._client.cdp.on = MagicMock()
46
+ backend._client.cdp.off = MagicMock()
47
+ return backend
48
+
49
+
50
+ @pytest.mark.unit
51
+ class TestGetRequestBodyCDP:
52
+ """Tests for get_request_body in CDP backend."""
53
+
54
+ def test_returns_body(self) -> None:
55
+ """Test that get_request_body returns the post data."""
56
+ backend = _make_cdp_backend()
57
+ backend._session.send = AsyncMock(
58
+ return_value={"postData": '{"key": "value"}'}
59
+ )
60
+ result = asyncio.run(backend.get_request_body("req-123"))
61
+ assert result == '{"key": "value"}'
62
+
63
+ def test_returns_none_on_error(self) -> None:
64
+ """Test that get_request_body returns None on error."""
65
+ backend = _make_cdp_backend()
66
+ backend._session.send = AsyncMock(side_effect=Exception("fail"))
67
+ result = asyncio.run(backend.get_request_body("req-123"))
68
+ assert result is None
69
+
70
+
71
+ @pytest.mark.unit
72
+ class TestGetResponseBodyCDP:
73
+ """Tests for get_response_body in CDP backend."""
74
+
75
+ def test_returns_body(self) -> None:
76
+ """Test that get_response_body returns the body."""
77
+ backend = _make_cdp_backend()
78
+ backend._session.send = AsyncMock(
79
+ return_value={"body": "<html>response</html>"}
80
+ )
81
+ result = asyncio.run(backend.get_response_body("req-123"))
82
+ assert result == "<html>response</html>"
83
+
84
+ def test_returns_none_on_error(self) -> None:
85
+ """Test that get_response_body returns None on error."""
86
+ backend = _make_cdp_backend()
87
+ backend._session.send = AsyncMock(side_effect=Exception("fail"))
88
+ result = asyncio.run(backend.get_response_body("req-123"))
89
+ assert result is None
90
+
91
+
92
+ @pytest.mark.unit
93
+ class TestGetRequestBodyBiDi:
94
+ """Tests for get_request_body in BiDi backend."""
95
+
96
+ def test_returns_body(self) -> None:
97
+ """Test that get_request_body returns the post data."""
98
+ backend = _make_bidi_backend()
99
+ backend._client.cdp.send_command = AsyncMock(
100
+ return_value={"postData": '{"key": "value"}'}
101
+ )
102
+ result = asyncio.run(backend.get_request_body("req-123"))
103
+ assert result == '{"key": "value"}'
104
+
105
+ def test_returns_none_on_error(self) -> None:
106
+ """Test that get_request_body returns None on error."""
107
+ backend = _make_bidi_backend()
108
+ backend._client.cdp.send_command = AsyncMock(
109
+ side_effect=Exception("fail")
110
+ )
111
+ result = asyncio.run(backend.get_request_body("req-123"))
112
+ assert result is None
113
+
114
+
115
+ @pytest.mark.unit
116
+ class TestGetResponseBodyBiDi:
117
+ """Tests for get_response_body in BiDi backend."""
118
+
119
+ def test_returns_body(self) -> None:
120
+ """Test that get_response_body returns the body."""
121
+ backend = _make_bidi_backend()
122
+ backend._client.cdp.send_command = AsyncMock(
123
+ return_value={"body": "<html>response</html>"}
124
+ )
125
+ result = asyncio.run(backend.get_response_body("req-123"))
126
+ assert result == "<html>response</html>"
127
+
128
+
129
+ @pytest.mark.unit
130
+ class TestModifyRequestCDP:
131
+ """Tests for modify_request in CDP backend."""
132
+
133
+ def test_calls_fetch_enable(self) -> None:
134
+ """Test that modify_request enables Fetch domain."""
135
+ backend = _make_cdp_backend()
136
+ backend._session.on = MagicMock()
137
+ asyncio.run(
138
+ backend.modify_request(
139
+ {"urlPattern": "*/api/*"},
140
+ {"method": "POST"},
141
+ )
142
+ )
143
+ backend._session.send.assert_any_call(
144
+ "Fetch.enable",
145
+ {"patterns": [{"urlPattern": "*/api/*"}]},
146
+ )
147
+
148
+
149
+ @pytest.mark.unit
150
+ class TestReplayHarCDP:
151
+ """Tests for replay_har in CDP backend."""
152
+
153
+ def test_replays_entries(self, tmp_path: Any) -> None:
154
+ """Test that replay_har reads and replays HAR entries."""
155
+ backend = _make_cdp_backend()
156
+ har_data = {
157
+ "log": {
158
+ "entries": [
159
+ {
160
+ "request": {
161
+ "url": "https://api.example.com/data",
162
+ "method": "GET",
163
+ "headers": [
164
+ {"name": "Accept", "value": "application/json"}
165
+ ],
166
+ }
167
+ }
168
+ ]
169
+ }
170
+ }
171
+ har_path = tmp_path / "test.har"
172
+ har_path.write_text(json.dumps(har_data))
173
+
174
+ asyncio.run(backend.replay_har(str(har_path)))
175
+ backend._session.runtime.evaluate.assert_called_once()
176
+
177
+ def test_filters_by_url(self, tmp_path: Any) -> None:
178
+ """Test that replay_har filters entries by URL."""
179
+ backend = _make_cdp_backend()
180
+ har_data = {
181
+ "log": {
182
+ "entries": [
183
+ {
184
+ "request": {
185
+ "url": "https://api.example.com/data",
186
+ "method": "GET",
187
+ "headers": [],
188
+ }
189
+ },
190
+ {
191
+ "request": {
192
+ "url": "https://other.example.com/data",
193
+ "method": "GET",
194
+ "headers": [],
195
+ }
196
+ },
197
+ ]
198
+ }
199
+ }
200
+ har_path = tmp_path / "test.har"
201
+ har_path.write_text(json.dumps(har_data))
202
+
203
+ asyncio.run(backend.replay_har(str(har_path), url_filter="api.example"))
204
+ backend._session.runtime.evaluate.assert_called_once()
205
+
206
+
207
+ @pytest.mark.unit
208
+ class TestCombinedTraceCDP:
209
+ """Tests for start/stop_combined_trace in CDP backend."""
210
+
211
+ def test_start_returns_trace_id(self) -> None:
212
+ """Test that start_combined_trace returns a trace ID."""
213
+ backend = _make_cdp_backend()
214
+ backend._session.on = MagicMock()
215
+ trace_id = asyncio.run(backend.start_combined_trace())
216
+ assert trace_id.startswith("trace-")
217
+
218
+ def test_stop_returns_data(self) -> None:
219
+ """Test that stop_combined_trace returns collected data."""
220
+ backend = _make_cdp_backend()
221
+ backend._session.on = MagicMock()
222
+ trace_id = asyncio.run(backend.start_combined_trace())
223
+ result = asyncio.run(backend.stop_combined_trace(trace_id))
224
+ assert "trace_events" in result
225
+ assert "screenshots" in result
226
+ assert "network" in result
227
+ assert "console" in result
228
+
229
+ def test_stop_unknown_trace(self) -> None:
230
+ """Test that stop_combined_trace returns error for unknown ID."""
231
+ backend = _make_cdp_backend()
232
+ result = asyncio.run(backend.stop_combined_trace("unknown"))
233
+ assert "error" in result
234
+
235
+
236
+ @pytest.mark.unit
237
+ class TestAxeAuditCDP:
238
+ """Tests for axe_audit in CDP backend."""
239
+
240
+ def test_returns_violations(self) -> None:
241
+ """Test that axe_audit returns audit results."""
242
+ backend = _make_cdp_backend()
243
+ audit_result = {
244
+ "violations": [{"id": "color-contrast"}],
245
+ "passes": [],
246
+ "incomplete": [],
247
+ "inapplicable": [],
248
+ }
249
+ backend._session.runtime.evaluate = AsyncMock(
250
+ return_value={"result": {"value": audit_result}}
251
+ )
252
+ result = asyncio.run(backend.axe_audit())
253
+ assert "violations" in result
254
+ assert len(result["violations"]) == 1
255
+
256
+ def test_returns_error_on_failure(self) -> None:
257
+ """Test that axe_audit returns error dict on failure."""
258
+ backend = _make_cdp_backend()
259
+ backend._session.runtime.evaluate = AsyncMock(
260
+ return_value={"result": {"value": None}}
261
+ )
262
+ result = asyncio.run(backend.axe_audit())
263
+ assert "error" in result
264
+
265
+
266
+ @pytest.mark.unit
267
+ class TestSubscribeEventsCDP:
268
+ """Tests for subscribe/unsubscribe_events in CDP backend."""
269
+
270
+ def test_subscribe_returns_id(self) -> None:
271
+ """Test that subscribe_events returns a subscription ID."""
272
+ backend = _make_cdp_backend()
273
+ backend._session.on = MagicMock()
274
+ sub_id = asyncio.run(
275
+ backend.subscribe_events(["console"], lambda e: None)
276
+ )
277
+ assert sub_id.startswith("sub-")
278
+
279
+ def test_unsubscribe_removes_handlers(self) -> None:
280
+ """Test that unsubscribe_events removes handlers."""
281
+ backend = _make_cdp_backend()
282
+ backend._session.on = MagicMock()
283
+ backend._session.off = MagicMock()
284
+ sub_id = asyncio.run(
285
+ backend.subscribe_events(["console"], lambda e: None)
286
+ )
287
+ asyncio.run(backend.unsubscribe_events(sub_id))
288
+ backend._session.off.assert_called()
289
+
290
+
291
+ @pytest.mark.unit
292
+ class TestCombinedTraceBiDi:
293
+ """Tests for combined trace in BiDi backend."""
294
+
295
+ def test_start_returns_trace_id(self) -> None:
296
+ """Test that start_combined_trace returns a trace ID."""
297
+ backend = _make_bidi_backend()
298
+ trace_id = asyncio.run(backend.start_combined_trace())
299
+ assert trace_id.startswith("trace-")
300
+
301
+ def test_stop_returns_data(self) -> None:
302
+ """Test that stop_combined_trace returns collected data."""
303
+ backend = _make_bidi_backend()
304
+ trace_id = asyncio.run(backend.start_combined_trace())
305
+ result = asyncio.run(backend.stop_combined_trace(trace_id))
306
+ assert "trace_events" in result
307
+ assert "network" in result
308
+
309
+
310
+ @pytest.mark.unit
311
+ class TestAxeAuditBiDi:
312
+ """Tests for axe_audit in BiDi backend."""
313
+
314
+ def test_returns_violations(self) -> None:
315
+ """Test that axe_audit returns audit results."""
316
+ backend = _make_bidi_backend()
317
+ audit_result = {
318
+ "violations": [{"id": "color-contrast"}],
319
+ "passes": [],
320
+ "incomplete": [],
321
+ "inapplicable": [],
322
+ }
323
+ result_mock = MagicMock()
324
+ result_mock.value = audit_result
325
+ backend._client.script.evaluate = AsyncMock(return_value=result_mock)
326
+ result = asyncio.run(backend.axe_audit())
327
+ assert "violations" in result
328
+
329
+
330
+ @pytest.mark.unit
331
+ class TestSubscribeEventsBiDi:
332
+ """Tests for subscribe_events in BiDi backend."""
333
+
334
+ def test_subscribe_returns_id(self) -> None:
335
+ """Test that subscribe_events returns a subscription ID."""
336
+ backend = _make_bidi_backend()
337
+ sub_id = asyncio.run(
338
+ backend.subscribe_events(["console"], lambda e: None)
339
+ )
340
+ assert sub_id.startswith("sub-")
341
+
342
+ def test_unsubscribe_removes_handlers(self) -> None:
343
+ """Test that unsubscribe_events removes handlers."""
344
+ backend = _make_bidi_backend()
345
+ sub_id = asyncio.run(
346
+ backend.subscribe_events(["console"], lambda e: None)
347
+ )
348
+ asyncio.run(backend.unsubscribe_events(sub_id))
349
+ backend._client.cdp.off.assert_called()