fix(wallet) fix Add Account Modal and wallet tests

- fix add-account-modal custom derivation checkbox blocking all workflows
- fix, improve and enable wallet tests
- wait_for_text_matching alternative, to is_text_matching, to check also for content as squish driver API
- add objectName based lookup for in some places where user-text was used
- add workaround to retry for 10 seconds add watch due to flakiness
- rename SquishDriver.type to type.text not to conflict with python's type
- add optional timeout to some APIs
- ignore error for extra step in reaching onboarding seedphrase in linux

Updates: #9576
This commit is contained in:
Stefan 2023-02-24 13:59:14 +01:00 committed by Stefan Dunca
parent 1c7c0d7d81
commit 16ed8739e8
24 changed files with 304 additions and 275 deletions

View File

@ -168,7 +168,6 @@ QtObject:
let cacheKey = getTokenPriceCacheKey(cryptoKey, fiatKey)
if self.priceCache.isCached(cacheKey):
return self.priceCache.get(cacheKey) * factor
var prices = initTable[string, Table[string, float]]()
try:
let response = backend.fetchPrices(@[cryptoKey], @[fiatKey])
@ -180,7 +179,7 @@ QtObject:
let errDesription = e.msg
error "error: ", errDesription
return 0.0
proc getCachedTokenPrice*(self: Service, crypto: string, fiat: string, fetchIfNotPresent: bool = false): float64 =
let (cryptoKey, factor) = getCryptoKeyAndFactor(crypto)

View File

@ -5,13 +5,13 @@ import drivers.SDKeyboardCommands as keyCommands
def start_application(app_name: str):
driver.start_application(app_name)
def click_on_an_object(objName: str):
driver.click_obj_by_name(objName)
driver.click_obj_by_name(objName)
def input_text(text: str, objName: str):
driver.type(objName, text)
driver.type_text(objName, text)
def object_not_enabled(objName: str):
verification.verify_object_enabled(objName, 500, False)

View File

@ -4,32 +4,32 @@ from drivers.SquishDriverVerification import *
def input_seed_phrase(input_object_name: str, words: str):
type(input_object_name + "1", words[0])
type(input_object_name + "2", words[1])
type(input_object_name + "3", words[2])
type(input_object_name + "4", words[3])
type(input_object_name + "5", words[4])
type(input_object_name + "6", words[5])
type(input_object_name + "7", words[6])
type(input_object_name + "8", words[7])
type(input_object_name + "9", words[8])
type(input_object_name + "10", words[9])
type(input_object_name + "11", words[10])
type(input_object_name + "12", words[11])
type_text(input_object_name + "1", words[0])
type_text(input_object_name + "2", words[1])
type_text(input_object_name + "3", words[2])
type_text(input_object_name + "4", words[3])
type_text(input_object_name + "5", words[4])
type_text(input_object_name + "6", words[5])
type_text(input_object_name + "7", words[6])
type_text(input_object_name + "8", words[7])
type_text(input_object_name + "9", words[8])
type_text(input_object_name + "10", words[9])
type_text(input_object_name + "11", words[10])
type_text(input_object_name + "12", words[11])
if len(words) >= 18:
type(input_object_name + "13", words[12])
type(input_object_name + "14", words[13])
type(input_object_name + "15", words[14])
type(input_object_name + "16", words[15])
type(input_object_name + "17", words[16])
type(input_object_name + "18", words[17])
type_text(input_object_name + "13", words[12])
type_text(input_object_name + "14", words[13])
type_text(input_object_name + "15", words[14])
type_text(input_object_name + "16", words[15])
type_text(input_object_name + "17", words[16])
type_text(input_object_name + "18", words[17])
if len(words) == 24:
type(input_object_name + "19", words[18])
type(input_object_name + "20", words[19])
type(input_object_name + "21", words[20])
type(input_object_name + "22", words[21])
type(input_object_name + "23", words[22])
type(input_object_name + "24", words[23])
type_text(input_object_name + "19", words[18])
type_text(input_object_name + "20", words[19])
type_text(input_object_name + "21", words[20])
type_text(input_object_name + "22", words[21])
type_text(input_object_name + "23", words[22])
type_text(input_object_name + "24", words[23])

View File

@ -2,13 +2,13 @@ from drivers.SquishDriver import *
def press_enter(objName: str):
type(objName, "<Return>")
type_text(objName, "<Return>")
def press_backspace(objName: str):
type(objName, "<Backspace>")
type_text(objName, "<Backspace>")
def press_escape(objName: str):
type(objName, "<Escape>")
type_text(objName, "<Escape>")
def press_select_all(objName: str):
click_obj_by_name(objName)

View File

@ -120,8 +120,12 @@ def hover_and_click_object_by_name(objName: str):
squish.mouseClick(obj, squish.Qt.LeftButton)
# It executes the left-click action into object with given object name:
def click_obj_by_name(objName: str):
obj = squish.waitForObject(getattr(names, objName))
# If timeout is 0, it will use the default timeout (testSettings.waitForObjectTimeout)
def click_obj_by_name(objName: str, timeout: int=0):
if timeout > 0:
obj = squish.waitForObject(getattr(names, objName), timeout)
else:
obj = squish.waitForObject(getattr(names, objName))
squish.mouseClick(obj, squish.Qt.LeftButton)
# It executes the click action into the given object at particular coordinates:
@ -184,22 +188,29 @@ def reset_scroll_obj_by_name(objName: str):
# execute do_fn until validation_fn returns True or timeout is reached
def do_until_validation_with_timeout(do_fn, validation_fn, message: str, timeout_ms: int=_MAX_WAIT_OBJ_TIMEOUT * 2):
start_time = time.time()
while(not validation_fn()):
while True:
do_fn()
if validation_fn():
break
if ((time.time() - start_time) * 1000) > timeout_ms:
raise Exception("Timeout reached while validating: " + message)
do_fn()
def scroll_item_until_item_is_visible(itemToScrollObjName: str, itemToBeVisibleObjName: str, timeout_ms: int=_MAX_WAIT_OBJ_TIMEOUT * 2):
is_item_visible_fn = lambda: is_loaded_visible_and_enabled(itemToBeVisibleObjName, 10)[0]
# It seems the underlying squish.waitForObject sometimes takes more than 300 ms to validate the object is visible
is_item_visible_fn = lambda: is_loaded_visible_and_enabled(itemToBeVisibleObjName, 500)[0]
scroll_item_fn = lambda: scroll_obj_by_name(itemToScrollObjName)
do_until_validation_with_timeout(scroll_item_fn, is_item_visible_fn, f'Scrolling {itemToScrollObjName} until {itemToBeVisibleObjName} is visible', timeout_ms)
def wait_until_item_not_visible_and_enabled(itemObjName: str, timeout_ms: int=2000):
is_item_invisible_fn = lambda: not is_loaded_visible_and_enabled(itemObjName, 100)[0]
do_until_validation_with_timeout(lambda: time.sleep(0.05), is_item_invisible_fn, f'Waiting until {itemObjName} is not visible', timeout_ms)
def check_obj_by_name(objName: str):
obj = squish.waitForObject(getattr(names, objName))
obj.checked = True
def is_text_matching(objName: str, text: str):
def is_text_matching(objName: str, text: str, timeout: int=0):
try:
obj = squish.waitForObject(getattr(names, objName))
test.compare(obj.text, text, "Found the following text " + str(obj.text) + " Wanted: " + text)
@ -208,6 +219,23 @@ def is_text_matching(objName: str, text: str):
print(objName + " is not found, please check app for correct object and update object mapper")
return False
def wait_for_text_matching(objName: str, text: str, timeout: int=0):
try:
start_time = time.time()
time_run_out = False
while not time_run_out:
obj = squish.waitForObject(getattr(names, objName))
if obj.text == text:
break
if timeout > 0:
time_run_out = ((time.time() - start_time) * 1000) > timeout
test.compare(obj.text, text, f'Found the following text {str(obj.text)} + Wanted: {text} {("; Aborted after " + str(int(time.time() - start_time)) + "s") if time_run_out else ""}')
return True
except LookupError:
print(objName + " is not found, please check app for correct object and update object mapper")
return False
def is_text_matching_insensitive(obj, text: str):
try:
@ -219,7 +247,7 @@ def is_text_matching_insensitive(obj, text: str):
# It types the specified text into the given object (as if the user had used the keyboard):
def type(objName: str, text: str):
def type_text(objName: str, text: str):
try:
obj = squish.findObject(getattr(names, objName))
squish.type(obj, text)
@ -227,17 +255,20 @@ def type(objName: str, text: str):
except LookupError:
return False
# It types the specified text in the currently focus input (like if the keyboard was typed on)
def native_type(text: str):
squish.nativeType(text)
# Wait for the object to appears and
# It types the specified text into the given object (as if the user had used the keyboard):
def wait_for_object_and_type(objName: str, text: str):
# If timeout is 0, it will use the default timeout (testSettings.waitForObjectTimeout)
def wait_for_object_and_type(objName: str, text: str, timeout: int=0):
try:
obj = squish.waitForObject(getattr(names, objName))
squish.type(obj, text)
if timeout > 0:
obj = squish.waitForObject(getattr(names, objName), timeout)
else:
obj = squish.waitForObject(getattr(names, objName))
squish.type(obj, text)
return True
except LookupError:
return False
@ -363,3 +394,6 @@ def get_child_item_with_object_name(item, objectName: str):
def sleep_test(seconds: float):
squish.snooze(seconds)
def wait_for(py_condition_to_check: str, timeout_msec: int = 500):
squish.waitFor(py_condition_to_check, timeout_msec)

View File

@ -14,9 +14,11 @@ _MIN_WAIT_OBJ_TIMEOUT = 500
# The default maximum timeout to wait for close the app in seconds
_MAX_WAIT_CLOSE_APP_TIMEOUT = 20
import traceback
def verify_screen(objName: str, timeout: int=1000):
result = is_loaded_visible_and_enabled(objName, timeout)
test.verify(result, True)
test.verify(result, f'Verifying screen for real-name {objName}')
def verify_object_enabled(objName: str, timeout: int=_MIN_WAIT_OBJ_TIMEOUT, condition: bool=True):
result = is_loaded_visible_and_enabled(objName, timeout)

View File

@ -19,6 +19,7 @@ from drivers.SquishDriverVerification import *
from utils.ObjectAccess import *
from .StatusMainScreen import MainScreenComponents
from .StatusMainScreen import StatusMainScreen
from .StatusMainScreen import authenticatePopupEnterPassword
class SettingsScreenComponents(Enum):
SAVE_BUTTON: str = "settingsSave_StatusButton"
@ -128,18 +129,14 @@ class BackupSeedPhrasePopup(Enum):
CONFIRM_SECOND_WORD_INPUT: str = "backup_seed_phrase_popup_BackupSeedStepBase_confirmSecondWord_inputText"
CONFIRM_YOU_STORED_CHECKBOX: str = "backup_seed_phrase_popup_ConfirmStoringSeedPhrasePanel_storeCheck"
CONFIRM_YOU_STORED_BUTTON: str = "backup_seed_phrase_popup_BackupSeedModal_completeAndDeleteSeedPhraseButton"
class SharedPopup(Enum):
POPUP_CONTENT: str = "sharedPopup_Popup_Content"
PASSWORD_INPUT: str = "sharedPopup_Password_Input"
PRIMARY_BUTTON: str = "sharedPopup_Primary_Button"
class SettingsScreen:
__pid = 0
def __init__(self):
verify_screen(SidebarComponents.ADVANCED_OPTION.value)
def open_wallet_settings(self):
click_obj_by_name(SidebarComponents.WALLET_OPTION.value)
@ -150,29 +147,28 @@ class SettingsScreen:
verify_object_enabled(SidebarComponents.WALLET_OPTION.value)
def activate_open_wallet_section(self):
self.activate_wallet_option()
self.activate_wallet_option()
click_obj_by_name(MainScreenComponents.WALLET_BUTTON.value)
def delete_account(self, account_name: str, password: str):
self.open_wallet_settings()
index = self._find_account_index(account_name)
if index == -1:
raise Exception("Account not found")
accounts = get_obj(WalletSettingsScreen.GENERATED_ACCOUNTS.value)
click_obj(accounts.itemAtIndex(index))
click_obj_by_name(WalletSettingsScreen.DELETE_ACCOUNT.value)
click_obj_by_name(WalletSettingsScreen.DELETE_ACCOUNT_CONFIRM.value)
wait_for_object_and_type(SharedPopup.PASSWORD_INPUT.value, password)
click_obj_by_name(SharedPopup.PRIMARY_BUTTON.value)
authenticatePopupEnterPassword(password)
def verify_no_account(self, account_name: str):
index = self._find_account_index(account_name)
verify_equal(index, -1)
def verify_address(self, address: str):
accounts = get_obj(WalletSettingsScreen.GENERATED_ACCOUNTS.value)
verify_text_matching_insensitive(accounts.itemAtIndex(0).statusListItemSubTitle, address)
@ -222,7 +218,7 @@ class SettingsScreen:
for _ in range(4):
name += string.ascii_lowercase[random.randrange(26)]
type(ENSScreen.ENS_SEARCH_INPUT.value, name)
type_text(ENSScreen.ENS_SEARCH_INPUT.value, name)
time.sleep(1)
click_obj_by_name(ENSScreen.NEXT_BUTTON.value)
@ -231,7 +227,7 @@ class SettingsScreen:
click_obj_by_name(ENSScreen.TRANSACTION_NEXT_BUTTON.value)
click_obj_by_name(ENSScreen.TRANSACTION_NEXT_BUTTON.value)
type(ENSScreen.PASSWORD_INPUT.value, password)
type_text(ENSScreen.PASSWORD_INPUT.value, password)
click_obj_by_name(ENSScreen.TRANSACTION_NEXT_BUTTON.value)
def _find_account_index(self, account_name: str) -> int:
@ -255,7 +251,7 @@ class SettingsScreen:
click_obj_by_name(WalletSettingsScreen.EDIT_ACCOUNT_BUTTON.value)
def edit_account(self, account_name: str, account_color: str):
type(WalletSettingsScreen.EDIT_ACCOUNT_NAME_INPUT.value, account_name)
type_text(WalletSettingsScreen.EDIT_ACCOUNT_NAME_INPUT.value, account_name)
colorList = get_obj(WalletSettingsScreen.EDIT_ACCOUNT_COLOR_REPEATER.value)
for index in range(colorList.count):
color = colorList.itemAt(index)
@ -433,11 +429,11 @@ class SettingsScreen:
def change_user_password(self, oldPassword: str, newPassword: str):
get_and_click_obj(ProfileSettingsScreen.CHANGE_PASSWORD_BUTTON.value)
type(ChangePasswordMenu.CHANGE_PASSWORD_CURRENT_PASSWORD_INPUT.value, oldPassword)
type_text(ChangePasswordMenu.CHANGE_PASSWORD_CURRENT_PASSWORD_INPUT.value, oldPassword)
type(ChangePasswordMenu.CHANGE_PASSWORD_NEW_PASSWORD_INPUT.value, newPassword)
type_text(ChangePasswordMenu.CHANGE_PASSWORD_NEW_PASSWORD_INPUT.value, newPassword)
type(ChangePasswordMenu.CHANGE_PASSWORD_NEW_PASSWORD_CONFIRM_INPUT.value, newPassword)
type_text(ChangePasswordMenu.CHANGE_PASSWORD_NEW_PASSWORD_CONFIRM_INPUT.value, newPassword)
click_obj_by_name(ChangePasswordMenu.CHANGE_PASSWORD_SUBMIT_BUTTON.value)
click_obj_by_name(ChangePasswordMenu.CHANGE_PASSWORD_SUCCESS_MENU_SIGN_OUT_QUIT_BUTTON.value)
@ -445,14 +441,14 @@ class SettingsScreen:
def add_contact_by_chat_key(self, chat_key: str, who_you_are: str):
click_obj_by_name(ContactsViewScreen.CONTACT_REQUEST_CHAT_KEY_BTN.value)
type(ContactsViewScreen.CONTACT_REQUEST_CHAT_KEY_INPUT.value, chat_key)
type(ContactsViewScreen.CONTACT_REQUEST_SAY_WHO_YOU_ARE_INPUT.value, who_you_are)
type_text(ContactsViewScreen.CONTACT_REQUEST_CHAT_KEY_INPUT.value, chat_key)
type_text(ContactsViewScreen.CONTACT_REQUEST_SAY_WHO_YOU_ARE_INPUT.value, who_you_are)
click_obj_by_name(ContactsViewScreen.CONTACT_REQUEST_SEND_BUTTON.value)
def send_contact_request_via_profile_popup(self, who_you_are: str):
click_obj_by_name(ProfilePopupScreen.PROFILE_POPUP_SEND_CONTACT_REQUEST_BUTTON.value)
type(ProfilePopupScreen.SAY_WHO_YOU_ARE_INPUT.value, who_you_are)
type_text(ProfilePopupScreen.SAY_WHO_YOU_ARE_INPUT.value, who_you_are)
click_obj_by_name(ProfilePopupScreen.SEND_CONTACT_REQUEST_BUTTON.value)

View File

@ -131,7 +131,7 @@ class StatusChatScreen:
### Screen actions region:
#####################################
def type_message(self, message: str):
type(ChatComponents.MESSAGE_INPUT.value, message)
type_text(ChatComponents.MESSAGE_INPUT.value, message)
def press_enter(self):
press_enter(ChatComponents.MESSAGE_INPUT.value)
@ -222,7 +222,7 @@ class StatusChatScreen:
move_mouse_over_object(found_edit_button)
click_obj(found_edit_button)
wait_for_object_and_type(ChatComponents.EDIT_MESSAGE_TEXTAREA.value, "<Ctrl+a>")
type(ChatComponents.EDIT_MESSAGE_TEXTAREA.value, message)
type_text(ChatComponents.EDIT_MESSAGE_TEXTAREA.value, message)
press_enter(ChatComponents.EDIT_MESSAGE_TEXTAREA.value)
def switch_to_chat(self, chatName: str):

View File

@ -26,10 +26,10 @@ class StatusCommunityPortalScreen:
def create_community(self, communityName: str, communityDescription: str, introMessage: str, outroMessage: str):
click_obj_by_name(MainCommunityPortalScreen.CREATE_COMMUNITY_BUTTON.value)
type(CreateCommunityPopup.COMMUNITY_NAME_INPUT.value, communityName)
type(CreateCommunityPopup.COMMUNITY_DESCRIPTION_INPUT.value, communityDescription)
type_text(CreateCommunityPopup.COMMUNITY_NAME_INPUT.value, communityName)
type_text(CreateCommunityPopup.COMMUNITY_DESCRIPTION_INPUT.value, communityDescription)
click_obj_by_name(CreateCommunityPopup.NEXT_SCREEN_BUTTON.value)
wait_for_object_and_type(CreateCommunityPopup.COMMUNITY_INTRO_MESSAGE_INPUT.value, introMessage)
type(CreateCommunityPopup.COMMUNITY_OUTRO_MESSAGE_INPUT.value, outroMessage)
type_text(CreateCommunityPopup.COMMUNITY_OUTRO_MESSAGE_INPUT.value, outroMessage)
click_obj_by_name(CreateCommunityPopup.DO_CREATE_COMMUNITY_BUTTON.value)

View File

@ -172,7 +172,7 @@ class StatusCommunityScreen:
click_obj_by_name(CommunityScreenComponents.COMMUNITY_CREATE_CHANNEL_MENU_ITEM.value)
wait_for_object_and_type(CreateOrEditCommunityChannelPopup.COMMUNITY_CHANNEL_NAME_INPUT.value, communityChannelName)
type(CreateOrEditCommunityChannelPopup.COMMUNITY_CHANNEL_DESCRIPTION_INPUT.value, communityChannelDescription)
type_text(CreateOrEditCommunityChannelPopup.COMMUNITY_CHANNEL_DESCRIPTION_INPUT.value, communityChannelDescription)
click_obj_by_name(CreateOrEditCommunityChannelPopup.COMMUNITY_CHANNEL_SAVE_OR_CREATE_BUTTON.value)
@ -181,7 +181,7 @@ class StatusCommunityScreen:
# Select all text in the input before typing
wait_for_object_and_type(CreateOrEditCommunityChannelPopup.COMMUNITY_CHANNEL_NAME_INPUT.value, "<Ctrl+a>")
type(CreateOrEditCommunityChannelPopup.COMMUNITY_CHANNEL_NAME_INPUT.value, new_community_channel_name)
type_text(CreateOrEditCommunityChannelPopup.COMMUNITY_CHANNEL_NAME_INPUT.value, new_community_channel_name)
click_obj_by_name(CreateOrEditCommunityChannelPopup.COMMUNITY_CHANNEL_SAVE_OR_CREATE_BUTTON.value)
time.sleep(0.5)
@ -207,7 +207,7 @@ class StatusCommunityScreen:
# Select all text in the input before typing
wait_for_object_and_type(CreateOrEditCommunityCategoryPopup.COMMUNITY_CATEGORY_NAME_INPUT.value, "<Ctrl+a>")
type(CreateOrEditCommunityCategoryPopup.COMMUNITY_CATEGORY_NAME_INPUT.value, new_community_category_name)
type_text(CreateOrEditCommunityCategoryPopup.COMMUNITY_CATEGORY_NAME_INPUT.value, new_community_category_name)
self._toggle_channels_in_category_popop(community_channel_names)
click_obj_by_name(CreateOrEditCommunityCategoryPopup.COMMUNITY_CATEGORY_BUTTON.value)
@ -249,11 +249,11 @@ class StatusCommunityScreen:
def change_community_name(self, new_community_name: str):
# Select all text in the input before typing
wait_for_object_and_type(CommunitySettingsComponents.EDIT_COMMUNITY_NAME_INPUT.value, "<Ctrl+a>")
type(CommunitySettingsComponents.EDIT_COMMUNITY_NAME_INPUT.value, new_community_name)
type_text(CommunitySettingsComponents.EDIT_COMMUNITY_NAME_INPUT.value, new_community_name)
def change_community_description(self, new_community_description: str):
wait_for_object_and_type(CommunitySettingsComponents.EDIT_COMMUNITY_DESCRIPTION_INPUT.value, "<Ctrl+a>")
type(CommunitySettingsComponents.EDIT_COMMUNITY_DESCRIPTION_INPUT.value, new_community_description)
type_text(CommunitySettingsComponents.EDIT_COMMUNITY_DESCRIPTION_INPUT.value, new_community_description)
def change_community_color(self, new_community_color: str):
scroll_obj_by_name(CommunitySettingsComponents.EDIT_COMMUNITY_SCROLL_VIEW.value)
@ -262,7 +262,7 @@ class StatusCommunityScreen:
click_obj_by_name(CommunitySettingsComponents.EDIT_COMMUNITY_COLOR_PICKER_BUTTON.value)
wait_for_object_and_type(CommunityColorPanelComponents.HEX_COLOR_INPUT.value, "<Ctrl+a>")
type(CommunityColorPanelComponents.HEX_COLOR_INPUT.value, new_community_color)
type_text(CommunityColorPanelComponents.HEX_COLOR_INPUT.value, new_community_color)
click_obj_by_name(CommunityColorPanelComponents.SAVE_COLOR_BUTTON.value)
def save_community_changes(self):
@ -378,7 +378,7 @@ class StatusCommunityScreen:
click_obj(contact_item)
click_obj_by_name(CommunityScreenComponents.INVITE_POPUP_NEXT_BUTTON.value)
time.sleep(0.5)
type(CommunityScreenComponents.INVITE_POPUP_MESSAGE_INPUT.value, message)
type_text(CommunityScreenComponents.INVITE_POPUP_MESSAGE_INPUT.value, message)
click_obj_by_name(CommunityScreenComponents.INVITE_POPUP_SEND_BUTTON.value)
def _get_member_obj(self, member_name: str):

View File

@ -56,7 +56,7 @@ class StatusLoginScreen():
def enter_password(self, password):
click_obj_by_name(SLoginComponents.PASSWORD_INPUT.value)
type(SLoginComponents.PASSWORD_INPUT.value, password)
type_text(SLoginComponents.PASSWORD_INPUT.value, password)
click_obj_by_name(SLoginComponents.SUBMIT_BTN.value)
def verify_error_message_is_displayed(self):

View File

@ -42,44 +42,50 @@ class ProfilePopup(Enum):
USER_IMAGE = "ProfileHeader_userImage"
DISPLAY_NAME = "ProfilePopup_displayName"
EDIT_PROFILE_BUTTON = "ProfilePopup_editButton"
class ChatNamePopUp(Enum):
CHAT_NAME_TEXT = "chat_name_PlaceholderText"
START_CHAT_BTN = "startChat_Btn"
class SharedPopup(Enum):
POPUP_CONTENT: str = "sharedPopup_Popup_Content"
PASSWORD_INPUT: str = "sharedPopup_Password_Input"
PRIMARY_BUTTON: str = "sharedPopup_Primary_Button"
def authenticatePopupEnterPassword(password):
wait_for_object_and_type(SharedPopup.PASSWORD_INPUT.value, password)
click_obj_by_name(SharedPopup.PRIMARY_BUTTON.value)
class StatusMainScreen:
def __init__(self):
verify_screen(MainScreenComponents.CONTACTS_COLUMN_MESSAGES_HEADLINE.value)
# Main screen is ready to interact with it (Splash screen animation not present)
def is_ready(self):
self.wait_for_splash_animation_ends()
verify(is_displayed(MainScreenComponents.CONTACTS_COLUMN_MESSAGES_HEADLINE.value), "Verifying if the Messages headline is displayed")
verify(is_displayed(MainScreenComponents.CONTACTS_COLUMN_MESSAGES_HEADLINE.value, 15000), "Verifying if the Messages headline is displayed")
def wait_for_splash_animation_ends(self, timeoutMSec: int = 10000):
start = time.time()
[loaded, obj] = is_loaded_visible_and_enabled(MainScreenComponents.SPLASH_SCREEN.value)
while loaded and (start + timeoutMSec / 1000 > time.time()):
log("Splash screen animation present!")
[loaded, obj] = is_loaded_visible_and_enabled(MainScreenComponents.SPLASH_SCREEN.value, 1000)
sleep_test(0.5)
verify_equal(loaded, False, "Checking splash screen animation has ended.")
do_until_validation_with_timeout(
do_fn = lambda: time.sleep(0.5),
validation_fn = lambda: not is_loaded_visible_and_enabled(MainScreenComponents.SPLASH_SCREEN.value, 1000)[0],
message = "Splash screen animation has ended",
timeout_ms = timeoutMSec)
def open_chat_section(self):
click_obj_by_name(MainScreenComponents.CHAT_NAVBAR_ICON.value)
def open_community_portal(self):
click_obj_by_name(MainScreenComponents.COMMUNITY_PORTAL_BUTTON.value)
def open_settings(self):
click_obj_by_name(MainScreenComponents.SETTINGS_BUTTON.value)
time.sleep(0.5)
def open_start_chat_view(self):
click_obj_by_name(MainScreenComponents.START_CHAT_BTN.value)
def open_chat(self, chatName: str):
[loaded, chat_button] = self._find_chat(chatName)
if loaded:
@ -96,7 +102,7 @@ class StatusMainScreen:
for index in range(chat_lists.statusChatListItems.count):
chat = chat_lists.statusChatListItems.itemAtIndex(index)
if(chat.objectName == chatName):
return True, chat
return True, chat
return False, None
def open_wallet(self):
@ -123,19 +129,19 @@ class StatusMainScreen:
def user_is_offline(self):
profileButton = squish.waitForObject(getattr(names, MainScreenComponents.PROFILE_NAVBAR_BUTTON.value))
verify_equal(profileButton.badge.color.name, "#7f8990", "The user is not offline")
def user_is_set_to_automatic(self):
profileButton = squish.waitForObject(getattr(names, MainScreenComponents.PROFILE_NAVBAR_BUTTON.value))
verify_equal(profileButton.badge.color.name, "#4ebc60", "The user is not online by default")
def set_user_state_offline(self):
click_obj_by_name(MainScreenComponents.PROFILE_NAVBAR_BUTTON.value)
click_obj_by_name(MainScreenComponents.USERSTATUSMENU_INACTIVE_ACTION.value)
def set_user_state_online(self):
click_obj_by_name(MainScreenComponents.PROFILE_NAVBAR_BUTTON.value)
click_obj_by_name(MainScreenComponents.USERSTATUSMENU_ALWAYS_ACTIVE_ACTION.value)
def set_user_state_to_automatic(self):
click_obj_by_name(MainScreenComponents.PROFILE_NAVBAR_BUTTON.value)
click_obj_by_name(MainScreenComponents.USERSTATUSMENU_AUTOMATIC_ACTION.value)
@ -146,21 +152,21 @@ class StatusMainScreen:
def verify_profile_popup_display_name(self, display_name: str):
verify_text_matching(ProfilePopup.DISPLAY_NAME.value, display_name)
def click_escape(self):
press_escape(MainScreenComponents.MAIN_WINDOW.value)
def click_tool_bar_back_button(self):
click_obj_by_name(MainScreenComponents.TOOLBAR_BACK_BUTTON.value)
press_escape(MainScreenComponents.MAIN_WINDOW.value)
def click_tool_bar_back_button(self):
click_obj_by_name(MainScreenComponents.TOOLBAR_BACK_BUTTON.value)
def leave_chat(self, chatName: str):
[loaded, chat_button] = self._find_chat(chatName)
if loaded:
right_click_obj(chat_button)
hover_and_click_object_by_name(MainScreenComponents.LEAVE_CHAT_MENUITEM.value)
verify(loaded, "Trying to get chat: " + chatName)
def profile_image_is_updated(self):
# open profile popup and check image on profileNavBarButton and profileNavBarPopup
profileNavBarButton = wait_and_get_obj(MainScreenComponents.PROFILE_NAVBAR_BUTTON.value)
@ -168,22 +174,22 @@ class StatusMainScreen:
profilePopupImage = wait_and_get_obj(ProfilePopup.USER_IMAGE.value)
image_present("loginUserName", True, 95, 75, 100, True, profileNavBarButton)
image_present("loginUserName", True, 95, 75, 100, True, profilePopupImage)
def profile_modal_image_is_updated(self):
click_obj_by_name(MainScreenComponents.PROFILE_NAVBAR_BUTTON.value)
click_obj_by_name(MainScreenComponents.USERSTATUSMENU_OPEN_PROFILE_POPUP.value)
image_present("profiletestimage", True, 97, 95, 100, True)
def profile_settings_image_is_updated(self):
# first time clicking on settings button closes the my profile modal
click_obj_by_name(MainScreenComponents.SETTINGS_BUTTON.value)
click_obj_by_name(MainScreenComponents.SETTINGS_BUTTON.value)
myProfileSettingsObject = wait_and_get_obj(MainScreenComponents.PROFILE_SETTINGS_VIEW.value)
image_present("profiletestimage", True, 95, 100, 183, True, myProfileSettingsObject)
def navigate_to_edit_profile(self):
click_obj_by_name(ProfilePopup.EDIT_PROFILE_BUTTON.value)
def close_popup(self):
# Click in the corner of the overlay to close the popup
click_obj_by_name_at_coordinates(MainScreenComponents.POPUP_OVERLAY.value, 1, 1)

View File

@ -37,7 +37,7 @@ class StatusSearchScreen:
def search_for(self, search_term: str):
click_obj_by_name(SearchPopupComponents.RESET_BUTTON.value)
type(SearchPopupComponents.SEARCH_INPUT.value, search_term)
type_text(SearchPopupComponents.SEARCH_INPUT.value, search_term)
self.wait_for_loading_done()
def verify_number_of_results(self, amount: int):

View File

@ -1,11 +1,12 @@
from ast import Tuple
from enum import Enum
import time
import os
import sys
from drivers.SquishDriver import *
from drivers.SquishDriverVerification import *
from common.SeedUtils import *
from .StatusMainScreen import StatusMainScreen
from .StatusMainScreen import authenticatePopupEnterPassword
from drivers.SquishDriver import type_text as type_text
class Tokens(Enum):
ETH: str = "ETH"
@ -67,15 +68,13 @@ class AddAccountPopup(Enum):
TYPE_SEED_PHRASE: str = "mainWallet_Add_Account_Popup_Type_Seed_Phrase"
TYPE_PRIVATE_KEY: str = "mainWallet_Add_Account_Popup_Type_Private_Key"
ADDRESS_INPUT: str = "mainWallet_Add_Account_Popup_Watch_Only_Address"
ADDRESS_INPUT_PLACEHOLDER: str = "mainWallet_Add_Account_Popup_Watch_Only_Address_Placeholder"
PRIVATE_KEY_INPUT: str = "mainWallet_Add_Account_Popup_Private_Key"
ADD_ACCOUNT_BUTTON: str = "mainWallet_Add_Account_Popup_Footer_Add_Account"
SEED_PHRASE_INPUT_TEMPLATE: str = "mainWindow_Add_Account_Popup_Seed_Phrase_"
SEED_PHRASE_INPUT_LAST: str = "mainWindow_Add_Account_Popup_Seed_Phrase_12"
class SharedPopup(Enum):
POPUP_CONTENT: str = "sharedPopup_Popup_Content"
PASSWORD_INPUT: str = "sharedPopup_Password_Input"
PRIMARY_BUTTON: str = "sharedPopup_Primary_Button"
FULLY_CUSTOM_PATH_CHECKBOX: str = "mainWallet_Add_Account_Popup_Advanced_Accept_Responsibility_Checkbox"
ADD_ACCOUNT_POPUP_ROOT: str = "mainWallet_Add_Account_Popup_Root"
class CollectiblesView(Enum):
COLLECTIONS_REPEATER: str = "mainWallet_Collections_Repeater"
@ -99,104 +98,97 @@ class StatusWalletScreen:
def accept_signing_phrase(self):
click_obj_by_name(SigningPhrasePopUp.OK_GOT_IT_BUTTON.value)
def add_watch_only_account(self, account_name: str, address: str):
def add_watch_only_account(self, account_name: str, address: str, password: str):
click_obj_by_name(MainWalletScreen.ADD_ACCOUNT_BUTTON.value)
type(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
type_text(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
click_obj_by_name(AddAccountPopup.ADVANCE_SECTION.value)
click_obj_by_name(AddAccountPopup.ADVANCE_SECTION.value, 2000)
click_obj_by_name(AddAccountPopup.TYPE_SELECTOR.value)
time.sleep(1)
click_obj_by_name(AddAccountPopup.TYPE_WATCH_ONLY.value)
# Found that all the involved controls are switching availability states very quickly based on the model data
# which makes it almost impossible to do a reliable check for different states, hence the retry for 10 seconds
# workaround.
# TODO remove workaround to retry after add account modal refactoring
max_expected_step_duration_ms = 10000
def scroll_and_type_fn():
try:
click_obj_by_name(AddAccountPopup.TYPE_SELECTOR.value)
click_obj_by_name(AddAccountPopup.TYPE_WATCH_ONLY.value)
scroll_item_until_item_is_visible(AddAccountPopup.SCROLL_BAR.value, AddAccountPopup.ADDRESS_INPUT.value)
type(AddAccountPopup.ADDRESS_INPUT.value, address)
click_obj_by_name(AddAccountPopup.ADD_ACCOUNT_BUTTON.value)
scroll_item_until_item_is_visible(AddAccountPopup.SCROLL_BAR.value, AddAccountPopup.ADDRESS_INPUT_PLACEHOLDER.value, 2000)
wait_for_object_and_type(AddAccountPopup.ADDRESS_INPUT_PLACEHOLDER.value, address)
except Exception as e:
log(f"Expected fail, ignore it for {max_expected_step_duration_ms/1000} seconds; exception {str(e)}")
do_until_validation_with_timeout(
scroll_and_type_fn,
lambda: is_loaded_visible_and_enabled(AddAccountPopup.ADDRESS_INPUT.value, 500)[0],
timeout_ms=max_expected_step_duration_ms,
message="Fill watch only account address")
click_obj_by_name(AddAccountPopup.ADD_ACCOUNT_BUTTON.value, 2000)
def import_private_key(self, account_name: str, password: str, private_key: str):
click_obj_by_name(MainWalletScreen.ADD_ACCOUNT_BUTTON.value)
type(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
type_text(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
click_obj_by_name(AddAccountPopup.ADVANCE_SECTION.value)
click_obj_by_name(AddAccountPopup.TYPE_SELECTOR.value)
time.sleep(1)
click_obj_by_name(AddAccountPopup.TYPE_PRIVATE_KEY.value)
scroll_item_until_item_is_visible(AddAccountPopup.SCROLL_BAR.value, AddAccountPopup.PRIVATE_KEY_INPUT.value)
type(AddAccountPopup.PRIVATE_KEY_INPUT.value, private_key)
type_text(AddAccountPopup.PRIVATE_KEY_INPUT.value, private_key)
click_obj_by_name(AddAccountPopup.ADD_ACCOUNT_BUTTON.value)
wait_for_object_and_type(SharedPopup.PASSWORD_INPUT.value, password)
click_obj_by_name(SharedPopup.PRIMARY_BUTTON.value)
time.sleep(1)
authenticatePopupEnterPassword(password)
def import_seed_phrase(self, account_name: str, password: str, mnemonic: str):
click_obj_by_name(MainWalletScreen.ADD_ACCOUNT_BUTTON.value)
type(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
type_text(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
click_obj_by_name(AddAccountPopup.ADVANCE_SECTION.value)
time.sleep(1)
click_obj_by_name(AddAccountPopup.TYPE_SELECTOR.value)
time.sleep(1)
click_obj_by_name(AddAccountPopup.TYPE_SEED_PHRASE.value)
time.sleep(1)
for i in range(1, 5):
scroll_obj_by_name(AddAccountPopup.SCROLL_BAR.value)
time.sleep(1)
is_loaded_visible_and_enabled(AddAccountPopup.SCROLL_BAR.value, 1000)
scroll_item_until_item_is_visible(AddAccountPopup.SCROLL_BAR.value, AddAccountPopup.SEED_PHRASE_INPUT_LAST.value)
words = mnemonic.split()
input_seed_phrase(AddAccountPopup.SEED_PHRASE_INPUT_TEMPLATE.value, words)
time.sleep(1)
click_obj_by_name(AddAccountPopup.ADD_ACCOUNT_BUTTON.value)
wait_for_object_and_type(SharedPopup.PASSWORD_INPUT.value, password)
click_obj_by_name(SharedPopup.PRIMARY_BUTTON.value)
time.sleep(1)
authenticatePopupEnterPassword(password)
def generate_new_account(self, account_name: str, password: str):
click_obj_by_name(MainWalletScreen.ADD_ACCOUNT_BUTTON.value)
type(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
time.sleep(1)
type_text(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
click_obj_by_name(AddAccountPopup.ADD_ACCOUNT_BUTTON.value)
time.sleep(1)
wait_for_object_and_type(SharedPopup.PASSWORD_INPUT.value, password)
click_obj_by_name(SharedPopup.PRIMARY_BUTTON.value)
time.sleep(1)
def verify_account_name_is_present(self, account_name: str):
verify_text_matching(MainWalletScreen.ACCOUNT_NAME.value, account_name)
type(AddAccountPopup.ACCOUNT_NAME_INPUT.value, account_name)
click_obj_by_name(AddAccountPopup.ADD_ACCOUNT_BUTTON.value)
authenticatePopupEnterPassword(password)
def send_transaction(self, account_name, amount, token, chain_name, password):
is_loaded_visible_and_enabled(AssetView.LIST.value, 2000)
list = get_obj(AssetView.LIST.value)
squish.waitFor("list.count > 0", 60*1000*2)
squish.waitFor("float(str(list.itemAtIndex(0).balance)) > 0", 60*1000*2)
# LoadingTokenDelegate will be visible until the balance is loaded verify_account_balance_is_positive checks for TokenDelegate
do_until_validation_with_timeout(lambda: time.sleep(0.1), lambda: self.verify_account_balance_is_positive(list, "ETH")[0], "Wait for tokens to load", 10000)
click_obj_by_name(MainWalletScreen.SEND_BUTTON_FOOTER.value)
self._click_repeater(SendPopup.HEADER_ACCOUNTS_LIST.value, account_name)
time.sleep(1)
type(SendPopup.AMOUNT_INPUT.value, amount)
is_loaded_visible_and_enabled(SendPopup.AMOUNT_INPUT.value, 1000)
type_text(SendPopup.AMOUNT_INPUT.value, amount)
click_obj_by_name(SendPopup.ASSET_SELECTOR.value)
asset_list = get_obj(SendPopup.ASSET_LIST.value)
for index in range(asset_list.count):
tokenObj = asset_list.itemAtIndex(index)
if(not squish.isNull(tokenObj) and tokenObj.objectName == "AssetSelector_ItemDelegate_" + token):
if(not is_null(tokenObj) and tokenObj.objectName == "AssetSelector_ItemDelegate_" + token):
click_obj(asset_list.itemAtIndex(index))
break
@ -210,12 +202,10 @@ class StatusWalletScreen:
break
scroll_obj_by_name(SendPopup.SCROLL_BAR.value)
time.sleep(1)
click_obj_by_name(SendPopup.SEND_BUTTON.value)
wait_for_object_and_type(SharedPopup.PASSWORD_INPUT.value, password)
click_obj_by_name(SharedPopup.PRIMARY_BUTTON.value)
authenticatePopupEnterPassword(password)
def _click_repeater(self, repeater_object_name: str, object_name: str):
repeater = get_obj(repeater_object_name)
@ -227,8 +217,8 @@ class StatusWalletScreen:
def add_saved_address(self, name: str, address: str):
click_obj_by_name(MainWalletScreen.SAVED_ADDRESSES_BUTTON.value)
click_obj_by_name(SavedAddressesScreen.ADD_BUTTON.value)
type(AddSavedAddressPopup.NAME_INPUT.value, name)
type(AddSavedAddressPopup.ADDRESS_INPUT.value, address)
type_text(AddSavedAddressPopup.NAME_INPUT.value, name)
type_text(AddSavedAddressPopup.ADDRESS_INPUT.value, address)
click_obj_by_name(AddSavedAddressPopup.ADD_BUTTON.value)
def _get_saved_address_delegate_item(self, name: str):
@ -250,7 +240,7 @@ class StatusWalletScreen:
self._find_saved_address_and_open_menu(name)
click_obj_by_name(SavedAddressesScreen.EDIT.value)
type(AddSavedAddressPopup.NAME_INPUT.value, new_name)
type_text(AddSavedAddressPopup.NAME_INPUT.value, new_name)
click_obj_by_name(AddSavedAddressPopup.ADD_BUTTON.value)
def delete_saved_address(self, name: str):
@ -275,17 +265,16 @@ class StatusWalletScreen:
wait_for_prop_value(item, "titleTextIcon", ("star-icon" if favourite else ""))
def toggle_network(self, network_name: str):
time.sleep(2)
is_loaded_visible_and_enabled(MainWalletScreen.NETWORK_SELECTOR_BUTTON.value, 2000)
click_obj_by_name(MainWalletScreen.NETWORK_SELECTOR_BUTTON.value)
time.sleep(2)
is_loaded_visible_and_enabled(NetworkSelectorPopup.LAYER_1_REPEATER.value, 2000)
list = wait_and_get_obj(NetworkSelectorPopup.LAYER_1_REPEATER.value)
for index in range(list.count):
item = list.itemAt(index)
if item.objectName == network_name:
click_obj(item)
click_obj_by_name(MainWalletScreen.ACCOUNT_NAME.value)
time.sleep(2)
return
assert False, "network name not found"
@ -299,29 +288,23 @@ class StatusWalletScreen:
#####################################
def verify_account_name_is_present(self, account_name: str):
verify_text_matching(MainWalletScreen.ACCOUNT_NAME.value, account_name)
# Wait 2 second for UI text to update
test.verify(wait_for_text_matching(MainWalletScreen.ACCOUNT_NAME.value, account_name, 2000), "Account name was updated and matches expected")
def verify_account_balance_is_positive(self, list, symbol: str) -> Tuple(bool, ):
if list is None:
return (False, )
for index in range(list.count):
tokenListItem = list.itemAtIndex(index)
if tokenListItem != None and tokenListItem.objectName == "AssetView_TokenListItem_" + symbol and tokenListItem.balance != "0":
return (True, tokenListItem)
return (False, )
def verify_positive_balance(self, symbol: str):
time.sleep(5) # TODO: remove when it is faster @alaibe!
is_loaded_visible_and_enabled(AssetView.LIST.value, 5000)
list = get_obj(AssetView.LIST.value)
reset = 0
while (reset < 3):
found = False
for index in range(list.count):
tokenListItem = list.itemAtIndex(index)
if tokenListItem.objectName == "AssetView_TokenListItem_" + symbol:
found = True
if (tokenListItem.balance == "0" and reset < 3):
break
return
if not found:
verify_failure("Symbol " + symbol + " not found in the asset list")
reset += 1
time.sleep(5)
verify_failure("Balance was not positive")
do_until_validation_with_timeout(lambda: time.sleep(0.1), lambda: self.verify_account_balance_is_positive(list, symbol)[0], "Symbol " + symbol + " not found in the asset list", 5000)
def verify_saved_address_exists(self, name: str):
list = wait_and_get_obj(SavedAddressesScreen.SAVED_ADDRESSES_LIST.value)
@ -365,7 +348,7 @@ class StatusWalletScreen:
transaction_list_view = get_obj(TransactionsView.TRANSACTIONS_LISTVIEW.value)
squish.waitFor("transaction_list_view.count > 0", 60*1000)
wait_for("transaction_list_view.count > 0", 60*1000)
verify(transaction_list_view.count > 1, "Transactions not retrieved for the account")
transaction_item = transaction_list_view.itemAtIndex(1)

View File

@ -43,7 +43,7 @@ class SignUpComponents(Enum):
WELCOME_SCREEN_USER_PROFILE_IMAGE: str = "mainWindow_WelcomeScreen_User_Profile_Image"
WELCOME_SCREEN_CHAT_KEY_TEXT: str = "mainWindow_WelcomeScreen_ChatKeyText"
BACK_BTN: str = "onboarding_back_button"
class SeedPhraseComponents(Enum):
IMPORT_A_SEED_TEXT: str = "import_a_seed_phrase_StatusBaseText"
INVALID_SEED_TEXT: str = "onboarding_InvalidSeed_Text"
@ -53,7 +53,7 @@ class SeedPhraseComponents(Enum):
TWENTY_FOUR_BUTTON: str = "switchTabBar_24_words_Button"
SEEDS_WORDS_TEXTFIELD_template: str = "onboarding_SeedPhrase_Input_TextField_"
SUBMIT_BUTTON: str = "seedPhraseView_Submit_Button"
class PasswordStrengthPossibilities(Enum):
LOWER_VERY_WEAK = "lower_very_weak"
UPPER_VERY_WEAK = "upper_very_weak"
@ -66,7 +66,7 @@ class PasswordStrengthPossibilities(Enum):
class MainScreen(Enum):
SETTINGS_BUTTON = "settings_navbar_settings_icon_StatusIcon"
class LoginView(Enum):
LOGIN_VIEW_USER_IMAGE: str = "loginView_userImage"
PASSWORD_INPUT = "loginView_passwordInput"
@ -97,7 +97,13 @@ class StatusWelcomeScreen:
def input_seed_phrase(self, seed_phrase: str):
words = seed_phrase.split()
# On MacOS, there is an additional step to import a seed phrase than on Linux
try:
click_obj_by_name(SeedPhraseComponents.IMPORT_A_SEED_BUTTON.value, 1000)
except LookupError:
log("Ignoring error on non MacOS, as it is expected to not find the import seed phrase button at this stage")
if len(words) == 12:
click_obj_by_name(SeedPhraseComponents.TWELVE_WORDS_BUTTON.value)
elif len(words) == 18:
@ -124,7 +130,7 @@ class StatusWelcomeScreen:
def input_username(self, username: str):
common.clear_input_text(SignUpComponents.USERNAME_INPUT.value)
type(SignUpComponents.USERNAME_INPUT.value, username)
type_text(SignUpComponents.USERNAME_INPUT.value, username)
click_obj_by_name(SignUpComponents.DETAILS_NEXT_BUTTON.value)
# The next click will move too fast sometime
@ -138,8 +144,8 @@ class StatusWelcomeScreen:
def input_password(self, password: str):
verify(is_loaded_visible_and_enabled(SignUpComponents.NEW_PSW_INPUT.value, 10)[0], 'New Password input is visible')
type(SignUpComponents.NEW_PSW_INPUT.value, password)
type(SignUpComponents.CONFIRM_PSW_INPUT.value, password)
type_text(SignUpComponents.NEW_PSW_INPUT.value, password)
type_text(SignUpComponents.CONFIRM_PSW_INPUT.value, password)
do_until_validation_with_timeout(
do_fn = lambda: click_obj_by_name(SignUpComponents.CREATE_PSW_BUTTON.value),
validation_fn = lambda: not is_loaded_visible_and_enabled(SignUpComponents.CREATE_PSW_BUTTON.value, 50)[0],
@ -147,7 +153,7 @@ class StatusWelcomeScreen:
def input_confirmation_password(self, password: str):
verify(is_loaded_visible_and_enabled(SignUpComponents.CONFIRM_PSW_AGAIN_INPUT.value, 10)[0], 'Reconfirm password is visible')
type(SignUpComponents.CONFIRM_PSW_AGAIN_INPUT.value, password)
type_text(SignUpComponents.CONFIRM_PSW_AGAIN_INPUT.value, password)
do_until_validation_with_timeout(
do_fn = lambda: click_obj_by_name(SignUpComponents.FINALIZE_PSW_BUTTON.value),
validation_fn = lambda: not is_loaded_visible_and_enabled(SignUpComponents.FINALIZE_PSW_BUTTON.value, 50)[0],
@ -162,10 +168,10 @@ class StatusWelcomeScreen:
click_obj_by_name(AgreementPopUp.GET_STARTED_BUTTON.value)
verify_text_matching(SignUpComponents.WELCOME_TO_STATUS.value, "Welcome to Status")
click_obj_by_name(SignUpComponents.NEW_TO_STATUS.value)
def seed_phrase_visible(self):
is_loaded_visible_and_enabled(SeedPhraseComponents.INVALID_SEED_TEXT.value)
# The following validation is based in screenshots comparison and is OS dependent:
def validate_password_strength(self, strength: str):
if sys.platform == "darwin":
@ -192,29 +198,29 @@ class StatusWelcomeScreen:
elif strength == PasswordStrengthPossibilities.NUMBERS_SYMBOLS_LOWER_UPPER_GREAT.value:
verify_screenshot("VP-PWStrength-numbers_symbols_lower_upper_great")
# TODO: Get screenshots in Linux
def input_profile_image(self, profileImageUrl: str):
workflow = get_obj(SignUpComponents.PROFILE_IMAGE_CROP_WORKFLOW_ITEM.value)
workflow.cropImage(profileImageUrl)
workflow.cropImage(profileImageUrl)
click_obj_by_name(SignUpComponents.PROFILE_IMAGE_CROPPER_ACCEPT_BUTTON.value)
def input_username_and_grab_profile_image_sreenshot(self, username: str):
type(SignUpComponents.USERNAME_INPUT.value, username)
type_text(SignUpComponents.USERNAME_INPUT.value, username)
click_obj_by_name(SignUpComponents.DETAILS_NEXT_BUTTON.value)
# take a screenshot of the profile image to compare it later with the main screen
profileIcon = wait_and_get_obj(SignUpComponents.WELCOME_SCREEN_USER_PROFILE_IMAGE.value)
grabScreenshot_and_save(profileIcon, "profiletestimage", 200)
# There is another page with the same Next button
click_obj_by_name(SignUpComponents.DETAILS_NEXT_BUTTON.value)
def input_username_profileImage_password_and_finalize_sign_up(self, profileImageUrl: str, username: str, password: str):
self.input_profile_image(profileImageUrl)
self.input_username_and_grab_profile_image_sreenshot(username)
self.input_username_and_grab_profile_image_sreenshot(username)
self.input_password(password)
@ -222,17 +228,17 @@ class StatusWelcomeScreen:
if sys.platform == "darwin":
click_obj_by_name(SignUpComponents.PASSWORD_PREFERENCE.value)
def grab_screenshot(self):
# take a screenshot of the profile image to compare it later with the main screen
loginUserName = wait_and_get_obj(LoginView.LOGIN_VIEW_USER_IMAGE.value)
grabScreenshot_and_save(loginUserName, "loginUserName", 200)
def enter_password(self, password):
click_obj_by_name(LoginView.PASSWORD_INPUT.value)
type(LoginView.PASSWORD_INPUT.value, password)
click_obj_by_name(LoginView.SUBMIT_BTN.value)
type_text(LoginView.PASSWORD_INPUT.value, password)
click_obj_by_name(LoginView.SUBMIT_BTN.value)
def navigate_back_to_user_profile_page(self):
count = 0
while not is_displayed(SignUpComponents.USERNAME_INPUT.value, 500):

View File

@ -38,7 +38,7 @@ Feature: Status Desktop Sign Up
| lemon card easy goose keen divide cabbage daughter glide glad sense dice promote present august obey stay cheese | 0xdd06a08d469dd61Cb2E5ECE30f5D16019eBe0fc9 |
| provide between target maze travel enroll edge churn random sight grass lion diet sugar cable fiction reflect reason gaze camp tone maximum task unlock | 0xCb59031d11D233112CB57DFd667fE1FF6Cd7b6Da |
@mayfail
Scenario: The user signs up with a profile image
Given A first time user lands on the status desktop and generates new key
And the user signs up with profileImage "doggo.jpeg", username "tester123" and password "TesTEr16843/!@00"

View File

@ -40,12 +40,14 @@ mainWallet_Send_Popup_GasPrice_Input = {"container": statusDesktop_mainWindow, "
mainWallet_Add_Account_Popup_Main = {"container": statusDesktop_mainWindow, "objectName": "AddAccountModalContent", "type": "StatusScrollView", "visible": True}
mainWallet_Add_Account_Popup_Password = {"container": mainWallet_Add_Account_Popup_Main, "text": "Enter your password...", "type": "PlaceholderText", "unnamed": 1, "visible": True}
mainWallet_Add_Account_Popup_Advanced = {"container": mainWallet_Add_Account_Popup_Main, "objectName": "ExpandableItem", "type": "MouseArea", "visible": True}
mainWallet_Add_Account_Popup_Type_Selector = {"container": mainWallet_Add_Account_Popup_Main, "text": "Default", "type": "StatusBaseText", "unnamed": 1, "visible": True}
mainWallet_Add_Account_Popup_Type_Watch_Only = {"container": statusDesktop_mainWindow, "text": "Add a watch-only address", "type": "StatusBaseText", "unnamed": 1, "visible": True}
mainWallet_Add_Account_Popup_Type_Private_Key = {"container": statusDesktop_mainWindow, "text": "Generate from Private key", "type": "StatusBaseText", "unnamed": 1, "visible": True}
mainWallet_Add_Account_Popup_Type_Seed_Phrase = {"container": statusDesktop_mainWindow, "text": "Import new Seed Phrase", "type": "StatusBaseText", "unnamed": 1, "visible": True}
mainWallet_Add_Account_Popup_Type_Selector = {"container": mainWallet_Add_Account_Popup_Main, "objectName": "selectGeneratedAccount", "type": "SelectGeneratedAccount", "visible": True}
mainWallet_Add_Account_Popup_Type_Watch_Only = {"container": statusDesktop_mainWindow, "objectName": "watchOnlyAccount", "type": "StatusListItem", "visible": True}
mainWallet_Add_Account_Popup_Type_Private_Key = {"container": statusDesktop_mainWindow, "objectName": "generateFromPrivateKey", "type": "StatusListItem", "visible": True}
mainWallet_Add_Account_Popup_Type_Seed_Phrase = {"container": statusDesktop_mainWindow, "objectName": "importNewSeedPhrase", "type": "StatusListItem", "visible": True}
mainWallet_Add_Account_Popup_Account_Name = {"container": mainWallet_Add_Account_Popup_Main, "text": "Enter an account name...", "type": "StatusBaseText", "unnamed": 1, "visible": True}
mainWallet_Add_Account_Popup_Watch_Only_Address = {"container": mainWallet_Add_Account_Popup_Main, "text": "Enter address...", "type": "StatusBaseText", "unnamed": 1, "visible": True}
mainWallet_Add_Account_Popup_Root = {"container": mainWallet_Add_Account_Popup_Main, "objectName": "advancedAddAccountViewRoot", "type": "AdvancedAddAccountView", "visible": True}
mainWallet_Add_Account_Popup_Watch_Only_Address = {"container": mainWallet_Add_Account_Popup_Main, "objectName": "advancedAddAccountViewAddressInput", "type": "StatusBaseInput", "visible": True}
mainWallet_Add_Account_Popup_Watch_Only_Address_Placeholder = {"container": mainWallet_Add_Account_Popup_Main, "objectName": "advancedAddAccountViewAddressInputPlaceholder", "type": "StatusBaseText", "visible": True}
mainWallet_Add_Account_Popup_Private_Key = {"container": mainWallet_Add_Account_Popup_Main, "text": "Paste the contents of your private key", "type": "StatusBaseText", "unnamed": 1, "visible": True}
mainWindow_Add_Account_Popup_Seed_Phrase_1 = {"container": mainWallet_Add_Account_Popup_Main, "type": "StatusBaseText", "objectName": "seedPhraseInputPlaceholder0", "visible": True}
mainWindow_Add_Account_Popup_Seed_Phrase_2 = {"container": mainWallet_Add_Account_Popup_Main, "type": "StatusBaseText", "objectName": "seedPhraseInputPlaceholder1", "visible": True}
@ -59,6 +61,7 @@ mainWindow_Add_Account_Popup_Seed_Phrase_9 = {"container": mainWallet_Add_Accoun
mainWindow_Add_Account_Popup_Seed_Phrase_10 = {"container": mainWallet_Add_Account_Popup_Main, "type": "StatusBaseText", "objectName": "seedPhraseInputPlaceholder9", "visible": True}
mainWindow_Add_Account_Popup_Seed_Phrase_11 = {"container": mainWallet_Add_Account_Popup_Main, "type": "StatusBaseText", "objectName": "seedPhraseInputPlaceholder10", "visible": True}
mainWindow_Add_Account_Popup_Seed_Phrase_12 = {"container": mainWallet_Add_Account_Popup_Main, "type": "StatusBaseText", "objectName": "seedPhraseInputPlaceholder11", "visible": True}
mainWallet_Add_Account_Popup_Advanced_Accept_Responsibility_Checkbox = {"container": mainWallet_Add_Account_Popup_Main, "objectName": "fullyCustomPathCheckBox", "type": "StatusCheckBox", "visible": True}
mainWallet_Add_Account_Popup_Footer = {"container": statusDesktop_mainWindow, "type": "StatusModalFooter", "unnamed": 1, "visible": True}
mainWallet_Authenticate_Popup_Footer_Add_Account = {"container": mainWallet_Add_Account_Popup_Footer, "text": "Authenticate", "type": "StatusBaseText", "unnamed": 1, "visible": True}
@ -82,7 +85,7 @@ mainWallet_Collectibles_Repeater = {"container": statusDesktop_mainWindow, "obje
# Shared Popup
sharedPopup_Popup_Content = {"container": statusDesktop_mainWindow, "objectName": "KeycardSharedPopupContent", "type": "Item"}
sharedPopup_Password_Input = {"container": sharedPopup_Popup_Content, "objectName": "Password", "type": "TextField"}
sharedPopup_Password_Input = {"container": sharedPopup_Popup_Content, "objectName": "keycardPasswordInput", "type": "TextField"}
sharedPopup_Primary_Button = {"container": statusDesktop_mainWindow, "objectName": "PrimaryButton", "type": "StatusButton"}
# Transactions view

View File

@ -28,10 +28,10 @@ def step(context):
### ACTIONS region:
#########################
@When("the user adds watch only account \"|any|\" named \"|any|\"")
@When("the user adds watch only account \"|any|\" named \"|any|\" and authenticated using password \"|any|\"")
@verify_screenshot
def step(context, address, account_name):
_walletScreen.add_watch_only_account(account_name, address)
def step(context, address, account_name, root_password):
_walletScreen.add_watch_only_account(account_name, address, root_password)
@When("an account named \"|any|\" is generated and authenticated using password \"|any|\"")
def step(context, account_name, password):
@ -39,11 +39,11 @@ def step(context, account_name, password):
@When("an account named \"|any|\" is added via private key \"|any|\" and authenticated using password \"|any|\"")
def step(context, account_name, private_key, password):
_walletScreen.import_private_key(account_name, password, private_key)
_walletScreen.import_private_key(account_name, password, private_key)
@When("an account named \"|any|\" is added via imported seed phrase \"|any|\" and authenticated using password \"|any|\"")
@When("an account named \"|any|\" is added via imported seed phrase \"|any|\" and authenticated using password \"|any|\"")
def step(context, account_name, mnemonic, password):
_walletScreen.import_seed_phrase(account_name, password, mnemonic)
_walletScreen.import_seed_phrase(account_name, password, mnemonic)
@When("the user sends a transaction to himself from account \"|any|\" of \"|any|\" \"|any|\" on \"|any|\" with password \"|any|\"")
def step(context, account_name, amount, token, chain_name, password):

View File

@ -16,7 +16,6 @@ Feature: Status Desktop Transaction
** and the user opens wallet screen
** and the user accepts the signing phrase
@mayfail
Scenario Outline: The user sends a transaction
When the user sends a transaction to himself from account "Status account" of "<amount>" "<token>" on "<chain_name>" with password "qqqqqqqqqq"
Then the transaction is in progress
@ -24,9 +23,9 @@ Feature: Status Desktop Transaction
Examples:
| amount | token | chain_name |
| 0.1 | ETH | Ethereum Mainnet |
#| 0 | ETH | Goerli |
#| 1 | STT | Goerli |
#| 0 | STT | Goerli |
# | 1 | ETH | Goerli |
# | 1 | STT | Goerli |
# | 100 | STT | Goerli |
@mayfail
Scenario: The user registers an ENS name

View File

@ -2,7 +2,7 @@ Feature: Status Desktop Wallet
As a user I want to use the wallet
The feature start sequence is the following (setup on its own `bdd_hooks`):
The feature start sequence is the following (setup on its own `bdd_hooks`):
** given A first time user lands on the status desktop and generates new key
** when user signs up with username "tester123" and password "TesTEr16843/!@00"
@ -17,33 +17,28 @@ Feature: Status Desktop Wallet
Given the user opens wallet screen
And the user clicks on the first account
@mayfail
Scenario: The user can manage and observe a watch only account
When the user adds watch only account "0xea123F7beFF45E3C9fdF54B324c29DBdA14a639A" named "AccountWatch"
Scenario: The user can manage and observe a watch only account
When the user adds watch only account "0xea123F7beFF45E3C9fdF54B324c29DBdA14a639A" named "AccountWatch" and authenticated using password "TesTEr16843/!@00"
Then the new account "AccountWatch" is added
And the user has a positive balance of "ETH"
And the user has a positive balance of "ETH"
And the user has a positive balance of "SNT"
# And the collectibles are listed for the on
# And the transactions are listed for the added account
# And the collectibles are listed for the on
# And the transactions are listed for the added account
@mayfail
Scenario: The user imports a private key
When an account named "AccountPrivate" is added via private key "8da4ef21b864d2cc526dbdb2a120bd2874c36c9d0a1fb7f8c63d7f7a8b41de8f" and authenticated using password "TesTEr16843/!@00"
Then the new account "AccountPrivate" is added
@mayfail
Scenario: The user generates a new account from wallet and deletes it
Scenario: The user generates a new account from wallet and deletes it
When an account named "AccountGenerated" is generated and authenticated using password "TesTEr16843/!@00"
Then the new account "AccountGenerated" is added
When the user deletes the account "AccountGenerated" with password "TesTEr16843/!@00"
Then the account "AccountGenerated" is not in the list of accounts
@mayfail
Scenario: The user can import seed phrase
Scenario: The user can import seed phrase
When an account named "AccountSeed" is added via imported seed phrase "pelican chief sudden oval media rare swamp elephant lawsuit wheat knife initial" and authenticated using password "TesTEr16843/!@00"
Then the new account "AccountSeed" is added
@mayfail
Scenario: The user edits the default account
Given the user opens app settings screen
And the user opens the wallet settings
@ -51,7 +46,6 @@ Feature: Status Desktop Wallet
And the user edits default account to "Default" name and "#FFCA0F" color
Then the default account is updated to be named "DefaultStatus account" with color "#FFCA0F"
@mayfail
Scenario Outline: The user can manage a saved address
When the user adds a saved address named "<name>" and address "<address>"
And the user toggles favourite for the saved address with name "<name>"
@ -63,6 +57,6 @@ Feature: Status Desktop Wallet
When the user adds a saved address named "<name>" and address "<address>"
And the user edits a saved address with name "<name>" to "<new_name>"
Then the name "<new_name><name>" is in the list of saved addresses
Examples:
| name | address | new_name |
| bar | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | foo |
Examples:
| name | address | new_name |
| bar | 0x8397bc3c5a60a1883174f722403d63a8833312b7 | foo |

View File

@ -34,9 +34,9 @@ StatusSelect {
QtObject {
id: _internal
property string importSeedPhraseString : qsTr("Import new Seed Phrase")
property string importPrivateKeyString : qsTr("Generate from Private key")
property string addWatchOnlyAccountString : qsTr("Add a watch-only address")
property string importSeedPhraseID: "importNewSeedPhrase"
property string importPrivateKeyID: "generateFromPrivateKey"
property string addWatchOnlyAccountID: "watchOnlyAccount"
property var delegateModel: DelegateModel {
model: RootStore.generatedAccountsViewModel
@ -60,9 +60,9 @@ StatusSelect {
}
}
generatedAccountsModel.append({"name": qsTr("Add new"), "iconName": "", "derivedfrom": "", "isHeader": true, "keyUid": "", "migratedToKeycard": false})
generatedAccountsModel.append({"name": _internal.importSeedPhraseString, "iconName": "seed-phrase", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false})
generatedAccountsModel.append({"name": _internal.importPrivateKeyString, "iconName": "password", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false})
generatedAccountsModel.append({"name": _internal.addWatchOnlyAccountString, "iconName": "show", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false})
generatedAccountsModel.append({"name": qsTr("Import new Seed Phrase"), "iconName": "seed-phrase", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false, "objectName": _internal.importSeedPhraseID})
generatedAccountsModel.append({"name": qsTr("Generate from Private key"), "iconName": "password", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false, "objectName": _internal.importPrivateKeyID})
generatedAccountsModel.append({"name": qsTr("Add a watch-only address"), "iconName": "show", "derivedfrom": "", "isHeader": false, "keyUid": "", "migratedToKeycard": false, "objectName": _internal.addWatchOnlyAccountID})
}
}
}
@ -89,6 +89,7 @@ StatusSelect {
}
menuDelegate: StatusListItem {
id: defaultListItem
objectName: !!model.objectName ? model.objectName : ""
title: model.name
asset.name: model.iconName
tagsModel : model.generatedModel
@ -108,9 +109,9 @@ StatusSelect {
titleText.color: Theme.palette.indirectColor1
}
onClicked: {
selectAccountType.addAccountType = (model.name === _internal.importSeedPhraseString) ? Constants.AddAccountType.ImportSeedPhrase :
(model.name === _internal.importPrivateKeyString) ? Constants.AddAccountType.ImportPrivateKey :
(model.name === _internal.addWatchOnlyAccountString) ? Constants.AddAccountType.WatchOnly :
selectAccountType.addAccountType = (model.objectName === _internal.importSeedPhraseID) ? Constants.AddAccountType.ImportSeedPhrase :
(model.objectName === _internal.importPrivateKeyID) ? Constants.AddAccountType.ImportPrivateKey :
(model.objectName === _internal.addWatchOnlyAccountID) ? Constants.AddAccountType.WatchOnly :
Constants.AddAccountType.GenerateNew
selectedItem.title = model.name
selectedItem.asset.name = model.iconName

View File

@ -92,17 +92,17 @@ StatusModal {
function getDerivedAddressList() {
if (d.useFullyCustomPath) {
if(d.selectedAccountType === Constants.AddAccountType.ImportSeedPhrase
&& !!advancedSelection.expandableItem.path
&& !!advancedSelection.expandableItem.mnemonicText) {
RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText,
advancedSelection.expandableItem.path, numOfItems, pageNumber)
} else if(!!d.selectedPath && !!d.selectedAccountDerivedFromAddress
if(!!d.selectedPath && !!d.selectedAccountDerivedFromAddress
&& (d.password.length > 0)) {
RootStore.getDerivedAddressList(d.password, d.selectedAccountDerivedFromAddress,
d.selectedPath, numOfItems, pageNumber,
!(d.selectedKeyUidMigratedToKeycard || userProfile.isKeycardUser))
}
} else if(d.selectedAccountType === Constants.AddAccountType.ImportSeedPhrase
&& !!advancedSelection.expandableItem.path
&& !!advancedSelection.expandableItem.mnemonicText) {
RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText,
advancedSelection.expandableItem.path, numOfItems, pageNumber)
} else if(!!d.selectedPath && !!d.selectedAccountDerivedFromAddress
&& (d.password.length > 0)) {
RootStore.getDerivedAddress(d.password, d.selectedAccountDerivedFromAddress, d.selectedPath,
@ -334,11 +334,10 @@ StatusModal {
return qsTr("Add account")
}
enabled: {
if (!accountNameInput.valid ||
loading ||
(!d.useFullyCustomPath && !d.selectedAddressAvailable)) {
((root.authenticationNeeded && !d.useFullyCustomPath) && !d.selectedAddressAvailable)) {
return false
}
return advancedSelection.isValid

View File

@ -37,6 +37,8 @@ ColumnLayout {
signal calculateDerivedPath()
signal enterPressed()
objectName: "advancedAddAccountViewRoot"
function reset() {
//reset selectGeneratedAccount
selectGeneratedAccount.resetMe()
@ -103,6 +105,7 @@ ColumnLayout {
SelectGeneratedAccount {
id: selectGeneratedAccount
objectName: "selectGeneratedAccount"
Component.onCompleted: {
advancedSection.addAccountType = Qt.binding(function() {return addAccountType})
advancedSection.derivedFromAddress = Qt.binding(function() {return derivedFromAddress})
@ -133,6 +136,8 @@ ColumnLayout {
StatusInput {
id: addressInput
input.placeholder.objectName: "advancedAddAccountViewAddressInputPlaceholder"
input.objectName: "advancedAddAccountViewAddressInput"
visible: advancedSection.addAccountType === Constants.AddAccountType.WatchOnly && advancedSection.visible
placeholderText: qsTr("Enter address...")
label: qsTr("Account address")
@ -160,7 +165,7 @@ ColumnLayout {
readonly property int itemWidth: (advancedSection.width - Style.current.bigPadding) * 0.5
DerivationPathsPanel {
DerivationPathsPanel {
id: derivationPathsPanel
useFullyCustomPath: fullyCustomPathCheckBox.checked
Layout.preferredWidth: parent.itemWidth
@ -187,6 +192,8 @@ ColumnLayout {
StatusCheckBox {
id: fullyCustomPathCheckBox
objectName: "fullyCustomPathCheckBox"
visible: advancedSection.addAccountType === Constants.AddAccountType.GenerateNew
Layout.preferredWidth: advancedSection.width
text: qsTr("I acknowledge that by adding an account out of the default Status derivation path I will not be able to migrate a keypair to a Keycard")
onToggled: {

View File

@ -79,7 +79,7 @@ Item {
StatusPasswordInput {
id: password
objectName: "Password"
objectName: "keycardPasswordInput"
Layout.alignment: Qt.AlignHCenter
signingPhrase: root.sharedKeycardModule.getSigningPhrase()
placeholderText: qsTr("Password")