ragger 1.41.2__tar.gz → 1.42.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 (171) hide show
  1. {ragger-1.41.2 → ragger-1.42.0}/CHANGELOG.md +6 -0
  2. {ragger-1.41.2/src/ragger.egg-info → ragger-1.42.0}/PKG-INFO +1 -1
  3. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/__version__.py +3 -3
  4. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/backend/speculos.py +27 -2
  5. {ragger-1.41.2 → ragger-1.42.0/src/ragger.egg-info}/PKG-INFO +1 -1
  6. {ragger-1.41.2 → ragger-1.42.0}/tests/functional/backend/test_speculos.py +27 -0
  7. {ragger-1.41.2 → ragger-1.42.0}/.codeql-config.yaml +0 -0
  8. {ragger-1.41.2 → ragger-1.42.0}/.gitattributes +0 -0
  9. {ragger-1.41.2 → ragger-1.42.0}/.github/workflows/build_and_tests.yml +0 -0
  10. {ragger-1.41.2 → ragger-1.42.0}/.github/workflows/codeql-analysis.yml +0 -0
  11. {ragger-1.41.2 → ragger-1.42.0}/.github/workflows/documentation.yml +0 -0
  12. {ragger-1.41.2 → ragger-1.42.0}/.github/workflows/fast-checks.yml +0 -0
  13. {ragger-1.41.2 → ragger-1.42.0}/.github/workflows/force-rebase.yml +0 -0
  14. {ragger-1.41.2 → ragger-1.42.0}/.gitignore +0 -0
  15. {ragger-1.41.2 → ragger-1.42.0}/LICENSE +0 -0
  16. {ragger-1.41.2 → ragger-1.42.0}/MANIFEST.in +0 -0
  17. {ragger-1.41.2 → ragger-1.42.0}/README.md +0 -0
  18. {ragger-1.41.2 → ragger-1.42.0}/doc/Makefile +0 -0
  19. {ragger-1.41.2 → ragger-1.42.0}/doc/_static/layout.css +0 -0
  20. {ragger-1.41.2 → ragger-1.42.0}/doc/_templates/layout.html +0 -0
  21. {ragger-1.41.2 → ragger-1.42.0}/doc/architecture.rst +0 -0
  22. {ragger-1.41.2 → ragger-1.42.0}/doc/conf.py +0 -0
  23. {ragger-1.41.2 → ragger-1.42.0}/doc/faq.rst +0 -0
  24. {ragger-1.41.2 → ragger-1.42.0}/doc/glossary.rst +0 -0
  25. {ragger-1.41.2 → ragger-1.42.0}/doc/images/navigate.draw +0 -0
  26. {ragger-1.41.2 → ragger-1.42.0}/doc/images/navigate.svg +0 -0
  27. {ragger-1.41.2 → ragger-1.42.0}/doc/images/ragger.png +0 -0
  28. {ragger-1.41.2 → ragger-1.42.0}/doc/images/stax_infos.png +0 -0
  29. {ragger-1.41.2 → ragger-1.42.0}/doc/images/stax_welcome.png +0 -0
  30. {ragger-1.41.2 → ragger-1.42.0}/doc/images/usage.draw +0 -0
  31. {ragger-1.41.2 → ragger-1.42.0}/doc/images/usage.svg +0 -0
  32. {ragger-1.41.2 → ragger-1.42.0}/doc/index.rst +0 -0
  33. {ragger-1.41.2 → ragger-1.42.0}/doc/installation.rst +0 -0
  34. {ragger-1.41.2 → ragger-1.42.0}/doc/rationale.rst +0 -0
  35. {ragger-1.41.2 → ragger-1.42.0}/doc/source.rst +0 -0
  36. {ragger-1.41.2 → ragger-1.42.0}/doc/tutorial.rst +0 -0
  37. {ragger-1.41.2 → ragger-1.42.0}/doc/tutorial_conftest.rst +0 -0
  38. {ragger-1.41.2 → ragger-1.42.0}/doc/tutorial_installation.rst +0 -0
  39. {ragger-1.41.2 → ragger-1.42.0}/doc/tutorial_screen.rst +0 -0
  40. {ragger-1.41.2 → ragger-1.42.0}/ledger_app.toml +0 -0
  41. {ragger-1.41.2 → ragger-1.42.0}/pyproject.toml +0 -0
  42. {ragger-1.41.2 → ragger-1.42.0}/setup.cfg +0 -0
  43. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/__init__.py +0 -0
  44. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/backend/__init__.py +0 -0
  45. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/backend/interface.py +0 -0
  46. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/backend/ledgercomm.py +0 -0
  47. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/backend/ledgerwallet.py +0 -0
  48. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/backend/physical_backend.py +0 -0
  49. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/backend/stub.py +0 -0
  50. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/bip/__init__.py +0 -0
  51. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/bip/path.py +0 -0
  52. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/bip/seed.py +0 -0
  53. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/conftest/__init__.py +0 -0
  54. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/conftest/base_conftest.py +0 -0
  55. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/conftest/configuration.py +0 -0
  56. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/error.py +0 -0
  57. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/firmware/__init__.py +0 -0
  58. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/firmware/structs.py +0 -0
  59. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/firmware/touch/__init__.py +0 -0
  60. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/firmware/touch/element.py +0 -0
  61. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/firmware/touch/layouts.py +0 -0
  62. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/firmware/touch/positions.py +0 -0
  63. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/firmware/touch/screen.py +0 -0
  64. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/firmware/touch/use_cases.py +0 -0
  65. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/__init__.py +0 -0
  66. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanos_body.png +0 -0
  67. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanos_leftbutton.png +0 -0
  68. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanos_rightbutton.png +0 -0
  69. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanosp_body.png +0 -0
  70. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanosp_leftbutton.png +0 -0
  71. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanosp_rightbutton.png +0 -0
  72. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanox_body.png +0 -0
  73. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanox_leftbutton.png +0 -0
  74. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/nanox_rightbutton.png +0 -0
  75. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/stax_body.png +0 -0
  76. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/swipe_left_action.png +0 -0
  77. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/swipe_right_action.png +0 -0
  78. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/assets/touch_action.png +0 -0
  79. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/interface.py +0 -0
  80. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/gui/process.py +0 -0
  81. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/logger.py +0 -0
  82. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/navigator/__init__.py +0 -0
  83. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/navigator/instruction.py +0 -0
  84. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/navigator/nano_navigator.py +0 -0
  85. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/navigator/navigation_scenario.py +0 -0
  86. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/navigator/navigator.py +0 -0
  87. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/navigator/touch_navigator.py +0 -0
  88. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/py.typed +0 -0
  89. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/utils/__init__.py +0 -0
  90. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/utils/misc.py +0 -0
  91. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/utils/packing.py +0 -0
  92. {ragger-1.41.2 → ragger-1.42.0}/src/ragger/utils/structs.py +0 -0
  93. {ragger-1.41.2 → ragger-1.42.0}/src/ragger.egg-info/SOURCES.txt +0 -0
  94. {ragger-1.41.2 → ragger-1.42.0}/src/ragger.egg-info/dependency_links.txt +0 -0
  95. {ragger-1.41.2 → ragger-1.42.0}/src/ragger.egg-info/requires.txt +0 -0
  96. {ragger-1.41.2 → ragger-1.42.0}/src/ragger.egg-info/top_level.txt +0 -0
  97. {ragger-1.41.2 → ragger-1.42.0}/template/.dispatch_to_your_test_folder +0 -0
  98. {ragger-1.41.2 → ragger-1.42.0}/template/conftest.py +0 -0
  99. {ragger-1.41.2 → ragger-1.42.0}/template/usage.md +0 -0
  100. {ragger-1.41.2 → ragger-1.42.0}/tests/__init__.py +0 -0
  101. {ragger-1.41.2 → ragger-1.42.0}/tests/functional/__init__.py +0 -0
  102. {ragger-1.41.2 → ragger-1.42.0}/tests/functional/backend/__init__.py +0 -0
  103. {ragger-1.41.2 → ragger-1.42.0}/tests/functional/conftest.py +0 -0
  104. {ragger-1.41.2 → ragger-1.42.0}/tests/functional/navigator/__init__.py +0 -0
  105. {ragger-1.41.2 → ragger-1.42.0}/tests/functional/navigator/test_navigator.py +0 -0
  106. {ragger-1.41.2 → ragger-1.42.0}/tests/functional/test_boilerplate.py +0 -0
  107. {ragger-1.41.2 → ragger-1.42.0}/tests/pytest.ini +0 -0
  108. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/apex_p/waiting_screen/00000.png +0 -0
  109. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/apex_p/waiting_screen/00001.png +0 -0
  110. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/apex_p/waiting_screen/00002.png +0 -0
  111. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/apex_p/waiting_screen/00003.png +0 -0
  112. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/apex_p/waiting_screen/00004.png +0 -0
  113. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/flex/waiting_screen/00000.png +0 -0
  114. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/flex/waiting_screen/00001.png +0 -0
  115. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/flex/waiting_screen/00002.png +0 -0
  116. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/flex/waiting_screen/00003.png +0 -0
  117. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/flex/waiting_screen/00004.png +0 -0
  118. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/generic/00000.png +0 -0
  119. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/generic/00001.png +0 -0
  120. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/generic/00002.png +0 -0
  121. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/generic/00003.png +0 -0
  122. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/generic/00004.png +0 -0
  123. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare/00000.png +0 -0
  124. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare/00001.png +0 -0
  125. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare/00002.png +0 -0
  126. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare/00003.png +0 -0
  127. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare/00004.png +0 -0
  128. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare/00005.png +0 -0
  129. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare/00006.png +0 -0
  130. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare/00007.png +0 -0
  131. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare_no_golden/00000.png +0 -0
  132. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare_wrong_golden/00000.png +0 -0
  133. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_and_compare_wrong_golden/00001.png +0 -0
  134. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_until_text_and_compare/00000.png +0 -0
  135. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_until_text_and_compare/00001.png +0 -0
  136. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_until_text_and_compare/00002.png +0 -0
  137. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/nanos/test_navigate_until_text_and_compare/00003.png +0 -0
  138. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/stax/waiting_screen/00000.png +0 -0
  139. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/stax/waiting_screen/00001.png +0 -0
  140. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/stax/waiting_screen/00002.png +0 -0
  141. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/stax/waiting_screen/00003.png +0 -0
  142. {ragger-1.41.2 → ragger-1.42.0}/tests/snapshots/stax/waiting_screen/00004.png +0 -0
  143. {ragger-1.41.2 → ragger-1.42.0}/tests/stubs.py +0 -0
  144. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/__init__.py +0 -0
  145. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/backend/__init__.py +0 -0
  146. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/backend/test_interface.py +0 -0
  147. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/backend/test_ledgercomm.py +0 -0
  148. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/backend/test_ledgerwallet.py +0 -0
  149. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/backend/test_physical_backend.py +0 -0
  150. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/backend/test_speculos.py +0 -0
  151. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/backend/test_stub.py +0 -0
  152. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/bip/test_path.py +0 -0
  153. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/bip/test_seed.py +0 -0
  154. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/conftests/__init__.py +0 -0
  155. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/conftests/test_base_conftest.py +0 -0
  156. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/firmware/test_structs_Firmware.py +0 -0
  157. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/firmware/touch/test_element.py +0 -0
  158. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/firmware/touch/test_screen_FullScreen.py +0 -0
  159. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/firmware/touch/test_screen_MetaScreen.py +0 -0
  160. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/helpers.py +0 -0
  161. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/navigator/__init__.py +0 -0
  162. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/navigator/test_nano_navigator.py +0 -0
  163. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/navigator/test_navigation_scenario.py +0 -0
  164. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/navigator/test_navigator.py +0 -0
  165. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/navigator/test_touch_navigator.py +0 -0
  166. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/test_error_ApplicationError.py +0 -0
  167. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/utils/__init__.py +0 -0
  168. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/utils/test_misc.py +0 -0
  169. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/utils/test_packing.py +0 -0
  170. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/utils/test_path.py +0 -0
  171. {ragger-1.41.2 → ragger-1.42.0}/tests/unit/utils/test_structs.py +0 -0
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.42.0] - 2026-01-29
9
+
10
+ ### Changed
11
+
12
+ - Detect error APDUs during navigation to have proper error APDUs exceptions instead of timeouts
13
+
8
14
  ## [1.41.2] - 2026-01-08
9
15
 
10
16
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ragger
3
- Version: 1.41.2
3
+ Version: 1.42.0
4
4
  Summary: Testing framework using Speculos and LedgerComm as backends
5
5
  Author-email: Ledger <hello@ledger.fr>
6
6
  Project-URL: Homepage, https://github.com/LedgerHQ/ragger
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.41.2'
32
- __version_tuple__ = version_tuple = (1, 41, 2)
31
+ __version__ = version = '1.42.0'
32
+ __version_tuple__ = version_tuple = (1, 42, 0)
33
33
 
34
- __commit_id__ = commit_id = 'g9c99cb587'
34
+ __commit_id__ = commit_id = 'g16d6f9e71'
@@ -120,6 +120,7 @@ class SpeculosBackend(BackendInterface):
120
120
  api_url=self.url,
121
121
  **kwargs)
122
122
  self._pending: Optional[ApduResponse] = None
123
+ self._pending_async_response: Optional[ApduResponse] = None
123
124
  self._last_screenshot: Optional[BytesIO] = None
124
125
  self._home_screenshot: Optional[BytesIO] = None
125
126
  self._ticker_paused_count = 0
@@ -137,6 +138,15 @@ class SpeculosBackend(BackendInterface):
137
138
  def apdu_timeout(self, value: float) -> None:
138
139
  self._apdu_timeout = value
139
140
 
141
+ def _check_async_error(self) -> None:
142
+ """Check for async APDU errors and raise if present."""
143
+ if self._pending_async_response is not None and self._last_async_response is None:
144
+ if has_data_available(self._pending_async_response, timeout=0):
145
+ self.logger.info("[Ragger] Early async data available, retrieving it now.")
146
+ # This will raise ExceptionRAPDU immediately if status != 9000
147
+ self._last_async_response = self._get_last_async_response(
148
+ self._pending_async_response)
149
+
140
150
  def _retrieve_client_screen_content(self) -> dict:
141
151
  raw_content = self._client.get_current_screen_content()
142
152
  # Keep only text events
@@ -214,13 +224,22 @@ class SpeculosBackend(BackendInterface):
214
224
  @contextmanager
215
225
  def exchange_async_raw(self, data: bytes = b"") -> Generator[bool, None, None]:
216
226
  self.apdu_logger.info("=> %s", data.hex())
227
+ # Reset state for this new async exchange
228
+ self._last_async_response = None
217
229
  with self._client.apdu_exchange_nowait(cla=data[0],
218
230
  ins=data[1],
219
231
  p1=data[2],
220
232
  p2=data[3],
221
233
  data=data[5:]) as response:
222
- yield has_data_available(response, timeout=self.apdu_timeout)
223
- self._last_async_response = self._get_last_async_response(response)
234
+ self._pending_async_response = response
235
+ try:
236
+ yield has_data_available(response, timeout=self.apdu_timeout)
237
+ # Only retrieve if not already retrieved by _check_async_error during navigation
238
+ if self._last_async_response is None:
239
+ self._last_async_response = self._get_last_async_response(response)
240
+ finally:
241
+ # Clear pending async response flag in all cases
242
+ self._pending_async_response = None
224
243
 
225
244
  def right_click(self) -> None:
226
245
  self._client.press_and_release("right")
@@ -315,6 +334,12 @@ class SpeculosBackend(BackendInterface):
315
334
  if not screenshot_equal(screenshot, self._last_screenshot):
316
335
  break
317
336
 
337
+ # Check for async APDU errors before sending a tick. This ensures that if the
338
+ # application has already refused the APDU (e.g., due to an error), we detect and raise
339
+ # the error immediately instead of waiting for a screen change that will never occur.
340
+ # This makes navigation robust and prevents hanging.
341
+ self._check_async_error()
342
+
318
343
  # Send a ticker event and let the app process it
319
344
  self.send_tick()
320
345
  screenshot = BytesIO(self._client.get_screenshot())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ragger
3
- Version: 1.41.2
3
+ Version: 1.42.0
4
4
  Summary: Testing framework using Speculos and LedgerComm as backends
5
5
  Author-email: Ledger <hello@ledger.fr>
6
6
  Project-URL: Homepage, https://github.com/LedgerHQ/ragger
@@ -147,3 +147,30 @@ class TestbackendSpeculos(TestCase):
147
147
  self.backend.right_click()
148
148
  self.backend.left_click()
149
149
  self.backend.both_click()
150
+
151
+ def test_async_error_raised_during_navigation(self):
152
+ """
153
+ Ensure that an async APDU error is raised immediately during navigation (e.g., while waiting for a screen change),
154
+ not just at context exit. This simulates an error response being available before navigation completes.
155
+ Also verifies that sequential async exchanges properly reset state and retrieve fresh responses.
156
+ """
157
+ with patch("speculos.client.subprocess"):
158
+ with SpeculosServerStub():
159
+ with self.backend:
160
+ # Start an async exchange with an error APDU (not 0x9000)
161
+ with self.assertRaises(ExceptionRAPDU) as error:
162
+ with self.backend.exchange_async_raw(bytes.fromhex("01000000")):
163
+ # Simulate navigation that would trigger the async error check
164
+ # (wait_for_screen_change calls _check_async_error)
165
+ self.backend.wait_for_screen_change(timeout=0.5)
166
+ # This line should be unreachable - if reached, the error wasn't raised during navigation
167
+ assert False, "Expected ExceptionRAPDU was not raised during navigation" # pragma: no cover
168
+ self.assertEqual(error.exception.status, APDUStatus.ERROR)
169
+
170
+ # Perform a second async exchange with a SUCCESS APDU to ensure state is properly reset
171
+ # If state wasn't reset, this could incorrectly raise due to the previous error response
172
+ with self.assertRaises(TimeoutError):
173
+ with self.backend.exchange_async_raw(bytes.fromhex("00000000")):
174
+ self.backend.wait_for_screen_change(timeout=1)
175
+ # Verify that the response was successfully retrieved despite the timeout
176
+ self.assertEqual(self.backend.last_async_response.status, APDUStatus.SUCCESS)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes