diff --git a/common/protob/messages-debug.proto b/common/protob/messages-debug.proto index 88d2820259..3727b6243f 100644 --- a/common/protob/messages-debug.proto +++ b/common/protob/messages-debug.proto @@ -110,6 +110,8 @@ message DebugLinkGetState { // trezor-core only - wait until current layout changes // changed in 2.6.4: multiple wait types instead of true/false. optional DebugWaitType wait_layout = 3 [default=IMMEDIATE]; + // Responds immediately with an empty `DebugLinkState` (used for client-side synchronization). + optional bool return_empty_state = 4 [default=false]; } /** diff --git a/core/Makefile b/core/Makefile index 7af1fc4895..3bca1bb31d 100644 --- a/core/Makefile +++ b/core/Makefile @@ -204,12 +204,12 @@ test_emu_persistence_ui: ## run persistence tests with UI testing test_emu_ui: ## run ui integration tests $(EMU_TEST) $(PYTEST) $(TESTPATH)/device_tests $(TESTOPTS) \ - --ui=test --ui-check-missing --record-text-layout --do-master-diff \ + --ui=test --ui-check-missing --do-master-diff \ --lang=$(TEST_LANG) test_emu_ui_multicore: ## run ui integration tests using multiple cores $(PYTEST) -n $(MULTICORE) $(TESTPATH)/device_tests $(TESTOPTS) --timeout $(PYTEST_TIMEOUT) \ - --ui=test --ui-check-missing --record-text-layout --do-master-diff \ + --ui=test --ui-check-missing --do-master-diff \ --control-emulators --model=core --random-order-seed=$(RANDOM) \ --lang=$(TEST_LANG) diff --git a/core/src/apps/debug/__init__.py b/core/src/apps/debug/__init__.py index 6486a27e1c..21ffc9f1bd 100644 --- a/core/src/apps/debug/__init__.py +++ b/core/src/apps/debug/__init__.py @@ -268,6 +268,11 @@ if __debug__: async def dispatch_DebugLinkGetState( msg: DebugLinkGetState, ) -> DebugLinkState | None: + if msg.return_empty_state: + from trezor.messages import DebugLinkState + + return DebugLinkState() + if msg.wait_layout == DebugWaitType.IMMEDIATE: return _state() diff --git a/core/src/trezor/messages.py b/core/src/trezor/messages.py index e529707f4b..1dbfcbc407 100644 --- a/core/src/trezor/messages.py +++ b/core/src/trezor/messages.py @@ -2898,11 +2898,13 @@ if TYPE_CHECKING: class DebugLinkGetState(protobuf.MessageType): wait_layout: "DebugWaitType" + return_empty_state: "bool" def __init__( self, *, wait_layout: "DebugWaitType | None" = None, + return_empty_state: "bool | None" = None, ) -> None: pass diff --git a/python/src/trezorlib/debuglink.py b/python/src/trezorlib/debuglink.py index 71fab102ba..0a2096993b 100644 --- a/python/src/trezorlib/debuglink.py +++ b/python/src/trezorlib/debuglink.py @@ -64,8 +64,7 @@ if TYPE_CHECKING: def __call__( self, hold_ms: int | None = None, - wait: bool | None = None, - ) -> "LayoutContent": ... + ) -> "None": ... InputFlowType = Generator[None, messages.ButtonRequest, None] @@ -416,11 +415,10 @@ def _make_input_func( def input_func( self: "DebugLink", hold_ms: int | None = None, - wait: bool | None = None, - ) -> LayoutContent: + ) -> None: __tracebackhide__ = True # for pytest # pylint: disable=W0612 decision.hold_ms = hold_ms - return self._decision(decision, wait=wait) + self._decision(decision) return input_func # type: ignore [Parameter name mismatch] @@ -442,12 +440,7 @@ class DebugLink: self.t1_screenshot_directory: Path | None = None self.t1_screenshot_counter = 0 - # Optional file for saving text representation of the screen - self.screen_text_file: Path | None = None - self.last_screen_content = "" - self.waiting_for_layout_change = False - self.layout_dirty = True self.input_wait_type = DebugWaitType.IMMEDIATE @@ -477,11 +470,6 @@ class DebugLink: assert self.model is not None return LayoutType.from_model(self.model) - def set_screen_text_file(self, file_path: Path | None) -> None: - if file_path is not None: - file_path.write_bytes(b"") - self.screen_text_file = file_path - def open(self) -> None: self.transport.begin_session() @@ -543,8 +531,19 @@ class DebugLink: raise TrezorFailure(result) return result - def read_layout(self) -> LayoutContent: - return LayoutContent(self.state().tokens) + def read_layout(self, wait: bool | None = None) -> LayoutContent: + """ + Force waiting for the layout by setting `wait=True`. Force not waiting by + setting `wait=False` -- useful when, e.g., you are causing the next layout to be + deliberately delayed. + """ + if wait is True: + wait_type = DebugWaitType.CURRENT_LAYOUT + elif wait is False: + wait_type = DebugWaitType.IMMEDIATE + else: + wait_type = None + return LayoutContent(self.state(wait_type=wait_type).tokens) def wait_layout(self, wait_for_external_change: bool = False) -> LayoutContent: # Next layout change will be caused by external event @@ -558,18 +557,12 @@ class DebugLink: obj = self._call( messages.DebugLinkGetState(wait_layout=DebugWaitType.NEXT_LAYOUT) ) - self.layout_dirty = True if isinstance(obj, messages.Failure): raise TrezorFailure(obj) return LayoutContent(obj.tokens) @contextmanager - def wait_for_layout_change(self) -> Iterator[LayoutContent]: - # set up a dummy layout content object to be yielded - layout_content = LayoutContent( - ["DUMMY CONTENT, WAIT UNTIL THE END OF THE BLOCK :("] - ) - + def wait_for_layout_change(self) -> Iterator[None]: # make sure some current layout is up by issuing a dummy GetState self.state() @@ -579,18 +572,14 @@ class DebugLink: # allow the block to proceed self.waiting_for_layout_change = True try: - yield layout_content + yield finally: self.waiting_for_layout_change = False - self.layout_dirty = True # wait for the reply resp = self._read() assert isinstance(resp, messages.DebugLinkState) - # replace contents of the yielded object with the new thing - layout_content.__init__(resp.tokens) - def reset_debug_events(self) -> None: # Only supported on TT and above certain version if (self.model is not models.T1B1) and not self.legacy_debug: @@ -634,44 +623,24 @@ class DebugLink: state = self._call(messages.DebugLinkGetState(wait_word_list=True)) return state.reset_word - def _decision( - self, decision: messages.DebugLinkDecision, wait: bool | None = None - ) -> LayoutContent: - """Send a debuglink decision and returns the resulting layout. + def _decision(self, decision: messages.DebugLinkDecision) -> None: + """Send a debuglink decision. If hold_ms is set, an additional 200ms is added to account for processing delays. (This is needed for hold-to-confirm to trigger reliably.) - - If `wait` is unset, the following wait mode is used: - - - `IMMEDIATE`, when in normal tests, which never deadlocks the device, but may - return an empty layout in case the next one didn't come up immediately. (E.g., - in SignTx flow, the device is waiting for more TxRequest/TxAck exchanges - before showing the next UI layout.) - - `CURRENT_LAYOUT`, when in tests running through a `DeviceHandler`. This mode - returns the current layout or waits for some layout to come up if there is - none at the moment. The assumption is that wirelink is communicating on - another thread and won't be blocked by waiting on debuglink. - - Force waiting for the layout by setting `wait=True`. Force not waiting by - setting `wait=False` -- useful when, e.g., you are causing the next layout to be - deliberately delayed. """ if not self.allow_interactions: - return self.wait_layout() + self.wait_layout() + return if decision.hold_ms is not None: decision.hold_ms += 200 self._write(decision) - self.layout_dirty = True - if wait is True: - wait_type = DebugWaitType.CURRENT_LAYOUT - elif wait is False: - wait_type = DebugWaitType.IMMEDIATE - else: - wait_type = self.input_wait_type - return self._snapshot_core(wait_type) + if self.model is models.T1B1: + return + # When the call below returns, we know that `decision` has been processed in Core. + self._call(messages.DebugLinkGetState(return_empty_state=True)) press_yes = _make_input_func(button=messages.DebugButton.YES) """Confirm current layout. See `_decision` for more details.""" @@ -698,58 +667,14 @@ class DebugLink: ) """Press right button. See `_decision` for more details.""" - def input(self, word: str, wait: bool | None = None) -> LayoutContent: + def input(self, word: str) -> None: """Send text input to the device. See `_decision` for more details.""" - return self._decision(messages.DebugLinkDecision(input=word), wait) + self._decision(messages.DebugLinkDecision(input=word)) - def click( - self, - click: Tuple[int, int], - hold_ms: int | None = None, - wait: bool | None = None, - ) -> LayoutContent: + def click(self, click: Tuple[int, int], hold_ms: int | None = None) -> None: """Send a click to the device. See `_decision` for more details.""" x, y = click - return self._decision( - messages.DebugLinkDecision(x=x, y=y, hold_ms=hold_ms), wait - ) - - def _snapshot_core( - self, wait_type: DebugWaitType = DebugWaitType.IMMEDIATE - ) -> LayoutContent: - """Save text and image content of the screen to relevant directories.""" - # skip the snapshot if we are on T1 - if self.model is models.T1B1: - return LayoutContent([]) - - # take the snapshot - state = self.state(wait_type) - layout = LayoutContent(state.tokens) - - if state.tokens and self.layout_dirty: - # save it, unless we already did or unless it's empty - self.save_debug_screen(layout.visible_screen()) - self.layout_dirty = False - - # return the layout - return layout - - def save_debug_screen(self, screen_content: str) -> None: - if self.screen_text_file is None: - return - - if not self.screen_text_file.exists(): - self.screen_text_file.write_bytes(b"") - - # Not writing the same screen twice - if screen_content == self.last_screen_content: - return - - self.last_screen_content = screen_content - - with open(self.screen_text_file, "a") as f: - f.write(screen_content) - f.write("\n" + 80 * "/" + "\n") + self._decision(messages.DebugLinkDecision(x=x, y=y, hold_ms=hold_ms)) def stop(self) -> None: self._write(messages.DebugLinkStop()) @@ -882,7 +807,8 @@ class DebugUI: # Paginating (going as further as possible) and pressing Yes if br.pages is not None: for _ in range(br.pages - 1): - self.debuglink.swipe_up(wait=True) + self.debuglink.swipe_up() + if self.debuglink.model is models.T3T1: layout = self.debuglink.read_layout() if "PromptScreen" in layout.all_components(): diff --git a/python/src/trezorlib/messages.py b/python/src/trezorlib/messages.py index 6d28305f69..024c3ae696 100644 --- a/python/src/trezorlib/messages.py +++ b/python/src/trezorlib/messages.py @@ -4136,6 +4136,7 @@ class DebugLinkGetState(protobuf.MessageType): 1: protobuf.Field("wait_word_list", "bool", repeated=False, required=False, default=None), 2: protobuf.Field("wait_word_pos", "bool", repeated=False, required=False, default=None), 3: protobuf.Field("wait_layout", "DebugWaitType", repeated=False, required=False, default=DebugWaitType.IMMEDIATE), + 4: protobuf.Field("return_empty_state", "bool", repeated=False, required=False, default=False), } def __init__( @@ -4144,10 +4145,12 @@ class DebugLinkGetState(protobuf.MessageType): wait_word_list: Optional["bool"] = None, wait_word_pos: Optional["bool"] = None, wait_layout: Optional["DebugWaitType"] = DebugWaitType.IMMEDIATE, + return_empty_state: Optional["bool"] = False, ) -> None: self.wait_word_list = wait_word_list self.wait_word_pos = wait_word_pos self.wait_layout = wait_layout + self.return_empty_state = return_empty_state class DebugLinkState(protobuf.MessageType): diff --git a/rust/trezor-client/src/protos/generated/messages_debug.rs b/rust/trezor-client/src/protos/generated/messages_debug.rs index d384b11545..58328f5250 100644 --- a/rust/trezor-client/src/protos/generated/messages_debug.rs +++ b/rust/trezor-client/src/protos/generated/messages_debug.rs @@ -1128,6 +1128,8 @@ pub struct DebugLinkGetState { pub wait_word_pos: ::std::option::Option, // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkGetState.wait_layout) pub wait_layout: ::std::option::Option<::protobuf::EnumOrUnknown>, + // @@protoc_insertion_point(field:hw.trezor.messages.debug.DebugLinkGetState.return_empty_state) + pub return_empty_state: ::std::option::Option, // special fields // @@protoc_insertion_point(special_field:hw.trezor.messages.debug.DebugLinkGetState.special_fields) pub special_fields: ::protobuf::SpecialFields, @@ -1204,8 +1206,27 @@ impl DebugLinkGetState { self.wait_layout = ::std::option::Option::Some(::protobuf::EnumOrUnknown::new(v)); } + // optional bool return_empty_state = 4; + + pub fn return_empty_state(&self) -> bool { + self.return_empty_state.unwrap_or(false) + } + + pub fn clear_return_empty_state(&mut self) { + self.return_empty_state = ::std::option::Option::None; + } + + pub fn has_return_empty_state(&self) -> bool { + self.return_empty_state.is_some() + } + + // Param is passed by value, moved + pub fn set_return_empty_state(&mut self, v: bool) { + self.return_empty_state = ::std::option::Option::Some(v); + } + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { - let mut fields = ::std::vec::Vec::with_capacity(3); + let mut fields = ::std::vec::Vec::with_capacity(4); let mut oneofs = ::std::vec::Vec::with_capacity(0); fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( "wait_word_list", @@ -1222,6 +1243,11 @@ impl DebugLinkGetState { |m: &DebugLinkGetState| { &m.wait_layout }, |m: &mut DebugLinkGetState| { &mut m.wait_layout }, )); + fields.push(::protobuf::reflect::rt::v2::make_option_accessor::<_, _>( + "return_empty_state", + |m: &DebugLinkGetState| { &m.return_empty_state }, + |m: &mut DebugLinkGetState| { &mut m.return_empty_state }, + )); ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( "DebugLinkGetState", fields, @@ -1249,6 +1275,9 @@ impl ::protobuf::Message for DebugLinkGetState { 24 => { self.wait_layout = ::std::option::Option::Some(is.read_enum_or_unknown()?); }, + 32 => { + self.return_empty_state = ::std::option::Option::Some(is.read_bool()?); + }, tag => { ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; }, @@ -1270,6 +1299,9 @@ impl ::protobuf::Message for DebugLinkGetState { if let Some(v) = self.wait_layout { my_size += ::protobuf::rt::int32_size(3, v.value()); } + if let Some(v) = self.return_empty_state { + my_size += 1 + 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 @@ -1285,6 +1317,9 @@ impl ::protobuf::Message for DebugLinkGetState { if let Some(v) = self.wait_layout { os.write_enum(3, ::protobuf::EnumOrUnknown::value(&v))?; } + if let Some(v) = self.return_empty_state { + os.write_bool(4, v)?; + } os.write_unknown_fields(self.special_fields.unknown_fields())?; ::std::result::Result::Ok(()) } @@ -1305,6 +1340,7 @@ impl ::protobuf::Message for DebugLinkGetState { self.wait_word_list = ::std::option::Option::None; self.wait_word_pos = ::std::option::Option::None; self.wait_layout = ::std::option::Option::None; + self.return_empty_state = ::std::option::Option::None; self.special_fields.clear(); } @@ -1313,6 +1349,7 @@ impl ::protobuf::Message for DebugLinkGetState { wait_word_list: ::std::option::Option::None, wait_word_pos: ::std::option::Option::None, wait_layout: ::std::option::Option::None, + return_empty_state: ::std::option::Option::None, special_fields: ::protobuf::SpecialFields::new(), }; &instance @@ -3650,39 +3687,40 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x01\x20\x03(\tR\x06tokens:\x02\x18\x01\"-\n\x15DebugLinkReseedRandom\ \x12\x14\n\x05value\x18\x01\x20\x01(\rR\x05value\"j\n\x15DebugLinkRecord\ Screen\x12)\n\x10target_directory\x18\x01\x20\x01(\tR\x0ftargetDirectory\ - \x12&\n\rrefresh_index\x18\x02\x20\x01(\r:\x010R\x0crefreshIndex\"\x91\ + \x12&\n\rrefresh_index\x18\x02\x20\x01(\r:\x010R\x0crefreshIndex\"\xc6\ \x02\n\x11DebugLinkGetState\x12(\n\x0ewait_word_list\x18\x01\x20\x01(\ \x08R\x0cwaitWordListB\x02\x18\x01\x12&\n\rwait_word_pos\x18\x02\x20\x01\ (\x08R\x0bwaitWordPosB\x02\x18\x01\x12e\n\x0bwait_layout\x18\x03\x20\x01\ (\x0e29.hw.trezor.messages.debug.DebugLinkGetState.DebugWaitType:\tIMMED\ - IATER\nwaitLayout\"C\n\rDebugWaitType\x12\r\n\tIMMEDIATE\x10\0\x12\x0f\n\ - \x0bNEXT_LAYOUT\x10\x01\x12\x12\n\x0eCURRENT_LAYOUT\x10\x02\"\x97\x04\n\ - \x0eDebugLinkState\x12\x16\n\x06layout\x18\x01\x20\x01(\x0cR\x06layout\ - \x12\x10\n\x03pin\x18\x02\x20\x01(\tR\x03pin\x12\x16\n\x06matrix\x18\x03\ - \x20\x01(\tR\x06matrix\x12'\n\x0fmnemonic_secret\x18\x04\x20\x01(\x0cR\ - \x0emnemonicSecret\x129\n\x04node\x18\x05\x20\x01(\x0b2%.hw.trezor.messa\ - ges.common.HDNodeTypeR\x04node\x123\n\x15passphrase_protection\x18\x06\ - \x20\x01(\x08R\x14passphraseProtection\x12\x1d\n\nreset_word\x18\x07\x20\ - \x01(\tR\tresetWord\x12#\n\rreset_entropy\x18\x08\x20\x01(\x0cR\x0creset\ - Entropy\x12,\n\x12recovery_fake_word\x18\t\x20\x01(\tR\x10recoveryFakeWo\ - rd\x12*\n\x11recovery_word_pos\x18\n\x20\x01(\rR\x0frecoveryWordPos\x12$\ - \n\x0ereset_word_pos\x18\x0b\x20\x01(\rR\x0cresetWordPos\x12N\n\rmnemoni\ - c_type\x18\x0c\x20\x01(\x0e2).hw.trezor.messages.management.BackupTypeR\ - \x0cmnemonicType\x12\x16\n\x06tokens\x18\r\x20\x03(\tR\x06tokens\"\x0f\n\ - \rDebugLinkStop\"P\n\x0cDebugLinkLog\x12\x14\n\x05level\x18\x01\x20\x01(\ - \rR\x05level\x12\x16\n\x06bucket\x18\x02\x20\x01(\tR\x06bucket\x12\x12\n\ - \x04text\x18\x03\x20\x01(\tR\x04text\"G\n\x13DebugLinkMemoryRead\x12\x18\ - \n\x07address\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06length\x18\x02\ - \x20\x01(\rR\x06length\")\n\x0fDebugLinkMemory\x12\x16\n\x06memory\x18\ - \x01\x20\x01(\x0cR\x06memory\"^\n\x14DebugLinkMemoryWrite\x12\x18\n\x07a\ - ddress\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06memory\x18\x02\x20\ - \x01(\x0cR\x06memory\x12\x14\n\x05flash\x18\x03\x20\x01(\x08R\x05flash\"\ - -\n\x13DebugLinkFlashErase\x12\x16\n\x06sector\x18\x01\x20\x01(\rR\x06se\ - ctor\".\n\x14DebugLinkEraseSdCard\x12\x16\n\x06format\x18\x01\x20\x01(\ - \x08R\x06format\"0\n\x14DebugLinkWatchLayout\x12\x14\n\x05watch\x18\x01\ - \x20\x01(\x08R\x05watch:\x02\x18\x01\"\x1f\n\x19DebugLinkResetDebugEvent\ - s:\x02\x18\x01\"\x1a\n\x18DebugLinkOptigaSetSecMaxB=\n#com.satoshilabs.t\ - rezor.lib.protobufB\x12TrezorMessageDebug\x80\xa6\x1d\x01\ + IATER\nwaitLayout\x123\n\x12return_empty_state\x18\x04\x20\x01(\x08:\x05\ + falseR\x10returnEmptyState\"C\n\rDebugWaitType\x12\r\n\tIMMEDIATE\x10\0\ + \x12\x0f\n\x0bNEXT_LAYOUT\x10\x01\x12\x12\n\x0eCURRENT_LAYOUT\x10\x02\"\ + \x97\x04\n\x0eDebugLinkState\x12\x16\n\x06layout\x18\x01\x20\x01(\x0cR\ + \x06layout\x12\x10\n\x03pin\x18\x02\x20\x01(\tR\x03pin\x12\x16\n\x06matr\ + ix\x18\x03\x20\x01(\tR\x06matrix\x12'\n\x0fmnemonic_secret\x18\x04\x20\ + \x01(\x0cR\x0emnemonicSecret\x129\n\x04node\x18\x05\x20\x01(\x0b2%.hw.tr\ + ezor.messages.common.HDNodeTypeR\x04node\x123\n\x15passphrase_protection\ + \x18\x06\x20\x01(\x08R\x14passphraseProtection\x12\x1d\n\nreset_word\x18\ + \x07\x20\x01(\tR\tresetWord\x12#\n\rreset_entropy\x18\x08\x20\x01(\x0cR\ + \x0cresetEntropy\x12,\n\x12recovery_fake_word\x18\t\x20\x01(\tR\x10recov\ + eryFakeWord\x12*\n\x11recovery_word_pos\x18\n\x20\x01(\rR\x0frecoveryWor\ + dPos\x12$\n\x0ereset_word_pos\x18\x0b\x20\x01(\rR\x0cresetWordPos\x12N\n\ + \rmnemonic_type\x18\x0c\x20\x01(\x0e2).hw.trezor.messages.management.Bac\ + kupTypeR\x0cmnemonicType\x12\x16\n\x06tokens\x18\r\x20\x03(\tR\x06tokens\ + \"\x0f\n\rDebugLinkStop\"P\n\x0cDebugLinkLog\x12\x14\n\x05level\x18\x01\ + \x20\x01(\rR\x05level\x12\x16\n\x06bucket\x18\x02\x20\x01(\tR\x06bucket\ + \x12\x12\n\x04text\x18\x03\x20\x01(\tR\x04text\"G\n\x13DebugLinkMemoryRe\ + ad\x12\x18\n\x07address\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06leng\ + th\x18\x02\x20\x01(\rR\x06length\")\n\x0fDebugLinkMemory\x12\x16\n\x06me\ + mory\x18\x01\x20\x01(\x0cR\x06memory\"^\n\x14DebugLinkMemoryWrite\x12\ + \x18\n\x07address\x18\x01\x20\x01(\rR\x07address\x12\x16\n\x06memory\x18\ + \x02\x20\x01(\x0cR\x06memory\x12\x14\n\x05flash\x18\x03\x20\x01(\x08R\ + \x05flash\"-\n\x13DebugLinkFlashErase\x12\x16\n\x06sector\x18\x01\x20\ + \x01(\rR\x06sector\".\n\x14DebugLinkEraseSdCard\x12\x16\n\x06format\x18\ + \x01\x20\x01(\x08R\x06format\"0\n\x14DebugLinkWatchLayout\x12\x14\n\x05w\ + atch\x18\x01\x20\x01(\x08R\x05watch:\x02\x18\x01\"\x1f\n\x19DebugLinkRes\ + etDebugEvents:\x02\x18\x01\"\x1a\n\x18DebugLinkOptigaSetSecMaxB=\n#com.s\ + atoshilabs.trezor.lib.protobufB\x12TrezorMessageDebug\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 8d32078d45..fff7d47f9d 100644 --- a/tests/click_tests/common.py +++ b/tests/click_tests/common.py @@ -49,36 +49,27 @@ def get_char_category(char: str) -> PassphraseCategory: def go_next(debug: "DebugLink") -> LayoutContent: if debug.layout_type is LayoutType.Bolt: - return debug.click(buttons.OK) + debug.click(buttons.OK) elif debug.layout_type is LayoutType.Caesar: - return debug.press_right() + debug.press_right() elif debug.layout_type is LayoutType.Delizia: - return debug.swipe_up() - else: - raise RuntimeError("Unknown model") - - -def tap_to_confirm(debug: "DebugLink") -> LayoutContent: - if debug.layout_type is LayoutType.Bolt: - return debug.read_layout() - elif debug.layout_type is LayoutType.Caesar: - return debug.read_layout() - elif debug.layout_type is LayoutType.Delizia: - return debug.click(buttons.TAP_TO_CONFIRM) + debug.swipe_up() else: raise RuntimeError("Unknown model") + return debug.read_layout() def go_back(debug: "DebugLink", r_middle: bool = False) -> LayoutContent: if debug.layout_type in (LayoutType.Bolt, LayoutType.Delizia): - return debug.click(buttons.CANCEL) + debug.click(buttons.CANCEL) elif debug.layout_type is LayoutType.Caesar: if r_middle: - return debug.press_middle() + debug.press_middle() else: - return debug.press_left() + debug.press_left() else: raise RuntimeError("Unknown model") + return debug.read_layout() def navigate_to_action_and_press( @@ -108,10 +99,10 @@ def navigate_to_action_and_press( if steps < 0: for _ in range(-steps): - layout = debug.press_left() + debug.press_left() else: for _ in range(steps): - layout = debug.press_right() + debug.press_right() # Press or hold debug.press_middle(hold_ms=hold_ms) @@ -125,13 +116,14 @@ def _carousel_steps(current_index: int, wanted_index: int, length: int) -> int: def unlock_gesture(debug: "DebugLink") -> LayoutContent: if debug.layout_type is LayoutType.Bolt: - return debug.click(buttons.OK) + debug.click(buttons.OK) elif debug.layout_type is LayoutType.Caesar: - return debug.press_right() + debug.press_right() elif debug.layout_type is LayoutType.Delizia: - return debug.click(buttons.TAP_TO_CONFIRM) + debug.click(buttons.TAP_TO_CONFIRM) else: raise RuntimeError("Unknown model") + return debug.read_layout() def _get_action_index(wanted_action: str, all_actions: AllActionsType) -> int: diff --git a/tests/click_tests/recovery.py b/tests/click_tests/recovery.py index 1a9169e6eb..51836683b3 100644 --- a/tests/click_tests/recovery.py +++ b/tests/click_tests/recovery.py @@ -23,7 +23,8 @@ def enter_word( if debug.layout_type is LayoutType.Delizia and not is_slip39 and len(word) > 4: # T3T1 (delizia) BIP39 keyboard allows to "confirm" only if the word is fully written, you need to click the word to auto-complete debug.click(buttons.CONFIRM_WORD) - return debug.click(buttons.CONFIRM_WORD) + debug.click(buttons.CONFIRM_WORD) + return debug.read_layout() elif debug.layout_type is LayoutType.Caesar: letter_index = 0 layout = debug.read_layout() @@ -32,16 +33,20 @@ def enter_word( while layout.find_values_by_key("letter_choices"): letter = word[letter_index] while not layout.get_middle_choice() == letter: - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() - layout = debug.press_middle() + debug.press_middle() + layout = debug.read_layout() letter_index += 1 # Word choices while not layout.get_middle_choice() == word: - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() - return debug.press_middle() + debug.press_middle() + return debug.read_layout() else: raise ValueError("Unknown model") @@ -78,7 +83,8 @@ def select_number_of_words( coords = coords_map.get(num_of_words) if coords is None: raise ValueError("Invalid num_of_words") - return debug.click(coords) + debug.click(coords) + return debug.read_layout() def select_caesar() -> "LayoutContent": # navigate to the number and confirm it @@ -86,7 +92,8 @@ def select_number_of_words( index = word_options.index(num_of_words) for _ in range(index): debug.press_right() - return debug.press_middle() + debug.press_middle() + return debug.read_layout() def select_delizia() -> "LayoutContent": # click the button from ValuePad @@ -103,13 +110,15 @@ def select_number_of_words( coords = coords_map.get(num_of_words) if coords is None: raise ValueError("Invalid num_of_words") - return debug.click(coords) + debug.click(coords) + return debug.read_layout() if debug.layout_type is LayoutType.Bolt: assert debug.read_layout().text_content() == TR.recovery__num_of_words layout = select_bolt() elif debug.layout_type is LayoutType.Caesar: - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() assert layout.title() == TR.word_count__title layout = select_caesar() elif debug.layout_type is LayoutType.Delizia: @@ -150,12 +159,15 @@ def enter_share( assert TR.translate(before_title) in debug.read_layout().title() layout = debug.read_layout() for _ in range(layout.page_count()): - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() elif debug.layout_type is LayoutType.Delizia: - layout = debug.swipe_up() + debug.swipe_up() + layout = debug.read_layout() else: assert TR.translate(before_title) in debug.read_layout().title() - layout = debug.click(buttons.OK) + debug.click(buttons.OK) + layout = debug.read_layout() assert "MnemonicKeyboard" in layout.all_components() @@ -236,13 +248,17 @@ def enter_seed_previous_correct( layout = debug.read_layout() while layout.get_middle_choice() not in DELETE_BTNS: - layout = debug.press_right() - layout = debug.press_middle() + debug.press_right() + layout = debug.read_layout() + debug.press_middle() + layout = debug.read_layout() for _ in range(len(bad_word)): while layout.get_middle_choice() not in DELETE_BTNS: - layout = debug.press_left() - layout = debug.press_middle() + debug.press_left() + layout = debug.read_layout() + debug.press_middle() + layout = debug.read_layout() elif debug.layout_type is LayoutType.Delizia: debug.click(buttons.RECOVERY_DELETE) # Top-left for _ in range(len(bad_word)): @@ -278,7 +294,8 @@ def prepare_enter_seed( elif debug.layout_type is LayoutType.Caesar: debug.press_right() debug.press_right() - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() assert "MnemonicKeyboard" in layout.all_components() diff --git a/tests/click_tests/reset.py b/tests/click_tests/reset.py index b8f7c1fc85..d0a34a8a04 100644 --- a/tests/click_tests/reset.py +++ b/tests/click_tests/reset.py @@ -79,7 +79,8 @@ def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> Non in TR.reset__title_number_of_shares + TR.words__title_threshold ): # Special info screens - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() assert "NumberInput" in layout.all_components() if button == buttons.reset_minus(debug.model.internal_name): for _ in range(diff): @@ -102,7 +103,8 @@ def read_words(debug: "DebugLink", do_htc: bool = True) -> list[str]: layout = debug.read_layout() for _ in range(layout.page_count() - 1): words.extend(layout.seed_words()) - layout = debug.swipe_up() + debug.swipe_up() + layout = debug.read_layout() assert layout is not None if debug.layout_type in (LayoutType.Bolt, LayoutType.Delizia): words.extend(layout.seed_words()) @@ -147,7 +149,8 @@ def confirm_words(debug: "DebugLink", words: list[str]) -> None: ] wanted_word = words[word_pos - 1].lower() button_pos = btn_texts.index(wanted_word) - layout = debug.click(buttons.RESET_WORD_CHECK[button_pos]) + debug.click(buttons.RESET_WORD_CHECK[button_pos]) + layout = debug.read_layout() elif debug.layout_type is LayoutType.Delizia: assert TR.regexp("reset__select_word_x_of_y_template").match(layout.subtitle()) for _ in range(3): @@ -162,10 +165,12 @@ def confirm_words(debug: "DebugLink", words: list[str]) -> None: ] wanted_word = words[word_pos - 1].lower() button_pos = btn_texts.index(wanted_word) - layout = debug.click(buttons.VERTICAL_MENU[button_pos]) + debug.click(buttons.VERTICAL_MENU[button_pos]) + layout = debug.read_layout() elif debug.layout_type is LayoutType.Caesar: assert TR.reset__select_correct_word in layout.text_content() - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() for _ in range(3): # "SELECT 2ND WORD" # ^ @@ -176,9 +181,11 @@ def confirm_words(debug: "DebugLink", words: list[str]) -> None: wanted_word = words[word_pos - 1].lower() while not layout.get_middle_choice() == wanted_word: - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() - layout = debug.press_middle() + debug.press_middle() + layout = debug.read_layout() def validate_mnemonics(mnemonics: list[str], expected_ems: bytes) -> None: diff --git a/tests/click_tests/test_autolock.py b/tests/click_tests/test_autolock.py index cb550b9920..78ef5c9e13 100644 --- a/tests/click_tests/test_autolock.py +++ b/tests/click_tests/test_autolock.py @@ -30,7 +30,7 @@ from .. import translations as TR from ..device_tests.bitcoin.payment_req import make_coinjoin_request from ..tx_cache import TxCache from . import recovery -from .common import go_next, tap_to_confirm, unlock_gesture +from .common import go_next, unlock_gesture if TYPE_CHECKING: from trezorlib.debuglink import DebugLink, LayoutContent @@ -71,7 +71,8 @@ def set_autolock_delay(device_handler: "BackgroundDeviceHandler", delay_ms: int) layout = go_next(debug) if debug.layout_type is LayoutType.Delizia: - layout = tap_to_confirm(debug) + debug.click(buttons.TAP_TO_CONFIRM) + layout = debug.read_layout() assert layout.main_component() == "Homescreen" device_handler.result() @@ -106,17 +107,20 @@ def test_autolock_interrupts_signing(device_handler: "BackgroundDeviceHandler"): if debug.layout_type is LayoutType.Bolt: debug.click(buttons.OK) - layout = debug.click(buttons.OK) + debug.click(buttons.OK) + layout = debug.read_layout() assert TR.send__total_amount in layout.text_content() assert "0.0039 BTC" in layout.text_content() elif debug.layout_type is LayoutType.Delizia: debug.swipe_up() - layout = debug.swipe_up() + debug.swipe_up() + layout = debug.read_layout() assert TR.send__total_amount in layout.text_content() assert "0.0039 BTC" in layout.text_content() elif debug.layout_type is LayoutType.Caesar: debug.press_right() - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() assert TR.send__total_amount in layout.text_content() assert "0.0039 BTC" in layout.text_content() @@ -158,18 +162,21 @@ def test_autolock_does_not_interrupt_signing(device_handler: "BackgroundDeviceHa if debug.layout_type is LayoutType.Bolt: debug.click(buttons.OK) - layout = debug.click(buttons.OK) + debug.click(buttons.OK) + layout = debug.read_layout() assert TR.send__total_amount in layout.text_content() assert "0.0039 BTC" in layout.text_content() elif debug.layout_type is LayoutType.Delizia: debug.swipe_up() - layout = debug.swipe_up() + debug.swipe_up() + layout = debug.read_layout() assert TR.send__total_amount in layout.text_content() assert "0.0039 BTC" in layout.text_content() debug.swipe_up() elif debug.layout_type is LayoutType.Caesar: debug.press_right() - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() assert TR.send__total_amount in layout.text_content() assert "0.0039 BTC" in layout.text_content() @@ -181,17 +188,12 @@ def test_autolock_does_not_interrupt_signing(device_handler: "BackgroundDeviceHa with device_handler.client: device_handler.client.set_filter(messages.TxAck, sleepy_filter) # confirm transaction - # In all cases we set wait=False to avoid waiting for the screen and triggering - # the layout deadlock detection. In reality there is no deadlock but the - # `sleepy_filter` delays the response by 10 secs while the layout deadlock - # timeout is 3. In this test we don't need the result of the input event so - # waiting for it is not necessary. if debug.layout_type is LayoutType.Bolt: - debug.click(buttons.OK, wait=False) + debug.click(buttons.OK) elif debug.layout_type is LayoutType.Delizia: - debug.click(buttons.TAP_TO_CONFIRM, wait=False) + debug.click(buttons.TAP_TO_CONFIRM) elif debug.layout_type is LayoutType.Caesar: - debug.press_middle(wait=False) + debug.press_middle() signatures, tx = device_handler.result() assert len(signatures) == 1 @@ -277,7 +279,8 @@ def unlock_dry_run(debug: "DebugLink") -> "LayoutContent": layout = go_next(debug) assert "PinKeyboard" in layout.all_components() - layout = debug.input(PIN4) + debug.input(PIN4) + layout = debug.read_layout() assert layout is not None return layout @@ -307,7 +310,8 @@ def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandle layout = unlock_gesture(debug) assert "PinKeyboard" in layout.all_components() - layout = debug.input(PIN4) + debug.input(PIN4) + layout = debug.read_layout() assert layout is not None # we are back at homescreen @@ -330,7 +334,8 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"): layout = go_next(debug) assert layout.main_component() == "MnemonicKeyboard" elif debug.layout_type is LayoutType.Caesar: - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() assert "MnemonicKeyboard" in layout.all_components() # make sure keyboard locks @@ -353,31 +358,36 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"): recovery.select_number_of_words(debug, 20) if debug.layout_type is LayoutType.Bolt: - layout = debug.click(buttons.OK) + debug.click(buttons.OK) + layout = debug.read_layout() assert layout.main_component() == "MnemonicKeyboard" # type the word OCEAN slowly for coords in buttons.type_word("ocea", is_slip39=True): time.sleep(9) debug.click(coords) - layout = debug.click(buttons.CONFIRM_WORD) + debug.click(buttons.CONFIRM_WORD) + layout = debug.read_layout() # should not have locked, even though we took 9 seconds to type each letter assert layout.main_component() == "MnemonicKeyboard" elif debug.layout_type is LayoutType.Delizia: - layout = debug.swipe_up() + debug.swipe_up() + layout = debug.read_layout() assert layout.main_component() == "MnemonicKeyboard" # type the word OCEAN slowly for coords in buttons.type_word("ocea", is_slip39=True): time.sleep(9) debug.click(coords) - layout = debug.click(buttons.CONFIRM_WORD) + debug.click(buttons.CONFIRM_WORD) + layout = debug.read_layout() # should not have locked, even though we took 9 seconds to type each letter assert layout.main_component() == "MnemonicKeyboard" elif debug.layout_type is LayoutType.Caesar: - layout = debug.press_right() + debug.press_right() + layout = debug.read_layout() assert "MnemonicKeyboard" in layout.all_components() # pressing middle button three times diff --git a/tests/click_tests/test_lock.py b/tests/click_tests/test_lock.py index ed1db64b07..9a0340910f 100644 --- a/tests/click_tests/test_lock.py +++ b/tests/click_tests/test_lock.py @@ -79,9 +79,10 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"): # unlock by touching if debug.layout_type is LayoutType.Caesar: - layout = debug.press_right() + debug.press_right() else: - layout = debug.click(buttons.INFO) + debug.click(buttons.INFO) + layout = debug.read_layout() assert "PinKeyboard" in layout.all_components() debug.input("1234") diff --git a/tests/click_tests/test_pin.py b/tests/click_tests/test_pin.py index c2a8d56bd3..d810910dc8 100644 --- a/tests/click_tests/test_pin.py +++ b/tests/click_tests/test_pin.py @@ -212,6 +212,11 @@ def _cancel_pin(debug: "DebugLink") -> None: # TODO: implement cancel PIN for TR? _delete_pin(debug, 1, check=False) + # Note: `prepare()` context manager will send a tap after PIN cancellation, + # so we make sure the lockscreen is already up to receive it -- otherwise + # the input event may get lost in the loop restart. + assert debug.read_layout().main_component() != "PinKeyboard" + def _confirm_pin(debug: "DebugLink") -> None: """Navigate to "ENTER" and press it""" diff --git a/tests/click_tests/test_tutorial_caesar.py b/tests/click_tests/test_tutorial_caesar.py index 81d2645ace..2394b0a102 100644 --- a/tests/click_tests/test_tutorial_caesar.py +++ b/tests/click_tests/test_tutorial_caesar.py @@ -56,7 +56,8 @@ def go_through_tutorial_tr(debug: "DebugLink") -> None: debug.press_right(hold_ms=1000) debug.press_right() debug.press_right() - layout = debug.press_middle() + debug.press_middle() + layout = debug.read_layout() assert layout.title() == TR.tutorial__title_tutorial_complete diff --git a/tests/click_tests/test_tutorial_delizia.py b/tests/click_tests/test_tutorial_delizia.py index a642c06f3a..08902162f6 100644 --- a/tests/click_tests/test_tutorial_delizia.py +++ b/tests/click_tests/test_tutorial_delizia.py @@ -38,18 +38,17 @@ def test_tutorial_ignore_menu(device_handler: "BackgroundDeviceHandler"): debug = device_handler.debuglink() device_handler.run(device.show_device_tutorial) - layout = debug.read_layout() - assert layout.title() == TR.tutorial__welcome_safe5 - layout = debug.click(buttons.TAP_TO_CONFIRM) - assert layout.title() == TR.tutorial__title_lets_begin - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_easy_navigation - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_handy_menu - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_hold - layout = debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) - assert layout.title() == TR.tutorial__title_well_done + assert debug.read_layout().title() == TR.tutorial__welcome_safe5 + debug.click(buttons.TAP_TO_CONFIRM) + assert debug.read_layout().title() == TR.tutorial__title_lets_begin + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_easy_navigation + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_handy_menu + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_hold + debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) + assert debug.read_layout().title() == TR.tutorial__title_well_done debug.swipe_up() device_handler.result() @@ -59,24 +58,23 @@ def test_tutorial_menu_open_close(device_handler: "BackgroundDeviceHandler"): debug = device_handler.debuglink() device_handler.run(device.show_device_tutorial) - layout = debug.read_layout() - assert layout.title() == TR.tutorial__welcome_safe5 - layout = debug.click(buttons.TAP_TO_CONFIRM) - assert layout.title() == TR.tutorial__title_lets_begin - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_easy_navigation - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_handy_menu + assert debug.read_layout().title() == TR.tutorial__welcome_safe5 + debug.click(buttons.TAP_TO_CONFIRM) + assert debug.read_layout().title() == TR.tutorial__title_lets_begin + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_easy_navigation + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_handy_menu - layout = debug.click(buttons.CORNER_BUTTON) - assert TR.tutorial__did_you_know in layout.text_content() - layout = debug.click(buttons.CORNER_BUTTON) - assert layout.title() == TR.tutorial__title_handy_menu + debug.click(buttons.CORNER_BUTTON) + assert TR.tutorial__did_you_know in debug.read_layout().text_content() + debug.click(buttons.CORNER_BUTTON) + assert debug.read_layout().title() == TR.tutorial__title_handy_menu - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_hold - layout = debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) - assert layout.title() == TR.tutorial__title_well_done + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_hold + debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) + assert debug.read_layout().title() == TR.tutorial__title_well_done debug.swipe_up() device_handler.result() @@ -86,21 +84,20 @@ def test_tutorial_menu_exit(device_handler: "BackgroundDeviceHandler"): debug = device_handler.debuglink() device_handler.run(device.show_device_tutorial) - layout = debug.read_layout() - assert layout.title() == TR.tutorial__welcome_safe5 - layout = debug.click(buttons.TAP_TO_CONFIRM) - assert layout.title() == TR.tutorial__title_lets_begin - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_easy_navigation - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_handy_menu + assert debug.read_layout().title() == TR.tutorial__welcome_safe5 + debug.click(buttons.TAP_TO_CONFIRM) + assert debug.read_layout().title() == TR.tutorial__title_lets_begin + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_easy_navigation + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_handy_menu - layout = debug.click(buttons.CORNER_BUTTON) - assert TR.tutorial__did_you_know in layout.text_content() - layout = debug.click(buttons.VERTICAL_MENU[2]) - assert TR.instructions__hold_to_exit_tutorial in layout.footer() - layout = debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) - assert layout.title() == TR.tutorial__title_well_done + debug.click(buttons.CORNER_BUTTON) + assert TR.tutorial__did_you_know in debug.read_layout().text_content() + debug.click(buttons.VERTICAL_MENU[2]) + assert TR.instructions__hold_to_exit_tutorial in debug.read_layout().footer() + debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) + assert debug.read_layout().title() == TR.tutorial__title_well_done debug.swipe_up() device_handler.result() @@ -110,28 +107,27 @@ def test_tutorial_menu_repeat(device_handler: "BackgroundDeviceHandler"): debug = device_handler.debuglink() device_handler.run(device.show_device_tutorial) - layout = debug.read_layout() - assert layout.title() == TR.tutorial__welcome_safe5 - layout = debug.click(buttons.TAP_TO_CONFIRM) - assert layout.title() == TR.tutorial__title_lets_begin - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_easy_navigation - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_handy_menu + assert debug.read_layout().title() == TR.tutorial__welcome_safe5 + debug.click(buttons.TAP_TO_CONFIRM) + assert debug.read_layout().title() == TR.tutorial__title_lets_begin + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_easy_navigation + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_handy_menu - layout = debug.click(buttons.CORNER_BUTTON) - assert TR.tutorial__did_you_know in layout.text_content() - layout = debug.click(buttons.VERTICAL_MENU[1]) + debug.click(buttons.CORNER_BUTTON) + assert TR.tutorial__did_you_know in debug.read_layout().text_content() + debug.click(buttons.VERTICAL_MENU[1]) - assert layout.title() == TR.tutorial__title_lets_begin - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_easy_navigation - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_handy_menu - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_hold - layout = debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) - assert layout.title() == TR.tutorial__title_well_done + assert debug.read_layout().title() == TR.tutorial__title_lets_begin + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_easy_navigation + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_handy_menu + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_hold + debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) + assert debug.read_layout().title() == TR.tutorial__title_well_done debug.swipe_up() device_handler.result() @@ -141,29 +137,30 @@ def test_tutorial_menu_funfact(device_handler: "BackgroundDeviceHandler"): debug = device_handler.debuglink() device_handler.run(device.show_device_tutorial) - layout = debug.read_layout() - assert layout.title() == TR.tutorial__welcome_safe5 - layout = debug.click(buttons.TAP_TO_CONFIRM) - assert layout.title() == TR.tutorial__title_lets_begin - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_easy_navigation - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_handy_menu + assert debug.read_layout().title() == TR.tutorial__welcome_safe5 + debug.click(buttons.TAP_TO_CONFIRM) + assert debug.read_layout().title() == TR.tutorial__title_lets_begin + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_easy_navigation + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_handy_menu - layout = debug.click(buttons.CORNER_BUTTON) - assert TR.tutorial__did_you_know in layout.text_content() - layout = debug.click(buttons.VERTICAL_MENU[0]) - assert layout.text_content() in TR.tutorial__first_wallet.replace("\n", " ") + debug.click(buttons.CORNER_BUTTON) + assert TR.tutorial__did_you_know in debug.read_layout().text_content() + debug.click(buttons.VERTICAL_MENU[0]) + assert debug.read_layout().text_content() in TR.tutorial__first_wallet.replace( + "\n", " " + ) - layout = debug.click(buttons.CORNER_BUTTON) - assert TR.tutorial__did_you_know in layout.text_content() - layout = debug.click(buttons.CORNER_BUTTON) - assert layout.title() == TR.tutorial__title_handy_menu + debug.click(buttons.CORNER_BUTTON) + assert TR.tutorial__did_you_know in debug.read_layout().text_content() + debug.click(buttons.CORNER_BUTTON) + assert debug.read_layout().title() == TR.tutorial__title_handy_menu - layout = debug.swipe_up() - assert layout.title() == TR.tutorial__title_hold - layout = debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) - assert layout.title() == TR.tutorial__title_well_done + debug.swipe_up() + assert debug.read_layout().title() == TR.tutorial__title_hold + debug.click(buttons.TAP_TO_CONFIRM, hold_ms=1000) + assert debug.read_layout().title() == TR.tutorial__title_well_done debug.swipe_up() device_handler.result() diff --git a/tests/common.py b/tests/common.py index 554d0fb65e..4d2151ce08 100644 --- a/tests/common.py +++ b/tests/common.py @@ -323,7 +323,8 @@ def click_info_button_bolt(debug: "DebugLink") -> Generator[Any, Any, ButtonRequ def click_info_button_delizia(debug: "DebugLink"): """Click Shamir backup info button and return back.""" - layout = debug.click(buttons.CORNER_BUTTON) + debug.click(buttons.CORNER_BUTTON) + layout = debug.read_layout() assert "VerticalMenu" in layout.all_components() debug.click(buttons.VERTICAL_MENU[0]) debug.click(buttons.CORNER_BUTTON) diff --git a/tests/conftest.py b/tests/conftest.py index 5d1a43ec4d..05e6622e1b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -375,7 +375,6 @@ def pytest_sessionfinish(session: pytest.Session, exitstatus: pytest.ExitCode) - exitstatus, test_ui, # type: ignore bool(session.config.getoption("ui_check_missing")), - bool(session.config.getoption("record_text_layout")), bool(session.config.getoption("do_master_diff")), ) diff --git a/tests/input_flows.py b/tests/input_flows.py index 363a4c6d60..5896523ce7 100644 --- a/tests/input_flows.py +++ b/tests/input_flows.py @@ -347,10 +347,12 @@ class InputFlowShowAddressQRCode(InputFlowBase): # really cancel self.debug.click(buttons.CORNER_BUTTON) # menu - layout = self.debug.click(buttons.CORNER_BUTTON) + self.debug.click(buttons.CORNER_BUTTON) + layout = self.debug.read_layout() while "PromptScreen" not in layout.all_components(): - layout = self.debug.swipe_up() + self.debug.swipe_up() + layout = self.debug.read_layout() self.debug.synchronize_at("PromptScreen") # tap to confirm self.debug.click(buttons.TAP_TO_CONFIRM) @@ -437,14 +439,16 @@ class InputFlowShowMultisigXPUBs(InputFlowBase): self.debug.click(buttons.CORNER_BUTTON) assert "Qr" in self.all_components() - layout = self.debug.swipe_left() + self.debug.swipe_left() + layout = self.debug.read_layout() # address details assert "Multisig 2 of 3" in layout.screen_content() assert TR.address_details__derivation_path in layout.screen_content() # Three xpub pages with the same testing logic for xpub_num in range(3): - layout = self.debug.swipe_left() + self.debug.swipe_left() + layout = self.debug.read_layout() self._assert_xpub_title(layout.title(), xpub_num) content = layout.text_content().replace(" ", "") assert self.xpubs[xpub_num] in content @@ -470,18 +474,21 @@ class InputFlowShowMultisigXPUBs(InputFlowBase): self.debug.press_right() assert "Qr" in self.all_components() - layout = self.debug.press_right() + self.debug.press_right() + layout = self.debug.read_layout() # address details # TODO: locate it more precisely assert "Multisig 2 of 3" in layout.json_str # Three xpub pages with the same testing logic for xpub_num in range(3): - layout = self.debug.press_right() + self.debug.press_right() + layout = self.debug.read_layout() self._assert_xpub_title(layout.title(), xpub_num) xpub_part_1 = layout.text_content().replace(" ", "") # Press "SHOW MORE" - layout = self.debug.press_middle() + self.debug.press_middle() + layout = self.debug.read_layout() xpub_part_2 = layout.text_content().replace(" ", "") # Go back self.debug.press_left() @@ -525,24 +532,25 @@ class InputFlowShowMultisigXPUBs(InputFlowBase): # three xpub pages with the same testing logic for _xpub_num in range(3): - layout = self.debug.swipe_left() - layout = self.debug.swipe_left() + self.debug.swipe_left() + self.debug.swipe_left() self.debug.click(buttons.CORNER_BUTTON) - layout = self.debug.synchronize_at("VerticalMenu") + self.debug.synchronize_at("VerticalMenu") # menu self.debug.click(buttons.VERTICAL_MENU[2]) # cancel self.debug.swipe_up() # really cancel self.debug.click(buttons.CORNER_BUTTON) - layout = self.debug.synchronize_at("VerticalMenu") + self.debug.synchronize_at("VerticalMenu") # menu self.debug.click(buttons.CORNER_BUTTON) layout = self.debug.synchronize_at("Paragraphs") # address while "PromptScreen" not in layout.all_components(): - layout = self.debug.swipe_up() + self.debug.swipe_up() + layout = self.debug.read_layout() self.debug.synchronize_at("PromptScreen") # tap to confirm self.debug.press_yes() @@ -652,7 +660,8 @@ class InputFlowShowXpubQRCode(InputFlowBase): layout = self.debug.synchronize_at("Paragraphs") # address while "PromptScreen" not in layout.all_components(): - layout = self.debug.swipe_up() + self.debug.swipe_up() + layout = self.debug.read_layout() self.debug.synchronize_at("PromptScreen") # tap to confirm self.debug.press_yes() @@ -834,10 +843,12 @@ def sign_tx_go_to_info_tr( client.debug.press_middle() yield - layout = client.debug.press_right() + client.debug.press_right() + layout = client.debug.read_layout() screen_texts.append(layout.visible_screen()) - layout = client.debug.press_right() + client.debug.press_right() + layout = client.debug.read_layout() screen_texts.append(layout.visible_screen()) client.debug.press_left() diff --git a/tests/input_flows_helpers.py b/tests/input_flows_helpers.py index 90e03c16ae..1d33d39aec 100644 --- a/tests/input_flows_helpers.py +++ b/tests/input_flows_helpers.py @@ -179,7 +179,8 @@ class RecoveryFlow: self.debug.synchronize_at("VerticalMenu") self.debug.click(buttons.VERTICAL_MENU[0]) assert (yield).name == "abort_recovery" - layout = self.debug.swipe_up() + self.debug.swipe_up() + layout = self.debug.read_layout() assert layout.title() == TR.recovery__title_cancel_recovery self.debug.click(buttons.TAP_TO_CONFIRM) else: diff --git a/tests/ui_tests/__init__.py b/tests/ui_tests/__init__.py index c7ab04a45e..2213a03dab 100644 --- a/tests/ui_tests/__init__.py +++ b/tests/ui_tests/__init__.py @@ -44,8 +44,6 @@ def screen_recording( yield return - record_text_layout = request.config.getoption("record_text_layout") - testcase = TestCase.build(client, request) testcase.dir.mkdir(exist_ok=True, parents=True) @@ -55,9 +53,6 @@ def screen_recording( try: client.debug.start_recording(str(testcase.actual_dir)) - if record_text_layout: - client.debug.set_screen_text_file(testcase.screen_text_file) - client.debug.watch_layout(True) yield finally: client.ensure_open() @@ -65,9 +60,6 @@ def screen_recording( # Wait for response to Initialize, which gives the emulator time to catch up # and redraw the homescreen. Otherwise there's a race condition between that # and stopping recording. - if record_text_layout: - client.debug.set_screen_text_file(None) - client.debug.watch_layout(False) client.init_device() client.debug.stop_recording() @@ -163,13 +155,12 @@ def sessionfinish( exitstatus: pytest.ExitCode, test_ui: str, check_missing: bool, - record_text_layout: bool, do_master_diff: bool, ) -> pytest.ExitCode: if not _should_write_ui_report(exitstatus): return exitstatus - testreport.generate_reports(record_text_layout, do_master_diff) + testreport.generate_reports(do_master_diff) recents = list(TestResult.recent_results()) diff --git a/tests/ui_tests/reporting/testreport.py b/tests/ui_tests/reporting/testreport.py index 20ce5d9640..8ffe636682 100644 --- a/tests/ui_tests/reporting/testreport.py +++ b/tests/ui_tests/reporting/testreport.py @@ -5,7 +5,6 @@ from collections import defaultdict from datetime import datetime from pathlib import Path -import dominate import dominate.tags as t from dominate.tags import ( a, @@ -31,7 +30,6 @@ from .common import REPORTS_PATH, document, generate_master_diff_report, get_dif TESTREPORT_PATH = REPORTS_PATH / "test" IMAGES_PATH = TESTREPORT_PATH / "images" -SCREEN_TEXT_FILE = TESTREPORT_PATH / "screen_text.txt" # These two html files are referencing each other ALL_SCREENS = "all_screens.html" @@ -201,35 +199,6 @@ def all_unique_screens() -> Path: return html.write(TESTREPORT_PATH, doc, ALL_UNIQUE_SCREENS) -def screen_text_report() -> None: - """Generate a report with text representation of all screens.""" - recent_results = list(TestResult.recent_results()) - - # Creating both a text file (suitable for offline usage) - # and an HTML file (suitable for online usage). - - with open(SCREEN_TEXT_FILE, "w") as f2: - for result in recent_results: - if not result.test.screen_text_file.exists(): - continue - f2.write(f"\n{result.test.id}\n") - with open(result.test.screen_text_file, "r") as f: - for line in f.readlines(): - f2.write(f"\t{line}") - - doc = dominate.document(title="Screen text report") - with doc: - for result in recent_results: - if not result.test.screen_text_file.exists(): - continue - with a(href=f"{ALL_SCREENS}#{result.test.id}"): - h2(result.test.id) - with open(result.test.screen_text_file, "r") as f: - for line in f.readlines(): - p(line) - html.write(TESTREPORT_PATH, doc, "screen_text.html") - - def differing_screens() -> None: """Creating an HTML page showing all the unique screens that got changed.""" unique_diffs: set[tuple[str | None, str | None]] = set() @@ -316,17 +285,13 @@ def master_index() -> Path: return html.write(TESTREPORT_PATH, doc, "master_index.html") -def generate_reports( - do_screen_text: bool = False, do_master_diff: bool = False -) -> None: +def generate_reports(do_master_diff: bool = False) -> None: """Generate HTML reports for the test.""" html.set_image_dir(IMAGES_PATH) index() all_screens() all_unique_screens() differing_screens() - if do_screen_text: - screen_text_report() if do_master_diff: master_diff() master_index() diff --git a/tests/upgrade_tests/recovery_old.py b/tests/upgrade_tests/recovery_old.py index 6b8fed4c22..3836a3be78 100644 --- a/tests/upgrade_tests/recovery_old.py +++ b/tests/upgrade_tests/recovery_old.py @@ -11,17 +11,21 @@ def _enter_word( ) -> "LayoutContent": typed_word = word[:4] for coords in buttons.type_word(typed_word, is_slip39=is_slip39): - debug.click(coords, wait=False) + debug.click(coords) + debug.read_layout(wait=False) - return debug.click(buttons.CONFIRM_WORD, wait=True) + debug.click(buttons.CONFIRM_WORD) + return debug.read_layout(wait=True) def confirm_recovery(debug: "DebugLink") -> None: debug.click(buttons.OK) + debug.read_layout(wait=True) def select_number_of_words(debug: "DebugLink", num_of_words: int = 20) -> None: debug.click(buttons.OK) + debug.read_layout(wait=True) # click the number word_option_offset = 6 @@ -31,12 +35,12 @@ def select_number_of_words(debug: "DebugLink", num_of_words: int = 20) -> None: ) # raises if num of words is invalid coords = buttons.grid34(index % 3, index // 3) debug.click(coords) + debug.read_layout(wait=True) -def enter_share( - debug: "DebugLink", share: str, is_first: bool = True -) -> "LayoutContent": - layout = debug.click(buttons.OK) +def enter_share(debug: "DebugLink", share: str) -> "LayoutContent": + debug.click(buttons.OK) for word in share.split(" "): - layout = _enter_word(debug, word, is_slip39=True) - return layout + _enter_word(debug, word, is_slip39=True) + + return debug.read_layout(wait=True)