What changed, and why it matters
This commit only changes automated test helper code for Trezor hardware wallets. It adds a way for test scripts to run small callback steps while the test framework clicks through on-screen menus, and updates a few Ethereum transaction test flows to use that mechanism. There is no change to the actual wallet firmware, cryptography, user-facing behavior, or security boundaries.
No security action needed. Treat as a normal test-maintenance commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch modifies python/src/trezorlib/debuglink.py (a Python test/debug harness) and tests/input_flows_helpers.py. It introduces a _step() helper and a gen parameter to DebugUI menu-visiting methods so a caller-supplied generator can be advanced between simulated UI clicks. The Ethereum test helper is refactored to use visit_menu_items() with an inline generator instead of manually clicking menu items. No device firmware, protocol, or cryptographic code is touched.
Changed components
python/src/trezorlib/debuglink.pytests/input_flows_helpers.pyInspect captured patch +48 / −17
diff --git a/python/src/trezorlib/debuglink.py b/python/src/trezorlib/debuglink.py
index 07f1f823..358042c4 100644
--- a/python/src/trezorlib/debuglink.py
+++ b/python/src/trezorlib/debuglink.py
@@ -997,6 +997,18 @@ class UnexpectedMenuError(Exception):
return f"Layout content: {self.layout_content}"
+def _step(
+ gen: t.Generator[None, t.Any, None] | None,
+) -> t.Generator[None, t.Any, None] | None:
+ if gen is not None:
+ try:
+ gen.send(None)
+ return gen
+ except StopIteration:
+ pass
+ return None
+
+
class DebugUI:
INPUT_FLOW_DONE = object()
@@ -1021,7 +1033,9 @@ class DebugUI:
else:
self._paginate_and_confirm(br.pages)
- def _visit_vertical_menu(self, menu_layout: LayoutContent) -> None:
+ def _visit_vertical_menu(
+ self, menu_layout: LayoutContent, gen: t.Generator[None, t.Any, None] | None
+ ) -> None:
assert self.debuglink.layout_type in (LayoutType.Delizia, LayoutType.Eckhart)
assert "VerticalMenu" in menu_layout.all_components()
@@ -1031,20 +1045,28 @@ class DebugUI:
menu_buttons = menu_layout.find_unique_value_by_key(
key="buttons", default=None, only_type=list
)
+ if gen:
+ next(gen)
for menu_button, item_button in zip(menu_buttons, item_buttons):
if menu_button.get("skip_test_visit"):
continue # visit only idempotent entries (e.g. for showing more information)
self.debuglink.click(item_button)
+ gen = _step(gen)
self.debuglink.click(close_button)
+ assert gen is None
- def _visit_scrolled_vertical_menu(self, menu_layout: LayoutContent) -> None:
+ def _visit_scrolled_vertical_menu(
+ self, menu_layout: LayoutContent, gen: t.Generator[None, t.Any, None] | None
+ ) -> None:
assert self.debuglink.layout_type is LayoutType.Delizia
assert "ScrolledVerticalMenu" in menu_layout.all_components()
item_buttons = self.debuglink.screen_buttons.vertical_menu_items()
close_button = self.debuglink.screen_buttons.menu()
- _prev, next = self.debuglink.screen_buttons.vertical_menu_prev_next()
+ _prev, next_button = self.debuglink.screen_buttons.vertical_menu_prev_next()
+ if gen:
+ next(gen)
while True:
menu_items = menu_layout.find_unique_value_by_key(
key="menu_items", default=None, only_type=dict
@@ -1053,14 +1075,18 @@ class DebugUI:
if "cancel" in menu_item:
continue # don't click cancel
self.debuglink.click(item_button)
+ gen = _step(gen)
self.debuglink.click(close_button)
if not menu_items["has_next"]:
break
- self.debuglink.click(next)
+ self.debuglink.click(next_button)
menu_layout = self.debuglink.read_layout()
assert "ScrolledVerticalMenu" in menu_layout.all_components()
+ assert gen is None
- def visit_menu_items(self) -> LayoutContent:
+ def visit_menu_items(
+ self, gen: t.Generator[None, t.Any, None] | None = None
+ ) -> LayoutContent:
layout = self.debuglink.read_layout()
if (
not layout.has_menu()
@@ -1082,18 +1108,19 @@ class DebugUI:
if self.debuglink.layout_type is LayoutType.Eckhart:
menu_layout = self.debuglink.read_layout()
if "VerticalMenu" in menu_layout.all_components():
- self._visit_vertical_menu(menu_layout)
+ self._visit_vertical_menu(menu_layout, gen)
else:
raise UnexpectedMenuError(menu_layout.json_str)
elif self.debuglink.layout_type is LayoutType.Delizia:
menu_layout = self.debuglink.read_layout()
if "ScrolledVerticalMenu" in menu_layout.all_components():
- self._visit_scrolled_vertical_menu(menu_layout)
+ self._visit_scrolled_vertical_menu(menu_layout, gen)
elif "VerticalMenu" in menu_layout.all_components():
- self._visit_vertical_menu(menu_layout)
+ self._visit_vertical_menu(menu_layout, gen)
else:
raise UnexpectedMenuError(menu_layout.json_str)
elif self.debuglink.layout_type is LayoutType.Caesar:
+ assert gen is None, "Menu visiting callback not yet supported on Caesar"
menu_items_count = self.debuglink.read_layout().page_count()
for _ in range(menu_items_count):
self.debuglink.press_middle()
diff --git a/tests/input_flows_helpers.py b/tests/input_flows_helpers.py
index cc5ccccb..e78bae98 100644
--- a/tests/input_flows_helpers.py
+++ b/tests/input_flows_helpers.py
@@ -583,10 +583,12 @@ class EthereumFlow:
if go_back_from_summary:
# Get back to the address screen
self.debug.swipe_down()
+ assert (yield).name == "confirm_output"
title = self.debug.read_layout().title()
assert TR.words__address in title
# Get back to the summary screen
self.debug.swipe_up()
+ assert (yield).name == "confirm_total"
layout = self.debug.read_layout()
assert layout.title() == TR.words__title_summary
assert TR.send__maximum_fee in layout.text_content()
@@ -632,10 +634,12 @@ class EthereumFlow:
if go_back_from_summary:
# Get back to the address screen
self.debug.click(self.debug.screen_buttons.cancel())
+ assert (yield).name == "confirm_output"
title = self.debug.read_layout().title()
assert title_exp in title
# Get back to the summary screen
self.debug.click(self.debug.screen_buttons.ok())
+ assert (yield).name == "confirm_total"
layout = self.debug.read_layout()
assert layout.title() == title_exp
assert TR.send__maximum_fee in layout.text_content()
@@ -741,15 +745,15 @@ class EthereumFlow:
elif self.client.layout_type is LayoutType.Delizia:
# confirm intro
if info:
- self.debug.click(self.debug.screen_buttons.menu())
- self.debug.synchronize_at("VerticalMenu")
- self.debug.button_actions.navigate_to_menu_item(0)
- assert self.debug.read_layout().title() in (
- TR.ethereum__staking_stake_address,
- TR.ethereum__staking_claim_address,
- )
- self.debug.click(self.debug.screen_buttons.menu())
- self.debug.click(self.debug.screen_buttons.menu())
+
+ def check_address():
+ yield
+ assert self.debug.read_layout().title() in (
+ TR.ethereum__staking_stake_address,
+ TR.ethereum__staking_claim_address,
+ )
+
+ self.client.ui.visit_menu_items(check_address())
self.debug.swipe_up()
br = yield
Why this scored 13/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.