nvda-addon-testkit 1.0.0__tar.gz → 1.1.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 (153) hide show
  1. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/PKG-INFO +18 -2
  2. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/README.md +17 -1
  3. nvda_addon_testkit-1.1.0/docs/modules/ROOT/examples/tests_e2e/test_demo_dsl.py +63 -0
  4. nvda_addon_testkit-1.1.0/docs/modules/ROOT/examples/tests_e2e/test_dsl_probes.py +29 -0
  5. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/tests_e2e/test_smoke.py +13 -0
  6. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/nav.adoc +2 -0
  7. nvda_addon_testkit-1.1.0/docs/modules/ROOT/pages/guide/dsl.adoc +154 -0
  8. nvda_addon_testkit-1.1.0/docs/modules/ROOT/pages/guide/modal-dialogs.adoc +48 -0
  9. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/__init__.py +1 -0
  10. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/eval_api.py +29 -0
  11. nvda_addon_testkit-1.1.0/spy/globalPlugins/nvda_testkit_spy/modal_api.py +229 -0
  12. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/_spy/nvda-testkit-spy.nvda-addon +0 -0
  13. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/_version.py +2 -2
  14. nvda_addon_testkit-1.1.0/src/nvda_testkit/actionmark.py +20 -0
  15. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/client.py +76 -3
  16. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/__init__.py +5 -0
  17. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/dialogs.py +60 -0
  18. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/hearing.py +56 -0
  19. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/lifecycle.py +65 -0
  20. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/logsteps.py +67 -0
  21. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/matching.py +44 -0
  22. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/messages.py +105 -0
  23. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/nvda.py +271 -0
  24. nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/waiting.py +26 -0
  25. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/keys.py +15 -3
  26. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/plugin.py +13 -2
  27. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/rpcclient.py +1 -1
  28. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/settings.py +21 -5
  29. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/conftest.py +40 -0
  30. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/fake_nvda.py +41 -0
  31. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_client.py +80 -0
  32. nvda_addon_testkit-1.1.0/tests/test_client_action_mark.py +143 -0
  33. nvda_addon_testkit-1.1.0/tests/test_dsl_dialogs.py +181 -0
  34. nvda_addon_testkit-1.1.0/tests/test_dsl_expecting.py +70 -0
  35. nvda_addon_testkit-1.1.0/tests/test_dsl_hearing.py +111 -0
  36. nvda_addon_testkit-1.1.0/tests/test_dsl_lifecycle.py +126 -0
  37. nvda_addon_testkit-1.1.0/tests/test_dsl_logsteps.py +97 -0
  38. nvda_addon_testkit-1.1.0/tests/test_dsl_messages.py +159 -0
  39. nvda_addon_testkit-1.1.0/tests/test_dsl_nvda.py +88 -0
  40. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_plugin.py +53 -1
  41. nvda_addon_testkit-1.1.0/tests/test_settings.py +126 -0
  42. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_eval_api.py +46 -2
  43. nvda_addon_testkit-1.1.0/tests_spy/test_modal_api.py +203 -0
  44. nvda_addon_testkit-1.0.0/tests/test_settings.py +0 -58
  45. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/CODEOWNERS +0 -0
  46. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/CONTRIBUTING.md +0 -0
  47. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  48. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  49. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  50. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/pull_request_template.md +0 -0
  51. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/renovate.json +0 -0
  52. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/CI.yml +0 -0
  53. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/notify-docs.yml +0 -0
  54. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/prepare-release.yml +0 -0
  55. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/project-sync.yml +0 -0
  56. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/publish-python.yml +0 -0
  57. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/sonar-fork-coverage.yml +0 -0
  58. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/sonar-fork-scan.yml +0 -0
  59. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/sonar.yml +0 -0
  60. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/stale.yml +0 -0
  61. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.github/workflows/zizmor.yml +0 -0
  62. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.gitignore +0 -0
  63. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/.sonarlint/connectedMode.json +0 -0
  64. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/LICENSE +0 -0
  65. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/action.yml +0 -0
  66. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/antora.yml +0 -0
  67. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/demo-addon/build.py +0 -0
  68. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/demo-addon/globalPlugins/testkit_demo.py +0 -0
  69. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/demo-addon/manifest.ini +0 -0
  70. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/tests_e2e/conftest.py +0 -0
  71. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/tests_e2e/test_demo_addon.py +0 -0
  72. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/tests_e2e/test_modal_dialog_investigation.py +0 -0
  73. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/ci-guide.adoc +0 -0
  74. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/contributing.adoc +0 -0
  75. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/example-project.adoc +0 -0
  76. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/addons.adoc +0 -0
  77. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/braille.adoc +0 -0
  78. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/config.adoc +0 -0
  79. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/fixtures.adoc +0 -0
  80. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/keys.adoc +0 -0
  81. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/log.adoc +0 -0
  82. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/speech.adoc +0 -0
  83. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/index.adoc +0 -0
  84. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/installation.adoc +0 -0
  85. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/troubleshooting.adoc +0 -0
  86. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/tutorial.adoc +0 -0
  87. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/pyproject.toml +0 -0
  88. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/ruff.toml +0 -0
  89. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/scripts/next-version.sh +0 -0
  90. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/sonar-project.properties +0 -0
  91. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/addons_api.py +0 -0
  92. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/braille_tap.py +0 -0
  93. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/config_api.py +0 -0
  94. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/input_api.py +0 -0
  95. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/log_tap.py +0 -0
  96. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/mainthread.py +0 -0
  97. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/registry.py +0 -0
  98. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/serialise.py +0 -0
  99. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/server.py +0 -0
  100. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/speech_tap.py +0 -0
  101. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/spy/manifest.ini +0 -0
  102. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/__init__.py +0 -0
  103. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/cli.py +0 -0
  104. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/download.py +0 -0
  105. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/errors.py +0 -0
  106. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/__init__.py +0 -0
  107. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/addons.py +0 -0
  108. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/braille.py +0 -0
  109. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/config.py +0 -0
  110. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/log.py +0 -0
  111. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/speech.py +0 -0
  112. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/portable.py +0 -0
  113. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/process.py +0 -0
  114. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/provisioning.py +0 -0
  115. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/resolve.py +0 -0
  116. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/speechtypes.py +0 -0
  117. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/spybundle.py +0 -0
  118. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/fixtures/snapshot_index_alpha.html +0 -0
  119. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/fixtures/update_check_stable.txt +0 -0
  120. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_addons_namespace.py +0 -0
  121. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_braille_namespace.py +0 -0
  122. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_build_spy.py +0 -0
  123. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_cli.py +0 -0
  124. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_config_namespace.py +0 -0
  125. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_download.py +0 -0
  126. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_e2e_conftest.py +0 -0
  127. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_errors.py +0 -0
  128. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_fake_nvda.py +0 -0
  129. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_keys_namespace.py +0 -0
  130. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_log_namespace.py +0 -0
  131. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_portable.py +0 -0
  132. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_process.py +0 -0
  133. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_provisioning.py +0 -0
  134. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_resolve.py +0 -0
  135. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_rpcclient.py +0 -0
  136. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_speech_namespace.py +0 -0
  137. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests/test_speechtypes.py +0 -0
  138. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/__init__.py +0 -0
  139. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/conftest.py +0 -0
  140. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/nvda_stubs.py +0 -0
  141. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_addons_api.py +0 -0
  142. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_braille_tap.py +0 -0
  143. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_config_api.py +0 -0
  144. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_input_api.py +0 -0
  145. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_log_tap.py +0 -0
  146. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_mainthread.py +0 -0
  147. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_plugin.py +0 -0
  148. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_registry.py +0 -0
  149. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_serialise.py +0 -0
  150. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_server.py +0 -0
  151. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tests_spy/test_speech_tap.py +0 -0
  152. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/tools/build_spy.py +0 -0
  153. {nvda_addon_testkit-1.0.0 → nvda_addon_testkit-1.1.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nvda-addon-testkit
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: End-to-end testing for NVDA add-ons against a real NVDA
5
5
  Project-URL: Homepage, https://github.com/ZirekHQ/nvda-addon-testkit
6
6
  Project-URL: Issues, https://github.com/ZirekHQ/nvda-addon-testkit/issues
@@ -44,6 +44,15 @@ def test_my_addon_announces_itself(nvda, addon_under_test):
44
44
  nvda.log.assert_no_errors()
45
45
  ```
46
46
 
47
+ The same test reads as plain steps with the (experimental) DSL:
48
+
49
+ ```python
50
+ def test_my_addon_announces_itself(nvda, addon_under_test):
51
+ nvda.press("NVDA+shift+m")
52
+ nvda.should_hear("my add-on is ready")
53
+ nvda.should_have_no_errors()
54
+ ```
55
+
47
56
  ## Install
48
57
 
49
58
  ```bash
@@ -106,6 +115,13 @@ exercise NVDA's own restart logic. For that, use `nvda.restart_nvda()`,
106
115
  which triggers NVDA's real `core.restart()` and waits for the replacement
107
116
  process — needs `--nvda-allow-eval`, since it is built on `nvda.eval()`.
108
117
 
118
+ A real `wx.Dialog.ShowModal()` never returns control to any of the above —
119
+ NVDA's main-thread queue doesn't drain while one is up. Open it with
120
+ `nvda.exec_nowait()` instead of `exec()` (queues the scenario without
121
+ waiting for it to finish), then close it with `nvda.simulate_modal(gesture,
122
+ timeout=10.0)`, which sends real injected keyboard input once our process
123
+ takes the foreground.
124
+
109
125
  ## Requirements
110
126
 
111
127
  Windows to run the tests. NVDA is downloaded automatically — you do not need
@@ -138,4 +154,4 @@ If this repository saves you time and effort, please consider supporting it!
138
154
 
139
155
  - ⭐ [Star on GitHub](https://github.com/ZirekHQ/nvda-addon-testkit)
140
156
  - 🐦 [Share on Twitter](https://twitter.com/intent/tweet?text=nvda-addon-testkit%20-%20real%20end-to-end%20testing%20for%20NVDA%20add-ons&url=https%3A%2F%2Fgithub.com%2FZirekHQ%2Fnvda-addon-testkit)
141
- - 💖 [More ways to support](https://github.com/ZirekHQ) — Open Collective coming soon
157
+ - 💖 [Support on Open Collective](https://opencollective.com/zirek)
@@ -17,6 +17,15 @@ def test_my_addon_announces_itself(nvda, addon_under_test):
17
17
  nvda.log.assert_no_errors()
18
18
  ```
19
19
 
20
+ The same test reads as plain steps with the (experimental) DSL:
21
+
22
+ ```python
23
+ def test_my_addon_announces_itself(nvda, addon_under_test):
24
+ nvda.press("NVDA+shift+m")
25
+ nvda.should_hear("my add-on is ready")
26
+ nvda.should_have_no_errors()
27
+ ```
28
+
20
29
  ## Install
21
30
 
22
31
  ```bash
@@ -79,6 +88,13 @@ exercise NVDA's own restart logic. For that, use `nvda.restart_nvda()`,
79
88
  which triggers NVDA's real `core.restart()` and waits for the replacement
80
89
  process — needs `--nvda-allow-eval`, since it is built on `nvda.eval()`.
81
90
 
91
+ A real `wx.Dialog.ShowModal()` never returns control to any of the above —
92
+ NVDA's main-thread queue doesn't drain while one is up. Open it with
93
+ `nvda.exec_nowait()` instead of `exec()` (queues the scenario without
94
+ waiting for it to finish), then close it with `nvda.simulate_modal(gesture,
95
+ timeout=10.0)`, which sends real injected keyboard input once our process
96
+ takes the foreground.
97
+
82
98
  ## Requirements
83
99
 
84
100
  Windows to run the tests. NVDA is downloaded automatically — you do not need
@@ -111,4 +127,4 @@ If this repository saves you time and effort, please consider supporting it!
111
127
 
112
128
  - ⭐ [Star on GitHub](https://github.com/ZirekHQ/nvda-addon-testkit)
113
129
  - 🐦 [Share on Twitter](https://twitter.com/intent/tweet?text=nvda-addon-testkit%20-%20real%20end-to-end%20testing%20for%20NVDA%20add-ons&url=https%3A%2F%2Fgithub.com%2FZirekHQ%2Fnvda-addon-testkit)
114
- - 💖 [More ways to support](https://github.com/ZirekHQ) — Open Collective coming soon
130
+ - 💖 [Support on Open Collective](https://opencollective.com/zirek)
@@ -0,0 +1,63 @@
1
+ """The demo add-on driven through the DSL. Each tagged block is a docs example.
2
+
3
+ This file assumes the demo add-on is not installed when it starts: test_demo_addon.py,
4
+ sorted earlier, uninstalls it last.
5
+ """
6
+
7
+ import pytest
8
+
9
+ # Mirrors RUNNER_ENVIRONMENT_ERRORS in tests_e2e/conftest.py.
10
+ RUNNER_NOISE = (
11
+ r"nvwave|WASAPI|audio (?:device|output|session|endpoint)",
12
+ r"synthDriver|synthesi[sz]|espeak|oneCore|SAPI",
13
+ r"braille ?display|brailleDisplayDriver|brailleInput",
14
+ r"UIAHandler|IAccessible|interactive desktop|desktop object",
15
+ )
16
+
17
+
18
+ @pytest.mark.fresh_nvda
19
+ def test_install_and_remove_are_one_step_each(nvda):
20
+ # tag::dsl-lifecycle[]
21
+ nvda.should_have_addon("testkit-demo", "not installed")
22
+ nvda.install_addon()
23
+ nvda.should_have_addon("testkit-demo", "enabled")
24
+ nvda.remove_addon("testkit-demo")
25
+ nvda.should_have_addon("testkit-demo", "not installed")
26
+ # end::dsl-lifecycle[]
27
+
28
+
29
+ def test_the_gesture_announces_the_phrase(nvda):
30
+ nvda.install_addon()
31
+ # tag::dsl-basic[]
32
+ nvda.press("NVDA+shift+control+d")
33
+ nvda.should_hear("testkit demo says hello")
34
+ # end::dsl-basic[]
35
+
36
+
37
+ def test_startup_logs_the_loaded_message(nvda):
38
+ nvda.install_addon()
39
+ nvda.relaunch()
40
+ nvda.should_log("testkit demo add-on loaded", within=20)
41
+ nvda.should_have_no_errors(ignoring=list(RUNNER_NOISE))
42
+
43
+
44
+ def test_the_block_form_waits_for_the_speech_a_step_causes(nvda):
45
+ nvda.install_addon()
46
+ # tag::dsl-expecting[]
47
+ with nvda.expecting_speech("testkit demo says hello"):
48
+ nvda.press("NVDA+shift+control+d")
49
+ # end::dsl-expecting[]
50
+
51
+
52
+ def test_a_real_dialog_opens_and_closes_in_a_block(require_eval, nvda):
53
+ # tag::dsl-dialog[]
54
+ with nvda.dialog(
55
+ "import wx\n"
56
+ "dlg = wx.MessageDialog(None, 'confirm?', 'confirm?', wx.YES_NO)\n"
57
+ "dlg.ShowModal()\n"
58
+ "dlg.Destroy()\n",
59
+ close_with="enter",
60
+ ):
61
+ pass
62
+ # end::dsl-dialog[]
63
+ nvda.wait_until_idle(timeout=15)
@@ -0,0 +1,29 @@
1
+ """Probes for DSL behaviour not yet verified on real NVDA."""
2
+
3
+ import pytest
4
+
5
+ DIALOG = (
6
+ "import wx\n"
7
+ "dlg = wx.MessageDialog(None, 'confirm?', 'confirm?', wx.YES_NO)\n"
8
+ "dlg.ShowModal()\n"
9
+ "dlg.Destroy()\n"
10
+ )
11
+
12
+
13
+ def test_speech_and_log_reads_work_while_a_modal_is_open(require_eval, nvda):
14
+ nvda.open_dialog(DIALOG)
15
+ try:
16
+ assert isinstance(nvda.speech.index(), int)
17
+ assert isinstance(nvda.log.all(), list)
18
+ finally:
19
+ nvda.close_dialog("enter")
20
+
21
+
22
+ @pytest.mark.parametrize("text", ["Hello", "a.b,c", "x!y"])
23
+ def test_typing_characters_beyond_lowercase_letters(nvda, text):
24
+ nvda.type(text)
25
+
26
+
27
+ @pytest.mark.xfail(strict=False, reason="non-ASCII gesture names are unverified")
28
+ def test_typing_non_ascii_characters(nvda):
29
+ nvda.type("ünï")
@@ -46,3 +46,16 @@ def test_restart_nvda_exercises_core_restart(require_eval, nvda, assert_no_unexp
46
46
  nvda.restart_nvda(timeout=60)
47
47
  assert nvda.process.handshake.pid != old_pid
48
48
  assert_no_unexpected_errors(nvda)
49
+
50
+
51
+ def test_simulate_modal_closes_a_real_dialog(require_eval, nvda):
52
+ # tag::modal[]
53
+ nvda.exec_nowait(
54
+ "import wx\n"
55
+ "dlg = wx.MessageDialog(None, 'confirm?', 'confirm?', wx.YES_NO)\n"
56
+ "dlg.ShowModal()\n"
57
+ "dlg.Destroy()\n"
58
+ )
59
+ assert nvda.simulate_modal("enter", timeout=10)
60
+ # end::modal[]
61
+ nvda.wait_until_idle(timeout=15)
@@ -3,12 +3,14 @@
3
3
  * xref:tutorial.adoc[Writing Your First Test]
4
4
  * Guide
5
5
  ** xref:guide/fixtures.adoc[Fixtures]
6
+ ** xref:guide/dsl.adoc[Writing tests with the DSL]
6
7
  ** xref:guide/speech.adoc[speech]
7
8
  ** xref:guide/braille.adoc[braille]
8
9
  ** xref:guide/keys.adoc[keys]
9
10
  ** xref:guide/config.adoc[config]
10
11
  ** xref:guide/log.adoc[log]
11
12
  ** xref:guide/addons.adoc[addons]
13
+ ** xref:guide/modal-dialogs.adoc[Closing a real modal dialog]
12
14
  * xref:example-project.adoc[Example Project]
13
15
  * xref:ci-guide.adoc[Configuring CI]
14
16
  * xref:troubleshooting.adoc[Troubleshooting]
@@ -0,0 +1,154 @@
1
+ = Writing tests with the DSL
2
+
3
+ NOTE: The DSL is experimental. Names may change in minor releases until it is marked stable.
4
+
5
+ The `nvda` fixture reads as a list of plain steps. Each step is one sentence on one line. Nothing
6
+ needs nesting, and failure messages are plain numbered text with no colour or tables, so they read
7
+ cleanly through a screen reader.
8
+
9
+ The examples assume the add-on under test is installed. Call `nvda.install_addon()` in the test, or
10
+ request the `addon_under_test` fixture.
11
+
12
+ [source,python]
13
+ ----
14
+ def test_the_gesture_announces_the_phrase(nvda):
15
+ include::example$tests_e2e/test_demo_dsl.py[tag=dsl-basic,indent=4]
16
+ ----
17
+
18
+ == Actions
19
+
20
+ Actions send input or change NVDA's state. Each one records where speech begins to count for the
21
+ next assertion, so you never pass `since=`.
22
+
23
+ * `nvda.press("NVDA+t")` sends a gesture.
24
+ * `nvda.type("text")` sends one gesture per character. A character that is not a valid gesture
25
+ name raises an error that names it.
26
+ * `nvda.relaunch()` kills and relaunches the NVDA process. The add-on stays installed.
27
+ * `nvda.restart_nvda()` runs NVDA's own restart. Needs `--nvda-allow-eval`.
28
+
29
+ == Assertions
30
+
31
+ * `nvda.should_hear("text")` waits for speech produced after the last action.
32
+ * `nvda.should_hear(matching=r"\d+:\d+")` does the same with a regular expression.
33
+ * `nvda.should_not_hear("error", for_seconds=1)` watches for a fixed time. Put a `should_hear`
34
+ before it, and raise `for_seconds` on slow machines.
35
+ * `nvda.should_log("text")` waits for a log record. It searches the log since the test began, or
36
+ since the last relaunch, because a relaunch starts a new NVDA process with a fresh log. It does
37
+ not restrict the search to records written after the last action.
38
+ * `nvda.should_have_no_errors()` fails on logged errors. Pass `ignoring=[...]` or set
39
+ `ignore-log-errors` to skip runner noise.
40
+ * `nvda.should_have_addon("name", "enabled")` checks an add-on's state.
41
+
42
+ Text matching is plain and case-insensitive, and finds the text anywhere in an utterance. Use
43
+ `matching=` only when you need a regex. A string pattern is searched case-insensitively; a compiled
44
+ pattern keeps its own flags. The wait defaults to 10 seconds. Change it per call with `within=20`,
45
+ or for the project with `timeout = 20` under `[tool.nvda-testkit]`.
46
+
47
+ Two `should_hear` calls after one action each search all of that action's speech. They do not
48
+ check order, and two identical calls pass on a single occurrence.
49
+
50
+ == Waiting for speech around a block
51
+
52
+ [source,python]
53
+ ----
54
+ def test_it_speaks_after_the_step(nvda):
55
+ include::example$tests_e2e/test_demo_dsl.py[tag=dsl-expecting,indent=4]
56
+ ----
57
+
58
+ If the block relaunches NVDA, the search covers everything the new process said.
59
+
60
+ == Add-on lifecycle
61
+
62
+ Mark the test with `@pytest.mark.fresh_nvda` so it starts from a clean add-on state. The included
63
+ snippet omits the decorator.
64
+
65
+ [source,python]
66
+ ----
67
+ def test_install_and_remove(nvda):
68
+ include::example$tests_e2e/test_demo_dsl.py[tag=dsl-lifecycle,indent=4]
69
+ ----
70
+
71
+ `install_addon()` installs the bundle from the `addon-bundle` setting, or the path you pass, then
72
+ relaunches NVDA and checks that the add-on is enabled. `remove_addon(name)` removes it, relaunches,
73
+ and checks that it is gone. An add-on installed with `install_addon()` is removed again when the
74
+ test ends.
75
+
76
+ == Dialogs
77
+
78
+ [source,python]
79
+ ----
80
+ def test_a_dialog(nvda):
81
+ include::example$tests_e2e/test_demo_dsl.py[tag=dsl-dialog,indent=4]
82
+ ----
83
+
84
+ `open_dialog()` and `close_dialog()` are the same steps without the block. While a dialog is open,
85
+ the steps that send input or change state raise an error instead of hanging: `press`, `type`,
86
+ `relaunch`, `restart_nvda`, `restart_harness`, `install_addon`, `remove_addon`, `should_have_addon`,
87
+ `expecting_speech` and `open_dialog`. The assertions that only read speech and log (`should_hear`,
88
+ `should_not_hear`, `should_log` and `should_have_no_errors`) are not guarded and work while a dialog
89
+ is open. The low-level passthroughs `exec`, `eval`, `exec_nowait`, `simulate_modal`,
90
+ `wait_until_idle`, `reset` and `keys.*` are not guarded and still block. Needs
91
+ `--nvda-allow-eval`.
92
+
93
+ A test that ends with a dialog open fails after the kit closes it.
94
+
95
+ == Teardown
96
+
97
+ When a test ends, the `nvda` fixture runs three checks in order. With `fail-on-log-errors`, it
98
+ checks the log first. It then closes a leaked dialog. It then removes the add-ons installed with
99
+ `install_addon()`. Every check runs even if an earlier one fails, and the problems are reported
100
+ together as one teardown error.
101
+
102
+ == Reading failures
103
+
104
+ A failed `should_hear` states what it expected, the action before it, the time waited, and what was
105
+ heard, numbered, one item per line:
106
+
107
+ ----
108
+ Expected to hear "PM" within 10 seconds after pressing NVDA+t.
109
+ Time elapsed: 10.05 seconds.
110
+ Heard since that action, 2 items:
111
+ 1. "12 colon 00"
112
+ 2. "Tuesday"
113
+ Nothing matched. Matching is case-insensitive plain text; use matching= for a regex.
114
+ ----
115
+
116
+ Long lists in `should_hear` and `should_log` messages show ten items. `should_have_no_errors` shows
117
+ six unexpected and three ignored records. Run with `--nvda-verbose` to see all of them. For
118
+ terminal output without colour, run `pytest --color=no`.
119
+
120
+ == Before and after
121
+
122
+ Without the DSL:
123
+
124
+ [source,python]
125
+ ----
126
+ before = nvda.speech.index()
127
+ nvda.keys.press("NVDA+t")
128
+ found = nvda.speech.wait_for("12:00", timeout=10, since=before)
129
+ assert "12:00" in found.text
130
+ ----
131
+
132
+ With it:
133
+
134
+ [source,python]
135
+ ----
136
+ nvda.press("NVDA+t")
137
+ nvda.should_hear("12:00")
138
+ ----
139
+
140
+ The old API keeps working, and `nvda.speech`, `nvda.keys` and the other namespaces are still there.
141
+
142
+ == Project settings
143
+
144
+ [source,toml]
145
+ ----
146
+ [tool.nvda-testkit]
147
+ timeout = 20
148
+ fail-on-log-errors = true
149
+ ignore-log-errors = ["nvwave", "WASAPI"]
150
+ ----
151
+
152
+ With `fail-on-log-errors`, every test that uses `nvda` reports a teardown error if NVDA logged an
153
+ unignored error. Each `ignore-log-errors` entry is a regular expression, searched
154
+ case-insensitively.
@@ -0,0 +1,48 @@
1
+ = Closing a real modal dialog
2
+
3
+ `nvda.exec()` and `nvda.eval()` both dispatch through NVDA's own main-thread
4
+ queue and wait for the scenario to finish. That works for anything that
5
+ returns on its own, but a real `wx.Dialog.ShowModal()` never drains that
6
+ queue for as long as it's up — confirmed against a real NVDA, not just
7
+ suspected. A scenario that opens one and waits for `exec()` to return will
8
+ sit there until `exec()`'s own timeout fires, with the dialog still open
9
+ afterwards. `nvda.keys.press()` can't reach it either, for the same reason:
10
+ it dispatches through the same queue.
11
+
12
+ The dialog's own message loop is still alive, though — it has to be, to
13
+ receive the click a real user would make. `exec_nowait()` and
14
+ `simulate_modal()` reach it that way instead.
15
+
16
+ [source,python]
17
+ ----
18
+ def test_simulate_modal_closes_a_real_dialog(nvda):
19
+ include::example$tests_e2e/test_smoke.py[tag=modal,indent=4]
20
+ ----
21
+
22
+ `exec_nowait()` queues a scenario the same way `exec()` does, but returns
23
+ immediately instead of waiting for it to finish — freeing this process's
24
+ single-threaded RPC server to accept the next call while the scenario is
25
+ stuck inside `ShowModal()`. A syntax error in the scenario still raises
26
+ `ScenarioSyntaxError` synchronously, the same as `exec()`; only running the
27
+ scenario is deferred.
28
+
29
+ `simulate_modal(gesture, timeout=10.0)` then runs on that next call's own
30
+ thread, never touching the blocked queue: it polls for our own process to
31
+ take the foreground — what a modal dialog does unconditionally on showing —
32
+ and, once it does, sends `gesture` as real injected keyboard input via
33
+ Win32's `SendInput`, the same path a human's keypress takes. It returns
34
+ `False` on a timeout instead of raising, since that usually means the
35
+ scenario never actually opened a dialog rather than NVDA hanging.
36
+ `gesture` is one of `"enter"`, `"escape"`, `"tab"`, `"space"`, `"yes"`, or
37
+ `"no"`.
38
+
39
+ `exec_nowait()` needs `--nvda-allow-eval`, the same as `eval()`/`exec()`,
40
+ because it runs arbitrary code inside NVDA. `simulate_modal()` does not need
41
+ this flag because it only injects keyboard input.
42
+
43
+ Pair the two: `exec_nowait()` to open the dialog, `simulate_modal()` to
44
+ close it. `exec_nowait()` records which window is in the foreground before it
45
+ queues the scenario, and the next `simulate_modal()` call treats only a
46
+ different window as the dialog. Calling `exec()` instead of `exec_nowait()`
47
+ to open the dialog defeats the point — `simulate_modal()`'s call would never
48
+ even be dispatched.
@@ -17,6 +17,7 @@ from . import braille_tap, log_tap, speech_tap
17
17
  from . import config_api as config_api
18
18
  from . import eval_api as eval_api
19
19
  from . import input_api as input_api
20
+ from . import modal_api as modal_api
20
21
  from .server import SpyServer
21
22
 
22
23
 
@@ -11,11 +11,22 @@ exec_in_nvda runs one or more statements and returns whatever the code bound
11
11
  to a name called __result__, or None if it bound nothing -- multi-statement
12
12
  scenarios (e.g. "import core; core.restart()") do not compile under eval()
13
13
  and previously needed an unreadable immediately-invoked-lambda workaround.
14
+
15
+ exec_in_nvda_nowait queues a scenario onto the main thread like exec_in_nvda
16
+ does, but does not wait for it to finish before returning. Use it for a
17
+ scenario that opens a real modal dialog: exec_in_nvda would block this
18
+ process's single-threaded RPC server for the dialog's whole lifetime, so a
19
+ paired simulate_modal call (see modal_api.py) could never even be
20
+ dispatched to close it.
14
21
  """
15
22
 
16
23
  import builtins
17
24
 
25
+ import queueHandler
26
+ from logHandler import log
27
+
18
28
  from .mainthread import run_on_main_thread
29
+ from .modal_api import remember_foreground_baseline
19
30
  from .registry import rpc_method
20
31
 
21
32
  _SCALARS = (str, int, float, bool, type(None))
@@ -50,3 +61,21 @@ def _execute(source):
50
61
  @rpc_method
51
62
  def exec_in_nvda(source, timeout=30.0):
52
63
  return _marshallable(run_on_main_thread(lambda: _execute(source), timeout=timeout))
64
+
65
+
66
+ @rpc_method
67
+ def exec_in_nvda_nowait(source):
68
+ # Compiled here, synchronously, so a SyntaxError still surfaces on this
69
+ # call the same way exec_in_nvda's does -- only *running* the scenario
70
+ # (which may never return, if it opens a modal dialog) gets queued.
71
+ code = compile(source, "<nvda-testkit>", "exec")
72
+
73
+ def _run():
74
+ try:
75
+ exec(code, {"__builtins__": builtins})
76
+ except Exception:
77
+ log.error("nvda-testkit: exec_in_nvda_nowait scenario raised", exc_info=True)
78
+
79
+ remember_foreground_baseline()
80
+ queueHandler.queueFunction(queueHandler.eventQueue, _run)
81
+ return True