From 26b77dba45e73b831b77f39ac4c0d1ee1df00924 Mon Sep 17 00:00:00 2001 From: Vladimir Druzhinin Date: Mon, 11 Sep 2023 20:24:13 +0200 Subject: [PATCH] Test(Community) Join community via owner invite #96 --- test/e2e/.gitignore | 9 ++ test/e2e/configs/__init__.py | 4 - test/e2e/configs/_local.py.ci | 2 +- test/e2e/configs/_local.py.default | 2 +- test/e2e/configs/timeouts.py | 1 + test/e2e/constants/user.py | 11 +- test/e2e/driver/aut.py | 37 +++-- test/e2e/driver/server.py | 3 +- test/e2e/gui/components/base_popup.py | 2 +- .../community/authenticate_popup.py | 27 ++++ .../components/community/invite_contacts.py | 54 +++++++ .../components/community/welcome_community.py | 39 +++++ test/e2e/gui/components/profile_popup.py | 8 ++ test/e2e/gui/components/settings/__init__.py | 0 .../settings/send_contact_request_popup.py | 21 +++ .../{ => wallet}/authenticate_popup.py | 0 .../wallet/wallet_account_popups.py | 2 +- test/e2e/gui/elements/base_object.py | 7 +- test/e2e/gui/elements/qt/object.py | 6 +- test/e2e/gui/elements/qt/window.py | 9 ++ test/e2e/gui/main_window.py | 34 ++++- test/e2e/gui/objects_map/__init__.py | 1 + test/e2e/gui/objects_map/community_names.py | 5 + test/e2e/gui/objects_map/component_names.py | 40 +++++- test/e2e/gui/objects_map/main_names.py | 3 +- test/e2e/gui/objects_map/messages_names.py | 23 +++ test/e2e/gui/objects_map/settings_names.py | 11 +- test/e2e/gui/screens/community.py | 25 ++++ test/e2e/gui/screens/community_portal.py | 5 +- test/e2e/gui/screens/messages.py | 124 ++++++++++++++++ test/e2e/gui/screens/settings.py | 104 +++++++++++++- test/e2e/requirements.txt | 1 + test/e2e/scripts/utils/local_system.py | 20 ++- test/e2e/tests/fixtures/aut.py | 55 ++++--- test/e2e/tests/test_communities.py | 134 ++++++++++++++---- test/e2e/tests/test_onboarding.py | 2 +- test/e2e/tests/test_wallet.py | 2 +- 37 files changed, 730 insertions(+), 103 deletions(-) create mode 100644 test/e2e/.gitignore create mode 100644 test/e2e/gui/components/community/authenticate_popup.py create mode 100644 test/e2e/gui/components/community/invite_contacts.py create mode 100644 test/e2e/gui/components/community/welcome_community.py create mode 100644 test/e2e/gui/components/settings/__init__.py create mode 100644 test/e2e/gui/components/settings/send_contact_request_popup.py rename test/e2e/gui/components/{ => wallet}/authenticate_popup.py (100%) create mode 100644 test/e2e/gui/objects_map/messages_names.py create mode 100644 test/e2e/gui/screens/messages.py diff --git a/test/e2e/.gitignore b/test/e2e/.gitignore new file mode 100644 index 0000000000..558f93cfc4 --- /dev/null +++ b/test/e2e/.gitignore @@ -0,0 +1,9 @@ + +configs/_local.py + +.idea/ +/squish_server.ini + +*.pyc + +tmp/ diff --git a/test/e2e/configs/__init__.py b/test/e2e/configs/__init__.py index 8f4c3bcd06..fc27f746fa 100644 --- a/test/e2e/configs/__init__.py +++ b/test/e2e/configs/__init__.py @@ -19,7 +19,3 @@ if APP_DIR is None: if system.IS_WIN and 'bin' not in APP_DIR: exit('Please use launcher from "bin" folder in "APP_DIR"') APP_DIR = SystemPath(APP_DIR) - -# Application will be stuck after the first test execution if set to False -# We need to investigate more time on it. -ATTACH_MODE = True diff --git a/test/e2e/configs/_local.py.ci b/test/e2e/configs/_local.py.ci index 267eac0e17..c68ab16cb4 100644 --- a/test/e2e/configs/_local.py.ci +++ b/test/e2e/configs/_local.py.ci @@ -3,5 +3,5 @@ import os LOG_LEVEL = logging.DEBUG DEV_BUILD = False - +ATTACH_MODE = False APP_DIR = os.getenv('APP_DIR') diff --git a/test/e2e/configs/_local.py.default b/test/e2e/configs/_local.py.default index e3d99d1bd4..ed169a9fba 100644 --- a/test/e2e/configs/_local.py.default +++ b/test/e2e/configs/_local.py.default @@ -2,5 +2,5 @@ import logging LOG_LEVEL = logging.DEBUG DEV_BUILD = False - +ATTACH_MODE = False APP_DIR = None diff --git a/test/e2e/configs/timeouts.py b/test/e2e/configs/timeouts.py index 5f9016f202..b8be85228e 100644 --- a/test/e2e/configs/timeouts.py +++ b/test/e2e/configs/timeouts.py @@ -4,3 +4,4 @@ UI_LOAD_TIMEOUT_SEC = 5 UI_LOAD_TIMEOUT_MSEC = UI_LOAD_TIMEOUT_SEC * 1000 PROCESS_TIMEOUT_SEC = 10 APP_LOAD_TIMEOUT_MSEC = 60000 +MESSAGING_TIMEOUT_SEC = 60 diff --git a/test/e2e/constants/user.py b/test/e2e/constants/user.py index 6a59a0119d..969c88daf6 100644 --- a/test/e2e/constants/user.py +++ b/test/e2e/constants/user.py @@ -3,14 +3,15 @@ from collections import namedtuple import configs UserAccount = namedtuple('User', ['name', 'password', 'seed_phrase']) -user_account_default = UserAccount('squisher', '*P@ssw0rd*', [ +user_account_one = UserAccount('squisher', '*P@ssw0rd*', [ 'rail', 'witness', 'era', 'asthma', 'empty', 'cheap', 'shed', 'pond', 'skate', 'amount', 'invite', 'year' ]) -user_account_one = UserAccount('tester123', 'TesTEr16843/!@00', []) -user_account_two = UserAccount('Athletic', 'TesTEr16843/!@00', []) +user_account_two = UserAccount('athletic', '*P@ssw0rd*', [ + 'measure', 'cube', 'cousin', 'debris', 'slam', 'ignore', 'seven', 'hat', 'satisfy', 'frown', 'casino', 'inflict' +]) user_account_three = UserAccount('Nervous', 'TesTEr16843/!@00', []) -default_community_params = { +community_params = { 'name': 'Name', 'description': 'Description', 'logo': {'fp': configs.testpath.TEST_FILES / 'tv_signal.png', 'zoom': None, 'shift': None}, @@ -22,4 +23,4 @@ default_community_params = { UserCommunityInfo = namedtuple('CommunityInfo', ['name', 'description', 'members', 'image']) UserChannel = namedtuple('Channel', ['name', 'image', 'selected']) -account_list_item = namedtuple('AccountListItem', ['name', 'color', 'emoji']) \ No newline at end of file +account_list_item = namedtuple('AccountListItem', ['name', 'color', 'emoji']) diff --git a/test/e2e/driver/aut.py b/test/e2e/driver/aut.py index a7f65e1b2e..cb002f6b2b 100644 --- a/test/e2e/driver/aut.py +++ b/test/e2e/driver/aut.py @@ -1,3 +1,5 @@ +from datetime import datetime + import allure import squish @@ -7,6 +9,7 @@ from configs.system import IS_LIN from driver import context from driver.server import SquishServer from scripts.utils import system_path, local_system +from scripts.utils.system_path import SystemPath class AUT: @@ -14,7 +17,8 @@ class AUT: self, app_path: system_path.SystemPath = configs.APP_DIR, host: str = '127.0.0.1', - port: int = configs.squish.AUT_PORT + port: int = configs.squish.AUT_PORT, + user_data: SystemPath = None ): super(AUT, self).__init__() self.path = app_path @@ -23,11 +27,19 @@ class AUT: self.ctx = None self.pid = None self.aut_id = self.path.name if IS_LIN else self.path.stem + self.app_data = configs.testpath.STATUS_DATA / f'app_{datetime.now():%H%M%S_%f}' + self.user_data = user_data driver.testSettings.setWrappersForApplication(self.aut_id, ['Qt']) def __str__(self): return type(self).__qualname__ + def __enter__(self): + return self.launch() + + def __exit__(self, *args): + self.detach().stop() + @allure.step('Attach Squish to Test Application') def attach(self, timeout_sec: int = configs.timeouts.PROCESS_TIMEOUT_SEC, attempt: int = 2): if self.ctx is None: @@ -44,30 +56,33 @@ class AUT: def detach(self): if self.ctx is not None: squish.currentApplicationContext().detach() - assert squish.waitFor(lambda: not self.ctx.isRunning, configs.timeouts.PROCESS_TIMEOUT_SEC) self.ctx = None return self @allure.step('Close application') def stop(self): - local_system.kill_process(self.pid) + local_system.kill_process(self.pid, verify=True) @allure.step("Start application") - def launch(self, *args) -> 'AUT': + def launch(self) -> 'AUT': + if self.user_data is not None: + self.user_data.copy_to(self.app_data / 'data') + SquishServer().set_aut_timeout() + if configs.ATTACH_MODE: SquishServer().add_attachable_aut('AUT', self.port) command = [ - configs.testpath.SQUISH_DIR / 'bin' / 'startaut', - f'--port={self.port}', - f'"{self.path}"' - ] + list(args) + configs.testpath.SQUISH_DIR / 'bin' / 'startaut', + f'--port={self.port}', + f'"{self.path}"', + f'-d={self.app_data}' + ] local_system.execute(command) else: SquishServer().add_executable_aut(self.aut_id, self.path.parent) - command = [self.aut_id] + list(args) - self.ctx = squish.startApplication( - ' '.join(command), configs.timeouts.PROCESS_TIMEOUT_SEC) + command = [self.aut_id, f'-d={self.app_data}'] + self.ctx = squish.startApplication(' '.join(command), configs.timeouts.PROCESS_TIMEOUT_SEC) self.attach() self.pid = self.ctx.pid diff --git a/test/e2e/driver/server.py b/test/e2e/driver/server.py index 4cf5feeec5..f509db3ae6 100644 --- a/test/e2e/driver/server.py +++ b/test/e2e/driver/server.py @@ -1,6 +1,5 @@ import logging import typing -from subprocess import CalledProcessError import configs.testpath from scripts.utils import local_system @@ -35,7 +34,7 @@ class SquishServer: def stop(self): if self.pid is not None: local_system.kill_process(self.pid) - self.pid = None + self.pid = None # https://doc-snapshots.qt.io/squish/cli-squishserver.html def configuring(self, action: str, options: typing.Union[int, str, list]): diff --git a/test/e2e/gui/components/base_popup.py b/test/e2e/gui/components/base_popup.py index e086ee300c..4c6d189721 100644 --- a/test/e2e/gui/components/base_popup.py +++ b/test/e2e/gui/components/base_popup.py @@ -11,5 +11,5 @@ class BasePopup(QObject): @allure.step('Close') def close(self): - driver.nativeType('') + driver.type(self.object, '') self.wait_until_hidden() diff --git a/test/e2e/gui/components/community/authenticate_popup.py b/test/e2e/gui/components/community/authenticate_popup.py new file mode 100644 index 0000000000..8203d8fd89 --- /dev/null +++ b/test/e2e/gui/components/community/authenticate_popup.py @@ -0,0 +1,27 @@ +import allure + +import configs +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject +from gui.elements.qt.text_edit import TextEdit + + +class AuthenticatePopup(BasePopup): + + def __init__(self): + super().__init__() + self._content = QObject('keycardSharedPopupContent_KeycardPopupContent') + self._passwort_text_edit = TextEdit('password_PlaceholderText') + self._authenticate_button = Button('authenticate_StatusButton') + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + self._content.wait_until_appears(timeout_msec) + return self + + @allure.step('Authenticate actions with password {0}') + def authenticate(self, password: str): + self._passwort_text_edit.type_text(password) + self._authenticate_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/community/invite_contacts.py b/test/e2e/gui/components/community/invite_contacts.py new file mode 100644 index 0000000000..6dac5fe1e0 --- /dev/null +++ b/test/e2e/gui/components/community/invite_contacts.py @@ -0,0 +1,54 @@ +import typing + +import allure + +import configs.timeouts +import driver +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject +from gui.elements.qt.text_edit import TextEdit + + +class InviteContactsPopup(BasePopup): + + def __init__(self): + super().__init__() + self._member_item = QObject('o_StatusMemberListItem') + self._next_button = Button('next_StatusButton') + self._message_text_edit = TextEdit('communityProfilePopupInviteMessagePanel_MessageInput_TextEdit') + self._invited_member_item = QObject('o_StatusMemberListItem_2') + self._send_button = Button('send_1_invite_StatusButton') + + @property + @allure.step('Get contacts') + def contacts(self) -> typing.List[str]: + return [str(getattr(user, 'title', '')) for user in driver.findAllObjects(self._member_item.real_name)] + + @property + @allure.step('Invite contacts') + def invited_contacts(self) -> typing.List[str]: + return [str(getattr(user, 'title', '')) for user in driver.findAllObjects(self._invited_member_item.real_name)] + + def invite(self, contacts: typing.List[str], message: str): + for contact in contacts: + assert driver.waitFor(lambda: contact in self.contacts, configs.timeouts.UI_LOAD_TIMEOUT_MSEC), \ + f'Contact: {contact} not found in {self.contacts}' + + selected = [] + for member in driver.findAllObjects(self._member_item.real_name): + if str(getattr(member, 'title', '')) in contacts: + driver.mouseClick(member) + selected.append(member.title) + + assert len(contacts) == len(selected), f'Selected contacts: {selected}, expected: {contacts}' + + self._next_button.click() + self._message_text_edit.text = message + + for contact in contacts: + assert driver.waitFor(lambda: contact in self.invited_contacts, configs.timeouts.UI_LOAD_TIMEOUT_MSEC), \ + f'Contact: {contact} not found in {self.invited_contacts}' + + self._send_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/community/welcome_community.py b/test/e2e/gui/components/community/welcome_community.py new file mode 100644 index 0000000000..523d8471e6 --- /dev/null +++ b/test/e2e/gui/components/community/welcome_community.py @@ -0,0 +1,39 @@ +import allure + +from gui.components.community.authenticate_popup import AuthenticatePopup +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject +from gui.elements.qt.text_label import TextLabel +from scripts.tools.image import Image + + +class WelcomeCommunityPopup(BasePopup): + + def __init__(self): + super().__init__() + self._title_text_label = TextLabel('headerTitle_StatusBaseText') + self._community_icon = QObject('image_StatusImage') + self._intro_text_label = TextLabel('intro_StatusBaseText') + self._select_address_button = Button('select_addresses_to_share_StatusFlatButton') + self._join_button = Button('join_StatusButton') + + @property + @allure.step('Get title') + def title(self) -> str: + return self._title_text_label.text + + @property + @allure.step('Get community icon') + def community_icon(self) -> Image: + return self._community_icon.image + + @property + @allure.step('Get community intro') + def intro(self) -> str: + return self._intro_text_label.text + + @allure.step('Join community') + def join(self) -> AuthenticatePopup: + self._join_button.click() + return AuthenticatePopup().wait_until_appears() diff --git a/test/e2e/gui/components/profile_popup.py b/test/e2e/gui/components/profile_popup.py index 1abf3b48fb..e7836c5659 100644 --- a/test/e2e/gui/components/profile_popup.py +++ b/test/e2e/gui/components/profile_popup.py @@ -1,4 +1,5 @@ import allure +import pyperclip import constants import driver @@ -18,6 +19,7 @@ class ProfilePopup(BasePopup): self._edit_profile_button = Button('ProfilePopup_editButton') self._chat_key_text_label = TextLabel('https_status_app_StatusBaseText') self._emoji_hash = QObject('profileDialog_userEmojiHash_EmojiHash') + self._chat_key_copy_button = Button('copy_icon_StatusIcon') @property @allure.step('Get profile image') @@ -53,6 +55,12 @@ class ProfilePopup(BasePopup): def emoji_hash(self) -> Image: return self._emoji_hash.image + @property + @allure.step('Get chat key') + def chat_key(self) -> str: + self._chat_key_copy_button.click() + return pyperclip.paste() + @allure.step('Verify: user image contains text') def is_user_image_contains(self, text: str): # To remove all artifacts, the image cropped. diff --git a/test/e2e/gui/components/settings/__init__.py b/test/e2e/gui/components/settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/settings/send_contact_request_popup.py b/test/e2e/gui/components/settings/send_contact_request_popup.py new file mode 100644 index 0000000000..7b7f47cfa4 --- /dev/null +++ b/test/e2e/gui/components/settings/send_contact_request_popup.py @@ -0,0 +1,21 @@ +import allure + +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.text_edit import TextEdit + + +class SendContactRequest(BasePopup): + + def __init__(self): + super().__init__() + self._chat_key_text_edit = TextEdit('sendContactRequestModal_ChatKey_Input_TextEdit') + self._message_text_edit = TextEdit('sendContactRequestModal_SayWhoYouAre_Input_TextEdit') + self._send_button = Button('send_Contact_Request_StatusButton') + + @allure.step('Send contact request') + def send(self, chat_key: str, message: str): + self._chat_key_text_edit.text = chat_key + self._message_text_edit.text = message + self._send_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/authenticate_popup.py b/test/e2e/gui/components/wallet/authenticate_popup.py similarity index 100% rename from test/e2e/gui/components/authenticate_popup.py rename to test/e2e/gui/components/wallet/authenticate_popup.py diff --git a/test/e2e/gui/components/wallet/wallet_account_popups.py b/test/e2e/gui/components/wallet/wallet_account_popups.py index f6c8b8db02..e09b645c05 100644 --- a/test/e2e/gui/components/wallet/wallet_account_popups.py +++ b/test/e2e/gui/components/wallet/wallet_account_popups.py @@ -3,7 +3,7 @@ import allure import configs import constants.wallet import driver -from gui.components.authenticate_popup import AuthenticatePopup +from gui.components.wallet.authenticate_popup import AuthenticatePopup from gui.components.base_popup import BasePopup from gui.components.emoji_popup import EmojiPopup from gui.elements.qt.button import Button diff --git a/test/e2e/gui/elements/base_object.py b/test/e2e/gui/elements/base_object.py index cde0bc0934..c7f9330662 100644 --- a/test/e2e/gui/elements/base_object.py +++ b/test/e2e/gui/elements/base_object.py @@ -11,9 +11,12 @@ _logger = logging.getLogger(__name__) class BaseObject: - def __init__(self, name: str): + def __init__(self, name: str, real_name: [str, dict] = None): self.symbolic_name = name - self.real_name = getattr(objects_map, name) + if real_name: + self.real_name = real_name + else: + self.real_name = getattr(objects_map, name) def __str__(self): return f'{type(self).__qualname__}({self.symbolic_name})' diff --git a/test/e2e/gui/elements/qt/object.py b/test/e2e/gui/elements/qt/object.py index 085508463b..22f4801e7c 100644 --- a/test/e2e/gui/elements/qt/object.py +++ b/test/e2e/gui/elements/qt/object.py @@ -14,8 +14,8 @@ _logger = logging.getLogger(__name__) class QObject(BaseObject): - def __init__(self, name: str): - super().__init__(name) + def __init__(self, name, real_name: [str, dict] = None): + super().__init__(name, real_name) self._image = Image(self.real_name) def __str__(self): @@ -80,7 +80,7 @@ class QObject(BaseObject): @allure.step('Get visible {0}') def is_visible(self) -> bool: try: - return driver.waitForObject(self.real_name, 0).visible + return driver.waitForObjectExists(self.real_name, 0).visible except (AttributeError, LookupError, RuntimeError): return False diff --git a/test/e2e/gui/elements/qt/window.py b/test/e2e/gui/elements/qt/window.py index 554bada9c3..354b5790e4 100644 --- a/test/e2e/gui/elements/qt/window.py +++ b/test/e2e/gui/elements/qt/window.py @@ -11,6 +11,7 @@ _logger = logging.getLogger(__name__) class Window(QObject): def prepare(self) -> 'Window': + self.show() self.maximize() self.on_top_level() return self @@ -38,3 +39,11 @@ class Window(QObject): @allure.step("Close {0}") def close(self): driver.toplevel_window.close(self.real_name) + + @allure.step("Show {0}") + def show(self): + driver.waitForObjectExists(self.real_name).setVisible(True) + + @allure.step("Hide {0}") + def hide(self): + driver.waitForObjectExists(self.real_name).setVisible(False) diff --git a/test/e2e/gui/main_window.py b/test/e2e/gui/main_window.py index 8336f3422b..d08946fab4 100644 --- a/test/e2e/gui/main_window.py +++ b/test/e2e/gui/main_window.py @@ -7,6 +7,7 @@ import configs import constants import driver from constants import UserAccount +from gui.components.community.invite_contacts import InviteContactsPopup from gui.components.onboarding.before_started_popup import BeforeStartedPopUp from gui.components.onboarding.welcome_status_popup import WelcomeStatusPopup from gui.components.splash_screen import SplashScreen @@ -16,6 +17,7 @@ from gui.elements.qt.object import QObject from gui.elements.qt.window import Window from gui.screens.community import CommunityScreen from gui.screens.community_portal import CommunitiesPortal +from gui.screens.messages import MessagesScreen from gui.screens.onboarding import AllowNotificationsView, WelcomeView, TouchIDAuthView, LoginView from gui.screens.settings import SettingsScreen from gui.screens.wallet import WalletScreen @@ -29,10 +31,12 @@ class LeftPanel(QObject): def __init__(self): super(LeftPanel, self).__init__('mainWindow_StatusAppNavBar') self._profile_button = Button('mainWindow_ProfileNavBarButton') + self._messages_button = Button('messages_navbar_StatusNavBarTabButton') self._communities_portal_button = Button('communities_Portal_navbar_StatusNavBarTabButton') self._community_template_button = Button('statusCommunityMainNavBarListView_CommunityNavBarButton') self._settings_button = Button('settings_navbar_StatusNavBarTabButton') self._wallet_button = Button('wallet_navbar_StatusNavBarTabButton') + self._community_invite_people_context_item = QObject('invite_People_StatusMenuItem') @property @allure.step('Get communities names') @@ -47,6 +51,11 @@ class LeftPanel(QObject): def user_badge_color(self) -> str: return str(self._profile_button.object.badge.color.name) + @allure.step('Open messages screen') + def open_messages_screen(self) -> MessagesScreen: + self._messages_button.click() + return MessagesScreen().wait_until_appears() + @allure.step('Open user canvas') def open_user_canvas(self) -> UserCanvas: self._profile_button.click() @@ -86,6 +95,12 @@ class LeftPanel(QObject): def get_community_logo(self, name: str) -> Image: return Image(driver.objectMap.realName(self._get_community(name))) + @allure.step('Invite people in community') + def invite_people_in_community(self, contacts: typing.List[str], message: str, community_name: str): + driver.mouseClick(self._get_community(community_name), driver.Qt.RightButton) + self._community_invite_people_context_item.click() + InviteContactsPopup().wait_until_appears().invite(contacts, message) + @allure.step('Open settings') def open_settings(self) -> CommunitiesPortal: self._settings_button.click() @@ -96,6 +111,7 @@ class LeftPanel(QObject): self._wallet_button.click() return WalletScreen().wait_until_appears() + class MainWindow(Window): def __init__(self): @@ -103,7 +119,7 @@ class MainWindow(Window): self.left_panel = LeftPanel() @allure.step('Sign Up user') - def sign_up(self, user_account: UserAccount = constants.user.user_account_one): + def sign_up(self, user_account: UserAccount = constants.user.community_params): if configs.system.IS_MAC: AllowNotificationsView().wait_until_appears().allow() BeforeStartedPopUp().get_started() @@ -128,3 +144,19 @@ class MainWindow(Window): if not configs.DEV_BUILD: WelcomeStatusPopup().wait_until_appears().confirm() return self + + @allure.step('Authorize user') + def authorize_user(self, user_account) -> 'MainWindow': + self.prepare() + assert isinstance(user_account, UserAccount) + if LoginView().is_visible: + return self.log_in(user_account) + else: + return self.sign_up(user_account) + + @allure.step('Create community') + def create_community(self, params: dict) -> CommunityScreen: + communities_portal = self.left_panel.open_communities_portal() + create_community_form = communities_portal.open_create_community_popup() + app_screen = create_community_form.create(params) + return app_screen diff --git a/test/e2e/gui/objects_map/__init__.py b/test/e2e/gui/objects_map/__init__.py index cb8eef59de..7829d70605 100644 --- a/test/e2e/gui/objects_map/__init__.py +++ b/test/e2e/gui/objects_map/__init__.py @@ -1,6 +1,7 @@ from .community_names import * from .component_names import * from .main_names import * +from .messages_names import * from .onboarding_names import * from .os_names import * from .settings_names import * diff --git a/test/e2e/gui/objects_map/community_names.py b/test/e2e/gui/objects_map/community_names.py index 2a99259570..d4c9c1eac7 100644 --- a/test/e2e/gui/objects_map/community_names.py +++ b/test/e2e/gui/objects_map/community_names.py @@ -22,6 +22,7 @@ channel_identicon_StatusSmartIdenticon = {"container": None, "id": "identicon", channel_name_StatusBaseText = {"container": None, "type": "StatusBaseText", "unnamed": 1, "visible": True} mainWindow_createChannelOrCategoryBtn_StatusBaseText = {"container": mainWindow_communityColumnView_CommunityColumnView, "objectName": "createChannelOrCategoryBtn", "type": "StatusBaseText", "visible": True} create_channel_StatusMenuItem = {"container": statusDesktop_mainWindow_overlay, "enabled": True, "objectName": "createCommunityChannelBtn", "type": "StatusMenuItem", "visible": True} +mainWindow_Join_Community_StatusButton = {"checkable": False, "container": mainWindow_communityColumnView_CommunityColumnView, "id": "joinCommunityButton", "text": "Join Community", "type": "StatusButton", "unnamed": 1, "visible": True} # Tool Bar mainWindow_statusToolBar_StatusToolBar = {"container": mainWindow_communityLoader_Loader, "objectName": "statusToolBar", "type": "StatusToolBar", "visible": True} @@ -76,3 +77,7 @@ communityEditPanelScrollView_pinMessagesToggle_StatusCheckBox = {"checkable": Tr communityEditPanelScrollView_editCommunityIntroInput_TextEdit = {"container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "objectName": "editCommunityIntroInput", "type": "TextEdit", "visible": True} communityEditPanelScrollView_editCommunityOutroInput_TextEdit = {"container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "objectName": "editCommunityOutroInput", "type": "TextEdit", "visible": True} mainWindow_Save_changes_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow, "objectName": "settingsDirtyToastMessageSaveButton", "text": "Save changes", "type": "StatusButton", "visible": True} + +# User List Panel +mainWindow_UserListPanel = {"container": statusDesktop_mainWindow, "type": "UserListPanel", "unnamed": 1, "visible": True} +userListPanel_StatusMemberListItem = {"container": mainWindow_UserListPanel, "type": "StatusMemberListItem", "unnamed": 1, "visible": True} diff --git a/test/e2e/gui/objects_map/component_names.py b/test/e2e/gui/objects_map/component_names.py index f1bcf66c02..d509e72d3a 100644 --- a/test/e2e/gui/objects_map/component_names.py +++ b/test/e2e/gui/objects_map/component_names.py @@ -1,8 +1,7 @@ from objectmaphelper import * -from .main_names import statusDesktop_mainWindow -from .main_names import statusDesktop_mainWindow_overlay -from .main_names import statusDesktop_mainWindow_overlay_popup2 +from .main_names import statusDesktop_mainWindow_overlay, statusDesktop_mainWindow, \ + statusDesktop_mainWindow_overlay_popup2 # Scroll o_Flickable = {"container": statusDesktop_mainWindow_overlay, "type": "Flickable", "unnamed": 1, "visible": True} @@ -61,6 +60,8 @@ ProfilePopup_SendContactRequestButton = {"container": statusDesktop_mainWindow_o profileDialog_userEmojiHash_EmojiHash = {"container": statusDesktop_mainWindow_overlay, "objectName": "ProfileDialog_userEmojiHash", "type": "EmojiHash", "visible": True} edit_TextEdit = {"container": statusDesktop_mainWindow_overlay, "id": "edit", "type": "TextEdit", "unnamed": 1, "visible": True} https_status_app_StatusBaseText = {"container": edit_TextEdit, "type": "StatusBaseText", "unnamed": 1, "visible": True} +copy_icon_StatusIcon = {"container": statusDesktop_mainWindow_overlay, "objectName": "copy-icon", "type": "StatusIcon", "visible": True} + # Welcome Status Popup agreeToUse_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "id": "agreeToUse", "type": "StatusCheckBox", "unnamed": 1, "visible": True} @@ -92,12 +93,37 @@ createCommunityIntroMessageInput_TextEdit = {"container": statusDesktop_mainWind createCommunityOutroMessageInput_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "createCommunityOutroMessageInput", "type": "TextEdit", "visible": True} createCommunityFinalBtn_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "createCommunityFinalBtn", "type": "StatusButton", "visible": True} -# Community channel popup: +# Community Channel Popup: createOrEditCommunityChannelNameInput_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "createOrEditCommunityChannelNameInput", "type": "TextEdit", "visible": True} createOrEditCommunityChannelDescriptionInput_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "createOrEditCommunityChannelDescriptionInput", "type": "TextEdit", "visible": True} createOrEditCommunityChannelBtn_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "createOrEditCommunityChannelBtn", "type": "StatusButton", "visible": True} createOrEditCommunityChannel_EmojiButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "StatusChannelPopup_emojiButton", "type": "StatusRoundButton", "visible": True} +# Invite Contacts Popup +communityProfilePopupInviteFrindsPanel = {"container": statusDesktop_mainWindow_overlay, "objectName": "CommunityProfilePopupInviteFrindsPanel_ColumnLayout", "type": "ProfilePopupInviteFriendsPanel", "visible": True} +communityProfilePopupInviteMessagePanel = {"container": statusDesktop_mainWindow_overlay, "objectName": "CommunityProfilePopupInviteMessagePanel_ColumnLayout", "type": "ProfilePopupInviteMessagePanel", "visible": True} +o_StatusMemberListItem = {"container": communityProfilePopupInviteFrindsPanel, "type": "StatusMemberListItem", "unnamed": 1, "visible": True} +next_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "objectName": "InviteFriendsToCommunityPopup_NextButton", "text": "Next", "type": "StatusButton", "visible": True} +communityProfilePopupInviteMessagePanel_MessageInput_TextEdit = {"container": communityProfilePopupInviteMessagePanel, "objectName": "CommunityProfilePopupInviteMessagePanel_MessageInput", "type": "TextEdit", "visible": True} +send_1_invite_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "objectName": "InviteFriendsToCommunityPopup_SendButton", "text": "Send 1 invite", "type": "StatusButton", "visible": True} +o_StatusMemberListItem_2 = {"container": communityProfilePopupInviteMessagePanel, "type": "StatusMemberListItem", "unnamed": 1, "visible": True} + +# Welcome community +headerTitle_StatusBaseText = {"container": statusDesktop_mainWindow_overlay, "objectName": "headerTitle", "type": "StatusBaseText", "visible": True} +image_StatusImage = {"container": statusDesktop_mainWindow_overlay, "id": "image", "type": "StatusImage", "unnamed": 1, "visible": True} +intro_StatusBaseText = {"container": statusDesktop_mainWindow_overlay, "text": "Intro", "type": "StatusBaseText", "unnamed": 1, "visible": True} +select_addresses_to_share_StatusFlatButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "type": "StatusFlatButton", "unnamed": 1, "visible": True} +join_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "type": "StatusButton", "unnamed": 1, "visible": True} + +""" Settings """ + +# Send Contact Request +sendContactRequestModal_ChatKey_Input_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "SendContactRequestModal_ChatKey_Input", "type": "TextEdit", "visible": True} +sendContactRequestModal_SayWhoYouAre_Input_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "SendContactRequestModal_SayWhoYouAre_Input", "type": "TextEdit", "visible": True} +send_Contact_Request_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "SendContactRequestModal_Send_Button", "type": "StatusButton", "visible": True} + + + """ Common """ # Select Color Popup @@ -155,6 +181,12 @@ mainWallet_AddEditAccountPopup_AccountEmoji = {"container": statusDesktop_mainWi o_StatusDialogBackground = {"container": statusDesktop_mainWindow_overlay, "type": "StatusDialogBackground", "unnamed": 1, "visible": True} delete_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "objectName": "deleteChatConfirmationDialogDeleteButton", "type": "StatusButton", "visible": True} +# Authenticate Popup +keycardSharedPopupContent_KeycardPopupContent = {"container": statusDesktop_mainWindow_overlay, "objectName": "KeycardSharedPopupContent", "type": "KeycardPopupContent", "visible": True} +password_PlaceholderText = {"container": statusDesktop_mainWindow_overlay, "type": "PlaceholderText", "unnamed": 1, "visible": True} +authenticate_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "PrimaryButton", "type": "StatusButton", "visible": True} + + # Shared Popup sharedPopup_Popup_Content = {"container": statusDesktop_mainWindow, "objectName": "KeycardSharedPopupContent", "type": "Item"} sharedPopup_Password_Input = {"container": sharedPopup_Popup_Content, "objectName": "keycardPasswordInput", "type": "TextField"} diff --git a/test/e2e/gui/objects_map/main_names.py b/test/e2e/gui/objects_map/main_names.py index 82d960e808..56c55eb7ae 100644 --- a/test/e2e/gui/objects_map/main_names.py +++ b/test/e2e/gui/objects_map/main_names.py @@ -1,4 +1,4 @@ -statusDesktop_mainWindow = {"name": "mainWindow", "type": "StatusWindow", "visible": True} +statusDesktop_mainWindow = {"name": "mainWindow", "type": "StatusWindow"} statusDesktop_mainWindow_overlay = {"container": statusDesktop_mainWindow, "type": "Overlay", "unnamed": 1, "visible": True} statusDesktop_mainWindow_overlay_popup2 = {"container": statusDesktop_mainWindow_overlay, "occurrence": 2, "type": "PopupItem", "unnamed": 1, "visible": True} scrollView_StatusScrollView = {"container": statusDesktop_mainWindow_overlay, "id": "scrollView", "type": "StatusScrollView", "unnamed": 1, "visible": True} @@ -16,6 +16,7 @@ settings_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWin mainWindow_ProfileNavBarButton = {"container": statusDesktop_mainWindow, "objectName": "statusProfileNavBarTabButton", "type": "StatusNavBarTabButton", "visible": True} mainWindow_statusCommunityMainNavBarListView_ListView = {"container": statusDesktop_mainWindow, "objectName": "statusCommunityMainNavBarListView", "type": "ListView", "visible": True} statusCommunityMainNavBarListView_CommunityNavBarButton = {"checkable": True, "container": mainWindow_statusCommunityMainNavBarListView_ListView, "objectName": "CommunityNavBarButton", "type": "StatusNavBarTabButton", "visible": True} +invite_People_StatusMenuItem = {"container": statusDesktop_mainWindow_overlay, "enabled": True, "objectName": "invitePeople", "type": "StatusMenuItem", "visible": True} # Banners secureYourSeedPhraseBanner_ModuleWarning = {"container": statusDesktop_mainWindow, "objectName": "secureYourSeedPhraseBanner", "type": "ModuleWarning", "visible": True} diff --git a/test/e2e/gui/objects_map/messages_names.py b/test/e2e/gui/objects_map/messages_names.py new file mode 100644 index 0000000000..166b2a2d26 --- /dev/null +++ b/test/e2e/gui/objects_map/messages_names.py @@ -0,0 +1,23 @@ +from .main_names import statusDesktop_mainWindow + +mainWindow_chatView_ChatView = {"container": statusDesktop_mainWindow, "id": "chatView", "type": "ChatView", "unnamed": 1, "visible": True} + +# Left Panel +mainWindow_contactColumnLoader_Loader = {"container": mainWindow_chatView_ChatView, "id": "contactColumnLoader", "type": "Loader", "unnamed": 1, "visible": True} +mainWindow_startChatButton_StatusIconTabButton = {"checkable": True, "container": mainWindow_contactColumnLoader_Loader, "objectName": "startChatButton", "type": "StatusIconTabButton", "visible": True} +mainWindow_search_edit_TextEdit = {"container": mainWindow_contactColumnLoader_Loader, "id": "edit", "type": "TextEdit", "unnamed": 1, "visible": True} +mainWindow_scrollView_StatusScrollView = {"container": mainWindow_contactColumnLoader_Loader, "id": "scrollView", "type": "StatusScrollView", "unnamed": 1, "visible": True} +scrollView_Flickable = {"container": mainWindow_scrollView_StatusScrollView, "type": "Flickable", "unnamed": 1, "visible": True} +scrollView_ContactsColumnView_chatList_StatusChatList = {"container": mainWindow_scrollView_StatusScrollView, "objectName": "ContactsColumnView_chatList", "type": "StatusChatList", "visible": True} +chatList_ListView = {"container": statusDesktop_mainWindow, "objectName": "chatListItems", "type": "StatusListView", "visible": True} +# Tool Bar +mainWindow_statusToolBar_StatusToolBar = {"container": mainWindow_chatView_ChatView, "objectName": "statusToolBar", "type": "StatusToolBar", "visible": True} + +# Chat View +mainWindow_ChatColumnView = {"container": mainWindow_chatView_ChatView, "type": "ChatColumnView", "unnamed": 1, "visible": True} +mainWindow_chatLogView_StatusListView = {"container": mainWindow_ChatColumnView, "objectName": "chatLogView", "type": "StatusListView", "visible": True} +chatLogView_chatMessageViewDelegate_MessageView = {"container": mainWindow_chatLogView_StatusListView, "objectName": "chatMessageViewDelegate", "type": "MessageView", "visible": True, "enabled": True} + +# User List Panel +mainWindow_UserListPanel = {"container": mainWindow_chatView_ChatView, "type": "UserListPanel", "unnamed": 1, "visible": True} +userListPanel_StatusMemberListItem = {"container": mainWindow_UserListPanel, "type": "StatusMemberListItem", "unnamed": 1, "visible": True} diff --git a/test/e2e/gui/objects_map/settings_names.py b/test/e2e/gui/objects_map/settings_names.py index 2ac392013e..2f17449299 100644 --- a/test/e2e/gui/objects_map/settings_names.py +++ b/test/e2e/gui/objects_map/settings_names.py @@ -13,10 +13,19 @@ scrollView_AppMenuItem_StatusNavigationListItem = {"container": mainWindow_scrol mainWindow_CommunitiesView = {"container": statusDesktop_mainWindow, "type": "CommunitiesView", "unnamed": 1, "visible": True} mainWindow_settingsContentBaseScrollView_StatusScrollView = {"container": mainWindow_CommunitiesView, "objectName": "settingsContentBaseScrollView", "type": "StatusScrollView", "visible": True} settingsContentBaseScrollView_listItem_StatusListItem = {"container": mainWindow_settingsContentBaseScrollView_StatusScrollView, "id": "listItem", "type": "StatusListItem", "unnamed": 1, "visible": True} - +# Templates to generate Real Name in test settings_iconOrImage_StatusSmartIdenticon = {"id": "iconOrImage", "type": "StatusSmartIdenticon", "unnamed": 1, "visible": True} settings_Name_StatusTextWithLoadingState = {"type": "StatusTextWithLoadingState", "unnamed": 1, "visible": True} settings_statusListItemSubTitle = {"objectName": "statusListItemSubTitle", "type": "StatusTextWithLoadingState", "visible": True} settings_member_StatusTextWithLoadingState = {"text": "1 member", "type": "StatusTextWithLoadingState", "unnamed": 1, "visible": True} settings_StatusFlatButton = {"type": "StatusFlatButton", "unnamed": 1, "visible": True} +# Messaging View +mainWindow_MessagingView = {"container": statusDesktop_mainWindow, "type": "MessagingView", "unnamed": 1, "visible": True} +contactsListItem_btn_StatusContactRequestsIndicatorListItem = {"container": mainWindow_MessagingView, "objectName": "MessagingView_ContactsListItem_btn", "type": "StatusContactRequestsIndicatorListItem", "visible": True} + +# Contacts View +mainWindow_ContactsView = {"container": statusDesktop_mainWindow, "type": "ContactsView", "unnamed": 1, "visible": True} +mainWindow_Send_contact_request_to_chat_key_StatusButton = {"checkable": False, "container": mainWindow_ContactsView, "objectName": "ContactsView_ContactRequest_Button", "type": "StatusButton", "visible": True} +contactsTabBar_Pending_Requests_StatusTabButton = {"checkable": True, "container": mainWindow_ContactsView, "objectName": "ContactsView_PendingRequest_Button", "type": "StatusTabButton", "visible": True} +settingsContentBaseScrollView_ContactListPanel = {"container": mainWindow_ContactsView, "objectName": "ContactListPanel_ListView", "type": "StatusListView", "visible": True} diff --git a/test/e2e/gui/screens/community.py b/test/e2e/gui/screens/community.py index 9ec2042317..335b5087a2 100644 --- a/test/e2e/gui/screens/community.py +++ b/test/e2e/gui/screens/community.py @@ -6,6 +6,7 @@ from allure_commons._allure import step import driver from constants import UserChannel from gui.components.community.community_channel_popups import EditChannelPopup, NewChannelPopup +from gui.components.community.welcome_community import WelcomeCommunityPopup from gui.components.delete_popup import DeletePopup from gui.elements.qt.button import Button from gui.elements.qt.list import List @@ -23,6 +24,7 @@ class CommunityScreen(QObject): self.left_panel = LeftPanel() self.tool_bar = ToolBar() self.chat = Chat() + self.right_panel = Members() @allure.step('Create channel') def create_channel(self, name: str, description: str, emoji: str = None): @@ -109,6 +111,7 @@ class LeftPanel(QObject): self._channel_icon_template = QObject('channel_identicon_StatusSmartIdenticon') self._channel_or_category_button = Button('mainWindow_createChannelOrCategoryBtn_StatusBaseText') self._create_channel_menu_item = Button('create_channel_StatusMenuItem') + self._join_community_button = Button('mainWindow_Join_Community_StatusButton') @property @allure.step('Get community logo') @@ -125,6 +128,11 @@ class LeftPanel(QObject): def members(self) -> str: return self._members_text_label.text + @property + @allure.step('Get Join button visible attribute') + def is_join_community_visible(self) -> bool: + return self._join_community_button.is_visible + @property @allure.step('Get channels') def channels(self) -> typing.List[UserChannel]: @@ -165,6 +173,11 @@ class LeftPanel(QObject): return raise LookupError('Channel not found') + @allure.step('Open join community popup') + def open_welcome_community_popup(self): + self._join_community_button.click() + return WelcomeCommunityPopup().wait_until_appears() + class Chat(QObject): @@ -188,3 +201,15 @@ class Chat(QObject): @allure.step('Get channel welcome note') def channel_welcome_note(self) -> str: return self._channel_welcome_label.text + + +class Members(QObject): + + def __init__(self): + super().__init__('mainWindow_UserListPanel') + self._member_item = QObject('userListPanel_StatusMemberListItem') + + @property + @allure.step('Get all members') + def members(self) -> typing.List[str]: + return [str(member.statusListItemTitle.text) for member in driver.findAllObjects(self._member_item.real_name)] diff --git a/test/e2e/gui/screens/community_portal.py b/test/e2e/gui/screens/community_portal.py index a28c6aa0ac..6ac35d6945 100644 --- a/test/e2e/gui/screens/community_portal.py +++ b/test/e2e/gui/screens/community_portal.py @@ -1,9 +1,8 @@ import allure -from gui.components.community.create_community_popups import CreateCommunitiesBanner +from gui.components.community.create_community_popups import CreateCommunitiesBanner, CreateCommunityPopup from gui.elements.qt.button import Button from gui.elements.qt.object import QObject -from gui.screens.community import CommunityScreen class CommunitiesPortal(QObject): @@ -13,6 +12,6 @@ class CommunitiesPortal(QObject): self._create_community_button = Button('mainWindow_Create_New_Community_StatusButton') @allure.step('Open create community popup') - def open_create_community_popup(self) -> CommunityScreen: + def open_create_community_popup(self) -> CreateCommunityPopup: self._create_community_button.click() return CreateCommunitiesBanner().wait_until_appears().open_create_community_popup() diff --git a/test/e2e/gui/screens/messages.py b/test/e2e/gui/screens/messages.py new file mode 100644 index 0000000000..a4bfd2d1c6 --- /dev/null +++ b/test/e2e/gui/screens/messages.py @@ -0,0 +1,124 @@ +import time +import typing + +import allure + +import configs +import driver +from driver.objects_access import walk_children +from gui.elements.qt.button import Button +from gui.elements.qt.list import List +from gui.elements.qt.object import QObject +from gui.elements.qt.scroll import Scroll +from gui.elements.qt.text_edit import TextEdit +from gui.screens.community import CommunityScreen +from scripts.tools.image import Image + + +class LeftPanel(QObject): + + def __init__(self): + super().__init__('mainWindow_contactColumnLoader_Loader') + self._start_chat_button = Button('mainWindow_startChatButton_StatusIconTabButton') + self._search_text_edit = TextEdit('mainWindow_search_edit_TextEdit') + self._scroll = Scroll('scrollView_Flickable') + self._contacts_list = List('chatList_ListView') + + @property + @allure.step('Get contacts') + def contacts(self) -> typing.List[str]: + return self._contacts_list.get_values('objectName') + + @allure.step('Open chat') + def open_chat(self, contact: str): + assert driver.waitFor(lambda: contact in self.contacts), f'Contact: {contact} not found in {self.contacts}' + self._contacts_list.select(contact, 'objectName') + return ChatView() + + +class ToolBar(QObject): + + def __init__(self): + super().__init__('mainWindow_statusToolBar_StatusToolBar') + + +class Message: + + def __init__(self, obj): + self.object = obj + self.date: typing.Optional[str] = None + self.time: typing.Optional[str] = None + self.icon: typing.Optional[Image] = None + self.from_user: typing.Optional[str] = None + self.text: typing.Optional[str] = None + self._join_community_button: typing.Optional[Button] = None + self.community_invitation: dict = {} + self.init_ui() + + def init_ui(self): + for child in walk_children(self.object): + if getattr(child, 'objectName', '') == 'StatusDateGroupLabel': + self.date = str(child.text) + elif getattr(child, 'objectName', '') == 'communityName': + self.community_invitation['name'] = str(child.text) + elif getattr(child, 'objectName', '') == 'communityDescription': + self.community_invitation['description'] = str(child.text) + elif getattr(child, 'objectName', '') == 'communityMembers': + self.community_invitation['members'] = str(child.text) + else: + match getattr(child, 'id', ''): + case 'profileImage': + self.icon = Image(driver.objectMap.realName(child)) + case 'primaryDisplayName': + self.from_user = str(child.text) + case 'timestampText': + self.time = str(child.text) + case 'chatText': + self.text = str(child.text) + case 'joinBtn': + self._join_community_button = Button(name='', real_name=driver.objectMap.realName(child)) + + @allure.step('Join community') + def join_community(self): + assert self._join_community_button is not None, 'Join button not found' + self._join_community_button.click() + return CommunityScreen().wait_until_appears() + + +class ChatView(QObject): + + def __init__(self): + super().__init__('mainWindow_ChatColumnView') + self._message_list_item = QObject('chatLogView_chatMessageViewDelegate_MessageView') + + @property + @allure.step('Get messages') + def messages(self) -> typing.List[Message]: + _messages = [] + for item in driver.findAllObjects(self._message_list_item.real_name): + if getattr(item, 'isMessage', False): + _messages.append(Message(item)) + return _messages + + @allure.step('Accept community invitation') + def accept_community_invite(self, community: str) -> 'CommunityScreen': + message = None + started_at = time.monotonic() + while message is None: + for _message in self.messages: + if _message.community_invitation.get('name', '') == community: + message = _message + break + if time.monotonic() - started_at > configs.timeouts.MESSAGING_TIMEOUT_SEC: + raise LookupError(f'Invitation not found') + + return message.join_community() + + +class MessagesScreen(QObject): + + def __init__(self): + super().__init__('mainWindow_chatView_ChatView') + self.left_panel = LeftPanel() + self.tool_bar = ToolBar() + self.chat = ChatView() diff --git a/test/e2e/gui/screens/settings.py b/test/e2e/gui/screens/settings.py index dccc789345..046765b3c3 100644 --- a/test/e2e/gui/screens/settings.py +++ b/test/e2e/gui/screens/settings.py @@ -1,32 +1,130 @@ +import time import typing import allure +import configs.timeouts import driver from constants import UserCommunityInfo from driver import objects_access +from driver.objects_access import walk_children +from gui.components.settings.send_contact_request_popup import SendContactRequest from gui.elements.qt.button import Button +from gui.elements.qt.list import List from gui.elements.qt.object import QObject from gui.elements.qt.text_label import TextLabel from gui.screens.community_settings import CommunitySettingsScreen +from gui.screens.messages import MessagesScreen +from scripts.tools.image import Image -class SettingsScreen(QObject): +class LeftPanel(QObject): def __init__(self): - super().__init__('mainWindow_ProfileLayout') + super().__init__('mainWindow_LeftTabView') self._settings_section_template = QObject('scrollView_AppMenuItem_StatusNavigationListItem') def _open_settings(self, index: int): self._settings_section_template.real_name['objectName'] = f'{index}-AppMenuItem' self._settings_section_template.click() + @allure.step('Open messaging settings') + def open_messaging_settings(self) -> 'MessagingSettingsView': + self._open_settings(3) + return MessagingSettingsView() + @allure.step('Open communities settings') - def open_communities_settings(self): + def open_communities_settings(self) -> 'CommunitiesSettingsView': self._open_settings(12) return CommunitiesSettingsView() +class SettingsScreen(QObject): + + def __init__(self): + super().__init__('mainWindow_ProfileLayout') + self.left_panel = LeftPanel() + + +class MessagingSettingsView(QObject): + + def __init__(self): + super().__init__('mainWindow_MessagingView') + self._contacts_button = Button('contactsListItem_btn_StatusContactRequestsIndicatorListItem') + + @allure.step('Open contacts settings') + def open_contacts_settings(self) -> 'ContactsSettingsView': + self._contacts_button.click() + return ContactsSettingsView().wait_until_appears() + + +class PendingRequest: + + def __init__(self, obj): + self.object = obj + self.icon: typing.Optional[Image] = None + self.contact: typing.Optional[Image] = None + self._accept_button: typing.Optional[Button] = None + self._reject_button: typing.Optional[Button] = None + self._open_canvas_button: typing.Optional[Button] = None + self.init_ui() + + def __repr__(self): + return self.contact + + def init_ui(self): + for child in walk_children(self.object): + if str(getattr(child, 'id', '')) == 'iconOrImage': + self.icon = Image(driver.objectMap.realName(child)) + elif str(getattr(child, 'id', '')) == 'menuButton': + self._open_canvas_button = Button(name='', real_name=driver.objectMap.realName(child)) + elif str(getattr(child, 'objectName', '')) == 'checkmark-circle-icon': + self._accept_button = Button(name='', real_name=driver.objectMap.realName(child)) + elif str(getattr(child, 'objectName', '')) == 'close-circle-icon': + self._reject_button = Button(name='', real_name=driver.objectMap.realName(child)) + elif str(getattr(child, 'id', '')) == 'statusListItemTitle': + self.contact = str(child.text) + + def accept(self) -> MessagesScreen: + assert self._accept_button is not None, 'Button not found' + self._accept_button.click() + return MessagesScreen().wait_until_appears() + + +class ContactsSettingsView(QObject): + + def __init__(self): + super().__init__('mainWindow_ContactsView') + self._contact_request_button = Button('mainWindow_Send_contact_request_to_chat_key_StatusButton') + self._pending_request_tab = Button('contactsTabBar_Pending_Requests_StatusTabButton') + self._pending_requests_list = List('settingsContentBaseScrollView_ContactListPanel') + + @property + @allure.step('Get all pending requests') + def pending_requests(self) -> typing.List[PendingRequest]: + self._pending_request_tab.click() + return [PendingRequest(item) for item in self._pending_requests_list.items] + + @allure.step('Open contacts request form') + def open_contact_request_form(self) -> SendContactRequest: + self._contact_request_button.click() + return SendContactRequest().wait_until_appears() + + @allure.step('Accept contact request') + def accept_contact_request( + self, contact: str, timeout_sec: int = configs.timeouts.MESSAGING_TIMEOUT_SEC) -> MessagesScreen: + self._pending_request_tab.click() + started_at = time.monotonic() + request = None + while request is None: + requests = self.pending_requests + for _request in requests: + if _request.contact == contact: + request = _request + assert time.monotonic() - started_at < timeout_sec, f'Contact: {contact} not found in {requests}' + return request.accept() + + class CommunitiesSettingsView(QObject): def __init__(self): diff --git a/test/e2e/requirements.txt b/test/e2e/requirements.txt index 4a99e3c485..815a57fd6a 100644 --- a/test/e2e/requirements.txt +++ b/test/e2e/requirements.txt @@ -7,3 +7,4 @@ pytesseract==0.3.10 atomacos==3.3.0; platform_system == "Darwin" allure-pytest==2.13.2 testrail-api==1.12.0 +pyperclip==1.8.2 diff --git a/test/e2e/scripts/utils/local_system.py b/test/e2e/scripts/utils/local_system.py index cb9bd69326..1aa4d85e8b 100644 --- a/test/e2e/scripts/utils/local_system.py +++ b/test/e2e/scripts/utils/local_system.py @@ -23,9 +23,25 @@ def find_process_by_port(port: int) -> int: pass +def wait_for_close(pid: int, timeout_sec: int = configs.timeouts.PROCESS_TIMEOUT_SEC): + started_at = time.monotonic() + while True: + if pid in [proc.pid for proc in psutil.process_iter()]: + time.sleep(1) + if time.monotonic() - started_at > timeout_sec: + raise RuntimeError(f'Process with PID: {pid} not closed') + else: + break + + @allure.step('Kill process') -def kill_process(pid): - os.kill(pid, signal.SIGILL if IS_WIN else signal.SIGKILL) +def kill_process(pid, verify: bool = False): + try: + os.kill(pid, signal.SIGILL if IS_WIN else signal.SIGKILL) + except ProcessLookupError as err: + _logger.debug(err) + if verify: + wait_for_close(pid) @allure.step('System execute command') diff --git a/test/e2e/tests/fixtures/aut.py b/test/e2e/tests/fixtures/aut.py index 4413dcb982..b759c05e7b 100644 --- a/test/e2e/tests/fixtures/aut.py +++ b/test/e2e/tests/fixtures/aut.py @@ -1,5 +1,3 @@ -from datetime import datetime - import pytest import configs @@ -7,33 +5,39 @@ import constants from constants import UserAccount from driver.aut import AUT from gui.main_window import MainWindow -from gui.screens.onboarding import LoginView from scripts.utils import system_path - - -@pytest.fixture() -def aut() -> AUT: - if not configs.APP_DIR.exists(): - pytest.exit(f"Application not found: {configs.APP_DIR}") - _aut = AUT() - yield _aut +from scripts.utils.system_path import SystemPath @pytest.fixture def user_data(request) -> system_path.SystemPath: - user_data = configs.testpath.STATUS_DATA / f'app_{datetime.now():%H%M%S_%f}' / 'data' if hasattr(request, 'param'): fp = request.param - if isinstance(fp, str): - fp = configs.testpath.TEST_USER_DATA / fp / 'data' assert fp.is_dir() - fp.copy_to(user_data) - yield user_data + return fp + + +@pytest.fixture +def aut(user_data) -> AUT: + if not configs.APP_DIR.exists(): + pytest.exit(f"Application not found: {configs.APP_DIR}") + _aut = AUT(user_data=user_data) + yield _aut + + +@pytest.fixture() +def multiple_instance(): + def _aut(user_data: SystemPath = None) -> AUT: + if not configs.APP_DIR.exists(): + pytest.exit(f"Application not found: {configs.APP_DIR}") + return AUT(user_data=user_data) + + yield _aut @pytest.fixture def main_window(aut: AUT, user_data): - aut.launch(f'-d={user_data.parent}') + aut.launch() yield MainWindow().wait_until_appears().prepare() aut.detach().stop() @@ -44,22 +48,11 @@ def user_account(request) -> UserAccount: user_account = request.param assert isinstance(user_account, UserAccount) else: - user_account = constants.user.user_account_default + user_account = constants.user.user_account_one yield user_account @pytest.fixture def main_screen(user_account: UserAccount, main_window: MainWindow) -> MainWindow: - if LoginView().is_visible: - yield main_window.log_in(user_account) - else: - yield main_window.sign_up(user_account) - - -@pytest.fixture -def community(main_screen, request) -> dict: - community_params = request.param - communities_portal = main_screen.left_panel.open_communities_portal() - create_community_form = communities_portal.open_create_community_popup() - create_community_form.create(community_params) - return community_params + main_window.authorize_user(user_account) + return main_window diff --git a/test/e2e/tests/test_communities.py b/test/e2e/tests/test_communities.py index 836a3cf784..6cb00d6896 100644 --- a/test/e2e/tests/test_communities.py +++ b/test/e2e/tests/test_communities.py @@ -1,3 +1,4 @@ +from copy import deepcopy from datetime import datetime import allure @@ -5,7 +6,9 @@ import pytest from allure_commons._allure import step import configs.testpath -import constants.user +import constants +import driver +from constants import UserAccount from gui.main_window import MainWindow from gui.screens.community import CommunityScreen from scripts.tools import image @@ -15,7 +18,7 @@ pytestmark = allure.suite("Communities") @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703084', 'Create community') @pytest.mark.case(703084) -@pytest.mark.parametrize('params', [constants.user.default_community_params]) +@pytest.mark.parametrize('params', [constants.community_params]) def test_create_community(user_account, main_screen: MainWindow, params): with step('Create community'): communities_portal = main_screen.left_panel.open_communities_portal() @@ -46,7 +49,7 @@ def test_create_community(user_account, main_screen: MainWindow, params): with step('Verify community parameters in community settings screen'): settings_screen = main_screen.left_panel.open_settings() - community_settings = settings_screen.open_communities_settings() + community_settings = settings_screen.left_panel.open_communities_settings() community = community_settings.get_community_info(params['name']) assert community.name == params['name'] assert community.description == params['description'] @@ -56,19 +59,19 @@ def test_create_community(user_account, main_screen: MainWindow, params): @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703056', 'Edit community separately') @pytest.mark.case(703056) -@pytest.mark.parametrize('community', [constants.user.default_community_params], indirect=True) @pytest.mark.parametrize('community_params', [ - { - 'name': f'Name_{datetime.now():%H%M%S}', - 'description': f'Description_{datetime.now():%H%M%S}', - 'color': '#ff7d46', - }, + { + 'name': f'Name_{datetime.now():%H%M%S}', + 'description': f'Description_{datetime.now():%H%M%S}', + 'color': '#ff7d46', + }, ]) -def test_edit_community_separately(main_screen, community: dict, community_params): +def test_edit_community_separately(main_screen, community_params): + main_screen.create_community(constants.community_params) with step('Edit community name'): - community_screen = main_screen.left_panel.select_community(community['name']) + community_screen = main_screen.left_panel.select_community(constants.community_params['name']) community_setting = community_screen.left_panel.open_community_settings() edit_community_form = community_setting.left_panel.open_overview().open_edit_community_view() edit_community_form.edit({'name': community_params['name']}) @@ -77,7 +80,7 @@ def test_edit_community_separately(main_screen, community: dict, community_param overview_setting = community_setting.left_panel.open_overview() assert overview_setting.name == community_params['name'] with step('Description is correct'): - assert overview_setting.description == constants.default_community_params['description'] + assert overview_setting.description == constants.community_params['description'] with step('Edit community name'): edit_community_form = overview_setting.open_edit_community_view() @@ -92,7 +95,6 @@ def test_edit_community_separately(main_screen, community: dict, community_param @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703057', 'Edit community') @pytest.mark.case(703057) -@pytest.mark.parametrize('community', [constants.user.default_community_params], indirect=True) @pytest.mark.parametrize('params', [ { 'name': 'Updated Name', @@ -105,10 +107,11 @@ def test_edit_community_separately(main_screen, community: dict, community_param 'outro': 'Updated Outro' } ]) -def test_edit_community(main_screen: MainWindow, community: dict, params): +def test_edit_community(main_screen: MainWindow, params): + main_screen.create_community(constants.community_params) with step('Edit community'): - community_screen = main_screen.left_panel.select_community(community['name']) + community_screen = main_screen.left_panel.select_community(constants.community_params['name']) community_setting = community_screen.left_panel.open_community_settings() edit_community_form = community_setting.left_panel.open_overview().open_edit_community_view() edit_community_form.edit(params) @@ -132,7 +135,7 @@ def test_edit_community(main_screen: MainWindow, community: dict, params): with step('Verify community parameters in community settings screen'): settings_screen = main_screen.left_panel.open_settings() - community_settings = settings_screen.open_communities_settings() + community_settings = settings_screen.left_panel.open_communities_settings() community_info = community_settings.communities[0] assert community_info.name == params['name'] assert community_info.description == params['description'] @@ -142,11 +145,10 @@ def test_edit_community(main_screen: MainWindow, community: dict, params): @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703049', 'Create community channel') @pytest.mark.case(703049) -@pytest.mark.parametrize('community', [constants.user.default_community_params], indirect=True) @pytest.mark.parametrize('channel_name, channel_description, channel_emoji', [('Channel', 'Description', 'sunglasses')]) -def test_create_community_channel(main_screen: MainWindow, community: dict, channel_name, channel_description, - channel_emoji): - community_screen = main_screen.left_panel.select_community(community['name']) +def test_create_community_channel(main_screen: MainWindow, channel_name, channel_description, channel_emoji): + main_screen.create_community(constants.community_params) + community_screen = main_screen.left_panel.select_community(constants.community_params['name']) community_screen.create_channel(channel_name, channel_description, channel_emoji) with step('Verify channel'): @@ -161,9 +163,9 @@ def test_create_community_channel(main_screen: MainWindow, community: dict, chan @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703050', 'Edit community channel') @pytest.mark.case(703050) -@pytest.mark.parametrize('community', [constants.user.default_community_params], indirect=True) @pytest.mark.parametrize('channel_name, channel_description, channel_emoji', [('Channel', 'Description', 'sunglasses')]) -def test_edit_community_channel(community: dict, channel_name, channel_description, channel_emoji): +def test_edit_community_channel(main_screen, channel_name, channel_description, channel_emoji): + main_screen.create_community(constants.community_params) community_screen = CommunityScreen() with step('Verify General channel'): @@ -189,10 +191,94 @@ def test_edit_community_channel(community: dict, channel_name, channel_descripti @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703051', 'Delete community channel') @pytest.mark.case(703051) -@pytest.mark.parametrize('community', [constants.user.default_community_params], indirect=True) -def test_delete_community_channel(community: dict): +def test_delete_community_channel(main_screen): + main_screen.create_community(constants.community_params) + with step('Delete channel'): CommunityScreen().delete_channel('general') with step('Verify channel is not exists'): assert not CommunityScreen().left_panel.channels + + +@allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703510', 'Join community via owner invite') +@pytest.mark.case(703510) +@pytest.mark.parametrize('user_data_one, user_data_two', [ + (configs.testpath.TEST_USER_DATA / 'user_account_one', configs.testpath.TEST_USER_DATA / 'user_account_two') +]) +def test_join_community_via_owner_invite(multiple_instance, user_data_one, user_data_two): + user_one: UserAccount = constants.user_account_one + user_two: UserAccount = constants.user_account_two + community_params = deepcopy(constants.community_params) + community_params['name'] = f'{datetime.now():%d%m%Y_%H%M%S}' + main_window = MainWindow() + + with multiple_instance() as aut_one, multiple_instance() as aut_two: + with step(f'Launch multiple instances with authorized users {user_one.name} and {user_two.name}'): + for aut, account in zip([aut_one, aut_two], [user_one, user_two]): + aut.attach() + main_window.wait_until_appears(configs.timeouts.APP_LOAD_TIMEOUT_MSEC).prepare() + main_window.authorize_user(account) + main_window.hide() + + with step(f'User {user_two.name}, get chat key'): + aut_two.attach() + main_window.prepare() + profile_popup = main_window.left_panel.open_user_canvas().open_profile_popup() + chat_key = profile_popup.chat_key + profile_popup.close() + main_window.hide() + + with step(f'User {user_one.name}, send contact request to {user_two.name}'): + aut_one.attach() + main_window.prepare() + settings = main_window.left_panel.open_settings() + messaging_settings = settings.left_panel.open_messaging_settings() + contacts_settings = messaging_settings.open_contacts_settings() + contact_request_popup = contacts_settings.open_contact_request_form() + contact_request_popup.send(chat_key, f'Hello {user_two.name}') + main_window.hide() + + with step(f'User {user_two.name}, accept contact request from {user_one.name}'): + aut_two.attach() + main_window.prepare() + settings = main_window.left_panel.open_settings() + messaging_settings = settings.left_panel.open_messaging_settings() + contacts_settings = messaging_settings.open_contacts_settings() + contacts_settings.accept_contact_request(user_one.name) + main_window.hide() + + with step(f'User {user_one.name}, create community and {user_two.name}'): + aut_one.attach() + main_window.prepare() + main_window.create_community(community_params) + main_window.left_panel.invite_people_in_community([user_two.name], 'Message', community_params['name']) + main_window.hide() + + with step(f'User {user_two.name}, accept invitation from {user_one.name}'): + aut_two.attach() + main_window.prepare() + messages_view = main_window.left_panel.open_messages_screen() + chat = messages_view.left_panel.open_chat(user_one.name) + community_screen = chat.accept_community_invite(community_params['name']) + + with step(f'User {user_two.name}, verify welcome community popup'): + welcome_popup = community_screen.left_panel.open_welcome_community_popup() + assert community_params['name'] in welcome_popup.title + assert community_params['intro'] == welcome_popup.intro + welcome_popup.join().authenticate(user_one.password) + assert driver.waitFor(lambda: not community_screen.left_panel.is_join_community_visible, + configs.timeouts.UI_LOAD_TIMEOUT_MSEC), 'Join community button not hidden' + + with step(f'User {user_two.name}, see two members in community members list'): + assert user_one.name in community_screen.right_panel.members + assert driver.waitFor(lambda: user_two.name in community_screen.right_panel.members) + assert '2' in community_screen.left_panel.members + main_window.hide() + + with step(f'User {user_one.name}, see two members in community members list'): + aut_one.attach() + main_window.prepare() + assert user_one.name in community_screen.right_panel.members + assert driver.waitFor(lambda: user_two.name in community_screen.right_panel.members) + assert '2' in community_screen.left_panel.members diff --git a/test/e2e/tests/test_onboarding.py b/test/e2e/tests/test_onboarding.py index bf49abe19c..d2cfb1b50b 100755 --- a/test/e2e/tests/test_onboarding.py +++ b/test/e2e/tests/test_onboarding.py @@ -108,7 +108,7 @@ def test_generate_new_keys(main_window, keys_screen, user_name: str, password, u @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703039', 'Import: 12 word seed phrase') @pytest.mark.case(703039) -@pytest.mark.parametrize('user_account', [constants.user.user_account_default]) +@pytest.mark.parametrize('user_account', [constants.user.user_account_two]) def test_import_seed_phrase(keys_screen, main_window, user_account): with step('Open import seed phrase view and enter seed phrase'): input_view = keys_screen.open_import_seed_phrase_view().open_seed_phrase_input_view() diff --git a/test/e2e/tests/test_wallet.py b/test/e2e/tests/test_wallet.py index af6116fe1e..53b92c09ec 100644 --- a/test/e2e/tests/test_wallet.py +++ b/test/e2e/tests/test_wallet.py @@ -7,7 +7,7 @@ from allure import step import configs.timeouts import constants import driver -from gui.components.authenticate_popup import AuthenticatePopup +from gui.components.wallet.authenticate_popup import AuthenticatePopup from gui.components.signing_phrase_popup import SigningPhrasePopup from gui.main_window import MainWindow