1
0
mirror of https://github.com/trezor/trezor-firmware.git synced 2024-12-18 04:18:10 +00:00

fix(core/ui): T3T1 device tests

[no changelog]
This commit is contained in:
Martin Milata 2024-05-28 00:19:01 +02:00
parent c03781aef9
commit 69e406f7cf
33 changed files with 8547 additions and 8121 deletions

View File

@ -266,6 +266,7 @@ jobs:
needs:
- param
- core_emu
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
@ -350,6 +351,7 @@ jobs:
needs:
- param
- core_emu
timeout-minutes: 30
strategy:
fail-fast: false
matrix:

View File

@ -569,8 +569,8 @@ impl EventCtx {
}
pub fn set_page_count(&mut self, count: usize) {
#[cfg(feature = "ui_debug")]
assert!(self.page_count.is_none());
// #[cfg(feature = "ui_debug")]
// assert!(self.page_count.unwrap_or(count) == count);
self.page_count = Some(count);
}

View File

@ -90,6 +90,7 @@ impl<T: Component + Paginate + Clone> Component for SwipePage<T> {
}
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
ctx.set_page_count(self.pages);
if let Some(t) = &mut self.transition {
let finished = Self::handle_transition(ctx, event, t);
if finished {

View File

@ -136,7 +136,7 @@ where
}
pub fn with_description(self, description: impl Into<TString<'static>>) -> Self {
self.with_text(&theme::TEXT_NORMAL_OFF_WHITE, description)
self.with_text(&theme::TEXT_NORMAL_GREY_EXTRA_LIGHT, description)
}
pub fn with_value(self, value: impl Into<TString<'static>>) -> Self {
@ -154,9 +154,9 @@ where
theme::BG,
)),
paragraphs: ParagraphVecShort::from_iter([
Paragraph::new(&theme::TEXT_NORMAL_OFF_WHITE, l0).centered(),
Paragraph::new(&theme::TEXT_NORMAL_GREY_EXTRA_LIGHT, l0).centered(),
Paragraph::new(&theme::TEXT_DEMIBOLD, l1).centered(),
Paragraph::new(&theme::TEXT_NORMAL_OFF_WHITE, l2).centered(),
Paragraph::new(&theme::TEXT_NORMAL_GREY_EXTRA_LIGHT, l2).centered(),
Paragraph::new(&theme::TEXT_DEMIBOLD, l3).centered(),
])
.into_paragraphs()

View File

@ -76,7 +76,7 @@ pub struct PassphraseKeyboard {
input_prompt: Child<Label<'static>>,
erase_btn: Child<Maybe<Button>>,
cancel_btn: Child<Maybe<Button>>,
confirm_btn: Child<Maybe<Button>>,
confirm_btn: Child<Button>,
next_btn: Child<Button>,
keys: [Child<Button>; KEY_COUNT],
active_layout: KeyboardLayout,
@ -87,8 +87,8 @@ const PAGE_COUNT: usize = 4;
const KEY_COUNT: usize = 10;
#[rustfmt::skip]
const KEYBOARD: [[&str; KEY_COUNT]; PAGE_COUNT] = [
["abc", "def", "ghi", "jkl", "mno", "pq", "rst", "uvq", "xyz", " *#"],
["ABC", "DEF", "GHI", "JKL", "MNO", "PQ", "RST", "UVQ", "XYZ", " *#"],
["abc", "def", "ghi", "jkl", "mno", "pq", "rst", "uvw", "xyz", " *#"],
["ABC", "DEF", "GHI", "JKL", "MNO", "PQ", "RST", "UVW", "XYZ", " *#"],
["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
["_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^="],
];
@ -102,8 +102,7 @@ impl PassphraseKeyboard {
let confirm_btn = Button::with_icon(theme::ICON_CONFIRM)
.styled(theme::button_passphrase_confirm())
.with_radius(15)
.initially_enabled(false);
let confirm_btn = Maybe::hidden(theme::BG, confirm_btn).into_child();
.into_child();
let next_btn = Button::new(active_layout.next().into())
.styled(theme::button_passphrase_next())
@ -206,10 +205,6 @@ impl PassphraseKeyboard {
btn.show_if(ctx, is_empty);
btn.inner_mut().enable_if(ctx, is_empty);
});
self.confirm_btn.mutate(ctx, |ctx, btn| {
btn.show_if(ctx, !is_empty);
btn.inner_mut().enable_if(ctx, !is_empty);
});
self.update_input_btns_state(ctx);
}
@ -381,11 +376,11 @@ impl Component for PassphraseKeyboard {
self.input.render(target);
self.next_btn.render(target);
self.erase_btn.render(target);
self.confirm_btn.render(target);
if self.input.inner().textbox.is_empty() {
self.cancel_btn.render(target);
self.input_prompt.render(target);
} else {
self.confirm_btn.render(target);
// FIXME: when prompt fixed in Figma
// self.input_prompt.render(target);
}
for btn in &self.keys {
btn.render(target);

View File

@ -6,6 +6,7 @@ use crate::{
geometry::{Alignment2D, Offset, Rect},
shape,
shape::Renderer,
util::animation_disabled,
},
};
@ -97,6 +98,9 @@ impl Component for PromptScreen {
(DismissType::Hold, Some(ButtonMsg::LongPressed)) => {
return Some(());
}
(DismissType::Hold, Some(ButtonMsg::Clicked)) if animation_disabled() => {
return Some(());
}
_ => (),
}
None

View File

@ -205,5 +205,6 @@ impl<'a> crate::trace::Trace for ShareWords<'a> {
let content =
word.map(|w| build_string!(50, inttostr!(self.page_index as u8 + 1), ". ", w, "\n"));
t.string("screen_content", content.as_str().into());
t.int("page_count", self.share_words.len() as i64)
}
}

View File

@ -3,9 +3,10 @@ use crate::{
strutil::TString,
translations::TR,
ui::{
button_request::ButtonRequestCode,
component::{
text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, SwipeDirection,
ButtonRequestExt, ComponentExt, SwipeDirection,
},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
},
@ -86,7 +87,8 @@ impl ConfirmResetCreate {
let content_intro = Frame::left_aligned(title, SwipePage::vertical(paragraphs))
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
.one_button_request(ButtonRequestCode::ResetDevice.with_type("setup_device"));
let content_menu = Frame::left_aligned(
"".into(),
@ -106,7 +108,8 @@ impl ConfirmResetCreate {
.map(|msg| match msg {
FrameMsg::Content(()) => Some(FlowMsg::Confirmed),
_ => Some(FlowMsg::Cancelled),
});
})
.one_button_request(ButtonRequestCode::ResetDevice.with_type("confirm_setup_device"));
let store = flow_store()
.add(content_intro)?

View File

@ -2,9 +2,10 @@ use crate::{
error,
translations::TR,
ui::{
button_request::ButtonRequestCode,
component::{
text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, SwipeDirection,
ButtonRequestExt, ComponentExt, SwipeDirection,
},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
},
@ -80,7 +81,8 @@ impl ConfirmResetRecover {
)
.with_menu_button()
.with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info));
.map(|msg| matches!(msg, FrameMsg::Button(_)).then_some(FlowMsg::Info))
.one_button_request(ButtonRequestCode::ProtectCall.with_type("recover_device"));
let content_menu = Frame::left_aligned(
"".into(),

View File

@ -4,9 +4,10 @@ use crate::{
strutil::TString,
translations::TR,
ui::{
button_request::ButtonRequestCode,
component::{
text::paragraphs::{Paragraph, Paragraphs},
ComponentExt, SwipeDirection,
ButtonRequestExt, ComponentExt, SwipeDirection,
},
flow::{base::Decision, flow_store, FlowMsg, FlowState, FlowStore, SwipeFlow, SwipePage},
layout::obj::LayoutObj,
@ -79,6 +80,7 @@ impl ShowShareWords {
let share_words_vec: Vec<TString, 33> = util::iter_into_vec(share_words_obj)?;
let text_info: TString = kwargs.get(Qstr::MP_QSTR_text_info)?.try_into()?;
let text_confirm: TString = kwargs.get(Qstr::MP_QSTR_text_confirm)?.try_into()?;
let nwords = share_words_vec.len();
let content_instruction = Frame::left_aligned(
title,
@ -89,7 +91,9 @@ impl ShowShareWords {
)
.with_subtitle(TR::words__instructions.into())
.with_footer(TR::instructions__swipe_up.into(), None)
.map(|msg| matches!(msg, FrameMsg::Content(_)).then_some(FlowMsg::Confirmed));
.map(|msg| matches!(msg, FrameMsg::Content(_)).then_some(FlowMsg::Confirmed))
.one_button_request(ButtonRequestCode::ResetDevice.with_type("share_words"))
.with_pages(move |_| nwords + 2);
let content_words = Frame::left_aligned(title, ShareWords::new(share_words_vec))
.with_subtitle(subtitle)

View File

@ -47,11 +47,7 @@ pub const ORANGE_LIGHT: Color = Color::rgb(0xFF, 0x8D, 0x6A); // cancel button
pub const RED: Color = Color::rgb(0xE7, 0x0E, 0x0E); // button
pub const RED_DARK: Color = Color::rgb(0xAE, 0x09, 0x09); // button pressed
pub const YELLOW: Color = Color::rgb(0xD9, 0x9E, 0x00); // button
pub const YELLOW_DARK: Color = Color::rgb(0x7A, 0x58, 0x00); // button pressed
pub const BLUE: Color = Color::rgb(0x06, 0x1E, 0xAD); // button
pub const BLUE_DARK: Color = Color::rgb(0x04, 0x10, 0x58); // button pressed
pub const OFF_WHITE: Color = Color::rgb(0xDE, 0xDE, 0xDE); // very light grey
pub const GREY_MEDIUM: Color = Color::rgb(0x4F, 0x4F, 0x4F); // button pressed
pub const VIOLET: Color = Color::rgb(0x95, 0x00, 0xCA);
pub const FATAL_ERROR_COLOR: Color = Color::rgb(0xE7, 0x0E, 0x0E);
@ -189,7 +185,7 @@ pub const fn label_default() -> TextStyle {
}
pub const fn label_keyboard() -> TextStyle {
TextStyle::new(Font::DEMIBOLD, OFF_WHITE, BG, GREY_LIGHT, GREY_LIGHT)
TextStyle::new(Font::DEMIBOLD, GREY_EXTRA_LIGHT, BG, GREY_LIGHT, GREY_LIGHT)
}
pub const fn label_keyboard_prompt() -> TextStyle {
@ -197,11 +193,11 @@ pub const fn label_keyboard_prompt() -> TextStyle {
}
pub const fn label_keyboard_warning() -> TextStyle {
TextStyle::new(Font::DEMIBOLD, RED, BG, GREY_LIGHT, GREY_LIGHT)
TextStyle::new(Font::DEMIBOLD, ORANGE_LIGHT, BG, GREY_LIGHT, GREY_LIGHT)
}
pub const fn label_keyboard_minor() -> TextStyle {
TEXT_NORMAL_OFF_WHITE
TEXT_NORMAL_GREY_EXTRA_LIGHT
}
pub const fn label_warning() -> TextStyle {
@ -209,7 +205,7 @@ pub const fn label_warning() -> TextStyle {
}
pub const fn label_warning_value() -> TextStyle {
TEXT_NORMAL_OFF_WHITE
TEXT_NORMAL_GREY_EXTRA_LIGHT
}
pub const fn label_recovery_title() -> TextStyle {
@ -217,7 +213,7 @@ pub const fn label_recovery_title() -> TextStyle {
}
pub const fn label_recovery_description() -> TextStyle {
TEXT_NORMAL_OFF_WHITE
TEXT_NORMAL_GREY_EXTRA_LIGHT
}
pub const fn label_progress() -> TextStyle {
@ -363,21 +359,21 @@ pub const fn button_cancel() -> ButtonStyleSheet {
normal: &ButtonStyle {
font: Font::BOLD,
text_color: FG,
button_color: RED,
button_color: ORANGE_LIGHT,
icon_color: GREY_LIGHT,
background_color: BG,
},
active: &ButtonStyle {
font: Font::BOLD,
text_color: FG,
button_color: RED_DARK,
button_color: ORANGE_DIMMED,
icon_color: GREY_LIGHT,
background_color: BG,
},
disabled: &ButtonStyle {
font: Font::BOLD,
text_color: GREY_LIGHT,
button_color: RED,
button_color: ORANGE_DIMMED,
icon_color: GREY_LIGHT,
background_color: BG,
},
@ -791,8 +787,8 @@ pub fn textstyle_number(num: i32) -> &'static TextStyle {
}
}
pub const TEXT_NORMAL_OFF_WHITE: TextStyle =
TextStyle::new(Font::NORMAL, OFF_WHITE, BG, GREY_LIGHT, GREY_LIGHT);
pub const TEXT_NORMAL_GREY_EXTRA_LIGHT: TextStyle =
TextStyle::new(Font::NORMAL, GREY_EXTRA_LIGHT, BG, GREY_LIGHT, GREY_LIGHT);
pub const TEXT_CHECKLIST_DEFAULT: TextStyle = TextStyle::new(Font::SUB, GREY, BG, GREY, GREY);
pub const TEXT_CHECKLIST_SELECTED: TextStyle =
TextStyle::new(Font::NORMAL, GREY_LIGHT, BG, GREY_LIGHT, GREY_LIGHT);

View File

@ -91,6 +91,7 @@ async def request_mnemonic(
# show_identifier_mismatch
await show_recovery_warning(
"warning_mismatched_share",
"",
TR.recovery__share_from_another_multi_share_backup,
)
return None
@ -129,14 +130,14 @@ async def show_invalid_mnemonic(word_count: int) -> None:
if backup_types.is_slip39_word_count(word_count):
await show_recovery_warning(
"warning_invalid_share",
TR.recovery__invalid_share_entered,
TR.words__please_try_again,
TR.recovery__invalid_share_entered,
)
else:
await show_recovery_warning(
"warning_invalid_seed",
TR.recovery__invalid_wallet_backup_entered,
TR.words__please_try_again,
TR.recovery__invalid_wallet_backup_entered,
)

View File

@ -348,19 +348,11 @@ async def confirm_single(
async def confirm_reset_device(_title: str, recovery: bool = False) -> None:
if recovery:
await raise_if_not_confirmed(
interact(
RustLayout(trezorui2.flow_confirm_reset_recover()),
"recover_device",
ButtonRequestType.ProtectCall,
)
RustLayout(trezorui2.flow_confirm_reset_recover()),
)
else:
await raise_if_not_confirmed(
interact(
RustLayout(trezorui2.flow_confirm_reset_create()),
"setup_device",
ButtonRequestType.ResetDevice,
)
RustLayout(trezorui2.flow_confirm_reset_create()),
)

View File

@ -158,8 +158,10 @@ async def show_recovery_warning(
interact(
RustLayout(
trezorui2.show_warning(
title=content,
value="Try again", # TODO: use TR
title=content or TR.words__warning,
value=subheader or "",
button=button,
description="",
)
),
br_type,

View File

@ -35,18 +35,14 @@ async def show_share_words(
text_info = TR.reset__write_down_words_template.format(words_count)
text_confirm = TR.reset__words_written_down_template.format(words_count)
result = await interact(
RustLayout(
trezorui2.flow_show_share_words(
title=title,
subtitle=subtitle,
words=share_words,
text_info=text_info,
text_confirm=text_confirm,
)
),
"backup_words",
ButtonRequestType.ResetDevice,
result = await RustLayout(
trezorui2.flow_show_share_words(
title=title,
subtitle=subtitle,
words=share_words,
text_info=text_info,
text_confirm=text_confirm,
)
)
if result != CONFIRMED:
@ -341,10 +337,10 @@ async def show_reset_warning(
interact(
RustLayout(
trezorui2.show_warning(
title=subheader or "",
title=content or TR.words__warning,
description="",
value=content,
button="",
value=subheader or "",
button=button,
allow_cancel=False,
)
),

View File

@ -1,8 +1,8 @@
{
"current": {
"merkle_root": "e79fdd3c9052dfd140aef2b4800b6b0b8000ae99822cf3e8be083da0dc2b376c",
"datetime": "2024-05-26T15:18:17.124044",
"commit": "3e9598245d57044418f4eeb5ce5bc792f20587c3"
"merkle_root": "e406bb0127f82bf476884b46c05ca86993c22e1373c4274443f2aef20b138a8e",
"datetime": "2024-05-28T00:05:41.698473",
"commit": "342b88f62bcd34595ea81ced550bb579b4e4a813"
},
"history": [
{

View File

@ -31,8 +31,24 @@ CORNER_BUTTON = (215, 25)
CONFIRM_WORD = (MID, TOP)
TOP_ROW = (MID, TOP)
RESET_MINUS = (LEFT, grid(DISPLAY_HEIGHT, 5, 1))
RESET_PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 1))
def reset_minus(model_internal_name: str) -> Coords:
RESET_MINUS_T3T1 = (LEFT, grid(DISPLAY_HEIGHT, 5, 3))
RESET_MINUS = (LEFT, grid(DISPLAY_HEIGHT, 5, 1))
if model_internal_name == "T3T1":
return RESET_MINUS_T3T1
else:
return RESET_MINUS
def reset_plus(model_internal_name: str) -> Coords:
RESET_PLUS_T3T1 = (RIGHT, grid(DISPLAY_HEIGHT, 5, 3))
RESET_PLUS = (RIGHT, grid(DISPLAY_HEIGHT, 5, 1))
if model_internal_name == "T3T1":
return RESET_PLUS_T3T1
else:
return RESET_PLUS
RESET_WORD_CHECK = [
(MID, grid(DISPLAY_HEIGHT, 5, 2)),

View File

@ -22,7 +22,9 @@ def enter_word(
typed_word = word[:4]
for coords in buttons.type_word(typed_word, is_slip39=is_slip39):
debug.click(coords)
if debug.model is models.T3T1 and not is_slip39 and len(word) > 4:
# T3T1 (mercury) 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, wait=True)
return debug.click(buttons.CONFIRM_WORD, wait=True)
elif debug.model in (models.T2B1,):
letter_index = 0
@ -49,8 +51,10 @@ def enter_word(
def confirm_recovery(debug: "DebugLink") -> None:
layout = debug.wait_layout()
TR.assert_equals(layout.title(), "recovery__title")
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click(buttons.OK, wait=True)
elif debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
elif debug.model in (models.T2B1,):
debug.press_right(wait=True)
debug.press_right()
@ -99,20 +103,29 @@ def select_number_of_words(
raise ValueError("Unknown model")
if num_of_words in (20, 33):
TR.assert_in(layout.text_content(), "recovery__enter_any_share")
TR.assert_in_multiple(
layout.text_content(),
["recovery__enter_any_share", "recovery__only_first_n_letters"],
)
else:
TR.assert_in(layout.text_content(), "recovery__enter_backup")
TR.assert_in_multiple(
layout.text_content(),
["recovery__enter_backup", "recovery__only_first_n_letters"],
)
def enter_share(
debug: "DebugLink", share: str, is_first: bool = True
) -> "LayoutContent":
TR.assert_in(debug.read_layout().title(), "recovery__title_recover")
if debug.model in (models.T2B1,):
TR.assert_in(debug.read_layout().title(), "recovery__title_recover")
layout = debug.wait_layout()
for _ in range(layout.page_count()):
layout = debug.press_right(wait=True)
elif debug.model in (models.T3T1,):
layout = debug.swipe_up(wait=True)
else:
TR.assert_in(debug.read_layout().title(), "recovery__title_recover")
layout = debug.click(buttons.OK, wait=True)
assert "MnemonicKeyboard" in layout.all_components()
@ -124,15 +137,20 @@ def enter_share(
def enter_shares(debug: "DebugLink", shares: list[str]) -> None:
TR.assert_in(debug.read_layout().text_content(), "recovery__enter_any_share")
TR.assert_in_multiple(
debug.read_layout().text_content(),
["recovery__enter_any_share", "recovery__only_first_n_letters"],
)
for index, share in enumerate(shares):
enter_share(debug, share, is_first=index == 0)
if index < len(shares) - 1:
TR.assert_in(
debug.read_layout().text_content(),
"recovery__x_of_y_entered_template",
template=(index + 1, len(shares)),
)
# FIXME: when ui-t3t1 done for shamir, we want to check the template below
TR.assert_in(debug.read_layout().title(), "recovery__title_recover")
# TR.assert_in(
# debug.read_layout().text_content(),
# "recovery__x_of_y_entered_template",
# template=(index + 1, len(shares)),
# )
TR.assert_in(debug.read_layout().text_content(), "recovery__wallet_recovered")
@ -188,13 +206,19 @@ def enter_seed_previous_correct(
i += 1
layout = enter_word(debug, word, is_slip39=False)
TR.assert_in(debug.read_layout().text_content(), "recovery__wallet_recovered")
# TR.assert_in(debug.read_layout().text_content(), "recovery__wallet_recovered")
def prepare_enter_seed(debug: "DebugLink") -> None:
TR.assert_in(debug.read_layout().text_content(), "recovery__enter_backup")
if debug.model in (models.T2T1, models.T3T1):
TR.assert_in_multiple(
debug.read_layout().text_content(),
["recovery__enter_backup", "recovery__only_first_n_letters"],
)
if debug.model in (models.T2T1,):
debug.click(buttons.OK, wait=True)
elif debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
debug.swipe_up(wait=True)
elif debug.model in (models.T2B1,):
debug.press_right(wait=True)
TR.assert_equals(debug.read_layout().title(), "recovery__title_recover")

View File

@ -14,16 +14,22 @@ if TYPE_CHECKING:
def confirm_new_wallet(debug: "DebugLink") -> None:
TR.assert_equals(debug.read_layout().title(), "reset__title_create_wallet")
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click(buttons.OK, wait=True)
elif debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
debug.click(buttons.TAP_TO_CONFIRM, wait=True)
debug.swipe_up(wait=True) # Wallet created
elif debug.model in (models.T2B1,):
debug.press_right(wait=True)
debug.press_right(wait=True)
def confirm_read(debug: "DebugLink", middle_r: bool = False) -> None:
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click(buttons.OK, wait=True)
elif debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
elif debug.model in (models.T2B1,):
page_count = debug.read_layout().page_count()
if page_count > 1:
@ -36,18 +42,28 @@ def confirm_read(debug: "DebugLink", middle_r: bool = False) -> None:
def cancel_backup(debug: "DebugLink", middle_r: bool = False) -> None:
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click(buttons.CANCEL, wait=True)
debug.click(buttons.CANCEL, wait=True)
elif debug.model in (models.T3T1,):
debug.click(buttons.CORNER_BUTTON, wait=True)
debug.click(buttons.VERTICAL_MENU[0], wait=True)
debug.swipe_up(wait=True)
debug.click(buttons.TAP_TO_CONFIRM)
elif debug.model in (models.T2B1,):
debug.press_left(wait=True)
debug.press_left(wait=True)
def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> None:
if debug.model in (models.T2T1, models.T3T1):
assert "NumberInputDialog" in debug.read_layout().all_components()
for _ in range(diff):
debug.click(button)
debug.click(buttons.OK, wait=True)
debug.click(button, wait=True)
if debug.model in (models.T2T1,):
debug.click(buttons.OK, wait=True)
else:
debug.swipe_up(wait=True)
elif debug.model in (models.T2B1,):
layout = debug.read_layout()
if layout.title() in TR.translate(
@ -56,7 +72,7 @@ def set_selection(debug: "DebugLink", button: tuple[int, int], diff: int) -> Non
# Special info screens
layout = debug.press_right(wait=True)
assert "NumberInput" in layout.all_components()
if button == buttons.RESET_MINUS:
if button == buttons.reset_minus(debug.model.internal_name):
for _ in range(diff):
debug.press_left(wait=True)
else:
@ -72,6 +88,8 @@ def read_words(
if debug.model in (models.T2B1,):
debug.press_right(wait=True)
elif debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
# Swiping through all the pages and loading the words
layout = debug.read_layout()
@ -82,10 +100,15 @@ def read_words(
if debug.model in (models.T2T1, models.T3T1):
words.extend(layout.seed_words())
if debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
# There is hold-to-confirm button
if do_htc:
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click_hold(buttons.OK, hold_ms=1500)
elif debug.model in (models.T3T1,):
debug.click_hold(buttons.TAP_TO_CONFIRM, hold_ms=1500)
elif debug.model in (models.T2B1,):
debug.press_right_htc(1200)
else:
@ -97,6 +120,9 @@ def read_words(
def confirm_words(debug: "DebugLink", words: list[str]) -> None:
if debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
layout = debug.wait_layout()
if debug.model in (models.T2T1, models.T3T1):
TR.assert_template(layout.text_content(), "reset__select_word_x_of_y_template")
@ -112,7 +138,11 @@ 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], wait=True)
if debug.model is models.T3T1:
btn_positions = buttons.VERTICAL_MENU
else:
btn_positions = buttons.RESET_WORD_CHECK
layout = debug.click(btn_positions[button_pos], wait=True)
elif debug.model in (models.T2B1,):
TR.assert_in(layout.text_content(), "reset__select_correct_word")
layout = debug.press_right(wait=True)

View File

@ -102,11 +102,16 @@ def test_autolock_interrupts_signing(device_handler: "BackgroundDeviceHandler"):
in debug.wait_layout().text_content().replace(" ", "")
)
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click(buttons.OK, wait=True)
layout = debug.click(buttons.OK, wait=True)
TR.assert_in(layout.text_content(), "send__total_amount")
assert "0.0039 BTC" in layout.text_content()
elif debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
layout = debug.swipe_up(wait=True)
TR.assert_in(layout.text_content(), "send__total_amount")
assert "0.0039 BTC" in layout.text_content()
elif debug.model in (models.T2B1,):
debug.press_right(wait=True)
layout = debug.press_right(wait=True)
@ -149,11 +154,17 @@ def test_autolock_does_not_interrupt_signing(device_handler: "BackgroundDeviceHa
in debug.wait_layout().text_content().replace(" ", "")
)
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click(buttons.OK, wait=True)
layout = debug.click(buttons.OK, wait=True)
TR.assert_in(layout.text_content(), "send__total_amount")
assert "0.0039 BTC" in layout.text_content()
elif debug.model in (models.T3T1,):
debug.swipe_up(wait=True)
layout = debug.swipe_up(wait=True)
TR.assert_in(layout.text_content(), "send__total_amount")
assert "0.0039 BTC" in layout.text_content()
debug.swipe_up(wait=True)
elif debug.model in (models.T2B1,):
debug.press_right(wait=True)
layout = debug.press_right(wait=True)
@ -168,8 +179,10 @@ 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.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click(buttons.OK)
elif debug.model in (models.T3T1,):
debug.click(buttons.TAP_TO_CONFIRM)
elif debug.model in (models.T2B1,):
debug.press_middle()
@ -197,9 +210,12 @@ def test_autolock_passphrase_keyboard(device_handler: "BackgroundDeviceHandler")
# enter passphrase - slowly
# keep clicking for long enough to trigger the autolock if it incorrectly ignored key presses
for _ in range(math.ceil(11 / 1.5)):
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
# click at "j"
debug.click(CENTER_BUTTON)
elif debug.model in (models.T3T1,):
# click at "j"
debug.click((20, 120))
elif debug.model in (models.T2B1,):
# just go right
# NOTE: because of passphrase randomization it would be a pain to input
@ -208,8 +224,10 @@ def test_autolock_passphrase_keyboard(device_handler: "BackgroundDeviceHandler")
time.sleep(1.5)
# Send the passphrase to the client (TT has it clicked already, TR needs to input it)
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
debug.click(buttons.OK, wait=True)
elif debug.model in (models.T3T1,):
debug.click(buttons.CORNER_BUTTON, wait=True)
elif debug.model in (models.T2B1,):
debug.input("j" * 8, wait=True)
@ -327,10 +345,21 @@ def test_dryrun_enter_word_slowly(device_handler: "BackgroundDeviceHandler"):
# select 20 words
recovery.select_number_of_words(debug, 20)
if debug.model in (models.T2T1, models.T3T1):
if debug.model in (models.T2T1,):
layout = debug.click(buttons.OK, wait=True)
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, wait=True)
# should not have locked, even though we took 9 seconds to type each letter
assert layout.main_component() == "MnemonicKeyboard"
elif debug.model in (models.T3T1,):
layout = debug.swipe_up(wait=True)
assert layout.main_component() == "MnemonicKeyboard"
# type the word OCEAN slowly
for coords in buttons.type_word("ocea", is_slip39=True):
time.sleep(9)

View File

@ -64,9 +64,6 @@ def test_backup_slip39_custom(
# cancel back up
reset.cancel_backup(debug)
# confirm cancel
reset.cancel_backup(debug)
assert device_handler.result() == "Initialized"
device_handler.run(

View File

@ -0,0 +1,327 @@
# This file is part of the Trezor project.
#
# Copyright (C) 2012-2023 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
import time
from contextlib import contextmanager
from typing import TYPE_CHECKING, Generator, Optional, Tuple
import pytest
from trezorlib import exceptions
from .. import buttons
from ..common import get_test_address
from .common import CommonPass, PassphraseCategory, get_char_category
if TYPE_CHECKING:
from trezorlib.debuglink import DebugLink
from ..device_handler import BackgroundDeviceHandler
pytestmark = [pytest.mark.skip_t1b1, pytest.mark.skip_t2b1, pytest.mark.skip_t2t1]
PASSPHRASE_CANCELLED = pytest.raises(exceptions.Cancelled, match="")
MERCURY_CATEGORIES = [
PassphraseCategory.LOWERCASE,
PassphraseCategory.UPPERCASE,
PassphraseCategory.DIGITS,
PassphraseCategory.SPECIAL,
]
# fmt: off
PASSPHRASE_LOWERCASE = ("abc", "def", "ghi", "jkl", "mno", "pq", "rst", "uvw", "xyz", " *#")
PASSPHRASE_UPPERCASE = ("ABC", "DEF", "GHI", "JKL", "MNO", "PQ", "RST", "UVW", "XYZ", " *#")
PASSPHRASE_DIGITS = ("1", "2", "3", "4", "5", "6", "7", "8", "9", "0")
PASSPHRASE_SPECIAL = ("_<>", ".:@", "/|\\", "!()", "+%&", "-[]", "?{}", ",'`", ";\"~", "$^=")
# fmt: on
# TODO: better read this from the trace
MERCURY_CATEGORY = PassphraseCategory.LOWERCASE
MERCURY_COORDS_PREV: buttons.Coords = (0, 0)
# Testing the maximum length is really 50
DA_50 = 25 * "da"
DA_50_ADDRESS = "mg5L2i8HZKUvceK1sfmGHhE4gichFSsdvm"
assert len(DA_50) == 50
DA_49 = DA_50[:-1]
DA_49_ADDRESS = "mxrB75ydMS3ZzqmYKK28fj4bNMEx7dDw6e"
assert len(DA_49) == 49
assert DA_49_ADDRESS != DA_50_ADDRESS
DA_51 = DA_50 + "d"
DA_51_ADDRESS = DA_50_ADDRESS
assert len(DA_51) == 51
assert DA_51_ADDRESS == DA_50_ADDRESS
def get_passphrase_choices(char: str) -> tuple[str, ...]:
if char in " *#":
return PASSPHRASE_LOWERCASE
if char.islower():
return PASSPHRASE_LOWERCASE
elif char.isupper():
return PASSPHRASE_UPPERCASE
elif char.isdigit():
return PASSPHRASE_DIGITS
else:
return PASSPHRASE_SPECIAL
def passphrase(char: str) -> Tuple[buttons.Coords, int]:
choices = get_passphrase_choices(char)
idx = next(i for i, letters in enumerate(choices) if char in letters)
click_amount = choices[idx].index(char) + 1
return buttons.pin_passphrase_index(idx), click_amount
@contextmanager
def prepare_passphrase_dialogue(
device_handler: "BackgroundDeviceHandler", address: Optional[str] = None
) -> Generator["DebugLink", None, None]:
debug = device_handler.debuglink()
device_handler.run(get_test_address) # type: ignore
assert debug.wait_layout().main_component() == "PassphraseKeyboard"
# Resetting the category as it could have been changed by previous tests
global MERCURY_CATEGORY
MERCURY_CATEGORY = PassphraseCategory.LOWERCASE # type: ignore
yield debug
result = device_handler.result()
if address is not None:
assert result == address
def go_to_category(debug: "DebugLink", category: PassphraseCategory) -> None:
"""Go to a specific category"""
global MERCURY_CATEGORY
global MERCURY_COORDS_PREV
# Already there
if MERCURY_CATEGORY == category:
return
current_index = MERCURY_CATEGORIES.index(MERCURY_CATEGORY)
target_index = MERCURY_CATEGORIES.index(category)
if target_index > current_index:
for _ in range(target_index - current_index):
debug.swipe_left(wait=True)
else:
for _ in range(current_index - target_index):
debug.swipe_right(wait=True)
MERCURY_CATEGORY = category # type: ignore
# Category changed, reset coordinates
MERCURY_COORDS_PREV = (0, 0) # type: ignore
def press_char(debug: "DebugLink", char: str) -> None:
"""Press a character"""
global MERCURY_COORDS_PREV
# Space and couple others are a special case
if char in " *#":
char_category = PassphraseCategory.LOWERCASE
else:
char_category = get_char_category(char)
go_to_category(debug, char_category)
coords, amount = passphrase(char)
# If the button is the same as for the previous char,
# waiting a second before pressing it again.
if coords == MERCURY_COORDS_PREV:
time.sleep(1.1)
MERCURY_COORDS_PREV = coords # type: ignore
for _ in range(amount):
debug.click(coords, wait=True)
def input_passphrase(debug: "DebugLink", passphrase: str, check: bool = True) -> None:
"""Input a passphrase with validation it got added"""
if check:
before = debug.read_layout().passphrase()
for char in passphrase:
press_char(debug, char)
if check:
after = debug.read_layout().passphrase()
assert after == before + passphrase
def enter_passphrase(debug: "DebugLink") -> None:
"""Enter a passphrase"""
coords = buttons.grid35(2, 0) # top-right corner
debug.click(coords, wait=True)
def delete_char(debug: "DebugLink") -> None:
"""Deletes the last char"""
coords = buttons.pin_passphrase_grid(9)
debug.click(coords, wait=True)
VECTORS = ( # passphrase, address
(CommonPass.SHORT, CommonPass.SHORT_ADDRESS),
(CommonPass.WITH_SPACE, CommonPass.WITH_SPACE_ADDRESS),
(CommonPass.RANDOM_25, CommonPass.RANDOM_25_ADDRESS),
(DA_49, DA_49_ADDRESS),
(DA_50, DA_50_ADDRESS),
)
@pytest.mark.parametrize("passphrase, address", VECTORS)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_input(
device_handler: "BackgroundDeviceHandler", passphrase: str, address: str
):
with prepare_passphrase_dialogue(device_handler, address) as debug:
input_passphrase(debug, passphrase)
enter_passphrase(debug)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_input_over_50_chars(device_handler: "BackgroundDeviceHandler"):
with prepare_passphrase_dialogue(device_handler, DA_51_ADDRESS) as debug: # type: ignore
input_passphrase(debug, DA_51, check=False)
assert debug.read_layout().passphrase() == DA_50
enter_passphrase(debug)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_delete(device_handler: "BackgroundDeviceHandler"):
with prepare_passphrase_dialogue(device_handler, CommonPass.SHORT_ADDRESS) as debug:
input_passphrase(debug, CommonPass.SHORT[:8])
for _ in range(4):
delete_char(debug)
debug.wait_layout()
input_passphrase(debug, CommonPass.SHORT[8 - 4 :])
enter_passphrase(debug)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_delete_all(
device_handler: "BackgroundDeviceHandler",
):
with prepare_passphrase_dialogue(device_handler, CommonPass.EMPTY_ADDRESS) as debug:
passphrase = "trezor"
input_passphrase(debug, passphrase)
for _ in range(len(passphrase)):
delete_char(debug)
enter_passphrase(debug)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_loop_all_characters(device_handler: "BackgroundDeviceHandler"):
with prepare_passphrase_dialogue(device_handler, CommonPass.EMPTY_ADDRESS) as debug:
for category in (
PassphraseCategory.DIGITS,
PassphraseCategory.LOWERCASE,
PassphraseCategory.UPPERCASE,
PassphraseCategory.SPECIAL,
):
go_to_category(debug, category)
debug.wait_layout()
enter_passphrase(debug)
coords = buttons.pin_passphrase_grid(11)
debug.click(coords)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_click_same_button_many_times(
device_handler: "BackgroundDeviceHandler",
):
with prepare_passphrase_dialogue(device_handler) as debug:
a_coords, _ = buttons.passphrase("a")
for _ in range(10):
debug.click(a_coords)
enter_passphrase(debug)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_prompt_disappears(
device_handler: "BackgroundDeviceHandler",
):
with prepare_passphrase_dialogue(device_handler) as debug:
input_passphrase(debug, "a")
# Wait a second for the prompt to disappear
time.sleep(1.1)
enter_passphrase(debug)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_long_spaces_deletion(
device_handler: "BackgroundDeviceHandler",
):
with prepare_passphrase_dialogue(device_handler) as debug:
input_passphrase(
debug,
"a"
+ " " * 7
+ "b"
+ " " * 7
+ "c"
+ " " * 7
+ "d"
+ " " * 7
+ "e"
+ " " * 7
+ "f",
)
for _ in range(12):
delete_char(debug)
enter_passphrase(debug)
@pytest.mark.setup_client(passphrase=True)
def test_passphrase_dollar_sign_deletion(
device_handler: "BackgroundDeviceHandler",
):
# Checks that dollar signs will not leave one pixel on the top after deleting
# (was a bug previously on model T)
with prepare_passphrase_dialogue(device_handler, CommonPass.EMPTY_ADDRESS) as debug:
passphrase = "$$ I want $$"
input_passphrase(debug, passphrase)
for _ in range(len(passphrase)):
delete_char(debug)
enter_passphrase(debug)
@pytest.mark.setup_client(passphrase=True)
def test_cycle_through_last_character(
device_handler: "BackgroundDeviceHandler",
):
# Checks that we can cycle through the last (50th) passphrase character
# (was a bug previously)
with prepare_passphrase_dialogue(device_handler) as debug:
passphrase = DA_49 + "i" # for i we need to cycle through "ghi" three times
input_passphrase(debug, passphrase)
enter_passphrase(debug)

View File

@ -30,7 +30,7 @@ if TYPE_CHECKING:
from ..device_handler import BackgroundDeviceHandler
pytestmark = [pytest.mark.skip_t1b1, pytest.mark.skip_t2b1]
pytestmark = [pytest.mark.skip_t1b1, pytest.mark.skip_t2b1, pytest.mark.skip_t3t1]
# TODO: it is not possible to cancel the passphrase entry on TT
# NOTE: the prompt (underscoring) is not there when a space is entered

View File

@ -68,10 +68,12 @@ def test_reset_slip39_advanced(
reset.confirm_read(debug)
# set num of groups - default is 5
assert debug.model is not None
model_name: str = debug.model.internal_name
if group_count < 5:
reset.set_selection(debug, buttons.RESET_MINUS, 5 - group_count)
reset.set_selection(debug, buttons.reset_minus(model_name), 5 - group_count)
else:
reset.set_selection(debug, buttons.RESET_PLUS, group_count - 5)
reset.set_selection(debug, buttons.reset_plus(model_name), group_count - 5)
# confirm checklist
reset.confirm_read(debug)
@ -79,9 +81,9 @@ def test_reset_slip39_advanced(
# set group threshold
# TODO: could make it general as well
if group_count == 2 and group_threshold == 2:
reset.set_selection(debug, buttons.RESET_PLUS, 0)
reset.set_selection(debug, buttons.reset_plus(model_name), 0)
elif group_count == 16 and group_threshold == 16:
reset.set_selection(debug, buttons.RESET_PLUS, 11)
reset.set_selection(debug, buttons.reset_plus(model_name), 11)
else:
raise RuntimeError("not a supported combination")
@ -92,16 +94,16 @@ def test_reset_slip39_advanced(
for _ in range(group_count):
# set num of shares - default is 5
if share_count < 5:
reset.set_selection(debug, buttons.RESET_MINUS, 5 - share_count)
reset.set_selection(debug, buttons.reset_minus(model_name), 5 - share_count)
else:
reset.set_selection(debug, buttons.RESET_PLUS, share_count - 5)
reset.set_selection(debug, buttons.reset_plus(model_name), share_count - 5)
# set share threshold
# TODO: could make it general as well
if share_count == 2 and share_threshold == 2:
reset.set_selection(debug, buttons.RESET_PLUS, 0)
reset.set_selection(debug, buttons.reset_plus(model_name), 0)
elif share_count == 16 and share_threshold == 16:
reset.set_selection(debug, buttons.RESET_PLUS, 11)
reset.set_selection(debug, buttons.reset_plus(model_name), 11)
else:
raise RuntimeError("not a supported combination")

View File

@ -65,10 +65,12 @@ def test_reset_slip39_basic(
reset.confirm_read(debug)
# set num of shares - default is 5
assert debug.model is not None
model_name: str = debug.model.internal_name
if num_of_shares < 5:
reset.set_selection(debug, buttons.RESET_MINUS, 5 - num_of_shares)
reset.set_selection(debug, buttons.reset_minus(model_name), 5 - num_of_shares)
else:
reset.set_selection(debug, buttons.RESET_PLUS, num_of_shares - 5)
reset.set_selection(debug, buttons.reset_plus(model_name), num_of_shares - 5)
# confirm checklist
reset.confirm_read(debug)
@ -76,9 +78,9 @@ def test_reset_slip39_basic(
# set threshold
# TODO: could make it general as well
if num_of_shares == 1 and threshold == 1:
reset.set_selection(debug, buttons.RESET_PLUS, 0)
reset.set_selection(debug, buttons.reset_plus(model_name), 0)
elif num_of_shares == 16 and threshold == 16:
reset.set_selection(debug, buttons.RESET_PLUS, 11)
reset.set_selection(debug, buttons.reset_plus(model_name), 11)
else:
raise RuntimeError("not a supported combination")

View File

@ -25,6 +25,8 @@ import pytest
from trezorlib import btc, messages, models, tools
from . import buttons
if TYPE_CHECKING:
from _pytest.mark.structures import MarkDecorator
@ -255,12 +257,16 @@ def read_mnemonic_from_screen_mercury(
assert br.pages is not None
debug.wait_layout()
debug.swipe_up()
for _ in range(br.pages):
for _ in range(br.pages - 2):
words = debug.wait_layout().seed_words()
mnemonic.extend(words)
debug.swipe_up()
debug.wait_layout()
debug.press_yes()
return mnemonic
@ -298,6 +304,15 @@ def click_info_button_tt(debug: "DebugLink"):
debug.press_yes()
def click_info_button_mercury(debug: "DebugLink"):
"""Click Shamir backup info button and return back."""
debug.click(buttons.CORNER_BUTTON, wait=True)
debug.synchronize_at("VerticalMenu")
debug.click(buttons.VERTICAL_MENU[0], wait=True)
debug.click(buttons.CORNER_BUTTON, wait=True)
debug.click(buttons.CORNER_BUTTON)
def check_pin_backoff_time(attempts: int, start: float) -> None:
"""Helper to assert the exponentially growing delay after incorrect PIN attempts"""
expected = (2**attempts) - 1

View File

@ -21,6 +21,7 @@ from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.exceptions import TrezorFailure
from ...common import parametrize_using_common_fixtures
from ...input_flows import InputFlowConfirmAllWarnings
pytestmark = [
pytest.mark.altcoin,
@ -53,7 +54,11 @@ def show_details_input_flow(client: Client):
"cardano/sign_tx.slip39.json",
)
def test_cardano_sign_tx(client: Client, parameters, result):
response = call_sign_tx(client, parameters)
response = call_sign_tx(
client,
parameters,
input_flow=lambda client: InputFlowConfirmAllWarnings(client).get(),
)
assert response == _transform_expected_result(result)

View File

@ -73,6 +73,7 @@ def test_secret(client: Client, shares: list[str], secret: str):
_test_secret(client, shares, secret)
@pytest.mark.skip_t3t1(reason="currently broken on T3T1")
@pytest.mark.parametrize("shares, secret", VECTORS)
@pytest.mark.setup_client(uninitialized=True)
def test_secret_click_info_button(client: Client, shares: list[str], secret: str):

View File

@ -22,6 +22,8 @@ from trezorlib import btc, device, exceptions, messages, misc, models
from trezorlib.debuglink import TrezorClientDebugLink as Client
from trezorlib.tools import parse_path
from ..input_flows import InputFlowConfirmAllWarnings
HERE = Path(__file__).parent.resolve()
@ -364,6 +366,8 @@ def test_safety_checks(client: Client):
client.set_expected_responses(
[messages.ButtonRequest, messages.ButtonRequest, messages.Address]
)
IF = InputFlowConfirmAllWarnings(client)
client.set_input_flow(IF.get())
get_bad_address()
with client:
@ -388,6 +392,9 @@ def test_safety_checks(client: Client):
client.set_expected_responses(
[messages.ButtonRequest, messages.ButtonRequest, messages.Address]
)
if client.model is not models.T1B1:
IF = InputFlowConfirmAllWarnings(client)
client.set_input_flow(IF.get())
get_bad_address()

View File

@ -24,6 +24,7 @@ from . import translations as TR
from .common import (
BRGeneratorType,
check_pin_backoff_time,
click_info_button_mercury,
click_info_button_tt,
click_through,
get_text_possible_pagination,
@ -1307,7 +1308,7 @@ class InputFlowBip39ResetBackup(InputFlowBase):
# 1. Confirm Reset x3
# 2. Backup your seed
# 3. Confirm warning
yield from click_through(self.debug, screens=3, code=B.ResetDevice)
yield from click_through(self.debug, screens=4, code=B.ResetDevice)
# mnemonic phrases and rest
self.mnemonic = yield from get_mnemonic_and_confirm_success(self.debug)
@ -1329,6 +1330,11 @@ class InputFlowBip39ResetPIN(InputFlowBase):
assert br.code == B.ResetDevice
self.debug.press_yes()
if self.debug.model is models.T3T1:
br = yield # Wallet created
assert br.code == B.ResetDevice
self.debug.press_yes()
br = yield # Backup your seed
assert br.code == B.ResetDevice
self.debug.press_yes()
@ -1355,10 +1361,12 @@ class InputFlowBip39ResetFailedCheck(InputFlowBase):
self.mnemonic = None
def input_flow_common(self) -> BRGeneratorType:
screens = 4 if self.debug.model is models.T3T1 else 3
# 1. Confirm Reset
# 1a. (T3T1) done
# 2. Backup your seed
# 3. Confirm warning
yield from click_through(self.debug, screens=3, code=B.ResetDevice)
yield from click_through(self.debug, screens=screens, code=B.ResetDevice)
# mnemonic phrases, wrong answer
self.mnemonic = yield from read_and_confirm_mnemonic(
@ -1458,21 +1466,22 @@ class InputFlowSlip39BasicBackup(InputFlowBase):
def input_flow_t3t1(self) -> BRGeneratorType:
yield # 1. Checklist
self.debug.press_yes()
self.debug.wait_layout()
self.debug.swipe_up(wait=True)
if self.click_info:
yield from click_info_button_tt(self.debug)
click_info_button_mercury(self.debug)
yield # 2. Number of shares (5)
self.debug.press_yes()
self.debug.swipe_up()
yield # 3. Checklist
self.debug.press_yes()
self.debug.swipe_up(wait=True)
if self.click_info:
yield from click_info_button_tt(self.debug)
click_info_button_mercury(self.debug)
yield # 4. Threshold (3)
self.debug.press_yes()
self.debug.swipe_up()
yield # 5. Checklist
self.debug.press_yes()
self.debug.swipe_up()
yield # 6. Confirm show seeds
self.debug.press_yes()
self.debug.swipe_up()
# Mnemonic phrases
self.mnemonics = yield from load_N_shares(self.debug, 5)
@ -1543,7 +1552,7 @@ class InputFlowSlip39BasicResetRecovery(InputFlowBase):
# 6. threshold info
# 7. Set & confirm threshold value
# 8. Confirm show seeds
yield from click_through(self.debug, screens=8, code=B.ResetDevice)
yield from click_through(self.debug, screens=9, code=B.ResetDevice)
# Mnemonic phrases
self.mnemonics = yield from load_N_shares(self.debug, 5)
@ -1695,28 +1704,28 @@ class InputFlowSlip39AdvancedBackup(InputFlowBase):
def input_flow_t3t1(self) -> BRGeneratorType:
yield # 1. Checklist
self.debug.press_yes()
self.debug.swipe_up(wait=True)
if self.click_info:
yield from click_info_button_tt(self.debug)
click_info_button_mercury(self.debug)
yield # 2. Set and confirm group count
self.debug.press_yes()
self.debug.swipe_up()
yield # 3. Checklist
self.debug.press_yes()
self.debug.swipe_up(wait=True)
if self.click_info:
yield from click_info_button_tt(self.debug)
click_info_button_mercury(self.debug)
yield # 4. Set and confirm group threshold
self.debug.press_yes()
self.debug.swipe_up()
yield # 5. Checklist
self.debug.press_yes()
for _ in range(5): # for each of 5 groups
self.debug.swipe_up(wait=True)
for _i in range(5): # for each of 5 groups
if self.click_info:
yield from click_info_button_tt(self.debug)
click_info_button_mercury(self.debug)
yield # Set & Confirm number of shares
self.debug.press_yes()
self.debug.swipe_up(wait=True)
if self.click_info:
yield from click_info_button_tt(self.debug)
click_info_button_mercury(self.debug)
yield # Set & confirm share threshold value
self.debug.press_yes()
self.debug.swipe_up(wait=_i != 4)
yield # Confirm show seeds
self.debug.press_yes()
@ -1801,7 +1810,7 @@ class InputFlowSlip39AdvancedResetRecovery(InputFlowBase):
# 1. Set & Confirm number of shares
# 2. Set & confirm share threshold value
# 18. Confirm show seeds
yield from click_through(self.debug, screens=18, code=B.ResetDevice)
yield from click_through(self.debug, screens=19, code=B.ResetDevice)
# Mnemonic phrases - show & confirm shares for all groups
self.mnemonics = yield from load_5_groups_5_shares(self.debug)
@ -2119,22 +2128,38 @@ class InputFlowResetSkipBackup(InputFlowBase):
def __init__(self, client: Client):
super().__init__(client)
def input_flow_common(self) -> BRGeneratorType:
def input_flow_tt(self) -> BRGeneratorType:
yield from self.BAK.confirm_new_wallet()
yield # Skip Backup
info_path = (
"backup__new_wallet_created"
if self.model() is models.T2B1
else "backup__new_wallet_successfully_created"
)
TR.assert_in(self.text_content(), info_path)
if self.model() is models.T2B1:
self.debug.press_right()
TR.assert_in(self.text_content(), "backup__new_wallet_successfully_created")
self.debug.press_no()
yield # Confirm skip backup
TR.assert_in(self.text_content(), "backup__want_to_skip")
self.debug.press_no()
def input_flow_tr(self) -> BRGeneratorType:
yield from self.BAK.confirm_new_wallet()
yield # Skip Backup
TR.assert_in(self.text_content(), "backup__new_wallet_created")
self.debug.press_right()
self.debug.press_no()
yield # Confirm skip backup
TR.assert_in(self.text_content(), "backup__want_to_skip")
self.debug.press_no()
def input_flow_t3t1(self) -> BRGeneratorType:
yield from self.BAK.confirm_new_wallet()
yield # Skip Backup
TR.assert_in(self.text_content(), "backup__new_wallet_created")
self.debug.swipe_up()
yield
self.debug.click(buttons.CORNER_BUTTON)
self.debug.synchronize_at("VerticalMenu")
self.debug.click(buttons.VERTICAL_MENU[0])
self.debug.swipe_up()
self.debug.synchronize_at("PromptScreen")
self.debug.click(buttons.TAP_TO_CONFIRM)
class InputFlowConfirmAllWarnings(InputFlowBase):
def __init__(self, client: Client):
@ -2164,7 +2189,16 @@ class InputFlowConfirmAllWarnings(InputFlowBase):
layout = self.debug.read_layout()
text = layout.text_content().lower()
# hi priority warning
if ("wrong derivation path" in text) or ("to a multisig" in text):
hi_prio = (
TR.translate("addr_mismatch__wrong_derivation_path")
+ TR.translate("send__receiving_to_multisig")
+ [
"witness path",
"certificate path",
"pool owner staking path",
]
)
if any(needle.lower() in text for needle in hi_prio):
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.synchronize_at("VerticalMenu")
self.debug.click(buttons.VERTICAL_MENU[1])

View File

@ -1,6 +1,7 @@
from trezorlib import messages, models
from trezorlib.debuglink import TrezorClientDebugLink as Client
from . import buttons
from . import translations as TR
from .common import BRGeneratorType, get_text_possible_pagination
@ -51,7 +52,8 @@ class RecoveryFlow:
self.debug = self.client.debug
def _text_content(self) -> str:
return self.debug.wait_layout().text_content()
layout = self.debug.wait_layout()
return layout.title() + " " + layout.text_content()
def confirm_recovery(self) -> BRGeneratorType:
yield
@ -84,7 +86,10 @@ class RecoveryFlow:
def enter_your_backup(self) -> BRGeneratorType:
yield
TR.assert_in(self._text_content(), "recovery__enter_backup")
if self.debug.model is models.T3T1:
TR.assert_in(self._text_content(), "recovery__only_first_n_letters")
else:
TR.assert_in(self._text_content(), "recovery__enter_backup")
is_dry_run = any(
title in self.debug.wait_layout().title().lower()
for title in TR.translate("recovery__title_dry_run", lower=True)
@ -97,7 +102,10 @@ class RecoveryFlow:
def enter_any_share(self) -> BRGeneratorType:
yield
TR.assert_in(self._text_content(), "recovery__enter_any_share")
TR.assert_in_multiple(
self._text_content(),
["recovery__enter_any_share", "recovery__only_first_n_letters"],
)
is_dry_run = any(
title in self.debug.wait_layout().title().lower()
for title in TR.translate("recovery__title_dry_run", lower=True)
@ -112,6 +120,8 @@ class RecoveryFlow:
yield
if self.client.model is models.T2B1:
TR.assert_in(self._text_content(), "recovery__num_of_words")
elif self.client.model is models.T3T1:
TR.assert_in(self._text_content(), "recovery__only_first_n_letters")
else:
TR.assert_in(self._text_content(), "recovery__enter_any_share")
self.debug.press_no()
@ -242,8 +252,11 @@ class RecoveryFlow:
if index < len(shares) - 1:
if has_groups:
yield from self.success_share_group_entered()
if self.client.model in (models.T2T1, models.T3T1) and click_info:
yield from self.tt_click_info()
if click_info:
if self.client.model is models.T2T1:
yield from self.tt_click_info()
elif self.client.model is models.T3T1:
self.mercury_click_info()
yield from self.success_more_shares_needed()
def tt_click_info(
@ -255,6 +268,13 @@ class RecoveryFlow:
self.debug.swipe_up()
self.debug.press_yes()
def mercury_click_info(self):
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.synchronize_at("VerticalMenu")
self.debug.click(buttons.VERTICAL_MENU[0], wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True)
class EthereumFlow:
GO_BACK = (16, 220)
@ -392,7 +412,7 @@ class EthereumFlow:
if self.client.model in (models.T2T1, models.T3T1):
# confirm intro
if info:
self.debug.press_info(wait=True)
self.debug.click(buttons.CORNER_BUTTON, wait=True)
TR.assert_equals_multiple(
self.debug.wait_layout().title(),
[
@ -405,7 +425,7 @@ class EthereumFlow:
yield
# confirm summary
if info:
if info and self.client.model != models.T3T1:
self.debug.press_info(wait=True)
TR.assert_in(
self.debug.wait_layout().text_content(), "ethereum__gas_limit"

View File

@ -14,7 +14,9 @@
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
from trezorlib import device
import pytest
from trezorlib import device, models
from trezorlib.debuglink import DebugLink
from .. import buttons
@ -38,6 +40,9 @@ def test_abort(core_emulator: Emulator):
debug = device_handler.debuglink()
features = device_handler.features()
if debug.model is models.T3T1:
pytest.skip("abort not supported on T3T1")
assert features.recovery_mode is False
device_handler.run(device.recover, pin_protection=False)
@ -132,7 +137,7 @@ def test_recovery_on_old_wallet(core_emulator: Emulator):
words = first_share.split(" ")
# start entering first share
assert "Enter any share" in debug.read_layout().text_content()
assert "the first 2-4 letters" in debug.read_layout().text_content()
debug.press_yes()
assert debug.wait_layout().main_component() == "MnemonicKeyboard"
@ -151,7 +156,7 @@ def test_recovery_on_old_wallet(core_emulator: Emulator):
layout = debug.wait_layout()
# check that we entered the first share successfully
assert "1 of 3 shares entered" in layout.text_content()
assert "2 more shares needed" in layout.text_content()
# try entering the remaining shares
for share in MNEMONIC_SLIP39_BASIC_20_3of6[1:3]:
@ -170,7 +175,7 @@ def test_recovery_multiple_resets(core_emulator: Emulator):
def enter_shares_with_restarts(debug: DebugLink) -> None:
shares = MNEMONIC_SLIP39_ADVANCED_20
layout = debug.read_layout()
expected_text = "Enter any share"
expected_text = "the first 2-4 letters"
remaining = len(shares)
for share in shares:
assert expected_text in layout.text_content()

File diff suppressed because it is too large Load Diff