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/python/src/trezorlib/debuglink.py b/python/src/trezorlib/debuglink.py index 71fab102ba..52951c9fe5 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,20 @@ 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) press_yes = _make_input_func(button=messages.DebugButton.YES) """Confirm current layout. See `_decision` for more details.""" @@ -698,58 +663,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 +803,9 @@ 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() + self.debuglink.state(DebugWaitType.CURRENT_LAYOUT) + if self.debuglink.model is models.T3T1: layout = self.debuglink.read_layout() if "PromptScreen" in layout.all_components(): diff --git a/tests/click_tests/common.py b/tests/click_tests/common.py index 8d32078d45..2060c9726b 100644 --- a/tests/click_tests/common.py +++ b/tests/click_tests/common.py @@ -49,13 +49,14 @@ 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() + debug.swipe_up() else: raise RuntimeError("Unknown model") + return debug.read_layout() def tap_to_confirm(debug: "DebugLink") -> LayoutContent: @@ -64,21 +65,23 @@ def tap_to_confirm(debug: "DebugLink") -> LayoutContent: 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.click(buttons.TAP_TO_CONFIRM) + return debug.read_layout() else: raise RuntimeError("Unknown model") 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,13 +111,14 @@ 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) + debug.read_layout() # TODO: make sure the press above takes action def _carousel_steps(current_index: int, wanted_index: int, length: int) -> int: @@ -125,13 +129,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..207162f1e9 100644 --- a/tests/click_tests/reset.py +++ b/tests/click_tests/reset.py @@ -44,6 +44,7 @@ def confirm_read(debug: "DebugLink", middle_r: bool = False) -> None: debug.press_middle() else: debug.press_right() + debug.read_layout() # TODO: what is being confirmed here? def cancel_backup( @@ -61,6 +62,7 @@ def cancel_backup( elif debug.layout_type is LayoutType.Caesar: debug.press_left() debug.press_left() + debug.read_layout() # TODO: make sure cancellation took place def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None: @@ -79,7 +81,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 +105,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 +151,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 +167,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 +183,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..e6b1d6916b 100644 --- a/tests/click_tests/test_autolock.py +++ b/tests/click_tests/test_autolock.py @@ -106,17 +106,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 +161,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 +187,19 @@ 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 + if debug.layout_type is LayoutType.Bolt: + debug.click(buttons.OK) + elif debug.layout_type is LayoutType.Delizia: + debug.click(buttons.TAP_TO_CONFIRM) + elif debug.layout_type is LayoutType.Caesar: + debug.press_middle() + # 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) - elif debug.layout_type is LayoutType.Delizia: - debug.click(buttons.TAP_TO_CONFIRM, wait=False) - elif debug.layout_type is LayoutType.Caesar: - debug.press_middle(wait=False) + debug.read_layout(wait=False) signatures, tx = device_handler.result() assert len(signatures) == 1 @@ -277,7 +285,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 +316,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 +340,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 +364,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..85ac51a92e 100644 --- a/tests/click_tests/test_lock.py +++ b/tests/click_tests/test_lock.py @@ -55,6 +55,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"): debug.press_right(hold_ms=duration) else: debug.click((13, 37), hold_ms=duration) + debug.read_layout() # TODO: is it needed? assert device_handler.features().unlocked is False @@ -79,11 +80,13 @@ 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") + debug.read_layout() # TODO: is it needed? assert device_handler.features().unlocked is True diff --git a/tests/click_tests/test_passphrase_bolt.py b/tests/click_tests/test_passphrase_bolt.py index 8f490c0309..5d2fb8a034 100644 --- a/tests/click_tests/test_passphrase_bolt.py +++ b/tests/click_tests/test_passphrase_bolt.py @@ -126,6 +126,7 @@ def press_char(debug: "DebugLink", char: str) -> None: TT_COORDS_PREV = coords # type: ignore for _ in range(amount): debug.click(coords) + debug.read_layout() # TODO: seems to be needed def input_passphrase(debug: "DebugLink", passphrase: str, check: bool = True) -> None: diff --git a/tests/click_tests/test_passphrase_delizia.py b/tests/click_tests/test_passphrase_delizia.py index 85bdc37173..e7f75a114f 100644 --- a/tests/click_tests/test_passphrase_delizia.py +++ b/tests/click_tests/test_passphrase_delizia.py @@ -154,6 +154,7 @@ def press_char(debug: "DebugLink", char: str) -> None: COORDS_PREV = coords # type: ignore for _ in range(amount): debug.click(coords) + debug.read_layout() # TODO: seems to be needed def input_passphrase(debug: "DebugLink", passphrase: str, check: bool = True) -> None: diff --git a/tests/click_tests/test_pin.py b/tests/click_tests/test_pin.py index c2a8d56bd3..13064be4ee 100644 --- a/tests/click_tests/test_pin.py +++ b/tests/click_tests/test_pin.py @@ -186,6 +186,7 @@ def _delete_pin(debug: "DebugLink", digits_to_delete: int, check: bool = True) - for _ in range(digits_to_delete): if debug.layout_type in (LayoutType.Bolt, LayoutType.Delizia): debug.click(buttons.pin_passphrase_grid(9)) + debug.read_layout() # TODO: make sure the press above takes action elif debug.layout_type is LayoutType.Caesar: navigate_to_action_and_press(debug, DELETE, TR_PIN_ACTIONS) 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)