-
Notifications
You must be signed in to change notification settings - Fork 88
e2e appium/test settings password change password #18977
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
glitchminer
merged 2 commits into
master
from
e2e_appium/test_settings_password_change_password
Oct 21, 2025
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
test/e2e_appium/locators/settings/password_change_locators.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| from ..base_locators import BaseLocators | ||
|
|
||
|
|
||
| class PasswordChangeLocators(BaseLocators): | ||
| CURRENT_PASSWORD_CONTAINER = BaseLocators.content_desc_exact( | ||
| "Enter current password" | ||
| ) | ||
| CURRENT_PASSWORD_INPUT = BaseLocators.xpath( | ||
| "//*[contains(@resource-id, 'passwordViewCurrentPassword')]" | ||
| ) | ||
| NEW_PASSWORD_INPUT = BaseLocators.xpath( | ||
| "//*[contains(@resource-id, 'passwordViewNewPassword') and not(contains(@resource-id, 'Confirm'))]" | ||
| ) | ||
| CONFIRM_PASSWORD_INPUT = BaseLocators.xpath( | ||
| "//*[contains(@resource-id, 'passwordViewNewPasswordConfirm')]" | ||
| ) | ||
| CHANGE_PASSWORD_BUTTON = BaseLocators.xpath( | ||
| "//*[contains(@resource-id, 'changePasswordModalSubmitButton') or " | ||
| "contains(@content-desc, 'changePasswordModalSubmitButton')]" | ||
| ) | ||
|
|
||
|
|
||
| class ChangePasswordModalLocators(BaseLocators): | ||
| MODAL_CONTAINER = BaseLocators.xpath( | ||
| "//*[@resource-id='QGuiApplication.mainWindow.ConfirmChangePasswordModal']" | ||
| ) | ||
| PRIMARY_BUTTON = BaseLocators.xpath( | ||
| "//*[@resource-id='QGuiApplication.mainWindow.ConfirmChangePasswordModal']" | ||
| "//*[contains(@content-desc, 'tid:changePasswordModalSubmitButton')]" | ||
| ) | ||
| STATUS_MESSAGE = BaseLocators.xpath( | ||
| "//*[@resource-id='QGuiApplication.mainWindow.ConfirmChangePasswordModal']" | ||
| "//*[contains(@resource-id, 'statusListItemSubTitle')]" | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| import time | ||
|
|
||
| from ..base_page import BasePage | ||
| from locators.onboarding.welcome_back_screen_locators import WelcomeBackScreenLocators | ||
| from services.app_initialization_manager import AppInitializationManager | ||
| from utils.element_state_checker import ElementStateChecker | ||
|
|
||
|
|
||
| class WelcomeBackPage(BasePage): | ||
| def __init__(self, driver): | ||
| super().__init__(driver) | ||
| self.locators = WelcomeBackScreenLocators() | ||
|
|
||
| def is_welcome_back_screen_displayed(self, timeout: int = 5) -> bool: | ||
| checks = [ | ||
| self.locators.LOGIN_SCREEN, | ||
| self.locators.PASSWORD_INPUT, | ||
| self.locators.LOGIN_BUTTON, | ||
| ] | ||
|
|
||
| for locator in checks: | ||
| if not locator: | ||
| continue | ||
| element = self.find_element_safe(locator, timeout=timeout) | ||
| if element and ElementStateChecker.is_displayed(element): | ||
| return True | ||
|
|
||
| return False | ||
|
|
||
| def perform_login(self, password: str, timeout: int = 60) -> bool: | ||
| self._activate_screen_if_needed() | ||
|
|
||
| max_attempts = 2 | ||
| for attempt in range(1, max_attempts + 1): | ||
| if not self._focus_password_field(): | ||
| self.logger.error("Failed to focus password field on attempt %s", attempt) | ||
| return False | ||
|
|
||
| if not self.qt_safe_input( | ||
| self.locators.PASSWORD_INPUT, password, verify=False | ||
| ): | ||
| self.logger.error("Password input failed on attempt %s", attempt) | ||
| return False | ||
|
|
||
| try: | ||
| self.hide_keyboard() | ||
| except Exception: | ||
| pass | ||
|
|
||
| if not self._wait_for_button_enabled(timeout=10): | ||
| self.logger.error("Login button never enabled on attempt %s", attempt) | ||
| return False | ||
|
|
||
| if not self.safe_click(self.locators.LOGIN_BUTTON, timeout=10): | ||
| self.logger.error("Login button click failed on attempt %s", attempt) | ||
| return False | ||
|
|
||
| if self._wait_for_login_transition(timeout=10): | ||
| return True | ||
|
|
||
| if attempt < max_attempts: | ||
| self.logger.warning( | ||
| "Login attempt %s did not dismiss welcome back screen; retrying", | ||
| attempt, | ||
| ) | ||
| time.sleep(1.0) | ||
|
|
||
| self.logger.error("Welcome back screen persisted after login retries") | ||
| return False | ||
|
|
||
| def _activate_screen_if_needed(self) -> None: | ||
| try: | ||
| manager = AppInitializationManager(self.driver) | ||
| manager.perform_initial_activation(timeout=3) | ||
| except Exception: | ||
| try: | ||
| size = self.driver.get_window_size() | ||
| self.gestures.tap(size["width"] // 2, size["height"] // 2) | ||
| except Exception: | ||
| pass | ||
|
|
||
| def _focus_password_field(self, retries: int = 5, wait_between: float = 2.0) -> bool: | ||
| for attempt in range(retries): | ||
| overlay = self.find_element_safe( | ||
| self.locators.PASSWORD_INPUT_OVERLAY, timeout=2 | ||
| ) | ||
| if overlay and ElementStateChecker.is_displayed(overlay): | ||
| try: | ||
| rect = overlay.rect | ||
| tap_x = int(rect.get("x", 0) + rect.get("width", 0) * 0.25) | ||
| tap_y = int(rect.get("y", 0) + rect.get("height", 0) * 0.25) | ||
| if not self.gestures.tap(tap_x, tap_y): | ||
| self.gestures.double_tap(tap_x, tap_y) | ||
| except Exception: | ||
| self.logger.debug( | ||
| "Overlay tap failed on attempt %s", attempt + 1 | ||
| ) | ||
| time.sleep(wait_between) | ||
|
|
||
| field = self.find_element_safe(self.locators.PASSWORD_INPUT, timeout=3) | ||
| if not field: | ||
| time.sleep(wait_between) | ||
| continue | ||
|
|
||
| if not ElementStateChecker.is_displayed(field): | ||
| time.sleep(wait_between) | ||
| continue | ||
|
|
||
| if ElementStateChecker.is_focused(field): | ||
| return True | ||
|
|
||
| try: | ||
| rect = field.rect | ||
| tap_x = int(rect.get("x", 0) + rect.get("width", 0) * 0.25) | ||
| tap_y = int(rect.get("y", 0) + rect.get("height", 0) * 0.25) | ||
| if not self.gestures.tap(tap_x, tap_y): | ||
| self.gestures.double_tap(tap_x, tap_y) | ||
| except Exception: | ||
| self.logger.debug("Password field tap failed on attempt %s", attempt + 1) | ||
|
|
||
| time.sleep(wait_between) | ||
|
|
||
| refreshed = self.find_element_safe(self.locators.PASSWORD_INPUT, timeout=1) | ||
| if refreshed and ElementStateChecker.is_focused(refreshed): | ||
| return True | ||
|
|
||
| time.sleep(wait_between) | ||
|
|
||
| self.logger.warning("Unable to focus password input on welcome back screen") | ||
| return False | ||
|
|
||
| def _wait_for_button_enabled(self, timeout: int = 10) -> bool: | ||
| deadline = time.time() + timeout | ||
| while time.time() < deadline: | ||
| try: | ||
| button = self.find_element(self.locators.LOGIN_BUTTON, timeout=2) | ||
| if str(button.get_attribute("enabled")).lower() == "true": | ||
| return True | ||
| except Exception: | ||
| pass | ||
| time.sleep(0.3) | ||
| return False | ||
|
|
||
| def _wait_for_login_transition(self, timeout: int = 10) -> bool: | ||
| deadline = time.time() + timeout | ||
| while time.time() < deadline: | ||
| if not self.is_welcome_back_screen_displayed(timeout=1): | ||
| return True | ||
| time.sleep(0.5) | ||
| return False | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this manual positioning?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In some cases there was some overlay preventing the component we want being reached, clicking manually let us reach the right one. Also due to the way Qt components are exposed I haven't found the right locator for some of them yet.
I intend to revisit all instances of using coordinates to try and keep them to a minimum (I'll be updating qml where necessary to add ids as well).