nvda-addon-testkit 0.1.3__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.
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/PKG-INFO +30 -2
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/README.md +29 -1
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/tests_e2e/conftest.py +6 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/tests_e2e/test_demo_addon.py +5 -5
- nvda_addon_testkit-1.1.0/docs/modules/ROOT/examples/tests_e2e/test_demo_dsl.py +63 -0
- nvda_addon_testkit-1.1.0/docs/modules/ROOT/examples/tests_e2e/test_dsl_probes.py +29 -0
- nvda_addon_testkit-1.1.0/docs/modules/ROOT/examples/tests_e2e/test_modal_dialog_investigation.py +67 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/tests_e2e/test_smoke.py +21 -1
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/nav.adoc +2 -0
- nvda_addon_testkit-1.1.0/docs/modules/ROOT/pages/guide/dsl.adoc +154 -0
- nvda_addon_testkit-1.1.0/docs/modules/ROOT/pages/guide/modal-dialogs.adoc +48 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/troubleshooting.adoc +1 -1
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/__init__.py +1 -0
- nvda_addon_testkit-1.1.0/spy/globalPlugins/nvda_testkit_spy/eval_api.py +81 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/mainthread.py +11 -2
- nvda_addon_testkit-1.1.0/spy/globalPlugins/nvda_testkit_spy/modal_api.py +229 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/registry.py +3 -1
- nvda_addon_testkit-1.1.0/src/nvda_testkit/_spy/nvda-testkit-spy.nvda-addon +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/_version.py +2 -2
- nvda_addon_testkit-1.1.0/src/nvda_testkit/actionmark.py +20 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/client.py +225 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/__init__.py +5 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/dialogs.py +60 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/hearing.py +56 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/lifecycle.py +65 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/logsteps.py +67 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/matching.py +44 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/messages.py +105 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/nvda.py +271 -0
- nvda_addon_testkit-1.1.0/src/nvda_testkit/dsl/waiting.py +26 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/errors.py +14 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/addons.py +1 -1
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/keys.py +15 -3
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/plugin.py +15 -4
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/process.py +55 -1
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/rpcclient.py +11 -4
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/settings.py +21 -5
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/conftest.py +40 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/fake_nvda.py +46 -0
- nvda_addon_testkit-1.1.0/tests/test_client.py +392 -0
- nvda_addon_testkit-1.1.0/tests/test_client_action_mark.py +143 -0
- nvda_addon_testkit-1.1.0/tests/test_dsl_dialogs.py +181 -0
- nvda_addon_testkit-1.1.0/tests/test_dsl_expecting.py +70 -0
- nvda_addon_testkit-1.1.0/tests/test_dsl_hearing.py +111 -0
- nvda_addon_testkit-1.1.0/tests/test_dsl_lifecycle.py +126 -0
- nvda_addon_testkit-1.1.0/tests/test_dsl_logsteps.py +97 -0
- nvda_addon_testkit-1.1.0/tests/test_dsl_messages.py +159 -0
- nvda_addon_testkit-1.1.0/tests/test_dsl_nvda.py +88 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_plugin.py +53 -1
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_process.py +113 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_rpcclient.py +23 -1
- nvda_addon_testkit-1.1.0/tests/test_settings.py +126 -0
- nvda_addon_testkit-1.1.0/tests_spy/test_eval_api.py +129 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_mainthread.py +34 -0
- nvda_addon_testkit-1.1.0/tests_spy/test_modal_api.py +203 -0
- nvda_addon_testkit-0.1.3/spy/globalPlugins/nvda_testkit_spy/eval_api.py +0 -35
- nvda_addon_testkit-0.1.3/src/nvda_testkit/_spy/nvda-testkit-spy.nvda-addon +0 -0
- nvda_addon_testkit-0.1.3/src/nvda_testkit/client.py +0 -110
- nvda_addon_testkit-0.1.3/tests/test_client.py +0 -123
- nvda_addon_testkit-0.1.3/tests/test_settings.py +0 -58
- nvda_addon_testkit-0.1.3/tests_spy/test_eval_api.py +0 -47
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/CODEOWNERS +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/CONTRIBUTING.md +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/pull_request_template.md +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/renovate.json +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/CI.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/notify-docs.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/prepare-release.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/project-sync.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/publish-python.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/sonar-fork-coverage.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/sonar-fork-scan.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/sonar.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/stale.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.github/workflows/zizmor.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.gitignore +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/.sonarlint/connectedMode.json +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/LICENSE +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/action.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/antora.yml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/demo-addon/build.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/demo-addon/globalPlugins/testkit_demo.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/examples/demo-addon/manifest.ini +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/ci-guide.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/contributing.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/example-project.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/addons.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/braille.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/config.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/fixtures.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/keys.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/log.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/guide/speech.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/index.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/installation.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/tutorial.adoc +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/pyproject.toml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/ruff.toml +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/scripts/next-version.sh +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/sonar-project.properties +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/addons_api.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/braille_tap.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/config_api.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/input_api.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/log_tap.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/serialise.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/server.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/globalPlugins/nvda_testkit_spy/speech_tap.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/spy/manifest.ini +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/__init__.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/cli.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/download.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/__init__.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/braille.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/config.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/log.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/namespaces/speech.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/portable.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/provisioning.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/resolve.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/speechtypes.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/src/nvda_testkit/spybundle.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/fixtures/snapshot_index_alpha.html +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/fixtures/update_check_stable.txt +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_addons_namespace.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_braille_namespace.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_build_spy.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_cli.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_config_namespace.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_download.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_e2e_conftest.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_errors.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_fake_nvda.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_keys_namespace.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_log_namespace.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_portable.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_provisioning.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_resolve.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_speech_namespace.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests/test_speechtypes.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/__init__.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/conftest.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/nvda_stubs.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_addons_api.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_braille_tap.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_config_api.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_input_api.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_log_tap.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_plugin.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_registry.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_serialise.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_server.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tests_spy/test_speech_tap.py +0 -0
- {nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/tools/build_spy.py +0 -0
- {nvda_addon_testkit-0.1.3 → 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:
|
|
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
|
|
@@ -94,6 +103,25 @@ still on Extended Security Updates).
|
|
|
94
103
|
| `nvda.log` | structured log records, and `assert_no_errors()` |
|
|
95
104
|
| `nvda.addons` | two-phase install, remove, and state |
|
|
96
105
|
|
|
106
|
+
`nvda.eval()` runs a single expression inside NVDA; `nvda.exec()` runs a
|
|
107
|
+
full multi-statement scenario and returns whatever it binds to
|
|
108
|
+
`__result__`. Both need `--nvda-allow-eval`. A bad scenario raises
|
|
109
|
+
`ScenarioSyntaxError`, so catching bare `except Exception: pass` around
|
|
110
|
+
either call still swallows it — catch the types you expect instead.
|
|
111
|
+
|
|
112
|
+
`nvda.restart_harness()` kills and relaunches the NVDA process — use it to
|
|
113
|
+
finish a two-phase add-on install or reset to a clean process. It does not
|
|
114
|
+
exercise NVDA's own restart logic. For that, use `nvda.restart_nvda()`,
|
|
115
|
+
which triggers NVDA's real `core.restart()` and waits for the replacement
|
|
116
|
+
process — needs `--nvda-allow-eval`, since it is built on `nvda.eval()`.
|
|
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
|
+
|
|
97
125
|
## Requirements
|
|
98
126
|
|
|
99
127
|
Windows to run the tests. NVDA is downloaded automatically — you do not need
|
|
@@ -126,4 +154,4 @@ If this repository saves you time and effort, please consider supporting it!
|
|
|
126
154
|
|
|
127
155
|
- ⭐ [Star on GitHub](https://github.com/ZirekHQ/nvda-addon-testkit)
|
|
128
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)
|
|
129
|
-
- 💖 [
|
|
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
|
|
@@ -67,6 +76,25 @@ still on Extended Security Updates).
|
|
|
67
76
|
| `nvda.log` | structured log records, and `assert_no_errors()` |
|
|
68
77
|
| `nvda.addons` | two-phase install, remove, and state |
|
|
69
78
|
|
|
79
|
+
`nvda.eval()` runs a single expression inside NVDA; `nvda.exec()` runs a
|
|
80
|
+
full multi-statement scenario and returns whatever it binds to
|
|
81
|
+
`__result__`. Both need `--nvda-allow-eval`. A bad scenario raises
|
|
82
|
+
`ScenarioSyntaxError`, so catching bare `except Exception: pass` around
|
|
83
|
+
either call still swallows it — catch the types you expect instead.
|
|
84
|
+
|
|
85
|
+
`nvda.restart_harness()` kills and relaunches the NVDA process — use it to
|
|
86
|
+
finish a two-phase add-on install or reset to a clean process. It does not
|
|
87
|
+
exercise NVDA's own restart logic. For that, use `nvda.restart_nvda()`,
|
|
88
|
+
which triggers NVDA's real `core.restart()` and waits for the replacement
|
|
89
|
+
process — needs `--nvda-allow-eval`, since it is built on `nvda.eval()`.
|
|
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
|
+
|
|
70
98
|
## Requirements
|
|
71
99
|
|
|
72
100
|
Windows to run the tests. NVDA is downloaded automatically — you do not need
|
|
@@ -99,4 +127,4 @@ If this repository saves you time and effort, please consider supporting it!
|
|
|
99
127
|
|
|
100
128
|
- ⭐ [Star on GitHub](https://github.com/ZirekHQ/nvda-addon-testkit)
|
|
101
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)
|
|
102
|
-
- 💖 [
|
|
130
|
+
- 💖 [Support on Open Collective](https://opencollective.com/zirek)
|
|
@@ -65,3 +65,9 @@ def check_no_unexpected_errors(client, *, since: int = 0) -> None:
|
|
|
65
65
|
@pytest.fixture
|
|
66
66
|
def assert_no_unexpected_errors():
|
|
67
67
|
return check_no_unexpected_errors
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def require_eval(pytestconfig):
|
|
72
|
+
if not pytestconfig.option.nvda_allow_eval:
|
|
73
|
+
pytest.skip("needs --nvda-allow-eval")
|
|
@@ -27,19 +27,19 @@ def test_install_is_two_phase_and_completes_on_restart(
|
|
|
27
27
|
assert info.name == "testkit-demo"
|
|
28
28
|
assert nvda.addons.state("testkit-demo") is AddonState.PENDING_INSTALL
|
|
29
29
|
|
|
30
|
-
nvda.
|
|
30
|
+
nvda.restart_harness()
|
|
31
31
|
assert nvda.addons.state("testkit-demo") is AddonState.ENABLED
|
|
32
32
|
assert_no_unexpected_errors(nvda)
|
|
33
33
|
|
|
34
34
|
nvda.addons.remove("testkit-demo")
|
|
35
|
-
nvda.
|
|
35
|
+
nvda.restart_harness()
|
|
36
36
|
assert nvda.addons.state("testkit-demo") is AddonState.NOT_INSTALLED
|
|
37
37
|
# end::addons[]
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def test_the_installed_addon_logs_at_startup(nvda, addon_under_test):
|
|
41
41
|
# tag::log[]
|
|
42
|
-
nvda.
|
|
42
|
+
nvda.restart_harness()
|
|
43
43
|
nvda.log.wait_for(re.escape(STARTUP_MESSAGE), since=0, timeout=20)
|
|
44
44
|
# end::log[]
|
|
45
45
|
|
|
@@ -53,7 +53,7 @@ def test_its_gesture_produces_the_expected_speech(nvda, addon_under_test):
|
|
|
53
53
|
|
|
54
54
|
def test_it_survives_a_restart(nvda, addon_under_test):
|
|
55
55
|
# tag::fixtures[]
|
|
56
|
-
nvda.
|
|
56
|
+
nvda.restart_harness()
|
|
57
57
|
assert nvda.addons.state("testkit-demo") is AddonState.ENABLED
|
|
58
58
|
before = nvda.speech.index()
|
|
59
59
|
nvda.keys.press("NVDA+shift+control+d")
|
|
@@ -66,5 +66,5 @@ def test_removal_is_also_two_phase(nvda, addon_under_test):
|
|
|
66
66
|
and that session-scoped fixture will not reinstall it."""
|
|
67
67
|
nvda.addons.remove("testkit-demo")
|
|
68
68
|
assert nvda.addons.state("testkit-demo") is AddonState.PENDING_REMOVE
|
|
69
|
-
nvda.
|
|
69
|
+
nvda.restart_harness()
|
|
70
70
|
assert nvda.addons.state("testkit-demo") is AddonState.NOT_INSTALLED
|
|
@@ -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ï")
|
nvda_addon_testkit-1.1.0/docs/modules/ROOT/examples/tests_e2e/test_modal_dialog_investigation.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Investigation for issue #34, gaps 2 & 5: does a queued main-thread job
|
|
2
|
+
start while a real ShowModal() dialog is up?
|
|
3
|
+
|
|
4
|
+
This is not a regression test. It is a one-shot probe: run it once on
|
|
5
|
+
Windows CI, read the result, and act on it per the plan (either open a new
|
|
6
|
+
issue describing a real fix, or proceed with the simulate_modal() fallback
|
|
7
|
+
in the next task). Delete or keep this file once the investigation is
|
|
8
|
+
resolved -- it is not meant to run on every CI build.
|
|
9
|
+
|
|
10
|
+
Everything happens inside a single exec_in_nvda call, entirely on NVDA's
|
|
11
|
+
main thread: the spy's XML-RPC server (server.py) is a plain, unthreaded
|
|
12
|
+
SimpleXMLRPCServer, so two separate RPC connections can never be genuinely
|
|
13
|
+
concurrent at the transport level -- the second call simply can't be
|
|
14
|
+
dispatched until the first's handler returns. Queuing the second job from
|
|
15
|
+
*inside* the running scenario, via queueHandler.queueFunction directly,
|
|
16
|
+
avoids needing RPC-level concurrency at all: it tests whether NVDA's own
|
|
17
|
+
queue-draining mechanism still runs while the main thread is nested inside
|
|
18
|
+
ShowModal()'s event loop, using a timestamp comparison instead of a second
|
|
19
|
+
network round trip.
|
|
20
|
+
|
|
21
|
+
A third, lower-probability outcome is possible: if the dialog's own
|
|
22
|
+
wx.CallLater dismiss timer never fires for some unrelated reason, this
|
|
23
|
+
scenario never returns from ShowModal(), so nvda.exec() raises an RpcError
|
|
24
|
+
(wrapping exec_in_nvda's server-side "started on NVDA's main thread but did
|
|
25
|
+
not return within 30.0s" timeout) instead of returning a __result__ to
|
|
26
|
+
assert on. If you see that on a real run, it's worth investigating
|
|
27
|
+
separately -- it doesn't confirm or refute the queue-blocking hypothesis
|
|
28
|
+
either way.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_a_second_job_while_a_real_modal_is_up(require_eval, nvda):
|
|
33
|
+
scenario = (
|
|
34
|
+
"import queueHandler\n"
|
|
35
|
+
"import time\n"
|
|
36
|
+
"import wx\n"
|
|
37
|
+
"job_b_ran_at = []\n"
|
|
38
|
+
# exec_in_nvda runs this with separate globals/locals dicts (like a
|
|
39
|
+
# class body), so a nested def can't see job_b_ran_at/time as
|
|
40
|
+
# globals -- bind both as defaults, evaluated now, in this scope.
|
|
41
|
+
"def job_b(sink=job_b_ran_at, now=time.monotonic):\n"
|
|
42
|
+
" sink.append(now())\n"
|
|
43
|
+
"queueHandler.queueFunction(queueHandler.eventQueue, job_b)\n"
|
|
44
|
+
"dlg = wx.MessageDialog(None, 'probe', 'probe', wx.YES_NO)\n"
|
|
45
|
+
"wx.CallLater(1000, dlg.EndModal, wx.ID_YES)\n"
|
|
46
|
+
"before = time.monotonic()\n"
|
|
47
|
+
"dlg.ShowModal()\n"
|
|
48
|
+
"after = time.monotonic()\n"
|
|
49
|
+
"dlg.Destroy()\n"
|
|
50
|
+
"__result__ = {\n"
|
|
51
|
+
" 'ran_during_modal': bool(job_b_ran_at) and before <= job_b_ran_at[0] <= after,\n"
|
|
52
|
+
" 'ran_at_all': bool(job_b_ran_at),\n"
|
|
53
|
+
"}\n"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
result = nvda.exec(scenario)
|
|
57
|
+
|
|
58
|
+
assert result["ran_during_modal"], (
|
|
59
|
+
"CONFIRMS THE DEADLOCK: a second main-thread job never ran while a "
|
|
60
|
+
f"real ShowModal() dialog was up (ran_at_all={result['ran_at_all']!r}). "
|
|
61
|
+
"Proceed with Task 7 (the simulate_modal() fallback)."
|
|
62
|
+
)
|
|
63
|
+
# If this assertion passes instead, the queue IS drained during
|
|
64
|
+
# ShowModal() in this NVDA version: STOP, do not proceed to Task 7, and
|
|
65
|
+
# open a new issue describing this finding plus what actually blocks
|
|
66
|
+
# input_api.py's keys_press() from dismissing the dialog (if anything
|
|
67
|
+
# still does).
|
|
@@ -37,5 +37,25 @@ def test_config_round_trips_through_a_real_nvda(nvda):
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def test_startup_produced_no_errors(nvda, assert_no_unexpected_errors):
|
|
40
|
-
nvda.
|
|
40
|
+
nvda.restart_harness()
|
|
41
41
|
assert_no_unexpected_errors(nvda)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_restart_nvda_exercises_core_restart(require_eval, nvda, assert_no_unexpected_errors):
|
|
45
|
+
old_pid = nvda.process.handshake.pid
|
|
46
|
+
nvda.restart_nvda(timeout=60)
|
|
47
|
+
assert nvda.process.handshake.pid != old_pid
|
|
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.
|
{nvda_addon_testkit-0.1.3 → nvda_addon_testkit-1.1.0}/docs/modules/ROOT/pages/troubleshooting.adoc
RENAMED
|
@@ -63,7 +63,7 @@ the current one; clean it out or narrow the pattern.
|
|
|
63
63
|
fails.
|
|
64
64
|
|
|
65
65
|
Install is two-phase by design — `install()` only reaches
|
|
66
|
-
`PENDING_INSTALL`; a call to `nvda.
|
|
66
|
+
`PENDING_INSTALL`; a call to `nvda.restart_harness()` is what completes it. See
|
|
67
67
|
xref:guide/addons.adoc[].
|
|
68
68
|
|
|
69
69
|
== Speech assertion mismatches
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""Run code inside NVDA's own process: one expression, or a scenario.
|
|
3
|
+
|
|
4
|
+
The host refuses to call either unless the session opted in, so the spy does
|
|
5
|
+
not second-guess it: the point is to reach NVDA's live state, which means
|
|
6
|
+
full builtins and real imports. Both run on the main thread for the same
|
|
7
|
+
reason every other mutation does.
|
|
8
|
+
|
|
9
|
+
eval_in_nvda evaluates a single expression and returns its value.
|
|
10
|
+
exec_in_nvda runs one or more statements and returns whatever the code bound
|
|
11
|
+
to a name called __result__, or None if it bound nothing -- multi-statement
|
|
12
|
+
scenarios (e.g. "import core; core.restart()") do not compile under eval()
|
|
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.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import builtins
|
|
24
|
+
|
|
25
|
+
import queueHandler
|
|
26
|
+
from logHandler import log
|
|
27
|
+
|
|
28
|
+
from .mainthread import run_on_main_thread
|
|
29
|
+
from .modal_api import remember_foreground_baseline
|
|
30
|
+
from .registry import rpc_method
|
|
31
|
+
|
|
32
|
+
_SCALARS = (str, int, float, bool, type(None))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _marshallable(value):
|
|
36
|
+
"""xmlrpc carries scalars and containers of scalars; everything else is a repr."""
|
|
37
|
+
if isinstance(value, _SCALARS):
|
|
38
|
+
return value
|
|
39
|
+
if isinstance(value, dict):
|
|
40
|
+
return {str(key): _marshallable(item) for key, item in value.items()}
|
|
41
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
42
|
+
return [_marshallable(item) for item in value]
|
|
43
|
+
return repr(value)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _evaluate(source):
|
|
47
|
+
return eval(source, {"__builtins__": builtins})
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@rpc_method
|
|
51
|
+
def eval_in_nvda(source, timeout=30.0):
|
|
52
|
+
return _marshallable(run_on_main_thread(lambda: _evaluate(source), timeout=timeout))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _execute(source):
|
|
56
|
+
scope = {"__builtins__": builtins}
|
|
57
|
+
exec(compile(source, "<nvda-testkit>", "exec"), scope)
|
|
58
|
+
return scope.get("__result__")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@rpc_method
|
|
62
|
+
def exec_in_nvda(source, timeout=30.0):
|
|
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
|