Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions test/e2e_appium/locators/wallet/accounts_locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class WalletAccountsLocators(BaseLocators):
ACCOUNT_MENU_DELETE = BaseLocators.xpath(
"//*[@content-desc='Delete' or contains(@resource-id,'AccountMenu-DeleteAction')]"
)
ACCOUNT_MENU_EDIT = BaseLocators.xpath(
"//*[@content-desc='Edit' or contains(@resource-id,'AccountMenu-EditAction')]"
)
KEYCARD_POPUP = BaseLocators.xpath(
"//*[contains(@resource-id,'KeycardPopup')]"
)
Expand Down Expand Up @@ -47,8 +50,8 @@ class WalletAccountsLocators(BaseLocators):
ACCOUNT_NAME_INPUT = BaseLocators.content_desc_exact(
"Account name [tid:statusBaseInput]"
)
ADD_ACCOUNT_PRIMARY = BaseLocators.content_desc_exact(
"Add account [tid:AddAccountPopup-PrimaryButton]"
ADD_ACCOUNT_PRIMARY = BaseLocators.content_desc_contains(
"[tid:AddAccountPopup-PrimaryButton]"
)
EDIT_DERIVATION_BUTTON = BaseLocators.content_desc_exact(
"Edit [tid:AddAccountPopup-EditDerivationPath]"
Expand Down
34 changes: 34 additions & 0 deletions test/e2e_appium/pages/base_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,40 @@ def _verify_input_success(self, element, expected_text: str) -> bool:
except Exception:
return True

def _clear_input_field(self, locator: tuple, timeout: int = 5) -> bool:
"""Clear text from an input field using select-all + delete.

Uses Ctrl+A to select all text, then Backspace to delete.
More reliable than element.clear() for Qt/QML fields.

Args:
locator: Element locator tuple.
timeout: Timeout for finding the element.

Returns:
bool: True if clearing was attempted, False if element not found.
"""
from selenium.webdriver.common.keys import Keys

element = self.find_element_safe(locator, timeout=timeout)
if not element:
return False

try:
element.click()

# Select all with Ctrl+A, then delete (Ctrl is correct for Android)
actions = ActionChains(self.driver)
actions.key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL)
actions.send_keys(Keys.BACKSPACE)
actions.perform()

self.logger.debug("Cleared input field via Ctrl+A + Backspace")
return True
except Exception as exc:
self.logger.debug(f"Select-all clear failed: {exc}")
return False

def long_press_element(self, element, duration: int = 800) -> bool:
"""Perform long-press gesture on element to trigger context menu.

Expand Down
13 changes: 12 additions & 1 deletion test/e2e_appium/pages/wallet/add_edit_account_modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ def __init__(self, driver):
def is_displayed(self, timeout: Optional[int] = 10) -> bool:
return self.is_element_visible(self.locators.ADD_ACCOUNT_MODAL, timeout=timeout)

def set_name(self, name: str) -> bool:
def set_name(self, name: str, clear_existing: bool = False) -> bool:
"""Set account name in the modal.

Args:
name: The account name to set.
clear_existing: If True, clears existing text before typing (for edit flow).
If False, relies on qt_safe_input's native clear (for add flow).
"""
if clear_existing:
if not self._clear_input_field(self.locators.ACCOUNT_NAME_INPUT):
self.logger.error("Failed to clear existing account name")
return False
return self.qt_safe_input(self.locators.ACCOUNT_NAME_INPUT, name, verify=False)

def save_changes(self) -> bool:
Expand Down
50 changes: 50 additions & 0 deletions test/e2e_appium/pages/wallet/wallet_left_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ def account_rows(self) -> List:
self.logger.debug(f"account_rows lookup failed: {e}")
return []

def account_names(self) -> List[str]:
"""Extract account names from visible account rows."""
names: List[str] = []
for row in self.account_rows():
try:
desc = row.get_attribute("content-desc") or row.get_attribute("text") or ""
if desc:
name = desc.split(" [tid:", 1)[0]
if name:
names.append(name)
except Exception as e:
self.logger.debug(f"Failed to extract account name: {e}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we dont raise the expection but silence it to logger ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, it's just to avoid some flakiness for the time being. I didn't want to mark the whole thing expected fail while other more critical parts can still be checked.

return names

def wait_for_account_name(self, name: str, timeout: int = 10) -> bool:
return self.wait_for_condition(lambda: name in self.account_names(), timeout=timeout)

def long_press_row(self, index: int = -1, duration_ms: int = 800) -> bool:
rows = self.account_rows()
if not rows:
Expand All @@ -72,6 +89,39 @@ def open_context_menu_for_row(self, index: int = -1) -> bool:
return False
return self.is_element_visible(self.locators.ACCOUNT_CONTEXT_MENU, timeout=5)

def edit_account_via_menu(self, new_name: str, index: int = -1) -> bool:
"""Edit account name via context menu.

Args:
new_name: New name to set for the account.
index: Index of the account row to edit (-1 for last).

Returns:
bool: True if edit succeeded.
"""
if not self.open_context_menu_for_row(index=index):
self.logger.error("Failed to open account context menu via long-press")
return False

self.safe_click(self.locators.ACCOUNT_MENU_EDIT, timeout=5)

modal = AddEditAccountModal(self.driver)
if not modal.is_displayed(timeout=10):
self.logger.error("Edit account modal did not appear")
return False

if not modal.set_name(new_name, clear_existing=True):
self.logger.error(f"Failed to set account name to '{new_name}'")
return False

modal.save_changes()

if not modal.wait_until_hidden(timeout=10):
self.logger.error("Edit account modal did not close after saving")
return False

return True

def delete_latest_account_via_menu(self, auth_password: Optional[str] = None) -> bool:
if not self.open_context_menu_for_row(index=-1):
self.logger.error("Failed to open account context menu via long-press")
Expand Down
13 changes: 12 additions & 1 deletion test/e2e_appium/tests/test_wallet_accounts_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,21 @@ async def test_add_and_delete_generated_account(self):
f"Before: {before}, After: {after_add}"
)

async with self.step(self.device, "Rename account via context menu"):
renamed_name = generate_account_name(16)
assert panel.edit_account_via_menu(
renamed_name, index=-1
), f"Failed to rename account '{name}' via context menu"

async with self.step(self.device, "Verify account renamed"):
assert panel.wait_for_account_name(renamed_name, timeout=10), (
f"Renamed account '{renamed_name}' not visible in account list"
)

async with self.step(self.device, "Delete account"):
assert panel.delete_latest_account_via_menu(
auth_password=user_password
), f"Failed to delete generated account '{name}' via context menu"
), f"Failed to delete generated account '{renamed_name}' via context menu"

async with self.step(self.device, "Verify account deleted"):
toast = app.wait_for_toast(
Expand Down