feat(core/tests): device menu click tests
What changed, and why it matters
This commit is almost entirely a test-suite expansion for the Trezor Safe 7 device menu. It adds automated click tests for settings like auto-lock, device label, LED, haptic feedback, brightness, backup checks, notifications, menu traversal, and device wipe. A small amount of production code changed: a new 'led' field was added to the Features protobuf message so the device can report RGB LED support, and a few UI debug-trace strings were improved to make automated testing easier. There is no obvious security vulnerability in the diff.
No security action required. Reviewers may optionally confirm that the new 'led' Features field does not leak sensitive information and that storage_device.get_rgb_led() returns only a non-sensitive configuration bit, which the diff suggests is the case.
Security signals we found
New protobuf field 'led' in Features message exposes RGB LED setting/state to host
Test-only debug trace additions increase UI introspection surface
No changes to PIN, passphrase, signing, storage, or bootloader security logic
No input parsing, buffer handling, or memory allocation changes in firmware
Evidence from the diff
The commit adds ~2,100 lines of test code under tests/click_tests/device_menu/ for the Eckhart (T3W1/Trezor Safe 7) layout. It refactors shared passphrase/label keyboard helpers into tests/click_tests/common.py and extends python/src/trezorlib/debuglink.py with helpers for reading labels, clicking the header back button, and choosing label characters. Production changes are minimal: (1) common/protob/messages-management.proto adds optional bool led=58 to Features, propagated to generated Python/Rust bindings and core/src/apps/base.py where f.led is populated from storage when USE_RGB_LED is set; (2) Rust UI trace implementations for PassphraseKeyboard, Header, DeviceMenuScreen, and RegulatoryScreen emit extra debug fields used by the tests. No cryptographic, authorization, or memory-safety changes are present.
Changed components
common/protob/messages-management.protocore/src/apps/base.pycore/src/trezor/messages.pypython/src/trezorlib/messages.pypython/src/trezorlib/debuglink.pyrust/trezor-client/src/protos/generated/messages_management.rscore/embed/rust UI trace code (layout_bolt, layout_eckhart)tests/click_tests/device_menu/*tests/ui_tests/fixtures.jsonInspect captured patch +2180 / −272
diff --git a/common/protob/messages-management.proto b/common/protob/messages-management.proto
index 84e0919d..5347d142 100644
--- a/common/protob/messages-management.proto
+++ b/common/protob/messages-management.proto
@@ -129,6 +129,7 @@ message Features {
optional uint32 soc = 55; // Battery state of charge (0 - 100%)
optional bool firmware_corrupted = 56; // true if the firmware is corrupted
optional uint32 auto_lock_delay_battery_ms = 57; // number of milliseconds after which the battery-powered device locks itself
+ optional bool led = 58; // RGB LED settings
enum BackupAvailability {
/// Device is already backed up, or a previous backup has failed.
diff --git a/core/embed/rust/src/ui/layout_bolt/component/keyboard/passphrase.rs b/core/embed/rust/src/ui/layout_bolt/component/keyboard/passphrase.rs
index 2788a162..64073bde 100644
--- a/core/embed/rust/src/ui/layout_bolt/component/keyboard/passphrase.rs
+++ b/core/embed/rust/src/ui/layout_bolt/component/keyboard/passphrase.rs
@@ -49,6 +49,36 @@ const KEYBOARD: [[&str; KEY_COUNT]; PAGE_COUNT] = [
["_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^="],
];
+/// Enum keeping track of which keyboard is shown and which comes next. Keep the
+/// number of values and the constant PAGE_COUNT in sync.
+#[repr(u32)]
+#[derive(Copy, Clone, PartialEq)]
+#[cfg_attr(feature = "ui_debug", derive(ufmt::derive::uDebug))]
+pub(crate) enum KeyboardLayout {
+ Numeric = 0,
+ LettersLower = 1,
+ LettersUpper = 2,
+ Special = 3,
+}
+
+impl KeyboardLayout {
+ /// Number of variants (kept in sync with the enum by using the last
+ /// discriminant).
+ pub const VARIANT_COUNT: usize = KeyboardLayout::Special as usize + 1;
+
+ /// Map page index -> layout (bounds must be valid).
+ pub const fn from_page_unchecked(page: usize) -> Self {
+ // Order must match KEYBOARD rows.
+ const MAP: [KeyboardLayout; KeyboardLayout::VARIANT_COUNT] = [
+ KeyboardLayout::Numeric,
+ KeyboardLayout::LettersLower,
+ KeyboardLayout::LettersUpper,
+ KeyboardLayout::Special,
+ ];
+ MAP[page]
+ }
+}
+
const INPUT_AREA_HEIGHT: i16 = ScrollBar::DOT_SIZE + 9;
impl PassphraseKeyboard {
@@ -377,7 +407,11 @@ impl Component for Input {
#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for PassphraseKeyboard {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
+ let page = self.scrollbar.pager().current();
+ debug_assert!(page < PAGE_COUNT as u16);
+ let active_layout = uformat!("{:?}", KeyboardLayout::from_page_unchecked(page.into()));
t.component("PassphraseKeyboard");
+ t.string("active_layout", active_layout.as_str().into());
t.string("passphrase", self.passphrase().into());
}
}
diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
index 822cef90..ab89d13e 100644
--- a/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/firmware/device_menu_screen.rs
@@ -1127,6 +1127,11 @@ impl crate::trace::Trace for DeviceMenuScreen {
match self.active_screen.deref() {
ActiveScreen::Menu(ref screen, ..) => {
t.child("Menu", screen);
+ if let Subscreen::Submenu(_, id) =
+ self.subscreens[usize::from(self.active_subscreen)]
+ {
+ t.int("MenuId", u8::from(id).into());
+ }
}
ActiveScreen::Device(ref screen) => {
t.child("Device", screen);
diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/header.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/header.rs
index 71b44dfb..b6e9618a 100644
--- a/core/embed/rust/src/ui/layout_eckhart/firmware/header.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/firmware/header.rs
@@ -234,7 +234,16 @@ impl crate::trace::Trace for Header {
t.component("Header");
t.child("title", &self.title);
if let Some(button) = &self.right_button {
- t.child("button", button);
+ t.child("right_button", button);
+ }
+ if let Some(button) = &self.left_button {
+ t.child("left_button", button);
+ }
+ if let Some(icon) = &self.icon {
+ t.string("icon", icon.name.into());
+ }
+ if let Some(fuel_gauge) = &self.fuel_gauge {
+ t.child("fuel_gauge", fuel_gauge);
}
}
}
diff --git a/core/embed/rust/src/ui/layout_eckhart/firmware/regulatory_screen.rs b/core/embed/rust/src/ui/layout_eckhart/firmware/regulatory_screen.rs
index bf6c6836..a23a0374 100644
--- a/core/embed/rust/src/ui/layout_eckhart/firmware/regulatory_screen.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/firmware/regulatory_screen.rs
@@ -136,6 +136,7 @@ impl crate::trace::Trace for RegulatoryScreen {
t.child("Header", &self.header);
t.child("Content", &self.content);
t.child("ActionBar", &self.action_bar);
+ t.int("page_count", self.content.pager().total() as i64);
}
}
@@ -316,5 +317,11 @@ impl Component for RegulatoryContent {
impl crate::trace::Trace for RegulatoryContent {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("RegulatoryContent");
+
+ let current = self.pager.current();
+ let zone = &RegulatoryContent::ZONES[current as usize];
+
+ t.string("subtitle", zone.name.unwrap_or_default().into());
+ t.string("content", zone.content.into());
}
}
diff --git a/core/src/apps/base.py b/core/src/apps/base.py
index e7a9531f..8fc7460b 100644
--- a/core/src/apps/base.py
+++ b/core/src/apps/base.py
@@ -227,6 +227,9 @@ def get_features() -> Features:
storage_device.get_autolock_delay_battery_ms()
)
+ if utils.USE_RGB_LED:
+ f.led = storage_device.get_rgb_led()
+
return f
diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py
index fd6e07d4..2d94acdc 100644
--- a/core/src/trezor/messages.py
+++ b/core/src/trezor/messages.py
@@ -2088,6 +2088,7 @@ if TYPE_CHECKING:
soc: "int | None"
firmware_corrupted: "bool | None"
auto_lock_delay_battery_ms: "int | None"
+ led: "bool | None"
def __init__(
self,
@@ -2146,6 +2147,7 @@ if TYPE_CHECKING:
soc: "int | None" = None,
firmware_corrupted: "bool | None" = None,
auto_lock_delay_battery_ms: "int | None" = None,
+ led: "bool | None" = None,
) -> None:
pass
diff --git a/python/src/trezorlib/debuglink.py b/python/src/trezorlib/debuglink.py
index 974b0fdc..b926b970 100644
--- a/python/src/trezorlib/debuglink.py
+++ b/python/src/trezorlib/debuglink.py
@@ -227,8 +227,7 @@ class LayoutContent(UnstructuredJSONReader):
def subtitle(self) -> str:
"""Getting text that is displayed as a subtitle."""
- subtitle = self._get_str_or_dict_text("subtitle")
- return subtitle
+ return self._get_str_or_dict_text("subtitle")
def text_content(self) -> str:
"""What is on the screen, in one long string, so content can be
@@ -406,6 +405,11 @@ class LayoutContent(UnstructuredJSONReader):
else:
raise ValueError("No passphrase component in layout")
+ def label(self) -> str:
+ """Get label from the layout."""
+ assert "StringKeyboard" in self.all_components()
+ return self.find_unique_value_by_key("content", default="", only_type=str)
+
def page_count(self) -> int:
"""Get number of pages for the layout."""
return (
@@ -1859,6 +1863,13 @@ class ScreenButtons:
def menu(self) -> Coords:
return self._grid55(4, 0)
+ # Header back button
+ def back(self) -> Coords:
+ if self.layout_type is LayoutType.Eckhart:
+ return self._grid55(0, 0)
+ else:
+ raise ValueError("Wrong layout type")
+
# Center of the screen
def tap_to_confirm(self) -> Coords:
assert self.layout_type is LayoutType.Delizia
@@ -2092,6 +2103,11 @@ PASSPHRASE_DIGITS = ("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
PASSPHRASE_SPECIAL = ("_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^=")
# fmt: on
+LABEL_LOWERCASE_ECKHART = PASSPHRASE_LOWERCASE_DE
+LABEL_UPPERCASE_ECKHART = PASSPHRASE_UPPERCASE_DE
+LABEL_DIGITS = PASSPHRASE_DIGITS
+LABEL_SPECIAL = PASSPHRASE_SPECIAL
+
class ButtonActions:
def __init__(self, debuglink: DebugLink) -> None:
@@ -2117,12 +2133,34 @@ class ButtonActions:
else:
return PASSPHRASE_SPECIAL
+ def _label_choices(self, char: str) -> "tuple[str, ...]":
+ if char in " *#" or char.islower():
+ if self.debuglink.layout_type is LayoutType.Eckhart:
+ return LABEL_LOWERCASE_ECKHART
+ else:
+ raise ValueError("Wrong layout type")
+ elif char.isupper():
+ if self.debuglink.layout_type is LayoutType.Eckhart:
+ return LABEL_UPPERCASE_ECKHART
+ else:
+ raise ValueError("Wrong layout type")
+ elif char.isdigit():
+ return PASSPHRASE_DIGITS
+ else:
+ return PASSPHRASE_SPECIAL
+
def passphrase(self, char: str) -> t.Tuple[Coords, int]:
choices = self._passphrase_choices(char)
idx = next(i for i, letters in enumerate(choices) if char in letters)
click_amount = choices[idx].index(char) + 1
return self.debuglink.screen_buttons.pin_passphrase_index(idx), click_amount
+ def label(self, char: str) -> t.Tuple[Coords, int]:
+ choices = self._label_choices(char)
+ idx = next(i for i, letters in enumerate(choices) if char in letters)
+ click_amount = choices[idx].index(char) + 1
+ return self.debuglink.screen_buttons.pin_passphrase_index(idx), click_amount
+
def type_word(self, word: str, is_slip39: bool = False) -> t.Iterator[Coords]:
if is_slip39:
yield from self._type_word_slip39(word)
diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py
index f187e887..1af6d412 100644
--- a/python/src/trezorlib/messages.py
+++ b/python/src/trezorlib/messages.py
@@ -3241,6 +3241,7 @@ class Features(protobuf.MessageType):
55: protobuf.Field("soc", "uint32", repeated=False, required=False, default=None),
56: protobuf.Field("firmware_corrupted", "bool", repeated=False, required=False, default=None),
57: protobuf.Field("auto_lock_delay_battery_ms", "uint32", repeated=False, required=False, default=None),
+ 58: protobuf.Field("led", "bool", repeated=False, required=False, default=None),
}
def __init__(
@@ -3301,6 +3302,7 @@ class Features(protobuf.MessageType):
soc: Optional["int"] = None,
firmware_corrupted: Optional["bool"] = None,
auto_lock_delay_battery_ms: Optional["int"] = None,
+ led: Optional["bool"] = None,
) -> None:
self.capabilities: Sequence["Capability"] = capabilities if capabilities is not None else []
self.major_version = major_version
@@ -3357,6 +3359,7 @@ class Features(protobuf.MessageType):
self.soc = soc
self.firmware_corrupted = firmware_corrupted
self.auto_lock_delay_battery_ms = auto_lock_delay_battery_ms
+ self.led = led
class LockDevice(protobuf.MessageType):
diff --git a/rust/trezor-client/src/protos/generated/messages_management.rs b/rust/trezor-client/src/protos/generated/messages_management.rs
index b331882f..ceb8ee40 100644
--- a/rust/trezor-client/src/protos/generated/messages_management.rs
+++ b/rust/trezor-client/src/protos/generated/messages_management.rs
@@ -473,6 +473,8 @@ pub struct Features {
pub firmware_corrupted: ::std::option::Option<bool>,
// @@protoc_insertion_point(field:hw.trezor.messages.management.Features.auto_lock_delay_battery_ms)
pub auto_lock_delay_battery_ms: ::std::option::Option<u32>,
+ // @@protoc_insertion_point(field:hw.trezor.messages.management.Features.led)
+ pub led: ::std::option::Option<bool>,
// special fields
// @@protoc_insertion_point(special_field:hw.trezor.messages.management.Features.special_fields)
pub special_fields: ::protobuf::SpecialFields,
@@ -1706,8 +1708,27 @@ impl Features {
self.auto_lock_delay_battery_ms = ::std::option::Option::Some(v);
}
+ // optional bool led = 58;
+
+ pub fn led(&self) -> bool {
+ self.led.unwrap_or(false)
+ }
+
+ pub fn clear_led(&mut self) {
+ self.led = ::std::option::Option::None;
+ }
+
+ pub fn has_led(&self) -> bool {
+ self.led.is_some()
+ }
+
+ // Param is passed by value, moved
+ pub fn set_led(&mut self, v: bool) {
+ self.led = ::std::option::Option::Some(v);
+ }
+
fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData {
- let mut fields = ::std::vec::Vec::with_capacity(55);
+ let mut fields = ::std::vec::Vec::with_capacity(56);
let mut oneofs = ::std::vec::Vec::with_capacity(0);
fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
"vendor",
@@ -1984,6 +2005,11 @@ impl Features {
|m: &Features| { &m.auto_lock_delay_battery_ms },
|m: &mut Features| { &mut m.auto_lock_delay_battery_ms },
));
+ fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>(
+ "led",
+ |m: &Features| { &m.led },
+ |m: &mut Features| { &mut m.led },
+ ));
::protobuf::reflect::GeneratedMessageDescriptorData::new_2::<Features>(
"Features",
fields,
@@ -2179,6 +2205,9 @@ impl ::protobuf::Message for Features {
456 => {
self.auto_lock_delay_battery_ms = ::std::option::Option::Some(is.read_uint32()?);
},
+ 464 => {
+ self.led = ::std::option::Option::Some(is.read_bool()?);
+ },
tag => {
::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?;
},
@@ -2356,6 +2385,9 @@ impl ::protobuf::Message for Features {
if let Some(v) = self.auto_lock_delay_battery_ms {
my_size += ::protobuf::rt::uint32_size(57, v);
}
+ if let Some(v) = self.led {
+ my_size += 2 + 1;
+ }
my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields());
self.special_fields.cached_size().set(my_size as u32);
my_size
@@ -2527,6 +2559,9 @@ impl ::protobuf::Message for Features {
if let Some(v) = self.auto_lock_delay_battery_ms {
os.write_uint32(57, v)?;
}
+ if let Some(v) = self.led {
+ os.write_bool(58, v)?;
+ }
os.write_unknown_fields(self.special_fields.unknown_fields())?;
::std::result::Result::Ok(())
}
@@ -2599,6 +2634,7 @@ impl ::protobuf::Message for Features {
self.soc = ::std::option::Option::None;
self.firmware_corrupted = ::std::option::Option::None;
self.auto_lock_delay_battery_ms = ::std::option::Option::None;
+ self.led = ::std::option::Option::None;
self.special_fields.clear();
}
@@ -2659,6 +2695,7 @@ impl ::protobuf::Message for Features {
soc: ::std::option::Option::None,
firmware_corrupted: ::std::option::Option::None,
auto_lock_delay_battery_ms: ::std::option::Option::None,
+ led: ::std::option::Option::None,
special_fields: ::protobuf::SpecialFields::new(),
};
&instance
@@ -11833,7 +11870,7 @@ static file_descriptor_proto_data: &'static [u8] = b"\
\roptions.proto\"\x80\x01\n\nInitialize\x12\x1d\n\nsession_id\x18\x01\
\x20\x01(\x0cR\tsessionId\x12,\n\x10_skip_passphrase\x18\x02\x20\x01(\
\x08R\x0eSkipPassphraseB\x02\x18\x01\x12%\n\x0ederive_cardano\x18\x03\
- \x20\x01(\x08R\rderiveCardano\"\r\n\x0bGetFeatures\"\xb7\x19\n\x08Featur\
+ \x20\x01(\x08R\rderiveCardano\"\r\n\x0bGetFeatures\"\xc9\x19\n\x08Featur\
es\x12\x16\n\x06vendor\x18\x01\x20\x01(\tR\x06vendor\x12#\n\rmajor_versi\
on\x18\x02\x20\x02(\rR\x0cmajorVersion\x12#\n\rminor_version\x18\x03\x20\
\x02(\rR\x0cminorVersion\x12#\n\rpatch_version\x18\x04\x20\x02(\rR\x0cpa\
@@ -11886,138 +11923,139 @@ static file_descriptor_proto_data: &'static [u8] = b"\
nt.RecoveryTypeR\x0crecoveryType\x12\x1d\n\noptiga_sec\x186\x20\x01(\rR\
\toptigaSec\x12\x10\n\x03soc\x187\x20\x01(\rR\x03soc\x12-\n\x12firmware_\
corrupted\x188\x20\x01(\x08R\x11firmwareCorrupted\x12:\n\x1aauto_lock_de\
- lay_battery_ms\x189\x20\x01(\rR\x16autoLockDelayBatteryMs\"C\n\x12Backup\
- Availability\x12\x10\n\x0cNotAvailable\x10\0\x12\x0c\n\x08Required\x10\
- \x01\x12\r\n\tAvailable\x10\x02\"7\n\x0eRecoveryStatus\x12\x0b\n\x07Noth\
- ing\x10\0\x12\x0c\n\x08Recovery\x10\x01\x12\n\n\x06Backup\x10\x02\"\xf6\
- \x04\n\nCapability\x12\x1c\n\x12Capability_Bitcoin\x10\x01\x1a\x04\x80\
- \xa6\x1d\x01\x12\x1b\n\x17Capability_Bitcoin_like\x10\x02\x12\x16\n\x12C\
- apability_Binance\x10\x03\x12\x16\n\x12Capability_Cardano\x10\x04\x12\
- \x1b\n\x11Capability_Crypto\x10\x05\x1a\x04\x80\xa6\x1d\x01\x12\x12\n\
- \x0eCapability_EOS\x10\x06\x12\x17\n\x13Capability_Ethereum\x10\x07\x12\
- \x17\n\x0fCapability_Lisk\x10\x08\x1a\x02\x08\x01\x12\x15\n\x11Capabilit\
- y_Monero\x10\t\x12\x12\n\x0eCapability_NEM\x10\n\x12\x15\n\x11Capability\
- _Ripple\x10\x0b\x12\x16\n\x12Capability_Stellar\x10\x0c\x12\x14\n\x10Cap\
- ability_Tezos\x10\r\x12\x12\n\x0eCapability_U2F\x10\x0e\x12\x1b\n\x11Cap\
- ability_Shamir\x10\x0f\x1a\x04\x80\xa6\x1d\x01\x12!\n\x17Capability_Sham\
- irGroups\x10\x10\x1a\x04\x80\xa6\x1d\x01\x12$\n\x1aCapability_Passphrase\
- Entry\x10\x11\x1a\x04\x80\xa6\x1d\x01\x12\x15\n\x11Capability_Solana\x10\
- \x12\x12!\n\x17Capability_Translations\x10\x13\x1a\x04\x80\xa6\x1d\x01\
- \x12\x1f\n\x15Capability_Brightness\x10\x14\x1a\x04\x80\xa6\x1d\x01\x12\
- \x1b\n\x11Capability_Haptic\x10\x15\x1a\x04\x80\xa6\x1d\x01\x12\x18\n\
- \x0eCapability_BLE\x10\x16\x1a\x04\x80\xa6\x1d\x01\x12\x18\n\x0eCapabili\
- ty_NFC\x10\x17\x1a\x04\x80\xa6\x1d\x01\x1a\x04\xc8\xf3\x18\x01\"\x0c\n\n\
- LockDevice\"&\n\x07SetBusy\x12\x1b\n\texpiry_ms\x18\x01\x20\x01(\rR\x08e\
- xpiryMs\"\x0c\n\nEndSession\"\xdd\x05\n\rApplySettings\x12\x1e\n\x08lang\
- uage\x18\x01\x20\x01(\tR\x08languageB\x02\x18\x01\x12\x14\n\x05label\x18\
- \x02\x20\x01(\tR\x05label\x12%\n\x0euse_passphrase\x18\x03\x20\x01(\x08R\
- \rusePassphrase\x12\x1e\n\nhomescreen\x18\x04\x20\x01(\x0cR\nhomescreen\
- \x120\n\x12_passphrase_source\x18\x05\x20\x01(\rR\x10PassphraseSourceB\
- \x02\x18\x01\x12+\n\x12auto_lock_delay_ms\x18\x06\x20\x01(\rR\x0fautoLoc\
- kDelayMs\x12Y\n\x10display_rotation\x18\x07\x20\x01(\x0e2..hw.trezor.mes\
- sages.management.DisplayRotationR\x0fdisplayRotation\x12=\n\x1bpassphras\
- e_always_on_device\x18\x08\x20\x01(\x08R\x18passphraseAlwaysOnDevice\x12\
- T\n\rsafety_checks\x18\t\x20\x01(\x0e2/.hw.trezor.messages.management.Sa\
- fetyCheckLevelR\x0csafetyChecks\x123\n\x15experimental_features\x18\n\
- \x20\x01(\x08R\x14experimentalFeatures\x129\n\x19hide_passphrase_from_ho\
- st\x18\x0b\x20\x01(\x08R\x16hidePassphraseFromHost\x12'\n\x0fhaptic_feed\
- back\x18\r\x20\x01(\x08R\x0ehapticFeedback\x12+\n\x11homescreen_length\
- \x18\x0e\x20\x01(\rR\x10homescreenLength\x12:\n\x1aauto_lock_delay_batte\
- ry_ms\x18\x0f\x20\x01(\rR\x16autoLockDelayBatteryMs\"T\n\x0eChangeLangua\
- ge\x12\x1f\n\x0bdata_length\x18\x01\x20\x02(\rR\ndataLength\x12!\n\x0csh\
- ow_display\x18\x02\x20\x01(\x08R\x0bshowDisplay\"T\n\x10DataChunkRequest\
- \x12\x1f\n\x0bdata_length\x18\x01\x20\x02(\rR\ndataLength\x12\x1f\n\x0bd\
- ata_offset\x18\x02\x20\x02(\rR\ndataOffset\"-\n\x0cDataChunkAck\x12\x1d\
- \n\ndata_chunk\x18\x01\x20\x02(\x0cR\tdataChunk\"\"\n\nApplyFlags\x12\
- \x14\n\x05flags\x18\x01\x20\x02(\rR\x05flags\"#\n\tChangePin\x12\x16\n\
- \x06remove\x18\x01\x20\x01(\x08R\x06remove\"(\n\x0eChangeWipeCode\x12\
- \x16\n\x06remove\x18\x01\x20\x01(\x08R\x06remove\"\xaa\x01\n\tSdProtect\
- \x12]\n\toperation\x18\x01\x20\x02(\x0e2?.hw.trezor.messages.management.\
- SdProtect.SdProtectOperationTypeR\toperation\">\n\x16SdProtectOperationT\
- ype\x12\x0b\n\x07DISABLE\x10\0\x12\n\n\x06ENABLE\x10\x01\x12\x0b\n\x07RE\
- FRESH\x10\x02\"O\n\x04Ping\x12\x1a\n\x07message\x18\x01\x20\x01(\t:\0R\
- \x07message\x12+\n\x11button_protection\x18\x02\x20\x01(\x08R\x10buttonP\
- rotection\"\x08\n\x06Cancel\"\x20\n\nGetEntropy\x12\x12\n\x04size\x18\
- \x01\x20\x02(\rR\x04size\"#\n\x07Entropy\x12\x18\n\x07entropy\x18\x01\
- \x20\x02(\x0cR\x07entropy\"/\n\x0fGetFirmwareHash\x12\x1c\n\tchallenge\
- \x18\x01\x20\x01(\x0cR\tchallenge\"\"\n\x0cFirmwareHash\x12\x12\n\x04has\
- h\x18\x01\x20\x02(\x0cR\x04hash\"2\n\x12AuthenticateDevice\x12\x1c\n\tch\
- allenge\x18\x01\x20\x02(\x0cR\tchallenge\"\xcb\x01\n\x11AuthenticityProo\
- f\x12/\n\x13optiga_certificates\x18\x01\x20\x03(\x0cR\x12optigaCertifica\
- tes\x12)\n\x10optiga_signature\x18\x02\x20\x02(\x0cR\x0foptigaSignature\
- \x12/\n\x13tropic_certificates\x18\x03\x20\x03(\x0cR\x12tropicCertificat\
- es\x12)\n\x10tropic_signature\x18\x04\x20\x01(\x0cR\x0ftropicSignature\"\
- \x0c\n\nWipeDevice\"\xad\x02\n\nLoadDevice\x12\x1c\n\tmnemonics\x18\x01\
- \x20\x03(\tR\tmnemonics\x12\x10\n\x03pin\x18\x03\x20\x01(\tR\x03pin\x123\
- \n\x15passphrase_protection\x18\x04\x20\x01(\x08R\x14passphraseProtectio\
- n\x12\x1e\n\x08language\x18\x05\x20\x01(\tR\x08languageB\x02\x18\x01\x12\
- \x14\n\x05label\x18\x06\x20\x01(\tR\x05label\x12#\n\rskip_checksum\x18\
- \x07\x20\x01(\x08R\x0cskipChecksum\x12\x1f\n\x0bu2f_counter\x18\x08\x20\
- \x01(\rR\nu2fCounter\x12!\n\x0cneeds_backup\x18\t\x20\x01(\x08R\x0bneeds\
- Backup\x12\x1b\n\tno_backup\x18\n\x20\x01(\x08R\x08noBackup\"\x9d\x03\n\
- \x0bResetDevice\x12\x1f\n\x08strength\x18\x02\x20\x01(\r:\x03256R\x08str\
- ength\x123\n\x15passphrase_protection\x18\x03\x20\x01(\x08R\x14passphras\
- eProtection\x12%\n\x0epin_protection\x18\x04\x20\x01(\x08R\rpinProtectio\
- n\x12\x1e\n\x08language\x18\x05\x20\x01(\tR\x08languageB\x02\x18\x01\x12\
- \x14\n\x05label\x18\x06\x20\x01(\tR\x05label\x12\x1f\n\x0bu2f_counter\
- \x18\x07\x20\x01(\rR\nu2fCounter\x12\x1f\n\x0bskip_backup\x18\x08\x20\
- \x01(\x08R\nskipBackup\x12\x1b\n\tno_backup\x18\t\x20\x01(\x08R\x08noBac\
- kup\x12Q\n\x0bbackup_type\x18\n\x20\x01(\x0e2).hw.trezor.messages.manage\
- ment.BackupType:\x05Bip39R\nbackupType\x12#\n\rentropy_check\x18\x0b\x20\
- \x01(\x08R\x0centropyCheckJ\x04\x08\x01\x10\x02\"\xe5\x01\n\x0cBackupDev\
- ice\x12'\n\x0fgroup_threshold\x18\x01\x20\x01(\rR\x0egroupThreshold\x12O\
- \n\x06groups\x18\x02\x20\x03(\x0b27.hw.trezor.messages.management.Backup\
- Device.Slip39GroupR\x06groups\x1a[\n\x0bSlip39Group\x12)\n\x10member_thr\
- eshold\x18\x01\x20\x02(\rR\x0fmemberThreshold\x12!\n\x0cmember_count\x18\
- \x02\x20\x02(\rR\x0bmemberCount\"b\n\x0eEntropyRequest\x12-\n\x12entropy\
- _commitment\x18\x01\x20\x01(\x0cR\x11entropyCommitment\x12!\n\x0cprev_en\
- tropy\x18\x02\x20\x01(\x0cR\x0bprevEntropy\"&\n\nEntropyAck\x12\x18\n\
- \x07entropy\x18\x01\x20\x02(\x0cR\x07entropy\"\x13\n\x11EntropyCheckRead\
- y\"5\n\x14EntropyCheckContinue\x12\x1d\n\x06finish\x18\x01\x20\x01(\x08:\
- \x05falseR\x06finish\"\x8d\x04\n\x0eRecoveryDevice\x12\x1d\n\nword_count\
- \x18\x01\x20\x01(\rR\twordCount\x123\n\x15passphrase_protection\x18\x02\
- \x20\x01(\x08R\x14passphraseProtection\x12%\n\x0epin_protection\x18\x03\
- \x20\x01(\x08R\rpinProtection\x12\x1e\n\x08language\x18\x04\x20\x01(\tR\
- \x08languageB\x02\x18\x01\x12\x14\n\x05label\x18\x05\x20\x01(\tR\x05labe\
- l\x12)\n\x10enforce_wordlist\x18\x06\x20\x01(\x08R\x0fenforceWordlist\
- \x12j\n\x0cinput_method\x18\x08\x20\x01(\x0e2G.hw.trezor.messages.manage\
- ment.RecoveryDevice.RecoveryDeviceInputMethodR\x0binputMethod\x12\x1f\n\
- \x0bu2f_counter\x18\t\x20\x01(\rR\nu2fCounter\x12O\n\x04type\x18\n\x20\
- \x01(\x0e2+.hw.trezor.messages.management.RecoveryType:\x0eNormalRecover\
- yR\x04type\";\n\x19RecoveryDeviceInputMethod\x12\x12\n\x0eScrambledWords\
- \x10\0\x12\n\n\x06Matrix\x10\x01J\x04\x08\x07\x10\x08\"\xc5\x01\n\x0bWor\
- dRequest\x12N\n\x04type\x18\x01\x20\x02(\x0e2:.hw.trezor.messages.manage\
- ment.WordRequest.WordRequestTypeR\x04type\"f\n\x0fWordRequestType\x12\
- \x19\n\x15WordRequestType_Plain\x10\0\x12\x1b\n\x17WordRequestType_Matri\
- x9\x10\x01\x12\x1b\n\x17WordRequestType_Matrix6\x10\x02\"\x1d\n\x07WordA\
- ck\x12\x12\n\x04word\x18\x01\x20\x02(\tR\x04word\"0\n\rSetU2FCounter\x12\
- \x1f\n\x0bu2f_counter\x18\x01\x20\x02(\rR\nu2fCounter\"\x13\n\x11GetNext\
- U2FCounter\"1\n\x0eNextU2FCounter\x12\x1f\n\x0bu2f_counter\x18\x01\x20\
- \x02(\rR\nu2fCounter\"\x11\n\x0fDoPreauthorized\"\x16\n\x14Preauthorized\
- Request\"\x15\n\x13CancelAuthorization\"\x9a\x02\n\x12RebootToBootloader\
- \x12o\n\x0cboot_command\x18\x01\x20\x01(\x0e2=.hw.trezor.messages.manage\
- ment.RebootToBootloader.BootCommand:\rSTOP_AND_WAITR\x0bbootCommand\x12'\
- \n\x0ffirmware_header\x18\x02\x20\x01(\x0cR\x0efirmwareHeader\x123\n\x14\
- language_data_length\x18\x03\x20\x01(\r:\x010R\x12languageDataLength\"5\
- \n\x0bBootCommand\x12\x11\n\rSTOP_AND_WAIT\x10\0\x12\x13\n\x0fINSTALL_UP\
- GRADE\x10\x01\"\x10\n\x08GetNonce:\x04\x88\xb2\x19\x01\"#\n\x05Nonce\x12\
- \x14\n\x05nonce\x18\x01\x20\x02(\x0cR\x05nonce:\x04\x88\xb2\x19\x01\";\n\
- \nUnlockPath\x12\x1b\n\taddress_n\x18\x01\x20\x03(\rR\x08addressN\x12\
- \x10\n\x03mac\x18\x02\x20\x01(\x0cR\x03mac\"'\n\x13UnlockedPathRequest\
- \x12\x10\n\x03mac\x18\x01\x20\x02(\x0cR\x03mac\"\x14\n\x12ShowDeviceTuto\
- rial\"\x12\n\x10UnlockBootloader\"%\n\rSetBrightness\x12\x14\n\x05value\
- \x18\x01\x20\x01(\rR\x05value*\x99\x01\n\nBackupType\x12\t\n\x05Bip39\
- \x10\0\x12\x10\n\x0cSlip39_Basic\x10\x01\x12\x13\n\x0fSlip39_Advanced\
- \x10\x02\x12\x1c\n\x18Slip39_Single_Extendable\x10\x03\x12\x1b\n\x17Slip\
- 39_Basic_Extendable\x10\x04\x12\x1e\n\x1aSlip39_Advanced_Extendable\x10\
- \x05*G\n\x10SafetyCheckLevel\x12\n\n\x06Strict\x10\0\x12\x10\n\x0cPrompt\
- Always\x10\x01\x12\x15\n\x11PromptTemporarily\x10\x02*=\n\x0fDisplayRota\
- tion\x12\t\n\x05North\x10\0\x12\x08\n\x04East\x10Z\x12\n\n\x05South\x10\
- \xb4\x01\x12\t\n\x04West\x10\x8e\x02*0\n\x10HomescreenFormat\x12\x08\n\
- \x04Toif\x10\x01\x12\x08\n\x04Jpeg\x10\x02\x12\x08\n\x04ToiG\x10\x03*H\n\
- \x0cRecoveryType\x12\x12\n\x0eNormalRecovery\x10\0\x12\n\n\x06DryRun\x10\
- \x01\x12\x18\n\x14UnlockRepeatedBackup\x10\x02BB\n#com.satoshilabs.trezo\
- r.lib.protobufB\x17TrezorMessageManagement\x80\xa6\x1d\x01\
+ lay_battery_ms\x189\x20\x01(\rR\x16autoLockDelayBatteryMs\x12\x10\n\x03l\
+ ed\x18:\x20\x01(\x08R\x03led\"C\n\x12BackupAvailability\x12\x10\n\x0cNot\
+ Available\x10\0\x12\x0c\n\x08Required\x10\x01\x12\r\n\tAvailable\x10\x02\
+ \"7\n\x0eRecoveryStatus\x12\x0b\n\x07Nothing\x10\0\x12\x0c\n\x08Recovery\
+ \x10\x01\x12\n\n\x06Backup\x10\x02\"\xf6\x04\n\nCapability\x12\x1c\n\x12\
+ Capability_Bitcoin\x10\x01\x1a\x04\x80\xa6\x1d\x01\x12\x1b\n\x17Capabili\
+ ty_Bitcoin_like\x10\x02\x12\x16\n\x12Capability_Binance\x10\x03\x12\x16\
+ \n\x12Capability_Cardano\x10\x04\x12\x1b\n\x11Capability_Crypto\x10\x05\
+ \x1a\x04\x80\xa6\x1d\x01\x12\x12\n\x0eCapability_EOS\x10\x06\x12\x17\n\
+ \x13Capability_Ethereum\x10\x07\x12\x17\n\x0fCapability_Lisk\x10\x08\x1a\
+ \x02\x08\x01\x12\x15\n\x11Capability_Monero\x10\t\x12\x12\n\x0eCapabilit\
+ y_NEM\x10\n\x12\x15\n\x11Capability_Ripple\x10\x0b\x12\x16\n\x12Capabili\
+ ty_Stellar\x10\x0c\x12\x14\n\x10Capability_Tezos\x10\r\x12\x12\n\x0eCapa\
+ bility_U2F\x10\x0e\x12\x1b\n\x11Capability_Shamir\x10\x0f\x1a\x04\x80\
+ \xa6\x1d\x01\x12!\n\x17Capability_ShamirGroups\x10\x10\x1a\x04\x80\xa6\
+ \x1d\x01\x12$\n\x1aCapability_PassphraseEntry\x10\x11\x1a\x04\x80\xa6\
+ \x1d\x01\x12\x15\n\x11Capability_Solana\x10\x12\x12!\n\x17Capability_Tra\
+ nslations\x10\x13\x1a\x04\x80\xa6\x1d\x01\x12\x1f\n\x15Capability_Bright\
+ ness\x10\x14\x1a\x04\x80\xa6\x1d\x01\x12\x1b\n\x11Capability_Haptic\x10\
+ \x15\x1a\x04\x80\xa6\x1d\x01\x12\x18\n\x0eCapability_BLE\x10\x16\x1a\x04\
+ \x80\xa6\x1d\x01\x12\x18\n\x0eCapability_NFC\x10\x17\x1a\x04\x80\xa6\x1d\
+ \x01\x1a\x04\xc8\xf3\x18\x01\"\x0c\n\nLockDevice\"&\n\x07SetBusy\x12\x1b\
+ \n\texpiry_ms\x18\x01\x20\x01(\rR\x08expiryMs\"\x0c\n\nEndSession\"\xdd\
+ \x05\n\rApplySettings\x12\x1e\n\x08language\x18\x01\x20\x01(\tR\x08langu\
+ ageB\x02\x18\x01\x12\x14\n\x05label\x18\x02\x20\x01(\tR\x05label\x12%\n\
+ \x0euse_passphrase\x18\x03\x20\x01(\x08R\rusePassphrase\x12\x1e\n\nhomes\
+ creen\x18\x04\x20\x01(\x0cR\nhomescreen\x120\n\x12_passphrase_source\x18\
+ \x05\x20\x01(\rR\x10PassphraseSourceB\x02\x18\x01\x12+\n\x12auto_lock_de\
+ lay_ms\x18\x06\x20\x01(\rR\x0fautoLockDelayMs\x12Y\n\x10display_rotation\
+ \x18\x07\x20\x01(\x0e2..hw.trezor.messages.management.DisplayRotationR\
+ \x0fdisplayRotation\x12=\n\x1bpassphrase_always_on_device\x18\x08\x20\
+ \x01(\x08R\x18passphraseAlwaysOnDevice\x12T\n\rsafety_checks\x18\t\x20\
+ \x01(\x0e2/.hw.trezor.messages.management.SafetyCheckLevelR\x0csafetyChe\
+ cks\x123\n\x15experimental_features\x18\n\x20\x01(\x08R\x14experimentalF\
+ eatures\x129\n\x19hide_passphrase_from_host\x18\x0b\x20\x01(\x08R\x16hid\
+ ePassphraseFromHost\x12'\n\x0fhaptic_feedback\x18\r\x20\x01(\x08R\x0ehap\
+ ticFeedback\x12+\n\x11homescreen_length\x18\x0e\x20\x01(\rR\x10homescree\
+ nLength\x12:\n\x1aauto_lock_delay_battery_ms\x18\x0f\x20\x01(\rR\x16auto\
+ LockDelayBatteryMs\"T\n\x0eChangeLanguage\x12\x1f\n\x0bdata_length\x18\
+ \x01\x20\x02(\rR\ndataLength\x12!\n\x0cshow_display\x18\x02\x20\x01(\x08\
+ R\x0bshowDisplay\"T\n\x10DataChunkRequest\x12\x1f\n\x0bdata_length\x18\
+ \x01\x20\x02(\rR\ndataLength\x12\x1f\n\x0bdata_offset\x18\x02\x20\x02(\r\
+ R\ndataOffset\"-\n\x0cDataChunkAck\x12\x1d\n\ndata_chunk\x18\x01\x20\x02\
+ (\x0cR\tdataChunk\"\"\n\nApplyFlags\x12\x14\n\x05flags\x18\x01\x20\x02(\
+ \rR\x05flags\"#\n\tChangePin\x12\x16\n\x06remove\x18\x01\x20\x01(\x08R\
+ \x06remove\"(\n\x0eChangeWipeCode\x12\x16\n\x06remove\x18\x01\x20\x01(\
+ \x08R\x06remove\"\xaa\x01\n\tSdProtect\x12]\n\toperation\x18\x01\x20\x02\
+ (\x0e2?.hw.trezor.messages.management.SdProtect.SdProtectOperationTypeR\
+ \toperation\">\n\x16SdProtectOperationType\x12\x0b\n\x07DISABLE\x10\0\
+ \x12\n\n\x06ENABLE\x10\x01\x12\x0b\n\x07REFRESH\x10\x02\"O\n\x04Ping\x12\
+ \x1a\n\x07message\x18\x01\x20\x01(\t:\0R\x07message\x12+\n\x11button_pro\
+ tection\x18\x02\x20\x01(\x08R\x10buttonProtection\"\x08\n\x06Cancel\"\
+ \x20\n\nGetEntropy\x12\x12\n\x04size\x18\x01\x20\x02(\rR\x04size\"#\n\
+ \x07Entropy\x12\x18\n\x07entropy\x18\x01\x20\x02(\x0cR\x07entropy\"/\n\
+ \x0fGetFirmwareHash\x12\x1c\n\tchallenge\x18\x01\x20\x01(\x0cR\tchalleng\
+ e\"\"\n\x0cFirmwareHash\x12\x12\n\x04hash\x18\x01\x20\x02(\x0cR\x04hash\
+ \"2\n\x12AuthenticateDevice\x12\x1c\n\tchallenge\x18\x01\x20\x02(\x0cR\t\
+ challenge\"\xcb\x01\n\x11AuthenticityProof\x12/\n\x13optiga_certificates\
+ \x18\x01\x20\x03(\x0cR\x12optigaCertificates\x12)\n\x10optiga_signature\
+ \x18\x02\x20\x02(\x0cR\x0foptigaSignature\x12/\n\x13tropic_certificates\
+ \x18\x03\x20\x03(\x0cR\x12tropicCertificates\x12)\n\x10tropic_signature\
+ \x18\x04\x20\x01(\x0cR\x0ftropicSignature\"\x0c\n\nWipeDevice\"\xad\x02\
+ \n\nLoadDevice\x12\x1c\n\tmnemonics\x18\x01\x20\x03(\tR\tmnemonics\x12\
+ \x10\n\x03pin\x18\x03\x20\x01(\tR\x03pin\x123\n\x15passphrase_protection\
+ \x18\x04\x20\x01(\x08R\x14passphraseProtection\x12\x1e\n\x08language\x18\
+ \x05\x20\x01(\tR\x08languageB\x02\x18\x01\x12\x14\n\x05label\x18\x06\x20\
+ \x01(\tR\x05label\x12#\n\rskip_checksum\x18\x07\x20\x01(\x08R\x0cskipChe\
+ cksum\x12\x1f\n\x0bu2f_counter\x18\x08\x20\x01(\rR\nu2fCounter\x12!\n\
+ \x0cneeds_backup\x18\t\x20\x01(\x08R\x0bneedsBackup\x12\x1b\n\tno_backup\
+ \x18\n\x20\x01(\x08R\x08noBackup\"\x9d\x03\n\x0bResetDevice\x12\x1f\n\
+ \x08strength\x18\x02\x20\x01(\r:\x03256R\x08strength\x123\n\x15passphras\
+ e_protection\x18\x03\x20\x01(\x08R\x14passphraseProtection\x12%\n\x0epin\
+ _protection\x18\x04\x20\x01(\x08R\rpinProtection\x12\x1e\n\x08language\
+ \x18\x05\x20\x01(\tR\x08languageB\x02\x18\x01\x12\x14\n\x05label\x18\x06\
+ \x20\x01(\tR\x05label\x12\x1f\n\x0bu2f_counter\x18\x07\x20\x01(\rR\nu2fC\
+ ounter\x12\x1f\n\x0bskip_backup\x18\x08\x20\x01(\x08R\nskipBackup\x12\
+ \x1b\n\tno_backup\x18\t\x20\x01(\x08R\x08noBackup\x12Q\n\x0bbackup_type\
+ \x18\n\x20\x01(\x0e2).hw.trezor.messages.management.BackupType:\x05Bip39\
+ R\nbackupType\x12#\n\rentropy_check\x18\x0b\x20\x01(\x08R\x0centropyChec\
+ kJ\x04\x08\x01\x10\x02\"\xe5\x01\n\x0cBackupDevice\x12'\n\x0fgroup_thres\
+ hold\x18\x01\x20\x01(\rR\x0egroupThreshold\x12O\n\x06groups\x18\x02\x20\
+ \x03(\x0b27.hw.trezor.messages.management.BackupDevice.Slip39GroupR\x06g\
+ roups\x1a[\n\x0bSlip39Group\x12)\n\x10member_threshold\x18\x01\x20\x02(\
+ \rR\x0fmemberThreshold\x12!\n\x0cmember_count\x18\x02\x20\x02(\rR\x0bmem\
+ berCount\"b\n\x0eEntropyRequest\x12-\n\x12entropy_commitment\x18\x01\x20\
+ \x01(\x0cR\x11entropyCommitment\x12!\n\x0cprev_entropy\x18\x02\x20\x01(\
+ \x0cR\x0bprevEntropy\"&\n\nEntropyAck\x12\x18\n\x07entropy\x18\x01\x20\
+ \x02(\x0cR\x07entropy\"\x13\n\x11EntropyCheckReady\"5\n\x14EntropyCheckC\
+ ontinue\x12\x1d\n\x06finish\x18\x01\x20\x01(\x08:\x05falseR\x06finish\"\
+ \x8d\x04\n\x0eRecoveryDevice\x12\x1d\n\nword_count\x18\x01\x20\x01(\rR\t\
+ wordCount\x123\n\x15passphrase_protection\x18\x02\x20\x01(\x08R\x14passp\
+ hraseProtection\x12%\n\x0epin_protection\x18\x03\x20\x01(\x08R\rpinProte\
+ ction\x12\x1e\n\x08language\x18\x04\x20\x01(\tR\x08languageB\x02\x18\x01\
+ \x12\x14\n\x05label\x18\x05\x20\x01(\tR\x05label\x12)\n\x10enforce_wordl\
+ ist\x18\x06\x20\x01(\x08R\x0fenforceWordlist\x12j\n\x0cinput_method\x18\
+ \x08\x20\x01(\x0e2G.hw.trezor.messages.management.RecoveryDevice.Recover\
+ yDeviceInputMethodR\x0binputMethod\x12\x1f\n\x0bu2f_counter\x18\t\x20\
+ \x01(\rR\nu2fCounter\x12O\n\x04type\x18\n\x20\x01(\x0e2+.hw.trezor.messa\
+ ges.management.RecoveryType:\x0eNormalRecoveryR\x04type\";\n\x19Recovery\
+ DeviceInputMethod\x12\x12\n\x0eScrambledWords\x10\0\x12\n\n\x06Matrix\
+ \x10\x01J\x04\x08\x07\x10\x08\"\xc5\x01\n\x0bWordRequest\x12N\n\x04type\
+ \x18\x01\x20\x02(\x0e2:.hw.trezor.messages.management.WordRequest.WordRe\
+ questTypeR\x04type\"f\n\x0fWordRequestType\x12\x19\n\x15WordRequestType_\
+ Plain\x10\0\x12\x1b\n\x17WordRequestType_Matrix9\x10\x01\x12\x1b\n\x17Wo\
+ rdRequestType_Matrix6\x10\x02\"\x1d\n\x07WordAck\x12\x12\n\x04word\x18\
+ \x01\x20\x02(\tR\x04word\"0\n\rSetU2FCounter\x12\x1f\n\x0bu2f_counter\
+ \x18\x01\x20\x02(\rR\nu2fCounter\"\x13\n\x11GetNextU2FCounter\"1\n\x0eNe\
+ xtU2FCounter\x12\x1f\n\x0bu2f_counter\x18\x01\x20\x02(\rR\nu2fCounter\"\
+ \x11\n\x0fDoPreauthorized\"\x16\n\x14PreauthorizedRequest\"\x15\n\x13Can\
+ celAuthorization\"\x9a\x02\n\x12RebootToBootloader\x12o\n\x0cboot_comman\
+ d\x18\x01\x20\x01(\x0e2=.hw.trezor.messages.management.RebootToBootloade\
+ r.BootCommand:\rSTOP_AND_WAITR\x0bbootCommand\x12'\n\x0ffirmware_header\
+ \x18\x02\x20\x01(\x0cR\x0efirmwareHeader\x123\n\x14language_data_length\
+ \x18\x03\x20\x01(\r:\x010R\x12languageDataLength\"5\n\x0bBootCommand\x12\
+ \x11\n\rSTOP_AND_WAIT\x10\0\x12\x13\n\x0fINSTALL_UPGRADE\x10\x01\"\x10\n\
+ \x08GetNonce:\x04\x88\xb2\x19\x01\"#\n\x05Nonce\x12\x14\n\x05nonce\x18\
+ \x01\x20\x02(\x0cR\x05nonce:\x04\x88\xb2\x19\x01\";\n\nUnlockPath\x12\
+ \x1b\n\taddress_n\x18\x01\x20\x03(\rR\x08addressN\x12\x10\n\x03mac\x18\
+ \x02\x20\x01(\x0cR\x03mac\"'\n\x13UnlockedPathRequest\x12\x10\n\x03mac\
+ \x18\x01\x20\x02(\x0cR\x03mac\"\x14\n\x12ShowDeviceTutorial\"\x12\n\x10U\
+ nlockBootloader\"%\n\rSetBrightness\x12\x14\n\x05value\x18\x01\x20\x01(\
+ \rR\x05value*\x99\x01\n\nBackupType\x12\t\n\x05Bip39\x10\0\x12\x10\n\x0c\
+ Slip39_Basic\x10\x01\x12\x13\n\x0fSlip39_Advanced\x10\x02\x12\x1c\n\x18S\
+ lip39_Single_Extendable\x10\x03\x12\x1b\n\x17Slip39_Basic_Extendable\x10\
+ \x04\x12\x1e\n\x1aSlip39_Advanced_Extendable\x10\x05*G\n\x10SafetyCheckL\
+ evel\x12\n\n\x06Strict\x10\0\x12\x10\n\x0cPromptAlways\x10\x01\x12\x15\n\
+ \x11PromptTemporarily\x10\x02*=\n\x0fDisplayRotation\x12\t\n\x05North\
+ \x10\0\x12\x08\n\x04East\x10Z\x12\n\n\x05South\x10\xb4\x01\x12\t\n\x04We\
+ st\x10\x8e\x02*0\n\x10HomescreenFormat\x12\x08\n\x04Toif\x10\x01\x12\x08\
+ \n\x04Jpeg\x10\x02\x12\x08\n\x04ToiG\x10\x03*H\n\x0cRecoveryType\x12\x12\
+ \n\x0eNormalRecovery\x10\0\x12\n\n\x06DryRun\x10\x01\x12\x18\n\x14Unlock\
+ RepeatedBackup\x10\x02BB\n#com.satoshilabs.trezor.lib.protobufB\x17Trezo\
+ rMessageManagement\x80\xa6\x1d\x01\
";
/// `FileDescriptorProto` object which was a source for this generated file
diff --git a/tests/click_tests/common.py b/tests/click_tests/common.py
index 05996bde..e9c0e4a9 100644
--- a/tests/click_tests/common.py
+++ b/tests/click_tests/common.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import time
import typing as t
from enum import Enum
@@ -27,7 +28,8 @@ class CommonPass:
EMPTY_ADDRESS = "mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q"
-class PassphraseCategory(Enum):
+# Passphrase/Label keyboard layouts
+class KeyboardCategory(Enum):
Menu = "MENU"
Numeric = "123"
LettersLower = "abc"
@@ -35,15 +37,33 @@ class PassphraseCategory(Enum):
Special = "#$!"
-def get_char_category(char: str) -> PassphraseCategory:
+KEYBOARD_CATEGORIES_BOLT = [
+ KeyboardCategory.Numeric,
+ KeyboardCategory.LettersLower,
+ KeyboardCategory.LettersUpper,
+ KeyboardCategory.Special,
+]
+
+# Common for Delizia and Eckhart
+KEYBOARD_CATEGORIES_DE = [
+ KeyboardCategory.LettersLower,
+ KeyboardCategory.LettersUpper,
+ KeyboardCategory.Numeric,
+ KeyboardCategory.Special,
+]
+
+COORDS_PREV: tuple[int, int] = (0, 0)
+
+
+def get_char_category(char: str) -> KeyboardCategory:
"""What is the category of a character"""
if char.isdigit():
- return PassphraseCategory.Numeric
+ return KeyboardCategory.Numeric
if char.islower():
- return PassphraseCategory.LettersLower
+ return KeyboardCategory.LettersLower
if char.isupper():
- return PassphraseCategory.LettersUpper
- return PassphraseCategory.Special
+ return KeyboardCategory.LettersUpper
+ return KeyboardCategory.Special
def go_next(debug: "DebugLink") -> LayoutContent:
@@ -141,3 +161,86 @@ def _get_action_index(wanted_action: str, all_actions: AllActionsType) -> int:
return index
raise ValueError(f"Action {wanted_action} is not supported in {all_actions}")
+
+
+def keyboard_categories(layout_type: LayoutType) -> list[KeyboardCategory]:
+ if layout_type is LayoutType.Bolt:
+ return KEYBOARD_CATEGORIES_BOLT
+ elif layout_type in (LayoutType.Delizia, LayoutType.Eckhart):
+ return KEYBOARD_CATEGORIES_DE
+ else:
+ raise ValueError("Wrong layout type")
+
+
+def get_category(debug: "DebugLink") -> KeyboardCategory:
+ category = debug.read_layout().find_unique_value_by_key(
+ "active_layout", default="", only_type=str
+ )
+ assert (
+ category in KeyboardCategory.__members__
+ ), f"Unknown layout name from debug: {category}"
+ return KeyboardCategory[category]
+
+
+def go_to_category(
+ debug: "DebugLink", category: KeyboardCategory, verify_layout: bool = False
+) -> None:
+ """
+ Go to a specific category on the passphrase/label keyboard.
+
+ Navigates through the on-screen categories by swiping left or right
+ until the desired category is reached. If `verify_layout` is set to True,
+ the function will assert that the category change has been correctly applied
+ by reading and validating the current layout from the debug interface.
+ """
+ global COORDS_PREV
+
+ keyboard_category = get_category(debug)
+
+ # Already there
+ if keyboard_category == category:
+ return
+
+ current_index = keyboard_categories(debug.layout_type).index(keyboard_category)
+ target_index = keyboard_categories(debug.layout_type).index(category)
+ if target_index > current_index:
+ for _ in range(target_index - current_index):
+ debug.swipe_left()
+ else:
+ for _ in range(current_index - target_index):
+ debug.swipe_right()
+ if verify_layout:
+ layout = get_category(debug)
+ assert layout == category, f"Layout mismatch: expected {category}, got {layout}"
+
+ # Category changed, reset coordinates
+ COORDS_PREV = (0, 0) # type: ignore
+
+
+def press_char(debug: "DebugLink", char: str) -> None:
+ """Press a character on the passphrase/label keyboard."""
+ global COORDS_PREV
+
+ # Space and couple others are a special case
+ if char in " *#":
+ char_category = KeyboardCategory.LettersLower
+ else:
+ char_category = get_char_category(char)
+
+ go_to_category(debug, char_category)
+
+ coords, amount = debug.button_actions.passphrase(char)
+ # If the button is the same as for the previous char,
+ # waiting a second before pressing it again.
+ # (not for a space in Bolt layout)
+ is_bolt_space = debug.layout_type is LayoutType.Bolt and char == " "
+ if coords == COORDS_PREV and not is_bolt_space:
+ time.sleep(1.1)
+ COORDS_PREV = coords # type: ignore
+ for _ in range(amount):
+ debug.click(coords)
+
+
+def delete_char(debug: "DebugLink") -> None:
+ """Deletes the last char"""
+ debug.click(debug.screen_buttons.pin_passphrase_erase())
diff --git a/tests/click_tests/device_menu/__init__.py b/tests/click_tests/device_menu/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/click_tests/device_menu/common.py b/tests/click_tests/device_menu/common.py
new file mode 100644
index 00000000..98a210c5
--- /dev/null
+++ b/tests/click_tests/device_menu/common.py
@@ -0,0 +1,458 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2025 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+
+from enum import Enum, auto
+from typing import TYPE_CHECKING, Callable
+
+from trezorlib.messages import BackupAvailability
+
+from ... import translations as TR
+
+if TYPE_CHECKING:
+ from trezorlib.debuglink import DebugLink
+ from trezorlib.messages import Features
+
+ from ...device_handler import BackgroundDeviceHandler
+
+PIN4 = "1234"
+
+REGULATORY_AREAS = [
+ "United States",
+ "", # additional information to the US regulations
+ "Canada",
+ "Europe",
+ "Australia",
+ "Ukraine",
+ "Japan",
+ "South Korea",
+ "Taiwan",
+]
+
+AUTOLOCK_DELAY_USB_DEFAULT_MS = 10 * 60 * 1000 # 10 minutes
+AUTOLOCK_DELAY_BATT_DEFAULT_MS = 40 * 1000 # 40 seconds
+
+
+def format_duration_ms(milliseconds: int) -> str:
+ """
+ Returns a human-friendly representation of a duration. Truncates all decimals.
+ """
+
+ assert milliseconds >= 0
+
+ unit_plurals = {
+ "millisecond": TR.plurals__lock_after_x_milliseconds,
+ "second": TR.plurals__lock_after_x_seconds,
+ "minute": TR.plurals__lock_after_x_minutes,
+ "hour": TR.plurals__lock_after_x_hours,
+ }
+
+ # Pick appropriate unit and divisor
+ units: tuple[tuple[str, int], ...] = (
+ (unit_plurals["hour"], 60 * 60 * 1000),
+ (unit_plurals["minute"], 60 * 1000),
+ (unit_plurals["second"], 1000),
+ )
+ for unit, divisor in units:
+ if milliseconds >= divisor:
+ break
+ else:
+ unit = unit_plurals["millisecond"]
+ divisor = 1
+
+ count = milliseconds // divisor
+
+ # Inline plural formatting
+ plural_options = unit.split("|")
+ if len(plural_options) not in (2, 3):
+ raise ValueError("Unit plurals must have 2 or 3 forms separated by '|'")
+
+ if count == 1:
+ plural = plural_options[0]
+ else:
+ plural = plural_options[-1]
+
+ if len(plural_options) == 3 and 1 < count < 5:
+ plural = plural_options[1]
+
+ return f"{count} {plural}"
+
+
+class Menu(Enum):
+ ROOT = 0
+ PAIR_AND_CONNECT = auto()
+ SETTINGS = auto()
+ SECURITY = auto()
+ PIN = auto()
+ AUTO_LOCK = auto()
+ WIPE_CODE = auto()
+ DEVICE = auto()
+ POWER = auto()
+
+ def path_from_root(self) -> list[str]:
+ """
+ Return the sequence of labels to click from ROOT to reach this Menu.
+ """
+ paths = {
+ Menu.ROOT: [],
+ Menu.PAIR_AND_CONNECT: [TR.ble__pair_title],
+ Menu.SETTINGS: [TR.words__settings],
+ Menu.SECURITY: [TR.words__settings, TR.words__security],
+ Menu.PIN: [TR.words__settings, TR.words__security, TR.pin__title],
+ Menu.AUTO_LOCK: [
+ TR.words__settings,
+ TR.words__security,
+ TR.auto_lock__title,
+ ],
+ Menu.WIPE_CODE: [
+ TR.words__settings,
+ TR.words__security,
+ TR.wipe_code__title,
+ ],
+ Menu.DEVICE: [TR.words__settings, TR.words__device],
+ Menu.POWER: [TR.words__power],
+ }
+ try:
+ return paths[self]
+ except KeyError:
+ raise ValueError(f"No path defined for menu {self}")
+
+ def content(self, features: "Features") -> list[str] | None:
+ """
+ Expected vertical menu content for this Menu, given device features.
+ """
+ initialized = features.initialized
+ has_pin = features.pin_protection
+ has_wipe_code = features.wipe_code_protection
+ unfinished_backup = features.unfinished_backup
+ needs_backup = features.backup_availability == BackupAvailability.Required
+ no_backup = features.no_backup
+
+ def root_content():
+ content: list[str] = []
+ if initialized:
+ if unfinished_backup:
+ content.append(TR.homescreen__title_backup_failed)
+ if needs_backup:
+ content.append(TR.homescreen__title_backup_needed)
+ if not has_pin:
+ content.append(TR.homescreen__title_pin_not_set)
+ content.extend([TR.ble__pair_title, TR.words__settings, TR.words__power])
+
+ return content
+
+ def connection_content():
+ return [TR.ble__pair_new, TR.ble__forget_all]
+
+ def settings_content():
+ content = []
+ if initialized:
+ content.append(TR.words__security)
+ content.append(TR.words__device)
+ return content
+
+ def security_content():
+ if not initialized:
+ return None
+ content: list[str] = [TR.pin__title]
+ if has_pin:
+ content.append(TR.auto_lock__title)
+ content.append(TR.wipe_code__title)
+ if not needs_backup and not no_backup and not unfinished_backup:
+ content.append(TR.reset__check_backup_title)
+ return content
+
+ def pin_content():
+ if initialized and has_pin:
+ return [TR.pin__change, TR.pin__remove]
+ return None
+
+ def auto_lock_content():
+ if initialized and has_pin:
+
+ auto_lock_batt = (
+ features.auto_lock_delay_battery_ms
+ or AUTOLOCK_DELAY_BATT_DEFAULT_MS
+ )
+ auto_lock_usb = (
+ features.auto_lock_delay_ms or AUTOLOCK_DELAY_USB_DEFAULT_MS
+ )
+
+ return [
+ format_duration_ms(auto_lock_batt),
+ format_duration_ms(auto_lock_usb),
+ ]
+ return None
+
+ def wipe_code_content():
+ if initialized and has_wipe_code:
+ return [TR.wipe_code__change, TR.wipe_code__remove]
+ return None
+
+ def device_content():
+ content: list[str] = []
+ if initialized:
+ content.extend(
+ [
+ TR.words__name,
+ TR.brightness__title,
+ ]
+ )
+ if features.haptic_feedback is not None:
+ content.append(TR.haptic_feedback__title)
+ content.append(TR.led__title)
+ content.extend(
+ [TR.regulatory_certification__title, TR.words__about, TR.wipe__title]
+ )
+ return content
+
+ lookup: dict["Menu", Callable[[], list[str] | None]] = {
+ Menu.ROOT: root_content,
+ Menu.PAIR_AND_CONNECT: connection_content,
+ Menu.SETTINGS: settings_content,
+ Menu.SECURITY: security_content,
+ Menu.PIN: pin_content,
+ Menu.AUTO_LOCK: auto_lock_content,
+ Menu.WIPE_CODE: wipe_code_content,
+ Menu.DEVICE: device_content,
+ }
+
+ return lookup[self]()
+
+ def navigate_back(self, debug: "DebugLink", features: "Features"):
+ """
+ Press back button if the Header has one.
+
+ Returns the vertical menu content of the target screen.
+ Raises error if the back button isn't present.
+ """
+
+ # Ensure we start at the current menu
+ expected = self.content(features)
+ menu = debug.read_layout().vertical_menu_content()
+ if self == Menu.PAIR_AND_CONNECT:
+ # The connection menu has two permanent items at the end
+ assert menu[-2:] == expected
+ else:
+ assert expected == menu
+
+ # Navigate back to the previous menu
+ assert debug.read_layout().find_unique_value_by_key(
+ "left_button", default={}, only_type=dict
+ )
+ debug.click(debug.screen_buttons.back())
+
+ # After navigation, we should be at the target menu
+ return debug.read_layout().vertical_menu_content()
+
+ def navigate_to(self, debug: "DebugLink", features: "Features"):
+ """
+ Navigate UI from the current menu to the target menu using the debug
+ interface without pressing any back buttons.
+
+ Returns the vertical menu content of the target screen.
+ Raises error if there is no direct path to the target menu.
+ """
+ # Ensure we the target menu can be directly navigated to
+ menu = debug.read_layout().vertical_menu_content()
+ path = self.path_from_root()
+ matches = [(idx, x) for idx, x in enumerate(path) if x in menu]
+ assert len(matches) == 1
+ start_idx = matches[0][0]
+
+ # Follow the path
+ for label in self.path_from_root()[start_idx:]:
+ menu = debug.read_layout().vertical_menu_content()
+ idx = menu.index(label)
+ debug.button_actions.navigate_to_menu_item(idx)
+ assert_device_screen(debug, self)
+
+ # After navigation, we should be at the target menu
+ menu = debug.read_layout().vertical_menu_content()
+ expected = self.content(features)
+ if self == Menu.PAIR_AND_CONNECT:
+ # The connection menu has two permanent items at the end
+ assert menu[-2:] == expected
+ else:
+ assert menu == expected
+ return menu
+
+ @classmethod
+ def assert_menu_exists(cls, features: "Features"):
+ """
+ Assert that the menu content is as expected depending on the feature flags.
+ """
+ # Always exist
+ assert Menu.ROOT.content(features)
+ assert Menu.SETTINGS.content(features)
+ assert Menu.PAIR_AND_CONNECT.content(features)
+ assert Menu.DEVICE.content(features)
+
+ security = Menu.SECURITY.content(features)
+ if features.initialized:
+ assert security is not None
+ else:
+ assert security is None
+
+ pin = Menu.PIN.content(features)
+ if features.initialized and features.pin_protection:
+ assert pin is not None
+ else:
+ assert pin is None
+
+ auto_lock = Menu.AUTO_LOCK.content(features)
+ if features.initialized and features.pin_protection:
+ assert auto_lock is not None
+ else:
+ assert auto_lock is None
+
+ wipe_code = Menu.WIPE_CODE.content(features)
+ if (
+ features.initialized
+ and features.pin_protection
+ and features.wipe_code_protection
+ ):
+ assert wipe_code is not None
+ else:
+ assert wipe_code is None
+
+ @classmethod
+ def traverse(cls, debug: "DebugLink", features: "Features") -> None:
+ """
+ Traverse the entire menu from the root.
+ """
+
+ # Assert that all required menus exist because traversing skips non-existing menus
+ cls.assert_menu_exists(features)
+
+ # Start at the root menu
+ menu = debug.read_layout().vertical_menu_content()
+ assert menu == cls.ROOT.content(features)
+
+ root_child = cls.PAIR_AND_CONNECT
+ if root_child.content(features) is not None:
+ menu = root_child.navigate_to(debug, features)
+
+ # Check if the last two items match the expected content
+ assert menu[-2:] == root_child.content(features)
+
+ # TODO traverse through connected devices
+ # for item in menu[:-2]:
+ # assert item in child.content(features)
+
+ # Go back to root
+ menu = root_child.navigate_back(debug, features)
+
+ root_child = cls.SETTINGS
+ if root_child.content(features) is not None:
+ menu = root_child.navigate_to(debug, features)
+
+ child_1 = cls.SECURITY
+ if child_1.content(features) is not None:
+ menu = child_1.navigate_to(debug, features)
+
+ child_2 = cls.PIN
+ if child_2.content(features) is not None:
+ menu = child_2.navigate_to(debug, features)
+ # Go back to security
+ menu = child_2.navigate_back(debug, features)
+
+ child_2 = cls.AUTO_LOCK
+ if child_2.content(features) is not None:
+ menu = child_2.navigate_to(debug, features)
+ # Go back to security
+ menu = child_2.navigate_back(debug, features)
+
+ child_2 = cls.WIPE_CODE
+ if child_2.content(features) is not None:
+ menu = child_2.navigate_to(debug, features)
+ # Go back to security
+ menu = child_2.navigate_back(debug, features)
+
+ # Go back to settings
+ menu = child_1.navigate_back(debug, features)
+
+ child_1 = cls.DEVICE
+ if child_1.content(features) is not None:
+ menu = child_1.navigate_to(debug, features)
+
+ # Regulatory screen
+ regulatory_idx = menu.index(TR.regulatory_certification__title)
+ debug.button_actions.navigate_to_menu_item(regulatory_idx)
+ debug.synchronize_at("RegulatoryScreen")
+ layout = debug.read_layout()
+ assert TR.regulatory_certification__title in layout.title()
+ assert layout.page_count() == len(REGULATORY_AREAS)
+ # Scroll through all regulatory areas
+ assert REGULATORY_AREAS[0] in debug.read_layout().subtitle()
+ for area in REGULATORY_AREAS[1:]:
+ debug.click(debug.screen_buttons.ok())
+ assert area in debug.read_layout().subtitle()
+ # Close the regulatory screen
+ debug.click(debug.screen_buttons.menu())
+ menu = debug.read_layout().vertical_menu_content()
+ assert menu == child_1.content(features)
+
+ # Go to about screen
+ about_idx = menu.index(TR.words__about)
+ debug.button_actions.navigate_to_menu_item(about_idx)
+ debug.synchronize_at("TextScreen")
+ layout = debug.read_layout()
+ assert layout.title() == TR.words__about
+ assert TR.homescreen__firmware_version in layout.text_content()
+ assert TR.homescreen__firmware_type in layout.text_content()
+ assert TR.ble__version in layout.text_content()
+ # Close the about screen
+ debug.click(debug.screen_buttons.menu())
+ menu = debug.read_layout().vertical_menu_content()
+ assert menu == child_1.content(features)
+ # Go back to settings
+ menu = child_1.navigate_back(debug, features)
+
+ # Go back to root
+ menu = root_child.navigate_back(debug, features)
+
+
+def open_device_menu(debug: "DebugLink"):
+ # Start at homescreen
+ debug.synchronize_at("Homescreen")
+
+ # Go to device menu
+ debug.click(debug.screen_buttons.ok())
+ debug.synchronize_at("DeviceMenuScreen")
+
+
+def close_device_menu(debug: "DebugLink"):
+ # Start at device menu
+ debug.synchronize_at("DeviceMenuScreen")
+
+ # Close the device menu
+ debug.click(debug.screen_buttons.menu())
+ debug.synchronize_at("Homescreen")
+
+
+def assert_device_screen(debug: "DebugLink", menu: Menu):
+ assert (
+ debug.read_layout().find_unique_value_by_key("MenuId", default=0, only_type=int)
+ == menu.value
+ )
+
+
+def enter_pin(device_handler: "BackgroundDeviceHandler", pin: str = PIN4):
+ debug = device_handler.debuglink()
+ device_handler.get_session()
+ debug.synchronize_at("PinKeyboard")
+ debug.input(pin)
+ device_handler.result()
diff --git a/tests/click_tests/device_menu/test_auto_lock.py b/tests/click_tests/device_menu/test_auto_lock.py
new file mode 100644
index 00000000..fa8c669d
--- /dev/null
+++ b/tests/click_tests/device_menu/test_auto_lock.py
@@ -0,0 +1,222 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2025 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+from ... import translations as TR
+from ..test_pin import PIN4
+from .common import (
+ Menu,
+ assert_device_screen,
+ close_device_menu,
+ enter_pin,
+ format_duration_ms,
+ open_device_menu,
+)
+
+if TYPE_CHECKING:
+ from trezorlib.debuglink import DebugLink
+ from trezorlib.messages import Features
+
+ from ...device_handler import BackgroundDeviceHandler
+# Trezor Safe 7 only
+pytestmark = [pytest.mark.models("eckhart")]
+
+
+BATTERY_AUTO_LOCK_IDX = 0
+USB_AUTO_LOCK_IDX = 1
+
+
+def prepare_auto_lock(debug: "DebugLink", features: "Features") -> None:
+ """Navigate to the auto-lock settings dialogue"""
+
+ # Open device menu
+ open_device_menu(debug)
+
+ # Navigate to device menu
+ Menu.AUTO_LOCK.navigate_to(debug, features)
+
+
+@pytest.mark.setup_client(uninitialized=True)
+def test_auto_lock_uninitialized(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is False
+ assert features.pin_protection is False
+
+ # device is uninitialized, security menu is not accessible
+ with pytest.raises(ValueError, match=f"'{TR.words__security}' is not in"):
+ prepare_auto_lock(debug, features)
+
+
+@pytest.mark.setup_client(pin=None)
+def test_auto_lock_pin_not_set(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+
+ # device is uninitialized, auto-lock menu is not accessible
+ with pytest.raises(ValueError, match=f"'{TR.auto_lock__title}' is not in"):
+ prepare_auto_lock(debug, features)
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_auto_lock_battery_change(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is True
+ # auto-lock is not set by default
+ assert features.auto_lock_delay_battery_ms is None
+
+ enter_pin(device_handler)
+ debug = device_handler.debuglink()
+
+ prepare_auto_lock(debug, features)
+
+ # Battery auto-lock
+ debug.button_actions.navigate_to_menu_item(0)
+ # Decrease value by one step
+ debug.click(debug.screen_buttons.number_input_minus())
+ # Confirm value
+ debug.click(debug.screen_buttons.ok())
+ # Confirm changes
+ debug.click(debug.screen_buttons.ok())
+ # Make sure we are back at the security menu and go to homescreen
+ assert_device_screen(debug, Menu.SECURITY)
+ close_device_menu(debug)
+
+ # Refresh features
+ features = device_handler.features()
+ # Make sure auto-lock is set
+ assert features.auto_lock_delay_ms is not None
+
+ # Verify the changes are reflected in the menu
+ prepare_auto_lock(debug, features)
+ auto_lock_items = debug.read_layout().vertical_menu_content()
+ assert format_duration_ms(features.auto_lock_delay_ms) == auto_lock_items[1]
+ close_device_menu(debug)
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_auto_lock_usb_change(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is True
+ # auto-lock is not set by default
+ assert features.auto_lock_delay_ms is None
+
+ enter_pin(device_handler)
+ debug = device_handler.debuglink()
+
+ prepare_auto_lock(debug, features)
+
+ # USB auto-lock
+ debug.button_actions.navigate_to_menu_item(1)
+ # Increase value by one step
+ debug.click(debug.screen_buttons.number_input_plus())
+ # Confirm value
+ debug.click(debug.screen_buttons.ok())
+ # Confirm changes
+ debug.click(debug.screen_buttons.ok())
+ # Make sure we are back at the security menu and go to homescreen
+ assert_device_screen(debug, Menu.SECURITY)
+ close_device_menu(debug)
+
+ # Refresh features
+ features = device_handler.features()
+ # Make sure auto-lock is set
+ assert features.auto_lock_delay_ms is not None
+
+ # Verify the changes are reflected in the menu
+ prepare_auto_lock(debug, features)
+ auto_lock_items = debug.read_layout().vertical_menu_content()
+ assert format_duration_ms(features.auto_lock_delay_ms) == auto_lock_items[1]
+ close_device_menu(debug)
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_auto_lock_battery_cancel(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is True
+ # auto-lock is not set by default
+ assert features.auto_lock_delay_battery_ms is None
+
+ enter_pin(device_handler)
+ debug = device_handler.debuglink()
+
+ prepare_auto_lock(debug, features)
+
+ # Battery auto-lock
+ old_items = debug.read_layout().vertical_menu_content()
+ debug.button_actions.navigate_to_menu_item(BATTERY_AUTO_LOCK_IDX)
+ # Increase value by one step
+ debug.click(debug.screen_buttons.number_input_plus())
+ # Cancel auto-lock change
+ debug.click(debug.screen_buttons.cancel())
+ # Make sure we are back at the security menu and go to homescreen
+ assert_device_screen(debug, Menu.SECURITY)
+ close_device_menu(debug)
+
+ # Refresh features
+ features = device_handler.features()
+
+ # Verify there are no changes
+ prepare_auto_lock(debug, features)
+ items = debug.read_layout().vertical_menu_content()
+ assert old_items[BATTERY_AUTO_LOCK_IDX] == items[BATTERY_AUTO_LOCK_IDX]
+ close_device_menu(debug)
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_auto_lock_usb_cancel(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is True
+ # auto-lock is not set by default
+ assert features.auto_lock_delay_ms is None
+
+ enter_pin(device_handler)
+ debug = device_handler.debuglink()
+
+ prepare_auto_lock(debug, features)
+
+ # USB auto-lock
+ old_items = debug.read_layout().vertical_menu_content()
+ debug.button_actions.navigate_to_menu_item(USB_AUTO_LOCK_IDX)
+ # Increase value by one step
+ debug.click(debug.screen_buttons.number_input_plus())
+ # Cancel auto-lock change
+ debug.click(debug.screen_buttons.cancel())
+ # Make sure we are back at the security menu and go to homescreen
+ assert_device_screen(debug, Menu.SECURITY)
+ close_device_menu(debug)
+
+ # Refresh features
+ features = device_handler.features()
+
+ # Verify there are no changes
+ prepare_auto_lock(debug, features)
+ items = debug.read_layout().vertical_menu_content()
+ assert old_items[USB_AUTO_LOCK_IDX] == items[USB_AUTO_LOCK_IDX]
+ close_device_menu(debug)
diff --git a/tests/click_tests/device_menu/test_check_backup.py b/tests/click_tests/device_menu/test_check_backup.py
new file mode 100644
index 00000000..e874ac4d
--- /dev/null
+++ b/tests/click_tests/device_menu/test_check_backup.py
@@ -0,0 +1,113 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2025 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+from trezorlib import messages
+
+from ... import translations as TR
+from ...common import MNEMONIC12
+from .common import Menu, assert_device_screen, close_device_menu, open_device_menu
+
+if TYPE_CHECKING:
+ from trezorlib.debuglink import DebugLink
+ from trezorlib.messages import Features
+
+ from ...device_handler import BackgroundDeviceHandler
+# Trezor Safe 7 only
+pytestmark = [pytest.mark.models("eckhart")]
+
+
+class NoSecuritySettings(Exception):
+ pass
+
+
+def prepare_check_backup(debug: "DebugLink", features: "Features") -> None:
+ check_backup_title = TR.reset__check_backup_title
+ security_content = Menu.SECURITY.content(features)
+ if security_content is None:
+ raise NoSecuritySettings
+
+ assert check_backup_title in Menu.SECURITY.content(features)
+
+ # Open device menu
+ open_device_menu(debug)
+
+ # Navigate to device menu
+ Menu.SECURITY.navigate_to(debug, features)
+
+ # Trigger check backup
+ layout = debug.read_layout()
+ label_idx = layout.vertical_menu_content().index(check_backup_title)
+ debug.button_actions.navigate_to_menu_item(label_idx)
+
+
+@pytest.mark.setup_client(uninitialized=True)
+def test_uninitialized_fails(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is False
+ assert features.pin_protection is False
+ assert features.unfinished_backup is False
+
+ # Device is uninitialized, security settings are not accessible in the settings
+ with pytest.raises(NoSecuritySettings):
+ prepare_check_backup(debug, features)
+
+
+@pytest.mark.setup_client(needs_backup=True)
+def test_backup_needed_fails(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.backup_availability == messages.BackupAvailability.Required
+
+ # Device needs backup, check backup is not accessible in the security settings
+ with pytest.raises(AssertionError, match=f"'{TR.reset__check_backup_title}' in"):
+ prepare_check_backup(debug, features)
+
+
+@pytest.mark.setup_client(no_backup=True)
+def test_no_backup_fails(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.no_backup is True
+
+ # Device has no backup, check backup is not accessible in the security settings
+ with pytest.raises(AssertionError, match=f"'{TR.reset__check_backup_title}' in"):
+ prepare_check_backup(debug, features)
+
+
+@pytest.mark.setup_client(pin=None, mnemonic=MNEMONIC12)
+def test_backup_check_cancel(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.backup_availability is messages.BackupAvailability.NotAvailable
+
+ # Start check backup flow
+ prepare_check_backup(debug, features)
+ assert TR.recovery__check_dry_run in debug.read_layout().text_content()
+
+ # Cancel the flow
+ debug.click(debug.screen_buttons.cancel())
+ assert_device_screen(debug, Menu.SECURITY)
+
+ close_device_menu(debug)
diff --git a/tests/click_tests/device_menu/test_device_settings.py b/tests/click_tests/device_menu/test_device_settings.py
new file mode 100644
index 00000000..8e71daee
--- /dev/null
+++ b/tests/click_tests/device_menu/test_device_settings.py
@@ -0,0 +1,148 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2025 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+from ... import translations as TR
+from .common import Menu, assert_device_screen, close_device_menu, open_device_menu
+
+if TYPE_CHECKING:
+ from trezorlib.debuglink import DebugLink
+ from trezorlib.messages import Features
+
+ from ...device_handler import BackgroundDeviceHandler
+# Trezor Safe 7 only
+pytestmark = [pytest.mark.models("eckhart")]
+
+LED_TITLE = "led__title"
+BRIGHTNESS_TITLE = "brightness__title"
+HAPTIC_FEEDBACK_TITLE = "haptic_feedback__title"
+
+
+def prepare_device_menu(debug: "DebugLink", features: "Features") -> None:
+ """Navigate to the device settings menu"""
+
+ # Open device menu
+ open_device_menu(debug)
+
+ # Navigate to device menu
+ Menu.DEVICE.navigate_to(debug, features)
+
+
+@pytest.mark.setup_client(uninitialized=True)
+def test_device_settings_uninitialized(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is False
+ assert features.pin_protection is False
+
+ prepare_device_menu(debug, features)
+
+ items = debug.read_layout().vertical_menu_content()
+
+ assert TR.translate(LED_TITLE) not in items
+ assert TR.translate(BRIGHTNESS_TITLE) not in items
+ assert TR.translate(HAPTIC_FEEDBACK_TITLE) not in items
+
+
+@pytest.mark.setup_client(pin=None)
+def test_toggle_led(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+
+ # make sure the device has LED
+ assert features.led is not None
+
+ # Go to device settings
+ prepare_device_menu(debug, features)
+
+ # Toggle LED setting
+ items = debug.read_layout().vertical_menu_content()
+ assert TR.translate(LED_TITLE) in items
+ led_idx = items.index(TR.translate(LED_TITLE))
+ led_old = features.led
+ debug.button_actions.navigate_to_menu_item(led_idx)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
+
+ # Refresh features
+ features = device_handler.features()
+ led_new = features.led
+ assert led_new is not None
+ # Make sure the LED setting was toggled
+ assert led_new != led_old
+
+
+@pytest.mark.setup_client(pin=None)
+def test_toggle_haptic(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+
+ if features.haptic_feedback is None:
+ pytest.skip("haptic feedback not supported")
+
+ assert features.initialized is True
+ assert features.pin_protection is False
+
+ # Go to device settings
+ prepare_device_menu(debug, features)
+
+ # Toggle haptic setting
+ items = debug.read_layout().vertical_menu_content()
+ assert TR.translate(HAPTIC_FEEDBACK_TITLE) in items
+ haptic_idx = items.index(TR.translate(HAPTIC_FEEDBACK_TITLE))
+ haptic_old = features.haptic_feedback
+ debug.button_actions.navigate_to_menu_item(haptic_idx)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
+
+ # Refresh features
+ features = device_handler.features()
+ haptic_new = features.haptic_feedback
+ assert haptic_new is not None
+ # Make sure the haptic setting was toggled
+ assert haptic_new != haptic_old
+
+
+@pytest.mark.setup_client(pin=None)
+def test_brightness(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+
+ # Go to device settings
+ prepare_device_menu(debug, features)
+
+ # Go to brightness setting
+ items = debug.read_layout().vertical_menu_content()
+ assert TR.translate(BRIGHTNESS_TITLE) in items
+ brightness_idx = items.index(TR.translate(BRIGHTNESS_TITLE))
+ debug.button_actions.navigate_to_menu_item(brightness_idx)
+ debug.synchronize_at("SetBrightnessScreen")
+
+ # Close brightness setting
+ debug.click(debug.screen_buttons.menu())
+
+ # Make sure we are back at the device settings menu
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
diff --git a/tests/click_tests/device_menu/test_label.py b/tests/click_tests/device_menu/test_label.py
new file mode 100644
index 00000000..7fc49fea
--- /dev/null
+++ b/tests/click_tests/device_menu/test_label.py
@@ -0,0 +1,314 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2025 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+from ... import translations as TR
+from ..common import KeyboardCategory, delete_char, go_to_category, press_char
+from .common import Menu, assert_device_screen, close_device_menu, open_device_menu
+
+if TYPE_CHECKING:
+ from trezorlib.debuglink import DebugLink
+ from trezorlib.messages import Features
+
+ from ...device_handler import BackgroundDeviceHandler
+# Trezor Safe 7 only
+pytestmark = [pytest.mark.models("eckhart")]
+
+KEYBOARD_CATEGORY = KeyboardCategory.LettersLower
+
+LABEL10 = "NewLabel0#"
+LABEL31 = "dadadadadadadadadadadadadadadad"
+LABEL32 = LABEL31 + "a"
+LABEL33 = LABEL32 + "d"
+
+
+def input_label(debug: "DebugLink", label: str, check: bool = True) -> None:
+ """Input a label with validation it got added"""
+ if check:
+ before = debug.read_layout().label()
+ for char in label:
+ press_char(debug, char)
+ if check:
+ after = debug.read_layout().label()
+ assert after == before + label
+
+
+def enter_label(debug: "DebugLink") -> None:
+ """Enter a label"""
+ debug.click(debug.screen_buttons.passphrase_confirm())
+
+
+def confirm_label(debug: "DebugLink") -> None:
+ """Apply label setting"""
+ # Confirm label change
+ debug.synchronize_at(TR.device_name__title)
+ debug.click(debug.screen_buttons.ok())
+
+
+def erase_label(debug: "DebugLink", check: bool = True) -> None:
+ """Erase a label with validation it got erased"""
+ # Erase the current label
+ for _ in range(len(debug.read_layout().label())):
+ delete_char(debug)
+ if check:
+ assert debug.read_layout().label() == ""
+
+
+def cancel_label(debug: "DebugLink") -> None:
+ """Cancel label input"""
+ debug.click(debug.screen_buttons.pin_passphrase_erase())
+
+
+def prepare_label_dialogue(debug: "DebugLink", features: "Features") -> None:
+ label_title = TR.words__name
+ assert label_title in Menu.DEVICE.content(features)
+
+ # Open device menu
+ open_device_menu(debug)
+
+ # Navigate to device menu
+ Menu.DEVICE.navigate_to(debug, features)
+
+ # Trigger label change
+ layout = debug.read_layout()
+ label_idx = layout.vertical_menu_content().index(label_title)
+ debug.button_actions.navigate_to_menu_item(label_idx)
+
+
+@pytest.mark.setup_client(uninitialized=True)
+def test_label_uninitialized(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is False
+ assert features.pin_protection is False
+ assert features.unfinished_backup is False
+
+ # device is uninitialized, device name is not accessible in the device menu
+ with pytest.raises(AssertionError, match=f"'{TR.words__name}' in"):
+ prepare_label_dialogue(debug, features)
+
+
+@pytest.mark.setup_client(pin=None)
+def test_change_label(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ label = features.label
+ assert isinstance(label, str)
+
+ prepare_label_dialogue(debug, features)
+
+ assert debug.read_layout().label() == label
+
+ # Input new label
+ erase_label(debug)
+ input_label(debug, LABEL10)
+ enter_label(debug)
+ confirm_label(debug)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label == LABEL10
+
+
+@pytest.mark.setup_client(pin=None)
+def test_label_cancel(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ label = features.label
+ assert isinstance(label, str)
+
+ prepare_label_dialogue(debug, features)
+
+ erase_label(debug)
+ cancel_label(debug)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label == label
+
+
+@pytest.mark.setup_client(pin=None)
+def test_label_empty(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+
+ prepare_label_dialogue(debug, features)
+
+ erase_label(debug)
+ enter_label(debug)
+ confirm_label(debug)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
+
+ # Make sure the label is empty
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.label == ""
+
+ # When the label is empty, the homescreen shows the model name
+ assert debug.read_layout().screen_content() == "Trezor Safe 7"
+
+
+@pytest.mark.setup_client(pin=None)
+def test_label_over_32_chars(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label is not None
+
+ prepare_label_dialogue(debug, features)
+
+ # Input new label
+ erase_label(debug)
+ input_label(debug, LABEL33, check=False)
+ assert debug.read_layout().label() == LABEL32
+ enter_label(debug)
+ confirm_label(debug)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label == LABEL32
+
+
+@pytest.mark.setup_client(pin=None)
+def test_label_loop_all_categories(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label is not None
+
+ prepare_label_dialogue(debug, features)
+
+ # Input new label
+ erase_label(debug)
+
+ for category in (
+ KeyboardCategory.Numeric,
+ KeyboardCategory.LettersLower,
+ KeyboardCategory.LettersUpper,
+ KeyboardCategory.Special,
+ ):
+ go_to_category(debug, category, True)
+
+ debug.read_layout()
+ cancel_label(debug)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label is not None
+
+
+@pytest.mark.setup_client(pin=None)
+def test_label_click_same_button_many_times(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label is not None
+
+ prepare_label_dialogue(debug, features)
+
+ # Input new label
+ erase_label(debug)
+
+ a_coords, _ = debug.button_actions.label("a")
+ for _ in range(10):
+ debug.click(a_coords)
+
+ enter_label(debug)
+ confirm_label(debug)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ # Close the device menu
+ debug.synchronize_at("DeviceMenuScreen")
+ debug.click(debug.screen_buttons.menu())
+
+ # Wait for the homescreen to appear
+ debug.synchronize_at("Homescreen")
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label is not None
+
+
+@pytest.mark.setup_client(pin=None)
+def test_label_cycle_through_last_character(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label is not None
+
+ prepare_label_dialogue(debug, features)
+
+ # Input new label
+ erase_label(debug)
+
+ label = LABEL31 + "i" # for i we need to cycle through "ghi" three times
+ input_label(debug, label)
+ assert debug.read_layout().label() == label
+ enter_label(debug)
+ confirm_label(debug)
+ assert_device_screen(debug, Menu.DEVICE)
+
+ close_device_menu(debug)
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+ assert features.label == label
diff --git a/tests/click_tests/device_menu/test_notifications.py b/tests/click_tests/device_menu/test_notifications.py
new file mode 100644
index 00000000..c0a1e352
--- /dev/null
+++ b/tests/click_tests/device_menu/test_notifications.py
@@ -0,0 +1,184 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2025 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+from trezorlib import device, messages
+
+from ... import translations as TR
+from .. import reset
+from ..test_pin import PIN4, _assert_pin_entry, _enter_two_times
+from .common import Menu, assert_device_screen, close_device_menu, open_device_menu
+
+if TYPE_CHECKING:
+
+ from ...device_handler import BackgroundDeviceHandler
+# Trezor Safe 7 only
+pytestmark = [pytest.mark.models("eckhart")]
+
+
+@pytest.mark.setup_client(pin=None)
+def test_pin_not_set(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is False
+
+ # Open device menu
+ open_device_menu(debug)
+
+ # Click on the "PIN not set" notification
+ pin_notification = TR.homescreen__title_pin_not_set
+ layout = debug.read_layout()
+ pin_notification_idx = layout.vertical_menu_content().index(pin_notification)
+ debug.button_actions.navigate_to_menu_item(pin_notification_idx)
+
+ # 1st screen of the pin set flow
+ assert debug.read_layout().text_content() == TR.pin__info
+ debug.click(debug.screen_buttons.ok())
+
+ # set new pin
+ _assert_pin_entry(debug)
+ _enter_two_times(debug, PIN4, PIN4)
+
+ # Close the flow
+ debug.click(debug.screen_buttons.ok())
+ assert_device_screen(debug, Menu.SECURITY)
+ close_device_menu(debug)
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is True
+
+
+@pytest.mark.setup_client(needs_backup=True, pin=None)
+def test_backup_needed(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.backup_availability is messages.BackupAvailability.Required
+ assert features.unfinished_backup is False
+ assert features.pin_protection is False
+
+ # Open device menu
+ open_device_menu(debug)
+
+ backup_notification = TR.homescreen__title_backup_needed
+ pin_notification = TR.homescreen__title_pin_not_set
+ layout = debug.read_layout()
+ pin_notification_idx = layout.vertical_menu_content().index(pin_notification)
+ backup_notification_idx = layout.vertical_menu_content().index(backup_notification)
+
+ # Make sure the "Backup needed" notification is above the "PIN not set" notification
+ assert backup_notification_idx < pin_notification_idx
+
+ # Go to the "Backup needed" info screen
+ debug.button_actions.navigate_to_menu_item(backup_notification_idx)
+ assert TR.homescreen__backup_needed_info in debug.read_layout().text_content()
+
+ # Close the screen
+ debug.click(debug.screen_buttons.menu())
+ assert_device_screen(debug, Menu.ROOT)
+ close_device_menu(debug)
+
+
+@pytest.mark.setup_client(no_backup=True)
+def test_seedless(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.no_backup is True
+ assert features.unfinished_backup is False
+
+ # Open device menu
+ open_device_menu(debug)
+
+ backup_notification = TR.homescreen__title_backup_needed
+ layout = debug.read_layout()
+
+ # No "Backup needed" notification should be present
+ with pytest.raises(ValueError, match=f"'{backup_notification}' is not in"):
+ layout.vertical_menu_content().index(backup_notification)
+
+
+@pytest.mark.setup_client(needs_backup=True)
+@pytest.mark.invalidate_client
+def test_backup_failed(
+ device_handler: "BackgroundDeviceHandler",
+):
+ features = device_handler.features()
+ debug = device_handler.debuglink()
+
+ assert features.initialized is True
+ assert features.unfinished_backup is False
+ assert features.backup_availability == messages.BackupAvailability.Required
+
+ # Spawn the backup process and cancel it
+ session = device_handler.client.get_seedless_session()
+ device_handler.run_with_provided_session(
+ session,
+ device.backup,
+ )
+
+ # confirm backup configuration
+ debug.synchronize_at("TextScreen")
+ assert TR.regexp("backup__info_single_share_backup").match(
+ debug.read_layout().text_content()
+ )
+ reset.confirm_read(debug)
+
+ # confirm backup intro
+ assert TR.reset__never_make_digital_copy in debug.read_layout().text_content()
+ reset.confirm_read(debug, middle_r=True)
+
+ # read words
+ reset.read_words(debug, do_htc=False, confirm_instruction=True)
+
+ device_handler.kill_task()
+ # Raise the loop restart exception to reset the flow
+ session.cancel()
+
+ # Wait for the homescreen to appear
+ debug.synchronize_at("Homescreen")
+
+ # Open device menu
+ open_device_menu(debug)
+ assert_device_screen(debug, Menu.ROOT)
+
+ # Click on the "Backup failed" notification
+ vertical_menu = debug.read_layout().vertical_menu_content()
+ idx = vertical_menu.index(TR.homescreen__title_backup_failed)
+ debug.button_actions.navigate_to_menu_item(idx)
+
+ # Info screen about the failed backup
+ debug.synchronize_at("TextScreen")
+ debug.click(debug.screen_buttons.ok())
+
+ # Wipe device
+ assert TR.wipe__want_to_wipe in debug.read_layout().text_content()
+ debug.click(debug.screen_buttons.ok())
+
+ # Wait for the homescreen to appear
+ debug.synchronize_at("Homescreen")
+
+ # Refresh features and check wiped state
+ device_handler.client = device_handler.client.get_new_client()
+ features = device_handler.features()
+ assert features.initialized is False
+ assert features.pin_protection is False
diff --git a/tests/click_tests/device_menu/test_traverse_menu.py b/tests/click_tests/device_menu/test_traverse_menu.py
new file mode 100644
index 00000000..dc40571b
--- /dev/null
+++ b/tests/click_tests/device_menu/test_traverse_menu.py
@@ -0,0 +1,81 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2025 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+from .common import PIN4, Menu, close_device_menu, enter_pin, open_device_menu
+
+if TYPE_CHECKING:
+ from ...device_handler import BackgroundDeviceHandler
+
+# Trezor Safe 7 only
+pytestmark = [pytest.mark.models("eckhart")]
+
+
+@pytest.mark.setup_client(pin=PIN4)
+def test_traverse_initialized(device_handler: "BackgroundDeviceHandler"):
+ enter_pin(device_handler)
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is True
+ assert features.unfinished_backup is not True
+
+ # Open device menu
+ open_device_menu(debug)
+
+ Menu.traverse(debug, features)
+
+ # Close the device menu
+ close_device_menu(debug)
+
+
+@pytest.mark.setup_client(pin=None)
+def test_traverse_initialized_no_pin(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is False
+ assert features.unfinished_backup is not True
+
+ # Open device menu
+ open_device_menu(debug)
+
+ Menu.traverse(debug, features)
+
+ # Close the device menu
+ close_device_menu(debug)
+
+
+@pytest.mark.setup_client(uninitialized=True)
+def test_traverse_uninitialized(device_handler: "BackgroundDeviceHandler"):
+ debug = device_handler.debuglink()
+ features = device_handler.features()
+ assert features.initialized is False
+ assert features.pin_protection is False
+ assert features.unfinished_backup is False
+
+ # Open device menu
+ open_device_menu(debug)
+
+ Menu.traverse(debug, features)
+
+ # Close the device menu
+ close_device_menu(debug)
diff --git a/tests/click_tests/device_menu/test_wipe.py b/tests/click_tests/device_menu/test_wipe.py
new file mode 100644
index 00000000..9e21f698
--- /dev/null
+++ b/tests/click_tests/device_menu/test_wipe.py
@@ -0,0 +1,64 @@
+# This file is part of the Trezor project.
+#
+# Copyright (C) 2012-2025 SatoshiLabs and contributors
+#
+# This library is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the License along with this library.
+# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
+
+from typing import TYPE_CHECKING
+
+import pytest
+
+from ... import translations as TR
+from .common import PIN4, Menu, enter_pin, open_device_menu
+
+if TYPE_CHECKING:
+ from ...device_handler import BackgroundDeviceHandler
+
+# Trezor Safe 7 only
+pytestmark = [pytest.mark.models("eckhart")]
+
+
+@pytest.mark.invalidate_client
+@pytest.mark.setup_client(pin=PIN4)
+def test_wipe(device_handler: "BackgroundDeviceHandler"):
+ enter_pin(device_handler)
+ debug = device_handler.debuglink()
+
+ features = device_handler.features()
+ assert features.initialized is True
+ assert features.pin_protection is True
+
+ wipe_title = TR.wipe__title
+ assert wipe_title in Menu.DEVICE.content(features)
+
+ open_device_menu(debug)
+
+ # Navigate to device menu
+ Menu.DEVICE.navigate_to(debug, features)
+
+ # Trigger wipe
+ layout = debug.read_layout()
+ wipe_idx = layout.vertical_menu_content().index(wipe_title)
+ debug.button_actions.navigate_to_menu_item(wipe_idx)
+
+ # Confirm wipe
+ debug.synchronize_at(wipe_title)
+ debug.click(debug.screen_buttons.ok())
+
+ # Wait for the homescreen to appear
+ debug.synchronize_at("Homescreen")
+
+ device_handler.client = device_handler.client.get_new_client()
+ features = device_handler.features()
+ assert features.initialized is False
+ assert features.pin_protection is False
diff --git a/tests/click_tests/test_passphrase_bde.py b/tests/click_tests/test_passphrase_bde.py
index d477f15f..0e8ad02a 100644
--- a/tests/click_tests/test_passphrase_bde.py
+++ b/tests/click_tests/test_passphrase_bde.py
@@ -26,7 +26,13 @@ from trezorlib.debuglink import LayoutType
from trezorlib.debuglink import SessionDebugWrapper as Session
from ..common import TEST_ADDRESS_N
-from .common import CommonPass, PassphraseCategory, get_char_category
+from .common import ( # KEYBOARD_CATEGORY,
+ CommonPass,
+ KeyboardCategory,
+ delete_char,
+ go_to_category,
+ press_char,
+)
if TYPE_CHECKING:
from trezorlib.debuglink import DebugLink
@@ -36,25 +42,6 @@ if TYPE_CHECKING:
pytestmark = pytest.mark.models("t2t1", "delizia", "eckhart")
-KEYBOARD_CATEGORIES_BOLT = [
- PassphraseCategory.Numeric,
- PassphraseCategory.LettersLower,
- PassphraseCategory.LettersUpper,
- PassphraseCategory.Special,
-]
-
-# Common for Delizia and Eckhart
-KEYBOARD_CATEGORIES_DE = [
- PassphraseCategory.LettersLower,
- PassphraseCategory.LettersUpper,
- PassphraseCategory.Numeric,
- PassphraseCategory.Special,
-]
-
-# TODO: better read this from the trace
-KEYBOARD_CATEGORY = PassphraseCategory.LettersLower
-COORDS_PREV: tuple[int, int] = (0, 0)
-
# Testing the maximum length is really 50
DA_50 = 25 * "da"
@@ -94,10 +81,6 @@ def prepare_passphrase_dialogue(
device_handler.get_session(passphrase=PASSPHRASE_ON_DEVICE)
debug.synchronize_at(["PassphraseKeyboard", "StringKeyboard"])
- # Resetting the category as it could have been changed by previous tests
- global KEYBOARD_CATEGORY
- KEYBOARD_CATEGORY = PassphraseCategory.LettersLower # type: ignore
-
yield debug
session = device_handler.result()
@@ -107,83 +90,6 @@ def prepare_passphrase_dialogue(
assert result == address
-def keyboard_categories(layout_type: LayoutType) -> list[PassphraseCategory]:
- if layout_type is LayoutType.Bolt:
- return KEYBOARD_CATEGORIES_BOLT
- elif layout_type in (LayoutType.Delizia, LayoutType.Eckhart):
- return KEYBOARD_CATEGORIES_DE
- else:
- raise ValueError("Wrong layout type")
-
-
-def go_to_category(
- debug: "DebugLink", category: PassphraseCategory, verify_layout: bool = False
-) -> None:
- """
- Go to a specific category on the passphrase keyboard.
-
- Navigates through the on-screen categories by swiping left or right
- until the desired category is reached. If `verify_layout` is set to True,
- the function will assert that the category change has been correctly applied
- by reading and validating the current layout from the debug interface.
- """
- global KEYBOARD_CATEGORY
- global COORDS_PREV
-
- # Already there
- if KEYBOARD_CATEGORY == category:
- return
-
- current_index = keyboard_categories(debug.layout_type).index(KEYBOARD_CATEGORY)
- target_index = keyboard_categories(debug.layout_type).index(category)
- if target_index > current_index:
- for _ in range(target_index - current_index):
- debug.swipe_left()
- else:
- for _ in range(current_index - target_index):
- debug.swipe_right()
- if verify_layout:
- layout = debug.read_layout().find_unique_value_by_key(
- "active_layout", default="", only_type=str
- )
- # do the check if Rust debug string exists
- if layout:
- assert (
- layout in PassphraseCategory.__members__
- ), f"Unknown layout name from debug: {layout}"
- assert (
- PassphraseCategory[layout] == category
- ), f"Layout mismatch: expected {category}, got {PassphraseCategory[layout]}"
-
- KEYBOARD_CATEGORY = category # type: ignore
- # Category changed, reset coordinates
- COORDS_PREV = (0, 0) # type: ignore
-
-
-def press_char(debug: "DebugLink", char: str) -> None:
- """Press a character"""
- global COORDS_PREV
-
- # Space and couple others are a special case
- if char in " *#":
- char_category = PassphraseCategory.LettersLower
- else:
- char_category = get_char_category(char)
-
- go_to_category(debug, char_category)
-
- coords, amount = debug.button_actions.passphrase(char)
- # If the button is the same as for the previous char,
- # waiting a second before pressing it again.
- # (not for a space in Bolt layout)
- is_bolt_space = debug.layout_type is LayoutType.Bolt and char == " "
- if coords == COORDS_PREV and not is_bolt_space:
- time.sleep(1.1)
- COORDS_PREV = coords # type: ignore
- for _ in range(amount):
- debug.click(coords)
-
-
def input_passphrase(debug: "DebugLink", passphrase: str, check: bool = True) -> None:
"""Input a passphrase with validation it got added"""
if check:
@@ -203,11 +109,6 @@ def enter_passphrase(debug: "DebugLink") -> None:
debug.click(debug.screen_buttons.ui_yes())
-def delete_char(debug: "DebugLink") -> None:
- """Deletes the last char"""
- debug.click(debug.screen_buttons.pin_passphrase_erase())
-
-
VECTORS = ( # passphrase, address
(CommonPass.SHORT, CommonPass.SHORT_ADDRESS),
(CommonPass.WITH_SPACE, CommonPass.WITH_SPACE_ADDRESS),
@@ -267,10 +168,10 @@ def test_passphrase_delete_all(
def test_passphrase_loop_all_characters(device_handler: "BackgroundDeviceHandler"):
with prepare_passphrase_dialogue(device_handler, CommonPass.EMPTY_ADDRESS) as debug:
for category in (
- PassphraseCategory.Numeric,
- PassphraseCategory.LettersLower,
- PassphraseCategory.LettersUpper,
- PassphraseCategory.Special,
+ KeyboardCategory.Numeric,
+ KeyboardCategory.LettersLower,
+ KeyboardCategory.LettersUpper,
+ KeyboardCategory.Special,
):
go_to_category(debug, category, True)
if debug.layout_type in (LayoutType.Delizia, LayoutType.Eckhart):
diff --git a/tests/click_tests/test_passphrase_caesar.py b/tests/click_tests/test_passphrase_caesar.py
index f71d64e3..7d842155 100644
--- a/tests/click_tests/test_passphrase_caesar.py
+++ b/tests/click_tests/test_passphrase_caesar.py
@@ -26,7 +26,7 @@ from trezorlib.transport.session import SessionV1
from ..common import TEST_ADDRESS_N
from .common import (
CommonPass,
- PassphraseCategory,
+ KeyboardCategory,
get_char_category,
navigate_to_action_and_press,
)
@@ -81,11 +81,11 @@ SPECIAL_ACTIONS = [
CATEGORY_ACTIONS = {
- PassphraseCategory.Menu: MENU_ACTIONS,
- PassphraseCategory.Numeric: DIGITS_ACTIONS,
- PassphraseCategory.LettersLower: LOWERCASE_ACTIONS,
- PassphraseCategory.LettersUpper: UPPERCASE_ACTIONS,
- PassphraseCategory.Special: SPECIAL_ACTIONS,
+ KeyboardCategory.Menu: MENU_ACTIONS,
+ KeyboardCategory.Numeric: DIGITS_ACTIONS,
+ KeyboardCategory.LettersLower: LOWERCASE_ACTIONS,
+ KeyboardCategory.LettersUpper: UPPERCASE_ACTIONS,
+ KeyboardCategory.Special: SPECIAL_ACTIONS,
}
@@ -114,7 +114,7 @@ def prepare_passphrase_dialogue(
device_handler.run_with_provided_session(session, _get_test_address) # type: ignore
layout = debug.synchronize_at("PassphraseKeyboard")
assert layout.passphrase() == ""
- assert _current_category(debug) == PassphraseCategory.Menu
+ assert _current_category(debug) == KeyboardCategory.Menu
yield debug
@@ -123,11 +123,11 @@ def prepare_passphrase_dialogue(
assert result == address
-def _current_category(debug: "DebugLink") -> PassphraseCategory:
+def _current_category(debug: "DebugLink") -> KeyboardCategory:
"""What is the current category we are in"""
layout = debug.read_layout()
category = layout.find_unique_value_by_key("current_category", "")
- return PassphraseCategory(category)
+ return KeyboardCategory(category)
def _current_actions(debug: "DebugLink") -> list[str]:
@@ -137,7 +137,7 @@ def _current_actions(debug: "DebugLink") -> list[str]:
def go_to_category(
- debug: "DebugLink", category: PassphraseCategory, use_carousel: bool = True
+ debug: "DebugLink", category: KeyboardCategory, use_carousel: bool = True
) -> None:
"""Go to a specific category"""
# Already there
@@ -145,15 +145,15 @@ def go_to_category(
return
# Need to be in MENU anytime to change category
- if _current_category(debug) != PassphraseCategory.Menu:
+ if _current_category(debug) != KeyboardCategory.Menu:
navigate_to_action_and_press(
debug, BACK, _current_actions(debug), is_carousel=use_carousel
)
- assert _current_category(debug) == PassphraseCategory.Menu
+ assert _current_category(debug) == KeyboardCategory.Menu
# Go to the right one, unless we want MENU
- if category != PassphraseCategory.Menu:
+ if category != KeyboardCategory.Menu:
navigate_to_action_and_press(
debug, category.value, _current_actions(debug), is_carousel=use_carousel
)
@@ -165,7 +165,7 @@ def press_char(debug: "DebugLink", char: str) -> None:
"""Press a character"""
# Space is a special case
if char == " ":
- go_to_category(debug, PassphraseCategory.Menu)
+ go_to_category(debug, KeyboardCategory.Menu)
navigate_to_action_and_press(debug, SPACE, _current_actions(debug))
else:
char_category = get_char_category(char)
@@ -184,19 +184,19 @@ def input_passphrase(debug: "DebugLink", passphrase: str) -> None:
def show_passphrase(debug: "DebugLink") -> None:
"""Show a passphrase"""
- go_to_category(debug, PassphraseCategory.Menu)
+ go_to_category(debug, KeyboardCategory.Menu)
navigate_to_action_and_press(debug, SHOW, _current_actions(debug))
def enter_passphrase(debug: "DebugLink") -> None:
"""Enter a passphrase"""
- go_to_category(debug, PassphraseCategory.Menu)
+ go_to_category(debug, KeyboardCategory.Menu)
navigate_to_action_and_press(debug, ENTER, _current_actions(debug))
def delete_char(debug: "DebugLink") -> None:
"""Deletes the last char"""
- go_to_category(debug, PassphraseCategory.Menu)
+ go_to_category(debug, KeyboardCategory.Menu)
navigate_to_action_and_press(debug, CANCEL_OR_DELETE, _current_actions(debug))
@@ -277,9 +277,9 @@ def test_cancel(device_handler: "BackgroundDeviceHandler"):
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_loop_all_characters(device_handler: "BackgroundDeviceHandler"):
with prepare_passphrase_dialogue(device_handler, CommonPass.EMPTY_ADDRESS) as debug:
- for category in PassphraseCategory:
+ for category in KeyboardCategory:
go_to_category(debug, category)
# use_carousel=False because we want to reach BACK at the end of the list
- go_to_category(debug, PassphraseCategory.Menu, use_carousel=False)
+ go_to_category(debug, KeyboardCategory.Menu, use_carousel=False)
enter_passphrase(debug)
diff --git a/tests/ui_tests/fixtures.json b/tests/ui_tests/fixtures.json
index c4b69969..d8061023 100644
--- a/tests/ui_tests/fixtures.json
+++ b/tests/ui_tests/fixtures.json
@@ -28820,6 +28820,36 @@
},
"T3W1": {
"click_tests": {
+"T3W1_cs_device_menu-test_auto_lock.py::test_auto_lock_battery_cancel": "8c1cd2f0d8e0c30746e1a3147269659de2e35195d058b14f21d494fe8bccec00",
+"T3W1_cs_device_menu-test_auto_lock.py::test_auto_lock_battery_change": "c5fe0d7b749f28d64d53f0434deeded48a4281fd8bfffd597300fbb0500441c6",
+"T3W1_cs_device_menu-test_auto_lock.py::test_auto_lock_pin_not_set": "5280d2d66d88ef5e024b50949c8223b99861454ba25e6da31ef3633a43b3daba",
+"T3W1_cs_device_menu-test_auto_lock.py::test_auto_lock_uninitialized": "d0850296a7ce37200f3439f808c095ae472133010d7c0ce312d8d15d3703fc64",
+"T3W1_cs_device_menu-test_auto_lock.py::test_auto_lock_usb_cancel": "736b7300d54cac1bd8a9d3d330d846774a310f050ac941e9a7026ce5070016b0",
+"T3W1_cs_device_menu-test_auto_lock.py::test_auto_lock_usb_change": "e72e4d229577a8b88532333890f5f5a1804cb035a9a86f5fb99b9a4c43607446",
+"T3W1_cs_device_menu-test_check_backup.py::test_backup_check_cancel": "c74a8cffe3965c505dc2b8022bc5f33bc5923bbca3d22b308777bd2a0f94a6eb",
+"T3W1_cs_device_menu-test_check_backup.py::test_backup_needed_fails": "13c0e7145d4e402b32f8b11c87e885beb26da0604eb0b022bce3b43f3bf5e23e",
+"T3W1_cs_device_menu-test_check_backup.py::test_no_backup_fails": "d19e7595c0cf0e67ba02f980571b6a2ac492b16326eac379415774139a24d30b",
+"T3W1_cs_device_menu-test_check_backup.py::test_uninitialized_fails": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_cs_device_menu-test_device_settings.py::test_brightness": "b78e5a7ba202c71acdb7e2f6d96f75e92c63f673fbf39a0c01c22838266ba011",
+"T3W1_cs_device_menu-test_device_settings.py::test_device_settings_uninitialized": "b9c0fb8e4188e451146f4654ddaf860f92fdf418a188d8e2c7bf121707ddf292",
+"T3W1_cs_device_menu-test_device_settings.py::test_toggle_haptic": "c24521e569c08e3605b164212c876f8ac57c5eef6cca6f2ca53a635a883ebc4b",
+"T3W1_cs_device_menu-test_device_settings.py::test_toggle_led": "b50282ff9649bf62c2618802eb993cf2b3d1ee3b91673cefe539fbff9f426dec",
+"T3W1_cs_device_menu-test_label.py::test_change_label": "d8cc7d9adeef3f90e8773ece084d16dfd253361800ef8ef1f3c452e4065768c7",
+"T3W1_cs_device_menu-test_label.py::test_label_cancel": "671fff7ec8ffeb4e815ca87a0d25f0856731f125c12503ef2c89488cd13fe0c1",
+"T3W1_cs_device_menu-test_label.py::test_label_click_same_button_many_times": "601422dd38201205378b3c46d1fb45293172c1f8ca898f9c55737b94cced8e93",
+"T3W1_cs_device_menu-test_label.py::test_label_cycle_through_last_character": "3ef47f8dffa6d35a397efdb17ef67825b56357384065a18c2c26e7ada48b5fc9",
+"T3W1_cs_device_menu-test_label.py::test_label_empty": "7d23f1e06b26341793f6ea925e6a8d364287251cb1979392f2627e6d96fab564",
+"T3W1_cs_device_menu-test_label.py::test_label_loop_all_categories": "eb2189a7f922b783d1a7363cd136d973689fe15340cc2f4251992fab872282db",
+"T3W1_cs_device_menu-test_label.py::test_label_over_32_chars": "0232d4ac948109aca876429c724014a9da805f700d01b57656e1af251136b72a",
+"T3W1_cs_device_menu-test_label.py::test_label_uninitialized": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_cs_device_menu-test_notifications.py::test_backup_failed": "dfd445ff46f1c9ba195036b9cc2b61a6640eff3bf6138ab17754dd8cc41032f7",
+"T3W1_cs_device_menu-test_notifications.py::test_backup_needed": "3e9219faf28dc045827113d9a0ebec8e42437bc39cb3e437cbdb6b3992e89538",
+"T3W1_cs_device_menu-test_notifications.py::test_pin_not_set": "0811c43580023d7c557c214a10b98fbeff9b9825ed8f9aaf02d2f4457c66b0f3",
+"T3W1_cs_device_menu-test_notifications.py::test_seedless": "f47325f31b7cff66ad1ed9a2d98659b7161306440ae40e503ceac2407f8ffa92",
+"T3W1_cs_device_menu-test_traverse_menu.py::test_traverse_initialized": "4b2d275434eac0db6d563129a5608f713131c556cb092996618be9d9bb0839ad",
+"T3W1_cs_device_menu-test_traverse_menu.py::test_traverse_initialized_no_pin": "20b549eaa6281bbe3f846ddbe79a814cf28cf0bfb11187410efd7ba41cd66f76",
+"T3W1_cs_device_menu-test_traverse_menu.py::test_traverse_uninitialized": "044bee0a60815ed431a6df5dff29a16aca8e341600dec7c874f02ef9ca27615d",
+"T3W1_cs_device_menu-test_wipe.py::test_wipe": "24009dc7bfb5e37fef83919ac1ee3180905bf39891c76ac18efe9e95c6b68dfb",
"T3W1_cs_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "eea8b7f8b5097ea3a9f8a7caa05c838a6794d2de9010df91ec37dcb5efaf2492",
"T3W1_cs_test_autolock.py::test_autolock_does_not_interrupt_signing": "98ccc6cb23c7f29b3fb76977e22aa274e5de8de0ebd3f52c58966520f4ad61de",
"T3W1_cs_test_autolock.py::test_autolock_interrupts_passphrase": "ce2c8c2c95610e4ddd8c6733225b9fb562896179080e58957d430fb4f51a9ee4",
@@ -28880,6 +28910,36 @@
"T3W1_cs_test_tutorial_eckhart.py::test_tutorial_menu_close": "158dcdaf56948d0509ae95b05da867488f806359d2cc9185942dbc8dd05c4d66",
"T3W1_cs_test_tutorial_eckhart.py::test_tutorial_menu_tropic": "ac66810d67dfd0a482bd428a4395795692a37c11d5ded87dba48cbd1adc81cd4",
"T3W1_cs_test_tutorial_eckhart.py::test_tutorial_restart": "42f76200b4b5a7947b7228380011d25fa2fcff5ed0af6659dc527fe05a6dfe6f",
+"T3W1_de_device_menu-test_auto_lock.py::test_auto_lock_battery_cancel": "e65edaa261bb497c4db8f5b0fcfa733c955fc2a913e02ee1d60ef966bdf5a204",
+"T3W1_de_device_menu-test_auto_lock.py::test_auto_lock_battery_change": "6b3481c3ba37682b32619d6427990be319d1b2d210afec50c449a4f1e4b9a902",
+"T3W1_de_device_menu-test_auto_lock.py::test_auto_lock_pin_not_set": "3bffcad9d9dda07a25bd3bc6e0d8aee14ae476be71647ee7d5ec5cc8a400bc78",
+"T3W1_de_device_menu-test_auto_lock.py::test_auto_lock_uninitialized": "a097c88f5646f60c71ef0aeaf30bd9eb11bf76c3a9bed8ff6a7df23e457df10c",
+"T3W1_de_device_menu-test_auto_lock.py::test_auto_lock_usb_cancel": "a5e1ac535b5fd18ac222417f3e67cb404c2975c9a77c906b0f79a650a5d0e85f",
+"T3W1_de_device_menu-test_auto_lock.py::test_auto_lock_usb_change": "42898fc84240fe7341bf32ab70bf3f9c3dece40773c4cc2c72b3afb9a4fd66c3",
+"T3W1_de_device_menu-test_check_backup.py::test_backup_check_cancel": "12d8ef3d8968866888f9192fa330a3a69e8c1316805e07885439248264d16044",
+"T3W1_de_device_menu-test_check_backup.py::test_backup_needed_fails": "c94696a52bb66f5a1c5ec8694749b6c5798319df1807a59fd939d67007e97a1a",
+"T3W1_de_device_menu-test_check_backup.py::test_no_backup_fails": "32f7ece1a421303cf214be5af0d36255a4ae85750129df37a4862c1e540b00e5",
+"T3W1_de_device_menu-test_check_backup.py::test_uninitialized_fails": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_de_device_menu-test_device_settings.py::test_brightness": "9ddf6d03eb92cbac09875f843dabe22b1cc5a54d33117f2f3ee3bae0984401ed",
+"T3W1_de_device_menu-test_device_settings.py::test_device_settings_uninitialized": "dd21f98cf9942fd89a1aaa767561c9b670835b034224fcf7798fd8e9d61fc718",
+"T3W1_de_device_menu-test_device_settings.py::test_toggle_haptic": "98735f888f827501b25c78c6a737c1983a800c620f709c65b1d95f6c8c9a2b90",
+"T3W1_de_device_menu-test_device_settings.py::test_toggle_led": "4071c6653dd5c7134ced77db9692de57a95bfef207269c66a10da1cb3ae0170e",
+"T3W1_de_device_menu-test_label.py::test_change_label": "fd54a6768d33e9b5c94b11bd702991ec6efbd9397f707f7d7b84fb55c21ef2a6",
+"T3W1_de_device_menu-test_label.py::test_label_cancel": "d996f3a5b5b2c6cf4cb06e92da579815d76b46d686843d83e365a1041038adac",
+"T3W1_de_device_menu-test_label.py::test_label_click_same_button_many_times": "c2707edea479d0cdf67f8d2a880daa5bffa91145b35584b3be38f6e47734ea64",
+"T3W1_de_device_menu-test_label.py::test_label_cycle_through_last_character": "9037074514813d432155d29438ae6976f4176d93fc5fa9b253f64b134d815e48",
+"T3W1_de_device_menu-test_label.py::test_label_empty": "15ae50b370a5f93a83eab41e368ed2b910882dbf081119f146f6d9416a6269ff",
+"T3W1_de_device_menu-test_label.py::test_label_loop_all_categories": "f63b5e43b2972b4394b9aaa3fb03a8327da9455e8708cc8457152bc29daf4f6e",
+"T3W1_de_device_menu-test_label.py::test_label_over_32_chars": "a15b839fccf0bbfcb6e5042d65bf69d4ce3a8dcadf815e7233359ce5c211125e",
+"T3W1_de_device_menu-test_label.py::test_label_uninitialized": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_de_device_menu-test_notifications.py::test_backup_failed": "9a6889d214b8707677827220958fe449460aedfe485b32d1626862124a92e3f3",
+"T3W1_de_device_menu-test_notifications.py::test_backup_needed": "ecb0fe08d4aa61ebfb7c91da6eb271744f2d50e9dd3e71552a75703417c17d61",
+"T3W1_de_device_menu-test_notifications.py::test_pin_not_set": "3d0075746175052f12ef3b62d9328d9d3fc2a6ba129b7dcecf24c3290490dba1",
+"T3W1_de_device_menu-test_notifications.py::test_seedless": "a5f1836c258f20a18e2be5dffc5583f684df48c7688a734f3fca89c01d613996",
+"T3W1_de_device_menu-test_traverse_menu.py::test_traverse_initialized": "0c0b57240a91ce1254cdc328977ad0eea4b7ac1b646ed687fb13c548701cfcd1",
+"T3W1_de_device_menu-test_traverse_menu.py::test_traverse_initialized_no_pin": "7535ae36627906d6c562b23daf476adb55b6a705379849e45a43b28386f3eaee",
+"T3W1_de_device_menu-test_traverse_menu.py::test_traverse_uninitialized": "8e8087b6db71321d853fe58403f409c131c6276b2bdaec44265039c6d50740ca",
+"T3W1_de_device_menu-test_wipe.py::test_wipe": "a3955a188bda02377236b1b7e083b6e66d92e28e4aef43b153baaf037fdabf30",
"T3W1_de_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "4e7039853f8840b953a1003575b84b1b207017df9eff5ec1bf70ebec37f97be5",
"T3W1_de_test_autolock.py::test_autolock_does_not_interrupt_signing": "2b9a53a874d2fbd1632d2147ea690658ac589268d6fbe1e1dbc3168d8f593a7a",
"T3W1_de_test_autolock.py::test_autolock_interrupts_passphrase": "985ae0cb82525e634741bb72815aaf2e06d7684ece8c05942de8ba82f654c0fb",
@@ -28940,6 +29000,36 @@
"T3W1_de_test_tutorial_eckhart.py::test_tutorial_menu_close": "db0e133f55ae1f5a85f85d5788766f1710742e63b352cfc5815de56e284e6976",
"T3W1_de_test_tutorial_eckhart.py::test_tutorial_menu_tropic": "415d3ec828f98d912750b572b8b4970cdfca0f69d5f911bd6d81d862dc953c87",
"T3W1_de_test_tutorial_eckhart.py::test_tutorial_restart": "a09e42527ef61960aed20e7483bd3d8ca99f880060877da54891b219679459c5",
+"T3W1_en_device_menu-test_auto_lock.py::test_auto_lock_battery_cancel": "e57521f8c5f46dd1b586898a7f0e8022c41f4f854d77d00311f3196d0215047d",
+"T3W1_en_device_menu-test_auto_lock.py::test_auto_lock_battery_change": "540f7e19a72600ee0214b3660a7622f56307247f400baca33740b4b171f0f7c2",
+"T3W1_en_device_menu-test_auto_lock.py::test_auto_lock_pin_not_set": "6431968f841f5716e9ddf8ff379811058dc466651d833148898e1017e3adcc42",
+"T3W1_en_device_menu-test_auto_lock.py::test_auto_lock_uninitialized": "00101f34359421370f958189a193d96fa1c7046dc1968ec1e367719418811d37",
+"T3W1_en_device_menu-test_auto_lock.py::test_auto_lock_usb_cancel": "dd8a49a6700fb1c2d683bb78ec57943383968868c738b9420525b287160d709b",
+"T3W1_en_device_menu-test_auto_lock.py::test_auto_lock_usb_change": "f3c1d3837bf7d817d6e8c4519b0a79b4fb17406a1aee726dc88ef9b48657a092",
+"T3W1_en_device_menu-test_check_backup.py::test_backup_check_cancel": "f7960ae38f737d57df017337c0e0a684124becd3d9de046aee3990902a997ceb",
+"T3W1_en_device_menu-test_check_backup.py::test_backup_needed_fails": "fb3120f1899ceab19654335ed391516b9030737762120bae07161f497deefcb0",
+"T3W1_en_device_menu-test_check_backup.py::test_no_backup_fails": "395556180048c5471d78770521fc625d75062b1774b3a64f0e0bb758faae556a",
+"T3W1_en_device_menu-test_check_backup.py::test_uninitialized_fails": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_en_device_menu-test_device_settings.py::test_brightness": "9a84b7043406ce654c4f0a0629cb589b9b2b865607fcfe28d734d97f7ffcf5af",
+"T3W1_en_device_menu-test_device_settings.py::test_device_settings_uninitialized": "3d886e672ca3e23fdca97a6d824c2a51fc6bfcf24d87330fb97b1c4a835d6a74",
+"T3W1_en_device_menu-test_device_settings.py::test_toggle_haptic": "931d9afceb0ba1e4faae891775819277242d889644d5c0c5863fc8c9fcf859b1",
+"T3W1_en_device_menu-test_device_settings.py::test_toggle_led": "db4c41e6b16224a315a35cb21807d7e371b2eeade2b390e2d9a62c5281ec03d3",
+"T3W1_en_device_menu-test_label.py::test_change_label": "0510f921b16a3284953c36900240033fb8ac4e6792b17ece23131810e621a1c7",
+"T3W1_en_device_menu-test_label.py::test_label_cancel": "8014cab2c4b2ff1c9a4e84edb64a8d1c3f14bb89d8ae800f919de0dc2bed7aa1",
+"T3W1_en_device_menu-test_label.py::test_label_click_same_button_many_times": "0ee97864e333d3c0e256fcc65526453ef46cb934d3671b6a2225bca90b3b0956",
+"T3W1_en_device_menu-test_label.py::test_label_cycle_through_last_character": "2755f64ec6ef7fced8955ed0b6b9bfb0aedf8c9cdb4a6ea04479ad3ef77a5493",
+"T3W1_en_device_menu-test_label.py::test_label_empty": "53a27891f5d331b9804e05b56a811d97c3e860516e143a3289c515a2d8d48af5",
+"T3W1_en_device_menu-test_label.py::test_label_loop_all_categories": "90e8636706a599bea41581a307d5f582c826e3c2791f8711caa6439704ce44c5",
+"T3W1_en_device_menu-test_label.py::test_label_over_32_chars": "4147cc80a6f206022cd4840b591d04c203bd211d1c618e5847d148ff2eaa8470",
+"T3W1_en_device_menu-test_label.py::test_label_uninitialized": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_en_device_menu-test_notifications.py::test_backup_failed": "40b56566057e3163e7b07afcdb7a3b60b709dd45f8a814320648dbe30aa3b9ff",
+"T3W1_en_device_menu-test_notifications.py::test_backup_needed": "f55c9030025edec1c3b08bfd444d2c77c232a7f7c22fda6f340bd602ef853a73",
+"T3W1_en_device_menu-test_notifications.py::test_pin_not_set": "a9c8a15dc28878101f85a94333e908006fe2e3184a62dbe911f0d390a4f70182",
+"T3W1_en_device_menu-test_notifications.py::test_seedless": "30782c88c129824c038c14d999b63b6c99f486333e1fa1dc4af8e76f9d42c3ec",
+"T3W1_en_device_menu-test_traverse_menu.py::test_traverse_initialized": "f42f29d7c23667386bfe088d5cf77b493056efd1373e3df85e20cd2d4fada08d",
+"T3W1_en_device_menu-test_traverse_menu.py::test_traverse_initialized_no_pin": "36e329cb7df28b5d341b9994c80744697bdcb09fb2c255e4a1ec179b701ca39d",
+"T3W1_en_device_menu-test_traverse_menu.py::test_traverse_uninitialized": "9707a2a06586e89ce609550b97401f45d59a36c7c8c1b06090edf90969d19208",
+"T3W1_en_device_menu-test_wipe.py::test_wipe": "6d61755d7374c71c7e9e85bb6501dafbf3e297da73cee19d9bf1acfab5873f7b",
"T3W1_en_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "060a9daf0f5d8ceed76e223755059f9fd7728a64818644964cdd0631979d05ee",
"T3W1_en_test_autolock.py::test_autolock_does_not_interrupt_signing": "2ee6ce1fdd78d5869690d1a3b9f1aad441e8d17a69efa6f064f0070f5686aedb",
"T3W1_en_test_autolock.py::test_autolock_interrupts_passphrase": "5f60b98ad8a3919e0dc65dd7415e0c5bdbc5461500ab8070185272a55e15a6d7",
@@ -29000,6 +29090,36 @@
"T3W1_en_test_tutorial_eckhart.py::test_tutorial_menu_close": "99cb0bed4bfdf7b67142b093c9b827a8fbf103c511efab534846f3fafbc0a362",
"T3W1_en_test_tutorial_eckhart.py::test_tutorial_menu_tropic": "a6c907391f99f5517ffe0bad2f4234aef4e3bfc5fedb5152f151ca291d90c1cb",
"T3W1_en_test_tutorial_eckhart.py::test_tutorial_restart": "5b3eff72e15fb7221cb6c6b16a9ebacd53a502c788a941bc38b5f352934aec41",
+"T3W1_es_device_menu-test_auto_lock.py::test_auto_lock_battery_cancel": "233ba1903cce403373be77cc9ac35294126c3f2eb026365c2e18ec0b2158136c",
+"T3W1_es_device_menu-test_auto_lock.py::test_auto_lock_battery_change": "1515d45c36c405d6be337892ac9ff9850e881366adcc9668f54cd3c3a84de014",
+"T3W1_es_device_menu-test_auto_lock.py::test_auto_lock_pin_not_set": "bc4e77eb539c6cbba76ed66792d034bae3e3cf60fdfd88bb1bed21a789ffc839",
+"T3W1_es_device_menu-test_auto_lock.py::test_auto_lock_uninitialized": "fa71bed01d78c01b061840fefdea55f11d1c6b25f024d41120548ef8066fa0b8",
+"T3W1_es_device_menu-test_auto_lock.py::test_auto_lock_usb_cancel": "79206e092ec118258aa55fc05b02006efb98a7956d83016c97ddedc6382017ff",
+"T3W1_es_device_menu-test_auto_lock.py::test_auto_lock_usb_change": "47ebb3e84095473ae2e1fec9658185428ed46d9982840a19cb6eb57e9353103c",
+"T3W1_es_device_menu-test_check_backup.py::test_backup_check_cancel": "0b50aad977376053ccdde0a8cfdffe0dc98f70e4450c4e97d9518ebbdaf4d3e6",
+"T3W1_es_device_menu-test_check_backup.py::test_backup_needed_fails": "3bfddf8b7921ff08a6a9f36a1ef82bca928dca2948d71ca6506cca0aaf0d5455",
+"T3W1_es_device_menu-test_check_backup.py::test_no_backup_fails": "b793498c5928d31ce35348ef10b92121136c03de9ad435c906ee8091b79aa553",
+"T3W1_es_device_menu-test_check_backup.py::test_uninitialized_fails": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_es_device_menu-test_device_settings.py::test_brightness": "e4229897dcd3c8d426be1d9ff520c949a366af48d370d428d47f9c7f3e0704b6",
+"T3W1_es_device_menu-test_device_settings.py::test_device_settings_uninitialized": "fadd1e8b5e0f9c8fe070e260313ca99e8fe16a97f74a0baa16ae044fdf066fa4",
+"T3W1_es_device_menu-test_device_settings.py::test_toggle_haptic": "56536ae9cd7c4ff8022def4ec3350031d3614064d961f53a2850f2be425af201",
+"T3W1_es_device_menu-test_device_settings.py::test_toggle_led": "41eed582e9df59931f2cc1043f11d839ad62181942df87754525b96e9b462850",
+"T3W1_es_device_menu-test_label.py::test_change_label": "9b5a9496aafd0b27fd8bd853463c02715f07eb6650cfbd8bda14f2088c744660",
+"T3W1_es_device_menu-test_label.py::test_label_cancel": "2504bc794d499a8ef42517e3233f1aacec91a766d2f7d567faae4fd8b0e757f8",
+"T3W1_es_device_menu-test_label.py::test_label_click_same_button_many_times": "050824be168d310494b39df72e78bc6e02d543f795188dc242c5ebcbeb8f4385",
+"T3W1_es_device_menu-test_label.py::test_label_cycle_through_last_character": "e93906c17cdc6872278c228097eeb32085815f35b441790bf4ac4a32f4efd878",
+"T3W1_es_device_menu-test_label.py::test_label_empty": "03a3d82cc1306ad52151856e758b7ea4d5558a143e292cd201e1983106051e3b",
+"T3W1_es_device_menu-test_label.py::test_label_loop_all_categories": "6cd2d93dd66cbfb1191a2d56a539f92dcda159802b1f5976103b47b3a7f907c9",
+"T3W1_es_device_menu-test_label.py::test_label_over_32_chars": "47247c002ed5f5e9e16917b7f68acdceaa5b4f1cd44cdbc694312675d1d68f61",
+"T3W1_es_device_menu-test_label.py::test_label_uninitialized": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_es_device_menu-test_notifications.py::test_backup_failed": "5499d9bdbf9b3a1161b1fa2cba37359bc0f220e6dae15fac0d927b813b7fc46a",
+"T3W1_es_device_menu-test_notifications.py::test_backup_needed": "6aa38827cb41ae91cd198cc32c55daa19850442faf10587b751ef3f7ffb803bb",
+"T3W1_es_device_menu-test_notifications.py::test_pin_not_set": "65c8ece078e93712f8cb54a8d687f22548cbbed8cb703dc86833fbaabf001502",
+"T3W1_es_device_menu-test_notifications.py::test_seedless": "bbe2a9d5b69d39d082b3c798a613e150a3ba9ce31e4c3dcff38a4d169f699ce3",
+"T3W1_es_device_menu-test_traverse_menu.py::test_traverse_initialized": "a83398fbd7c5c9c7f96a803cc3a791214c163356c81ce7acdd6f728fc5e8dd52",
+"T3W1_es_device_menu-test_traverse_menu.py::test_traverse_initialized_no_pin": "1fcde37866302b59bd8205f8fbf52e530ae828033597f7b9934876cdeb4ba976",
+"T3W1_es_device_menu-test_traverse_menu.py::test_traverse_uninitialized": "88741980041f2a4533318a2e8b269d046e4f0bff3a5243f9e6d24f8ee990b1e7",
+"T3W1_es_device_menu-test_wipe.py::test_wipe": "c54730002754153a0e1f93ccfc94762abbb05e5d3507a6e3fe9824d1dd85fb29",
"T3W1_es_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "09f47a5867ebf5d878a9b1a0ef4fc9ab88ad418ff0e2fe10b1a7c986e6dd377c",
"T3W1_es_test_autolock.py::test_autolock_does_not_interrupt_signing": "2d9ac3411ef9a28225282838562eeeac38885e40dbc820ffd26e8d9303a54b75",
"T3W1_es_test_autolock.py::test_autolock_interrupts_passphrase": "0ab05f9c2618121252cfabffe49b09d3bc8661f51c835d81fb350784f40a9877",
@@ -29060,6 +29180,36 @@
"T3W1_es_test_tutorial_eckhart.py::test_tutorial_menu_close": "9acd8b9d22c0c55912bc6838538a2cbe25b264d5818f626bae54be614a934a19",
"T3W1_es_test_tutorial_eckhart.py::test_tutorial_menu_tropic": "be951798cb7e6beb0f9c6da102d4c30c83bfab5fcb2a7df97f72f2a37d8a9507",
"T3W1_es_test_tutorial_eckhart.py::test_tutorial_restart": "602d1e507e80a3329b283667ad56b91e7dac918171c38a75d1d88931e6ba7908",
+"T3W1_fr_device_menu-test_auto_lock.py::test_auto_lock_battery_cancel": "c700a9e955cbb286237023768152d2529fd0fd6382606eab54c69594f48c80d9",
+"T3W1_fr_device_menu-test_auto_lock.py::test_auto_lock_battery_change": "3485f09f7fc4da99b16aa62383ef5ace6d1d3013e2f1dce39d43a190392d0e7c",
+"T3W1_fr_device_menu-test_auto_lock.py::test_auto_lock_pin_not_set": "ae75e0d8bce8fc0f4d3b585532d773eb30e890050b46c676eecee59f3f7afa45",
+"T3W1_fr_device_menu-test_auto_lock.py::test_auto_lock_uninitialized": "3837ee06b8d1ce050aedba6d620e29e4dacfd4b315d4dfd685d00a92ccc5975b",
+"T3W1_fr_device_menu-test_auto_lock.py::test_auto_lock_usb_cancel": "50173e061e35fe429f8647c0d5f4244ff8752b4073e6decdb65230bf5e79c7d4",
+"T3W1_fr_device_menu-test_auto_lock.py::test_auto_lock_usb_change": "f85a1fcfe82af1de882cf7a2762cf3ffbaf7e9de9a38c31891639c92b4bc2333",
+"T3W1_fr_device_menu-test_check_backup.py::test_backup_check_cancel": "336808f7f1215e07ba4dbd46b053d845e5208b3e6bc4991865d61c4562570509",
+"T3W1_fr_device_menu-test_check_backup.py::test_backup_needed_fails": "127e600409c0d92ab7ca95ae516b91d97b34aa4d1b8c38271d70b17810188cdc",
+"T3W1_fr_device_menu-test_check_backup.py::test_no_backup_fails": "3ca3d8dcf5e57671e92ae1dca97e7766dfbd9e3ffaf9343731d4f06580235fc9",
+"T3W1_fr_device_menu-test_check_backup.py::test_uninitialized_fails": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_fr_device_menu-test_device_settings.py::test_brightness": "fb50b25d714f18b50c53ddb66ae87f2435ad11014c84b2c17504e047162a5989",
+"T3W1_fr_device_menu-test_device_settings.py::test_device_settings_uninitialized": "9b133f37ca8c84195d75e463d81989a379d5f213d02f8b7478d0ce805fdd18f4",
+"T3W1_fr_device_menu-test_device_settings.py::test_toggle_haptic": "e8156cf4eda1d29060f05b4a18d50032b0b344230609b7de7cababfe0a86b20b",
+"T3W1_fr_device_menu-test_device_settings.py::test_toggle_led": "b148b19e4f459b294c47384a041ad41ffda9cbbc2d2a3431b5f263861ef3a240",
+"T3W1_fr_device_menu-test_label.py::test_change_label": "a3834dcb86c14af13e9628a0f0dbe137e48454e03eab84da21c5579e2e369e90",
+"T3W1_fr_device_menu-test_label.py::test_label_cancel": "cd6acd055923d55dbe944b7970ea7e953af5a3f455efbf719e7b2b736f049dd7",
+"T3W1_fr_device_menu-test_label.py::test_label_click_same_button_many_times": "924913d879d13b9441b307dec57c9d58afc6383e30569a08aafb49241914a273",
+"T3W1_fr_device_menu-test_label.py::test_label_cycle_through_last_character": "7690d065ed3e71f94a5ef0850b67ac1f144ac3ab363c6a2aa40e00177510fab9",
+"T3W1_fr_device_menu-test_label.py::test_label_empty": "cdc5e2157def5758c0b0f8e827c074dcc30ef8f97fcb93d95a8d6920994653b8",
+"T3W1_fr_device_menu-test_label.py::test_label_loop_all_categories": "7e8a558d8478b519a1bb22ca529036069bf70b16b19b45698c03557276c34e18",
+"T3W1_fr_device_menu-test_label.py::test_label_over_32_chars": "ec10a54c8bd31f908d6a318dffb1c5f567baa418530ea327c323991966366401",
+"T3W1_fr_device_menu-test_label.py::test_label_uninitialized": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_fr_device_menu-test_notifications.py::test_backup_failed": "646d17e5a3770b02fb574ea8932aa50f65eed046553be5f7d2b5dc81d55353a6",
+"T3W1_fr_device_menu-test_notifications.py::test_backup_needed": "55f2d653365928a4598ac9f4cb769dc4d72558d50011f5e27ecfa11ec1ef763e",
+"T3W1_fr_device_menu-test_notifications.py::test_pin_not_set": "94b37b42f3b1cfd33d196e5377eec866f5360e0ef6a08de089506525c7d0b006",
+"T3W1_fr_device_menu-test_notifications.py::test_seedless": "e6dd2332fbc9992e5418d109ba6d3b9089712500dc0ba9cb4a33fcf3d91ba552",
+"T3W1_fr_device_menu-test_traverse_menu.py::test_traverse_initialized": "840adda9a575f938874a2fa704281f027f2a9bf0e9e0f2f2e9a4dba4723ae309",
+"T3W1_fr_device_menu-test_traverse_menu.py::test_traverse_initialized_no_pin": "764f3ef5412debada381a5d892b73c0e3596e2eb853e10341d3755bdc473c82d",
+"T3W1_fr_device_menu-test_traverse_menu.py::test_traverse_uninitialized": "064d7dfe6195fe5d5e1160ace7eac14cbde44819a6c4c3e6023d59894667109c",
+"T3W1_fr_device_menu-test_wipe.py::test_wipe": "1ad96168439572368bfc1b74e9da63f91a6c8c193764886d7ff1ead255ac5380",
"T3W1_fr_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "839938053ffdd3a920d70a2bde8427be63e16d0e7555b960ac3e2c52cb4f25c5",
"T3W1_fr_test_autolock.py::test_autolock_does_not_interrupt_signing": "0c370820106050b9b8f45cd449b9249677f2bf28f9d900329ec7b337061c6a90",
"T3W1_fr_test_autolock.py::test_autolock_interrupts_passphrase": "38cf3fe298bc5a265c14eb39431532db87fb08f4a9efe9d890aeae9a578c8af5",
@@ -29120,6 +29270,36 @@
"T3W1_fr_test_tutorial_eckhart.py::test_tutorial_menu_close": "d534691b2a0a9456362f7857a2760309199625ce5f406aa8ff9b4e17864c1d74",
"T3W1_fr_test_tutorial_eckhart.py::test_tutorial_menu_tropic": "2496697ad773bae7cc5c2afb2cd834e3941cceeddb7d100a7fed0290d2d71087",
"T3W1_fr_test_tutorial_eckhart.py::test_tutorial_restart": "ffa0fe533b1463085d1ae36dbdfa2b02c8f46c98f95e5daf76665c875d2fa021",
+"T3W1_pt_device_menu-test_auto_lock.py::test_auto_lock_battery_cancel": "a5ebb4f73a7ea034ac87947b56e09c2813d283f8aecd9b835cf5a868cc61ea63",
+"T3W1_pt_device_menu-test_auto_lock.py::test_auto_lock_battery_change": "c1f12df4744d7f5cbcce657c9b9e7db196332fec2698f5a976ad028dac229a33",
+"T3W1_pt_device_menu-test_auto_lock.py::test_auto_lock_pin_not_set": "3b28d66badcaedc76572172aa444da8000fc0ae5eb96d07dafa2939a9a424ccb",
+"T3W1_pt_device_menu-test_auto_lock.py::test_auto_lock_uninitialized": "c2383c53dd4fc7dbb60c6ec9191a16514115d7376260579ce5169842c8d93211",
+"T3W1_pt_device_menu-test_auto_lock.py::test_auto_lock_usb_cancel": "0bb74e748fa3515dc52daf47425fe074480fd35c947083a32dcebce7d984caf6",
+"T3W1_pt_device_menu-test_auto_lock.py::test_auto_lock_usb_change": "62c627d84be24a4a754265d578d0abf0c632dd74191a804eb05843db9c82f0ec",
+"T3W1_pt_device_menu-test_check_backup.py::test_backup_check_cancel": "5444fde532035192eeadabbc38e076eaf19db70b831da18c12ae49f220485ac8",
+"T3W1_pt_device_menu-test_check_backup.py::test_backup_needed_fails": "ebf419d5203b62a9a6cb6874dfebce5b4ee78d089b5aa4a40fccf8bfb11c6fc9",
+"T3W1_pt_device_menu-test_check_backup.py::test_no_backup_fails": "3ed92e9990189e2e2ca4614e55e9abee6f5373778f3501a53cd4074f3d07d02b",
+"T3W1_pt_device_menu-test_check_backup.py::test_uninitialized_fails": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_pt_device_menu-test_device_settings.py::test_brightness": "ad3653244d8cd6fcb9f66cb080c0640c330eeb0cdb1f23ed618097c867a80f9f",
+"T3W1_pt_device_menu-test_device_settings.py::test_device_settings_uninitialized": "6e1a319cefad2fedfba3b79f448a20d981058b1738f737fac5453a8703aab00f",
+"T3W1_pt_device_menu-test_device_settings.py::test_toggle_haptic": "1c74667c078e25e7e0d37c7b2aa35f7c8ab02cd88e74695ecb355c82a293b0cc",
+"T3W1_pt_device_menu-test_device_settings.py::test_toggle_led": "9d65067cff7cb69503bf752236b63fed0b83500bca8ca1d09f2a84d0f0c38689",
+"T3W1_pt_device_menu-test_label.py::test_change_label": "3a357fd0ce74595be129a668529cf1fd8ee6938053a3917c88895b5c98452799",
+"T3W1_pt_device_menu-test_label.py::test_label_cancel": "657077f513d7c9f26ef2e20f156ce0adb5233986b411148e70ff8383e906ecd0",
+"T3W1_pt_device_menu-test_label.py::test_label_click_same_button_many_times": "277b78b64d0fdb63e7aa478addda87e8821418b182ca072122cb826640c61a5b",
+"T3W1_pt_device_menu-test_label.py::test_label_cycle_through_last_character": "6ff56acecfcad833886022e1963e01dcd7c32e53210586587c5ad67cb7b407ac",
+"T3W1_pt_device_menu-test_label.py::test_label_empty": "f2e9f0bae92ff32df2665a30b645e4d0b2881d48fa3883cb5b173ac967c1f896",
+"T3W1_pt_device_menu-test_label.py::test_label_loop_all_categories": "775151eff47ab119f33dabe3ac9ec672f8e205a033f448df79b0212bafd9f158",
+"T3W1_pt_device_menu-test_label.py::test_label_over_32_chars": "105e04fba1b007545f62d8a1b2b4ca1953af9e8491073540d5865daec95dad6e",
+"T3W1_pt_device_menu-test_label.py::test_label_uninitialized": "987d1c62e6576b9cc24373e00df522efdc9736af978d6032f7d319af8daaef5d",
+"T3W1_pt_device_menu-test_notifications.py::test_backup_failed": "6a8a7dd14ba9eb104d1a115792281a05f223170a3742c62b03b93826433734df",
+"T3W1_pt_device_menu-test_notifications.py::test_backup_needed": "29bc804809d98b87ecb26b473ae96585859e4f85cc9704c346531a312e27ef3b",
+"T3W1_pt_device_menu-test_notifications.py::test_pin_not_set": "48b3e5e0640d01defbce47a8f81f34ea3182ec8b24330d8e945211bfb744e26b",
+"T3W1_pt_device_menu-test_notifications.py::test_seedless": "27b59acec95fd7a89dbd4b4b85c45f4e21313c568c84c1de0a984eed084506ad",
+"T3W1_pt_device_menu-test_traverse_menu.py::test_traverse_initialized": "643cd232705a67a8d9611d1ee3f3bbd30f5feb29ab142982bea475c561700731",
+"T3W1_pt_device_menu-test_traverse_menu.py::test_traverse_initialized_no_pin": "b307892d26a386e64b94169d22497fea1fb28a50aa9579c3e2ccf6e9cb4020c1",
+"T3W1_pt_device_menu-test_traverse_menu.py::test_traverse_uninitialized": "2781fc5d6afbfcee175d9f0cb5ac8f7b42e2a9681752c2103ccde325f9b09c07",
+"T3W1_pt_device_menu-test_wipe.py::test_wipe": "9f71bd0b65a6a5517424ee26b322a32de09693570a18411fbbddbaeda82fc52e",
"T3W1_pt_test_autolock.py::test_autolock_does_not_interrupt_preauthorized": "55a6c9b57d940b275aa38784364015e35b6bbcdcf25dd7525056ce9b31c778e3",
"T3W1_pt_test_autolock.py::test_autolock_does_not_interrupt_signing": "5970508fe5625ab6f3bbc0b3fb84b30d4d31f97f9e9501a02ff4bdd3a6607612",
"T3W1_pt_test_autolock.py::test_autolock_interrupts_passphrase": "c3aeaec58a097cbd25929f482856d37bfd036bac4278f9072a615458a43d2cbe",
Why this scored 20/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.