From 5bff89a58fcd96644cedd19a0ac0f85d83248fb4 Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Tue, 2 Apr 2024 17:59:56 +0300 Subject: [PATCH] e2e: wallet tests added --- test/appium/support/api/network_api.py | 109 ++--- test/appium/support/testrail_report.py | 1 + test/appium/tests/critical/test_wallet.py | 213 +++++++++ .../{test_wallet.py => test_wallet_old.py} | 0 test/appium/views/base_element.py | 9 +- test/appium/views/base_view.py | 17 +- test/appium/views/home_view.py | 31 +- test/appium/views/profile_view.py | 1 - test/appium/views/wallet_view.py | 452 ++++-------------- test/appium/views/wallet_view_old_ui.py | 378 +++++++++++++++ 10 files changed, 792 insertions(+), 419 deletions(-) create mode 100644 test/appium/tests/critical/test_wallet.py rename test/appium/tests/old_ui/wallet_and_tx/{test_wallet.py => test_wallet_old.py} (100%) create mode 100644 test/appium/views/wallet_view_old_ui.py diff --git a/test/appium/support/api/network_api.py b/test/appium/support/api/network_api.py index ee68f48a29..0c5a6da7bd 100644 --- a/test/appium/support/api/network_api.py +++ b/test/appium/support/api/network_api.py @@ -1,59 +1,53 @@ import logging +import time +from decimal import Decimal +from json import JSONDecodeError +from os import environ from typing import List import pytest import requests -import time -from json import JSONDecodeError -from decimal import Decimal -from os import environ +from selenium.common import TimeoutException + import tests -class NetworkApi(object): +class NetworkApi: def __init__(self): - self.network_url = 'http://api-goerli.etherscan.io/api?' - self.headers = { - 'User-Agent':"Mozilla\\5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit\\537.36 (KHTML, like Gecko) Chrome\\7" - "7.0.3865.90 Safari\\537.36", } - self.chat_bot_url = 'http://offsite.chat:8099' + self.network_url = 'http://api-sepolia.etherscan.io/api' self.api_key = environ.get('ETHERSCAN_API_KEY') def log(self, text: str): tests.test_suite_data.current_test.testruns[-1].steps.append(text) logging.info(text) - def send_etherscan_request(self, method, extracted_param: str): - for attempt in range(3): - try: - response = requests.request('GET', url=method, headers=self.headers).json() - if response: - return response[extracted_param] - except TypeError as e: - self.log("Check response from etherscan API. Returned values do not match expected. %s" % str(e)) - except JSONDecodeError as e: - self.log("No valid JSON response from Etherscan: %s " % str(e)) - pass - time.sleep(30) + def send_etherscan_request(self, params): + params['apikey'] = self.api_key + try: + response = requests.get(url=self.network_url, params=params).json() + if response: + return response['result'] + except TypeError as e: + self.log("Check response from etherscan API. Returned values do not match expected. %s" % str(e)) + except JSONDecodeError as e: + self.log("No valid JSON response from Etherscan: %s " % str(e)) + pass def get_token_transactions(self, address: str) -> List[dict]: - method = self.network_url + 'module=account&action=tokentx&address=0x%s&sort=desc&apikey=%s' % ( - address, self.api_key) - return self.send_etherscan_request(method, 'result') + params = {'module': 'account', 'action': 'tokentx', 'address': address, 'sort': 'desc'} + return self.send_etherscan_request(params) def get_transactions(self, address: str) -> List[dict]: - method = self.network_url + 'module=account&action=txlist&address=0x%s&sort=desc&apikey=%s' % (address, self.api_key) - return self.send_etherscan_request(method, 'result') + params = {'module': 'account', 'action': 'txlist', 'address': address, 'sort': 'desc'} + return self.send_etherscan_request(params) def is_transaction_successful(self, transaction_hash: str) -> int: - method = self.network_url + 'module=transaction&action=getstatus&txhash=%s' % transaction_hash - return not int(requests.request('GET', url=method, headers=self.headers).json()['result']['isError']) + params = {'module': 'transaction', 'action': 'getstatus', 'txhash': transaction_hash} + return not int(self.send_etherscan_request(params)['isError']) - def get_balance(self, address): - address = '0x' + address - method = self.network_url + 'module=account&action=balance&address=%s&tag=latest&apikey=%s' % ( - address, self.api_key) - balance = self.send_etherscan_request(method, 'result') + def get_balance(self, address: str): + params = {'module': 'account', 'action': 'balance', 'address': address, 'tag': 'latest'} + balance = self.send_etherscan_request(params) if balance: self.log('Balance is %s Gwei' % balance) return int(balance) @@ -61,13 +55,12 @@ class NetworkApi(object): self.log('Cannot extract balance!') def get_latest_block_number(self) -> int: - method = self.network_url + 'module=proxy&action=eth_blockNumber' - return int(requests.request('GET', url=method).json()['result'], 0) + params = {'module': 'proxy', 'action': 'eth_blockNumber'} + return int(self.send_etherscan_request(params), 0) def find_transaction_by_hash(self, transaction_hash: str): - method = self.network_url + 'module=transaction&action=gettxreceiptstatus&txhash=%s&apikey=%s' % ( - transaction_hash, self.api_key) - result = self.send_etherscan_request(method, 'result') + params = {'module': 'transaction', 'action': 'gettxreceiptstatus', 'txhash': transaction_hash} + result = self.send_etherscan_request(params) if result: final_status = True @@ -86,13 +79,14 @@ class NetworkApi(object): while True: if counter >= wait_time: for entry in range(0, 5): - self.log('Transaction #%s, amount is %s' %(entry+1, float(int(transactions[entry]['value']) / 10 ** decimals))) + self.log('Transaction #%s, amount is %s' % ( + entry + 1, float(int(transactions[entry]['value']) / 10 ** decimals))) self.log(str(transactions[entry])) pytest.fail( 'Transaction with amount %s is not found in list of %s, address is %s during %ss' % (amount, additional_info, address, wait_time)) else: - self.log("Finding tx in %s, attempt #%s" % (additional_info, str(int(counter / 30)+1))) + self.log("Finding tx in %s, attempt #%s" % (additional_info, str(int(counter / 30) + 1))) try: if token: transactions = self.get_token_transactions(address) @@ -126,7 +120,8 @@ class NetworkApi(object): if int(transaction['confirmations']) >= confirmations: return time.sleep(20) - pytest.fail('Transaction with amount %s was not confirmed, address is %s, still has %s confirmations' % (amount, address, int(transaction['confirmations']))) + pytest.fail('Transaction with amount %s was not confirmed, address is %s, still has %s confirmations' % ( + amount, address, int(transaction['confirmations']))) def verify_balance_is_updated(self, initial_balance, recipient_address, wait_time=360): counter = 0 @@ -141,10 +136,13 @@ class NetworkApi(object): self.log('Balance is updated!') return - def verify_balance_is(self, expected_balance: int, recipient_address: str, errors: list): - balance = self.get_balance(recipient_address) - if balance / 1000000000000000000 != expected_balance: - errors.append('Recipients balance is not updated on etherscan') + def wait_for_balance_to_be(self, address: str, expected_balance: int, less: bool = True): + for _ in range(5): + balance = self.get_balance(address) / 1000000000000000000 + if (less and balance < expected_balance) or (not less and balance > expected_balance): + return + time.sleep(10) + raise TimeoutException('Balance is not updated on Etherscan') # Do not use until web3 update # def faucet(self, address): @@ -160,7 +158,6 @@ class NetworkApi(object): # address = "0x" + address # w3.donate_testnet_eth(address=address, amount=0.01, inscrease_default_gas_price=10) - # def get_donate(self, address, external_faucet=False, wait_time=300): # initial_balance = self.get_balance(address) # counter = 0 @@ -180,11 +177,6 @@ class NetworkApi(object): # self.log('Got %s Gwei for %s' % (self.get_balance(address), address)) # return - def start_chat_bot(self, chat_name: str, messages_number: int, interval: int = 1) -> list: - url = '%s/ping/%s?count=%s&interval=%s' % (self.chat_bot_url, chat_name, messages_number, interval) - text = requests.request('GET', url).text - return [i.split(maxsplit=5)[-1].strip('*') for i in text.splitlines()] - def get_rounded_balance(self, fetched_balance, actual_balance): fetched_balance, actual_balance = str(fetched_balance), str(actual_balance) # get actual number of decimals on account balance @@ -192,15 +184,14 @@ class NetworkApi(object): rounded_balance = round(float(actual_balance), decimals) return rounded_balance - def get_tx_param_by_hash(self, hash: str, param: str): - method = self.network_url + 'module=proxy&action=eth_getTransactionByHash&txhash=%s&apikey=%s' % ( - hash, self.api_key) - res = self.send_etherscan_request(method, 'result') + def get_tx_param_by_hash(self, transaction_hash: str, param: str): + params = {'module': 'proxy', 'action': 'eth_getTransactionByHash', 'txhash': transaction_hash} + res = self.send_etherscan_request(params) return int(res[param], 16) def get_custom_fee_tx_params(self, hash: str): return { - 'fee_cap': str(self.get_tx_param_by_hash(hash, 'maxFeePerGas')/1000000000), - 'tip_cap': str(self.get_tx_param_by_hash(hash, 'maxPriorityFeePerGas')/1000000000), + 'fee_cap': str(self.get_tx_param_by_hash(hash, 'maxFeePerGas') / 1000000000), + 'tip_cap': str(self.get_tx_param_by_hash(hash, 'maxPriorityFeePerGas') / 1000000000), 'gas_limit': str(self.get_tx_param_by_hash(hash, 'gas')) - } \ No newline at end of file + } diff --git a/test/appium/support/testrail_report.py b/test/appium/support/testrail_report.py index eafd1b0153..a80cc75428 100644 --- a/test/appium/support/testrail_report.py +++ b/test/appium/support/testrail_report.py @@ -129,6 +129,7 @@ class TestrailReport(BaseTestReport): test_cases['pr']['community_multiple'] = 50982 test_cases['pr']['activity_centre_contact_request'] = 50984 test_cases['pr']['activity_centre_other'] = 51005 + test_cases['pr']['wallet'] = 59443 ## Nightly e2e # test_cases['nightly']['activity_center'] = 736 diff --git a/test/appium/tests/critical/test_wallet.py b/test/appium/tests/critical/test_wallet.py new file mode 100644 index 0000000000..379bbf9cd1 --- /dev/null +++ b/test/appium/tests/critical/test_wallet.py @@ -0,0 +1,213 @@ +import time + +import pytest +from _pytest.outcomes import Failed +from selenium.common import TimeoutException + +from base_test_case import MultipleSharedDeviceTestCase, create_shared_drivers +from support.api.network_api import NetworkApi +from tests import marks, run_in_parallel +from users import transaction_recipients +from views.sign_in_view import SignInView + + +@pytest.mark.xdist_group(name="new_four_2") +@marks.new_ui_critical +class TestWalletMultipleDevice(MultipleSharedDeviceTestCase): + + def prepare_devices(self): + self.network_api = NetworkApi() + self.drivers, self.loop = create_shared_drivers(2) + self.sign_in_1, self.sign_in_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1]) + self.sender, self.receiver = transaction_recipients['J'], transaction_recipients['K'] + self.sender_username, self.receiver_username = 'sender', 'receiver' + self.loop.run_until_complete( + run_in_parallel(((self.sign_in_1.recover_access, {'passphrase': self.sender['passphrase'], + 'username': self.sender_username}), + (self.sign_in_2.recover_access, {'passphrase': self.receiver['passphrase'], + 'username': self.receiver_username})))) + self.home_1, self.home_2 = self.sign_in_1.get_home_view(), self.sign_in_2.get_home_view() + self.wallet_1, self.wallet_2 = self.sign_in_1.get_wallet_view(), self.sign_in_2.get_wallet_view() + + # ToDo: Add verification of Activity tabs when the feature is ready in the next 2 tests: + + def _get_balances_before_tx(self): + sender_balance = self.network_api.get_balance(self.sender['address']) + receiver_balance = self.network_api.get_balance(self.receiver['address']) + self.wallet_1.just_fyi("Getting ETH amount in the wallet of the sender before transaction") + self.wallet_1.wallet_tab.click() + self.wallet_1.get_account_element().click() + eth_amount_sender = self.wallet_1.get_asset(asset_name='Ether').get_amount() + self.wallet_2.just_fyi("Getting ETH amount in the wallet of the receiver before transaction") + self.wallet_2.wallet_tab.click() + self.wallet_2.get_account_element().click() + eth_amount_receiver = self.wallet_2.get_asset(asset_name='Ether').get_amount() + return sender_balance, receiver_balance, eth_amount_sender, eth_amount_receiver + + def _check_balances_after_tx(self, amount_to_send, sender_balance, receiver_balance, eth_amount_sender, + eth_amount_receiver): + try: + self.network_api.wait_for_balance_to_be(address=self.sender['address'], + expected_balance=sender_balance - amount_to_send) + except TimeoutException: + self.errors.append("Sender balance was not updated") + try: + self.network_api.wait_for_balance_to_be(address=self.receiver['address'], + expected_balance=receiver_balance + amount_to_send) + except TimeoutException: + self.errors.append("Receiver balance was not updated") + + def wait_for_wallet_balance_to_update(wallet_view, user_name, initial_eth_amount): + wallet_view.just_fyi("Getting ETH amount in the wallet of the %s after transaction" % user_name) + if user_name == 'sender': + exp_amount = round(initial_eth_amount - amount_to_send, 4) + else: + exp_amount = round(initial_eth_amount + amount_to_send, 4) + + # for _ in range(12): # ToDo: 120 sec wait time, enable when autoupdate feature is ready + wallet_view.wallet_tab.click() + new_eth_amount = round(wallet_view.get_asset(asset_name='Ether').get_amount(), 4) + if user_name == 'sender' and new_eth_amount < exp_amount: + return + if user_name == 'receiver' and new_eth_amount >= exp_amount: + return + # wallet_view.chats_tab.click() + # time.sleep(10) + self.errors.append( + "Eth amount in the %ss wallet is %s but should be %s" % (user_name, new_eth_amount, exp_amount)) + + # ToDo: disable relogin when autoupdate feature ia ready + self.home_1.just_fyi("Relogin for getting an updated balance") + self.home_2.just_fyi("Relogin for getting an updated balance") + for _ in range(6): # just waiting 1 minute here to be sure that balances are updated + self.wallet_1.wallet_tab.click() + self.wallet_2.wallet_tab.click() + time.sleep(10) + self.loop.run_until_complete( + run_in_parallel(((self.home_1.reopen_app,), + (self.home_2.reopen_app,)))) + self.loop.run_until_complete( + run_in_parallel(((wait_for_wallet_balance_to_update, {'wallet_view': self.wallet_1, + 'user_name': self.sender_username, + 'initial_eth_amount': eth_amount_sender}), + (wait_for_wallet_balance_to_update, {'wallet_view': self.wallet_2, + 'user_name': self.receiver_username, + 'initial_eth_amount': eth_amount_receiver})))) + + self.errors.verify_no_errors() + + @marks.testrail_id(727229) + def test_wallet_send_eth(self): + sender_balance, receiver_balance, eth_amount_sender, eth_amount_receiver = self._get_balances_before_tx() + + self.wallet_2.close_account_button.click() + self.wallet_2.chats_tab.click() + + self.wallet_1.just_fyi("Sending funds from wallet") + amount_to_send = 0.0001 + self.wallet_1.send_asset(address=self.receiver['address'], asset_name='Ether', amount=amount_to_send) + self.wallet_1.close_account_button.click_until_presence_of_element(self.home_1.show_qr_code_button) + + self._check_balances_after_tx(amount_to_send, sender_balance, receiver_balance, eth_amount_sender, + eth_amount_receiver) + + @marks.testrail_id(727230) + def test_wallet_send_asset_from_drawer(self): + sender_balance, receiver_balance, eth_amount_sender, eth_amount_receiver = self._get_balances_before_tx() + self.wallet_2.close_account_button.click() + self.wallet_2.chats_tab.click() + + self.wallet_1.just_fyi("Sending asset from drawer") + amount_to_send = 0.0001 + self.wallet_1.send_asset_from_drawer(address=self.receiver['address'], asset_name='Ether', + amount=amount_to_send) + self.wallet_1.close_account_button.click_until_presence_of_element(self.home_1.show_qr_code_button) + + self._check_balances_after_tx(amount_to_send, sender_balance, receiver_balance, eth_amount_sender, + eth_amount_receiver) + + +@pytest.mark.xdist_group(name="new_one_2") +@marks.new_ui_critical +class TestWalletOneDevice(MultipleSharedDeviceTestCase): + + def prepare_devices(self): + self.network_api = NetworkApi() + self.drivers, self.loop = create_shared_drivers(1) + self.sign_in_view = SignInView(self.drivers[0]) + self.sign_in_view.create_user() + self.home_view = self.sign_in_view.get_home_view() + self.wallet_view = self.home_view.wallet_tab.click() + + @marks.testrail_id(727231) + def test_wallet_add_remove_regular_account(self): + self.wallet_view.just_fyi("Adding new regular account") + new_account_name = "New Account" + self.wallet_view.add_regular_account(account_name=new_account_name) + + if self.wallet_view.account_name_text.text != new_account_name: + pytest.fail("New account is not created") + self.wallet_view.account_emoji_button.click_until_presence_of_element(self.wallet_view.copy_address_button) + self.wallet_view.share_address_button.click() + new_wallet_address = self.wallet_view.sharing_text_native.text + self.wallet_view.click_system_back_button() + self.wallet_view.close_account_button.click_until_presence_of_element(self.home_view.show_qr_code_button) + + self.wallet_view.just_fyi("Checking that the new wallet is added to the Sare QR Code menu") + self.home_view.show_qr_code_button.click() + self.home_view.share_wallet_tab_button.click() + if self.home_view.account_name_text.text != 'Account 1': + self.errors.append("Incorrect first account is shown on Share QR Code menu") + self.home_view.qr_code_image_element.swipe_left_on_element() + try: + self.home_view.account_name_text.wait_for_element_text(text=new_account_name, wait_time=3) + if self.home_view.copy_wallet_address() != new_wallet_address.split(':')[-1]: + self.home_view.driver.fail("Incorrect address") + except Failed: + self.errors.append("Can't swipe between accounts, newly added account is not shown") + self.home_view.click_system_back_button() + + self.wallet_view.just_fyi("Removing newly added account") + if self.wallet_view.get_account_element(account_name=new_account_name).is_element_displayed(): + self.wallet_view.remove_account(account_name=new_account_name) + if self.wallet_view.get_account_element(account_name=new_account_name).is_element_displayed(): + self.errors.append("Account was not removed from wallet") + else: + self.errors.append("Newly added account is not shown in the accounts list") + + self.errors.verify_no_errors() + + @marks.testrail_id(727232) + def test_wallet_add_remove_watch_only_account(self): + self.wallet_view.just_fyi("Adding new watch only account") + new_account_name = "Account to watch" + address_to_watch = "0x8d2413447ff297d30bdc475f6d5cb00254685aae" + self.wallet_view.add_watch_only_account(address=address_to_watch, account_name=new_account_name) + + if self.wallet_view.account_name_text.text != new_account_name: + pytest.fail("Account to watch was not added") + self.wallet_view.close_account_button.click_until_presence_of_element(self.home_view.show_qr_code_button) + + self.wallet_view.just_fyi("Checking that the new wallet is added to the Sare QR Code menu") + self.home_view.show_qr_code_button.click() + self.home_view.share_wallet_tab_button.click() + if self.home_view.account_name_text.text != 'Account 1': + self.errors.append("Incorrect first account is shown on Share QR Code menu") + self.home_view.qr_code_image_element.swipe_left_on_element() + try: + self.home_view.account_name_text.wait_for_element_text(text=new_account_name, wait_time=3) + if self.home_view.copy_wallet_address() != address_to_watch: + self.home_view.driver.fail("Incorrect address") + except Failed: + self.errors.append("Can't swipe between accounts, account to watch is not shown") + self.home_view.click_system_back_button() + + self.wallet_view.just_fyi("Removing account to watch") + if self.wallet_view.get_account_element(account_name=new_account_name).is_element_displayed(): + self.wallet_view.remove_account(account_name=new_account_name, watch_only=True) + if self.wallet_view.get_account_element(account_name=new_account_name).is_element_displayed(): + self.errors.append("Account was not removed from wallet") + else: + self.errors.append("Watch only account is not shown in the accounts list") + + self.errors.verify_no_errors() diff --git a/test/appium/tests/old_ui/wallet_and_tx/test_wallet.py b/test/appium/tests/old_ui/wallet_and_tx/test_wallet_old.py similarity index 100% rename from test/appium/tests/old_ui/wallet_and_tx/test_wallet.py rename to test/appium/tests/old_ui/wallet_and_tx/test_wallet_old.py diff --git a/test/appium/views/base_element.py b/test/appium/views/base_element.py index db254dcd75..126fa8ae8c 100644 --- a/test/appium/views/base_element.py +++ b/test/appium/views/base_element.py @@ -190,7 +190,7 @@ class BaseElement(object): self.driver.fail(message if message else "`%s` is not equal to expected `%s` in %s sec" % ( element_text, text, wait_time)) - def scroll_to_element(self, depth: int = 9, direction='down'): + def scroll_to_element(self, depth: int = 9, direction='down', down_start_y=0.4, down_end_y=0.05): self.driver.info('Scrolling %s to %s' % (direction, self.name)) for _ in range(depth): try: @@ -198,7 +198,7 @@ class BaseElement(object): except NoSuchElementException: size = self.driver.get_window_size() if direction == 'down': - self.driver.swipe(500, size["height"] * 0.4, 500, size["height"] * 0.05) + self.driver.swipe(500, size["height"] * down_start_y, 500, size["height"] * down_end_y) else: self.driver.swipe(500, size["height"] * 0.25, 500, size["height"] * 0.8) else: @@ -303,12 +303,13 @@ class BaseElement(object): width, height = size['width'], size['height'] self.driver.swipe(start_x=x + width * 0.75, start_y=y + height / 2, end_x=x, end_y=y + height / 2) - def swipe_right_on_element(self, width_percentage=0.9): + def swipe_right_on_element(self, width_percentage=0.9, start_x=0): self.driver.info("Swiping right on element %s" % self.name) location, size = self.get_element_coordinates() x, y = location['x'], location['y'] width, height = size['width'], size['height'] - self.driver.swipe(start_x=x, start_y=y + height / 2, end_x=x + width * width_percentage, end_y=y + height / 2) + self.driver.swipe(start_x=x + start_x, start_y=y + height / 2, end_x=x + width * width_percentage, + end_y=y + height / 2) def swipe_to_web_element(self, depth=700): element = self.find_element() diff --git a/test/appium/views/base_view.py b/test/appium/views/base_view.py index 8adb7e284d..0900f2766a 100644 --- a/test/appium/views/base_view.py +++ b/test/appium/views/base_view.py @@ -94,6 +94,10 @@ class WalletTab(TabButton): def __init__(self, driver): super().__init__(driver, accessibility_id="wallet-stack-tab") + def navigate(self): + from views.wallet_view import WalletView + return WalletView(self.driver) + class BrowserTab(TabButton): def __init__(self, driver): @@ -128,11 +132,11 @@ class WalletButton(TabButton): super().__init__(driver, xpath="//*[contains(@content-desc,'tab, 3 out of 5')]") def navigate(self): - from views.wallet_view import WalletView + from views.wallet_view_old_ui import WalletView return WalletView(self.driver) def click(self): - from views.wallet_view import WalletView + from views.wallet_view_old_ui import WalletView self.click_until_presence_of_element(WalletView(self.driver).multiaccount_more_options) return self.navigate() @@ -245,6 +249,14 @@ class SignInPhraseText(Text): return self.text.split() +class SlideButton(Button): + def __init__(self, driver): + super().__init__(driver, xpath="//*[@resource-id='slide-button-track']") + + def slide(self): + self.swipe_right_on_element(width_percentage=1.3, start_x=100) + + class BaseView(object): def __init__(self, driver): self.driver = driver @@ -318,6 +330,7 @@ class BaseView(object): # checkboxes and toggles self.checkbox_button = CheckBox(self.driver, accessibility_id="checkbox-off") + self.slide_button_track = SlideButton(self.driver) # external browser self.open_in_status_button = OpenInStatusButton(self.driver) diff --git a/test/appium/views/home_view.py b/test/appium/views/home_view.py index 9c5d4bb4f6..29dac0840e 100644 --- a/test/appium/views/home_view.py +++ b/test/appium/views/home_view.py @@ -225,6 +225,15 @@ class MuteButton(Button): return self.find_element().find_element(by=MobileBy.XPATH, value="//android.widget.TextView[2]").text +class ShareQRCodeInfoText(Text): + def __init__(self, driver): + super().__init__(driver, accessibility_id="share-qr-code-info-text") + + @property + def text(self): + return self.find_element().find_element(by=MobileBy.XPATH, value="/android.widget.TextView").text + + class HomeView(BaseView): def __init__(self, driver): super().__init__(driver) @@ -329,10 +338,16 @@ class HomeView(BaseView): self.mark_all_read_activity_button = Button(self.driver, translation_id="mark-all-notifications-as-read") # Share tab + self.share_qr_code_info_text = ShareQRCodeInfoText(self.driver) self.link_to_profile_button = Button(self.driver, accessibility_id="link-to-profile") self.link_to_profile_text = Text(self.driver, accessibility_id="share-qr-code-info-text") self.close_share_tab_button = Button(self.driver, accessibility_id="close-shell-share-tab") - + self.qr_code_image_element = BaseElement(self.driver, accessibility_id='share-qr-code') + self.share_wallet_tab_button = Button(self.driver, accessibility_id="Wallet") + self.account_avatar = BaseElement(self.driver, accessibility_id="account-avatar") + self.account_name_text = Text( + self.driver, xpath="//*[@content-desc='link-to-profile']/preceding-sibling::android.widget.TextView") + self.share_link_to_profile_button = Button(self.driver, accessibility_id='link-to-profile') # Discover communities self.community_card_item = BaseElement(self.driver, accessibility_id="community-card-item") @@ -571,3 +586,17 @@ class HomeView(BaseView): link_to_profile = self.get_link_to_profile() self.click_system_back_button() return link_to_profile.split("#")[-1] + + def copy_wallet_address(self): + self.share_link_to_profile_button.click() + address = self.sharing_text_native.text + self.click_system_back_button() + return address + + def get_wallet_address(self): + self.show_qr_code_button.click() + self.share_wallet_tab_button.click() + self.account_avatar.wait_for_visibility_of_element() + address = self.copy_wallet_address() + self.click_system_back_button() + return address diff --git a/test/appium/views/profile_view.py b/test/appium/views/profile_view.py index f78c7f0824..5ca7b18a92 100644 --- a/test/appium/views/profile_view.py +++ b/test/appium/views/profile_view.py @@ -292,7 +292,6 @@ class ProfileView(BaseView): self.syncing_button = Button(self.driver, accessibility_id="icon, Syncing, label-component, icon") self.sync_plus_button = Button(self.driver, xpath="//*[@text='Syncing']/following-sibling::android.view.ViewGroup[1]") - self.slide_button_track = Button(self.driver, xpath="//*[@resource-id='slide-button-track']") # Keycard self.keycard_button = Button(self.driver, accessibility_id="keycard-button") diff --git a/test/appium/views/wallet_view.py b/test/appium/views/wallet_view.py index 51e579dd2f..4bb27d4fb1 100644 --- a/test/appium/views/wallet_view.py +++ b/test/appium/views/wallet_view.py @@ -1,378 +1,126 @@ -import time +import pytest from tests import common_password -from views.base_element import Button, Text, EditBox, SilentButton, CheckBox +from views.base_element import Button, EditBox, Text from views.base_view import BaseView +from views.home_view import HomeView +from views.sign_in_view import SignInView -class TransactionHistoryButton(Button): - def __init__(self, driver): - super().__init__(driver, accessibility_id="History-item-button") +class AssetElement(Button): - def navigate(self): - from views.transactions_view import TransactionsView - return TransactionsView(self.driver) - - -class AssetCheckBox(CheckBox): def __init__(self, driver, asset_name): - super().__init__(driver, xpath="//*[@text='%s']" % asset_name) + self.asset_name = asset_name + self.locator = "//android.view.ViewGroup[@content-desc='container']/android.widget.TextView[@text='%s']" % \ + self.asset_name + super().__init__(driver=driver, xpath=self.locator) - def enable(self): - self.scroll_to_element(12) - super().enable() - - -class BackupRecoveryPhrase(Button): - def __init__(self, driver): - super().__init__(driver, translation_id="wallet-backup-recovery-title") - - def navigate(self): - from views.profile_view import ProfileView - return ProfileView(self.driver) - - -class AccountElementButton(SilentButton): - def __init__(self, driver, account_name): - super().__init__(driver, xpath="//*[@content-desc='accountcard%s']" % account_name) - - def color_matches(self, expected_color_image_name: str): - amount_text = Text(self.driver, xpath="%s//*[@content-desc='account-total-value']" % self.locator) - amount_text.wait_for_element_text('...', 60) - return not amount_text.is_element_differs_from_template(expected_color_image_name) - - -class SendTransactionButton(Button): - def __init__(self, driver): - super().__init__(driver, translation_id="wallet-send") - - def navigate(self): - from views.send_transaction_view import SendTransactionView - return SendTransactionView(self.driver) - - -class SendTransactionFromMainButton(Button): - def __init__(self, driver): - super().__init__(driver, accessibility_id="send-transaction-button") - - def navigate(self): - from views.send_transaction_view import SendTransactionView - return SendTransactionView(self.driver) - - -class ReceiveTransactionButton(Button): - def __init__(self, driver): - super().__init__(driver, translation_id="receive") - - def navigate(self): - from views.send_transaction_view import SendTransactionView - return SendTransactionView(self.driver) - - -class AddCustomTokenButton(Button): - def __init__(self, driver): - super().__init__(driver, translation_id="add-custom-token") - - def navigate(self): - from views.add_custom_token_view import AddCustomTokenView - return AddCustomTokenView(self.driver) - - -class AccountColorButton(Button): - def __init__(self, driver): - super().__init__(driver, translation_id="account-color", suffix="/following-sibling::android.view.ViewGroup[1]") - - def select_color_by_position(self, position: int): - self.click() - self.driver.find_element_by_xpath( - "((//android.widget.ScrollView)[1]/*/*)[%s]" % str(position + 1)).click() + def get_amount(self): + element = Text(self.driver, xpath=self.locator + "/../android.widget.TextView[3]") + element.scroll_to_element(down_start_y=0.89, down_end_y=0.8) + try: + amount = element.text.split()[0] + if '<' in amount: + return 0 + else: + return float(amount) + except ValueError: + pytest.fail("Cannot get %s amount" % self.asset_name) class WalletView(BaseView): def __init__(self, driver): super().__init__(driver) + # Wallet view + self.collectibles_tab = Button(self.driver, accessibility_id='Collectibles') + self.add_account_button = Button(self.driver, accessibility_id='add-account') - self.send_transaction_button = SendTransactionButton(self.driver) - self.send_transaction_from_main_screen = SendTransactionFromMainButton(self.driver) - self.transaction_history_button = TransactionHistoryButton(self.driver) - self.usd_total_value = Text(self.driver, accessibility_id="total-amount-value-text") + # Account adding + # ToDo: add unique accessibility ids for the next 2 elements: + self.create_account_button = HomeView(self.driver).start_a_new_chat_bottom_sheet_button + self.add_account_to_watch = HomeView(self.driver).add_a_contact_chat_bottom_sheet_button + self.address_to_watch_input = EditBox(self.driver, accessibility_id='add-address-to-watch') + self.account_has_activity_label = Text(self.driver, accessibility_id='account-has-activity') + self.add_account_continue_button = Button(self.driver, accessibility_id='Continue') + self.add_watched_address_button = Button(self.driver, accessibility_id='confirm-button-label') - self.receive_transaction_button = ReceiveTransactionButton(self.driver) - self.options_button = Button(self.driver, accessibility_id="options-menu-button") - self.manage_assets_button = Button(self.driver, accessibility_id="wallet-manage-assets") - self.manage_accounts_button = Button(self.driver, accessibility_id="wallet-manage-accounts") - self.scan_tokens_button = Button(self.driver, accessibility_id="wallet-scan-token") - self.all_assets_full_names = Text(self.driver, - xpath="//*[@content-desc='checkbox-off']/../android.widget.TextView[1]") - self.all_assets_symbols = Button(self.driver, - xpath="//*[@content-desc='checkbox-off']/../android.widget.TextView[2]") - self.currency_item_text = Text(self.driver, xpath="//*[@content-desc='currency-item']//android.widget.TextView") + # Account view + self.close_account_button = Button(self.driver, accessibility_id='top-bar') + self.account_name_text = Text( + self.driver, xpath="//*[@content-desc='account-avatar']/../following-sibling::android.widget.TextView[1]") + self.account_emoji_button = Button(self.driver, accessibility_id='account-emoji') + self.send_button = Button(self.driver, accessibility_id='send') + self.copy_address_button = Button(self.driver, accessibility_id='copy-address') + self.share_address_button = Button(self.driver, accessibility_id='share-account') + self.remove_account_button = Button(self.driver, accessibility_id='remove-account') + self.derivation_path_note_checkbox = Button(self.driver, accessibility_id='checkbox-off') - self.address_text = Text(self.driver, accessibility_id="address-text") + # Sending transaction + self.address_text_input = EditBox(self.driver, accessibility_id='address-text-input') + self.continue_button = Button(self.driver, accessibility_id='continue-button') + self.amount_input = EditBox(self.driver, xpath="//android.widget.EditText") + self.confirm_button = Button(self.driver, accessibility_id='button-one') + self.done_button = Button(self.driver, accessibility_id='done') - self.remind_me_later_button = Button(self.driver, translation_id="remind-me-later") + def get_account_element(self, account_name: str = 'Account 1'): + return Button(self.driver, xpath="//android.view.ViewGroup[contains(@content-desc,'%s')]" % account_name) - self.total_amount_text = Text(self.driver, accessibility_id="total-amount-value-text") - self.currency_text = Text(self.driver, accessibility_id="total-amount-currency-text") - self.backup_recovery_phrase = BackupRecoveryPhrase(self.driver) - self.backup_recovery_phrase_warning_text = Text(self.driver, - accessibility_id="back-up-your-seed-phrase-warning") + def get_asset(self, asset_name: str): + element = AssetElement(driver=self.driver, asset_name=asset_name) + element.scroll_to_element(down_start_y=0.89, down_end_y=0.8) + return element - self.add_custom_token_button = AddCustomTokenButton(self.driver) + def select_asset(self, asset_name: str): + return Button(driver=self.driver, + xpath="//*[@content-desc='token-network']/android.widget.TextView[@text='%s']" % asset_name) - # elements for multiaccount - self.multiaccount_more_options = Button(self.driver, accessibility_id="accounts-more-options") - self.accounts_status_account = AccountElementButton(self.driver, account_name=self.status_account_name) - self.set_currency_button = Button(self.driver, translation_id="set-currency") - self.add_account_button = Button(self.driver, accessibility_id="add-new-account") - self.generate_an_account_button = Button(self.driver, accessibility_id="add-account-sheet-generate") - self.add_watch_only_address_button = Button(self.driver, accessibility_id="add-account-sheet-watch") - self.enter_a_seed_phrase_button = Button(self.driver, accessibility_id="add-account-sheet-seed") - self.enter_a_private_key_button = Button(self.driver, accessibility_id="add-account-sheet-private-key") - self.enter_address_input = EditBox(self.driver, accessibility_id="add-account-enter-watch-address") - self.enter_seed_phrase_input = EditBox(self.driver, accessibility_id="add-account-enter-seed") - self.enter_a_private_key_input = EditBox(self.driver, accessibility_id="add-account-enter-private-key") - self.delete_account_button = Button(self.driver, translation_id="delete-account") - self.enter_your_password_input = EditBox(self.driver, accessibility_id="add-account-enter-password") - self.account_name_input = EditBox(self.driver, accessibility_id="enter-account-name") - self.account_color_button = AccountColorButton(self.driver) - self.add_account_generate_account_button = Button(self.driver, - accessibility_id="add-account-add-account-button") - self.status_account_total_usd_value = Text(self.driver, accessibility_id="account-total-value") - self.scan_qr_button = Button(self.driver, accessibility_id="accounts-qr-code") - self.close_send_transaction_view_button = Button(self.driver, - xpath="//androidx.appcompat.widget.LinearLayoutCompat") - self.hide_account_button = Button(self.driver, accessibility_id="hide-account-button") + def slide_and_confirm_with_password(self): + self.slide_button_track.slide() + self.password_input.send_keys(common_password) + self.login_button.click() - # collectibles - self.collectibles_button = Button(self.driver, translation_id="wallet-collectibles") - self.nft_asset_button = Button(self.driver, accessibility_id="nft-asset") - self.set_collectible_as_profile_photo_button = Button(self.driver, accessibility_id="set-nft-as-pfp") - self.view_collectible_on_opensea_button = Button(self.driver, translation_id="view-on-opensea") + def confirm_transaction(self): + self.confirm_button.click_until_presence_of_element(self.slide_button_track) + self.slide_and_confirm_with_password() + self.done_button.click() - # individual account settings - self.account_settings_button = Button(self.driver, translation_id="account-settings") - self.apply_settings_button = Button(self.driver, translation_id="apply") - self.password_delete_account_input = EditBox(self.driver, - xpath='//*[@text="Password"]/following-sibling::*/android.widget.EditText') - self.delete_account_confirm_button = Button(self.driver, accessibility_id="delete-account-confirm") + def send_asset(self, address: str, asset_name: str, amount: float): + self.send_button.click() + self.address_text_input.send_keys(address) + self.continue_button.click_until_presence_of_element(self.collectibles_tab) + self.select_asset(asset_name).click() + self.amount_input.send_keys('{:f}'.format(amount).rstrip('0')) + self.confirm_transaction() - def wait_balance_is_equal_expected_amount(self, asset='ETH', expected_balance=0.1, wait_time=300, main_screen=True): - counter = 0 - while True: - if counter >= wait_time: - self.driver.fail('**Balance is not changed during %s seconds!**' % wait_time) - elif self.get_asset_amount_by_name(asset) != expected_balance: - counter += 10 - time.sleep(10) - self.swipe_down() - self.driver.info('Waiting %s seconds for %s balance update to be equal to %s' % ( - counter, asset, expected_balance)) - else: - self.driver.info('Balance for %s is equal to %s' % (asset, expected_balance)) - if main_screen: - if not self.accounts_status_account.is_element_displayed(): - self.accounts_status_account.scroll_to_element(direction='up') - return + def send_asset_from_drawer(self, address: str, asset_name: str, amount: float): + asset_element = self.get_asset(asset_name) + asset_element.long_press_element() + self.send_button.wait_for_elements() + self.send_button.find_elements()[0].click() + self.address_text_input.send_keys(address) + self.continue_button.click_until_presence_of_element(self.confirm_button) + self.amount_input.send_keys('{:f}'.format(amount).rstrip('0')) + self.confirm_transaction() - def wait_balance_is_changed(self, asset='ETH', initial_balance=0, wait_time=180, scan_tokens=False, navigate_to_home=True): - self.driver.info('Waiting %ss for %s updated balance' % (wait_time, asset)) - counter = 0 - while True: - if counter >= wait_time: - self.driver.fail( - 'Balance %s %s is not changed during %s seconds!' % (asset, initial_balance, wait_time)) - elif self.asset_by_name(asset).is_element_displayed() and self.get_asset_amount_by_name( - asset) == initial_balance: - if scan_tokens: - self.scan_tokens() - if (counter / 60).is_integer(): - self.pull_to_refresh() - counter += 20 - counter += 10 - time.sleep(10) - self.driver.info('Waiting %ss for %s updated balance' % (counter, asset)) - elif not self.asset_by_name(asset).is_element_displayed(10): - if scan_tokens: - self.scan_tokens() - self.swipe_up() - counter += 10 - time.sleep(10) - self.driver.info('Waiting %s seconds for %s to display asset' % (counter, asset)) - else: - self.driver.info('Initial "%s" is not equal expected balance "%s", it is updated!' % (initial_balance, - self.get_asset_amount_by_name(asset))) - if navigate_to_home: - self.wallet_button.double_click() - self.element_by_translation_id("wallet-total-value").scroll_to_element(direction='up') - return self - - def get_sign_in_phrase(self): - return ' '.join([element.text for element in self.sign_in_phrase.find_elements()]) - - def set_up_wallet_when_sending_tx(self): - self.driver.info("Setting up wallet") - phrase = self.sign_in_phrase.text - self.ok_got_it_button.click() - return phrase - - def get_wallet_address(self, account_name=''): - account_name = self.status_account_name if not account_name else account_name - self.driver.info("Getting wallet address for '%s'" % account_name) - self.wallet_account_by_name(account_name).click() - self.receive_transaction_button.click_until_presence_of_element(self.qr_code_image) - address = self.address_text.text - self.close_share_popup() - return address - - def wallet_account_by_name(self, account_name): - self.driver.info("Getting '%s' wallet account" % account_name) - return AccountElementButton(self.driver, account_name) - - def get_asset_amount_by_name(self, asset: str): - self.driver.info("Getting %s amount" % asset) - asset_value = SilentButton(self.driver, xpath="//android.view.ViewGroup[@content-desc=':%s-asset-value']" - "//android.widget.TextView[1]" % asset) - for _ in range(2): - if not asset_value.is_element_displayed(): - self.element = asset_value.scroll_to_element() - try: - value = float(asset_value.text.split()[0]) - self.driver.info("%s value is %s" % (asset, value)) - return value - except ValueError: - self.driver.info("No value for %s" % asset) - return 0.0 - - def asset_by_name(self, asset_name): - self.driver.info("Selecting %s asset" % asset_name) - return SilentButton(self.driver, xpath="//*[contains(@text,'%s')]" % asset_name) - - def asset_checkbox_by_name(self, asset_name): - self.driver.info("Selecting %s asset checkbox by name" % asset_name) - return AssetCheckBox(self.driver, asset_name) - - def get_account_options_by_name(self, account_name=''): - account_name = self.status_account_name if not account_name else account_name - self.driver.info("Getting '%s'account options" % account_name) - return SilentButton(self.driver, xpath="(//*[@text='%s']/../..//*[@content-desc='icon'])[2]" % account_name) - - def get_account_options_from_main_screen(self, account_name=''): - account_name = self.status_account_name if not account_name else account_name - self.driver.info("Getting '%s'account options from main wallet screen" % account_name) - return SilentButton(self.driver, - xpath="//*[@content-desc='accountcard%s']//*[@content-desc='icon']" % account_name) - - def hidden_account_by_name_button(self, account_name=''): - return SilentButton(self.driver, - xpath="//*[@text='%s']/following-sibling::*[@content-desc='hide-icon']" % account_name) - - def show_account_by_name_button(self, account_name=''): - return SilentButton(self.driver, - xpath="//*[@text='%s']/following-sibling::*[@content-desc='show-icon']" % account_name) - - def select_asset(self, *args): - self.driver.info("Selecting asset(s)") - self.multiaccount_more_options.click() - self.manage_assets_button.click() - for asset in args: - self.element_by_text(asset).scroll_to_element() - self.element_by_text(asset).scroll_and_click() - self.cross_icon.click() - - def scan_tokens(self, *args): - self.driver.info("Scanning tokens") - self.multiaccount_more_options.click() - self.scan_tokens_button.click() - counter = 0 - if args: - for asset in args: - while True: - if counter >= 20: - self.driver.fail('Balance of %s is not changed during 20 seconds!' % asset) - elif self.get_asset_amount_by_name(asset) == 0.0: - self.multiaccount_more_options.click() - self.scan_tokens_button.click() - self.driver.info('Trying to scan for tokens one more time and waiting %s seconds for %s ' - 'to update' % (counter, asset)) - time.sleep(5) - counter += 5 - else: - self.driver.info('Balance of %s is updated!' % asset) - return self - - def send_transaction(self, **kwargs): - self.driver.info("## Sending transaction", device=False) - send_tx = self.send_transaction_from_main_screen.click() if kwargs.get('from_main_wallet', - True) else self.send_transaction_button.click() - send_tx.select_asset_button.click() - asset_name = kwargs.get('asset_name', 'ETH').upper() - asset_button = send_tx.asset_by_name(asset_name) - send_tx.select_asset_button.click_until_presence_of_element( - send_tx.eth_asset_in_select_asset_bottom_sheet_button) - asset_button.click() - send_tx.amount_edit_box.click() - - transaction_amount = str(kwargs.get('amount', send_tx.get_unique_amount())) - - send_tx.amount_edit_box.send_keys(transaction_amount) - if kwargs.get('account_name'): - send_tx.chose_recipient_button.click() - send_tx.accounts_button.click() - send_tx.element_by_text(kwargs.get('account_name')).click() - else: - send_tx.set_recipient_address(kwargs.get('recipient')) - if kwargs.get('sign_transaction', True): - send_tx.sign_transaction_button.click() - if self.sign_in_phrase.is_element_displayed(): - self.set_up_wallet_when_sending_tx() - send_tx.sign_transaction(keycard=kwargs.get('keycard', False), - sender_password=kwargs.get('sender_password', common_password)) - return send_tx - - def find_transaction_in_history(self, amount, asset='ETH', account_name=None, return_hash=False): - if account_name is None: - account_name = self.status_account_name - self.driver.info("Finding '%s %s' transaction for '%s'" % (amount, asset, account_name)) - if not self.transaction_history_button.is_element_displayed(): - self.get_account_by_name(account_name).click() - self.transaction_history_button.wait_for_element() - transactions_view = self.transaction_history_button.click() - transaction_element = transactions_view.transactions_table.find_transaction(amount=amount, asset=asset) - result = transaction_element - if return_hash: - transaction_element.click() - from views.transactions_view import TransactionTable - result = TransactionTable.TransactionElement.TransactionDetailsView(self.driver).get_transaction_hash() - return result - - def set_currency(self, desired_currency='EUR'): - self.driver.info("Setting '%s' currency" % desired_currency) - self.multiaccount_more_options.click_until_presence_of_element(self.set_currency_button) - self.set_currency_button.click() - desired_currency = self.element_by_text_part(desired_currency) - desired_currency.scroll_to_element() - desired_currency.click() - - def get_account_by_name(self, account_name: str): - self.driver.info("Getting account: '%s'" % account_name) - return AccountElementButton(self.driver, account_name) - - def add_account(self, account_name: str, password: str = common_password, keycard=False): - self.driver.info("## Add account: '%s'" % account_name, device=False) + def add_regular_account(self, account_name: str): self.add_account_button.click() - self.generate_an_account_button.click() - self.account_name_input.send_keys(account_name) - if keycard: - from views.keycard_view import KeycardView - keycard_view = KeycardView(self.driver) - self.add_account_generate_account_button.click() - keycard_view.enter_default_pin() - else: - self.enter_your_password_input.send_keys(password) - self.add_account_generate_account_button.click_until_presence_of_element(self.accounts_status_account) - self.driver.info("## Account is added!", device=False) + self.create_account_button.click() + SignInView(self.driver).profile_title_input.send_keys(account_name) + self.slide_and_confirm_with_password() - def get_collectibles_amount(self, collectibles='CryptoKitties'): - self.driver.info("Getting '%s' Collectibles amount" % collectibles) - return Text(self.driver, xpath="//*[@text='%s']//following-sibling::android.widget.TextView" % collectibles) + def add_watch_only_account(self, address: str, account_name: str): + self.add_account_button.click() + self.add_account_to_watch.click() + self.address_to_watch_input.send_keys(address) + self.account_has_activity_label.wait_for_visibility_of_element() + self.add_account_continue_button.click() + SignInView(self.driver).profile_title_input.send_keys(account_name) + self.add_watched_address_button.click() + + def remove_account(self, account_name: str, watch_only: bool = False): + self.get_account_element(account_name=account_name).click() + self.account_emoji_button.click() + self.remove_account_button.click() + if not watch_only: + self.derivation_path_note_checkbox.click() + self.confirm_button.click() diff --git a/test/appium/views/wallet_view_old_ui.py b/test/appium/views/wallet_view_old_ui.py new file mode 100644 index 0000000000..51e579dd2f --- /dev/null +++ b/test/appium/views/wallet_view_old_ui.py @@ -0,0 +1,378 @@ +import time + +from tests import common_password +from views.base_element import Button, Text, EditBox, SilentButton, CheckBox +from views.base_view import BaseView + + +class TransactionHistoryButton(Button): + def __init__(self, driver): + super().__init__(driver, accessibility_id="History-item-button") + + def navigate(self): + from views.transactions_view import TransactionsView + return TransactionsView(self.driver) + + +class AssetCheckBox(CheckBox): + def __init__(self, driver, asset_name): + super().__init__(driver, xpath="//*[@text='%s']" % asset_name) + + def enable(self): + self.scroll_to_element(12) + super().enable() + + +class BackupRecoveryPhrase(Button): + def __init__(self, driver): + super().__init__(driver, translation_id="wallet-backup-recovery-title") + + def navigate(self): + from views.profile_view import ProfileView + return ProfileView(self.driver) + + +class AccountElementButton(SilentButton): + def __init__(self, driver, account_name): + super().__init__(driver, xpath="//*[@content-desc='accountcard%s']" % account_name) + + def color_matches(self, expected_color_image_name: str): + amount_text = Text(self.driver, xpath="%s//*[@content-desc='account-total-value']" % self.locator) + amount_text.wait_for_element_text('...', 60) + return not amount_text.is_element_differs_from_template(expected_color_image_name) + + +class SendTransactionButton(Button): + def __init__(self, driver): + super().__init__(driver, translation_id="wallet-send") + + def navigate(self): + from views.send_transaction_view import SendTransactionView + return SendTransactionView(self.driver) + + +class SendTransactionFromMainButton(Button): + def __init__(self, driver): + super().__init__(driver, accessibility_id="send-transaction-button") + + def navigate(self): + from views.send_transaction_view import SendTransactionView + return SendTransactionView(self.driver) + + +class ReceiveTransactionButton(Button): + def __init__(self, driver): + super().__init__(driver, translation_id="receive") + + def navigate(self): + from views.send_transaction_view import SendTransactionView + return SendTransactionView(self.driver) + + +class AddCustomTokenButton(Button): + def __init__(self, driver): + super().__init__(driver, translation_id="add-custom-token") + + def navigate(self): + from views.add_custom_token_view import AddCustomTokenView + return AddCustomTokenView(self.driver) + + +class AccountColorButton(Button): + def __init__(self, driver): + super().__init__(driver, translation_id="account-color", suffix="/following-sibling::android.view.ViewGroup[1]") + + def select_color_by_position(self, position: int): + self.click() + self.driver.find_element_by_xpath( + "((//android.widget.ScrollView)[1]/*/*)[%s]" % str(position + 1)).click() + + +class WalletView(BaseView): + def __init__(self, driver): + super().__init__(driver) + + self.send_transaction_button = SendTransactionButton(self.driver) + self.send_transaction_from_main_screen = SendTransactionFromMainButton(self.driver) + self.transaction_history_button = TransactionHistoryButton(self.driver) + self.usd_total_value = Text(self.driver, accessibility_id="total-amount-value-text") + + self.receive_transaction_button = ReceiveTransactionButton(self.driver) + self.options_button = Button(self.driver, accessibility_id="options-menu-button") + self.manage_assets_button = Button(self.driver, accessibility_id="wallet-manage-assets") + self.manage_accounts_button = Button(self.driver, accessibility_id="wallet-manage-accounts") + self.scan_tokens_button = Button(self.driver, accessibility_id="wallet-scan-token") + self.all_assets_full_names = Text(self.driver, + xpath="//*[@content-desc='checkbox-off']/../android.widget.TextView[1]") + self.all_assets_symbols = Button(self.driver, + xpath="//*[@content-desc='checkbox-off']/../android.widget.TextView[2]") + self.currency_item_text = Text(self.driver, xpath="//*[@content-desc='currency-item']//android.widget.TextView") + + self.address_text = Text(self.driver, accessibility_id="address-text") + + self.remind_me_later_button = Button(self.driver, translation_id="remind-me-later") + + self.total_amount_text = Text(self.driver, accessibility_id="total-amount-value-text") + self.currency_text = Text(self.driver, accessibility_id="total-amount-currency-text") + self.backup_recovery_phrase = BackupRecoveryPhrase(self.driver) + self.backup_recovery_phrase_warning_text = Text(self.driver, + accessibility_id="back-up-your-seed-phrase-warning") + + self.add_custom_token_button = AddCustomTokenButton(self.driver) + + # elements for multiaccount + self.multiaccount_more_options = Button(self.driver, accessibility_id="accounts-more-options") + self.accounts_status_account = AccountElementButton(self.driver, account_name=self.status_account_name) + self.set_currency_button = Button(self.driver, translation_id="set-currency") + self.add_account_button = Button(self.driver, accessibility_id="add-new-account") + self.generate_an_account_button = Button(self.driver, accessibility_id="add-account-sheet-generate") + self.add_watch_only_address_button = Button(self.driver, accessibility_id="add-account-sheet-watch") + self.enter_a_seed_phrase_button = Button(self.driver, accessibility_id="add-account-sheet-seed") + self.enter_a_private_key_button = Button(self.driver, accessibility_id="add-account-sheet-private-key") + self.enter_address_input = EditBox(self.driver, accessibility_id="add-account-enter-watch-address") + self.enter_seed_phrase_input = EditBox(self.driver, accessibility_id="add-account-enter-seed") + self.enter_a_private_key_input = EditBox(self.driver, accessibility_id="add-account-enter-private-key") + self.delete_account_button = Button(self.driver, translation_id="delete-account") + self.enter_your_password_input = EditBox(self.driver, accessibility_id="add-account-enter-password") + self.account_name_input = EditBox(self.driver, accessibility_id="enter-account-name") + self.account_color_button = AccountColorButton(self.driver) + self.add_account_generate_account_button = Button(self.driver, + accessibility_id="add-account-add-account-button") + self.status_account_total_usd_value = Text(self.driver, accessibility_id="account-total-value") + self.scan_qr_button = Button(self.driver, accessibility_id="accounts-qr-code") + self.close_send_transaction_view_button = Button(self.driver, + xpath="//androidx.appcompat.widget.LinearLayoutCompat") + self.hide_account_button = Button(self.driver, accessibility_id="hide-account-button") + + # collectibles + self.collectibles_button = Button(self.driver, translation_id="wallet-collectibles") + self.nft_asset_button = Button(self.driver, accessibility_id="nft-asset") + self.set_collectible_as_profile_photo_button = Button(self.driver, accessibility_id="set-nft-as-pfp") + self.view_collectible_on_opensea_button = Button(self.driver, translation_id="view-on-opensea") + + # individual account settings + self.account_settings_button = Button(self.driver, translation_id="account-settings") + self.apply_settings_button = Button(self.driver, translation_id="apply") + self.password_delete_account_input = EditBox(self.driver, + xpath='//*[@text="Password"]/following-sibling::*/android.widget.EditText') + self.delete_account_confirm_button = Button(self.driver, accessibility_id="delete-account-confirm") + + def wait_balance_is_equal_expected_amount(self, asset='ETH', expected_balance=0.1, wait_time=300, main_screen=True): + counter = 0 + while True: + if counter >= wait_time: + self.driver.fail('**Balance is not changed during %s seconds!**' % wait_time) + elif self.get_asset_amount_by_name(asset) != expected_balance: + counter += 10 + time.sleep(10) + self.swipe_down() + self.driver.info('Waiting %s seconds for %s balance update to be equal to %s' % ( + counter, asset, expected_balance)) + else: + self.driver.info('Balance for %s is equal to %s' % (asset, expected_balance)) + if main_screen: + if not self.accounts_status_account.is_element_displayed(): + self.accounts_status_account.scroll_to_element(direction='up') + return + + def wait_balance_is_changed(self, asset='ETH', initial_balance=0, wait_time=180, scan_tokens=False, navigate_to_home=True): + self.driver.info('Waiting %ss for %s updated balance' % (wait_time, asset)) + counter = 0 + while True: + if counter >= wait_time: + self.driver.fail( + 'Balance %s %s is not changed during %s seconds!' % (asset, initial_balance, wait_time)) + elif self.asset_by_name(asset).is_element_displayed() and self.get_asset_amount_by_name( + asset) == initial_balance: + if scan_tokens: + self.scan_tokens() + if (counter / 60).is_integer(): + self.pull_to_refresh() + counter += 20 + counter += 10 + time.sleep(10) + self.driver.info('Waiting %ss for %s updated balance' % (counter, asset)) + elif not self.asset_by_name(asset).is_element_displayed(10): + if scan_tokens: + self.scan_tokens() + self.swipe_up() + counter += 10 + time.sleep(10) + self.driver.info('Waiting %s seconds for %s to display asset' % (counter, asset)) + else: + self.driver.info('Initial "%s" is not equal expected balance "%s", it is updated!' % (initial_balance, + self.get_asset_amount_by_name(asset))) + if navigate_to_home: + self.wallet_button.double_click() + self.element_by_translation_id("wallet-total-value").scroll_to_element(direction='up') + return self + + def get_sign_in_phrase(self): + return ' '.join([element.text for element in self.sign_in_phrase.find_elements()]) + + def set_up_wallet_when_sending_tx(self): + self.driver.info("Setting up wallet") + phrase = self.sign_in_phrase.text + self.ok_got_it_button.click() + return phrase + + def get_wallet_address(self, account_name=''): + account_name = self.status_account_name if not account_name else account_name + self.driver.info("Getting wallet address for '%s'" % account_name) + self.wallet_account_by_name(account_name).click() + self.receive_transaction_button.click_until_presence_of_element(self.qr_code_image) + address = self.address_text.text + self.close_share_popup() + return address + + def wallet_account_by_name(self, account_name): + self.driver.info("Getting '%s' wallet account" % account_name) + return AccountElementButton(self.driver, account_name) + + def get_asset_amount_by_name(self, asset: str): + self.driver.info("Getting %s amount" % asset) + asset_value = SilentButton(self.driver, xpath="//android.view.ViewGroup[@content-desc=':%s-asset-value']" + "//android.widget.TextView[1]" % asset) + for _ in range(2): + if not asset_value.is_element_displayed(): + self.element = asset_value.scroll_to_element() + try: + value = float(asset_value.text.split()[0]) + self.driver.info("%s value is %s" % (asset, value)) + return value + except ValueError: + self.driver.info("No value for %s" % asset) + return 0.0 + + def asset_by_name(self, asset_name): + self.driver.info("Selecting %s asset" % asset_name) + return SilentButton(self.driver, xpath="//*[contains(@text,'%s')]" % asset_name) + + def asset_checkbox_by_name(self, asset_name): + self.driver.info("Selecting %s asset checkbox by name" % asset_name) + return AssetCheckBox(self.driver, asset_name) + + def get_account_options_by_name(self, account_name=''): + account_name = self.status_account_name if not account_name else account_name + self.driver.info("Getting '%s'account options" % account_name) + return SilentButton(self.driver, xpath="(//*[@text='%s']/../..//*[@content-desc='icon'])[2]" % account_name) + + def get_account_options_from_main_screen(self, account_name=''): + account_name = self.status_account_name if not account_name else account_name + self.driver.info("Getting '%s'account options from main wallet screen" % account_name) + return SilentButton(self.driver, + xpath="//*[@content-desc='accountcard%s']//*[@content-desc='icon']" % account_name) + + def hidden_account_by_name_button(self, account_name=''): + return SilentButton(self.driver, + xpath="//*[@text='%s']/following-sibling::*[@content-desc='hide-icon']" % account_name) + + def show_account_by_name_button(self, account_name=''): + return SilentButton(self.driver, + xpath="//*[@text='%s']/following-sibling::*[@content-desc='show-icon']" % account_name) + + def select_asset(self, *args): + self.driver.info("Selecting asset(s)") + self.multiaccount_more_options.click() + self.manage_assets_button.click() + for asset in args: + self.element_by_text(asset).scroll_to_element() + self.element_by_text(asset).scroll_and_click() + self.cross_icon.click() + + def scan_tokens(self, *args): + self.driver.info("Scanning tokens") + self.multiaccount_more_options.click() + self.scan_tokens_button.click() + counter = 0 + if args: + for asset in args: + while True: + if counter >= 20: + self.driver.fail('Balance of %s is not changed during 20 seconds!' % asset) + elif self.get_asset_amount_by_name(asset) == 0.0: + self.multiaccount_more_options.click() + self.scan_tokens_button.click() + self.driver.info('Trying to scan for tokens one more time and waiting %s seconds for %s ' + 'to update' % (counter, asset)) + time.sleep(5) + counter += 5 + else: + self.driver.info('Balance of %s is updated!' % asset) + return self + + def send_transaction(self, **kwargs): + self.driver.info("## Sending transaction", device=False) + send_tx = self.send_transaction_from_main_screen.click() if kwargs.get('from_main_wallet', + True) else self.send_transaction_button.click() + send_tx.select_asset_button.click() + asset_name = kwargs.get('asset_name', 'ETH').upper() + asset_button = send_tx.asset_by_name(asset_name) + send_tx.select_asset_button.click_until_presence_of_element( + send_tx.eth_asset_in_select_asset_bottom_sheet_button) + asset_button.click() + send_tx.amount_edit_box.click() + + transaction_amount = str(kwargs.get('amount', send_tx.get_unique_amount())) + + send_tx.amount_edit_box.send_keys(transaction_amount) + if kwargs.get('account_name'): + send_tx.chose_recipient_button.click() + send_tx.accounts_button.click() + send_tx.element_by_text(kwargs.get('account_name')).click() + else: + send_tx.set_recipient_address(kwargs.get('recipient')) + if kwargs.get('sign_transaction', True): + send_tx.sign_transaction_button.click() + if self.sign_in_phrase.is_element_displayed(): + self.set_up_wallet_when_sending_tx() + send_tx.sign_transaction(keycard=kwargs.get('keycard', False), + sender_password=kwargs.get('sender_password', common_password)) + return send_tx + + def find_transaction_in_history(self, amount, asset='ETH', account_name=None, return_hash=False): + if account_name is None: + account_name = self.status_account_name + self.driver.info("Finding '%s %s' transaction for '%s'" % (amount, asset, account_name)) + if not self.transaction_history_button.is_element_displayed(): + self.get_account_by_name(account_name).click() + self.transaction_history_button.wait_for_element() + transactions_view = self.transaction_history_button.click() + transaction_element = transactions_view.transactions_table.find_transaction(amount=amount, asset=asset) + result = transaction_element + if return_hash: + transaction_element.click() + from views.transactions_view import TransactionTable + result = TransactionTable.TransactionElement.TransactionDetailsView(self.driver).get_transaction_hash() + return result + + def set_currency(self, desired_currency='EUR'): + self.driver.info("Setting '%s' currency" % desired_currency) + self.multiaccount_more_options.click_until_presence_of_element(self.set_currency_button) + self.set_currency_button.click() + desired_currency = self.element_by_text_part(desired_currency) + desired_currency.scroll_to_element() + desired_currency.click() + + def get_account_by_name(self, account_name: str): + self.driver.info("Getting account: '%s'" % account_name) + return AccountElementButton(self.driver, account_name) + + def add_account(self, account_name: str, password: str = common_password, keycard=False): + self.driver.info("## Add account: '%s'" % account_name, device=False) + self.add_account_button.click() + self.generate_an_account_button.click() + self.account_name_input.send_keys(account_name) + if keycard: + from views.keycard_view import KeycardView + keycard_view = KeycardView(self.driver) + self.add_account_generate_account_button.click() + keycard_view.enter_default_pin() + else: + self.enter_your_password_input.send_keys(password) + self.add_account_generate_account_button.click_until_presence_of_element(self.accounts_status_account) + self.driver.info("## Account is added!", device=False) + + def get_collectibles_amount(self, collectibles='CryptoKitties'): + self.driver.info("Getting '%s' Collectibles amount" % collectibles) + return Text(self.driver, xpath="//*[@text='%s']//following-sibling::android.widget.TextView" % collectibles)