From b9d5220da6163941eff008d20c124e9fe2de9770 Mon Sep 17 00:00:00 2001 From: Churikova Tetiana Date: Fri, 15 Oct 2021 12:26:36 +0200 Subject: [PATCH] e2e: fixes and collectibles Signed-off-by: Churikova Tetiana --- .../test_wallet_management.py | 62 ++++++++++++------ .../tests/atomic/chats/test_communities.py | 8 +-- .../tests/atomic/chats/test_group_chat.py | 17 +++++ .../tests/atomic/chats/test_one_to_one.py | 21 +++--- test/appium/tests/atomic/test_upgrade.py | 20 +++--- test/appium/tests/users.py | 8 ++- test/appium/views/chat_view.py | 17 +++-- .../elements_templates/collectible_pic.png | Bin 0 -> 9661 bytes test/appium/views/home_view.py | 12 ++-- test/appium/views/sign_in_view.py | 6 +- test/appium/views/wallet_view.py | 15 +++-- test/appium/views/web_views/base_web_view.py | 2 +- 12 files changed, 126 insertions(+), 62 deletions(-) create mode 100644 test/appium/views/elements_templates/collectible_pic.png diff --git a/test/appium/tests/atomic/account_management/test_wallet_management.py b/test/appium/tests/atomic/account_management/test_wallet_management.py index 9a4daf64c8..d45168bb40 100644 --- a/test/appium/tests/atomic/account_management/test_wallet_management.py +++ b/test/appium/tests/atomic/account_management/test_wallet_management.py @@ -90,27 +90,43 @@ class TestWalletManagement(SingleDeviceTestCase): @marks.testrail_id(5346) @marks.high - @marks.skip - #TODO: skipped due to bug on status-go, should be enabled after 12615 merge def test_collectible_from_wallet(self): passphrase = wallet_users['F']['passphrase'] home = SignInView(self.driver).recover_access(passphrase=passphrase) - profile = home.profile_button.click() - profile.switch_network() - wallet = profile.wallet_button.click() + + home.just_fyi('Check that collectibles are not shown on Ropsten') + wallet = home.wallet_button.click() wallet.scan_tokens() wallet.accounts_status_account.click() wallet.collectibles_button.click() + wallet.element_by_translation_id("display-collectibles").scroll_and_click() + if not wallet.element_by_translation_id("no-collectibles").is_element_displayed(): + self.errors.append("Collectibles are shown on Ropsten network!") wallet.just_fyi('Check collectibles amount in wallet') - wallet.element_by_translation_id("enable").scroll_and_click() - wallet.cryptokitties_in_collectibles_number.wait_for_visibility_of_element(30) - if wallet.cryptokitties_in_collectibles_number.text != '1': + profile = home.profile_button.click() + profile.switch_network() + profile.wallet_button.click() + wallet.accounts_status_account.click() + wallet.collectibles_button.click() + wallet.get_collectibles_amount().wait_for_visibility_of_element(30) + if wallet.get_collectibles_amount().text != '1': self.errors.append( - 'Wrong number is shown on CK assets: %s' % wallet.cryptokitties_in_collectibles_number.text) - # TODO: should be added check for that NFT image is shown after adding accessibility + 'Wrong number is shown on CK assets: %s' % wallet.get_collectibles_amount().text) + wallet.get_collectibles_amount().click() + if not wallet.nft_asset_button.is_element_displayed(): + self.driver.fail("Kitty is not shown after opening it from collectibles!") + wallet.nft_asset_button.click() + wallet.set_collectible_as_profile_photo_button.scroll_and_click() + web_view = wallet.get_base_web_view() + wallet.view_collectible_on_opensea_button.click_until_presence_of_element(web_view.browser_previous_page_button) + web_view.wait_for_d_aap_to_load() + if not web_view.element_by_text('Princess Gunklater').is_element_displayed(30): + self.errors.append("Collectible can't be opened when tapping 'View on OpenSea' via NFT page") + wallet.wallet_button.double_click() wallet.just_fyi('Check that collectibles are not shown when sending assets from wallet') + wallet.accounts_status_account.click() send_transaction = wallet.send_transaction_button.click() send_transaction.select_asset_button.click() if send_transaction.asset_by_name("CryptoKitties").is_element_displayed(): @@ -118,11 +134,15 @@ class TestWalletManagement(SingleDeviceTestCase): wallet.close_send_transaction_view_button.double_click() wallet.just_fyi('Check "Open in OpenSea" (that user is signed in)') - wallet.element_by_translation_id("check-on-opensea").click() - web_view = wallet.get_webview_view() + wallet.element_by_translation_id("check-on-opensea").click_until_presence_of_element((web_view.browser_previous_page_button)) web_view.wait_for_d_aap_to_load(10) - #wallet.swipe_by_custom_coordinates(0.5,0.8,0.5,0.7) wallet.element_by_text('e2ecryptokitty').wait_for_element(60) + + wallet.just_fyi("Check that custom image from collectible is set as profile photo") + wallet.profile_button.double_click() + if not profile.profile_picture.is_element_image_similar_to_template('collectible_pic.png'): + self.errors.append("Collectible image is not set as profile image") + self.errors.verify_no_errors() @marks.testrail_id(5341) @@ -173,10 +193,9 @@ class TestWalletManagement(SingleDeviceTestCase): @marks.testrail_id(5381) @marks.high - @marks.skip - # TODO: enabling after adding NFT support on Rinkeby - def test_user_can_see_all_own_assets_after_account_recovering(self): - home = SignInView(self.driver).recover_access(wallet_users['E']['passphrase']) + def test_user_can_see_collectibles_on_rinkeby_after_account_recovering(self): + user = wallet_users['E'] + home = SignInView(self.driver).recover_access(user['passphrase']) profile = home.profile_button.click() profile.switch_network('Rinkeby with upstream RPC') profile = home.profile_button.click() @@ -184,10 +203,11 @@ class TestWalletManagement(SingleDeviceTestCase): wallet.scan_tokens() wallet.accounts_status_account.click() wallet.collectibles_button.click() - if not wallet.element_by_text('KDO').is_element_displayed(): - self.driver.fail('User collectibles token name in not shown') - if not wallet.element_by_text('1').is_element_displayed(): - self.driver.fail('User collectibles amount does not match') + wallet.element_by_translation_id("display-collectibles").scroll_and_click() + for asset in user['collectibles']: + wallet.get_collectibles_amount(asset).scroll_to_element() + if wallet.get_collectibles_amount(asset).text != user['collectibles'][asset]: + self.errors.append('%s %s is not shown in Collectibles for Rinkeby!' % (user['collectibles'][asset], asset)) @marks.testrail_id(6224) @marks.critical diff --git a/test/appium/tests/atomic/chats/test_communities.py b/test/appium/tests/atomic/chats/test_communities.py index 5c2966b064..5fe9170b3d 100644 --- a/test/appium/tests/atomic/chats/test_communities.py +++ b/test/appium/tests/atomic/chats/test_communities.py @@ -76,8 +76,6 @@ class TestCommunitiesMultipleDevices(MultipleDeviceTestCase): @marks.testrail_id(695845) @marks.medium - @marks.skip - # TODO: blocked due to 12649 def test_notification_in_activity_center_for_mention_in_community_and_group_chat(self): self.create_drivers(2) home_1, home_2 = SignInView(self.drivers[0]).create_user(), SignInView(self.drivers[1]).create_user() @@ -104,7 +102,7 @@ class TestCommunitiesMultipleDevices(MultipleDeviceTestCase): community_link_text = community_1.copy_community_link() pub_1 = home_1.create_group_chat(user_names_to_add=[username_2], group_chat_name=pub_chat_name) - pub_2 = home_2.get_chat_from_home_view(pub_chat_name).click() + pub_2 = home_2.get_chat(pub_chat_name).click() pub_2.join_chat_button.click() pub_1.chat_message_input.paste_text_from_clipboard() pub_1.send_message_button.click() @@ -131,14 +129,14 @@ class TestCommunitiesMultipleDevices(MultipleDeviceTestCase): channel_2 = community_2.get_chat(channel_name).click() channel_2.select_mention_from_suggestion_list(username_1, username_1[:2]) channel_2.send_as_keyevent("community") - channel_mesage = "@" + username_1 + " community" + channel_mesage = username_1 + " community" channel_2.send_message_button.click() community_1.home_button.double_click() channel_2.home_button.click() home_2.get_chat_from_home_view(pub_chat_name).click() pub_2.select_mention_from_suggestion_list(username_1, username_1[:2]) pub_2.send_as_keyevent("group") - group_chat_message = "@" + username_1 + " group" + group_chat_message = username_1 + " group" pub_2.send_message_button.click() if not home_1.notifications_unread_badge.is_element_displayed(): diff --git a/test/appium/tests/atomic/chats/test_group_chat.py b/test/appium/tests/atomic/chats/test_group_chat.py index ccb84d2cfa..8b1c092ceb 100644 --- a/test/appium/tests/atomic/chats/test_group_chat.py +++ b/test/appium/tests/atomic/chats/test_group_chat.py @@ -16,6 +16,10 @@ class TestGroupChatMultipleDevice(MultipleDeviceTestCase): key_1, username_1 = device_1.get_public_key_and_username(True) device_1.home_button.click() chat_name = home_1.get_random_chat_name() + + home_2.just_fyi('Add admin to contacts to see PN about group chat invite') + home_2.home_button.double_click() + home_2.add_contact(key_1) home_1.plus_button.click() home_1.just_fyi('Check default placeholder when trying to create group chat without contacts') @@ -29,8 +33,21 @@ class TestGroupChatMultipleDevice(MultipleDeviceTestCase): device_2.home_button.click() home_1.add_contact(key_2) home_1.get_back_to_home_view() + home_2.put_app_to_background() chat_1 = home_1.create_group_chat([username_2], chat_name) + + home_2.just_fyi('check that PN invite to group chat is received and after tap you are redirected to group chat') + home_2.open_notification_bar() + pns = [chat_1.pn_invited_to_group_chat(username_1, chat_name), chat_1.pn_wants_you_to_join_to_group_chat(username_1, chat_name)] + for pn in pns: + if not home_2.element_by_text(pn).is_element_displayed(30): + self.errors.append('%s is not shown after invite to group chat' % pn) + if not home_2.pn_group_chat_invite_icon(pns[1]).is_element_displayed(30): + self.drivers[0].fail('No icon is shown for PN') + + home_2.pn_group_chat_invite_icon(pns[1]).click() + home_2.element_by_text(chat_1.pn_invited_to_group_chat(username_1, chat_name)) create_system_message = chat_1.create_system_message(username_1, chat_name) invite_system_message = chat_1.invite_system_message(username_1, username_2) join_system_message = chat_1.join_system_message(username_2) diff --git a/test/appium/tests/atomic/chats/test_one_to_one.py b/test/appium/tests/atomic/chats/test_one_to_one.py index 550bc396a5..2cf6433ce6 100644 --- a/test/appium/tests/atomic/chats/test_one_to_one.py +++ b/test/appium/tests/atomic/chats/test_one_to_one.py @@ -581,10 +581,8 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase): chat_1.home_button.double_click() chat_2.element_starts_with_text(url_message, 'button').click() web_view = chat_2.open_in_status_button.click() - try: - web_view.element_by_text('Private, Secure Communication').find_element() - except TimeoutException: - self.errors.append('Device 2: URL was not opened from 1-1 chat') + if not web_view.element_by_text('Private, Secure Communication').is_element_displayed(60): + self.errors.append('URL was not opened from 1-1 chat') home_2.dapp_tab_button.double_click() chat_2.home_button.click() @@ -595,10 +593,8 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase): chat_2.send_message(url_message) chat_1.element_starts_with_text(url_message, 'button').click() web_view = chat_1.open_in_status_button.click() - try: - web_view.element_by_text('Private, Secure Communication').find_element() - except TimeoutException: - self.errors.append('Device 1: URL was not opened from 1-1 chat') + if not web_view.element_by_text('Private, Secure Communication').is_element_displayed(60): + self.errors.append('URL was not opened from 1-1 chat') home_1.home_button.click(desired_view='chat') preview_urls = {'github_pr': {'url': 'https://github.com/status-im/status-react/pull/11707', @@ -607,7 +603,12 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase): 'yotube': { 'url': 'https://www.youtube.com/watch?v=XN-SVmuJH2g&list=PLbrz7IuP1hrgNtYe9g6YHwHO6F3OqNMao', 'txt': 'Status & Keycard – Hardware-Enforced Security', - 'subtitle': 'YouTube'}} + 'subtitle': 'YouTube'}, + 'twitter':{ + 'url': 'https://twitter.com/ethdotorg/status/1445161651771162627?s=20', + 'txt': "We've rethought how we translate content, allowing us to translate", + 'subtitle': 'Twitter' + }} home_1.just_fyi("Check enabling and sending first gif") giphy_url = 'https://giphy.com/gifs/this-is-fine-QMHoU66sBXqqLqYvGO' @@ -623,7 +624,7 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase): data = preview_urls[key] chat_2.send_message(data['url']) message = chat_1.get_preview_message_by_text(data['url']) - if message.preview_title.text != data['txt']: + if data['txt'] not in message.preview_title.text: self.errors.append("Title '%s' does not match expected" % message.preview_title.text) if message.preview_subtitle.text != data['subtitle']: self.errors.append("Subtitle '%s' does not match expected" % message.preview_subtitle.text) diff --git a/test/appium/tests/atomic/test_upgrade.py b/test/appium/tests/atomic/test_upgrade.py index 3b45837a28..04098dde92 100644 --- a/test/appium/tests/atomic/test_upgrade.py +++ b/test/appium/tests/atomic/test_upgrade.py @@ -91,10 +91,10 @@ class TestUpgradeApplication(SingleDeviceTestCase): public_chat = home.get_chat(markdown_name).click() messages = pub_chat_data['markdown_text_messages'] public_chat.element_starts_with_text(messages[0]).scroll_to_element(10, 'up') + public_chat.element_starts_with_text('quoted').scroll_to_element() for i in range(len(messages)): if not public_chat.chat_element_by_text(messages[i]).is_element_displayed(): - self.errors.append("Markdown message '%s' does not match expected" % messages[i]) - public_chat.element_starts_with_text('quoted').scroll_to_element() + self.errors.append("Markdown message '%s' does not match expected in %s" % (messages[i], markdown_name)) public_chat.home_button.click() home.just_fyi("Checking reactions, sticker, tag messages") @@ -112,13 +112,15 @@ class TestUpgradeApplication(SingleDeviceTestCase): home.just_fyi("Checking reply to long messages and mentions") mention = '#before-upgrade-2' public_chat = home.get_chat(mention).click() + public_chat.scroll_to_start_of_history() pub_chat_data = chats[mention] public_replied_message = public_chat.chat_element_by_text(pub_chat_data['reply']) + public_replied_message.scroll_to_element() if pub_chat_data['long'] not in public_replied_message.replied_message_text: - self.errors.append("Reply is not present in message received in public chat after upgrade") + self.errors.append("Reply is not present in message received in public chat %s after upgrade" % mention) public_chat.element_starts_with_text(pub_chat_data['mention']).scroll_to_element() if not public_chat.chat_element_by_text(pub_chat_data['mention']).is_element_displayed(): - self.errors.append("Mention is not present in public chat after upgrade") + self.errors.append("Mention is not present in %s after upgrade" % mention) home.home_button.click() home.just_fyi("Checking collapsable messages") @@ -126,7 +128,7 @@ class TestUpgradeApplication(SingleDeviceTestCase): public_chat = home.get_chat(long_name).click() pub_chat_data = chats[long_name] if not public_chat.chat_element_by_text(pub_chat_data['long']).uncollapse: - self.errors.append("No uncollapse icon on long message is shown!") + self.errors.append("No uncollapse icon on long message is shown in %s!" % long_name) self.errors.verify_no_errors() @@ -416,10 +418,10 @@ class TestUpgradeMultipleApplication(MultipleDeviceTestCase): self.errors.append("Reply is not present in message received in group chat after upgrade") if not chat_2.chat_element_by_text(messages['invite']).uncollapse: self.errors.append("No uncollapse icon on long message is shown!") - resolved_ens = '@%s' % admin['ens'] + resolved_ens = '@%s' % admin['ens_upgrade'] chat_2.chat_element_by_text(messages['text']).username.scroll_to_element(direction='up') if chat_2.chat_element_by_text(messages['text']).username.text != resolved_ens: - self.errors.append("ENS is not resolved in group chat") + self.errors.append("ENS '%s' is not resolved in group chat '%s'" % (resolved_ens, chat_name)) [chat.home_button.click() for chat in [chat_1, chat_2]] home_1.just_fyi("Check that can join group chat after upgrade") @@ -427,11 +429,11 @@ class TestUpgradeMultipleApplication(MultipleDeviceTestCase): invite_message = chat_1.invite_system_message(resolved_ens, member['username']) [chat_1, chat_2] = [home.get_chat(chat_name).click() for home in (home_1, home_2)] if not chat_2.chat_element_by_text(invite_message).is_element_displayed(): - self.errors.append("System message is not shown after upgrade") + self.errors.append("'%s' is not shown after upgrade in '%s'" % (invite_message, chat_name)) chat_2.join_chat_button.click() joined_system_message = chat_1.join_system_message(member['username']) if not chat_1.chat_element_by_text(joined_system_message).is_element_displayed(30): - self.errors.append("System message is not shown after user was joined") + self.errors.append("'%s' is not shown after user was joined in '%s'" % (joined_system_message, chat_name)) [chat.home_button.double_click() for chat in [chat_1, chat_2]] home_2.just_fyi("Check that removed member can't send messages") diff --git a/test/appium/tests/users.py b/test/appium/tests/users.py index 3e288ba821..250c8828b3 100644 --- a/test/appium/tests/users.py +++ b/test/appium/tests/users.py @@ -12,6 +12,7 @@ ens_user['username'] = "Legal Vibrant Indianabat" ens_user['public_key'] = "0x04359bb3e73cba0b815d71e562670ad00bb5d2db0d16cd1c4c92c668b61fde2274d6e487fcdffe66f913b3fea2a" \ "3058f53ce7946c2b501aa61a9ca8a883df72dc9" ens_user['ens'] = 'statuse2e.eth' +ens_user['ens_upgrade'] = 'statuse2e' ens_user['ens_another'] = 'status-another-ens-e2e.eth' ens_user['address'] = '0x1eE3058Bd300246B4B20E687Efc9Eba81FF7814b' @@ -83,7 +84,12 @@ wallet_users['E']['username'] = "Wry Shiny Damselfly" wallet_users['E']['address'] = "0x3e2e4077753d3c229a9ae332b9ca46958945e2f6" wallet_users['E']['public_key'] = "0x044cf0620ec3ea0aba9fb0e19cb42a6fbd6b4e74f234f0da82580564817b238cc6434745d31" \ "fa1649927ba48adfa7c95991fd51940bc00a71e80b095db5b107f1b" - +wallet_users['E']['collectibles'] = { + 'Coins & Steel Exclusive Item Skin V2' : '1', + 'Coins & Steel Founder Aura' : '2', + 'CryptoKittiesRinkeby' : '2', + 'KudosToken V7' : '1', +} wallet_users['F'] = dict() wallet_users['F']['passphrase'] = "jazz human replace save wreck merry evolve oval black expose clutch sword" wallet_users['F']['username'] = "Dual Sour Galapagostortoise" diff --git a/test/appium/views/chat_view.py b/test/appium/views/chat_view.py index 32e5870149..441d153a1a 100644 --- a/test/appium/views/chat_view.py +++ b/test/appium/views/chat_view.py @@ -738,18 +738,18 @@ class ChatView(BaseView): self.add_button.click() def get_user_options(self, username: str): - self.driver.info("**Get user options for %s**" % username) + self.driver.info("**Get user options for** %s" % username) self.chat_options.click() group_info_view = self.group_info.click() group_info_view.get_username_options(username).click() return self def chat_element_by_text(self, text): - self.driver.info("**Looking for a message by text: %s**" % text) + self.driver.info("**Looking for a message by text:** %s" % text) return ChatElementByText(self.driver, text) def verify_message_is_under_today_text(self, text, errors): - self.driver.info("**Veryfying that '%s' is under today**" % text) + self.driver.info("**Verifying that '%s' is under today**" % text) message_element = self.chat_element_by_text(text) message_element.wait_for_visibility_of_element() message_location = message_element.find_element().location['y'] @@ -969,4 +969,13 @@ class ChatView(BaseView): @staticmethod def changed_group_name_system_message(admin, chat_name): - return "%s changed the group's name to %s" % (admin, chat_name) \ No newline at end of file + return "%s changed the group's name to %s" % (admin, chat_name) + + ### Push notifications + @staticmethod + def pn_invited_to_group_chat(admin, chat_name): + return '%s invited you to %s' % (admin, chat_name) + + @staticmethod + def pn_wants_you_to_join_to_group_chat(admin, chat_name): + return '%s wants you to join group %s' % (admin, chat_name) \ No newline at end of file diff --git a/test/appium/views/elements_templates/collectible_pic.png b/test/appium/views/elements_templates/collectible_pic.png new file mode 100644 index 0000000000000000000000000000000000000000..ad88450f5e90344cd8fc4b4cfec7379b5a7ce497 GIT binary patch literal 9661 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