1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2025-07-19 13:08:14 +00:00

WIP - move debug-trace processing logic to Debuglink

This commit is contained in:
grdddj 2023-01-19 17:04:12 +01:00
parent d928a390d8
commit 002976e16d
32 changed files with 445 additions and 526 deletions

View File

@ -28,7 +28,6 @@ message DebugLinkDecision {
DOWN = 1;
LEFT = 2;
RIGHT = 3;
ALL_THE_WAY_UP = 4;
}
/**

View File

@ -14,7 +14,7 @@ pub trait Tracer {
fn title(&mut self, title: &str);
fn button(&mut self, button: &str);
fn content_flag(&mut self);
fn kw_pair(&mut self, key: &str, value: &str);
fn kw_pair(&mut self, key: &str, value: &dyn Trace);
fn close(&mut self);
}
@ -143,10 +143,10 @@ mod tests {
}
/// Key-value pair for easy parsing
fn kw_pair(&mut self, key: &str, value: &str) {
fn kw_pair(&mut self, key: &str, value: &dyn Trace) {
self.string(key);
self.string("::");
self.string(value);
value.trace(self);
self.string(","); // mostly for human readability
}

View File

@ -271,10 +271,10 @@ impl LayoutObj {
self.string(crate::trace::CONTENT_TAG);
}
fn kw_pair(&mut self, key: &str, value: &str) {
fn kw_pair(&mut self, key: &str, value: &dyn Trace) {
self.string(key);
self.string("::");
self.string(value);
value.trace(self);
self.string(","); // mostly for human readability
}

View File

@ -15,7 +15,7 @@ use super::{
/// To be returned directly from Flow.
pub enum FlowMsg {
Confirmed,
ConfirmedIndex(u8),
ConfirmedIndex(usize),
Cancelled,
}
@ -31,13 +31,13 @@ pub struct Flow<F, const M: usize> {
title_area: Rect,
pad: Pad,
buttons: Child<ButtonController>,
page_counter: u8,
page_counter: usize,
return_confirmed_index: bool,
}
impl<F, const M: usize> Flow<F, M>
where
F: Fn(u8) -> Page<M>,
F: Fn(usize) -> Page<M>,
{
pub fn new(pages: FlowPages<F, M>) -> Self {
let current_page = pages.get(0);
@ -123,16 +123,16 @@ where
/// Negative index means counting from the end.
fn go_to_page_absolute(&mut self, index: i16, ctx: &mut EventCtx) {
if index < 0 {
self.page_counter = (self.pages.count() as i16 + index) as u8;
self.page_counter = (self.pages.count() as i16 + index) as usize;
} else {
self.page_counter = index as u8;
self.page_counter = index as usize;
}
self.update(ctx, true);
}
/// Jumping to another page relative to the current one.
fn go_to_page_relative(&mut self, jump: i16, ctx: &mut EventCtx) {
self.page_counter = (self.page_counter as i16 + jump) as u8;
self.page_counter = (self.page_counter as i16 + jump) as usize;
self.update(ctx, true);
}
@ -176,7 +176,7 @@ where
impl<F, const M: usize> Component for Flow<F, M>
where
F: Fn(u8) -> Page<M>,
F: Fn(usize) -> Page<M>,
{
type Msg = FlowMsg;
@ -190,14 +190,15 @@ where
};
self.content_area = content_area;
// Placing a scrollbar in case the title is there
if self.title.is_some() {
// Finding out the total amount of pages in this flow
let complete_page_count = self.pages.scrollbar_page_count(content_area);
self.scrollbar
.inner_mut()
.set_page_count(complete_page_count);
// Finding out the total amount of pages in this flow
let complete_page_count = self.pages.scrollbar_page_count(content_area);
self.scrollbar
.inner_mut()
.set_page_count(complete_page_count);
// Placing a title and scrollbar in case the title is there
// (scrollbar will be active - counting pages - even when not placed and painted)
if self.title.is_some() {
let (title_area, scrollbar_area) =
title_area.split_right(self.scrollbar.inner().overall_width() + SCROLLBAR_SPACE);
@ -288,7 +289,7 @@ use heapless::String;
#[cfg(feature = "ui_debug")]
impl<F, const M: usize> crate::trace::Trace for Flow<F, M>
where
F: Fn(u8) -> Page<M>,
F: Fn(usize) -> Page<M>,
{
/// Accounting for the possibility that button is connected with the
/// currently paginated flow_page (only Prev or Next in that case).
@ -309,15 +310,15 @@ where
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Flow");
t.kw_pair("flow_page", inttostr!(self.page_counter));
t.kw_pair("flow_page_count", inttostr!(self.pages.count()));
t.kw_pair("flow_page", &self.page_counter);
t.kw_pair("flow_page_count", &self.pages.count());
self.report_btn_actions(t);
if self.title.is_some() {
t.field("title", &self.title);
}
t.field("content_area", &self.content_area);
t.field("scrollbar", &self.scrollbar);
t.field("buttons", &self.buttons);
t.field("flow_page", &self.current_page);
t.close()

View File

@ -32,14 +32,14 @@ pub struct FlowPages<F, const M: usize> {
/// Function/closure that will return appropriate page on demand.
get_page: F,
/// Number of pages in the flow.
page_count: u8,
page_count: usize,
}
impl<F, const M: usize> FlowPages<F, M>
where
F: Fn(u8) -> Page<M>,
F: Fn(usize) -> Page<M>,
{
pub fn new(get_page: F, page_count: u8) -> Self {
pub fn new(get_page: F, page_count: usize) -> Self {
Self {
get_page,
page_count,
@ -47,12 +47,12 @@ where
}
/// Returns a page on demand on a specified index.
pub fn get(&self, page_index: u8) -> Page<M> {
pub fn get(&self, page_index: usize) -> Page<M> {
(self.get_page)(page_index)
}
/// Total amount of pages.
pub fn count(&self) -> u8 {
pub fn count(&self) -> usize {
self.page_count
}
@ -63,8 +63,8 @@ where
}
/// Active scrollbar position connected with the beginning of a specific
/// page index
pub fn scrollbar_page_index(&self, bounds: Rect, page_index: u8) -> usize {
/// page index.
pub fn scrollbar_page_index(&self, bounds: Rect, page_index: usize) -> usize {
let mut page_count = 0;
for i in 0..page_index {
let mut current_page = self.get(i);
@ -364,8 +364,8 @@ pub mod trace {
impl<const M: usize> crate::trace::Trace for Page<M> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Page");
t.kw_pair("active_page", inttostr!(self.current_page as u8));
t.kw_pair("page_count", inttostr!(self.page_count as u8));
t.kw_pair("active_page", &self.current_page);
t.kw_pair("page_count", &self.page_count);
t.field("content", &trace::TraceText(self));
t.close();
}

View File

@ -8,7 +8,7 @@ use crate::ui::{
use super::super::{theme, ButtonController, ButtonControllerMsg, ButtonLayout, ButtonPos};
pub enum ChoicePageMsg {
Choice(u8),
Choice(usize),
LeftMost,
RightMost,
}
@ -46,8 +46,8 @@ pub trait ChoiceFactory {
type Item: Choice + Trace;
#[cfg(not(feature = "ui_debug"))]
type Item: Choice;
fn count(&self) -> u8;
fn get(&self, index: u8) -> Self::Item;
fn count(&self) -> usize;
fn get(&self, index: usize) -> Self::Item;
}
/// General component displaying a set of items on the screen
@ -70,7 +70,7 @@ where
choices: F,
pad: Pad,
buttons: Child<ButtonController>,
page_counter: u8,
page_counter: usize,
/// How many pixels from top should we render the items.
y_baseline: i16,
/// How many pixels are between the items.
@ -110,7 +110,7 @@ where
/// Set the page counter at the very beginning.
/// Need to update the initial button layout.
pub fn with_initial_page_counter(mut self, page_counter: u8) -> Self {
pub fn with_initial_page_counter(mut self, page_counter: usize) -> Self {
self.page_counter = page_counter;
let initial_btn_layout = self.choices.get(page_counter).btn_layout();
self.buttons = Child::new(ButtonController::new(initial_btn_layout));
@ -156,7 +156,7 @@ where
&mut self,
ctx: &mut EventCtx,
new_choices: F,
new_page_counter: Option<u8>,
new_page_counter: Option<usize>,
is_carousel: bool,
) {
self.choices = new_choices;
@ -168,7 +168,7 @@ where
}
/// Navigating to the chosen page index.
pub fn set_page_counter(&mut self, ctx: &mut EventCtx, page_counter: u8) {
pub fn set_page_counter(&mut self, ctx: &mut EventCtx, page_counter: usize) {
self.page_counter = page_counter;
self.update(ctx);
}
@ -188,7 +188,7 @@ where
// Getting the remaining left and right areas.
let (left_area, _center_area, right_area) =
available_area.split_center(self.choices.get(self.page_counter as u8).width_center());
available_area.split_center(self.choices.get(self.page_counter).width_center());
// Possibly drawing on the left side.
if self.has_previous_choice() || self.is_carousel {
@ -214,8 +214,8 @@ where
}
/// Index of the last page.
fn last_page_index(&self) -> u8 {
self.choices.count() as u8 - 1
fn last_page_index(&self) -> usize {
self.choices.count() - 1
}
/// Whether there is a previous choice (on the left).
@ -243,6 +243,7 @@ where
/// Display all the choices fitting on the left side.
/// Going as far as possible.
fn show_left_choices(&self, area: Rect) {
// page index can get negative here, so having it as i16 instead of usize
let mut page_index = self.page_counter as i16 - 1;
let mut x_offset = 0;
loop {
@ -261,7 +262,7 @@ where
if let Some(width) = self
.choices
.get(page_index as u8)
.get(page_index as usize)
.paint_left(current_area, self.show_incomplete)
{
// Updating loop variables.
@ -293,7 +294,7 @@ where
if let Some(width) = self
.choices
.get(page_index as u8)
.get(page_index as usize)
.paint_right(current_area, self.show_incomplete)
{
// Updating loop variables.
@ -326,7 +327,7 @@ where
}
/// Get current page counter.
pub fn page_index(&self) -> u8 {
pub fn page_index(&self) -> usize {
self.page_counter
}
@ -430,9 +431,9 @@ where
{
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ChoicePage");
t.kw_pair("active_page", inttostr!(self.page_counter));
t.kw_pair("page_count", inttostr!(self.choices.count() as u8));
t.kw_pair("is_carousel", booltostr!(self.is_carousel));
t.kw_pair("active_page", &self.page_counter);
t.kw_pair("page_count", &self.choices.count());
t.kw_pair("is_carousel", &booltostr!(self.is_carousel));
if self.has_previous_choice() {
t.field("prev_choice", &self.choices.get(self.page_counter - 1));

View File

@ -24,11 +24,11 @@ impl ChoiceFactoryNumberInput {
impl ChoiceFactory for ChoiceFactoryNumberInput {
type Item = ChoiceItem;
fn count(&self) -> u8 {
(self.max - self.min + 1) as u8
fn count(&self) -> usize {
(self.max - self.min + 1) as usize
}
fn get(&self, choice_index: u8) -> ChoiceItem {
fn get(&self, choice_index: usize) -> ChoiceItem {
let num = self.min + choice_index as u32;
let text: String<10> = String::from(num);
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
@ -59,7 +59,7 @@ impl NumberInput {
let initial_page = init_value - min;
Self {
min,
choice_page: ChoicePage::new(choices).with_initial_page_counter(initial_page as u8),
choice_page: ChoicePage::new(choices).with_initial_page_counter(initial_page as usize),
}
}
}

View File

@ -62,7 +62,7 @@ const MENU: [&str; MENU_LENGTH] = [
];
/// Get a character at a specified index for a specified category.
fn get_char(current_category: &ChoiceCategory, index: u8) -> char {
fn get_char(current_category: &ChoiceCategory, index: usize) -> char {
let index = index as usize;
match current_category {
ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS[index],
@ -74,7 +74,7 @@ fn get_char(current_category: &ChoiceCategory, index: u8) -> char {
}
/// Return category from menu based on page index.
fn get_category_from_menu(page_index: u8) -> ChoiceCategory {
fn get_category_from_menu(page_index: usize) -> ChoiceCategory {
match page_index as usize {
LOWERCASE_INDEX => ChoiceCategory::LowercaseLetter,
UPPERCASE_INDEX => ChoiceCategory::UppercaseLetter,
@ -86,18 +86,18 @@ fn get_category_from_menu(page_index: u8) -> ChoiceCategory {
/// How many choices are available for a specified category.
/// (does not count the extra MENU choice for characters)
fn get_category_length(current_category: &ChoiceCategory) -> u8 {
fn get_category_length(current_category: &ChoiceCategory) -> usize {
match current_category {
ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS.len() as u8,
ChoiceCategory::UppercaseLetter => UPPERCASE_LETTERS.len() as u8,
ChoiceCategory::Digit => DIGITS.len() as u8,
ChoiceCategory::SpecialSymbol => SPECIAL_SYMBOLS.len() as u8,
ChoiceCategory::Menu => MENU.len() as u8,
ChoiceCategory::LowercaseLetter => LOWERCASE_LETTERS.len(),
ChoiceCategory::UppercaseLetter => UPPERCASE_LETTERS.len(),
ChoiceCategory::Digit => DIGITS.len(),
ChoiceCategory::SpecialSymbol => SPECIAL_SYMBOLS.len(),
ChoiceCategory::Menu => MENU.len(),
}
}
/// Whether this index is the MENU index - the last one in the list.
fn is_menu_choice(current_category: &ChoiceCategory, page_index: u8) -> bool {
fn is_menu_choice(current_category: &ChoiceCategory, page_index: usize) -> bool {
if let ChoiceCategory::Menu = current_category {
unreachable!()
}
@ -120,7 +120,7 @@ impl ChoiceFactoryPassphrase {
}
/// MENU choices with accept and cancel hold-to-confirm side buttons.
fn get_menu_item(&self, choice_index: u8) -> ChoiceItem {
fn get_menu_item(&self, choice_index: usize) -> ChoiceItem {
let choice_index = choice_index as usize;
// More options for CANCEL/DELETE button
@ -165,7 +165,7 @@ impl ChoiceFactoryPassphrase {
/// Character choices with a BACK to MENU choice at the end (visible from
/// start) to return back
fn get_character_item(&self, choice_index: u8) -> ChoiceItem {
fn get_character_item(&self, choice_index: usize) -> ChoiceItem {
if is_menu_choice(&self.current_category, choice_index) {
ChoiceItem::new("BACK", ButtonLayout::arrow_armed_arrow("RETURN".into()))
.with_icon(Icon::new(theme::ICON_ARROW_BACK_UP))
@ -178,7 +178,7 @@ impl ChoiceFactoryPassphrase {
impl ChoiceFactory for ChoiceFactoryPassphrase {
type Item = ChoiceItem;
fn count(&self) -> u8 {
fn count(&self) -> usize {
let length = get_category_length(&self.current_category);
// All non-MENU categories have an extra item for returning back to MENU
match self.current_category {
@ -186,7 +186,7 @@ impl ChoiceFactory for ChoiceFactoryPassphrase {
_ => length + 1,
}
}
fn get(&self, choice_index: u8) -> ChoiceItem {
fn get(&self, choice_index: usize) -> ChoiceItem {
match self.current_category {
ChoiceCategory::Menu => self.get_menu_item(choice_index),
_ => self.get_character_item(choice_index),
@ -201,7 +201,7 @@ pub struct PassphraseEntry {
show_plain_passphrase: bool,
textbox: TextBox<MAX_PASSPHRASE_LENGTH>,
current_category: ChoiceCategory,
menu_position: u8, // position in the menu so we can return back
menu_position: usize, // position in the menu so we can return back
}
impl PassphraseEntry {
@ -209,7 +209,7 @@ impl PassphraseEntry {
Self {
choice_page: ChoicePage::new(ChoiceFactoryPassphrase::new(ChoiceCategory::Menu, true))
.with_carousel(true)
.with_initial_page_counter(LOWERCASE_INDEX as u8),
.with_initial_page_counter(LOWERCASE_INDEX),
passphrase_dots: Child::new(ChangingTextLine::center_mono(String::new())),
show_plain_passphrase: false,
textbox: TextBox::empty(),
@ -298,7 +298,7 @@ impl Component for PassphraseEntry {
self.update_passphrase_dots(ctx);
if self.is_empty() {
// Allowing for DELETE/CANCEL change
self.menu_position = CANCEL_DELETE_INDEX as u8;
self.menu_position = CANCEL_DELETE_INDEX;
self.show_menu_page(ctx);
}
ctx.request_paint();
@ -410,7 +410,7 @@ impl crate::trace::Trace for PassphraseEntry {
t.open("PassphraseEntry");
// NOTE: `show_plain_passphrase` was not able to be transferred,
// as it is true only for a very small amount of time
t.kw_pair("textbox", self.textbox.content());
t.kw_pair("textbox", &self.textbox.content());
self.report_btn_actions(t);
t.field("choice_page", &self.choice_page);
t.close();

View File

@ -42,31 +42,31 @@ impl ChoiceFactoryPIN {
impl ChoiceFactory for ChoiceFactoryPIN {
type Item = ChoiceItem;
fn get(&self, choice_index: u8) -> ChoiceItem {
let choice_str = CHOICES[choice_index as usize];
fn get(&self, choice_index: usize) -> ChoiceItem {
let choice_str = CHOICES[choice_index];
let mut choice_item = ChoiceItem::new(choice_str, ButtonLayout::default_three_icons());
// Action buttons have different middle button text
if [DELETE_INDEX, SHOW_INDEX, ENTER_INDEX].contains(&(choice_index as usize)) {
if [DELETE_INDEX, SHOW_INDEX, ENTER_INDEX].contains(&(choice_index)) {
let confirm_btn = ButtonDetails::armed_text("CONFIRM".into());
choice_item.set_middle_btn(Some(confirm_btn));
}
// Adding icons for appropriate items
if choice_index == DELETE_INDEX as u8 {
if choice_index == DELETE_INDEX {
choice_item = choice_item.with_icon(Icon::new(theme::ICON_DELETE));
} else if choice_index == SHOW_INDEX as u8 {
} else if choice_index == SHOW_INDEX {
choice_item = choice_item.with_icon(Icon::new(theme::ICON_EYE));
} else if choice_index == ENTER_INDEX as u8 {
} else if choice_index == ENTER_INDEX {
choice_item = choice_item.with_icon(Icon::new(theme::ICON_TICK));
}
choice_item
}
fn count(&self) -> u8 {
CHOICE_LENGTH as u8
fn count(&self) -> usize {
CHOICE_LENGTH
}
}
@ -87,7 +87,7 @@ impl PinEntry {
Self {
// Starting at the digit 0
choice_page: ChoicePage::new(choices)
.with_initial_page_counter(NUMBER_START_INDEX as u8)
.with_initial_page_counter(NUMBER_START_INDEX)
.with_carousel(true),
pin_line: Child::new(ChangingTextLine::center_bold(String::from(
prompt.clone().as_ref(),
@ -99,8 +99,8 @@ impl PinEntry {
}
}
fn append_new_digit(&mut self, ctx: &mut EventCtx, page_counter: u8) {
let digit = CHOICES[page_counter as usize];
fn append_new_digit(&mut self, ctx: &mut EventCtx, page_counter: usize) {
let digit = CHOICES[page_counter];
self.textbox.append_slice(ctx, digit);
}
@ -180,7 +180,7 @@ impl Component for PinEntry {
let msg = self.choice_page.event(ctx, event);
if let Some(ChoicePageMsg::Choice(page_counter)) = msg {
// Performing action under specific index or appending new digit
match page_counter as usize {
match page_counter {
DELETE_INDEX => {
self.delete_last_digit(ctx);
self.update(ctx);
@ -201,7 +201,7 @@ impl Component for PinEntry {
page_counter as u32,
);
self.choice_page
.set_page_counter(ctx, new_page_counter as u8);
.set_page_counter(ctx, new_page_counter as usize);
self.update(ctx);
}
}
@ -229,7 +229,7 @@ impl crate::trace::Trace for PinEntry {
ButtonPos::Left => ButtonAction::PrevPage.string(),
ButtonPos::Right => ButtonAction::NextPage.string(),
ButtonPos::Middle => {
let current_index = self.choice_page.page_index() as usize;
let current_index = self.choice_page.page_index();
match current_index {
DELETE_INDEX => ButtonAction::Action("Delete last digit").string(),
SHOW_INDEX => ButtonAction::Action("Show PIN").string(),
@ -245,7 +245,7 @@ impl crate::trace::Trace for PinEntry {
// NOTE: `show_real_pin` was not able to be transferred,
// as it is true only for a very small amount of time
t.title(self.prompt.as_ref());
t.kw_pair("textbox", self.textbox.content());
t.kw_pair("textbox", &self.textbox.content());
self.report_btn_actions(t);
t.field("choice_page", &self.choice_page);
t.close();

View File

@ -11,7 +11,7 @@ use heapless::{String, Vec};
pub enum SimpleChoiceMsg {
Result(String<50>),
Index(u8),
Index(usize),
}
struct ChoiceFactorySimple<const N: usize> {
@ -28,12 +28,12 @@ impl<const N: usize> ChoiceFactorySimple<N> {
impl<const N: usize> ChoiceFactory for ChoiceFactorySimple<N> {
type Item = ChoiceItem;
fn count(&self) -> u8 {
N as u8
fn count(&self) -> usize {
N
}
fn get(&self, choice_index: u8) -> ChoiceItem {
let text = &self.choices[choice_index as usize];
fn get(&self, choice_index: usize) -> ChoiceItem {
let text = &self.choices[choice_index];
let mut choice_item = ChoiceItem::new(text, ButtonLayout::default_three_icons());
// Disabling prev/next buttons for the first/last choice when not in carousel.
@ -42,7 +42,7 @@ impl<const N: usize> ChoiceFactory for ChoiceFactorySimple<N> {
if choice_index == 0 {
choice_item.set_left_btn(None);
}
if choice_index as usize == N - 1 {
if choice_index == N - 1 {
choice_item.set_right_btn(None);
}
}
@ -102,7 +102,7 @@ impl<const N: usize> Component for SimpleChoice<N> {
if self.return_index {
Some(SimpleChoiceMsg::Index(page_counter))
} else {
let result = String::from(self.choices[page_counter as usize].as_ref());
let result = String::from(self.choices[page_counter].as_ref());
Some(SimpleChoiceMsg::Result(result))
}
}
@ -133,7 +133,7 @@ impl<const N: usize> crate::trace::Trace for SimpleChoice<N> {
false => ButtonAction::empty(),
},
ButtonPos::Middle => {
let current_index = self.choice_page.page_index() as usize;
let current_index = self.choice_page.page_index();
ButtonAction::select_item(self.choices[current_index].as_ref())
}
}

View File

@ -25,10 +25,10 @@ const MAX_LETTERS_LENGTH: usize = 26;
const OFFER_WORDS_THRESHOLD: usize = 10;
/// Where will be the DELETE option - at the first position
const DELETE_INDEX: u8 = 0;
const DELETE_INDEX: usize = 0;
/// Which index will be used at the beginning.
/// (Accounts for DELETE to be at index 0)
const INITIAL_PAGE_COUNTER: u8 = DELETE_INDEX + 1;
const INITIAL_PAGE_COUNTER: usize = DELETE_INDEX + 1;
const PROMPT: &str = "_";
@ -57,15 +57,15 @@ impl ChoiceFactoryWordlist {
impl ChoiceFactory for ChoiceFactoryWordlist {
type Item = ChoiceItem;
fn count(&self) -> u8 {
fn count(&self) -> usize {
// Accounting for the DELETE option
match self {
Self::Letters(letter_choices) => letter_choices.len() as u8 + 1,
Self::Words(word_choices) => word_choices.len() as u8 + 1,
Self::Letters(letter_choices) => letter_choices.len() + 1,
Self::Words(word_choices) => word_choices.len() + 1,
}
}
fn get(&self, choice_index: u8) -> ChoiceItem {
fn get(&self, choice_index: usize) -> ChoiceItem {
// Letters have a carousel, words do not
// Putting DELETE as the first option in both cases
// (is a requirement for WORDS, doing it for LETTERS as well to unite it)
@ -281,7 +281,7 @@ impl crate::trace::Trace for WordlistEntry {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("Bip39Entry");
t.kw_pair("textbox", self.textbox.content());
t.kw_pair("textbox", &self.textbox.content());
self.report_btn_actions(t);

View File

@ -249,8 +249,8 @@ where
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ButtonPage");
t.kw_pair("active_page", inttostr!(self.active_page as u8));
t.kw_pair("page_count", inttostr!(self.page_count as u8));
t.kw_pair("active_page", &self.active_page);
t.kw_pair("page_count", &self.page_count);
self.report_btn_actions(t);
// TODO: it seems the button text is not updated when paginating (but actions
// above are)

View File

@ -247,8 +247,8 @@ impl Component for ScrollBar {
impl crate::trace::Trace for ScrollBar {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.open("ScrollBar");
t.field("page_count", &self.page_count);
t.field("active_page", &self.active_page);
t.kw_pair("scrollbar_page_count", &self.page_count);
t.kw_pair("scrollbar_active_page", &self.active_page);
t.close();
}
}

View File

@ -93,13 +93,13 @@ where
impl<F, const M: usize> ComponentMsgObj for Flow<F, M>
where
F: Fn(u8) -> Page<M>,
F: Fn(usize) -> Page<M>,
{
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
FlowMsg::Confirmed => Ok(CONFIRMED.as_obj()),
FlowMsg::Cancelled => Ok(CANCELLED.as_obj()),
FlowMsg::ConfirmedIndex(index) => Ok(index.into()),
FlowMsg::ConfirmedIndex(index) => index.try_into(),
}
}
}
@ -125,7 +125,7 @@ impl<const N: usize> ComponentMsgObj for SimpleChoice<N> {
fn msg_try_into_obj(&self, msg: Self::Msg) -> Result<Obj, Error> {
match msg {
SimpleChoiceMsg::Result(choice) => choice.as_str().try_into(),
SimpleChoiceMsg::Index(index) => Ok(index.into()),
SimpleChoiceMsg::Index(index) => index.try_into(),
}
}
}
@ -629,7 +629,7 @@ fn tutorial_screen(
extern "C" fn tutorial(n_args: usize, args: *const Obj, kwargs: *mut Map) -> Obj {
let block = |_args: &[Obj], _kwargs: &Map| {
const PAGE_COUNT: u8 = 7;
const PAGE_COUNT: usize = 7;
let get_page = |page_index| {
// Lazy-loaded list of screens to show, with custom content,
@ -806,8 +806,7 @@ extern "C" fn new_confirm_fido(n_args: usize, args: *const Obj, kwargs: *mut Map
.text_bold(account)
};
let pages = FlowPages::new(get_page, page_count as u8);
let pages = FlowPages::new(get_page, page_count);
// Returning the page index in case of confirmation.
let obj = LayoutObj::new(
Flow::new(pages)

View File

@ -73,7 +73,6 @@ if __debug__:
SWIPE_DOWN,
SWIPE_LEFT,
SWIPE_RIGHT,
SWIPE_ALL_THE_WAY_UP,
)
button = msg.button # local_cache_attribute
@ -99,8 +98,6 @@ if __debug__:
await swipe_chan.put(SWIPE_LEFT)
elif swipe == DebugSwipeDirection.RIGHT:
await swipe_chan.put(SWIPE_RIGHT)
elif swipe == DebugSwipeDirection.ALL_THE_WAY_UP:
await swipe_chan.put(SWIPE_ALL_THE_WAY_UP)
if msg.input is not None:
await input_chan.put(Result(msg.input))

View File

@ -6,4 +6,3 @@ UP = 0
DOWN = 1
LEFT = 2
RIGHT = 3
ALL_THE_WAY_UP = 4

View File

@ -449,7 +449,6 @@ if TYPE_CHECKING:
DOWN = 1
LEFT = 2
RIGHT = 3
ALL_THE_WAY_UP = 4
class DebugButton(IntEnum):
NO = 0

View File

@ -22,7 +22,6 @@ if __debug__:
SWIPE_DOWN = const(0x02)
SWIPE_LEFT = const(0x04)
SWIPE_RIGHT = const(0x08)
SWIPE_ALL_THE_WAY_UP = const(0x10)
# channel used to cancel layouts, see `Cancelled` exception

View File

@ -37,14 +37,7 @@ async def interact(
br_type: str,
br_code: ButtonRequestType = ButtonRequestType.Other,
) -> Any:
if hasattr(layout, "in_unknown_flow") and layout.in_unknown_flow(): # type: ignore [Cannot access member "in_unknown_flow" for type "LayoutType"]
# We cannot recognize before-hand how many pages the layout will have -
# but we know for certain we want to paginate through them
# TODO: could do something less hacky than sending 0 as page count
# (create new ButtonRequest field)
await button_request(ctx, br_type, br_code, pages=0)
return await ctx.wait(layout)
elif hasattr(layout, "page_count") and layout.page_count() > 1: # type: ignore [Cannot access member "page_count" for type "LayoutType"]
if hasattr(layout, "page_count") and layout.page_count() > 1: # type: ignore [Cannot access member "page_count" for type "LayoutType"]
# We know for certain how many pages the layout will have
await button_request(ctx, br_type, br_code, pages=layout.page_count()) # type: ignore [Cannot access member "page_count" for type "LayoutType"]
return await ctx.wait(layout)

View File

@ -30,125 +30,23 @@ if __debug__:
Used only in debug mode.
"""
# How will some information be identified in the content
TITLE_TAG = " **TITLE** "
CONTENT_TAG = " **CONTENT** "
BTN_TAG = " **BTN** "
EMPTY_BTN = "---"
NEXT_BTN = "Next"
PREV_BTN = "Prev"
# TODO: used only because of `page_count`
# We could do it some other way
# TT does this:
# def page_count(self) -> int:
# return self.layout.page_count()
def __init__(self, raw_content: list[str]) -> None:
self.raw_content = raw_content
self.str_content = " ".join(raw_content).replace(" ", " ")
print("str_content", self.str_content)
print(60 * "-")
print("active_page:", self.active_page())
print("page_count:", self.page_count())
print("flow_page:", self.flow_page())
print("flow_page_count:", self.flow_page_count())
print("can_go_next:", self.can_go_next())
print("get_next_button:", self.get_next_button())
print(30 * "/")
print(self.visible_screen())
def active_page(self) -> int:
"""Current index of the active page. Should always be there."""
return self.kw_pair_int("active_page") or 0
def page_count(self) -> int:
"""Overall number of pages in this screen. Should always be there."""
return self.kw_pair_int("page_count") or 1
def in_flow(self) -> bool:
"""Whether we are in flow."""
return self.flow_page() is not None
def flow_page(self) -> int | None:
"""When in flow, on which page we are. Missing when not in flow."""
return self.kw_pair_int("flow_page")
def flow_page_count(self) -> int | None:
"""When in flow, how many unique pages it has. Missing when not in flow."""
return self.kw_pair_int("flow_page_count")
def can_go_next(self) -> bool:
"""Checking if there is a next page."""
return self.get_next_button() is not None
def get_next_button(self) -> str | None:
"""Position of the next button, if any."""
return self._get_btn_by_action(self.NEXT_BTN)
def get_prev_button(self) -> str | None:
"""Position of the previous button, if any."""
return self._get_btn_by_action(self.PREV_BTN)
def _get_btn_by_action(self, btn_action: str) -> str | None:
"""Position of button described by some action. None if not found."""
btn_names = ("left", "middle", "right")
for index, action in enumerate(self.button_actions()):
if action == btn_action:
return btn_names[index]
return None
def visible_screen(self) -> str:
"""Getting all the visible screen content - header, content, buttons."""
title_separator = f"\n{20*'-'}\n"
btn_separator = f"\n{20*'*'}\n"
visible = ""
if self.title():
visible += self.title()
visible += title_separator
visible += self.content()
visible += btn_separator
visible += ", ".join(self.buttons())
return visible
def title(self) -> str:
"""Getting text that is displayed as a title."""
# there could be multiple of those - title and subtitle for example
title_strings = self._get_strings_inside_tag(
self.str_content, self.TITLE_TAG
return (
self.kw_pair_int("scrollbar_page_count")
or self.kw_pair_int("page_count")
or 1
)
return "\n".join(title_strings)
def content(self) -> str:
"""Getting text that is displayed in the main part of the screen."""
content_strings = self._get_strings_inside_tag(
self.str_content, self.CONTENT_TAG
)
# there are some unwanted spaces
strings = [
s.replace(" \n ", "\n").replace("\n ", "\n").lstrip()
for s in content_strings
]
return "\n".join(strings)
def buttons(self) -> tuple[str, str, str]:
"""Getting content and actions for all three buttons."""
contents = self.buttons_content()
actions = self.button_actions()
return tuple(f"{contents[i]} [{actions[i]}]" for i in range(3))
def buttons_content(self) -> tuple[str, str, str]:
"""Getting visual details for all three buttons. They should always be there."""
if self.BTN_TAG not in self.str_content:
return ("None", "None", "None")
btns = self._get_strings_inside_tag(self.str_content, self.BTN_TAG)
assert len(btns) == 3
return btns[0], btns[1], btns[2]
def button_actions(self) -> tuple[str, str, str]:
"""Getting actions for all three buttons. They should always be there."""
if "_action" not in self.str_content:
return ("None", "None", "None")
action_ids = ("left_action", "middle_action", "right_action")
assert len(action_ids) == 3
return tuple(self.kw_pair_compulsory(action) for action in action_ids)
def kw_pair_int(self, key: str) -> int | None:
"""Getting the value of a key-value pair as an integer. None if missing."""
@ -157,12 +55,6 @@ if __debug__:
return None
return int(val)
def kw_pair_compulsory(self, key: str) -> str:
"""Getting value of a key that cannot be missing."""
val = self.kw_pair(key)
assert val is not None
return val
def kw_pair(self, key: str) -> str | None:
"""Getting the value of a key-value pair. None if missing."""
# Pairs are sent in this format in the list:
@ -174,16 +66,6 @@ if __debug__:
return None
@staticmethod
def _get_strings_inside_tag(string: str, tag: str) -> list[str]:
"""Getting all strings that are inside two same tags."""
parts = string.split(tag)
if len(parts) == 1:
return []
else:
# returning all odd indexes in the list
return parts[1::2]
class RustLayout(ui.Layout):
# pylint: disable=super-init-not-called
@ -227,12 +109,6 @@ class RustLayout(ui.Layout):
if __debug__:
from trezor.enums import DebugPhysicalButton
BTN_MAP = {
"left": DebugPhysicalButton.LEFT_BTN,
"middle": DebugPhysicalButton.MIDDLE_BTN,
"right": DebugPhysicalButton.RIGHT_BTN,
}
def create_tasks(self) -> tuple[loop.AwaitableTask, ...]: # type: ignore [obscured-by-same-name]
from apps.debug import confirm_signal, input_signal
@ -248,7 +124,8 @@ class RustLayout(ui.Layout):
def read_content(self) -> list[str]:
"""Gets the visible content of the screen."""
self._place_layout()
return self._content_obj().visible_screen().split("\n")
raw = self._read_content_raw()
return " ".join(raw).split("\n")
def _place_layout(self) -> None:
"""It is necessary to place the layout to get data about its screen content."""
@ -315,18 +192,16 @@ class RustLayout(ui.Layout):
SWIPE_UP,
SWIPE_DOWN,
)
content_obj = self._content_obj()
from trezor.enums import DebugPhysicalButton
if direction == SWIPE_UP:
btn_to_press = content_obj.get_next_button()
btn_to_press = DebugPhysicalButton.RIGHT_BTN
elif direction == SWIPE_DOWN:
btn_to_press = content_obj.get_prev_button()
btn_to_press = DebugPhysicalButton.LEFT_BTN
else:
raise Exception(f"Unsupported direction: {direction}")
assert btn_to_press is not None
self._press_button(self.BTN_MAP[btn_to_press])
self._press_button(btn_to_press)
async def handle_swipe(self) -> None:
"""Enables pagination through the current page/flow page.
@ -334,17 +209,10 @@ class RustLayout(ui.Layout):
Waits for `swipe_signal` and carries it out.
"""
from apps.debug import swipe_signal
from trezor.ui import SWIPE_ALL_THE_WAY_UP, SWIPE_UP
while True:
direction = await swipe_signal()
if direction == SWIPE_ALL_THE_WAY_UP:
# Going as far as possible
while self._content_obj().can_go_next():
self._swipe(SWIPE_UP)
else:
self._swipe(direction)
self._swipe(direction)
async def handle_button_click(self) -> None:
"""Enables clicking arbitrary of the three buttons.
@ -359,17 +227,9 @@ class RustLayout(ui.Layout):
def page_count(self) -> int:
"""How many paginated pages current screen has."""
# TODO: leave it debug-only or use always?
self._place_layout()
return self._content_obj().page_count()
def in_unknown_flow(self) -> bool:
"""Whether we are in a longer flow where we cannot (easily)
beforehand say how much pages it will have.
"""
self._place_layout()
return self._content_obj().in_flow()
else:
def create_tasks(self) -> tuple[loop.Task, ...]:

View File

@ -115,6 +115,7 @@ class RustLayout(ui.Layout):
notify_layout_change(self)
def notify_backup(self):
# TODO: delete this in favor of debuglink::LayoutContent::seed_words
from apps.debug import reset_current_words
content = "\n".join(self.read_content())

View File

@ -61,84 +61,204 @@ EXPECTED_RESPONSES_CONTEXT_LINES = 3
LOG = logging.getLogger(__name__)
class LayoutContent:
def _get_strings_inside_tag(string: str, tag: str) -> List[str]:
"""Getting all strings that are inside two same tags.
Example:
_get_strings_inside_tag("abc **TAG** def **TAG** ghi")
-> ["def"]
"""
parts = string.split(tag)
if len(parts) == 1:
return []
else:
# returning all odd indexes in the list
return parts[1::2]
class LayoutBase:
"""Common base for layouts, containing common methods."""
def __init__(self, lines: Sequence[str]) -> None:
self.lines = list(lines)
self.str_content = "\n".join(self.lines)
self.tokens = self.str_content.split()
def kw_pair_int(self, key: str) -> Optional[int]:
"""Getting the value of a key-value pair as an integer. None if missing."""
val = self.kw_pair(key)
if val is None:
return None
return int(val)
def kw_pair_compulsory(self, key: str) -> str:
"""Getting value of a key that cannot be missing."""
val = self.kw_pair(key)
assert val is not None
return val
def kw_pair(self, key: str) -> Optional[str]:
"""Getting the value of a key-value pair. None if missing."""
# Pairs are sent in this format in the list:
# [..., "key", "::", "value", ...]
for key_index, item in enumerate(self.tokens):
if item == key:
if self.tokens[key_index + 1] == "::":
return self.tokens[key_index + 2]
return None
class LayoutButtons(LayoutBase):
"""Extension for the LayoutContent class to handle buttons."""
BTN_TAG = " **BTN** "
EMPTY_BTN = "---"
NEXT_BTN = "Next"
PREV_BTN = "Prev"
BTN_NAMES = ("left", "middle", "right")
def __init__(self, lines: Sequence[str]) -> None:
super().__init__(lines)
def is_applicable(self) -> bool:
"""Check if the layout has buttons."""
return self.BTN_TAG in self.str_content
def visible(self) -> str:
"""Getting content and actions for all three buttons."""
return ", ".join(self.all_buttons())
def all_buttons(self) -> Tuple[str, str, str]:
"""Getting content and actions for all three buttons."""
contents = self.content()
actions = self.actions()
return tuple(f"{contents[i]} [{actions[i]}]" for i in range(3))
def content(self) -> Tuple[str, str, str]:
"""Getting visual details for all three buttons. They should always be there."""
if self.BTN_TAG not in self.str_content:
return ("None", "None", "None")
btns = _get_strings_inside_tag(self.str_content, self.BTN_TAG)
assert len(btns) == 3
return btns[0].strip(), btns[1].strip(), btns[2].strip()
def actions(self) -> Tuple[str, str, str]:
"""Getting actions for all three buttons. They should always be there."""
if "_action" not in self.str_content:
return ("None", "None", "None")
action_ids = ("left_action", "middle_action", "right_action")
assert len(action_ids) == 3
return tuple(self.kw_pair_compulsory(action) for action in action_ids)
def can_go_next(self) -> bool:
"""Checking if there is a next page."""
return self.get_next_button() is not None
def can_go_back(self) -> bool:
"""Checking if there is a previous page."""
return self.get_prev_button() is not None
def get_next_button(self) -> Optional[str]:
"""Position of the next button, if any."""
return self._get_btn_by_action(self.NEXT_BTN)
def get_prev_button(self) -> Optional[str]:
"""Position of the previous button, if any."""
return self._get_btn_by_action(self.PREV_BTN)
def _get_btn_by_action(self, btn_action: str) -> Optional[str]:
"""Position of button described by some action. None if not found."""
for index, action in enumerate(self.actions()):
if action == btn_action:
return self.BTN_NAMES[index]
return None
class LayoutContent(LayoutBase):
"""Stores content of a layout as returned from Trezor.
Contains helper functions to extract specific parts of the layout.
"""
# How will some information be identified in the content
TITLE_TAG = " **TITLE** "
CONTENT_TAG = " **CONTENT** "
def __init__(self, lines: Sequence[str]) -> None:
self.lines = list(lines)
self.text = " ".join(self.lines)
super().__init__(lines)
self.buttons = LayoutButtons(lines)
def get_title(self) -> str:
"""Get title of the layout.
Title is located between "title" and "content" identifiers.
Example: "< Frame title : RECOVERY SHARE #1 content : < SwipePage"
-> "RECOVERY SHARE #1"
def visible_screen(self) -> str:
"""String representation of a current screen content.
Example:
SIGN TRANSACTION
--------------------
You are about to
sign 3 actions.
********************
Icon:cancel [Cancel], --- [None], CONFIRM [Confirm]
"""
match = re.search(r"title : (.*?) content :", self.text)
if not match:
return ""
return match.group(1).strip()
title_separator = f"\n{20*'-'}\n"
btn_separator = f"\n{20*'*'}\n"
def get_content(self, tag_name: str = "Paragraphs", raw: bool = False) -> str:
"""Get text of the main screen content of the layout."""
content = "".join(self._get_content_lines(tag_name, raw))
if not raw and content.endswith(" "):
# Stripping possible space at the end
content = content[:-1]
return content
visible = ""
if self.title():
visible += self.title()
visible += title_separator
visible += self.raw_content()
if self.buttons.is_applicable():
visible += btn_separator
visible += self.buttons.visible()
def get_button_texts(self) -> List[str]:
"""Get text of all buttons in the layout.
return visible
Example button: "< Button text : LADYBUG >"
-> ["LADYBUG"]
"""
return re.findall(r"< Button text : +(.*?) >", self.text)
def title(self) -> str:
"""Getting text that is displayed as a title."""
# there could be multiple of those - title and subtitle for example
title_strings = _get_strings_inside_tag(self.str_content, self.TITLE_TAG)
return "\n".join(title_strings).strip()
def get_seed_words(self) -> List[str]:
def text_content(self) -> str:
"""Getting text that is displayed in the main part of the screen."""
raw = self.raw_content()
return raw.replace("\n", " ")
def raw_content(self) -> str:
"""Getting raw text that is displayed in the main part of the screen,
with corresponding line breaks."""
content_strings = _get_strings_inside_tag(self.str_content, self.CONTENT_TAG)
# there are some unwanted spaces
strings = [
s.replace(" \n ", "\n").replace("\n ", "\n").lstrip()
for s in content_strings
]
return "\n".join(strings).strip()
def seed_words(self) -> List[str]:
"""Get all the seed words on the screen in order.
Example content: "1. ladybug 2. acid 3. academic 4. afraid"
-> ["ladybug", "acid", "academic", "afraid"]
"""
return re.findall(r"\d+\. (\w+)\b", self.get_content())
# Dot after index is optional (present on TT, not on TR)
return re.findall(r"\d+\.? (\w+)\b", self.raw_content())
def get_page_count(self) -> int:
def page_count(self) -> int:
"""Get number of pages for the layout."""
return self._get_number("page_count")
return (
self.kw_pair_int("scrollbar_page_count")
or self.kw_pair_int("page_count")
or 1
)
def get_active_page(self) -> int:
def active_page(self) -> int:
"""Get current page index of the layout."""
return self._get_number("active_page")
def _get_number(self, key: str) -> int:
"""Get number connected with a specific key."""
match = re.search(rf"{key} : +(\d+)", self.text)
if not match:
return 0
return int(match.group(1))
def _get_content_lines(
self, tag_name: str = "Paragraphs", raw: bool = False
) -> List[str]:
"""Get lines of the main screen content of the layout."""
# First line should have content after the tag, last line does not store content
tag = f"< {tag_name}"
if tag in self.lines[0]:
first_line = self.lines[0].split(tag)[1]
all_lines = [first_line] + self.lines[1:-1]
else:
all_lines = self.lines[1:-1]
if raw:
return all_lines
else:
return [_clean_line(line) for line in all_lines]
return (
self.kw_pair_int("scrollbar_active_page")
or self.kw_pair_int("active_page")
or 0
)
def _clean_line(line: str) -> str:
@ -170,10 +290,10 @@ def multipage_content(layouts: List[LayoutContent]) -> str:
"""Get overall content from multiple-page layout."""
final_text = ""
for layout in layouts:
final_text += layout.get_content()
final_text += layout.text_content()
# When the raw content of the page ends with ellipsis,
# we need to add a space to separate it with the next page
if layout.get_content(raw=True).endswith("... "):
if layout.raw_content().endswith("... "):
final_text += " "
# Stripping possible space at the end of last page
@ -328,23 +448,21 @@ class DebugLink:
layout = self.wait_layout()
else:
layout = self.read_layout()
self.save_debug_screen(layout.lines)
self.save_debug_screen(layout.visible_screen())
def save_debug_screen(self, lines: List[str]) -> None:
def save_debug_screen(self, screen_content: str) -> None:
if self.screen_text_file is not None:
if not self.screen_text_file.exists():
self.screen_text_file.write_bytes(b"")
content = "\n".join(lines)
# Not writing the same screen twice
if content == self.last_screen_content:
if screen_content == self.last_screen_content:
return
self.last_screen_content = content
self.last_screen_content = screen_content
with open(self.screen_text_file, "a") as f:
f.write(content)
f.write(screen_content)
f.write("\n" + 80 * "/" + "\n")
# Type overloads make sure that when we supply `wait=True` into `click()`,
@ -373,9 +491,6 @@ class DebugLink:
def press_info(self) -> None:
self.input(button=messages.DebugButton.INFO)
def swipe_all_the_way_up(self) -> None:
self.input(swipe=messages.DebugSwipeDirection.ALL_THE_WAY_UP, wait=True)
def swipe_up(self, wait: bool = False) -> None:
self.input(swipe=messages.DebugSwipeDirection.UP, wait=wait)
@ -521,12 +636,8 @@ class DebugUI:
else:
# Paginating (going as further as possible) and pressing Yes
if br.pages is not None:
if br.pages == 0:
# When we do not know how many, but want to paginate
self.debuglink.swipe_all_the_way_up()
else:
for _ in range(br.pages - 1):
self.debuglink.swipe_up(wait=True)
for _ in range(br.pages - 1):
self.debuglink.swipe_up(wait=True)
self.debuglink.press_yes()
elif self.input_flow is self.INPUT_FLOW_DONE:
raise AssertionError("input flow ended prematurely")

View File

@ -484,7 +484,6 @@ class DebugSwipeDirection(IntEnum):
DOWN = 1
LEFT = 2
RIGHT = 3
ALL_THE_WAY_UP = 4
class DebugButton(IntEnum):

View File

@ -23,9 +23,9 @@ def enter_word(
def confirm_recovery(debug: "DebugLink", legacy_ui: bool = False) -> None:
layout = debug.wait_layout()
if legacy_ui:
layout.text.startswith("Recovery mode")
layout.str_content.startswith("Recovery mode")
else:
assert layout.get_title() == "RECOVERY MODE"
assert layout.title() == "RECOVERY MODE"
debug.click(buttons.OK, wait=True)
@ -35,13 +35,13 @@ def select_number_of_words(
layout = debug.read_layout()
# select number of words
assert "select the number of words" in layout.get_content()
assert "select the number of words" in layout.text_content()
layout = debug.click(buttons.OK, wait=True)
if legacy_ui:
assert layout.text == "WordSelector"
assert layout.str_content == "WordSelector"
else:
# Two title options
assert layout.get_title() in ("SEED CHECK", "RECOVERY MODE")
assert layout.title() in ("SEED CHECK", "RECOVERY MODE")
# click the number
word_option_offset = 6
@ -51,7 +51,7 @@ def select_number_of_words(
) # raises if num of words is invalid
coords = buttons.grid34(index % 3, index // 3)
layout = debug.click(coords, wait=True)
assert "Enter any share" in layout.get_content()
assert "Enter any share" in layout.text_content()
def enter_share(
@ -60,9 +60,9 @@ def enter_share(
layout = debug.click(buttons.OK, wait=True)
if legacy_ui:
assert layout.text == "Slip39Keyboard"
assert layout.str_content == "Slip39Keyboard"
else:
assert layout.text == "< MnemonicKeyboard >"
assert layout.str_content == "< MnemonicKeyboard >"
for word in share.split(" "):
layout = enter_word(debug, word, is_slip39=True)
@ -75,15 +75,15 @@ def enter_shares(debug: "DebugLink", shares: list[str]) -> None:
expected_text = "Enter any share"
remaining = len(shares)
for share in shares:
assert expected_text in layout.get_content()
assert expected_text in layout.text_content()
layout = enter_share(debug, share)
remaining -= 1
expected_text = f"{remaining} more share"
assert "You have successfully recovered your wallet" in layout.get_content()
assert "You have successfully recovered your wallet" in layout.text_content()
def finalize(debug: "DebugLink") -> None:
layout = debug.click(buttons.OK, wait=True)
# TODO: should we also run Click/Persistence tests for model R?
assert layout.text.startswith("< Homescreen ")
assert layout.str_content.startswith("< Homescreen ")

View File

@ -12,26 +12,26 @@ if TYPE_CHECKING:
def confirm_wait(debug: "DebugLink", title: str) -> None:
layout = debug.wait_layout()
assert title.upper() in layout.get_title()
assert title.upper() in layout.title()
debug.click(buttons.OK, wait=True)
def confirm_read(debug: "DebugLink", title: str) -> None:
layout = debug.read_layout()
if title == "Caution":
assert "OK, I UNDERSTAND" in layout.text
assert "OK, I UNDERSTAND" in layout.str_content
elif title == "Success":
assert any(
text in layout.get_content() for text in ("success", "finished", "done")
text in layout.text_content() for text in ("success", "finished", "done")
)
else:
assert title.upper() in layout.get_title()
assert title.upper() in layout.title()
debug.click(buttons.OK, wait=True)
def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None:
layout = debug.read_layout()
assert "NumberInputDialog" in layout.text
assert "NumberInputDialog" in layout.str_content
for _ in range(diff):
debug.click(button, wait=False)
debug.click(buttons.OK, wait=True)
@ -41,15 +41,15 @@ def read_words(debug: "DebugLink", is_advanced: bool = False) -> list[str]:
words: list[str] = []
layout = debug.read_layout()
if is_advanced:
assert layout.get_title().startswith("GROUP")
assert layout.title().startswith("GROUP")
else:
assert layout.get_title().startswith("RECOVERY SHARE #")
assert layout.title().startswith("RECOVERY SHARE #")
# Swiping through all the page and loading the words
for _ in range(layout.get_page_count() - 1):
words.extend(layout.get_seed_words())
for _ in range(layout.page_count() - 1):
words.extend(layout.seed_words())
layout = debug.input(swipe=messages.DebugSwipeDirection.UP, wait=True)
words.extend(layout.get_seed_words())
words.extend(layout.seed_words())
debug.press_yes()
@ -58,13 +58,13 @@ def read_words(debug: "DebugLink", is_advanced: bool = False) -> list[str]:
def confirm_words(debug: "DebugLink", words: list[str]) -> None:
layout = debug.wait_layout()
assert "Select word" in layout.text
assert "Select word" in layout.str_content
for _ in range(3):
# "Select word 3 of 20"
# ^
word_pos = int(layout.get_content().split()[2])
word_pos = int(layout.text_content().split()[2])
# Unifying both the buttons and words to lowercase
btn_texts = [text.lower() for text in layout.get_button_texts()]
btn_texts = [text.lower() for text in layout.buttons()]
wanted_word = words[word_pos - 1].lower()
button_pos = btn_texts.index(wanted_word)
layout = debug.click(buttons.RESET_WORD_CHECK[button_pos], wait=True)

View File

@ -46,18 +46,18 @@ def set_autolock_delay(device_handler: "BackgroundDeviceHandler", delay_ms: int)
device_handler.run(device.apply_settings, auto_lock_delay_ms=delay_ms)
layout = debug.wait_layout()
assert layout.text == "< PinKeyboard >"
assert layout.str_content == "< PinKeyboard >"
debug.input("1234")
layout = debug.wait_layout()
assert (
f"auto-lock your device after {delay_ms // 1000} seconds"
in layout.get_content()
in layout.text_content()
)
debug.click(buttons.OK)
layout = debug.wait_layout()
assert layout.text.startswith("< Homescreen")
assert layout.str_content.startswith("< Homescreen")
assert device_handler.result() == "Settings applied"
@ -83,13 +83,15 @@ def test_autolock_interrupts_signing(device_handler: "BackgroundDeviceHandler"):
device_handler.run(btc.sign_tx, "Bitcoin", [inp1], [out1], prev_txes=TX_CACHE)
layout = debug.wait_layout()
assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.get_content().replace(" ", "")
assert "1MJ2tj2ThBE62zXbBYA5ZaN3fdve5CPAz1" in layout.text_content().replace(
" ", ""
)
debug.click(buttons.OK, wait=True)
debug.click(buttons.OK, wait=True)
layout = debug.click(buttons.OK, wait=True)
assert "Total amount: 0.0039 BTC" in layout.get_content()
assert "Total amount: 0.0039 BTC" in layout.text_content()
# wait for autolock to kick in
time.sleep(10.1)
@ -108,7 +110,7 @@ def test_autolock_passphrase_keyboard(device_handler: "BackgroundDeviceHandler")
# enter passphrase - slowly
layout = debug.wait_layout()
assert layout.text == "PassphraseKeyboard"
assert layout.str_content == "PassphraseKeyboard"
CENTER_BUTTON = buttons.grid35(1, 2)
for _ in range(11):
@ -127,26 +129,26 @@ def test_dryrun_locks_at_number_of_words(device_handler: "BackgroundDeviceHandle
# unlock
layout = debug.wait_layout()
assert "Do you really want to check the recovery seed?" in layout.get_content()
assert "Do you really want to check the recovery seed?" in layout.text_content()
layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >"
assert layout.str_content == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True)
assert "select the number of words " in layout.get_content()
assert "select the number of words " in layout.text_content()
# wait for autolock to trigger
time.sleep(10.1)
layout = debug.wait_layout()
assert layout.text.startswith("< Lockscreen")
assert layout.str_content.startswith("< Lockscreen")
with pytest.raises(exceptions.Cancelled):
device_handler.result()
# unlock
layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >"
assert layout.str_content == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True)
# we are back at homescreen
assert "select the number of words" in layout.get_content()
assert "select the number of words" in layout.text_content()
@pytest.mark.setup_client(pin=PIN4)
@ -158,9 +160,9 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"):
# unlock
layout = debug.wait_layout()
assert "Do you really want to check the recovery seed?" in layout.get_content()
assert "Do you really want to check the recovery seed?" in layout.text_content()
layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >"
assert layout.str_content == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True)
# select 20 words
@ -168,10 +170,10 @@ def test_dryrun_locks_at_word_entry(device_handler: "BackgroundDeviceHandler"):
layout = debug.click(buttons.OK, wait=True)
# make sure keyboard locks
assert layout.text == "< MnemonicKeyboard >"
assert layout.str_content == "< MnemonicKeyboard >"
time.sleep(10.1)
layout = debug.wait_layout()
assert layout.text.startswith("< Lockscreen")
assert layout.str_content.startswith("< Lockscreen")
with pytest.raises(exceptions.Cancelled):
device_handler.result()
@ -185,9 +187,9 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"):
# unlock
layout = debug.wait_layout()
assert "Do you really want to check the recovery seed?" in layout.get_content()
assert "Do you really want to check the recovery seed?" in layout.text_content()
layout = debug.click(buttons.OK, wait=True)
assert layout.text == "< PinKeyboard >"
assert layout.str_content == "< PinKeyboard >"
layout = debug.input(PIN4, wait=True)
# select 20 words
@ -195,11 +197,11 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"):
layout = debug.click(buttons.OK, wait=True)
# type the word OCEAN slowly
assert layout.text == "< MnemonicKeyboard >"
assert layout.str_content == "< MnemonicKeyboard >"
for coords in buttons.type_word("ocea", is_slip39=True):
time.sleep(9)
debug.click(coords)
layout = debug.click(buttons.CONFIRM_WORD, wait=True)
# should not have locked, even though we took 9 seconds to type each letter
assert layout.text == "< MnemonicKeyboard >"
assert layout.str_content == "< MnemonicKeyboard >"
device_handler.kill_task()

View File

@ -41,7 +41,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
# unlock with message
device_handler.run(common.get_test_address)
layout = debug.wait_layout()
assert layout.text == "< PinKeyboard >"
assert layout.str_content == "< PinKeyboard >"
debug.input("1234", wait=True)
assert device_handler.result()
@ -57,7 +57,7 @@ def test_hold_to_lock(device_handler: "BackgroundDeviceHandler"):
# unlock by touching
layout = debug.click(buttons.INFO, wait=True)
assert layout.text == "< PinKeyboard >"
assert layout.str_content == "< PinKeyboard >"
debug.input("1234", wait=True)
assert device_handler.features().unlocked is True

View File

@ -25,7 +25,6 @@ from trezorlib import btc, tools
from trezorlib.messages import ButtonRequestType
if TYPE_CHECKING:
from trezorlib.debuglink import LayoutContent
from trezorlib.debuglink import DebugLink, TrezorClientDebugLink as Client
from trezorlib.messages import ButtonRequest
from _pytest.mark.structures import MarkDecorator
@ -332,8 +331,7 @@ def read_and_confirm_mnemonic_tr(
assert br.pages is not None
for _ in range(br.pages):
layout = debug.wait_layout()
words = ModelRLayout(layout).get_mnemonic_words()
words = layout.seed_words()
mnemonic.extend(words)
debug.press_right()
@ -351,47 +349,6 @@ def read_and_confirm_mnemonic_tr(
return " ".join(mnemonic)
class ModelRLayout:
"""Layout shortcuts for Model R."""
def __init__(self, layout: "LayoutContent") -> None:
self.layout = layout
def get_mnemonic_words(self) -> list[str]:
"""Extract mnemonic words from the layout lines.
Example input: [..., '4 must', '5 during', '6 monitor', ...]
Example output: ['must', 'during', 'monitor']
"""
words: list[str] = []
for line in self.layout.lines:
if " " in line:
number, word = line.split(" ", 1)
if all(c.isdigit() for c in number):
words.append(word.strip())
return words
def get_word_index(self) -> int:
"""Extract currently asked mnemonic index.
Example input: "Select word 3/12"
Example output: 2
"""
prompt = self.layout.lines[0]
human_index = prompt.split(" ")[-1].split("/")[0]
return int(human_index) - 1
def get_current_word(self) -> str:
"""Extract currently selected word.
Example input: "SELECT [Select(monitor)]"
Example output: "monitor"
"""
buttons = self.layout.lines[-1]
return buttons.split("[Select(")[1].split(")]")[0]
def click_info_button(debug: "DebugLink"):
"""Click Shamir backup info button and return back."""
debug.press_info()
@ -414,9 +371,9 @@ def get_test_address(client: "Client") -> str:
def get_text_from_paginated_screen(client: "Client", screen_count: int) -> str:
"""Aggregating screen text from more pages into one string."""
text: str = client.debug.wait_layout().text
text: str = client.debug.wait_layout().str_content
for _ in range(screen_count - 1):
client.debug.swipe_up()
text += client.debug.wait_layout().text
text += client.debug.wait_layout().str_content
return text

View File

@ -53,19 +53,19 @@ def test_sd_protect_unlock(client: Client):
def input_flow_enable_sd_protect():
yield # Enter PIN to unlock device
assert "< PinKeyboard >" == layout().text
assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234")
yield # do you really want to enable SD protection
assert "SD card protection" in layout().get_content()
assert "SD card protection" in layout().text_content()
client.debug.press_yes()
yield # enter current PIN
assert "< PinKeyboard >" == layout().text
assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234")
yield # you have successfully enabled SD protection
assert "You have successfully enabled SD protection." in layout().get_content()
assert "You have successfully enabled SD protection." in layout().text_content()
client.debug.press_yes()
with client:
@ -75,23 +75,23 @@ def test_sd_protect_unlock(client: Client):
def input_flow_change_pin():
yield # do you really want to change PIN?
assert "CHANGE PIN" == layout().get_title()
assert "CHANGE PIN" == layout().title()
client.debug.press_yes()
yield # enter current PIN
assert "< PinKeyboard >" == layout().text
assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234")
yield # enter new PIN
assert "< PinKeyboard >" == layout().text
assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234")
yield # enter new PIN again
assert "< PinKeyboard >" == layout().text
assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234")
yield # Pin change successful
assert "You have successfully changed your PIN." in layout().get_content()
assert "You have successfully changed your PIN." in layout().text_content()
client.debug.press_yes()
with client:
@ -103,15 +103,15 @@ def test_sd_protect_unlock(client: Client):
def input_flow_change_pin_format():
yield # do you really want to change PIN?
assert "CHANGE PIN" == layout().get_title()
assert "CHANGE PIN" == layout().title()
client.debug.press_yes()
yield # enter current PIN
assert "< PinKeyboard >" == layout().text
assert "< PinKeyboard >" == layout().str_content
client.debug.input("1234")
yield # SD card problem
assert "Wrong SD card" in layout().get_content()
assert "Wrong SD card" in layout().text_content()
client.debug.press_no() # close
with client, pytest.raises(TrezorFailure) as e:

View File

@ -225,12 +225,12 @@ class InputFlowShowMultisigXPUBs(InputFlowBase):
def input_flow_tt(self) -> GeneratorType:
yield # show address
layout = self.debug.wait_layout() # TODO: do not need to *wait* now?
assert layout.get_title() == "MULTISIG 2 OF 3"
assert layout.get_content().replace(" ", "") == self.address
assert layout.title() == "MULTISIG 2 OF 3"
assert layout.text_content().replace(" ", "") == self.address
self.debug.press_no()
yield # show QR code
assert "Painter" in self.debug.wait_layout().text
assert "Painter" in self.debug.wait_layout().str_content
# Three xpub pages with the same testing logic
for xpub_num in range(3):
@ -241,12 +241,12 @@ class InputFlowShowMultisigXPUBs(InputFlowBase):
self.debug.press_no()
yield # show XPUB#{xpub_num}
layout1 = self.debug.wait_layout()
assert layout1.get_title() == expected_title
assert layout1.title() == expected_title
self.debug.swipe_up()
layout2 = self.debug.wait_layout()
assert layout2.get_title() == expected_title
content = (layout1.get_content() + layout2.get_content()).replace(" ", "")
assert layout2.title() == expected_title
content = (layout1.text_content() + layout2.text_content()).replace(" ", "")
assert content == self.xpubs[xpub_num]
self.debug.press_yes()
@ -263,14 +263,14 @@ class InputFlowPaymentRequestDetails(InputFlowBase):
self.debug.press_info()
yield # confirm first output
assert self.outputs[0].address[:16] in self.layout().text
assert self.outputs[0].address[:16] in self.layout().text_content()
self.debug.press_yes()
yield # confirm first output
self.debug.wait_layout()
self.debug.press_yes()
yield # confirm second output
assert self.outputs[1].address[:16] in self.layout().text
assert self.outputs[1].address[:16] in self.layout().text_content()
self.debug.press_yes()
yield # confirm second output
self.debug.wait_layout()
@ -325,7 +325,7 @@ def lock_time_input_flow_tt(
debug.press_yes()
yield # confirm locktime
layout_text = debug.wait_layout().text
layout_text = debug.wait_layout().text_content()
layout_assert_func(layout_text)
debug.press_yes()
@ -345,7 +345,7 @@ def lock_time_input_flow_tr(
debug.press_yes()
yield # confirm locktime
layout_text = debug.wait_layout().text
layout_text = debug.wait_layout().text_content()
layout_assert_func(layout_text)
debug.press_yes()
@ -989,15 +989,15 @@ class InputFlowSlip39AdvancedResetRecovery(InputFlowBase):
def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> GeneratorType:
yield
assert "check the recovery seed" in debug.wait_layout().get_content()
assert "check the recovery seed" in debug.wait_layout().text_content()
debug.click(buttons.OK)
yield
assert "Select number of words" in debug.wait_layout().get_content()
assert "Select number of words" in debug.wait_layout().text_content()
debug.click(buttons.OK)
yield
assert "SelectWordCount" in debug.wait_layout().text
assert "SelectWordCount" in debug.wait_layout().str_content
# click the correct number
word_option_offset = 6
word_options = (12, 18, 20, 24, 33)
@ -1005,12 +1005,12 @@ def enter_recovery_seed_dry_run(debug: DebugLink, mnemonic: list[str]) -> Genera
debug.click(buttons.grid34(index % 3, index // 3))
yield
assert "Enter recovery seed" in debug.wait_layout().get_content()
assert "Enter recovery seed" in debug.wait_layout().text_content()
debug.click(buttons.OK)
yield
for word in mnemonic:
assert debug.wait_layout().text == "< MnemonicKeyboard >"
assert debug.wait_layout().str_content == "< MnemonicKeyboard >"
debug.input(word)
@ -1028,16 +1028,16 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase):
def input_flow_tr(self) -> GeneratorType:
yield
assert "check the recovery seed" in self.layout().text
assert "check the recovery seed" in self.layout().text_content()
self.debug.press_right()
yield
assert "select the number of words" in self.layout().text
assert "select the number of words" in self.layout().text_content()
self.debug.press_yes()
yield
yield
assert "NUMBER OF WORDS" in self.layout().text
assert "NUMBER OF WORDS" in self.layout().title()
word_options = (12, 18, 20, 24, 33)
index = word_options.index(len(self.mnemonic))
for _ in range(index):
@ -1045,14 +1045,14 @@ class InputFlowBip39RecoveryDryRun(InputFlowBase):
self.debug.input(str(len(self.mnemonic)))
yield
assert "enter your recovery seed" in self.layout().text
assert "enter your recovery seed" in self.layout().text_content()
self.debug.press_yes()
yield
self.debug.press_right()
yield
for word in self.mnemonic:
assert "WORD" in self.layout().text
assert "WORD" in self.layout().title()
self.debug.input(word)
self.debug.wait_layout()
@ -1072,56 +1072,56 @@ class InputFlowBip39RecoveryDryRunInvalid(InputFlowBase):
br = yield
assert br.code == messages.ButtonRequestType.Warning
assert "invalid recovery seed" in self.layout().get_content()
assert "invalid recovery seed" in self.layout().text_content()
self.debug.click(buttons.OK)
yield # retry screen
assert "Select number of words" in self.layout().get_content()
assert "Select number of words" in self.layout().text_content()
self.debug.click(buttons.CANCEL)
yield
assert "ABORT SEED CHECK" == self.layout().get_title()
assert "ABORT SEED CHECK" == self.layout().title()
self.debug.click(buttons.OK)
def input_flow_tr(self) -> GeneratorType:
yield
assert "check the recovery seed" in self.layout().text
assert "check the recovery seed" in self.layout().text_content()
self.debug.press_right()
yield
assert "select the number of words" in self.layout().text
assert "select the number of words" in self.layout().text_content()
self.debug.press_yes()
yield
yield
assert "NUMBER OF WORDS" in self.layout().text
assert "NUMBER OF WORDS" in self.layout().title()
# select 12 words
self.debug.press_middle()
yield
assert "enter your recovery seed" in self.layout().text
assert "enter your recovery seed" in self.layout().text_content()
self.debug.press_yes()
yield
assert "WORD ENTERING" in self.layout().text
assert "WORD ENTERING" in self.layout().title()
self.debug.press_yes()
yield
for _ in range(12):
assert "WORD" in self.layout().text
assert "WORD" in self.layout().title()
self.debug.input("stick")
br = yield
assert br.code == messages.ButtonRequestType.Warning
assert "invalid recovery seed" in self.layout().text
assert "invalid recovery seed" in self.layout().text_content()
self.debug.press_right()
yield # retry screen
assert "select the number of words" in self.layout().text
assert "select the number of words" in self.layout().text_content()
self.debug.press_left()
yield
assert "abort" in self.layout().text
assert "abort" in self.layout().text_content()
self.debug.press_right()
@ -1130,41 +1130,41 @@ def bip39_recovery_possible_pin(
) -> GeneratorType:
yield
assert (
"Do you really want to recover a wallet?" in debug.wait_layout().get_content()
"Do you really want to recover a wallet?" in debug.wait_layout().text_content()
)
debug.press_yes()
# PIN when requested
if pin is not None:
yield
assert debug.wait_layout().text == "< PinKeyboard >"
assert debug.wait_layout().str_content == "< PinKeyboard >"
debug.input(pin)
yield
assert debug.wait_layout().text == "< PinKeyboard >"
assert debug.wait_layout().str_content == "< PinKeyboard >"
debug.input(pin)
yield
assert "Select number of words" in debug.wait_layout().get_content()
assert "Select number of words" in debug.wait_layout().text_content()
debug.press_yes()
yield
assert "SelectWordCount" in debug.wait_layout().text
assert "SelectWordCount" in debug.wait_layout().str_content
debug.input(str(len(mnemonic)))
yield
assert "Enter recovery seed" in debug.wait_layout().get_content()
assert "Enter recovery seed" in debug.wait_layout().text_content()
debug.press_yes()
yield
for word in mnemonic:
assert debug.wait_layout().text == "< MnemonicKeyboard >"
assert debug.wait_layout().str_content == "< MnemonicKeyboard >"
debug.input(word)
yield
assert (
"You have successfully recovered your wallet."
in debug.wait_layout().get_content()
in debug.wait_layout().text_content()
)
debug.press_yes()
@ -1179,47 +1179,49 @@ class InputFlowBip39RecoveryPIN(InputFlowBase):
def input_flow_tr(self) -> GeneratorType:
yield
assert "By continuing you agree" in self.layout().text
assert "By continuing you agree" in self.layout().text_content()
self.debug.press_right()
assert "trezor.io/tos" in self.layout().text
assert "trezor.io/tos" in self.layout().text_content()
self.debug.press_yes()
yield
assert "ENTER" in self.layout().text
assert "ENTER" in self.layout().text_content()
self.debug.input("654")
yield
assert "re-enter PIN to confirm" in self.layout().text
assert "re-enter PIN to confirm" in self.layout().text_content()
self.debug.press_right()
yield
assert "ENTER" in self.layout().text
assert "ENTER" in self.layout().text_content()
self.debug.input("654")
yield
assert "select the number of words" in self.layout().text
assert "select the number of words" in self.layout().text_content()
self.debug.press_yes()
yield
yield
assert "NUMBER OF WORDS" in self.layout().text
assert "NUMBER OF WORDS" in self.layout().title()
self.debug.input(str(len(self.mnemonic)))
yield
assert "enter your recovery seed" in self.layout().text
assert "enter your recovery seed" in self.layout().text_content()
self.debug.press_yes()
yield
assert "WORD ENTERING" in self.layout().text
assert "WORD ENTERING" in self.layout().title()
self.debug.press_right()
yield
for word in self.mnemonic:
assert "WORD" in self.layout().text
assert "WORD" in self.layout().title()
self.debug.input(word)
yield
assert "You have finished recovering your wallet." in self.layout().text
assert (
"You have finished recovering your wallet." in self.layout().text_content()
)
self.debug.press_yes()

View File

@ -50,10 +50,10 @@ def test_abort(emulator: Emulator):
device_handler.run(device.recover, pin_protection=False, show_tutorial=False)
layout = debug.wait_layout()
assert layout.get_title() == "RECOVERY MODE"
assert layout.title() == "RECOVERY MODE"
layout = debug.click(buttons.OK, wait=True)
assert "select the number of words" in layout.text
assert "select the number of words" in layout.str_content
device_handler.restart(emulator)
debug = device_handler.debuglink()
@ -63,13 +63,13 @@ def test_abort(emulator: Emulator):
# no waiting for layout because layout doesn't change
layout = debug.read_layout()
assert "select the number of words" in layout.text
assert "select the number of words" in layout.str_content
layout = debug.click(buttons.CANCEL, wait=True)
assert layout.get_title() == "ABORT RECOVERY"
assert layout.title() == "ABORT RECOVERY"
layout = debug.click(buttons.OK, wait=True)
assert layout.text.startswith("< Homescreen")
assert layout.str_content.startswith("< Homescreen")
features = device_handler.features()
assert features.recovery_mode is False
@ -136,10 +136,10 @@ def test_recovery_on_old_wallet(emulator: Emulator):
# start entering first share
layout = debug.read_layout()
assert "Enter any share" in layout.text
assert "Enter any share" in layout.str_content
debug.press_yes()
layout = debug.wait_layout()
assert layout.text == "< MnemonicKeyboard >"
assert layout.str_content == "< MnemonicKeyboard >"
# enter first word
debug.input(words[0])
@ -151,12 +151,12 @@ def test_recovery_on_old_wallet(emulator: Emulator):
# try entering remaining 19 words
for word in words[1:]:
assert layout.text == "< MnemonicKeyboard >"
assert layout.str_content == "< MnemonicKeyboard >"
debug.input(word)
layout = debug.wait_layout()
# check that we entered the first share successfully
assert "2 more shares" in layout.text
assert "2 more shares" in layout.str_content
# try entering the remaining shares
for share in MNEMONIC_SLIP39_BASIC_20_3of6[1:3]:
@ -178,13 +178,13 @@ def test_recovery_multiple_resets(emulator: Emulator):
expected_text = "Enter any share"
remaining = len(shares)
for share in shares:
assert expected_text in layout.text
assert expected_text in layout.str_content
layout = recovery.enter_share(debug, share)
remaining -= 1
expected_text = "You have entered"
debug = _restart(device_handler, emulator)
assert "You have successfully recovered your wallet" in layout.get_content()
assert "You have successfully recovered your wallet" in layout.text_content()
device_handler = BackgroundDeviceHandler(emulator.client)
debug = device_handler.debuglink()
@ -212,7 +212,7 @@ def test_recovery_multiple_resets(emulator: Emulator):
enter_shares_with_restarts(debug)
debug = device_handler.debuglink()
layout = debug.read_layout()
assert layout.text.startswith("< Homescreen")
assert layout.str_content.startswith("< Homescreen")
features = device_handler.features()
assert features.initialized is True

View File

@ -317,7 +317,7 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]):
layout = recovery.enter_share(
debug, MNEMONIC_SLIP39_BASIC_20_3of6[0], legacy_ui=legacy_ui
)
assert "2 more shares" in layout.text
assert "2 more shares" in layout.str_content
device_id = emu.client.features.device_id
storage = emu.get_storage()
@ -331,11 +331,11 @@ def test_upgrade_shamir_recovery(gen: str, tag: Optional[str]):
# second share
layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[2])
assert "1 more share" in layout.text
assert "1 more share" in layout.str_content
# last one
layout = recovery.enter_share(debug, MNEMONIC_SLIP39_BASIC_20_3of6[1])
assert "You have successfully" in layout.text
assert "You have successfully" in layout.str_content
# Check the result
state = debug.state()