diff --git a/test/ui-test/src/common/Common.py b/test/ui-test/src/common/Common.py index 8943c0e93c..f8e5f1dd22 100644 --- a/test/ui-test/src/common/Common.py +++ b/test/ui-test/src/common/Common.py @@ -14,3 +14,6 @@ def input_text(text: str, objName: str): def object_not_enabled(objName: str): verify_object_enabled(objName, 500, False) + +def str_to_bool(string: str): + return string.lower() in ["yes", "true", "1", "y", "enabled"] diff --git a/test/ui-test/src/drivers/SquishDriver.py b/test/ui-test/src/drivers/SquishDriver.py index a95ac774fc..5333ede917 100755 --- a/test/ui-test/src/drivers/SquishDriver.py +++ b/test/ui-test/src/drivers/SquishDriver.py @@ -86,11 +86,7 @@ def get_child(obj, child_index=None): # It executes the click action into the given object: def click_obj(obj): - try: - squish.mouseClick(obj, squish.Qt.LeftButton) - return True - except LookupError: - return False + squish.mouseClick(obj, squish.Qt.LeftButton) # It executes the right-click action into the given object: def right_click_obj(obj): @@ -104,8 +100,8 @@ def get_obj(objName: str): obj = squish.findObject(getattr(names, objName)) return obj -def wait_and_get_obj(objName: str): - obj = squish.waitForObject(getattr(names, objName)) +def wait_and_get_obj(objName: str, timeout: int=_MAX_WAIT_OBJ_TIMEOUT): + obj = squish.waitForObject(getattr(names, objName), timeout) return obj def get_and_click_obj(obj_name: str): @@ -318,7 +314,28 @@ def verify_not_found(realNameVarName: str, message: str, timeoutMSec: int = 500) test.fail(message, f'Unexpected: the object "{realNameVarName}" was found.') except LookupError as err: test.passes(message, f'Expected: the object "{realNameVarName}" was not found. Exception: {str(err)}.') - -def grabScreenshot_and_save(obj, imageName:str, delay:int = 0): - img = object.grabScreenshot(obj, {"delay": delay}) + +def grabScreenshot_and_save(obj, imageName:str, delay:int = 0): + img = object.grabScreenshot(obj, {"delay": delay}) img.save(_SEARCH_IMAGES_PATH + imageName + ".png") + +def wait_for_prop_value(object, propertyName, value, timeoutMSec: int = 2000): + start = time.time() + while(start + timeoutMSec / 1000 > time.time()): + propertyNames = propertyName.split('.') + objThenVal = object + for name in propertyNames: + objThenVal = getattr(objThenVal, name) + if objThenVal == value: + return + squish.snooze(0.1) + raise Exception(f'Failed to match value "{value}" for property "{propertyName}" before timeout {timeoutMSec}ms; actual value: "{objThenVal}"') + +def get_child_item_with_object_name(item, objectName: str): + for index in range(item.components.count()): + if item.components.at(index).objectName == objectName: + return item.components.at(index) + raise Exception("Could not find child component with object name '{}'".format(objectName)) + +def sleep_test(seconds: float): + squish.snooze(seconds) diff --git a/test/ui-test/src/screens/StatusWalletScreen.py b/test/ui-test/src/screens/StatusWalletScreen.py index 52c0809d21..03cd729de1 100644 --- a/test/ui-test/src/screens/StatusWalletScreen.py +++ b/test/ui-test/src/screens/StatusWalletScreen.py @@ -4,7 +4,7 @@ import sys from drivers.SquishDriver import * from drivers.SquishDriverVerification import * from common.SeedUtils import * - +from .StatusMainScreen import StatusMainScreen class SigningPhrasePopUp(Enum): OK_GOT_IT_BUTTON: str = "signPhrase_Ok_Button" @@ -30,7 +30,9 @@ class SavedAddressesScreen(Enum): EDIT: str = "mainWallet_Saved_Addreses_More_Edit" DELETE: str = "mainWallet_Saved_Addreses_More_Delete" CONFIRM_DELETE: str = "mainWallet_Saved_Addreses_More_Confirm_Delete" - + DELEGATE_MENU_BUTTON_OBJECT_NAME: str = "savedAddressView_Delegate_menuButton" + DELEGATE_FAVOURITE_BUTTON_OBJECT_NAME: str = "savedAddressView_Delegate_favouriteButton" + class AddSavedAddressPopup(Enum): NAME_INPUT: str = "mainWallet_Saved_Addreses_Popup_Name_Input" ADDRESS_INPUT: str = "mainWallet_Saved_Addreses_Popup_Address_Input" @@ -191,40 +193,56 @@ class StatusWalletScreen: type(AddSavedAddressPopup.NAME_INPUT.value, name) type(AddSavedAddressPopup.ADDRESS_INPUT.value, address) click_obj_by_name(AddSavedAddressPopup.ADD_BUTTON.value) - - def edit_saved_address(self, name: str, new_name: str): - list = get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value) + + def _get_saved_address_delegate_item(self, name: str): + list = wait_and_get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value) found = -1 for index in range(list.count): - if list.itemAtIndex(index).objectName == name: + if list.itemAtIndex(index).objectName == f"savedAddressView_Delegate_{name}": found = index - + assert found != -1, "saved address not found" - - # More icon is at index 2 - time.sleep(1) - click_obj(list.itemAtIndex(found).components.at(2)) - + return list.itemAtIndex(found) + + def _find_saved_address_and_open_menu(self, name: str): + item = self._get_saved_address_delegate_item(name) + obj = get_child_item_with_object_name(item, SavedAddressesScreen.DELEGATE_MENU_BUTTON_OBJECT_NAME.value) + click_obj(obj) + + def edit_saved_address(self, name: str, new_name: str): + StatusMainScreen.wait_for_banner_to_disappear() + + self._find_saved_address_and_open_menu(name) + click_obj_by_name(SavedAddressesScreen.EDIT.value) type(AddSavedAddressPopup.NAME_INPUT.value, new_name) click_obj_by_name(AddSavedAddressPopup.ADD_BUTTON.value) - + def delete_saved_address(self, name: str): - list = get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value) - found = -1 - for index in range(list.count): - if list.itemAtIndex(index).objectName == name: - found = index - - assert found != -1, "saved address not found" - - # More icon is at index 2 - time.sleep(1) - click_obj(list.itemAtIndex(found).components.at(2)) - + StatusMainScreen.wait_for_banner_to_disappear() + + self._find_saved_address_and_open_menu(name) + click_obj_by_name(SavedAddressesScreen.DELETE.value) click_obj_by_name(SavedAddressesScreen.CONFIRM_DELETE.value) - + + def toggle_favourite_for_saved_address(self, name: str): + StatusMainScreen.wait_for_banner_to_disappear() + + # Find the saved address and click favourite to toggle + item = self._get_saved_address_delegate_item(name) + favouriteButton = get_child_item_with_object_name(item, SavedAddressesScreen.DELEGATE_FAVOURITE_BUTTON_OBJECT_NAME.value) + click_obj(favouriteButton) + + def check_favourite_status_for_saved_address(self, name: str, favourite: bool): + # Find the saved address + item = self._get_saved_address_delegate_item(name) + favouriteButton = get_child_item_with_object_name(item, SavedAddressesScreen.DELEGATE_FAVOURITE_BUTTON_OBJECT_NAME.value) + + # if favourite is true, check that the favourite shows "unfavourite" icon and vice versa + wait_for_prop_value(favouriteButton, "icon.name", ("unfavourite" if favourite else "favourite")) + wait_for_prop_value(item, "titleTextIcon", ("star-icon" if favourite else "")) + def toggle_network(self, network_name: str): click_obj_by_name(MainWalletScreen.NETWORK_SELECTOR_BUTTON.value) @@ -250,18 +268,24 @@ class StatusWalletScreen: assert False, "symbol not found" def verify_saved_address_exists(self, name: str): - list = get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value) + list = wait_and_get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value) for index in range(list.count): - if list.itemAtIndex(index).objectName == name: - return - - assert False, "no saved address found" - + if list.itemAtIndex(index).objectName == f"savedAddressView_Delegate_{name}": + return + + verify_failure(f'FAIL: saved address {name} not found"') + def verify_saved_address_doesnt_exist(self, name: str): - list = get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value) + # The list should be hidden when there are no saved addresses + try: + list = wait_and_get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value, 250) + except LookupError: + return + + list = wait_and_get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value) for index in range(list.count): - if list.itemAtIndex(index).objectName == name: - assert False, "saved address found" + if list.itemAtIndex(index).objectName == f"savedAddressView_Delegate_{name}": + verify_failure(f'FAIL: saved address {name} exists') def verify_transaction(self): print("TODO: fix notification and ensure there is one") diff --git a/test/ui-test/testSuites/suite_status/shared/scripts/debug_helpers.py b/test/ui-test/testSuites/suite_status/shared/scripts/debug_helpers.py index 30bd600dd0..89c83b6687 100644 --- a/test/ui-test/testSuites/suite_status/shared/scripts/debug_helpers.py +++ b/test/ui-test/testSuites/suite_status/shared/scripts/debug_helpers.py @@ -2,14 +2,15 @@ import squish import object -import names -import test -def debugWaitForObject(objRealName: dict, timeoutMSec: int = 1000): +def wait_for_object(objRealName: dict, timeoutMSec: int = 1000): return squish.waitForObject(objRealName, timeoutMSec) def type_text(obj, text: str): squish.type(obj, text) def find_object(objRealName: dict): - obj = squish.findObject(objRealName) \ No newline at end of file + return squish.findObject(objRealName) + +def wait_for_object_exists(objRealName: dict, timeoutMSec: int = 1000): + return squish.waitForObjectExists(objRealName, timeoutMSec) diff --git a/test/ui-test/testSuites/suite_status/shared/scripts/sections/wallet_names.py b/test/ui-test/testSuites/suite_status/shared/scripts/sections/wallet_names.py index 5e0a399530..51689febf9 100644 --- a/test/ui-test/testSuites/suite_status/shared/scripts/sections/wallet_names.py +++ b/test/ui-test/testSuites/suite_status/shared/scripts/sections/wallet_names.py @@ -58,7 +58,7 @@ mainWallet_Add_Account_Popup_Footer_Add_Account = {"container": mainWallet_Add_A # saved address view mainWallet_Saved_Addreses_Add_Buttton = {"container": statusDesktop_mainWindow, "objectName": "addNewAddressBtn", "type": "StatusButton"} -mainWallet_Saved_Addreses_List = {"container": statusDesktop_mainWindow, "objectName": "savedAddresses", "type": "StatusListView"} +mainWallet_Saved_Addreses_List = {"container": statusDesktop_mainWindow, "objectName": "SavedAddressesView_savedAddresses", "type": "StatusListView"} mainWallet_Saved_Addreses_More_Edit = {"container": statusDesktop_mainWindow, "objectName": "editSavedAddress", "type": "StatusMenuItemDelegate"} mainWallet_Saved_Addreses_More_Delete = {"container": statusDesktop_mainWindow, "objectName": "deleteSavedAddress", "type": "StatusMenuItemDelegate"} mainWallet_Saved_Addreses_More_Confirm_Delete = {"container": statusDesktop_mainWindow, "objectName": "confirmDeleteSavedAddress", "type": "StatusButton"} diff --git a/test/ui-test/testSuites/suite_status/shared/steps/walletSteps.py b/test/ui-test/testSuites/suite_status/shared/steps/walletSteps.py index 405b13ddd5..9b2ebc7205 100644 --- a/test/ui-test/testSuites/suite_status/shared/steps/walletSteps.py +++ b/test/ui-test/testSuites/suite_status/shared/steps/walletSteps.py @@ -1,6 +1,7 @@ from screens.StatusWalletScreen import StatusWalletScreen -from scripts.decorators import verify_screenshot - +from scripts.decorators import verify_screenshot +from common.Common import str_to_bool + _statusMain = StatusMainScreen() _walletScreen = StatusWalletScreen() @@ -45,6 +46,14 @@ def step(context, name, new_name): def step(context, name): _walletScreen.delete_saved_address(name) +@When("the user toggles favourite for the saved address with name |any|") +def step(context, name): + _walletScreen.toggle_favourite_for_saved_address(name) + +@Then("the saved address |any| has favourite status |any|") +def step(context, name, favourite): + _walletScreen.check_favourite_status_for_saved_address(name, str_to_bool(favourite)) + @When("the user toggles the network |any|") def step(context, network_name): _walletScreen.toggle_network(network_name) diff --git a/test/ui-test/testSuites/suite_status/tst_wallet/test.feature b/test/ui-test/testSuites/suite_status/tst_wallet/test.feature index 0b0a51536c..87534bf5d9 100644 --- a/test/ui-test/testSuites/suite_status/tst_wallet/test.feature +++ b/test/ui-test/testSuites/suite_status/tst_wallet/test.feature @@ -62,32 +62,14 @@ Feature: Status Desktop Wallet | name | address | | one | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | - Scenario Outline: User can edit a saved address - When the user adds a saved address named and address
- And the user edits a saved address with name to - Then the name is in the list of saved addresses - - Examples: - | name | address | new_name | - | bar | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | foo | - - Scenario Outline: User can delete a saved address - When the user adds a saved address named and address
- And the user deletes the saved address with name - Then the name is not in the list of saved addresses - - Examples: - | name | address | - | one | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | - Scenario Outline: User can edit a saved address When the user adds a saved address named and address
And the user edits a saved address with name to Then the name is in the list of saved addresses Examples: - | name | address | new_name | - | bar | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | foo | + | name | address | new_name | + | bar | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | foo | Scenario Outline: User can delete a saved address When the user adds a saved address named and address
@@ -95,8 +77,19 @@ Feature: Status Desktop Wallet Then the name is not in the list of saved addresses Examples: - | name | address | - | one | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | + | name | address | + | one | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | + + Scenario Outline: User can toggle favourite for a saved address + When the user adds a saved address named and address
+ And the user toggles favourite for the saved address with name + Then the saved address has favourite status true + When the user toggles favourite for the saved address with name + Then the saved address has favourite status false + + Examples: + | name | address | + | favourite | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | @mayfail Scenario: User can toggle network and see balances @@ -120,7 +113,7 @@ Feature: Status Desktop Wallet | new_name | new_color | | Default | #FFCA0F | -Scenario Outline: Can see collectibles for an account + Scenario Outline: Can see collectibles for an account When the user adds watch only account with and
Then the collectibles are listed for the diff --git a/ui/app/AppLayouts/Wallet/views/SavedAddressesView.qml b/ui/app/AppLayouts/Wallet/views/SavedAddressesView.qml index 97d8d01e92..6e29c58ec5 100644 --- a/ui/app/AppLayouts/Wallet/views/SavedAddressesView.qml +++ b/ui/app/AppLayouts/Wallet/views/SavedAddressesView.qml @@ -94,7 +94,7 @@ Item { StatusListView { id: listView - objectName: "savedAddresses" + objectName: "SavedAddressesView_savedAddresses" anchors.top: errorMessage.bottom anchors.topMargin: Style.current.padding anchors.bottom: parent.bottom @@ -105,6 +105,8 @@ Item { spacing: 5 model: RootStore.savedAddresses delegate: SavedAddressesDelegate { + objectName: "savedAddressView_Delegate_" + name + name: model.name address: model.address favourite: model.favourite diff --git a/ui/imports/shared/controls/SavedAddressesDelegate.qml b/ui/imports/shared/controls/SavedAddressesDelegate.qml index ac315af44e..1841bcbb1a 100644 --- a/ui/imports/shared/controls/SavedAddressesDelegate.qml +++ b/ui/imports/shared/controls/SavedAddressesDelegate.qml @@ -56,6 +56,7 @@ StatusListItem { textToCopy: root.address }, StatusRoundButton { + objectName: "savedAddressView_Delegate_favouriteButton" icon.color: root.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1 type: StatusRoundButton.Type.Tertiary icon.name: root.favourite ? "unfavourite" : "favourite" @@ -64,6 +65,7 @@ StatusListItem { } }, StatusRoundButton { + objectName: "savedAddressView_Delegate_menuButton" visible: !!root.name icon.color: root.showButtons ? Theme.palette.directColor1 : Theme.palette.baseColor1 type: StatusRoundButton.Type.Tertiary