diff --git a/test/e2e/README.md b/test/e2e/README.md index 33a409a7ff..02cdeafdff 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -1 +1,3 @@ # desktop-qa-automation + +https://www.notion.so/Setup-Environment-e5d88399027042a0992e85fd9b0e5167?pvs=4 \ No newline at end of file diff --git a/test/e2e/configs/__init__.py b/test/e2e/configs/__init__.py new file mode 100644 index 0000000000..8f4c3bcd06 --- /dev/null +++ b/test/e2e/configs/__init__.py @@ -0,0 +1,25 @@ +import logging + +from scripts.utils.system_path import SystemPath +from . import testpath, timeouts, testrail, squish, system + +_logger = logging.getLogger(__name__) + +try: + from ._local import * +except ImportError: + exit( + 'Config file: "_local.py" not found in "./configs".\n' + 'Please use template "_.local.py.default" to create file or execute command: \n' + rf'cp {testpath.ROOT}/configs/_local.py.default {testpath.ROOT}/configs/_local.py' + ) + +if APP_DIR is None: + exit('Please add "APP_DIR" in ./configs/_local.py') +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 new file mode 100644 index 0000000000..267eac0e17 --- /dev/null +++ b/test/e2e/configs/_local.py.ci @@ -0,0 +1,7 @@ +import logging +import os + +LOG_LEVEL = logging.DEBUG +DEV_BUILD = False + +APP_DIR = os.getenv('APP_DIR') diff --git a/test/e2e/configs/_local.py.default b/test/e2e/configs/_local.py.default new file mode 100644 index 0000000000..e3d99d1bd4 --- /dev/null +++ b/test/e2e/configs/_local.py.default @@ -0,0 +1,6 @@ +import logging + +LOG_LEVEL = logging.DEBUG +DEV_BUILD = False + +APP_DIR = None diff --git a/test/e2e/configs/squish.py b/test/e2e/configs/squish.py new file mode 100644 index 0000000000..13a84b0dce --- /dev/null +++ b/test/e2e/configs/squish.py @@ -0,0 +1 @@ +AUT_PORT = 61500 \ No newline at end of file diff --git a/test/e2e/configs/system.py b/test/e2e/configs/system.py new file mode 100644 index 0000000000..03eef64e95 --- /dev/null +++ b/test/e2e/configs/system.py @@ -0,0 +1,7 @@ +import platform + +IS_LIN = True if platform.system() == 'Linux' else False +IS_MAC = True if platform.system() == 'Darwin' else False +IS_WIN = True if platform.system() == 'Windows' else False + +OS_ID = 'lin' if IS_LIN else 'mac' if IS_MAC else 'win' diff --git a/test/e2e/configs/testpath.py b/test/e2e/configs/testpath.py new file mode 100644 index 0000000000..109bce3137 --- /dev/null +++ b/test/e2e/configs/testpath.py @@ -0,0 +1,27 @@ +import os +import typing +from datetime import datetime + +from scripts.utils.system_path import SystemPath + +ROOT: SystemPath = SystemPath(__file__).resolve().parent.parent + +# Runtime initialisation +TEST: typing.Optional[SystemPath] = None +TEST_VP: typing.Optional[SystemPath] = None +TEST_ARTIFACTS: typing.Optional[SystemPath] = None + +# Test Directories +RUN_ID = os.getenv('RUN_DIR', f'run_{datetime.now():%d%m%Y_%H%M%S}') +TEMP: SystemPath = ROOT / 'tmp' +RESULTS: SystemPath = TEMP / 'results' +RUN: SystemPath = RESULTS / RUN_ID +VP: SystemPath = ROOT / 'ext' / 'vp' +TEST_FILES: SystemPath = ROOT / 'ext' / 'test_files' +TEST_USER_DATA: SystemPath = ROOT / 'ext' / 'user_data' + +# Driver Directories +SQUISH_DIR = SystemPath(os.getenv('SQUISH_DIR')) + +# Status Application +STATUS_DATA: SystemPath = RUN / 'status' diff --git a/test/e2e/configs/testrail.py b/test/e2e/configs/testrail.py new file mode 100644 index 0000000000..cd3a43755f --- /dev/null +++ b/test/e2e/configs/testrail.py @@ -0,0 +1,6 @@ +import os + +TESTRAIL_RUN_ID = os.getenv('TESTRAIL_URL', '').strip() +TESTRAIL_URL = os.getenv('TESTRAIL_URL', None) +TESTRAIL_USER = os.getenv('TESTRAIL_USER', None) +TESTRAIL_PWD = os.getenv('TESTRAIL_PWD', None) diff --git a/test/e2e/configs/timeouts.py b/test/e2e/configs/timeouts.py new file mode 100644 index 0000000000..5f9016f202 --- /dev/null +++ b/test/e2e/configs/timeouts.py @@ -0,0 +1,6 @@ +# Timoeuts before raising errors + +UI_LOAD_TIMEOUT_SEC = 5 +UI_LOAD_TIMEOUT_MSEC = UI_LOAD_TIMEOUT_SEC * 1000 +PROCESS_TIMEOUT_SEC = 10 +APP_LOAD_TIMEOUT_MSEC = 60000 diff --git a/test/e2e/conftest.py b/test/e2e/conftest.py new file mode 100644 index 0000000000..39f7b18fcb --- /dev/null +++ b/test/e2e/conftest.py @@ -0,0 +1,66 @@ +import logging +from datetime import datetime + +import allure +import pytest +from PIL import ImageGrab + +import configs +import driver +from driver.aut import AUT +from scripts.utils.system_path import SystemPath +from tests.fixtures.path import generate_test_info + +_logger = logging.getLogger(__name__) + +pytest_plugins = [ + 'tests.fixtures.aut', + 'tests.fixtures.path', + 'tests.fixtures.squish', + 'tests.fixtures.testrail', +] + + +@pytest.fixture(scope='session', autouse=True) +def setup_session_scope( + init_testrail_api, + prepare_test_directory, + start_squish_server, +): + yield + + +@pytest.fixture(autouse=True) +def setup_function_scope( + caplog, + generate_test_data, + check_result +): + caplog.set_level(configs.LOG_LEVEL) + yield + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + outcome = yield + rep = outcome.get_result() + setattr(item, 'rep_' + rep.when, rep) + + +def pytest_exception_interact(node): + try: + test_path, test_name, test_params = generate_test_info(node) + node_dir: SystemPath = configs.testpath.RUN / test_path / test_name / test_params + node_dir.mkdir(parents=True, exist_ok=True) + + screenshot = node_dir / 'screenshot.png' + if screenshot.exists(): + screenshot = node_dir / f'screenshot_{datetime.now():%H%M%S}.png' + ImageGrab.grab().save(screenshot) + allure.attach( + name='Screenshot on fail', + body=screenshot.read_bytes(), + attachment_type=allure.attachment_type.PNG) + driver.context.detach() + except Exception as ex: + _logger.debug(ex) diff --git a/test/e2e/constants/__init__.py b/test/e2e/constants/__init__.py new file mode 100644 index 0000000000..7c9bbe54c3 --- /dev/null +++ b/test/e2e/constants/__init__.py @@ -0,0 +1,4 @@ +from . import commands +from .colors import * +from .tesseract import * +from .user import * diff --git a/test/e2e/constants/colors.py b/test/e2e/constants/colors.py new file mode 100644 index 0000000000..96e3f1fe4a --- /dev/null +++ b/test/e2e/constants/colors.py @@ -0,0 +1,49 @@ +from enum import Enum + + +class Color(Enum): + WHITE = 1 + BLACK = 2 + RED = 3 + BLUE = 4 + GREEN = 5 + YELLOW = 6 + ORANGE = 7 + + +boundaries = { + Color.WHITE: [ + [0, 0, 0], + [0, 0, 255] + ], + Color.BLACK: [ + [0, 0, 0], + [179, 100, 130] + ], + Color.RED: [ + [ + [0, 100, 20], + [10, 255, 255] + ], + [ + [160, 100, 20], + [179, 255, 255] + ] + ], + Color.BLUE: [ + [110, 50, 50], + [130, 255, 255] + ], + Color.GREEN: [ + [36, 25, 25], + [70, 255, 255] + ], + Color.YELLOW: [ + [20, 100, 0], + [45, 255, 255] + ], + Color.ORANGE: [ + [10, 100, 20], + [25, 255, 255] + ] +} \ No newline at end of file diff --git a/test/e2e/constants/commands.py b/test/e2e/constants/commands.py new file mode 100644 index 0000000000..05d64b6bdc --- /dev/null +++ b/test/e2e/constants/commands.py @@ -0,0 +1,13 @@ +import configs.system + +# Buttons +BACKSPACE = 'Backspace' +COMMAND = 'Command' +CTRL = 'Ctrl' +ESCAPE = 'Escape' +RETURN = 'Return' +SHIFT = 'Shift' + +# Combinations +SELECT_ALL = f'{CTRL if configs.system.IS_WIN else COMMAND}+A' +OPEN_GOTO = f'{COMMAND}+{SHIFT}+G' diff --git a/test/e2e/constants/tesseract.py b/test/e2e/constants/tesseract.py new file mode 100644 index 0000000000..a33c56c5cd --- /dev/null +++ b/test/e2e/constants/tesseract.py @@ -0,0 +1,30 @@ +""" +Tesseract provides various configuration parameters that can be used to customize the OCR process. These parameters are passed as command-line arguments to Tesseract through the --oem and --psm options or through the config parameter in pytesseract. Here are some commonly used Tesseract configuration parameters: + +--oem (OCR Engine Mode): This parameter specifies the OCR engine mode to use. The available options are: + +0: Original Tesseract only. +1: Neural nets LSTM only. +2: Tesseract + LSTM. +3: Default, based on what is available. +--psm (Page Segmentation Mode): This parameter defines the page layout analysis mode to use. The available options are: + +0: Orientation and script detection (OSD) only. +1: Automatic page segmentation with OSD. +2: Automatic page segmentation, but no OSD or OCR. +3: Fully automatic page segmentation, but no OSD. (Default) +4: Assume a single column of text of variable sizes. +5: Assume a single uniform block of vertically aligned text. +6: Assume a single uniform block of text. +7: Treat the image as a single text line. +8: Treat the image as a single word. +9: Treat the image as a single word in a circle. +10: Treat the image as a single character. +--lang (Language): This parameter specifies the language(s) to use for OCR. Multiple languages can be specified separated by plus (+) signs. For example, --lang eng+fra for English and French. + +--tessdata-dir (Tessdata Directory): This parameter sets the path to the directory containing Tesseract's language data files. + +These are just a few examples of the commonly used configuration parameters in Tesseract. There are many more options available for advanced customization and fine-tuning of OCR results. You can refer to the official Tesseract documentation for a comprehensive list of configuration parameters and their descriptions: https://tesseract-ocr.github.io/tessdoc/Command-Line-Usage.html +""" + +text_on_profile_image = r'--oem 3 --psm 10' diff --git a/test/e2e/constants/user.py b/test/e2e/constants/user.py new file mode 100644 index 0000000000..6a59a0119d --- /dev/null +++ b/test/e2e/constants/user.py @@ -0,0 +1,25 @@ +from collections import namedtuple + +import configs + +UserAccount = namedtuple('User', ['name', 'password', 'seed_phrase']) +user_account_default = 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_three = UserAccount('Nervous', 'TesTEr16843/!@00', []) + +default_community_params = { + 'name': 'Name', + 'description': 'Description', + 'logo': {'fp': configs.testpath.TEST_FILES / 'tv_signal.png', 'zoom': None, 'shift': None}, + 'banner': {'fp': configs.testpath.TEST_FILES / 'banner.png', 'zoom': None, 'shift': None}, + 'intro': 'Intro', + 'outro': 'Outro' +} + +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 diff --git a/test/e2e/constants/wallet.py b/test/e2e/constants/wallet.py new file mode 100644 index 0000000000..2f8d34f159 --- /dev/null +++ b/test/e2e/constants/wallet.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class DerivationPath(Enum): + CUSTOM = 'Custom' + ETHEREUM = 'Ethereum' + ETHEREUM_ROPSTEN = 'Ethereum Testnet (Ropsten)' + ETHEREUM_LEDGER = 'Ethereum (Ledger)' + ETHEREUM_LEDGER_LIVE = 'Ethereum (Ledger Live/KeepKey)' diff --git a/test/e2e/driver/__init__.py b/test/e2e/driver/__init__.py new file mode 100755 index 0000000000..00f88059d9 --- /dev/null +++ b/test/e2e/driver/__init__.py @@ -0,0 +1,28 @@ +import squishtest # noqa + +import configs +from . import server, context, objects_access, toplevel_window, aut, atomacos, mouse + +imports = {module.__name__: module for module in [ + atomacos, + aut, + context, + objects_access, + mouse, + server, + toplevel_window + +]} + + +def __getattr__(name): + if name in imports: + return imports[name] + try: + return getattr(squishtest, name) + except AttributeError: + raise ImportError(f'Module "driver" has no attribute "{name}"') + + +squishtest.testSettings.waitForObjectTimeout = configs.timeouts.UI_LOAD_TIMEOUT_MSEC +squishtest.setHookSubprocesses(True) diff --git a/test/e2e/driver/atomacos.py b/test/e2e/driver/atomacos.py new file mode 100644 index 0000000000..c80657e782 --- /dev/null +++ b/test/e2e/driver/atomacos.py @@ -0,0 +1,63 @@ +import time +from copy import deepcopy + +import configs.timeouts +from scripts.utils import local_system + +if configs.system.IS_MAC: + from atomacos._a11y import _running_apps_with_bundle_id + import atomacos + +BUNDLE_ID = 'im.Status.NimStatusClient' + + +# https://pypi.org/project/atomacos/ + + +def attach_atomac(timeout_sec: int = configs.timeouts.UI_LOAD_TIMEOUT_SEC): + def from_bundle_id(bundle_id): + """ + Get the top level element for the application with the specified + bundle ID, such as com.vmware.fusion. + """ + apps = _running_apps_with_bundle_id(bundle_id) + if not apps: + raise ValueError( + "Specified bundle ID not found in " "running apps: %s" % bundle_id + ) + return atomacos.NativeUIElement.from_pid(apps[-1].processIdentifier()) + + if configs.DEV_BUILD: + pid = local_system.find_process_by_port(configs.squish.AUT_PORT) + atomator = atomacos.getAppRefByPid(pid) + else: + atomator = from_bundle_id(BUNDLE_ID) + started_at = time.monotonic() + while not hasattr(atomator, 'AXMainWindow'): + time.sleep(1) + assert time.monotonic() - started_at < timeout_sec, f'Attach error: {BUNDLE_ID}' + return atomator + + +def find_object(object_name: dict): + _object_name = deepcopy(object_name) + if 'container' in _object_name: + parent = find_object(_object_name['container']) + del _object_name['container'] + else: + return attach_atomac().windows()[0] + + assert parent is not None, f'Object not found: {object_name["container"]}' + _object = parent.findFirst(**_object_name) + assert _object is not None, f'Object not found: {_object_name}' + return _object + + +def wait_for_object(object_name: dict, timeout_sec: int = configs.timeouts.UI_LOAD_TIMEOUT_SEC): + started_at = time.monotonic() + while True: + try: + return find_object(object_name) + except AssertionError as err: + if time.monotonic() - started_at > timeout_sec: + raise LookupError(f'Object: {object_name} not found. Error: {err}') diff --git a/test/e2e/driver/aut.py b/test/e2e/driver/aut.py new file mode 100644 index 0000000000..a7f65e1b2e --- /dev/null +++ b/test/e2e/driver/aut.py @@ -0,0 +1,75 @@ +import allure +import squish + +import configs +import driver +from configs.system import IS_LIN +from driver import context +from driver.server import SquishServer +from scripts.utils import system_path, local_system + + +class AUT: + def __init__( + self, + app_path: system_path.SystemPath = configs.APP_DIR, + host: str = '127.0.0.1', + port: int = configs.squish.AUT_PORT + ): + super(AUT, self).__init__() + self.path = app_path + self.host = host + self.port = int(port) + self.ctx = None + self.pid = None + self.aut_id = self.path.name if IS_LIN else self.path.stem + driver.testSettings.setWrappersForApplication(self.aut_id, ['Qt']) + + def __str__(self): + return type(self).__qualname__ + + @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: + self.ctx = context.attach('AUT', timeout_sec) + try: + squish.setApplicationContext(self.ctx) + except TypeError as err: + if attempt: + return self.attach(timeout_sec, attempt - 1) + else: + raise err + + @allure.step('Detach Squish and Application') + 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) + + @allure.step("Start application") + def launch(self, *args) -> 'AUT': + 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) + 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) + + self.attach() + self.pid = self.ctx.pid + assert squish.waitFor(lambda: self.ctx.isRunning, configs.timeouts.PROCESS_TIMEOUT_SEC) + return self diff --git a/test/e2e/driver/context.py b/test/e2e/driver/context.py new file mode 100644 index 0000000000..4577c2c863 --- /dev/null +++ b/test/e2e/driver/context.py @@ -0,0 +1,31 @@ +import logging +import time + +import allure +import squish + +import configs + +_logger = logging.getLogger(__name__) + + +@allure.step('Attaching to "{0}"') +def attach(aut_name: str, timeout_sec: int = configs.timeouts.PROCESS_TIMEOUT_SEC): + started_at = time.monotonic() + while True: + try: + context = squish.attachToApplication(aut_name) + _logger.info(f'AUT: {aut_name} attached') + return context + except RuntimeError as err: + _logger.debug(err) + time.sleep(1) + assert time.monotonic() - started_at < timeout_sec, f'Attach error: {aut_name}' + + +@allure.step('Detaching') +def detach(): + for ctx in squish.applicationContextList(): + ctx.detach() + assert squish.waitFor(lambda: not ctx.isRunning, configs.timeouts.APP_LOAD_TIMEOUT_MSEC) + _logger.info(f'All AUTs detached') diff --git a/test/e2e/driver/mouse.py b/test/e2e/driver/mouse.py new file mode 100755 index 0000000000..9da7f7d0b3 --- /dev/null +++ b/test/e2e/driver/mouse.py @@ -0,0 +1,44 @@ +import time + +import squish + + +def move(obj: object, x: int, y: int, dx: int, dy: int, step: int, sleep: float = 0): + while True: + if x > dx: + x -= step + if x < x: + x = dx + elif x < dx: + x += step + if x > dx: + x = dx + if y > dy: + y -= step + if y < dy: + y = dy + elif y < dy: + y += step + if y > dy: + y = dy + squish.mouseMove(obj, x, y) + time.sleep(sleep) + if x == dx and y == dy: + break + + +def press_and_move( + obj, + x: int, + y: int, + dx: int, + dy: int, + mouse: int = squish.MouseButton.LeftButton, + step: int = 1, + sleep: float = 0 +): + squish.mouseMove(obj, x, y) + squish.mousePress(obj, x, y, mouse) + move(obj, x, y, dx, dy, step, sleep) + squish.mouseRelease(mouse) + time.sleep(1) diff --git a/test/e2e/driver/objects_access.py b/test/e2e/driver/objects_access.py new file mode 100644 index 0000000000..ad35c81c40 --- /dev/null +++ b/test/e2e/driver/objects_access.py @@ -0,0 +1,12 @@ +import logging + +import object + +_logger = logging.getLogger(__name__) + + +def walk_children(parent, depth: int = 1000): + for child in object.children(parent): + yield child + if depth: + yield from walk_children(child, depth - 1) diff --git a/test/e2e/driver/server.py b/test/e2e/driver/server.py new file mode 100644 index 0000000000..4cf5feeec5 --- /dev/null +++ b/test/e2e/driver/server.py @@ -0,0 +1,52 @@ +import logging +import typing +from subprocess import CalledProcessError + +import configs.testpath +from scripts.utils import local_system + +_PROCESS_NAME = '_squishserver' + +_logger = logging.getLogger(__name__) + + +class SquishServer: + + def __init__( + self, + host: str = '127.0.0.1', + port: int = 4322 + ): + self.path = configs.testpath.SQUISH_DIR / 'bin' / 'squishserver' + self.config = configs.testpath.ROOT / 'squish_server.ini' + self.host = host + self.port = port + self.pid = None + + def start(self): + cmd = [ + f'"{self.path}"', + '--configfile', str(self.config), + f'--host={self.host}', + f'--port={self.port}', + ] + self.pid = local_system.execute(cmd) + + def stop(self): + if self.pid is not None: + local_system.kill_process(self.pid) + self.pid = None + + # https://doc-snapshots.qt.io/squish/cli-squishserver.html + def configuring(self, action: str, options: typing.Union[int, str, list]): + local_system.run( + [f'"{self.path}"', '--configfile', str(self.config), '--config', action, ' '.join(options)]) + + def add_executable_aut(self, aut_id, app_dir): + self.configuring('addAUT', [aut_id, f'"{app_dir}"']) + + def add_attachable_aut(self, aut_id: str, port: int): + self.configuring('addAttachableAUT', [aut_id, f'localhost:{port}']) + + def set_aut_timeout(self, value: int = configs.timeouts.PROCESS_TIMEOUT_SEC): + self.configuring('setAUTTimeout', [str(value)]) diff --git a/test/e2e/driver/toplevel_window.py b/test/e2e/driver/toplevel_window.py new file mode 100644 index 0000000000..e39f6373f6 --- /dev/null +++ b/test/e2e/driver/toplevel_window.py @@ -0,0 +1,52 @@ +import squish +import toplevelwindow + +import configs + + +def maximize(object_name): + def _maximize() -> bool: + try: + toplevelwindow.ToplevelWindow(squish.waitForObject(object_name)).maximize() + return True + except RuntimeError: + return False + + return squish.waitFor(lambda: _maximize(), configs.timeouts.UI_LOAD_TIMEOUT_MSEC) + + +def minimize(object_name): + def _minimize() -> bool: + try: + toplevelwindow.ToplevelWindow(squish.waitForObject(object_name)).minimize() + return True + except RuntimeError: + return False + + return squish.waitFor(lambda: _minimize(), configs.timeouts.UI_LOAD_TIMEOUT_MSEC) + + +def set_focus(object_name): + def _set_focus() -> bool: + try: + toplevelwindow.ToplevelWindow(squish.waitForObject(object_name)).setFocus() + return True + except RuntimeError: + return False + + return squish.waitFor(lambda: _set_focus(), configs.timeouts.UI_LOAD_TIMEOUT_MSEC) + + +def on_top_level(object_name): + def _on_top() -> bool: + try: + toplevelwindow.ToplevelWindow(squish.waitForObject(object_name)).setForeground() + return True + except RuntimeError: + return False + + return squish.waitFor(lambda: _on_top(), configs.timeouts.UI_LOAD_TIMEOUT_MSEC) + + +def close(object_name): + squish.sendEvent("QCloseEvent", squish.waitForObject(object_name)) diff --git a/test/e2e/ext/test_files/banner.png b/test/e2e/ext/test_files/banner.png new file mode 100644 index 0000000000..5c90600a69 Binary files /dev/null and b/test/e2e/ext/test_files/banner.png differ diff --git a/test/e2e/ext/test_files/tv_signal.jpeg b/test/e2e/ext/test_files/tv_signal.jpeg new file mode 100644 index 0000000000..e824d72da5 Binary files /dev/null and b/test/e2e/ext/test_files/tv_signal.jpeg differ diff --git a/test/e2e/ext/test_files/tv_signal.png b/test/e2e/ext/test_files/tv_signal.png new file mode 100644 index 0000000000..d492cae138 Binary files /dev/null and b/test/e2e/ext/test_files/tv_signal.png differ diff --git a/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db b/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db new file mode 100644 index 0000000000..d1bb9b47bd Binary files /dev/null and b/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db differ diff --git a/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db-shm b/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db-shm new file mode 100644 index 0000000000..942f509623 Binary files /dev/null and b/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db-shm differ diff --git a/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db-wal b/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db-wal new file mode 100644 index 0000000000..235e3d1f00 Binary files /dev/null and b/test/e2e/ext/user_data/squisher/data/0xee8c7bac766464f549f39b3d8f271d905136e44c2204fa6ce1fb07739f2a32b6-v4.db-wal differ diff --git a/test/e2e/ext/user_data/squisher/data/accounts.sql b/test/e2e/ext/user_data/squisher/data/accounts.sql new file mode 100644 index 0000000000..9a47220943 Binary files /dev/null and b/test/e2e/ext/user_data/squisher/data/accounts.sql differ diff --git a/test/e2e/ext/user_data/squisher/data/accounts.sql-shm b/test/e2e/ext/user_data/squisher/data/accounts.sql-shm new file mode 100644 index 0000000000..d7c0ac3985 Binary files /dev/null and b/test/e2e/ext/user_data/squisher/data/accounts.sql-shm differ diff --git a/test/e2e/ext/user_data/squisher/data/accounts.sql-wal b/test/e2e/ext/user_data/squisher/data/accounts.sql-wal new file mode 100644 index 0000000000..e777c71051 Binary files /dev/null and b/test/e2e/ext/user_data/squisher/data/accounts.sql-wal differ diff --git a/test/e2e/ext/vp/test_communities/test_create_community/button_logo.png b/test/e2e/ext/vp/test_communities/test_create_community/button_logo.png new file mode 100644 index 0000000000..d5d18c4254 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_create_community/button_logo.png differ diff --git a/test/e2e/ext/vp/test_communities/test_create_community/logo.png b/test/e2e/ext/vp/test_communities/test_create_community/logo.png new file mode 100644 index 0000000000..a6a11f3e82 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_create_community/logo.png differ diff --git a/test/e2e/ext/vp/test_communities/test_create_community/logo_in_settings.png b/test/e2e/ext/vp/test_communities/test_create_community/logo_in_settings.png new file mode 100644 index 0000000000..52d2ab7f54 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_create_community/logo_in_settings.png differ diff --git a/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_chat.png b/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_chat.png new file mode 100644 index 0000000000..3ea7809bbf Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_chat.png differ diff --git a/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_list.png b/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_list.png new file mode 100644 index 0000000000..040e41d790 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_list.png differ diff --git a/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_toolbar.png b/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_toolbar.png new file mode 100644 index 0000000000..6d82723d57 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_create_community_channel/channel_icon_in_toolbar.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community/button_updated_logo.png b/test/e2e/ext/vp/test_communities/test_edit_community/button_updated_logo.png new file mode 100644 index 0000000000..e6e0cd3510 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community/button_updated_logo.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community/logo_in_settings_updated.png b/test/e2e/ext/vp/test_communities/test_edit_community/logo_in_settings_updated.png new file mode 100644 index 0000000000..b855a43e5e Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community/logo_in_settings_updated.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community/updated_logo.png b/test/e2e/ext/vp/test_communities/test_edit_community/updated_logo.png new file mode 100644 index 0000000000..99c8a90fe9 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community/updated_logo.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_chat.png b/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_chat.png new file mode 100644 index 0000000000..3ea7809bbf Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_chat.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_list.png b/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_list.png new file mode 100644 index 0000000000..040e41d790 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_list.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_toolbar.png b/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_toolbar.png new file mode 100644 index 0000000000..6d82723d57 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community_channel/channel_icon_in_toolbar.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_chat.png b/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_chat.png new file mode 100644 index 0000000000..97aecf827f Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_chat.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_list.png b/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_list.png new file mode 100644 index 0000000000..07bc28702b Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_list.png differ diff --git a/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_toolbar.png b/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_toolbar.png new file mode 100644 index 0000000000..5994cfee14 Binary files /dev/null and b/test/e2e/ext/vp/test_communities/test_edit_community_channel/general_channel_icon_in_toolbar.png differ diff --git a/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/jpeg_onboarding.png b/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/jpeg_onboarding.png new file mode 100644 index 0000000000..156de157d1 Binary files /dev/null and b/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/jpeg_onboarding.png differ diff --git a/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/jpeg_profile.png b/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/jpeg_profile.png new file mode 100644 index 0000000000..6b8b411ffc Binary files /dev/null and b/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/jpeg_profile.png differ diff --git a/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/png_onboarding.png b/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/png_onboarding.png new file mode 100644 index 0000000000..6f75463cdc Binary files /dev/null and b/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/png_onboarding.png differ diff --git a/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/png_profile.png b/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/png_profile.png new file mode 100644 index 0000000000..81449d8779 Binary files /dev/null and b/test/e2e/ext/vp/test_onboarding/test_generate_new_keys/png_profile.png differ diff --git a/test/e2e/gui/__init__.py b/test/e2e/gui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/__init__.py b/test/e2e/gui/components/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/authenticate_popup.py b/test/e2e/gui/components/authenticate_popup.py new file mode 100644 index 0000000000..cc0f7fbb93 --- /dev/null +++ b/test/e2e/gui/components/authenticate_popup.py @@ -0,0 +1,25 @@ +import allure + +from gui.elements.qt.button import Button +from gui.elements.qt.text_edit import TextEdit +from gui.elements.qt.object import QObject + + +class AuthenticatePopup(QObject): + + def __init__(self): + super(AuthenticatePopup, self).__init__('contextMenu_PopupItem') + self._password_text_edit = TextEdit('sharedPopup_Password_Input') + self._primary_button = Button('sharedPopup_Primary_Button') + + @allure.step('Authenticate action with password') + def authenticate(self, password: str, attempt: int = 2): + self._password_text_edit.text = password + self._primary_button.click() + try: + self._primary_button.wait_until_hidden() + except AssertionError as err: + if attempt: + self.authenticate(password, attempt - 1) + else: + raise err diff --git a/test/e2e/gui/components/base_popup.py b/test/e2e/gui/components/base_popup.py new file mode 100644 index 0000000000..e086ee300c --- /dev/null +++ b/test/e2e/gui/components/base_popup.py @@ -0,0 +1,15 @@ +import allure + +import driver +from gui.elements.qt.object import QObject + + +class BasePopup(QObject): + + def __init__(self): + super(BasePopup, self).__init__('statusDesktop_mainWindow_overlay') + + @allure.step('Close') + def close(self): + driver.nativeType('') + self.wait_until_hidden() diff --git a/test/e2e/gui/components/color_select_popup.py b/test/e2e/gui/components/color_select_popup.py new file mode 100644 index 0000000000..6c7f7cbfa0 --- /dev/null +++ b/test/e2e/gui/components/color_select_popup.py @@ -0,0 +1,29 @@ +import allure + +import configs +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.text_edit import TextEdit + + +class ColorSelectPopup(BasePopup): + + def __init__(self): + super().__init__() + self._hex_color_text_edit = TextEdit('communitySettings_ColorPanel_HexColor_Input') + self._save_button = Button('communitySettings_SaveColor_Button') + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + self._hex_color_text_edit.wait_until_appears() + return self + + @allure.step('Wait until hidden {0}') + def wait_until_hidden(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + self._hex_color_text_edit.wait_until_hidden() + + @allure.step('Select color {1}') + def select_color(self, value: str): + self._hex_color_text_edit.text = value + self._save_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/community/__init__.py b/test/e2e/gui/components/community/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/community/community_channel_popups.py b/test/e2e/gui/components/community/community_channel_popups.py new file mode 100644 index 0000000000..729dff5e50 --- /dev/null +++ b/test/e2e/gui/components/community/community_channel_popups.py @@ -0,0 +1,44 @@ +import configs +from gui.components.base_popup import BasePopup +from gui.components.emoji_popup import EmojiPopup +from gui.elements.qt.button import Button +from gui.elements.qt.text_edit import TextEdit + + +class ChannelPopup(BasePopup): + + def __init__(self): + super(ChannelPopup, self).__init__() + self._name_text_edit = TextEdit('createOrEditCommunityChannelNameInput_TextEdit') + self._description_text_edit = TextEdit('createOrEditCommunityChannelDescriptionInput_TextEdit') + self._save_create_button = Button('createOrEditCommunityChannelBtn_StatusButton') + self._emoji_button = Button('createOrEditCommunityChannel_EmojiButton') + + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + self._name_text_edit.wait_until_appears(timeout_msec) + return self + + +class NewChannelPopup(ChannelPopup): + + def create(self, name: str, description: str, emoji: str = None): + self._name_text_edit.text = name + self._description_text_edit.text = description + if emoji is not None: + self._emoji_button.click() + EmojiPopup().wait_until_appears().select(emoji) + self._save_create_button.click() + self.wait_until_hidden() + + +class EditChannelPopup(ChannelPopup): + + def edit(self, name: str, description: str = None, emoji: str = None): + self._name_text_edit.text = name + if description is not None: + self._description_text_edit.text = description + if emoji is not None: + self._emoji_button.click() + EmojiPopup().wait_until_appears().select(emoji) + self._save_create_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/community/create_community_popups.py b/test/e2e/gui/components/community/create_community_popups.py new file mode 100644 index 0000000000..cd8610ec42 --- /dev/null +++ b/test/e2e/gui/components/community/create_community_popups.py @@ -0,0 +1,147 @@ +import typing + +import allure + +from gui.components.base_popup import BasePopup +from gui.components.color_select_popup import ColorSelectPopup +from gui.components.community.tags_select_popup import TagsSelectPopup +from gui.components.os.open_file_dialogs import OpenFileDialog +from gui.components.picture_edit_popup import PictureEditPopup +from gui.elements.qt.button import Button +from gui.elements.qt.check_box import CheckBox +from gui.elements.qt.scroll import Scroll +from gui.elements.qt.text_edit import TextEdit +from gui.screens.community import CommunityScreen + + +class CreateCommunitiesBanner(BasePopup): + + def __init__(self): + super().__init__() + self._crete_community_button = Button('create_new_StatusButton') + + def open_create_community_popup(self) -> 'CreateCommunityPopup': + self._crete_community_button.click() + return CreateCommunityPopup().wait_until_appears() + + +class CreateCommunityPopup(BasePopup): + + def __init__(self): + super().__init__() + self._scroll = Scroll('o_Flickable') + self._name_text_edit = TextEdit('createCommunityNameInput_TextEdit') + self._description_text_edit = TextEdit('createCommunityDescriptionInput_TextEdit') + self._add_logo_button = Button('addButton_StatusRoundButton2') + self._add_banner_button = Button('addButton_StatusRoundButton') + self._select_color_button = Button('StatusPickerButton') + self._choose_tag_button = Button('choose_tags_StatusPickerButton') + self._archive_support_checkbox = CheckBox('archiveSupportToggle_StatusCheckBox') + self._request_to_join_checkbox = CheckBox('requestToJoinToggle_StatusCheckBox') + self._pin_messages_checkbox = CheckBox('pinMessagesToggle_StatusCheckBox') + self._next_button = Button('createCommunityNextBtn_StatusButton') + self._intro_text_edit = TextEdit('createCommunityIntroMessageInput_TextEdit') + self._outro_text_edit = TextEdit('createCommunityOutroMessageInput_TextEdit') + self._create_community_button = Button('createCommunityFinalBtn_StatusButton') + + @property + @allure.step('Get community name') + def name(self) -> str: + return self._name_text_edit.text + + @name.setter + @allure.step('Set community name') + def name(self, value: str): + self._name_text_edit.text = value + + @property + @allure.step('Get community description') + def description(self) -> str: + return self._description_text_edit.text + + @description.setter + @allure.step('Set community name') + def description(self, value: str): + self._description_text_edit.text = value + + @property + @allure.step('Get community logo') + def logo(self): + return NotImplementedError + + @logo.setter + @allure.step('Set community logo') + def logo(self, kwargs: dict): + self._add_logo_button.click() + OpenFileDialog().wait_until_appears().open_file(kwargs['fp']) + PictureEditPopup().wait_until_appears().make_picture(kwargs.get('zoom', None), kwargs.get('shift', None)) + + @property + @allure.step('Get community banner') + def banner(self): + raise NotImplementedError + + @banner.setter + @allure.step('Set community banner') + def banner(self, kwargs: dict): + self._add_banner_button.click() + OpenFileDialog().wait_until_appears().open_file(kwargs['fp']) + PictureEditPopup().wait_until_appears().make_picture(kwargs.get('zoom', None), kwargs.get('shift', None)) + + @property + @allure.step('Get community color') + def color(self): + raise NotImplementedError + + @color.setter + @allure.step('Set community color') + def color(self, value: str): + self._scroll.vertical_scroll_to(self._select_color_button) + self._select_color_button.click() + ColorSelectPopup().wait_until_appears().select_color(value) + + @property + @allure.step('Get community tags') + def tags(self): + raise NotImplementedError + + @tags.setter + @allure.step('Set community tags') + def tags(self, values: typing.List[str]): + self._scroll.vertical_scroll_to(self._choose_tag_button) + self._choose_tag_button.click() + TagsSelectPopup().wait_until_appears().select_tags(values) + + @property + @allure.step('Get community intro') + def intro(self) -> str: + return self._intro_text_edit.text + + @intro.setter + @allure.step('Set community intro') + def intro(self, value: str): + self._intro_text_edit.text = value + + @property + @allure.step('Get community outro') + def outro(self) -> str: + return self._outro_text_edit.text + + @outro.setter + @allure.step('Set community outro') + def outro(self, value: str): + self._outro_text_edit.text = value + + @allure.step('Open intro/outro form') + def open_next_form(self): + self._next_button.click() + + @allure.step('Create community') + def create(self, kwargs): + for key in list(kwargs): + if key in ['intro', 'outro'] and self._next_button.is_visible: + self._next_button.click() + setattr(self, key, kwargs.get(key)) + self._create_community_button.click() + self.wait_until_hidden() + return CommunityScreen().wait_until_appears() diff --git a/test/e2e/gui/components/community/tags_select_popup.py b/test/e2e/gui/components/community/tags_select_popup.py new file mode 100644 index 0000000000..bad70ed124 --- /dev/null +++ b/test/e2e/gui/components/community/tags_select_popup.py @@ -0,0 +1,48 @@ +import time +import typing + +import allure + +import configs +import driver +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject + + +class TagsSelectPopup(BasePopup): + + def __init__(self): + super().__init__() + self._tag_template = QObject('o_StatusCommunityTag') + self._save_button = Button('confirm_Community_Tags_StatusButton') + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + self._tag_template.wait_until_appears() + return self + + @allure.step('Select tags') + def select_tags(self, values: typing.List[str]): + tags = [] + checked = [] + unchecked = [] + for obj in driver.findAllObjects(self._tag_template.real_name): + name = str(obj.name) + tags.append(name) + if name in values: + if not obj.removable: + driver.mouseClick(obj) + checked.append(name) + time.sleep(1) + values.remove(name) + else: + # Selected and should be unselected + if obj.removable: + driver.mouseClick(obj) + time.sleep(1) + unchecked.append(name) + if values: + raise LookupError( + f'Tags: {values} not found in {tags}. Checked tags: {checked}, Unchecked tags: {unchecked}') + self._save_button.click() diff --git a/test/e2e/gui/components/context_menu.py b/test/e2e/gui/components/context_menu.py new file mode 100644 index 0000000000..83d171e53c --- /dev/null +++ b/test/e2e/gui/components/context_menu.py @@ -0,0 +1,15 @@ +import allure + +from gui.elements.qt.object import QObject + + +class ContextMenu(QObject): + + def __init__(self): + super(ContextMenu, self).__init__('contextMenu_PopupItem') + self._menu_item = QObject('contextMenuItem') + + @allure.step('Select in context menu') + def select(self, value: str): + self._menu_item.real_name['text'] = value + self._menu_item.click() \ No newline at end of file diff --git a/test/e2e/gui/components/delete_popup.py b/test/e2e/gui/components/delete_popup.py new file mode 100644 index 0000000000..cb7040efca --- /dev/null +++ b/test/e2e/gui/components/delete_popup.py @@ -0,0 +1,16 @@ +import allure + +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button + + +class DeletePopup(BasePopup): + + def __init__(self): + super().__init__() + self._delete_button = Button('delete_StatusButton') + + @allure.step("Delete entity") + def delete(self): + self._delete_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/emoji_popup.py b/test/e2e/gui/components/emoji_popup.py new file mode 100644 index 0000000000..d11c125f4e --- /dev/null +++ b/test/e2e/gui/components/emoji_popup.py @@ -0,0 +1,25 @@ +import allure + +import configs +from .base_popup import BasePopup +from ..elements.qt.object import QObject +from ..elements.qt.text_edit import TextEdit + + +class EmojiPopup(BasePopup): + def __init__(self): + super(EmojiPopup, self).__init__() + self._search_text_edit = TextEdit('mainWallet_AddEditAccountPopup_AccountEmojiSearchBox') + self._emoji_item = QObject('mainWallet_AddEditAccountPopup_AccountEmoji') + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + self._search_text_edit.wait_until_appears(timeout_msec) + return self + + @allure.step('Select emoji') + def select(self, name: str): + self._search_text_edit.text = name + self._emoji_item.real_name['objectName'] = 'statusEmoji_' + name + self._emoji_item.click() + self._search_text_edit.wait_until_hidden() \ No newline at end of file diff --git a/test/e2e/gui/components/onboarding/__init__.py b/test/e2e/gui/components/onboarding/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/onboarding/before_started_popup.py b/test/e2e/gui/components/onboarding/before_started_popup.py new file mode 100644 index 0000000000..c9177f8291 --- /dev/null +++ b/test/e2e/gui/components/onboarding/before_started_popup.py @@ -0,0 +1,26 @@ +import allure + +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.check_box import CheckBox + + +class BeforeStartedPopUp(BasePopup): + + def __init__(self): + super(BeforeStartedPopUp, self).__init__() + self._acknowledge_checkbox = CheckBox('acknowledge_checkbox') + self._terms_of_use_checkBox = CheckBox('termsOfUseCheckBox_StatusCheckBox') + self._get_started_button = Button('getStartedStatusButton_StatusButton') + + @property + @allure.step('Get visible attribute') + def is_visible(self) -> bool: + return self._get_started_button.is_visible + + @allure.step('Allow all and get started') + def get_started(self): + self._acknowledge_checkbox.set(True) + self._terms_of_use_checkBox.set(True, x=10) + self._get_started_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/onboarding/welcome_status_popup.py b/test/e2e/gui/components/onboarding/welcome_status_popup.py new file mode 100644 index 0000000000..ccee23556f --- /dev/null +++ b/test/e2e/gui/components/onboarding/welcome_status_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.check_box import CheckBox + + +class WelcomeStatusPopup(BasePopup): + + def __init__(self): + self._agree_to_use_checkbox = CheckBox('agreeToUse_StatusCheckBox') + self._ready_to_use_checkbox = CheckBox('readyToUse_StatusCheckBox') + self._ready_to_use_button = Button('i_m_ready_to_use_Status_Desktop_Beta_StatusButton') + super(WelcomeStatusPopup, self).__init__() + + @allure.step('Confirm all') + def confirm(self): + self._agree_to_use_checkbox.set(True) + self._ready_to_use_checkbox.set(True) + self._ready_to_use_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/os/__init__.py b/test/e2e/gui/components/os/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/os/lin/__init__.py b/test/e2e/gui/components/os/lin/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/os/lin/open_file_dialog.py b/test/e2e/gui/components/os/lin/open_file_dialog.py new file mode 100644 index 0000000000..06b56466b5 --- /dev/null +++ b/test/e2e/gui/components/os/lin/open_file_dialog.py @@ -0,0 +1,22 @@ +import allure + +import constants.commands +import driver +from gui.elements.qt.button import Button +from gui.elements.qt.text_edit import TextEdit +from gui.elements.qt.window import Window +from scripts.utils.system_path import SystemPath + + +class OpenFileDialog(Window): + + def __init__(self): + super(OpenFileDialog, self).__init__('please_choose_an_image_QQuickWindow') + self._path_text_edit = TextEdit('titleBar_textInput_TextInputWithHandles') + self._open_button = Button('please_choose_an_image_Open_Button') + + @allure.step('Open file') + def open_file(self, fp: SystemPath): + self._path_text_edit.text = str(fp) + driver.type(self._path_text_edit.object, f'<{constants.commands.RETURN}>') + self.wait_until_hidden() diff --git a/test/e2e/gui/components/os/mac/__init__.py b/test/e2e/gui/components/os/mac/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/os/mac/open_file_dialogs.py b/test/e2e/gui/components/os/mac/open_file_dialogs.py new file mode 100644 index 0000000000..8d0b545a3c --- /dev/null +++ b/test/e2e/gui/components/os/mac/open_file_dialogs.py @@ -0,0 +1,59 @@ +import logging +import time + +import allure + +import constants +import driver +from gui.elements.os.mac.button import Button +from gui.elements.os.mac.object import NativeObject +from gui.elements.os.mac.text_edit import TextEdit +from scripts.utils.system_path import SystemPath + +_logger = logging.getLogger(__name__) + + +class OpenFileDialog(NativeObject): + + def __init__(self): + super(OpenFileDialog, self).__init__('openFileDialog') + self._open_button = Button('openButton') + + def _open_go_to_dialog(self, attempt: int = 2): + # Set focus + driver.nativeMouseClick(int(self.bounds.x + 10), int(self.bounds.y + 10), driver.Qt.LeftButton) + time.sleep(1) + driver.nativeType(f'<{constants.commands.OPEN_GOTO}>') + try: + return _GoToDialog().wait_until_appears() + except LookupError as err: + _logger.debug(err) + if attempt: + self._open_go_to_dialog(attempt - 1) + else: + raise err + + @allure.step('Open file') + def open_file(self, fp: SystemPath): + # Set focus + driver.nativeMouseClick(int(self.bounds.x + 10), int(self.bounds.y + 10), driver.Qt.LeftButton) + time.sleep(1) + driver.nativeType(f'<{constants.commands.OPEN_GOTO}>') + self._open_go_to_dialog().select_file(fp) + self._open_button.click() + self.wait_until_hidden() + + +class _GoToDialog(NativeObject): + + def __init__(self): + self.go_to_text_edit = TextEdit('pathTextField') + super(_GoToDialog, self).__init__('goToDialog') + + @allure.step('Select file') + def select_file(self, fp: SystemPath): + self.go_to_text_edit.text = str(fp) + driver.nativeMouseClick(int(self.bounds.x + 10), int(self.bounds.y + 10), driver.Qt.LeftButton) + time.sleep(1) + driver.nativeType(f'<{constants.commands.RETURN}>') + self.wait_until_hidden() diff --git a/test/e2e/gui/components/os/open_file_dialogs.py b/test/e2e/gui/components/os/open_file_dialogs.py new file mode 100644 index 0000000000..5a40ba9463 --- /dev/null +++ b/test/e2e/gui/components/os/open_file_dialogs.py @@ -0,0 +1,12 @@ +import configs + +if configs.system.IS_WIN: + from .win.open_file_dialogs import OpenFileDialog as BaseOpenFileDialog +elif configs.system.IS_MAC: + from .mac.open_file_dialogs import OpenFileDialog as BaseOpenFileDialog +else: + from .lin.open_file_dialog import OpenFileDialog as BaseOpenFileDialog + + +class OpenFileDialog(BaseOpenFileDialog): + pass diff --git a/test/e2e/gui/components/os/win/__init__.py b/test/e2e/gui/components/os/win/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/os/win/open_file_dialogs.py b/test/e2e/gui/components/os/win/open_file_dialogs.py new file mode 100644 index 0000000000..722ea0073c --- /dev/null +++ b/test/e2e/gui/components/os/win/open_file_dialogs.py @@ -0,0 +1,24 @@ +import logging + +import allure + +from gui.elements.os.win.button import Button +from gui.elements.os.win.object import NativeObject +from gui.elements.os.win.text_edit import TextEdit +from scripts.utils.system_path import SystemPath + +_logger = logging.getLogger(__name__) + + +class OpenFileDialog(NativeObject): + + def __init__(self): + super().__init__('file_Dialog') + self._file_path_text_edit = TextEdit('choose_file_Edit') + self._select_button = Button('choose_Open_Button') + + @allure.step('Open file') + def open_file(self, fp: SystemPath): + self._file_path_text_edit.text = str(fp) + self._select_button.click() + self.wait_until_hidden() diff --git a/test/e2e/gui/components/picture_edit_popup.py b/test/e2e/gui/components/picture_edit_popup.py new file mode 100644 index 0000000000..1985e58919 --- /dev/null +++ b/test/e2e/gui/components/picture_edit_popup.py @@ -0,0 +1,51 @@ +import time +from collections import namedtuple + +import allure + +import driver.mouse +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.slider import Slider + +shift_image = namedtuple('Shift', ['left', 'right', 'top', 'bottom']) + + +class PictureEditPopup(BasePopup): + + def __init__(self): + super(PictureEditPopup, self).__init__() + self._zoom_slider = Slider('o_StatusSlider') + self._view = QObject('cropSpaceItem_Item') + self._make_picture_button = Button('make_picture_StatusButton') + self._slider_handler = QObject('o_DropShadow') + + @allure.step('Make picture') + def make_picture( + self, + zoom: int = None, + shift: shift_image = None + ): + if zoom is not None: + self._zoom_slider.value = zoom + # The slider changed value, but image updates only after click on slider + self._slider_handler.click() + time.sleep(1) + if shift is not None: + if shift.left: + driver.mouse.press_and_move(self._view.object, 1, 1, shift.left, 1) + time.sleep(1) + if shift.right: + driver.mouse.press_and_move( + self._view.object, self._view.width, 1, self._view.width - shift.right, 1) + time.sleep(1) + if shift.top: + driver.mouse.press_and_move(self._view.object, 1, 1, 1, shift.top, step=1) + time.sleep(1) + if shift.bottom: + driver.mouse.press_and_move( + self._view.object, 1, self._view.height, 1, self._view.height - shift.bottom, step=1) + time.sleep(1) + + self._make_picture_button.click() diff --git a/test/e2e/gui/components/profile_popup.py b/test/e2e/gui/components/profile_popup.py new file mode 100644 index 0000000000..1abf3b48fb --- /dev/null +++ b/test/e2e/gui/components/profile_popup.py @@ -0,0 +1,62 @@ +import allure + +import constants +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_label import TextLabel +from scripts.tools.image import Image + + +class ProfilePopup(BasePopup): + + def __init__(self): + super(ProfilePopup, self).__init__() + self._profile_image = QObject('ProfileHeader_userImage') + self._user_name_label = TextLabel('ProfilePopup_displayName') + self._edit_profile_button = Button('ProfilePopup_editButton') + self._chat_key_text_label = TextLabel('https_status_app_StatusBaseText') + self._emoji_hash = QObject('profileDialog_userEmojiHash_EmojiHash') + + @property + @allure.step('Get profile image') + def profile_image(self): + return self._profile_image.image + + @property + @allure.step('Get image without identicon_ring') + def cropped_profile_image(self): + # Profile image without identicon_ring + self._profile_image.image.update_view() + self._profile_image.image.crop( + driver.UiTypes.ScreenRectangle( + 15, 15, self._profile_image.image.width-30, self._profile_image.image.height-30 + )) + return self._profile_image.image + + @property + @allure.step('Get user name') + def user_name(self) -> str: + return self._user_name_label.text + + @property + @allure.step('Get chat key') + def chat_key(self) -> str: + chat_key = self._chat_key_text_label.text.split('https://status.app/u/')[1].strip() + if '#' in chat_key: + chat_key = chat_key.split('#')[1] + return chat_key + + @property + @allure.step('Get emoji hash image') + def emoji_hash(self) -> Image: + return self._emoji_hash.image + + @allure.step('Verify: user image contains text') + def is_user_image_contains(self, text: str): + # To remove all artifacts, the image cropped. + crop = driver.UiTypes.ScreenRectangle( + 15, 15, self._profile_image.image.width - 30, self._profile_image.image.height - 30 + ) + return self.profile_image.has_text(text, constants.tesseract.text_on_profile_image, crop=crop) diff --git a/test/e2e/gui/components/signing_phrase_popup.py b/test/e2e/gui/components/signing_phrase_popup.py new file mode 100644 index 0000000000..c556043e77 --- /dev/null +++ b/test/e2e/gui/components/signing_phrase_popup.py @@ -0,0 +1,16 @@ +import allure + +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button + + +class SigningPhrasePopup(BasePopup): + + def __init__(self): + super(SigningPhrasePopup, self).__init__() + self._ok_got_it_button = Button('signPhrase_Ok_Button') + + @allure.step('Confirm signing phrase in popup') + def confirm_phrase(self): + self._ok_got_it_button.click() + SigningPhrasePopup().wait_until_hidden() \ No newline at end of file diff --git a/test/e2e/gui/components/splash_screen.py b/test/e2e/gui/components/splash_screen.py new file mode 100644 index 0000000000..b9e7a7bde0 --- /dev/null +++ b/test/e2e/gui/components/splash_screen.py @@ -0,0 +1,20 @@ +import allure + +import configs + +from gui.elements.qt.object import QObject + + +class SplashScreen(QObject): + + def __init__(self): + super(SplashScreen, self).__init__('splashScreen') + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + assert self.wait_for(lambda: self.exists, timeout_msec), f'Object {self} is not visible' + return self + + @allure.step('Wait until hidden {0}') + def wait_until_hidden(self, timeout_msec: int = configs.timeouts.APP_LOAD_TIMEOUT_MSEC): + super().wait_until_hidden(timeout_msec) diff --git a/test/e2e/gui/components/user_canvas.py b/test/e2e/gui/components/user_canvas.py new file mode 100644 index 0000000000..11b6648dc5 --- /dev/null +++ b/test/e2e/gui/components/user_canvas.py @@ -0,0 +1,67 @@ +import time + +import allure + +import configs +import constants +import driver +from gui.components.profile_popup import ProfilePopup +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject +from gui.elements.qt.text_label import TextLabel + + +class UserCanvas(QObject): + + def __init__(self): + super(UserCanvas, self).__init__('o_StatusListView') + self._always_active_button = Button('userContextmenu_AlwaysActiveButton') + self._inactive_button = Button('userContextmenu_InActiveButton') + self._automatic_button = Button('userContextmenu_AutomaticButton') + self._view_my_profile_button = Button('userContextMenu_ViewMyProfileAction') + self._user_name_text_label = TextLabel('userLabel_StyledText') + self._profile_image = QObject('o_StatusIdenticonRing') + + @property + @allure.step('Get profile image') + def profile_image(self): + return self._profile_image.image + + @property + @allure.step('Get user name') + def user_name(self) -> str: + return self._user_name_text_label.text + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + super(UserCanvas, self).wait_until_appears(timeout_msec) + time.sleep(1) + return self + + @allure.step('Set user state online') + def set_user_state_online(self): + self._always_active_button.click() + self.wait_until_hidden() + + @allure.step('Set user state offline') + def set_user_state_offline(self): + self._inactive_button.click() + self.wait_until_hidden() + + @allure.step('Set user automatic state') + def set_user_automatic_state(self): + self._automatic_button.click() + self.wait_until_hidden() + + @allure.step('Open Profile popup') + def open_profile_popup(self) -> ProfilePopup: + self._view_my_profile_button.click() + return ProfilePopup().wait_until_appears() + + @allure.step('Verify: User image contains text') + def is_user_image_contains(self, text: str): + # To remove all artifacts, the image cropped. + crop = driver.UiTypes.ScreenRectangle( + 5, 5, self._profile_image.image.width-10, self._profile_image.image.height-10 + ) + return self._profile_image.image.has_text(text, constants.tesseract.text_on_profile_image, crop=crop) diff --git a/test/e2e/gui/components/wallet/__init__.py b/test/e2e/gui/components/wallet/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/components/wallet/add_saved_address_popup.py b/test/e2e/gui/components/wallet/add_saved_address_popup.py new file mode 100644 index 0000000000..1e7ceec7a3 --- /dev/null +++ b/test/e2e/gui/components/wallet/add_saved_address_popup.py @@ -0,0 +1,103 @@ +import allure + +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.check_box import CheckBox +from gui.elements.qt.object import QObject +from gui.elements.qt.text_edit import TextEdit +from gui.elements.qt.text_label import TextLabel + + +class AddSavedAddressPopup(BasePopup): + def __init__(self): + super(AddSavedAddressPopup, self).__init__() + self._name_text_edit = TextEdit('mainWallet_Saved_Addreses_Popup_Name_Input') + self._save_add_address_button = Button('mainWallet_Saved_Addreses_Popup_Address_Add_Button') + self._add_networks_selector = QObject('mainWallet_Saved_Addreses_Popup_Add_Network_Selector_Tag') + self._add_networks_button = Button('mainWallet_Saved_Addreses_Popup_Add_Network_Button') + self._ethereum_mainnet_checkbox = CheckBox( + 'mainWallet_Saved_Addresses_Popup_Add_Network_Selector_Mainnet_checkbox') + self._optimism_mainnet_checkbox = CheckBox( + 'mainWallet_Saved_Addresses_Popup_Add_Network_Selector_Optimism_checkbox') + self._arbitrum_mainnet_checkbox = CheckBox( + 'mainWallet_Saved_Addresses_Popup_Add_Network_Selector_Arbitrum_checkbox') + self._ethereum_mainnet_network_tag = QObject( + 'mainWallet_Saved_Addresses_Popup_Network_Selector_Mainnet_network_tag') + self._optimism_mainnet_network_tag = QObject( + 'mainWallet_Saved_Addresses_Popup_Network_Selector_Optimism_network_tag') + self._arbitrum_mainnet_network_tag = QObject( + 'mainWallet_Saved_Addresses_Popup_Network_Selector_Arbitrum_network_tag') + + @allure.step('Set ethereum mainnet network checkbox') + def set_ethereum_mainnet_network(self, value: bool): + self._ethereum_mainnet_checkbox.set(value) + return self + + @allure.step('Set optimism mainnet network checkbox') + def set_optimism_mainnet_network(self, value: bool): + self._optimism_mainnet_checkbox.set(value) + return self + + @allure.step('Set arbitrum mainnet network checkbox') + def set_arbitrum_mainnet_network(self, value: bool): + self._arbitrum_mainnet_checkbox.set(value) + return self + + @allure.step('Verify that network selector enabled') + def verify_network_selector_enabled(self): + assert self._add_networks_selector.is_visible, f'Network selector is not enabled' + + @allure.step('Verify that etherium mainnet network present') + def verify_ethereum_mainnet_network_tag_present(self): + assert self._ethereum_mainnet_network_tag.is_visible, f'Ethereum Mainnet network tag is not present' + + @allure.step('Verify that etherium mainnet network present') + def verify_otimism_mainnet_network_tag_present(self): + assert self._optimism_mainnet_network_tag.is_visible, f'Optimism Mainnet network tag is not present' + + @allure.step('Verify that arbitrum mainnet network present') + def verify_arbitrum_mainnet_network_tag_present(self): + assert self._arbitrum_mainnet_network_tag.is_visible, f'Arbitrum Mainnet network tag is not present' + + +class AddressPopup(AddSavedAddressPopup): + def __init__(self): + super(AddressPopup, self).__init__() + self._address_text_edit = TextEdit('mainWallet_Saved_Addreses_Popup_Address_Input_Edit') + + @allure.step('Add saved address') + def add_saved_address(self, name: str, address: str): + self._name_text_edit.text = name + self._address_text_edit.clear(verify=False) + self._address_text_edit.type_text(address) + if address.startswith("0x"): + self.verify_network_selector_enabled() + self._add_networks_selector.click(1, 1) + self.set_ethereum_mainnet_network(True) + self.set_optimism_mainnet_network(True) + self.set_arbitrum_mainnet_network(True) + self._save_add_address_button.click() # click it twice to close the network selector pop up + self.verify_ethereum_mainnet_network_tag_present() + self.verify_otimism_mainnet_network_tag_present() + self.verify_arbitrum_mainnet_network_tag_present(), + self._save_add_address_button.click() + self.wait_until_hidden() + + +class EditSavedAddressPopup(AddSavedAddressPopup): + + def __init__(self): + super(EditSavedAddressPopup, self).__init__() + self._address_text_label = TextLabel('mainWallet_Saved_Addreses_Popup_Address_Input_Edit') + + @allure.step('Edit saved address') + def edit_saved_address(self, new_name: str, address: str): + self._name_text_edit.text = new_name + if address.startswith("0x"): + self._add_networks_button.click() + self.set_ethereum_mainnet_network(False) + self.set_optimism_mainnet_network(False) + self.set_arbitrum_mainnet_network(False) + self._save_add_address_button.click() + self._save_add_address_button.click() + self.wait_until_hidden() \ No newline at end of file diff --git a/test/e2e/gui/components/wallet/confirmation_popup.py b/test/e2e/gui/components/wallet/confirmation_popup.py new file mode 100644 index 0000000000..e5aaecfd7c --- /dev/null +++ b/test/e2e/gui/components/wallet/confirmation_popup.py @@ -0,0 +1,16 @@ +import allure + +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject + + +class ConfirmationPopup(QObject): + + def __init__(self): + super(ConfirmationPopup, self).__init__('contextMenu_PopupItem') + self._confirm_button = Button('confirmButton') + + @allure.step('Confirm action') + def confirm(self): + self._confirm_button.click() + self.wait_until_hidden() \ No newline at end of file diff --git a/test/e2e/gui/components/wallet/remove_wallet_account_popup.py b/test/e2e/gui/components/wallet/remove_wallet_account_popup.py new file mode 100644 index 0000000000..ffb6e5fce3 --- /dev/null +++ b/test/e2e/gui/components/wallet/remove_wallet_account_popup.py @@ -0,0 +1,30 @@ +import allure + +import configs +from gui.components.base_popup import BasePopup +from gui.elements.qt.button import Button +from gui.elements.qt.check_box import CheckBox + + +class RemoveWalletAccountPopup(BasePopup): + + def __init__(self): + super(RemoveWalletAccountPopup, self).__init__() + self._confirm_button = Button('mainWallet_Remove_Account_Popup_ConfirmButton') + self._cancel_button = Button('mainWallet_Remove_Account_Popup_CancelButton') + self._have_pen_paper_checkbox = CheckBox('mainWallet_Remove_Account_Popup_HavePenPaperCheckBox') + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + self._cancel_button.wait_until_appears(timeout_msec) + return self + + @allure.step('Confirm removing account') + def confirm(self): + self._confirm_button.click() + self._confirm_button.wait_until_hidden() + + @allure.step('Agree and confirm removing account') + def agree_and_confirm(self): + self._have_pen_paper_checkbox.wait_until_appears().set(True) + self.confirm() diff --git a/test/e2e/gui/components/wallet/wallet_account_popups.py b/test/e2e/gui/components/wallet/wallet_account_popups.py new file mode 100644 index 0000000000..f6c8b8db02 --- /dev/null +++ b/test/e2e/gui/components/wallet/wallet_account_popups.py @@ -0,0 +1,157 @@ +import allure + +import configs +import constants.wallet +import driver +from gui.components.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 +from gui.elements.qt.check_box import CheckBox +from gui.elements.qt.text_edit import TextEdit +from gui.elements.qt.scroll import Scroll +from gui.elements.qt.object import QObject + +GENERATED_PAGES_LIMIT = 20 + +class AccountPopup(BasePopup): + def __init__(self): + super(AccountPopup, self).__init__() + self._scroll = Scroll('scrollView_StatusScrollView') + self._name_text_edit = TextEdit('mainWallet_AddEditAccountPopup_AccountName') + self._emoji_button = Button('mainWallet_AddEditAccountPopup_AccountEmojiPopupButton') + self._color_radiobutton = QObject('color_StatusColorRadioButton') + # origin + self._origin_combobox = QObject('mainWallet_AddEditAccountPopup_SelectedOrigin') + self._watch_only_account_origin_item = QObject("mainWallet_AddEditAccountPopup_OriginOptionWatchOnlyAcc") + self._new_master_key_origin_item = QObject('mainWallet_AddEditAccountPopup_OriginOptionNewMasterKey') + self._existing_origin_item = QObject('addAccountPopup_OriginOption_StatusListItem') + # derivation + self._address_text_edit = TextEdit('mainWallet_AddEditAccountPopup_AccountWatchOnlyAddress') + self._add_account_button = Button('mainWallet_AddEditAccountPopup_PrimaryButton') + self._edit_derivation_path_button = Button('mainWallet_AddEditAccountPopup_EditDerivationPathButton') + self._derivation_path_combobox_button = Button('mainWallet_AddEditAccountPopup_PreDefinedDerivationPathsButton') + self._derivation_path_list_item = QObject('mainWallet_AddEditAccountPopup_derivationPath') + self._reset_derivation_path_button = Button('mainWallet_AddEditAccountPopup_ResetDerivationPathButton') + self._derivation_path_text_edit = TextEdit('mainWallet_AddEditAccountPopup_DerivationPathInput') + self._address_combobox_button = Button('mainWallet_AddEditAccountPopup_GeneratedAddressComponent') + self._non_eth_checkbox = CheckBox('mainWallet_AddEditAccountPopup_NonEthDerivationPathCheckBox') + + @allure.step('Set name for account') + def set_name(self, value: str): + self._name_text_edit.text = value + return self + + @allure.step('Set color for account') + def set_color(self, value: str): + if 'radioButtonColor' in self._color_radiobutton.real_name.keys(): + del self._color_radiobutton.real_name['radioButtonColor'] + colors = [str(item.radioButtonColor) for item in driver.findAllObjects(self._color_radiobutton.real_name)] + assert value in colors, f'Color {value} not found in {colors}' + self._color_radiobutton.real_name['radioButtonColor'] = value + self._color_radiobutton.click() + return self + + @allure.step('Set emoji for account') + def set_emoji(self, value: str): + self._emoji_button.click() + EmojiPopup().wait_until_appears().select(value) + return self + + @allure.step('Set eth address for account added from context menu') + def set_eth_address(self, value: str): + self._address_text_edit.text = value + return self + + @allure.step('Set eth address for account added from plus button') + def set_origin_eth_address(self, value: str): + self._origin_combobox.click() + self._watch_only_account_origin_item.click() + self._address_text_edit.text = value + return self + + @allure.step('Set private key for account') + def set_origin_private_key(self, value: str): + self._origin_combobox.click() + self._new_master_key_origin_item.click() + AddNewAccountPopup().wait_until_appears().import_private_key(value) + return self + + @allure.step('Set derivation path for account') + def set_derivation_path(self, value: str, index: int, password: str): + self._edit_derivation_path_button.hover().click() + AuthenticatePopup().wait_until_appears().authenticate(password) + if value in [_.value for _ in constants.wallet.DerivationPath]: + self._derivation_path_combobox_button.click() + self._derivation_path_list_item.real_name['title'] = value + self._derivation_path_list_item.click() + del self._derivation_path_list_item.real_name['title'] + self._address_combobox_button.click() + GeneratedAddressesList().wait_until_appears().select(index) + if value != constants.wallet.DerivationPath.ETHEREUM.value: + self._scroll.vertical_down_to(self._non_eth_checkbox) + self._non_eth_checkbox.set(True) + else: + self._derivation_path_text_edit.type_text(str(index)) + return self + + @allure.step('Save added account') + def save(self): + self._add_account_button.wait_until_appears().click() + return self + + +class AddNewAccountPopup(BasePopup): + + def __init__(self): + super(AddNewAccountPopup, self).__init__() + self._import_private_key_button = Button('mainWallet_AddEditAccountPopup_MasterKey_ImportPrivateKeyOption') + self._private_key_text_edit = TextEdit('mainWallet_AddEditAccountPopup_PrivateKey') + self._private_key_name_text_edit = TextEdit('mainWallet_AddEditAccountPopup_PrivateKeyName') + self._continue_button = Button('mainWallet_AddEditAccountPopup_PrimaryButton') + + @allure.step('Import private key') + def import_private_key(self, private_key: str) -> str: + self._import_private_key_button.click() + self._private_key_text_edit.text = private_key + self._private_key_name_text_edit.text = private_key[:5] + self._continue_button.click() + return private_key[:5] + + +class GeneratedAddressesList(QObject): + + def __init__(self): + super(GeneratedAddressesList, self).__init__('statusDesktop_mainWindow_overlay_popup2') + self._address_list_item = QObject('addAccountPopup_GeneratedAddress') + self._paginator_page = QObject('page_StatusBaseButton') + + @property + @allure.step('Load generated addresses list') + def is_paginator_load(self) -> bool: + try: + return str(driver.findAllObjects(self._paginator_page.real_name)[0].text) == '1' + except IndexError: + return False + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + if 'text' in self._paginator_page.real_name: + del self._paginator_page.real_name['text'] + assert driver.waitFor(lambda: self.is_paginator_load, timeout_msec), 'Generated address list not load' + return self + + @allure.step('Select address in list') + def select(self, index: int): + self._address_list_item.real_name['objectName'] = f'AddAccountPopup-GeneratedAddress-{index}' + + selected_page_number = 1 + while selected_page_number != GENERATED_PAGES_LIMIT: + if self._address_list_item.is_visible: + self._address_list_item.click() + self._paginator_page.wait_until_hidden() + break + else: + selected_page_number += 1 + self._paginator_page.real_name['text'] = selected_page_number + self._paginator_page.click() \ No newline at end of file diff --git a/test/e2e/gui/elements/__init__.py b/test/e2e/gui/elements/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/elements/base_object.py b/test/e2e/gui/elements/base_object.py new file mode 100644 index 0000000000..cde0bc0934 --- /dev/null +++ b/test/e2e/gui/elements/base_object.py @@ -0,0 +1,43 @@ +import logging + +import allure + +import configs +import driver +from gui import objects_map + +_logger = logging.getLogger(__name__) + + +class BaseObject: + + def __init__(self, name: str): + self.symbolic_name = name + self.real_name = getattr(objects_map, name) + + def __str__(self): + return f'{type(self).__qualname__}({self.symbolic_name})' + + def __repr__(self): + return f'{type(self).__qualname__}({self.symbolic_name})' + + @property + def object(self): + raise NotImplementedError + + @property + def is_visible(self) -> bool: + raise NotImplementedError + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + assert driver.waitFor(lambda: self.is_visible, timeout_msec), f'Object {self} is not visible' + return self + + @allure.step('Wait until hidden {0}') + def wait_until_hidden(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + assert driver.waitFor(lambda: not self.is_visible, timeout_msec), f'Object {self} is not hidden' + + @classmethod + def wait_for(cls, condition, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC) -> bool: + return driver.waitFor(lambda: condition, timeout_msec) diff --git a/test/e2e/gui/elements/os/__init__.py b/test/e2e/gui/elements/os/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/elements/os/mac/__init__.py b/test/e2e/gui/elements/os/mac/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/elements/os/mac/button.py b/test/e2e/gui/elements/os/mac/button.py new file mode 100644 index 0000000000..bc5838f215 --- /dev/null +++ b/test/e2e/gui/elements/os/mac/button.py @@ -0,0 +1,10 @@ +import allure + +from .object import NativeObject + + +class Button(NativeObject): + + @allure.step('Click {0}') + def click(self): + self.object.Press() diff --git a/test/e2e/gui/elements/os/mac/object.py b/test/e2e/gui/elements/os/mac/object.py new file mode 100644 index 0000000000..5c8d17959e --- /dev/null +++ b/test/e2e/gui/elements/os/mac/object.py @@ -0,0 +1,48 @@ +import logging + +import allure + +import driver +from gui.elements.base_object import BaseObject + +_logger = logging.getLogger(__name__) + + +class NativeObject(BaseObject): + + def __init__(self, name: str): + super().__init__(name) + + @property + @allure.step('Get object {0}') + def object(self): + return driver.atomacos.wait_for_object(self.real_name) + + @property + @allure.step('Get visible {0}') + def is_visible(self): + try: + return self.object is not None + except (LookupError, ValueError) as err: + _logger.debug(err) + return False + + @property + @allure.step('Get bounds {0}') + def bounds(self): + return self.object.AXFrame + + @property + @allure.step('Get width {0}') + def width(self) -> int: + return int(self.object.AXSize.width) + + @property + @allure.step('Get height {0}') + def height(self) -> int: + return int(self.object.AXSize.height) + + @property + @allure.step('Get central coordinate {0}') + def center(self): + return self.bounds.center() diff --git a/test/e2e/gui/elements/os/mac/text_edit.py b/test/e2e/gui/elements/os/mac/text_edit.py new file mode 100644 index 0000000000..dad05c17a4 --- /dev/null +++ b/test/e2e/gui/elements/os/mac/text_edit.py @@ -0,0 +1,14 @@ +import driver +from .object import NativeObject + + +class TextEdit(NativeObject): + + @property + def text(self) -> str: + return str(self.object.AXValue) + + @text.setter + def text(self, value: str): + self.object.setString('AXValue', value) + driver.waitFor(lambda: self.text == value) diff --git a/test/e2e/gui/elements/os/win/__init__.py b/test/e2e/gui/elements/os/win/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/elements/os/win/button.py b/test/e2e/gui/elements/os/win/button.py new file mode 100644 index 0000000000..47399c7b40 --- /dev/null +++ b/test/e2e/gui/elements/os/win/button.py @@ -0,0 +1,10 @@ +import allure + +from .object import NativeObject + + +class Button(NativeObject): + + @allure.step('Click {0}') + def click(self): + super().click() diff --git a/test/e2e/gui/elements/os/win/object.py b/test/e2e/gui/elements/os/win/object.py new file mode 100644 index 0000000000..335a273aaa --- /dev/null +++ b/test/e2e/gui/elements/os/win/object.py @@ -0,0 +1,42 @@ +import logging + +import allure + +import driver +from gui.elements.base_object import BaseObject + +_logger = logging.getLogger(__name__) + + +class NativeObject(BaseObject): + + def __init__(self, name: str): + super().__init__(name) + + @property + @allure.step('Get object {0}') + def object(self): + return driver.waitForObject(self.real_name) + + @property + @allure.step('Get visible {0}') + def is_visible(self): + try: + driver.waitForObject(self.real_name, 1) + return True + except (AttributeError, LookupError, RuntimeError): + return False + + @property + @allure.step('Get bounds {0}') + def bounds(self): + return driver.object.globalBounds(self.object) + + @property + @allure.step('Get central coordinate {0}') + def center(self): + return self.bounds.center() + + @allure.step('Click {0}') + def click(self): + driver.mouseClick(self.object) diff --git a/test/e2e/gui/elements/os/win/text_edit.py b/test/e2e/gui/elements/os/win/text_edit.py new file mode 100644 index 0000000000..7ccbf2a0b2 --- /dev/null +++ b/test/e2e/gui/elements/os/win/text_edit.py @@ -0,0 +1,32 @@ +import allure + +import configs +import constants +import driver +from .object import NativeObject + + +class TextEdit(NativeObject): + + @property + @allure.step('Get current text {0}') + def text(self) -> str: + return str(self.object.text) + + @text.setter + @allure.step('Type: {1} {0}') + def text(self, value: str): + self.clear() + driver.nativeType(value) + assert driver.waitFor(lambda: self.text == value, configs.timeouts.UI_LOAD_TIMEOUT_MSEC), \ + f'Type text failed, value in field: "{self.text}", expected: {value}' + + @allure.step('Clear {0}') + def clear(self): + # Set focus + driver.nativeMouseClick(int(self.center.x), int(self.center.y), driver.Qt.LeftButton) + driver.type(self.object, f'<{constants.commands.SELECT_ALL}>') + driver.type(self.object, f'<{constants.commands.BACKSPACE}>') + assert driver.waitFor(lambda: not self.text), \ + f'Clear text field failed, value in field: "{self.text}"' + return self diff --git a/test/e2e/gui/elements/qt/__init__.py b/test/e2e/gui/elements/qt/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/elements/qt/button.py b/test/e2e/gui/elements/qt/button.py new file mode 100644 index 0000000000..16d4b1f043 --- /dev/null +++ b/test/e2e/gui/elements/qt/button.py @@ -0,0 +1,21 @@ +import typing + +import allure + +import driver +from gui.elements.qt.object import QObject + + +class Button(QObject): + + @allure.step('Click {0}') + def click( + self, + x: typing.Union[int, driver.UiTypes.ScreenPoint] = None, + y: typing.Union[int, driver.UiTypes.ScreenPoint] = None, + button: driver.MouseButton = None + ): + if None not in (x, y, button): + getattr(self.object, 'clicked')() + else: + super(Button, self).click(x, y, button) diff --git a/test/e2e/gui/elements/qt/check_box.py b/test/e2e/gui/elements/qt/check_box.py new file mode 100644 index 0000000000..b3a26b4124 --- /dev/null +++ b/test/e2e/gui/elements/qt/check_box.py @@ -0,0 +1,15 @@ +import allure + +import configs +import driver +from gui.elements.qt.object import QObject + + +class CheckBox(QObject): + + @allure.step("Set {0} value: {1}") + def set(self, value: bool, x: int = None, y: int = None): + if self.is_checked is not value: + self.click(x, y) + assert driver.waitFor( + lambda: self.is_checked is value, configs.timeouts.UI_LOAD_TIMEOUT_MSEC), 'Value not changed' diff --git a/test/e2e/gui/elements/qt/list.py b/test/e2e/gui/elements/qt/list.py new file mode 100644 index 0000000000..577fb5dff1 --- /dev/null +++ b/test/e2e/gui/elements/qt/list.py @@ -0,0 +1,43 @@ +import time +import typing + +import allure + +import configs +import driver +from gui.elements.qt.object import QObject + + +class List(QObject): + + @property + @allure.step('Get list items {0}') + def items(self): + return [self.object.itemAtIndex(index) for index in range(self.object.count)] + + @allure.step('Get values of list items {0}') + def get_values(self, attr_name: str) -> typing.List[str]: + values = [] + for index in range(self.object.count): + value = str(getattr(self.object.itemAtIndex(index), attr_name, '')) + if value: + values.append(value) + return values + + @allure.step('Select item {1} in {0}') + def select(self, value: str, attr_name: str): + driver.mouseClick(self.wait_for_item(value, attr_name)) + + @allure.step('Wait for item {1} in {0} with attribute {2}') + def wait_for_item(self, value: str, attr_name: str, timeout_sec: int = configs.timeouts.UI_LOAD_TIMEOUT_SEC): + started_at = time.monotonic() + values = [] + while True: + for index in range(self.object.count): + cur_value = str(getattr(self.object.itemAtIndex(index), attr_name, '')) + if cur_value == value: + return self.object.itemAtIndex(index) + values.append(cur_value) + time.sleep(1) + if time.monotonic() - started_at > timeout_sec: + raise RuntimeError(f'value not found in list: {values}') diff --git a/test/e2e/gui/elements/qt/object.py b/test/e2e/gui/elements/qt/object.py new file mode 100644 index 0000000000..085508463b --- /dev/null +++ b/test/e2e/gui/elements/qt/object.py @@ -0,0 +1,132 @@ +import logging +import time +import typing + +import allure + +import configs +import driver +from gui.elements.base_object import BaseObject +from scripts.tools.image import Image + +_logger = logging.getLogger(__name__) + + +class QObject(BaseObject): + + def __init__(self, name: str): + super().__init__(name) + self._image = Image(self.real_name) + + def __str__(self): + return f'{type(self).__qualname__}({self.symbolic_name})' + + @property + @allure.step('Get object {0}') + def object(self): + return driver.waitForObject(self.real_name, configs.timeouts.UI_LOAD_TIMEOUT_MSEC) + + @property + @allure.step('Get object exists {0}') + def exists(self) -> bool: + return driver.object.exists(self.real_name) + + @property + @allure.step('Get bounds {0}') + def bounds(self): + return driver.object.globalBounds(self.object) + + @property + @allure.step('Get "x" coordinate {0}') + def x(self) -> int: + return self.bounds.x + + @property + @allure.step('Get "y" coordinate {0}') + def y(self) -> int: + return self.bounds.y + + @property + @allure.step('Get width {0}') + def width(self) -> int: + return int(self.bounds.width) + + @property + @allure.step('Get height {0}') + def height(self) -> int: + return int(self.bounds.height) + + @property + @allure.step('Get central coordinate {0}') + def center(self): + return self.bounds.center() + + @property + @allure.step('Get enabled {0}') + def is_enabled(self) -> bool: + return self.object.enabled + + @property + @allure.step('Get selected {0}') + def is_selected(self) -> bool: + return self.object.selected + + @property + @allure.step('Get checked {0}') + def is_checked(self) -> bool: + return self.object.checked + + @property + @allure.step('Get visible {0}') + def is_visible(self) -> bool: + try: + return driver.waitForObject(self.real_name, 0).visible + except (AttributeError, LookupError, RuntimeError): + return False + + @property + @allure.step('Get image {0}') + def image(self): + self._image.update_view() + return self._image + + @allure.step('Click {0}') + def click( + self, + x: int = None, + y: int = None, + button=None + ): + driver.mouseClick( + self.object, + x or self.width // 2, + y or self.height // 2, + button or driver.Qt.LeftButton + ) + + @allure.step('Hover {0}') + def hover(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + def _hover(): + try: + driver.mouseMove(self.object) + return getattr(self.object, 'hovered', True) + except RuntimeError as err: + _logger.debug(err) + time.sleep(1) + return False + + assert driver.waitFor(lambda: _hover(), timeout_msec) + return self + + + @allure.step('Open context menu') + def open_context_menu( + self, + x: typing.Union[int, driver.UiTypes.ScreenPoint] = None, + y: typing.Union[int, driver.UiTypes.ScreenPoint] = None, + ): + self.click( + x or self.width // 2, + y or self.height // 2, + driver.Qt.RightButton + ) diff --git a/test/e2e/gui/elements/qt/scroll.py b/test/e2e/gui/elements/qt/scroll.py new file mode 100644 index 0000000000..ea92ec639f --- /dev/null +++ b/test/e2e/gui/elements/qt/scroll.py @@ -0,0 +1,33 @@ +import time + +import allure + +import driver +from .object import QObject + + +class Scroll(QObject): + + @allure.step('Scroll vertical to object') + def vertical_scroll_to(self, element: QObject, timeout_sec: int = 5): + started_at = time.monotonic() + step = 10 + direction = 1 + while not element.is_visible: + step *= 2 + direction *= -1 + driver.flick(self.object, 0, step * direction) + time.sleep(0.1) + if time.monotonic() - started_at > timeout_sec: + raise LookupError(f'Object not found: {element}') + if hasattr(element.object, 'y'): + driver.flick(self.object, 0, int(element.object.y)) + + @allure.step('Scroll down to object') + def vertical_down_to(self, element: QObject, timeout_sec: int = 5): + started_at = time.monotonic() + step = 100 + while not element.is_visible: + driver.flick(self.object, 0, step) + if time.monotonic() - started_at > timeout_sec: + raise LookupError(f'Object not found: {element}') diff --git a/test/e2e/gui/elements/qt/slider.py b/test/e2e/gui/elements/qt/slider.py new file mode 100644 index 0000000000..5e48a50410 --- /dev/null +++ b/test/e2e/gui/elements/qt/slider.py @@ -0,0 +1,33 @@ +import allure + +from gui.elements.qt.object import QObject + + +class Slider(QObject): + + @property + @allure.step('Get minimal value {0}') + def min(self) -> int: + return int(getattr(self.object, 'from')) + + @property + @allure.step('Get maximal value {0}') + def max(self) -> max: + return int(getattr(self.object, 'to')) + + @property + @allure.step('Get value {0}') + def value(self) -> int: + return int(self.object.value) + + @value.setter + @allure.step('Set value {1} {0}') + def value(self, value: int): + if value != self.value: + if self.min <= value <= self.max: + if self.value < value: + while self.value < value: + self.object.increase() + if self.value > value: + while self.value > value: + self.object.decrease() diff --git a/test/e2e/gui/elements/qt/text_edit.py b/test/e2e/gui/elements/qt/text_edit.py new file mode 100644 index 0000000000..c4445cb430 --- /dev/null +++ b/test/e2e/gui/elements/qt/text_edit.py @@ -0,0 +1,34 @@ +import allure + +import configs +import driver +from gui.elements.qt.object import QObject + + +class TextEdit(QObject): + + @property + @allure.step('Get current text {0}') + def text(self) -> str: + return str(self.object.text) + + @text.setter + @allure.step('Type text {1} {0}') + def text(self, value: str): + self.clear() + self.type_text(value) + assert driver.waitFor(lambda: self.text == value, configs.timeouts.UI_LOAD_TIMEOUT_MSEC), \ + f'Type text failed, value in field: "{self.text}", expected: {value}' + + @allure.step('Type: {1} in {0}') + def type_text(self, value: str): + driver.type(self.object, value) + return self + + @allure.step('Clear {0}') + def clear(self, verify: bool = True): + self.object.clear() + if verify: + assert driver.waitFor(lambda: not self.text), \ + f'Clear text field failed, value in field: "{self.text}"' + return self diff --git a/test/e2e/gui/elements/qt/text_label.py b/test/e2e/gui/elements/qt/text_label.py new file mode 100644 index 0000000000..ba604fdaaf --- /dev/null +++ b/test/e2e/gui/elements/qt/text_label.py @@ -0,0 +1,11 @@ +import allure + +from gui.elements.qt.object import QObject + + +class TextLabel(QObject): + + @property + @allure.step('Get text {0}') + def text(self) -> str: + return str(self.object.text) diff --git a/test/e2e/gui/elements/qt/window.py b/test/e2e/gui/elements/qt/window.py new file mode 100644 index 0000000000..554bada9c3 --- /dev/null +++ b/test/e2e/gui/elements/qt/window.py @@ -0,0 +1,40 @@ +import logging + +import allure + +import driver +from gui.elements.qt.object import QObject + +_logger = logging.getLogger(__name__) + + +class Window(QObject): + + def prepare(self) -> 'Window': + self.maximize() + self.on_top_level() + return self + + @allure.step("Maximize {0}") + def maximize(self): + assert driver.toplevel_window.maximize(self.real_name), 'Maximize failed' + _logger.info(f'Window {getattr(self.object, "title", "")} is maximized') + + @allure.step("Minimize {0}") + def minimize(self): + assert driver.toplevel_window.minimize(self.real_name), 'Minimize failed' + _logger.info(f'Window {getattr(self.object, "title", "")} is minimized') + + @allure.step("Set focus on {0}") + def set_focus(self): + assert driver.toplevel_window.set_focus(self.real_name), 'Set focus failed' + _logger.info(f'Window {getattr(self.object, "title", "")} in focus') + + @allure.step("Move {0} on top") + def on_top_level(self): + assert driver.toplevel_window.on_top_level(self.real_name), 'Set on top failed' + _logger.info(f'Window {getattr(self.object, "title", "")} moved on top') + + @allure.step("Close {0}") + def close(self): + driver.toplevel_window.close(self.real_name) diff --git a/test/e2e/gui/main_window.py b/test/e2e/gui/main_window.py new file mode 100644 index 0000000000..8336f3422b --- /dev/null +++ b/test/e2e/gui/main_window.py @@ -0,0 +1,130 @@ +import logging +import typing + +import allure + +import configs +import constants +import driver +from constants import UserAccount +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 +from gui.components.user_canvas import UserCanvas +from gui.elements.qt.button import Button +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.onboarding import AllowNotificationsView, WelcomeView, TouchIDAuthView, LoginView +from gui.screens.settings import SettingsScreen +from gui.screens.wallet import WalletScreen +from scripts.tools.image import Image + +_logger = logging.getLogger(__name__) + + +class LeftPanel(QObject): + + def __init__(self): + super(LeftPanel, self).__init__('mainWindow_StatusAppNavBar') + self._profile_button = Button('mainWindow_ProfileNavBarButton') + 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') + + @property + @allure.step('Get communities names') + def communities(self) -> typing.List[str]: + community_names = [] + for obj in driver.findAllObjects(self._community_template_button.real_name): + community_names.append(obj.name) + return community_names + + @property + @allure.step('Get user badge color') + def user_badge_color(self) -> str: + return str(self._profile_button.object.badge.color.name) + + @allure.step('Open user canvas') + def open_user_canvas(self) -> UserCanvas: + self._profile_button.click() + return UserCanvas().wait_until_appears() + + @allure.step('Verify: User is online') + def user_is_online(self) -> bool: + return self.user_badge_color == '#4ebc60' + + @allure.step('Verify: User is offline') + def user_is_offline(self): + return self.user_badge_color == '#7f8990' + + @allure.step('Verify: User is set to automatic') + def user_is_set_to_automatic(self): + return self.user_badge_color == '#4ebc60' + + @allure.step('Open community portal') + def open_communities_portal(self) -> CommunitiesPortal: + self._communities_portal_button.click() + return CommunitiesPortal().wait_until_appears() + + def _get_community(self, name: str): + community_names = [] + for obj in driver.findAllObjects(self._community_template_button.real_name): + community_names.append(str(obj.name)) + if str(obj.name) == str(name): + return obj + raise LookupError(f'Community: {name} not found in {community_names}') + + @allure.step('Open community') + def select_community(self, name: str) -> CommunityScreen: + driver.mouseClick(self._get_community(name)) + return CommunityScreen().wait_until_appears() + + @allure.step('Get community logo') + def get_community_logo(self, name: str) -> Image: + return Image(driver.objectMap.realName(self._get_community(name))) + + @allure.step('Open settings') + def open_settings(self) -> CommunitiesPortal: + self._settings_button.click() + return SettingsScreen().wait_until_appears() + + @allure.step('Open Wallet section') + def open_wallet(self) -> WalletScreen: + self._wallet_button.click() + return WalletScreen().wait_until_appears() + +class MainWindow(Window): + + def __init__(self): + super(MainWindow, self).__init__('statusDesktop_mainWindow') + self.left_panel = LeftPanel() + + @allure.step('Sign Up user') + def sign_up(self, user_account: UserAccount = constants.user.user_account_one): + if configs.system.IS_MAC: + AllowNotificationsView().wait_until_appears().allow() + BeforeStartedPopUp().get_started() + wellcome_screen = WelcomeView().wait_until_appears() + profile_view = wellcome_screen.get_keys().generate_new_keys() + profile_view.set_display_name(user_account.name) + details_view = profile_view.next() + create_password_view = details_view.next() + confirm_password_view = create_password_view.create_password(user_account.password) + confirm_password_view.confirm_password(user_account.password) + if configs.system.IS_MAC: + TouchIDAuthView().wait_until_appears().prefer_password() + SplashScreen().wait_until_appears().wait_until_hidden() + if not configs.DEV_BUILD: + WelcomeStatusPopup().confirm() + return self + + @allure.step('Log in user') + def log_in(self, user_account: UserAccount): + LoginView().log_in(user_account) + SplashScreen().wait_until_appears().wait_until_hidden() + if not configs.DEV_BUILD: + WelcomeStatusPopup().wait_until_appears().confirm() + return self diff --git a/test/e2e/gui/objects_map/__init__.py b/test/e2e/gui/objects_map/__init__.py new file mode 100644 index 0000000000..cb8eef59de --- /dev/null +++ b/test/e2e/gui/objects_map/__init__.py @@ -0,0 +1,7 @@ +from .community_names import * +from .component_names import * +from .main_names import * +from .onboarding_names import * +from .os_names import * +from .settings_names import * +from .wallet_names import * diff --git a/test/e2e/gui/objects_map/community_names.py b/test/e2e/gui/objects_map/community_names.py new file mode 100644 index 0000000000..2a99259570 --- /dev/null +++ b/test/e2e/gui/objects_map/community_names.py @@ -0,0 +1,78 @@ +from .main_names import statusDesktop_mainWindow, statusDesktop_mainWindow_overlay + +# Community Portal +mainWindow_communitiesPortalLayout_CommunitiesPortalLayout = {"container": statusDesktop_mainWindow, "objectName": "communitiesPortalLayout", "type": "CommunitiesPortalLayout", "visible": True} +mainWindow_Create_New_Community_StatusButton = {"checkable": False, "container": mainWindow_communitiesPortalLayout_CommunitiesPortalLayout, "objectName": "createCommunityButton", "type": "StatusButton", "visible": True} + +# Community View +mainWindow_communityLoader_Loader = {"container": statusDesktop_mainWindow, "id": "communityLoader", "type": "Loader", "unnamed": 1, "visible": True} +# Left Panel +mainWindow_communityColumnView_CommunityColumnView = {"container": mainWindow_communityLoader_Loader, "objectName": "communityColumnView", "type": "CommunityColumnView", "visible": True} +mainWindow_communityHeaderButton_StatusChatInfoButton = {"checkable": False, "container": mainWindow_communityColumnView_CommunityColumnView, "objectName": "communityHeaderButton", "type": "StatusChatInfoButton", "visible": True} +mainWindow_identicon_StatusSmartIdenticon = {"container": mainWindow_communityHeaderButton_StatusChatInfoButton, "id": "identicon", "type": "StatusSmartIdenticon", "unnamed": 1, "visible": True} +mainWindow_statusChatInfoButtonNameText_TruncatedTextWithTooltip = {"container": mainWindow_communityHeaderButton_StatusChatInfoButton, "objectName": "statusChatInfoButtonNameText", "type": "TruncatedTextWithTooltip", "visible": True} +mainWindow_Members_TruncatedTextWithTooltip = {"container": mainWindow_communityHeaderButton_StatusChatInfoButton, "type": "TruncatedTextWithTooltip", "unnamed": 1, "visible": True} +mainWindow_startChatButton_StatusIconTabButton = {"checkable": True, "container": mainWindow_communityColumnView_CommunityColumnView, "objectName": "startChatButton", "type": "StatusIconTabButton", "visible": True} +mainWindow_createChatOrCommunity_Loader = {"container": mainWindow_communityColumnView_CommunityColumnView, "id": "createChatOrCommunity", "type": "Loader", "unnamed": 1, "visible": True} +mainWindow_scrollView_StatusScrollView = {"container": mainWindow_communityColumnView_CommunityColumnView, "id": "scrollView", "type": "StatusScrollView", "unnamed": 1, "visible": True} +scrollView_Flickable = {"container": mainWindow_scrollView_StatusScrollView, "type": "Flickable", "unnamed": 1, "visible": True} +scrollView_chatListItems_StatusListView = {"container": scrollView_Flickable, "objectName": "chatListItems", "type": "StatusListView", "visible": True} +channel_listItem = {"container": scrollView_chatListItems_StatusListView, "id": "chatListDelegate", "type": "DropArea", "isCategory": False, "visible": True} +channel_identicon_StatusSmartIdenticon = {"container": None, "id": "identicon", "type": "StatusSmartIdenticon", "unnamed": 1, "visible": True} +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} + +# Tool Bar +mainWindow_statusToolBar_StatusToolBar = {"container": mainWindow_communityLoader_Loader, "objectName": "statusToolBar", "type": "StatusToolBar", "visible": True} +statusToolBar_chatToolbarMoreOptionsButton = {"container": mainWindow_statusToolBar_StatusToolBar, "objectName": "chatToolbarMoreOptionsButton", "type": "StatusFlatRoundButton", "visible": True} +delete_Channel_StatusMenuItem = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "enabled": True, "objectName": "deleteOrLeaveMenuItem", "type": "StatusMenuItem", "visible": True} +edit_Channel_StatusMenuItem = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "enabled": True, "objectName": "editChannelMenuItem", "type": "StatusMenuItem", "visible": True} +statusToolBar_statusSmartIdenticonLetter_StatusLetterIdenticon = {"container": mainWindow_statusToolBar_StatusToolBar, "objectName": "statusSmartIdenticonLetter", "type": "StatusLetterIdenticon", "visible": True} +statusToolBar_statusChatInfoButtonNameText_TruncatedTextWithTooltip = {"container": mainWindow_statusToolBar_StatusToolBar, "objectName": "statusChatInfoButtonNameText", "type": "TruncatedTextWithTooltip", "visible": True} +statusToolBar_TruncatedTextWithTooltip = {"container": mainWindow_statusToolBar_StatusToolBar, "type": "TruncatedTextWithTooltip", "unnamed": 1, "visible": True} + +# Chat +mainWindow_ChatColumnView = {"container": mainWindow_communityLoader_Loader, "type": "ChatColumnView", "unnamed": 1, "visible": True} +chatMessageViewDelegate_channelIdentifierNameText_StyledText = {"container": mainWindow_ChatColumnView, "objectName": "channelIdentifierNameText", "type": "StyledText", "visible": True} +chatMessageViewDelegate_Welcome = {"container": mainWindow_ChatColumnView, "type": "StatusBaseText", "unnamed": 1, "visible": True} +chatMessageViewDelegate_channelIdentifierSmartIdenticon_StatusSmartIdenticon = {"container": mainWindow_ChatColumnView, "objectName": "channelIdentifierSmartIdenticon", "type": "StatusSmartIdenticon", "visible": True} + +# Community Settings +mainWindow_communitySettingsBackToCommunityButton_StatusBaseText = {"container": mainWindow_communityLoader_Loader, "objectName": "communitySettingsBackToCommunityButton", "type": "StatusBaseText", "visible": True} +mainWindow_listView_StatusListView = {"container": mainWindow_communityLoader_Loader, "id": "listView", "type": "StatusListView", "unnamed": 1, "visible": True} +overview_StatusNavigationListItem = {"container": mainWindow_listView_StatusListView, "objectName": "CommunitySettingsView_NavigationListItem_Overview", "type": "StatusNavigationListItem", "visible": True} +members_StatusNavigationListItem = {"container": mainWindow_listView_StatusListView, "index": 1, "objectName": "CommunitySettingsView_NavigationListItem_Members", "type": "StatusNavigationListItem", "visible": True} +# Overview Settings View +mainWindow_OverviewSettingsPanel = {"container": mainWindow_communityLoader_Loader, "type": "OverviewSettingsPanel", "unnamed": 1, "visible": True} +communityOverviewSettingsCommunityName_StatusBaseText = {"container": mainWindow_OverviewSettingsPanel, "objectName": "communityOverviewSettingsCommunityName", "type": "StatusBaseText", "visible": True} +communityOverviewSettingsCommunityDescription_StatusBaseText = {"container": mainWindow_OverviewSettingsPanel, "objectName": "communityOverviewSettingsCommunityDescription", "type": "StatusBaseText", "visible": True} +mainWindow_Edit_Community_StatusButton = {"checkable": False, "container": mainWindow_OverviewSettingsPanel, "objectName": "communityOverviewSettingsEditCommunityButton", "text": "Edit Community", "type": "StatusButton", "visible": True} +# Members Settings View +mainWindow_MembersSettingsPanel = {"container": mainWindow_communityLoader_Loader, "type": "MembersSettingsPanel", "unnamed": 1, "visible": True} +embersListViews_ListView = {"container": mainWindow_MembersSettingsPanel, "objectName": "CommunityMembersTabPanel_MembersListViews", "type": "ListView", "visible": True} +memberItem_StatusMemberListItem = {"container": embersListViews_ListView, "id": "memberItem", "type": "StatusMemberListItem", "unnamed": 1, "visible": True} +# Permissions Settings View +mainWindow_PermissionsSettingsPanel = {"container": mainWindow_communityLoader_Loader, "type": "PermissionsSettingsPanel", "unnamed": 1, "visible": True} +# Edit Community +mainWindow_communityEditPanelScrollView_EditSettingsPanel = {"container": statusDesktop_mainWindow, "objectName": "communityEditPanelScrollView", "type": "EditSettingsPanel", "visible": True} +communityEditPanelScrollView_Flickable = {"container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "type": "Flickable", "unnamed": 1, "visible": True} +communityEditPanelScrollView_communityNameInput_TextEdit = {"container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "objectName": "communityNameInput", "type": "TextEdit", "visible": True} +communityEditPanelScrollView_communityDescriptionInput_TextEdit = {"container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "objectName": "communityDescriptionInput", "type": "TextEdit", "visible": True} +communityEditPanelScrollView_communityLogoPicker_LogoPicker = {"container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "objectName": "communityLogoPicker", "type": "LogoPicker", "visible": True} +communityEditPanelScrollView_image_StatusImage = {"container": communityEditPanelScrollView_communityLogoPicker_LogoPicker, "id": "image", "type": "StatusImage", "unnamed": 1, "visible": True} +communityEditPanelScrollView_editButton_StatusRoundButton = {"container": communityEditPanelScrollView_communityLogoPicker_LogoPicker, "id": "editButton", "type": "StatusRoundButton", "unnamed": 1, "visible": True} +communityEditPanelScrollView_communityBannerPicker_BannerPicker = {"container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "objectName": "communityBannerPicker", "type": "BannerPicker", "visible": True} +communityEditPanelScrollView_image_StatusImage_2 = {"container": communityEditPanelScrollView_communityBannerPicker_BannerPicker, "id": "image", "type": "StatusImage", "unnamed": 1, "visible": True} +communityEditPanelScrollView_editButton_StatusRoundButton_2 = {"container": communityEditPanelScrollView_communityBannerPicker_BannerPicker, "id": "editButton", "type": "StatusRoundButton", "unnamed": 1, "visible": True} +communityEditPanelScrollView_StatusPickerButton = {"checkable": False, "container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "type": "StatusPickerButton", "unnamed": 1, "visible": True} +communityEditPanelScrollView_communityTagsPicker_TagsPicker = {"container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "objectName": "communityTagsPicker", "type": "TagsPicker", "visible": True} +communityEditPanelScrollView_flow_Flow = {"container": communityEditPanelScrollView_communityTagsPicker_TagsPicker, "id": "flow", "type": "Flow", "unnamed": 1, "visible": True} +communityEditPanelScrollView_StatusCommunityTag = {"container": communityEditPanelScrollView_communityTagsPicker_TagsPicker, "type": "StatusCommunityTag", "unnamed": 1, "visible": True} +communityEditPanelScrollView_Choose_StatusPickerButton = {"checkable": False, "container": communityEditPanelScrollView_communityTagsPicker_TagsPicker, "type": "StatusPickerButton", "unnamed": 1, "visible": True} +communityEditPanelScrollView_archiveSupportToggle_StatusCheckBox = {"checkable": True, "container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "id": "archiveSupportToggle", "type": "StatusCheckBox", "unnamed": 1, "visible": True} +communityEditPanelScrollView_requestToJoinToggle_StatusCheckBox = {"checkable": True, "container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "id": "requestToJoinToggle", "type": "StatusCheckBox", "unnamed": 1, "visible": True} +communityEditPanelScrollView_pinMessagesToggle_StatusCheckBox = {"checkable": True, "container": mainWindow_communityEditPanelScrollView_EditSettingsPanel, "id": "pinMessagesToggle", "type": "StatusCheckBox", "unnamed": 1, "visible": True} +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} diff --git a/test/e2e/gui/objects_map/component_names.py b/test/e2e/gui/objects_map/component_names.py new file mode 100644 index 0000000000..f1bcf66c02 --- /dev/null +++ b/test/e2e/gui/objects_map/component_names.py @@ -0,0 +1,201 @@ +from objectmaphelper import * + +from .main_names import statusDesktop_mainWindow +from .main_names import statusDesktop_mainWindow_overlay +from .main_names import statusDesktop_mainWindow_overlay_popup2 + +# Scroll +o_Flickable = {"container": statusDesktop_mainWindow_overlay, "type": "Flickable", "unnamed": 1, "visible": True} + +# Context Menu +o_StatusListView = {"container": statusDesktop_mainWindow_overlay, "type": "StatusListView", "unnamed": 1, "visible": True} + + +""" Onboarding """ +# Before you get started Popup +acknowledge_checkbox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "objectName": "acknowledgeCheckBox", "type": "StatusCheckBox", "visible": True} +termsOfUseCheckBox_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "objectName":"termsOfUseCheckBox", "type": "StatusCheckBox", "visible": True} +getStartedStatusButton_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "getStartedStatusButton", "type": "StatusButton", "visible": True} + +# Back Up Your Seed Phrase Popup +o_PopupItem = {"container": statusDesktop_mainWindow_overlay, "type": "PopupItem", "unnamed": 1, "visible": True} +i_have_a_pen_and_paper_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "objectName": "Acknowledgements_havePen", "type": "StatusCheckBox", "visible": True} +i_know_where_I_ll_store_it_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "objectName": "Acknowledgements_storeIt", "type": "StatusCheckBox", "visible": True} +i_am_ready_to_write_down_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "objectName": "Acknowledgements_writeDown", "type": "StatusCheckBox", "visible": True} +not_Now_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "type": "StatusButton", "unnamed": 1, "visible": True} +confirm_Seed_Phrase_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "objectName": "BackupSeedModal_nextButton", "type": "StatusButton", "visible": True} +backup_seed_phrase_popup_ConfirmSeedPhrasePanel_StatusSeedPhraseInput_placeholder = {"container": statusDesktop_mainWindow_overlay, "objectName": "ConfirmSeedPhrasePanel_StatusSeedPhraseInput_%WORD_NO%", "type": "StatusSeedPhraseInput", "visible": True} +reveal_seed_phrase_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "objectName": "ConfirmSeedPhrasePanel_RevealSeedPhraseButton", "type": "StatusButton", "visible": True} +blur_GaussianBlur = {"container": statusDesktop_mainWindow_overlay, "id": "blur", "type": "GaussianBlur", "unnamed": 1, "visible": True} +confirmSeedPhrasePanel_StatusSeedPhraseInput = {"container": statusDesktop_mainWindow_overlay, "type": "StatusSeedPhraseInput", "visible": True} +confirmFirstWord = {"container": statusDesktop_mainWindow_overlay, "objectName": "BackupSeedModal_BackupSeedStepBase_confirmFirstWord", "type": "BackupSeedStepBase", "visible": True} +confirmFirstWord_inputText = {"container": confirmFirstWord, "objectName": "BackupSeedStepBase_inputText", "type": "TextEdit", "visible": True} +continue_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "objectName": "BackupSeedModal_nextButton", "type": "StatusButton", "visible": True} +confirmSecondWord = {"container": statusDesktop_mainWindow_overlay, "objectName": "BackupSeedModal_BackupSeedStepBase_confirmSecondWord", "type": "BackupSeedStepBase", "visible": True} +confirmSecondWord_inputText = {"container": confirmSecondWord, "objectName": "BackupSeedStepBase_inputText", "type": "TextEdit", "visible": True} +i_acknowledge_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "objectName": "ConfirmStoringSeedPhrasePanel_storeCheck", "type": "StatusCheckBox", "visible": True} +completeAndDeleteSeedPhraseButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "BackupSeedModal_completeAndDeleteSeedPhraseButton", "type": "StatusButton", "visible": True} + +# Send Contact Request Popup +contactRequest_ChatKey_Input = {"container": statusDesktop_mainWindow_overlay, "objectName": "SendContactRequestModal_ChatKey_Input", "type": "TextEdit"} +contactRequest_SayWhoYouAre_Input = {"container": statusDesktop_mainWindow_overlay, "objectName": "SendContactRequestModal_SayWhoYouAre_Input", "type": "TextEdit"} +contactRequest_Send_Button = {"container": statusDesktop_mainWindow_overlay, "objectName": "SendContactRequestModal_Send_Button", "type": "StatusButton"} + +# Change Language Popup +close_the_app_now_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "type": "StatusButton", "unnamed": 1, "visible": True} + +# User Status Profile Menu +o_StatusListView = {"container": statusDesktop_mainWindow_overlay, "type": "StatusListView", "unnamed": 1, "visible": True} +userContextmenu_AlwaysActiveButton= {"container": o_StatusListView, "objectName": "userStatusMenuAlwaysOnlineAction", "type": "StatusMenuItem", "visible": True} +userContextmenu_InActiveButton= {"container": o_StatusListView, "objectName": "userStatusMenuInactiveAction", "type": "StatusMenuItem", "visible": True} +userContextmenu_AutomaticButton= {"container": o_StatusListView, "objectName": "userStatusMenuAutomaticAction", "type": "StatusMenuItem", "visible": True} +userContextMenu_ViewMyProfileAction = {"container": o_StatusListView, "objectName": "userStatusViewMyProfileAction", "type": "StatusMenuItem", "visible": True} +userLabel_StyledText = {"container": o_StatusListView, "type": "StyledText", "unnamed": 1, "visible": True} +o_StatusIdenticonRing = {"container": o_StatusListView, "type": "StatusIdenticonRing", "unnamed": 1, "visible": True} + +# My Profile Popup +ProfileHeader_userImage = {"container": statusDesktop_mainWindow_overlay, "objectName": "ProfileDialog_userImage", "type": "UserImage", "visible": True} +ProfilePopup_displayName = {"container": statusDesktop_mainWindow_overlay, "objectName": "ProfileDialog_displayName", "type": "StatusBaseText", "visible": True} +ProfilePopup_editButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "editProfileButton", "type": "StatusButton", "visible": True} +ProfilePopup_SendContactRequestButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "profileDialog_sendContactRequestButton", "type": "StatusButton", "visible": True} +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} + +# Welcome Status Popup +agreeToUse_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "id": "agreeToUse", "type": "StatusCheckBox", "unnamed": 1, "visible": True} +readyToUse_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "id": "readyToUse", "type": "StatusCheckBox", "unnamed": 1, "visible": True} +i_m_ready_to_use_Status_Desktop_Beta_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "type": "StatusButton", "unnamed": 1, "visible": True} + + +""" Communities """ + +# Create Community Banner +create_new_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "objectName": "communityBannerButton", "type": "StatusButton", "visible": True} + +# Create Community Popup +createCommunityNameInput_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "communityNameInput", "type": "TextEdit", "visible": True} +createCommunityDescriptionInput_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "communityDescriptionInput", "type": "TextEdit", "visible": True} +communityBannerPicker_BannerPicker = {"container": statusDesktop_mainWindow_overlay, "objectName": "communityBannerPicker", "type": "BannerPicker", "visible": True} +addButton_StatusRoundButton = {"container": communityBannerPicker_BannerPicker, "id": "addButton", "type": "StatusRoundButton", "unnamed": 1, "visible": True} +communityLogoPicker_LogoPicker = {"container": statusDesktop_mainWindow_overlay, "objectName": "communityLogoPicker", "type": "LogoPicker", "visible": True} +addButton_StatusRoundButton2 = {"container": communityLogoPicker_LogoPicker, "id": "addButton", "type": "StatusRoundButton", "unnamed": 1, "visible": True} +communityColorPicker_ColorPicker = {"container": statusDesktop_mainWindow_overlay, "objectName": "communityColorPicker", "type": "ColorPicker", "visible": True} +StatusPickerButton = {"checkable": False, "container": communityColorPicker_ColorPicker, "type": "StatusPickerButton", "unnamed": 1, "visible": True} +communityTagsPicker_TagsPicker = {"container": statusDesktop_mainWindow_overlay, "objectName": "communityTagsPicker", "type": "TagsPicker", "visible": True} +choose_tags_StatusPickerButton = {"checkable": False, "container": communityTagsPicker_TagsPicker, "type": "StatusPickerButton", "unnamed": 1, "visible": True} +archiveSupportToggle_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "id": "archiveSupportToggle", "type": "StatusCheckBox", "unnamed": 1, "visible": True} +requestToJoinToggle_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "id": "requestToJoinToggle", "type": "StatusCheckBox", "unnamed": 1, "visible": True} +pinMessagesToggle_StatusCheckBox = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "id": "pinMessagesToggle", "type": "StatusCheckBox", "unnamed": 1, "visible": True} +createCommunityNextBtn_StatusButton = {"container": statusDesktop_mainWindow_overlay, "objectName": "createCommunityNextBtn", "type": "StatusButton", "visible": True} +createCommunityIntroMessageInput_TextEdit = {"container": statusDesktop_mainWindow_overlay, "objectName": "createCommunityIntroMessageInput", "type": "TextEdit", "visible": True} +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: +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} + +""" Common """ + +# Select Color Popup +communitySettings_ColorPanel_HexColor_Input = {"container": statusDesktop_mainWindow_overlay, "objectName": "communityColorPanelHexInput", "type": "TextEdit", "visible": True} +communitySettings_SaveColor_Button = {"container": statusDesktop_mainWindow_overlay, "objectName": "communityColorPanelSelectColorButton", "type": "StatusButton", "visible": True} + +# Select Tag Popup +o_StatusCommunityTag = {"container": statusDesktop_mainWindow_overlay, "type": "StatusCommunityTag", "unnamed": 1, "visible": True} +confirm_Community_Tags_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "type": "StatusButton", "unnamed": 1, "visible": True} + +# Signing phrase popup +signPhrase_Ok_Button = {"container": statusDesktop_mainWindow, "type": "StatusFlatButton", "objectName": "signPhraseModalOkButton", "visible": True} + +# Remove account popup: +mainWallet_Remove_Account_Popup_Account_Notification = {"container": statusDesktop_mainWindow, "objectName": "RemoveAccountPopup-Notification", "type": "StatusBaseText", "visible": True} +mainWallet_Remove_Account_Popup_Account_Path_Component = {"container": statusDesktop_mainWindow, "objectName": "RemoveAccountPopup-DerivationPath", "type": "StatusInput", "visible": True} +mainWallet_Remove_Account_Popup_Account_Path = {"container": mainWallet_Remove_Account_Popup_Account_Path_Component, "type": "TextEdit", "unnamed": 1, "visible": True} +mainWallet_Remove_Account_Popup_HavePenPaperCheckBox = {"checkable": True, "container": statusDesktop_mainWindow, "objectName": "RemoveAccountPopup-HavePenPaper", "type": "StatusCheckBox", "visible": True} +mainWallet_Remove_Account_Popup_ConfirmButton = {"container": statusDesktop_mainWindow, "objectName": "RemoveAccountPopup-ConfirmButton", "type": "StatusButton", "visible": True} +mainWallet_Remove_Account_Popup_CancelButton = {"container": statusDesktop_mainWindow, "objectName": "RemoveAccountPopup-CancelButton", "type": "StatusFlatButton", "visible": True} + +# Add saved address popup +mainWallet_Saved_Addreses_Popup_Name_Input = {"container": statusDesktop_mainWindow, "objectName": "savedAddressNameInput", "type": "TextEdit"} +mainWallet_Saved_Addreses_Popup_Address_Input = {"container": statusDesktop_mainWindow, "objectName": "savedAddressAddressInput", "type": "StatusInput"} +mainWallet_Saved_Addreses_Popup_Address_Input_Edit = {"container": statusDesktop_mainWindow, "objectName": "savedAddressAddressInputEdit", "type": "TextEdit"} +mainWallet_Saved_Addreses_Popup_Address_Add_Button = {"container": statusDesktop_mainWindow, "objectName": "addSavedAddress", "type": "StatusButton"} +mainWallet_Saved_Addreses_Popup_Add_Network_Selector = {"container": statusDesktop_mainWindow, "objectName": "addSavedAddressNetworkSelector", "type": "StatusNetworkSelector", "visible": True} +mainWallet_Saved_Addreses_Popup_Add_Network_Button = {"container": statusDesktop_mainWindow_overlay, "objectName": "addNetworkTagItemButton", "type": "StatusRoundButton", "visible": True} +mainWallet_Saved_Addreses_Popup_Add_Network_Selector_Tag = {"container": statusDesktop_mainWindow_overlay, "objectName": "networkSelectorTag", "type": "StatusNetworkListItemTag"} +mainWallet_Saved_Addresses_Popup_Add_Network_Selector_Mainnet_checkbox = {"container": statusDesktop_mainWindow_overlay, "objectName": "networkSelectionCheckbox_Mainnet", "type": "StatusCheckBox", "visible": True} +mainWallet_Saved_Addresses_Popup_Add_Network_Selector_Optimism_checkbox = {"container": statusDesktop_mainWindow_overlay, "objectName": "networkSelectionCheckbox_Optimism", "type": "StatusCheckBox", "visible": True} +mainWallet_Saved_Addresses_Popup_Add_Network_Selector_Arbitrum_checkbox = {"container": statusDesktop_mainWindow_overlay, "objectName": "networkSelectionCheckbox_Arbitrum", "type": "StatusCheckBox", "visible": True} +mainWallet_Saved_Addresses_Popup_Network_Selector_Mainnet_network_tag = {"container": statusDesktop_mainWindow_overlay, "objectName": "networkTagRectangle_Mainnet", "type": "Rectangle", "visible": True} +mainWallet_Saved_Addresses_Popup_Network_Selector_Optimism_network_tag = {"container": statusDesktop_mainWindow_overlay, "objectName": "networkTagRectangle_Optimism", "type": "Rectangle", "visible": True} +mainWallet_Saved_Addresses_Popup_Network_Selector_Arbitrum_network_tag = {"container": statusDesktop_mainWindow_overlay, "objectName": "networkTagRectangle_Arbitrum", "type": "Rectangle", "visible": True} + +# Context Menu +contextMenu_PopupItem = {"container": statusDesktop_mainWindow_overlay, "type": "PopupItem", "unnamed": 1, "visible": True} +contextMenuItem = {"container": statusDesktop_mainWindow_overlay, "type": "StatusBaseText", "unnamed": 1, "visible": True} + +# Confirmation Popup +confirmButton = {"container": statusDesktop_mainWindow_overlay, "objectName": RegularExpression("confirm*"), "type": "StatusButton"} + +# Picture Edit Popup +o_StatusSlider = {"container": statusDesktop_mainWindow_overlay, "type": "StatusSlider", "unnamed": 1, "visible": True} +cropSpaceItem_Item = {"container": statusDesktop_mainWindow_overlay, "id": "cropSpaceItem", "type": "Item", "unnamed": 1, "visible": True} +make_picture_StatusButton = {"checkable": False, "container": statusDesktop_mainWindow_overlay, "objectName": "imageCropperAcceptButton", "type": "StatusButton", "visible": True} +o_DropShadow = {"container": statusDesktop_mainWindow_overlay, "type": "DropShadow", "unnamed": 1, "visible": True} + +# Emoji Popup +mainWallet_AddEditAccountPopup_AccountEmojiSearchBox = {"container": statusDesktop_mainWindow, "objectName": "StatusEmojiPopup_searchBox", "type": "TextEdit", "visible": True} +mainWallet_AddEditAccountPopup_AccountEmoji = {"container": statusDesktop_mainWindow, "type": "StatusEmoji", "visible": True} + +# Delete Popup +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} + +# Shared Popup +sharedPopup_Popup_Content = {"container": statusDesktop_mainWindow, "objectName": "KeycardSharedPopupContent", "type": "Item"} +sharedPopup_Password_Input = {"container": sharedPopup_Popup_Content, "objectName": "keycardPasswordInput", "type": "TextField"} +sharedPopup_Primary_Button = {"container": statusDesktop_mainWindow, "objectName": "PrimaryButton", "type": "StatusButton", "visible": True, "enabled": True} + +# Wallet Account Popup +mainWallet_AddEditAccountPopup_derivationPath = {"container": statusDesktop_mainWindow, "objectName": RegularExpression("AddAccountPopup-PreDefinedDerivationPath*"), "type": "StatusListItem", "visible": True} +mainWallet_Address_Panel = {"container": statusDesktop_mainWindow, "objectName": "addressPanel", "type": "StatusAddressPanel", "visible": True} +addAccountPopup_GeneratedAddress = {"container": statusDesktop_mainWindow_overlay_popup2, "type": "Rectangle", "visible": True} +address_0x_StatusBaseText = {"container": statusDesktop_mainWindow_overlay_popup2, "text": RegularExpression("0x*"), "type": "StatusBaseText", "unnamed": 1, "visible": True} +addAccountPopup_GeneratedAddressesListPageIndicatior_StatusPageIndicator = {"container": statusDesktop_mainWindow_overlay_popup2, "objectName": "AddAccountPopup-GeneratedAddressesListPageIndicatior", "type": "StatusPageIndicator", "visible": True} +page_StatusBaseButton = {"checkable": False, "container": addAccountPopup_GeneratedAddressesListPageIndicatior_StatusPageIndicator, "objectName": RegularExpression("Page-*"), "type": "StatusBaseButton", "visible": True} + +# Add/Edit account popup: +grid_Grid = {"container": statusDesktop_mainWindow_overlay, "id": "grid", "type": "Grid", "unnamed": 1, "visible": True} +color_StatusColorRadioButton = {"checkable": True, "container": statusDesktop_mainWindow_overlay, "type": "StatusColorRadioButton", "unnamed": 1, "visible": True} + +mainWallet_AddEditAccountPopup_Content = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-Content", "type": "Item", "visible": True} +mainWallet_AddEditAccountPopup_PrimaryButton = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-PrimaryButton", "type": "StatusButton", "visible": True} +mainWallet_AddEditAccountPopup_BackButton = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-BackButton", "type": "StatusBackButton", "visible": True} +mainWallet_AddEditAccountPopup_AccountNameComponent = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-AccountName", "type": "StatusInput", "visible": True} +mainWallet_AddEditAccountPopup_AccountName = {"container": mainWallet_AddEditAccountPopup_AccountNameComponent, "type": "TextEdit", "unnamed": 1, "visible": True} +mainWallet_AddEditAccountPopup_AccountColorComponent = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-AccountColor", "type": "StatusColorSelectorGrid", "visible": True} +mainWallet_AddEditAccountPopup_AccountColorSelector = {"container": mainWallet_AddEditAccountPopup_AccountColorComponent, "type": "Repeater", "objectName": "statusColorRepeater", "visible": True, "enabled": True} +mainWallet_AddEditAccountPopup_AccountEmojiPopupButton = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-AccountEmoji", "type": "StatusFlatRoundButton", "visible": True} +mainWallet_AddEditAccountPopup_SelectedOrigin = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-SelectedOrigin", "type": "StatusListItem", "visible": True} +mainWallet_AddEditAccountPopup_OriginOption_Placeholder = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-OriginOption-%NAME%", "type": "StatusListItem", "visible": True} +mainWallet_AddEditAccountPopup_OriginOptionNewMasterKey = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-OriginOption-LABEL-OPTION-ADD-NEW-MASTER-KEY", "type": "StatusListItem", "visible": True} +addAccountPopup_OriginOption_StatusListItem = {"container": statusDesktop_mainWindow_overlay, "type": "StatusListItem", "visible": True} + +mainWallet_AddEditAccountPopup_OriginOptionWatchOnlyAcc = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-OriginOption-LABEL-OPTION-ADD-WATCH-ONLY-ACC", "type": "StatusListItem", "visible": True} +mainWallet_AddEditAccountPopup_AccountWatchOnlyAddressComponent = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-WatchOnlyAddress", "type": "StatusInput", "visible": True} +mainWallet_AddEditAccountPopup_AccountWatchOnlyAddress = {"container": mainWallet_AddEditAccountPopup_AccountWatchOnlyAddressComponent, "type": "TextEdit", "unnamed": 1, "visible": True} +mainWallet_AddEditAccountPopup_EditDerivationPathButton = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-EditDerivationPath", "type": "StatusButton", "visible": True} +mainWallet_AddEditAccountPopup_ResetDerivationPathButton = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-ResetDerivationPath", "type": "StatusLinkText", "enabled": True, "visible": True} +mainWallet_AddEditAccountPopup_DerivationPathInputComponent = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-DerivationPathInput", "type": "DerivationPathInput", "visible": True} +mainWallet_AddEditAccountPopup_DerivationPathInput = {"container": mainWallet_AddEditAccountPopup_DerivationPathInputComponent, "type": "TextEdit", "unnamed": 1, "visible": True} +mainWallet_AddEditAccountPopup_PreDefinedDerivationPathsButton = {"container": mainWallet_AddEditAccountPopup_DerivationPathInputComponent, "objectName": "chevron-down-icon", "type": "StatusIcon", "visible": True} +mainWallet_AddEditAccountPopup_GeneratedAddressComponent = {"container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-GeneratedAddress", "type": "StatusListItem", "visible": True} +mainWallet_AddEditAccountPopup_NonEthDerivationPathCheckBox = {"checkable": True, "container": statusDesktop_mainWindow, "objectName": "AddAccountPopup-ConfirmAddingNonEthDerivationPath", "type": "StatusCheckBox", "visible": True} +mainWallet_AddEditAccountPopup_MasterKey_ImportPrivateKeyOption = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-ImportPrivateKey", "type": "StatusListItem", "visible": True} +mainWallet_AddEditAccountPopup_PrivateKey = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-PrivateKeyInput", "type": "StatusPasswordInput", "visible": True} +mainWallet_AddEditAccountPopup_PrivateKeyNameComponent = {"container": mainWallet_AddEditAccountPopup_Content, "objectName": "AddAccountPopup-PrivateKeyName", "type": "StatusInput", "visible": True} +mainWallet_AddEditAccountPopup_PrivateKeyName = {"container": mainWallet_AddEditAccountPopup_PrivateKeyNameComponent, "type": "TextEdit", "unnamed": 1, "visible": True} diff --git a/test/e2e/gui/objects_map/main_names.py b/test/e2e/gui/objects_map/main_names.py new file mode 100644 index 0000000000..82d960e808 --- /dev/null +++ b/test/e2e/gui/objects_map/main_names.py @@ -0,0 +1,21 @@ +statusDesktop_mainWindow = {"name": "mainWindow", "type": "StatusWindow", "visible": True} +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} +splashScreen = {"container": statusDesktop_mainWindow, "objectName": "splashScreen", "type": "DidYouKnowSplashScreen"} + +# Main right panel +mainWindow_RighPanel = {"container": statusDesktop_mainWindow, "type": "ColumnLayout", "objectName": "mainRightView", "visible": True} + +# Navigation Panel +mainWindow_StatusAppNavBar = {"container": statusDesktop_mainWindow, "type": "StatusAppNavBar", "unnamed": 1, "visible": True} +messages_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWindow_StatusAppNavBar, "objectName": "Messages-navbar", "type": "StatusNavBarTabButton", "visible": True} +communities_Portal_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWindow_StatusAppNavBar, "objectName": "Communities Portal-navbar", "type": "StatusNavBarTabButton", "visible": True} +wallet_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWindow_StatusAppNavBar, "objectName": "Wallet-navbar", "type": "StatusNavBarTabButton", "visible": True} +settings_navbar_StatusNavBarTabButton = {"checkable": True, "container": mainWindow_StatusAppNavBar, "objectName": "Settings-navbar", "type": "StatusNavBarTabButton", "visible": True} +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} + +# Banners +secureYourSeedPhraseBanner_ModuleWarning = {"container": statusDesktop_mainWindow, "objectName": "secureYourSeedPhraseBanner", "type": "ModuleWarning", "visible": True} diff --git a/test/e2e/gui/objects_map/onboarding_names.py b/test/e2e/gui/objects_map/onboarding_names.py new file mode 100755 index 0000000000..17c32a489d --- /dev/null +++ b/test/e2e/gui/objects_map/onboarding_names.py @@ -0,0 +1,86 @@ +from . main_names import * + +mainWindow_onboardingBackButton_StatusRoundButton = {"container": statusDesktop_mainWindow, "objectName": "onboardingBackButton", "type": "StatusRoundButton", "visible": True} + +# Allow Notification View +mainWindow_AllowNotificationsView = {"container": statusDesktop_mainWindow, "type": "AllowNotificationsView", "unnamed": 1, "visible": True} +mainWindow_allowNotificationsOnboardingOkButton = {"container": mainWindow_AllowNotificationsView, "objectName": "allowNotificationsOnboardingOkButton", "type": "StatusButton", "visible": True} + +# Welcome View +mainWindow_WelcomeView = {"container": statusDesktop_mainWindow, "type": "WelcomeView", "unnamed": 1, "visible": True} +mainWindow_I_am_new_to_Status_StatusBaseText = {"container": mainWindow_WelcomeView, "objectName": "welcomeViewIAmNewToStatusButton", "type": "StatusButton"} +mainWindow_I_already_use_Status_StatusBaseText = {"container": mainWindow_WelcomeView, "objectName": "welcomeViewIAlreadyUseStatusButton", "type": "StatusFlatButton", "visible": True} + +# Get Keys View +mainWindow_KeysMainView = {"container": statusDesktop_mainWindow, "type": "KeysMainView", "unnamed": 1, "visible": True} +mainWindow_Generate_new_keys_StatusButton = {"checkable": False, "container": mainWindow_KeysMainView, "objectName": "keysMainViewPrimaryActionButton", "type": "StatusButton", "visible": True} +mainWindow_Generate_keys_for_new_Keycard_StatusBaseText = {"container": mainWindow_KeysMainView, "id": "button2", + "type": "StatusBaseText", "unnamed": 1, "visible": True} +mainWindow_Import_seed_phrase = {"container": mainWindow_KeysMainView, "id": "button3", "type": "Row", "unnamed": 1, + "visible": True} + +# Import Seed Phrase View +keysMainView_PrimaryAction_Button = {"container": statusDesktop_mainWindow, + "objectName": "keysMainViewPrimaryActionButton", "type": "StatusButton"} + +# Seed Phrase Input View +mainWindow_SeedPhraseInputView = {"container": statusDesktop_mainWindow, "type": "SeedPhraseInputView", "unnamed": 1, + "visible": True} +switchTabBar_12_words_Button = {"container": mainWindow_SeedPhraseInputView, "objectName": "12SeedButton", + "type": "StatusSwitchTabButton"} +switchTabBar_18_words_Button = {"container": mainWindow_SeedPhraseInputView, "objectName": "18SeedButton", + "type": "StatusSwitchTabButton"} +switchTabBar_24_words_Button = {"container": mainWindow_SeedPhraseInputView, "objectName": "24SeedButton", + "type": "StatusSwitchTabButton"} +mainWindow_statusSeedPhraseInputField_TextEdit = {"container": mainWindow_SeedPhraseInputView, + "objectName": "statusSeedPhraseInputField", "type": "TextEdit", + "visible": True} +mainWindow_Import_StatusButton = {"checkable": False, "container": mainWindow_SeedPhraseInputView, + "objectName": "seedPhraseViewSubmitButton", "text": "Import", "type": "StatusButton", + "visible": True} + +# Keycard Init View +mainWindow_KeycardInitView = {"container": statusDesktop_mainWindow, "type": "KeycardInitView", "unnamed": 1, + "visible": True} +mainWindow_Plug_in_Keycard_reader_StatusBaseText = {"container": mainWindow_KeycardInitView, "type": "StatusBaseText", + "unnamed": 1, "visible": True} + +# Your Profile View +mainWindow_InsertDetailsView = {"container": statusDesktop_mainWindow, "type": "InsertDetailsView", "unnamed": 1, "visible": True} +updatePicButton_StatusRoundButton = {"container": mainWindow_InsertDetailsView, "id": "updatePicButton", "type": "StatusRoundButton", "unnamed": 1, "visible": True} +mainWindow_CanvasItem = {"container": mainWindow_InsertDetailsView, "type": "CanvasItem", "unnamed": 1, "visible": True} +mainWindow_Next_StatusButton = {"container": statusDesktop_mainWindow, "objectName": "onboardingDetailsViewNextButton", "type": "StatusButton", "visible": True, "enabled": True} +mainWindow_inputLayout_ColumnLayout = {"container": statusDesktop_mainWindow, "id": "inputLayout", "type": "ColumnLayout", "unnamed": 1, "visible": True} +mainWindow_statusBaseInput_StatusBaseInput = {"container": mainWindow_inputLayout_ColumnLayout, "objectName": "onboardingDisplayNameInput", "type": "TextEdit", "visible": True} +mainWindow_errorMessage_StatusBaseText = {"container": mainWindow_inputLayout_ColumnLayout, "type": "StatusBaseText", "unnamed": 1, "visible": True} + +# Your emojihash and identicon ring +mainWindow_welcomeScreenUserProfileImage_StatusSmartIdenticon = {"container": mainWindow_InsertDetailsView, "objectName": "welcomeScreenUserProfileImage", "type": "StatusSmartIdenticon", "visible": True} +mainWindow_insertDetailsViewChatKeyTxt_StyledText = {"container": mainWindow_InsertDetailsView, "objectName": "insertDetailsViewChatKeyTxt", "type": "StyledText", "visible": True} +mainWindow_EmojiHash = {"container": mainWindow_InsertDetailsView, "type": "EmojiHash", "unnamed": 1, "visible": True} +mainWindow_userImageCopy_StatusSmartIdenticon = {"container": mainWindow_InsertDetailsView, "id": "userImageCopy", "type": "StatusSmartIdenticon", "unnamed": 1, "visible": True} + + +# Create Password View +mainWindow_CreatePasswordView = {"container": statusDesktop_mainWindow, "type": "CreatePasswordView", "unnamed": 1, "visible": True} +mainWindow_passwordViewNewPassword = {"container": mainWindow_CreatePasswordView, "echoMode": 2, "objectName": "passwordViewNewPassword", "type": "StatusPasswordInput", "visible": True} +mainWindow_passwordViewNewPasswordConfirm = {"container": mainWindow_CreatePasswordView, "echoMode": 2, "objectName": "passwordViewNewPasswordConfirm", "type": "StatusPasswordInput", "visible": True} +mainWindow_Create_password_StatusButton = {"checkable": False, "container": mainWindow_CreatePasswordView, "objectName": "onboardingCreatePasswordButton", "type": "StatusButton", "visible": True, "enabled": True} + +# Confirm Password View +mainWindow_ConfirmPasswordView = {"container": statusDesktop_mainWindow, "type": "ConfirmPasswordView", "unnamed": 1,"visible": True} +mainWindow_confirmAgainPasswordInput = {"container": mainWindow_ConfirmPasswordView, "objectName": "confirmAgainPasswordInput", "type": "StatusPasswordInput", "visible": True} +mainWindow_Finalise_Status_Password_Creation_StatusButton = {"checkable": False, "container": mainWindow_ConfirmPasswordView, "objectName": "confirmPswSubmitBtn", "type": "StatusButton", "visible": True} + +# Login View +mainWindow_LoginView = {"container": statusDesktop_mainWindow, "type": "LoginView", "unnamed": 1, "visible": True} +loginView_submitBtn = {"container": mainWindow_LoginView, "type": "StatusRoundButton", "visible": True} +loginView_passwordInput = {"container": mainWindow_LoginView, "objectName": "loginPasswordInput", "type": "StyledTextField"} +loginView_currentUserNameLabel = {"container": mainWindow_LoginView, "objectName": "currentUserNameLabel", "type": "StatusBaseText"} +loginView_changeAccountBtn = {"container": mainWindow_LoginView, "objectName": "loginChangeAccountButton", "type": "StatusFlatRoundButton"} +accountsView_accountListPanel = {"container": statusDesktop_mainWindow, "objectName": "LoginView_AccountsRepeater", "type": "Repeater", "visible": True} + + +# Touch ID Auth View +mainWindow_TouchIDAuthView = {"container": statusDesktop_mainWindow, "type": "TouchIDAuthView", "unnamed": 1, "visible": True} +mainWindow_touchIdIPreferToUseMyPasswordText = {"container": statusDesktop_mainWindow, "objectName": "touchIdIPreferToUseMyPasswordText", "type": "StatusBaseText"} diff --git a/test/e2e/gui/objects_map/os_names.py b/test/e2e/gui/objects_map/os_names.py new file mode 100644 index 0000000000..cc02ef6588 --- /dev/null +++ b/test/e2e/gui/objects_map/os_names.py @@ -0,0 +1,25 @@ +""" MAC """ +# Open Files Dialog +mainWindow = {"AXRole": "AXWindow", "AXMain": True} +openFileDialog = {"container": mainWindow, "AXRole": "AXSheet", "AXIdentifier": "open-panel"} +openButton = {"container": openFileDialog, "AXRole": "AXButton", "AXIdentifier": "OKButton"} + +# Go To Dialog +goToDialog = {"container": openFileDialog, "AXRole": "AXSheet", "AXIdentifier": "GoToWindow"} +pathTextField = {"container": goToDialog, "AXRole": "AXTextField", "AXIdentifier": "PathTextField"} + +""" WINDOWS """ +# Open File Dialog +file_Dialog = {"type": "Dialog"} +choose_file_Edit = {"container": file_Dialog, "type": "Edit"} +choose_Open_Button = {"container": file_Dialog, "text": "Open", "type": "Button"} + +""" LINUX """ +# Open File Dialog +# Select Image Dialog +please_choose_an_image_QQuickWindow = {"type": "QQuickWindow", "unnamed": 1, "visible": True} +please_choose_an_image_Open_Button = {"container": please_choose_an_image_QQuickWindow, "id": "okButton", "type": "Button", "unnamed": 1, "visible": True} +please_choose_an_image_titleBar_ToolBar = {"container": please_choose_an_image_QQuickWindow, "id": "titleBar", "type": "ToolBar", "unnamed": 1, "visible": True} +titleBar_textInput_TextInputWithHandles = {"container": please_choose_an_image_QQuickWindow, "echoMode": 0, "id": "textInput", "type": "TextInputWithHandles", "unnamed": 1, "visible": True} +view_listView_ListView = {"container": please_choose_an_image_QQuickWindow, "id": "listView", "type": "ListView", "unnamed": 1, "visible": True} +rowitem_Text = {"container": view_listView_ListView, "type": "Text", "unnamed": 1, "visible": True} diff --git a/test/e2e/gui/objects_map/settings_names.py b/test/e2e/gui/objects_map/settings_names.py new file mode 100644 index 0000000000..2ac392013e --- /dev/null +++ b/test/e2e/gui/objects_map/settings_names.py @@ -0,0 +1,22 @@ +from gui.objects_map.main_names import statusDesktop_mainWindow + +mainWindow_ProfileLayout = {"container": statusDesktop_mainWindow, "type": "ProfileLayout", "unnamed": 1, "visible": True} +mainWindow_StatusSectionLayout_ContentItem = {"container": mainWindow_ProfileLayout, "objectName": "StatusSectionLayout", "type": "ContentItem", "visible": True} + +# Left Panel +mainWindow_LeftTabView = {"container": mainWindow_StatusSectionLayout_ContentItem, "type": "LeftTabView", "unnamed": 1, "visible": True} +mainWindow_Settings_StatusNavigationPanelHeadline = {"container": mainWindow_LeftTabView, "type": "StatusNavigationPanelHeadline", "unnamed": 1, "visible": True} +mainWindow_scrollView_StatusScrollView = {"container": mainWindow_LeftTabView, "id": "scrollView", "type": "StatusScrollView", "unnamed": 1, "visible": True} +scrollView_AppMenuItem_StatusNavigationListItem = {"container": mainWindow_scrollView_StatusScrollView, "type": "StatusNavigationListItem", "visible": True} + +# Communities View +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} + +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} + diff --git a/test/e2e/gui/objects_map/wallet_names.py b/test/e2e/gui/objects_map/wallet_names.py new file mode 100644 index 0000000000..7001ec3081 --- /dev/null +++ b/test/e2e/gui/objects_map/wallet_names.py @@ -0,0 +1,24 @@ +from objectmaphelper import * +from . main_names import * + +mainWindow_WalletLayout = {"container": statusDesktop_mainWindow, "type": "WalletLayout", "unnamed": 1, "visible": True} + +# Left Wallet Panel +mainWallet_LeftTab = {"container": statusDesktop_mainWindow, "objectName": "walletLeftTab", "type": "LeftTabView", "visible": True} +mainWallet_Saved_Addresses_Button = {"container": mainWindow_RighPanel, "objectName": "savedAddressesBtn", "type": "StatusFlatButton"} +walletAccounts_StatusListView = {"container": statusDesktop_mainWindow, "objectName": "walletAccountsListView", "type": "StatusListView", "visible": True} +mainWallet_All_Accounts_Button = {"container": walletAccounts_StatusListView, "objectName": "allAccountsBtn", "type": "Button", "visible": True} +mainWallet_Add_Account_Button = {"container": statusDesktop_mainWindow, "objectName": "addAccountButton", "type": "StatusRoundButton", "visible": True} +walletAccount_StatusListItem = {"container": walletAccounts_StatusListView, "objectName": RegularExpression("walletAccount*"), "type": "StatusListItem", "visible": True} + +# Saved Address View +mainWindow_SavedAddressesView = {"container": mainWindow_WalletLayout, "type": "SavedAddressesView", "unnamed": 1, "visible": True} +mainWallet_Saved_Addreses_Add_Buttton = {"container": mainWindow_SavedAddressesView, "objectName": "addNewAddressBtn", "type": "StatusButton"} +mainWallet_Saved_Addreses_List = {"container": mainWindow_SavedAddressesView, "objectName": "SavedAddressesView_savedAddresses", "type": "StatusListView"} +savedAddressView_Delegate = {"container": mainWallet_Saved_Addreses_List, "objectName": RegularExpression("savedAddressView_Delegate*"), "type": "SavedAddressesDelegate", "visible": True} +send_StatusRoundButton = {"container": "", "type": "StatusRoundButton", "unnamed": 1, "visible": True} +savedAddressView_Delegate_menuButton = {"container": mainWindow_SavedAddressesView, "objectName": RegularExpression("savedAddressView_Delegate_menuButton*"), "type": "StatusRoundButton", "visible": True} + +# Wallet Account View +mainWindow_StatusSectionLayout_ContentItem = {"container": statusDesktop_mainWindow, "objectName": "StatusSectionLayout", "type": "ContentItem", "visible": True} +mainWallet_Account_Name = {"container": mainWindow_StatusSectionLayout_ContentItem, "objectName": "accountName", "type": "StatusBaseText", "visible": True} diff --git a/test/e2e/gui/screens/__init__.py b/test/e2e/gui/screens/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/gui/screens/community.py b/test/e2e/gui/screens/community.py new file mode 100644 index 0000000000..9ec2042317 --- /dev/null +++ b/test/e2e/gui/screens/community.py @@ -0,0 +1,190 @@ +import typing + +import allure +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.delete_popup import DeletePopup +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 scripts.tools import image +from scripts.tools.image import Image + + +class CommunityScreen(QObject): + + def __init__(self): + super().__init__('mainWindow_communityLoader_Loader') + self.left_panel = LeftPanel() + self.tool_bar = ToolBar() + self.chat = Chat() + + @allure.step('Create channel') + def create_channel(self, name: str, description: str, emoji: str = None): + self.left_panel.open_create_channel_popup().create(name, description, emoji) + + @allure.step('Create channel') + def edit_channel(self, channel, name: str, description: str, emoji: str = None): + self.left_panel.select_channel(channel) + self.tool_bar.open_edit_channel_popup().edit(name, description, emoji) + + @allure.step('Delete channel') + def delete_channel(self, name: str): + self.left_panel.select_channel(name) + self.tool_bar.open_delete_channel_popup().delete() + + @allure.step('Verify channel') + def verify_channel( + self, name: str, description: str, icon_in_list: str, icon_in_toolbar: str, icon_in_chat: str): + with step('Channel is correct in channels list'): + channel = self.left_panel.get_channel_parameters(name) + image.compare(channel.image, icon_in_list, timout_sec=5) + assert channel.name == name + assert channel.selected + + with step('Channel is correct in community toolbar'): + assert self.tool_bar.channel_name == name + assert self.tool_bar.channel_description == description + image.compare(self.tool_bar.channel_icon, icon_in_toolbar, timout_sec=5) + + with step('Verify channel in chat'): + assert self.chat.channel_name == name + image.compare(self.chat.channel_icon, icon_in_chat, timout_sec=5) + + +class ToolBar(QObject): + + def __init__(self): + super().__init__('mainWindow_statusToolBar_StatusToolBar') + self._more_options_button = Button('statusToolBar_chatToolbarMoreOptionsButton') + self._options_list = List('o_StatusListView') + self._edit_channel_context_item = QObject('edit_Channel_StatusMenuItem') + self._channel_icon = QObject('statusToolBar_statusSmartIdenticonLetter_StatusLetterIdenticon') + self._channel_name = TextLabel('statusToolBar_statusChatInfoButtonNameText_TruncatedTextWithTooltip') + self._channel_description = TextLabel('statusToolBar_TruncatedTextWithTooltip') + self._delete_channel_context_item = QObject('delete_Channel_StatusMenuItem') + + @property + @allure.step('Get channel icon') + def channel_icon(self) -> Image: + return self._channel_icon.image + + @property + @allure.step('Get channel name') + def channel_name(self) -> str: + return self._channel_name.text + + @property + @allure.step('Get channel description') + def channel_description(self) -> str: + return self._channel_description.text + + @allure.step('Open edit channel popup') + def open_edit_channel_popup(self): + self._more_options_button.click() + self._edit_channel_context_item.click() + return EditChannelPopup().wait_until_appears() + + @allure.step('Open delete channel popup') + def open_delete_channel_popup(self): + self._more_options_button.click() + self._delete_channel_context_item.click() + return DeletePopup().wait_until_appears() + + +class LeftPanel(QObject): + + def __init__(self): + super().__init__('mainWindow_communityColumnView_CommunityColumnView') + self._community_info_button = Button('mainWindow_communityHeaderButton_StatusChatInfoButton') + self._community_logo = QObject('mainWindow_identicon_StatusSmartIdenticon') + self._name_text_label = TextLabel('mainWindow_statusChatInfoButtonNameText_TruncatedTextWithTooltip') + self._members_text_label = TextLabel('mainWindow_Members_TruncatedTextWithTooltip') + self._channel_list_item = QObject('channel_listItem') + 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') + + @property + @allure.step('Get community logo') + def logo(self) -> Image: + return self._community_logo.image + + @property + @allure.step('Get community name') + def name(self) -> str: + return self._name_text_label.text + + @property + @allure.step('Get community members label') + def members(self) -> str: + return self._members_text_label.text + + @property + @allure.step('Get channels') + def channels(self) -> typing.List[UserChannel]: + channels_list = [] + for obj in driver.findAllObjects(self._channel_list_item.real_name): + container = driver.objectMap.realName(obj) + self._channel_icon_template.real_name['container'] = container + channels_list.append(UserChannel( + str(obj.objectName), + self._channel_icon_template.image, + obj.item.selected + )) + return channels_list + + @allure.step('Get channel params') + def get_channel_parameters(self, name) -> UserChannel: + for channal in self.channels: + if channal.name == name: + return channal + raise LookupError(f'Channel not found in {self.channels}') + + @allure.step('Open community settings') + def open_community_settings(self): + self._community_info_button.click() + return CommunitySettingsScreen().wait_until_appears() + + @allure.step('Open create channel popup') + def open_create_channel_popup(self) -> NewChannelPopup: + self._channel_or_category_button.click() + self._create_channel_menu_item.click() + return NewChannelPopup().wait_until_appears() + + @allure.step('Select channel') + def select_channel(self, name: str): + for obj in driver.findAllObjects(self._channel_list_item.real_name): + if str(obj.objectName) == name: + driver.mouseClick(obj) + return + raise LookupError('Channel not found') + + +class Chat(QObject): + + def __init__(self): + super().__init__('mainWindow_ChatColumnView') + self._channel_icon = QObject('chatMessageViewDelegate_channelIdentifierSmartIdenticon_StatusSmartIdenticon') + self._channel_name_label = TextLabel('chatMessageViewDelegate_channelIdentifierNameText_StyledText') + self._channel_welcome_label = TextLabel('chatMessageViewDelegate_Welcome') + + @property + @allure.step('Get channel icon') + def channel_icon(self) -> Image: + return self._channel_icon.image + + @property + @allure.step('Get channel name') + def channel_name(self) -> str: + return self._channel_name_label.text + + @property + @allure.step('Get channel welcome note') + def channel_welcome_note(self) -> str: + return self._channel_welcome_label.text diff --git a/test/e2e/gui/screens/community_portal.py b/test/e2e/gui/screens/community_portal.py new file mode 100644 index 0000000000..a28c6aa0ac --- /dev/null +++ b/test/e2e/gui/screens/community_portal.py @@ -0,0 +1,18 @@ +import allure + +from gui.components.community.create_community_popups import CreateCommunitiesBanner +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject +from gui.screens.community import CommunityScreen + + +class CommunitiesPortal(QObject): + + def __init__(self): + super().__init__('mainWindow_communitiesPortalLayout_CommunitiesPortalLayout') + self._create_community_button = Button('mainWindow_Create_New_Community_StatusButton') + + @allure.step('Open create community popup') + def open_create_community_popup(self) -> CommunityScreen: + self._create_community_button.click() + return CreateCommunitiesBanner().wait_until_appears().open_create_community_popup() diff --git a/test/e2e/gui/screens/community_settings.py b/test/e2e/gui/screens/community_settings.py new file mode 100644 index 0000000000..45db82f04d --- /dev/null +++ b/test/e2e/gui/screens/community_settings.py @@ -0,0 +1,206 @@ +import typing + +import allure + +import driver +from gui.components.color_select_popup import ColorSelectPopup +from gui.components.community.tags_select_popup import TagsSelectPopup +from gui.components.os.open_file_dialogs import OpenFileDialog +from gui.components.picture_edit_popup import PictureEditPopup +from gui.elements.qt.button import Button +from gui.elements.qt.check_box import CheckBox +from gui.elements.qt.object import QObject +from gui.elements.qt.scroll import Scroll +from gui.elements.qt.text_edit import TextEdit +from gui.elements.qt.text_label import TextLabel +from scripts.tools.image import Image + + +class CommunitySettingsScreen(QObject): + + def __init__(self): + super().__init__('mainWindow_communityLoader_Loader') + self.left_panel = LeftPanel() + + +class LeftPanel(QObject): + + def __init__(self): + super().__init__('mainWindow_communityColumnView_CommunityColumnView') + self._back_to_community_button = Button('mainWindow_communitySettingsBackToCommunityButton_StatusBaseText') + self._overview_button = Button('overview_StatusNavigationListItem') + self._members_button = Button('members_StatusNavigationListItem') + + @allure.step('Open community main view') + def back_to_community(self): + self._back_to_community_button.click() + + @allure.step('Open community overview') + def open_overview(self) -> 'OverviewView': + if not self._overview_button.is_selected: + self._overview_button.click() + return OverviewView().wait_until_appears() + + @allure.step('Open community members') + def open_members(self) -> 'MembersView': + if not self._members_button.is_selected: + self._members_button.click() + return MembersView().wait_until_appears() + + +class OverviewView(QObject): + + def __init__(self): + super().__init__('mainWindow_OverviewSettingsPanel') + self._name_text_label = TextLabel('communityOverviewSettingsCommunityName_StatusBaseText') + self._description_text_label = TextLabel('communityOverviewSettingsCommunityDescription_StatusBaseText') + self._edit_button = Button('mainWindow_Edit_Community_StatusButton') + + @property + @allure.step('Get community name') + def name(self) -> str: + return self._name_text_label.text + + @property + @allure.step('Get community description') + def description(self) -> str: + return self._description_text_label.text + + @allure.step('Open edit community view') + def open_edit_community_view(self) -> 'EditCommunityView': + self._edit_button.click() + return EditCommunityView().wait_until_appears() + + +class EditCommunityView(QObject): + + def __init__(self): + super().__init__('mainWindow_communityEditPanelScrollView_EditSettingsPanel') + self._scroll = Scroll('communityEditPanelScrollView_Flickable') + self._name_text_edit = TextEdit('communityEditPanelScrollView_communityNameInput_TextEdit') + self._description_text_edit = TextEdit('communityEditPanelScrollView_communityDescriptionInput_TextEdit') + self._logo = QObject('communityEditPanelScrollView_image_StatusImage') + self._add_logo_button = Button('communityEditPanelScrollView_editButton_StatusRoundButton') + self._banner = QObject('communityEditPanelScrollView_image_StatusImage_2') + self._add_banner_button = Button('communityEditPanelScrollView_editButton_StatusRoundButton_2') + self._select_color_button = Button('communityEditPanelScrollView_StatusPickerButton') + self._choose_tag_button = Button('communityEditPanelScrollView_Choose_StatusPickerButton') + self._tag_item = QObject('communityEditPanelScrollView_StatusCommunityTag') + self._archive_support_checkbox = CheckBox('communityEditPanelScrollView_archiveSupportToggle_StatusCheckBox') + self._request_to_join_checkbox = CheckBox('communityEditPanelScrollView_requestToJoinToggle_StatusCheckBox') + self._pin_messages_checkbox = CheckBox('communityEditPanelScrollView_pinMessagesToggle_StatusCheckBox') + self._intro_text_edit = TextEdit('communityEditPanelScrollView_editCommunityIntroInput_TextEdit') + self._outro_text_edit = TextEdit('communityEditPanelScrollView_editCommunityOutroInput_TextEdit') + self._save_changes_button = Button('mainWindow_Save_changes_StatusButton') + + @property + @allure.step('Get community name') + def name(self) -> str: + return self._name_text_edit.text + + @name.setter + @allure.step('Set community name') + def name(self, value: str): + self._name_text_edit.text = value + + @property + @allure.step('Get community description') + def description(self) -> str: + return self._description_text_edit.text + + @description.setter + @allure.step('Set community description') + def description(self, value: str): + self._description_text_edit.text = value + + @property + @allure.step('Get community logo') + def logo(self) -> Image: + return self._logo.image + + @logo.setter + @allure.step('Set community description') + def logo(self, kwargs: dict): + self._add_logo_button.click() + OpenFileDialog().wait_until_appears().open_file(kwargs['fp']) + PictureEditPopup().wait_until_appears().make_picture(kwargs.get('zoom', None), kwargs.get('shift', None)) + + @property + @allure.step('Get community banner') + def banner(self) -> Image: + return self._banner.image + + @banner.setter + @allure.step('Set community description') + def banner(self, kwargs: dict): + self._add_banner_button.click() + OpenFileDialog().wait_until_appears().open_file(kwargs['fp']) + PictureEditPopup().wait_until_appears().make_picture(kwargs.get('zoom', None), kwargs.get('shift', None)) + + @property + @allure.step('Get community color') + def color(self) -> str: + return str(self._select_color_button.object.text) + + @color.setter + @allure.step('Set community color') + def color(self, value: str): + self._scroll.vertical_scroll_to(self._select_color_button) + self._select_color_button.click() + ColorSelectPopup().wait_until_appears().select_color(value) + + @property + @allure.step('Get community tags') + def tags(self): + self._scroll.vertical_scroll_to(self._choose_tag_button) + return [str(tag.title) for tag in driver.fiandAllObjects(self._tag_item.real_name)] + + @tags.setter + @allure.step('Set community tags') + def tags(self, values: typing.List[str]): + self._scroll.vertical_scroll_to(self._choose_tag_button) + self._choose_tag_button.click() + TagsSelectPopup().wait_until_appears().select_tags(values) + + @property + @allure.step('Get community intro') + def intro(self) -> str: + self._scroll.vertical_scroll_to(self._intro_text_edit) + return self._intro_text_edit.text + + @intro.setter + @allure.step('Set community intro') + def intro(self, value: str): + self._scroll.vertical_scroll_to(self._intro_text_edit) + self._intro_text_edit.text = value + + @property + @allure.step('Get community outro') + def outro(self) -> str: + self._scroll.vertical_scroll_to(self._outro_text_edit) + return self._outro_text_edit.text + + @outro.setter + @allure.step('Set community outro') + def outro(self, value: str): + self._scroll.vertical_scroll_to(self._outro_text_edit) + self._outro_text_edit.text = value + + @allure.step('Edit community') + def edit(self, kwargs): + for key in list(kwargs): + setattr(self, key, kwargs.get(key)) + self._save_changes_button.click() + self.wait_until_hidden() + + +class MembersView(QObject): + + def __init__(self): + super().__init__('mainWindow_MembersSettingsPanel') + self._member_list_item = QObject('memberItem_StatusMemberListItem') + + @property + @allure.step('Get community members') + def members(self) -> typing.List[str]: + return [str(member.title) for member in driver.findAllObjects(self._member_list_item.real_name)] diff --git a/test/e2e/gui/screens/onboarding.py b/test/e2e/gui/screens/onboarding.py new file mode 100755 index 0000000000..a31810b0bf --- /dev/null +++ b/test/e2e/gui/screens/onboarding.py @@ -0,0 +1,366 @@ +import logging +import time +import typing +from abc import abstractmethod + +import allure + +import configs +import constants.tesseract +import driver +from gui.components.os.open_file_dialogs import OpenFileDialog +from gui.components.picture_edit_popup import PictureEditPopup +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject +from gui.elements.qt.text_edit import TextEdit +from gui.elements.qt.text_label import TextLabel +from scripts.tools.image import Image +from scripts.utils.system_path import SystemPath + +_logger = logging.getLogger(__name__) + + +class AllowNotificationsView(QObject): + + def __init__(self): + super(AllowNotificationsView, self).__init__('mainWindow_AllowNotificationsView') + self._allow_button = Button('mainWindow_allowNotificationsOnboardingOkButton') + + @allure.step("Allow Notifications") + def allow(self): + self._allow_button.click() + self.wait_until_hidden() + + +class WelcomeView(QObject): + + def __init__(self): + super(WelcomeView, self).__init__('mainWindow_WelcomeView') + self._new_user_button = Button('mainWindow_I_am_new_to_Status_StatusBaseText') + self._existing_user_button = Button('mainWindow_I_already_use_Status_StatusBaseText') + + @allure.step('Open Keys view') + def get_keys(self) -> 'KeysView': + self._new_user_button.click() + time.sleep(1) + return KeysView().wait_until_appears() + + +class OnboardingView(QObject): + + def __init__(self, object_name): + super(OnboardingView, self).__init__(object_name) + self._back_button = Button('mainWindow_onboardingBackButton_StatusRoundButton') + + @abstractmethod + def back(self): + pass + + +class KeysView(OnboardingView): + + def __init__(self): + super(KeysView, self).__init__('mainWindow_KeysMainView') + self._generate_key_button = Button('mainWindow_Generate_new_keys_StatusButton') + self._generate_key_for_new_keycard_button = Button('mainWindow_Generate_keys_for_new_Keycard_StatusBaseText') + self._import_seed_phrase_button = Button('mainWindow_Import_seed_phrase') + + @allure.step('Open Profile view') + def generate_new_keys(self) -> 'YourProfileView': + self._generate_key_button.click() + return YourProfileView().wait_until_appears() + + @allure.step('Open Keycard Init view') + def generate_key_for_new_keycard(self) -> 'KeycardInitView': + self._generate_key_for_new_keycard_button.click() + return KeycardInitView().wait_until_appears() + + @allure.step('Open Import Seed Phrase view') + def open_import_seed_phrase_view(self) -> 'ImportSeedPhraseView': + self._import_seed_phrase_button.click() + return ImportSeedPhraseView().wait_until_appears() + + @allure.step('Go back') + def back(self) -> WelcomeView: + self._back_button.click() + return WelcomeView().wait_until_appears() + + +class ImportSeedPhraseView(OnboardingView): + + def __init__(self): + super(ImportSeedPhraseView, self).__init__('mainWindow_KeysMainView') + self._import_seed_phrase_button = Button('keysMainView_PrimaryAction_Button') + + @allure.step('Open seed phrase input view') + def open_seed_phrase_input_view(self): + self._import_seed_phrase_button.click() + return SeedPhraseInputView().wait_until_appears() + + @allure.step('Go back') + def back(self) -> KeysView: + self._back_button.click() + return KeysView().wait_until_appears() + + +class SeedPhraseInputView(OnboardingView): + + def __init__(self): + super(SeedPhraseInputView, self).__init__('mainWindow_SeedPhraseInputView') + self._12_words_tab_button = Button('switchTabBar_12_words_Button') + self._18_words_tab_button = Button('switchTabBar_18_words_Button') + self._24_words_tab_button = Button('switchTabBar_24_words_Button') + self._seed_phrase_input_text_edit = TextEdit('mainWindow_statusSeedPhraseInputField_TextEdit') + self._import_button = Button('mainWindow_Import_StatusButton') + + @allure.step('Input seed phrase') + def input_seed_phrase(self, seed_phrase: typing.List[str]): + if len(seed_phrase) == 12: + if not self._12_words_tab_button.is_checked: + self._12_words_tab_button.click() + elif len(seed_phrase) == 18: + if not self._18_words_tab_button.is_checked: + self._18_words_tab_button.click() + elif len(seed_phrase) == 24: + if not self._24_words_tab_button.is_checked: + self._24_words_tab_button.click() + else: + raise RuntimeError("Wrong amount of seed words", len(seed_phrase)) + + for index, word in enumerate(seed_phrase, start=1): + self._seed_phrase_input_text_edit.real_name['objectName'] = f'statusSeedPhraseInputField{index}' + self._seed_phrase_input_text_edit.text = word + + self._import_button.click() + return YourProfileView().wait_until_appears() + + +class KeycardInitView(OnboardingView): + + def __init__(self): + super(KeycardInitView, self).__init__('mainWindow_KeycardInitView') + self._message = TextLabel('mainWindow_Plug_in_Keycard_reader_StatusBaseText') + + @property + def message(self) -> str: + return self._message.text + + def back(self) -> KeysView: + self._back_button.click() + return KeysView().wait_until_appears() + + +class YourProfileView(OnboardingView): + + def __init__(self): + super(YourProfileView, self).__init__('mainWindow_InsertDetailsView') + self._upload_picture_button = Button('updatePicButton_StatusRoundButton') + self._profile_image = QObject('mainWindow_CanvasItem') + self._display_name_text_field = TextEdit('mainWindow_statusBaseInput_StatusBaseInput') + self._erros_text_label = TextLabel('mainWindow_errorMessage_StatusBaseText') + self._next_button = Button('mainWindow_Next_StatusButton') + + @property + @allure.step('Get profile image') + def profile_image(self) -> Image: + return self._profile_image.image + + @property + @allure.step('Get error messages') + def error_message(self) -> str: + return self._erros_text_label.text if self._erros_text_label.is_visible else '' + + @allure.step('Set user display name') + def set_display_name(self, value: str): + self._display_name_text_field.clear().text = value + return self + + @allure.step('Set user image') + def set_user_image(self, fp: SystemPath) -> PictureEditPopup: + allure.attach(name='User image', body=fp.read_bytes(), attachment_type=allure.attachment_type.PNG) + self._upload_picture_button.hover() + self._upload_picture_button.click() + file_dialog = OpenFileDialog().wait_until_appears() + file_dialog.open_file(fp) + return PictureEditPopup().wait_until_appears() + + @allure.step('Open Emoji and Icon view') + def next(self) -> 'EmojiAndIconView': + self._next_button.click() + time.sleep(1) + return EmojiAndIconView() + + @allure.step('Go back') + def back(self): + self._back_button.click() + return KeysView().wait_until_appears() + + +class EmojiAndIconView(OnboardingView): + + def __init__(self): + super(EmojiAndIconView, self).__init__('mainWindow_InsertDetailsView') + self._profile_image = QObject('mainWindow_welcomeScreenUserProfileImage_StatusSmartIdenticon') + self._chat_key_text_label = TextLabel('mainWindow_insertDetailsViewChatKeyTxt_StyledText') + self._next_button = Button('mainWindow_Next_StatusButton') + self._emoji_hash = QObject('mainWindow_EmojiHash') + self._identicon_ring = QObject('mainWindow_userImageCopy_StatusSmartIdenticon') + + @property + @allure.step('Get profile image icon') + def profile_image(self) -> Image: + self._profile_image.image.update_view() + return self._profile_image.image + + @property + @allure.step('Get profile image icon without identicon ring') + def cropped_profile_image(self) -> Image: + # Profile image without identicon_ring + self._profile_image.image.update_view() + self._profile_image.image.crop( + driver.UiTypes.ScreenRectangle( + 20, 20, self._profile_image.image.width - 40, self._profile_image.image.height - 40 + )) + return self._profile_image.image + + @property + @allure.step('Get chat key') + def chat_key(self) -> str: + return self._chat_key_text_label.text.split(':')[1].strip() + + @property + @allure.step('Get emoji hash image') + def emoji_hash(self) -> Image: + return self._emoji_hash.image + + @property + @allure.step('Verify: Identicon ring visible') + def is_identicon_ring_visible(self): + return self._identicon_ring.is_visible + + @allure.step('Open Create password view') + def next(self) -> 'CreatePasswordView': + self._next_button.click() + time.sleep(1) + return CreatePasswordView().wait_until_appears() + + @allure.step('Go back') + def back(self): + self._back_button.click() + return YourProfileView().wait_until_appears() + + @allure.step + @allure.step('Verify: User image contains text') + def is_user_image_contains(self, text: str): + crop = driver.UiTypes.ScreenRectangle( + 20, 20, self._profile_image.image.width - 40, self._profile_image.image.height - 40 + ) + return self.profile_image.has_text(text, constants.tesseract.text_on_profile_image, crop=crop) + + @allure.step + @allure.step('Verify: User image background color') + def is_user_image_background_white(self): + crop = driver.UiTypes.ScreenRectangle( + 20, 20, self._profile_image.image.width - 40, self._profile_image.image.height - 40 + ) + return self.profile_image.has_color(constants.Color.WHITE, crop=crop) + + +class CreatePasswordView(OnboardingView): + + def __init__(self): + super(CreatePasswordView, self).__init__('mainWindow_CreatePasswordView') + self._new_password_text_field = TextEdit('mainWindow_passwordViewNewPassword') + self._confirm_password_text_field = TextEdit('mainWindow_passwordViewNewPasswordConfirm') + self._create_button = Button('mainWindow_Create_password_StatusButton') + + @property + @allure.step('Verify: Create password button enabled') + def is_create_password_button_enabled(self) -> bool: + # Verification is_enable can not be used + # LookupError, because of "Enable: True" in object real name, if button disabled + return self._create_button.is_visible + + @allure.step('Set password and open Confirmation password view') + def create_password(self, value: str) -> 'ConfirmPasswordView': + self._new_password_text_field.clear().text = value + self._confirm_password_text_field.clear().text = value + self._create_button.click() + time.sleep(1) + return ConfirmPasswordView().wait_until_appears() + + @allure.step('Go back') + def back(self): + self._back_button.click() + return EmojiAndIconView().wait_until_appears() + + +class ConfirmPasswordView(OnboardingView): + + def __init__(self): + super(ConfirmPasswordView, self).__init__('mainWindow_ConfirmPasswordView') + self._confirm_password_text_field = TextEdit('mainWindow_confirmAgainPasswordInput') + self._confirm_button = Button('mainWindow_Finalise_Status_Password_Creation_StatusButton') + + @allure.step('Confirm password') + def confirm_password(self, value: str): + self._confirm_password_text_field.text = value + self._confirm_button.click() + + @allure.step('Go back') + def back(self): + self._back_button.click() + return CreatePasswordView().wait_until_appears() + + +class TouchIDAuthView(OnboardingView): + + def __init__(self): + super(TouchIDAuthView, self).__init__('mainWindow_TouchIDAuthView') + self._prefer_password_button = Button('mainWindow_touchIdIPreferToUseMyPasswordText') + + @allure.step('Select prefer password') + def prefer_password(self): + self._prefer_password_button.click() + self.wait_until_hidden() + + +class LoginView(QObject): + + def __init__(self): + super(LoginView, self).__init__('mainWindow_LoginView') + self._password_text_edit = TextEdit('loginView_passwordInput') + self._arrow_right_button = Button('loginView_submitBtn') + self._current_user_name_label = TextLabel('loginView_currentUserNameLabel') + self._change_account_button = Button('loginView_changeAccountBtn') + self._accounts_combobox = QObject('accountsView_accountListPanel') + + @allure.step('Log in user') + def log_in(self, account): + if self._current_user_name_label.text != account.name: + self._change_account_button.hover() + self._change_account_button.click() + self.select_user_name(account.name) + + self._password_text_edit.text = account.password + self._arrow_right_button.click() + self.wait_until_hidden() + + @allure.step('Select user') + def select_user_name(self, user_name, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + names = set() + + def _select_user() -> bool: + for index in range(self._accounts_combobox.object.count): + name_object = self._accounts_combobox.object.itemAt(index) + name_label = str(name_object.label) + names.add(name_label) + if name_label == user_name: + try: + driver.mouseClick(name_object) + except RuntimeError: + continue + return True + return False + + assert driver.waitFor(lambda: _select_user(), timeout_msec), f'User name: "{user_name}" not found in {names}' diff --git a/test/e2e/gui/screens/settings.py b/test/e2e/gui/screens/settings.py new file mode 100644 index 0000000000..dccc789345 --- /dev/null +++ b/test/e2e/gui/screens/settings.py @@ -0,0 +1,77 @@ +import typing + +import allure + +import driver +from constants import UserCommunityInfo +from driver import objects_access +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject +from gui.elements.qt.text_label import TextLabel +from gui.screens.community_settings import CommunitySettingsScreen + + +class SettingsScreen(QObject): + + def __init__(self): + super().__init__('mainWindow_ProfileLayout') + 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 communities settings') + def open_communities_settings(self): + self._open_settings(12) + return CommunitiesSettingsView() + + +class CommunitiesSettingsView(QObject): + + def __init__(self): + super().__init__('mainWindow_CommunitiesView') + self._community_item = QObject('settingsContentBaseScrollView_listItem_StatusListItem') + self._community_template_image = QObject('settings_iconOrImage_StatusSmartIdenticon') + self._community_template_name = TextLabel('settings_Name_StatusTextWithLoadingState') + self._community_template_description = TextLabel('settings_statusListItemSubTitle') + self._community_template_members = TextLabel('settings_member_StatusTextWithLoadingState') + self._community_template_button = Button('settings_StatusFlatButton') + + @property + @allure.step('Get communities') + def communities(self) -> typing.List[UserCommunityInfo]: + _communities = [] + for obj in driver.findAllObjects(self._community_item.real_name): + container = driver.objectMap.realName(obj) + self._community_template_image.real_name['container'] = container + self._community_template_name.real_name['container'] = container + self._community_template_description.real_name['container'] = container + self._community_template_members.real_name['container'] = container + + _communities.append(UserCommunityInfo( + self._community_template_name.text, + self._community_template_description.text, + self._community_template_members.text, + self._community_template_image.image + )) + return _communities + + def _get_community_item(self, name: str): + for obj in driver.findAllObjects(self._community_item.real_name): + for item in objects_access.walk_children(obj): + if getattr(item, 'text', '') == name: + return obj + raise LookupError(f'Community item: {name} not found') + + @allure.step('Open community info') + def get_community_info(self, name: str) -> UserCommunityInfo: + for community in self.communities: + if community.name == name: + return community + raise LookupError(f'Community item: {name} not found') + + @allure.step('Open community overview settings') + def open_community_overview_settings(self, name: str): + driver.mouseClick(self._get_community_item(name)) + return CommunitySettingsScreen().wait_until_appears() diff --git a/test/e2e/gui/screens/wallet.py b/test/e2e/gui/screens/wallet.py new file mode 100644 index 0000000000..96073703b8 --- /dev/null +++ b/test/e2e/gui/screens/wallet.py @@ -0,0 +1,191 @@ +import typing + +import allure + +import configs +import constants.user +import driver +from driver.objects_access import walk_children +from gui.components.base_popup import BasePopup +from gui.components.wallet.add_saved_address_popup import AddressPopup, EditSavedAddressPopup +from gui.components.wallet.confirmation_popup import ConfirmationPopup +from gui.components.wallet.remove_wallet_account_popup import RemoveWalletAccountPopup +from gui.components.wallet.wallet_account_popups import AccountPopup +from gui.components.context_menu import ContextMenu +from gui.elements.qt.button import Button +from gui.elements.qt.object import QObject +from gui.elements.qt.text_label import TextLabel +from scripts.utils.decorators import close_exists + + +class WalletScreen(QObject): + + def __init__(self): + super().__init__('mainWindow_WalletLayout') + self.left_panel = LeftPanel() + +class LeftPanel(QObject): + + def __init__(self): + super(LeftPanel, self).__init__('mainWallet_LeftTab') + self._saved_addresses_button = Button('mainWallet_Saved_Addresses_Button') + self._wallet_account_item = QObject('walletAccount_StatusListItem') + self._add_account_button = Button('mainWallet_Add_Account_Button') + self._all_accounts_button = Button('mainWallet_All_Accounts_Button') + + @property + @allure.step('Get all accounts from list') + def accounts(self) -> typing.List[constants.user.account_list_item]: + if 'title' in self._wallet_account_item.real_name.keys(): + del self._wallet_account_item.real_name['title'] + + accounts = [] + for account_item in driver.findAllObjects(self._wallet_account_item.real_name): + try: + name = str(account_item.title) + color = str(account_item.asset.color.name).lower() + emoji = '' + for child in walk_children(account_item): + if hasattr(child, 'emojiId'): + emoji = str(child.emojiId) + break + accounts.append(constants.user.account_list_item(name, color, emoji)) + except (AttributeError, RuntimeError): + continue + + return accounts + + @allure.step('Choose saved addresses on left wallet panel') + @close_exists(BasePopup()) + def open_saved_addresses(self) -> 'SavedAddressesView': + self._saved_addresses_button.click() + return SavedAdressesView().wait_until_appears() + + @allure.step('Select account from list') + @close_exists(BasePopup()) + def select_account(self, account_name: str) -> 'WalletAccountView': + self._wallet_account_item.real_name['title'] = account_name + self._wallet_account_item.click() + return WalletAccountView().wait_until_appears() + + @allure.step('Open context menu from left wallet panel') + @close_exists(BasePopup()) + def _open_context_menu(self) -> ContextMenu: + super(LeftPanel, self).open_context_menu() + return ContextMenu().wait_until_appears() + + @allure.step('Open context menu for account') + @close_exists(BasePopup()) + def _open_context_menu_for_account(self, account_name: str) -> ContextMenu: + self._wallet_account_item.real_name['title'] = account_name + self._wallet_account_item.wait_until_appears().open_context_menu() + return ContextMenu().wait_until_appears() + + @allure.step('Open account popup for editing') + def open_edit_account_popup(self, account_name: str, attempt: int = 2) -> AccountPopup: + try: + self._open_context_menu_for_account(account_name).select('Edit') + return AccountPopup().wait_until_appears() + except: + if attempt: + return self.open_edit_account_popup(account_name, attempt - 1) + else: + raise + + @allure.step('Open account popup') + def open_add_account_popup(self, attempt: int = 2): + self._add_account_button.click() + try: + return AccountPopup().wait_until_appears() + except AssertionError as err: + if attempt: + self.open_add_account_popup(attempt-1) + else: + raise err + + @allure.step('Open account popup for watch only account') + def open_add_watch_only_account_popup(self, attempt: int = 2) -> AccountPopup: + try: + self._open_context_menu().select('Add watch-only account') + return AccountPopup().wait_until_appears() + except: + if attempt: + return self.open_add_watch_anly_account_popup(attempt - 1) + else: + raise + + @allure.step('Delete account from list') + def delete_account(self, account_name: str, attempt: int = 2) -> RemoveWalletAccountPopup: + try: + self._open_context_menu_for_account(account_name).select('Delete') + return RemoveWalletAccountPopup().wait_until_appears() + except: + if attempt: + return self.delete_account(account_name, attempt - 1) + else: + raise + +class SavedAdressesView(QObject): + + def __init__(self): + super(SavedAdressesView, self).__init__('mainWindow_SavedAddressesView') + self._add_new_address_button = Button('mainWallet_Saved_Addreses_Add_Buttton') + self._address_list_item = QObject('savedAddressView_Delegate') + self._send_button = Button('send_StatusRoundButton') + self._open_menu_button = Button('savedAddressView_Delegate_menuButton') + + @property + @allure.step('Get saved addresses names') + def address_names(self): + names = [str(address.name) for address in driver.findAllObjects(self._address_list_item.real_name)] + return names + + @allure.step('Open add new address popup') + def open_add_address_popup(self, attempt=2) -> 'AddressPopup': + self._add_new_address_button.click() + try: + return AddressPopup().wait_until_appears() + except AssertionError as err: + if attempt: + self.open_add_address_popup(attempt - 1) + else: + raise err + + @allure.step('Open edit address popup for saved address') + def open_edit_address_popup(self, name: str) -> 'EditSavedAddressPopup': + self.open_context_menu(name).select('Edit') + return EditSavedAddressPopup().wait_until_appears() + + @allure.step('Delete saved address from the list') + def delete_saved_address(self, address_name): + self.open_context_menu(address_name).select('Delete') + ConfirmationPopup().wait_until_appears().confirm() + + @allure.step('Open context menu in saved address') + def open_context_menu(self, name) -> ContextMenu: + self._open_menu_button.real_name['objectName'] = 'savedAddressView_Delegate_menuButton' + '_' + name + self._open_menu_button.click() + return ContextMenu().wait_until_appears() + + +class WalletAccountView(QObject): + + def __init__(self): + super(WalletAccountView, self).__init__('mainWindow_StatusSectionLayout_ContentItem') + self._account_name_text_label = TextLabel('mainWallet_Account_Name') + self._addresses_panel = QObject('mainWallet_Address_Panel') + + @property + @allure.step('Get name of account') + def name(self) -> str: + return self._account_name_text_label.text + + @property + @allure.step('Get address of account') + def address(self) -> str: + return str(self._addresses_panel.object.value) + + @allure.step('Wait until appears {0}') + def wait_until_appears(self, timeout_msec: int = configs.timeouts.UI_LOAD_TIMEOUT_MSEC): + self._account_name_text_label.wait_until_appears(timeout_msec) + return self \ No newline at end of file diff --git a/test/e2e/pytest.ini b/test/e2e/pytest.ini new file mode 100644 index 0000000000..eea75aed46 --- /dev/null +++ b/test/e2e/pytest.ini @@ -0,0 +1,8 @@ +[pytest] +log_format = %(asctime)s.%(msecs)03d %(levelname)7s %(name)s %(message).5000s +log_cli = true + +addopts = --disable-warnings + +markers = + smoke: Smoke tests diff --git a/test/e2e/requirements.txt b/test/e2e/requirements.txt new file mode 100644 index 0000000000..4a99e3c485 --- /dev/null +++ b/test/e2e/requirements.txt @@ -0,0 +1,9 @@ +pytest==7.4.0 +psutil==5.9.5 +pillow==10.0.0 +opencv-python-headless==4.8.0.74 +numpy~=1.25.1 +pytesseract==0.3.10 +atomacos==3.3.0; platform_system == "Darwin" +allure-pytest==2.13.2 +testrail-api==1.12.0 diff --git a/test/e2e/scripts/__init__.py b/test/e2e/scripts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/scripts/tools/__init__.py b/test/e2e/scripts/tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/scripts/tools/image.py b/test/e2e/scripts/tools/image.py new file mode 100755 index 0000000000..feb50300dd --- /dev/null +++ b/test/e2e/scripts/tools/image.py @@ -0,0 +1,228 @@ +import logging +import time +import typing +from datetime import datetime + +import allure +import cv2 +import numpy as np +import pytesseract +from PIL import ImageGrab + +import configs +import constants +import driver +from configs.system import IS_LIN +from scripts.tools.ocv import Ocv +from scripts.utils.system_path import SystemPath + +_logger = logging.getLogger(__name__) + + +class Image: + + def __init__(self, object_name: dict): + self.object_name = object_name + self._view = None + + @property + @allure.step('Get image view') + def view(self) -> np.ndarray: + return self._view + + @property + @allure.step('Get image height') + def height(self) -> int: + return self.view.shape[0] + + @property + @allure.step('Get image width') + def width(self) -> int: + return self.view.shape[1] + + @property + @allure.step('Get image is grayscale') + def is_grayscale(self) -> bool: + return self.view.ndim == 2 + + @allure.step('Set image in grayscale') + def set_grayscale(self) -> 'Image': + if not self.is_grayscale: + self._view = cv2.cvtColor(self.view, cv2.COLOR_BGR2GRAY) + return self + + @allure.step('Grab image view from object') + def update_view(self): + _logger.debug(f'Image view was grab from: {self.object_name}') + rect = driver.object.globalBounds(driver.waitForObject(self.object_name)) + img = ImageGrab.grab( + bbox=(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height), + xdisplay=":0" if IS_LIN else None + ) + self._view = cv2.cvtColor(np.array(img), cv2.COLOR_BGR2RGB) + + @allure.step('Save image') + def save(self, path: SystemPath, force: bool = False): + path.parent.mkdir(parents=True, exist_ok=True) + if path.exists() and not force: + raise FileExistsError(path) + if self.view is None: + self.update_view() + cv2.imwrite(str(path), self.view) + + @allure.step('Compare images') + def compare( + self, expected: np.ndarray, threshold: float = 0.99) -> bool: + if self.view is None: + self.update_view() + correlation = Ocv.compare_images(self.view, expected) + result = correlation >= threshold + _logger.info(f'Images equals on: {abs(round(correlation, 4) * 100)}%') + + if result: + _logger.info(f'Screenshot comparison passed') + else: + configs.testpath.TEST_ARTIFACTS.mkdir(parents=True, exist_ok=True) + diff = Ocv.draw_contours(self.view, expected) + + actual_fp = configs.testpath.TEST_ARTIFACTS / f'actual_image.png' + expected_fp = configs.testpath.TEST_ARTIFACTS / f'expected_image.png' + diff_fp = configs.testpath.TEST_ARTIFACTS / f'diff_image.png' + + self.save(actual_fp, force=True) + cv2.imwrite(str(expected_fp), expected) + cv2.imwrite(str(diff_fp), diff) + + allure.attach(name='actual', body=actual_fp.read_bytes(), attachment_type=allure.attachment_type.PNG) + allure.attach(name='expected', body=expected_fp.read_bytes(), attachment_type=allure.attachment_type.PNG) + allure.attach(name='diff', body=diff_fp.read_bytes(), attachment_type=allure.attachment_type.PNG) + + _logger.info( + f"Screenshot comparison failed.\n" + f"Actual, Diff and Expected screenshots are saved:\n" + f"{configs.testpath.TEST_ARTIFACTS.relative_to(configs.testpath.ROOT)}.") + return result + + @allure.step('Crop image') + def crop(self, rect: driver.UiTypes.ScreenRectangle): + assert rect.x + rect.width < self.width + assert rect.y + rect.height < self.height + self._view = self.view[rect.y: (rect.y + rect.height), rect.x: (rect.x + rect.width)] + + @allure.step('Parse text on image') + def to_string(self, custom_config: str): + text: str = pytesseract.image_to_string(self.view, config=custom_config) + _logger.debug(f'Text on image: {text}') + return text + + @allure.step('Verify: Image contains text: {1}') + def has_text(self, text: str, criteria: str, crop: driver.UiTypes.ScreenRectangle = None) -> bool: + self.update_view() + if crop: + self.crop(crop) + + # Search text on image converted in gray color + self.set_grayscale() + fp_gray = configs.testpath.TEST_ARTIFACTS / f'search_region_in_gray_color.png' + self.save(fp_gray, force=True) + if text.lower() in self.to_string(criteria).lower(): + allure.attach(name='search_region', body=fp_gray.read_bytes(), attachment_type=allure.attachment_type.PNG) + return True + + # Search text on image with inverted color + self._view = cv2.bitwise_not(self.view) + fp_invert = configs.testpath.TEST_ARTIFACTS / f'search_region_in_inverted_color.png' + self.save(fp_invert, force=True) + if text.lower() in self.to_string(criteria).lower(): + allure.attach(name='search_region', body=fp_invert.read_bytes(), attachment_type=allure.attachment_type.PNG) + return True + return False + + @allure.step('Search color on image') + def has_color(self, color: constants.Color, denoise: int = 10, crop: driver.UiTypes.ScreenRectangle = None) -> bool: + self.update_view() + if crop: + self.crop(crop) + + initial_view = configs.testpath.TEST_ARTIFACTS / f'{color.name}.png' + self.save(initial_view) + allure.attach(name='search_region', body=initial_view.read_bytes(), attachment_type=allure.attachment_type.PNG) + + contours = self._get_color_contours(color, denoise, apply=True) + + mask_view = configs.testpath.TEST_ARTIFACTS / f'{color.name}_mask.png' + self.save(mask_view) + allure.attach(name='contours', body=mask_view.read_bytes(), attachment_type=allure.attachment_type.PNG) + + self._view = None + return len(contours) >= 1 + + @allure.step('Apply contours with found color on image') + def _get_color_contours( + self, + color: constants.Color, + denoise: int = 10, + apply: bool = False + ) -> typing.List[driver.UiTypes.ScreenRectangle]: + if not self.is_grayscale: + view = cv2.cvtColor(self.view, cv2.COLOR_BGR2HSV) + else: + view = self.view + boundaries = constants.boundaries[color] + + if color == constants.Color.RED: + mask = None + for bond in boundaries: + lower_range = np.array(bond[0]) + upper_range = np.array(bond[1]) + _mask = cv2.inRange(view, lower_range, upper_range) + mask = _mask if mask is None else mask + _mask + else: + lower_range = np.array(boundaries[0]) + upper_range = np.array(boundaries[1]) + mask = cv2.inRange(view, lower_range, upper_range) + + contours = [] + _contours = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + _contours = _contours[0] if len(_contours) == 2 else _contours[1] + for _contour in _contours: + x, y, w, h = cv2.boundingRect(_contour) + # To remove small artifacts, less than denoise variable value + if w * h < denoise: + continue + contours.append(driver.UiTypes.ScreenRectangle(x, y, w, h)) + + if apply: + self._view = cv2.bitwise_and(self.view, self.view, mask=mask) + for contour in contours: + cv2.rectangle( + self.view, + (contour.x, contour.y), + (contour.x + contour.width, contour.y + contour.height), + (36, 255, 12), 2) + + return contours + + +@allure.step('Compare images') +def compare(actual: Image, + expected: typing.Union[str, SystemPath, Image], + threshold: float = 0.99, + timout_sec: int = 1 + ): + if isinstance(expected, str): + expected_fp = configs.testpath.TEST_VP / configs.system.OS_ID / expected + if not expected_fp.exists(): + expected_fp = configs.testpath.TEST_VP / expected + expected = expected_fp + if isinstance(expected, SystemPath): + assert expected.exists(), f'File: {expected} not found' + expected = cv2.imread(str(expected)) + else: + expected = expected.view + start = datetime.now() + while not actual.compare(expected, threshold): + time.sleep(1) + assert (datetime.now() - start).seconds < timout_sec, 'Comparison failed' + _logger.info(f'Screenshot comparison passed') + diff --git a/test/e2e/scripts/tools/ocv.py b/test/e2e/scripts/tools/ocv.py new file mode 100755 index 0000000000..6e675cc4bc --- /dev/null +++ b/test/e2e/scripts/tools/ocv.py @@ -0,0 +1,27 @@ +import cv2 +import numpy as np + + +class Ocv: + + @classmethod + def compare_images(cls, lhd: np.ndarray, rhd: np.ndarray) -> float: + res = cv2.matchTemplate(lhd, rhd, cv2.TM_CCOEFF_NORMED) + _, correlation, _, _ = cv2.minMaxLoc(res) + return correlation + + @classmethod + def draw_contours(cls, lhd: np.ndarray, rhd: np.ndarray) -> np.ndarray: + view = rhd.copy() + + lhd = cv2.cvtColor(lhd, cv2.COLOR_BGRA2GRAY) + _, thresh = cv2.threshold(lhd, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) + contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) + cv2.drawContours(view, contours, -1, (0, 0, 255), 1) + + rhd = cv2.cvtColor(rhd, cv2.COLOR_BGRA2GRAY) + _, thresh = cv2.threshold(rhd, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) + contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) + cv2.drawContours(view, contours, -1, (0, 255, 0), 1) + + return view diff --git a/test/e2e/scripts/utils/__init__.py b/test/e2e/scripts/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/scripts/utils/decorators.py b/test/e2e/scripts/utils/decorators.py new file mode 100644 index 0000000000..984daeb357 --- /dev/null +++ b/test/e2e/scripts/utils/decorators.py @@ -0,0 +1,10 @@ +def close_exists(element): + def _wrapper(method_to_decorate): + def wrapper(*args, **kwargs): + if element.is_visible: + element.close() + return method_to_decorate(*args, **kwargs) + + return wrapper + + return _wrapper \ No newline at end of file diff --git a/test/e2e/scripts/utils/local_system.py b/test/e2e/scripts/utils/local_system.py new file mode 100644 index 0000000000..cb9bd69326 --- /dev/null +++ b/test/e2e/scripts/utils/local_system.py @@ -0,0 +1,73 @@ +import logging +import os +import signal +import subprocess +import time + +import allure +import psutil + +import configs +from configs.system import IS_WIN + +_logger = logging.getLogger(__name__) + + +def find_process_by_port(port: int) -> int: + for proc in psutil.process_iter(): + try: + for conns in proc.connections(kind='inet'): + if conns.laddr.port == port: + return proc.pid + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + pass + + +@allure.step('Kill process') +def kill_process(pid): + os.kill(pid, signal.SIGILL if IS_WIN else signal.SIGKILL) + + +@allure.step('System execute command') +def execute( + command: list, + shell=False if IS_WIN else True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + check=False +): + def _is_process_exists(_process) -> bool: + return _process.poll() is None + + def _wait_for_execution(_process): + while _is_process_exists(_process): + time.sleep(1) + + def _get_output(_process): + _wait_for_execution(_process) + return _process.communicate() + + command = " ".join(str(atr) for atr in command) + _logger.info(f'Execute: {command}') + process = subprocess.Popen(command, shell=shell, stderr=stderr, stdout=stdout) + if check and process.returncode != 0: + stdout, stderr = _get_output(process) + raise RuntimeError(stderr) + return process.pid + + +@allure.step('System run command') +def run( + command: list, + shell=False if IS_WIN else True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + timeout_sec=configs.timeouts.PROCESS_TIMEOUT_SEC, + check=True +): + command = " ".join(str(atr) for atr in command) + _logger.info(f'Execute: {command}') + process = subprocess.run(command, shell=shell, stderr=stderr, stdout=stdout, timeout=timeout_sec) + if check and process.returncode != 0: + raise subprocess.CalledProcessError(process.returncode, command, process.stdout, process.stderr) + _logger.debug(f'stdout: {process.stdout}') diff --git a/test/e2e/scripts/utils/system_path.py b/test/e2e/scripts/utils/system_path.py new file mode 100644 index 0000000000..f8516f820b --- /dev/null +++ b/test/e2e/scripts/utils/system_path.py @@ -0,0 +1,21 @@ +import logging +import os +import pathlib +import shutil + +import allure + +_logger = logging.getLogger(__name__) + + +class SystemPath(pathlib.Path): + _accessor = pathlib._normal_accessor # noqa + _flavour = pathlib._windows_flavour if os.name == 'nt' else pathlib._posix_flavour # noqa + + @allure.step('Delete path') + def rmtree(self, ignore_errors=False): + shutil.rmtree(self, ignore_errors=ignore_errors) + + @allure.step('Copy path') + def copy_to(self, destination: 'SystemPath'): + shutil.copytree(self, destination, dirs_exist_ok=True) diff --git a/test/e2e/tests/__init__.py b/test/e2e/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/tests/fixtures/__init__.py b/test/e2e/tests/fixtures/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/e2e/tests/fixtures/aut.py b/test/e2e/tests/fixtures/aut.py new file mode 100644 index 0000000000..4413dcb982 --- /dev/null +++ b/test/e2e/tests/fixtures/aut.py @@ -0,0 +1,65 @@ +from datetime import datetime + +import pytest + +import configs +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 + + +@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 + + +@pytest.fixture +def main_window(aut: AUT, user_data): + aut.launch(f'-d={user_data.parent}') + yield MainWindow().wait_until_appears().prepare() + aut.detach().stop() + + +@pytest.fixture +def user_account(request) -> UserAccount: + if hasattr(request, 'param'): + user_account = request.param + assert isinstance(user_account, UserAccount) + else: + user_account = constants.user.user_account_default + 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 diff --git a/test/e2e/tests/fixtures/path.py b/test/e2e/tests/fixtures/path.py new file mode 100644 index 0000000000..9560a36058 --- /dev/null +++ b/test/e2e/tests/fixtures/path.py @@ -0,0 +1,45 @@ +import logging +import re + +import pytest + +import configs +from scripts.utils.system_path import SystemPath + +_logger = logging.getLogger(__name__) + + +@pytest.fixture +def generate_test_data(request): + test_path, test_name, test_params = generate_test_info(request.node) + configs.testpath.TEST = configs.testpath.RUN / test_path / test_name + node_dir = configs.testpath.TEST / test_params + configs.testpath.TEST_ARTIFACTS = node_dir / 'artifacts' + configs.testpath.TEST_VP = configs.testpath.VP / test_path / test_name + _logger.info( + f'\nArtifacts directory:\t{configs.testpath.TEST_ARTIFACTS.relative_to(configs.testpath.ROOT)}' + f'\nVerification points directory:\t{configs.testpath.TEST_VP.relative_to(configs.testpath.ROOT)}' + ) + _logger.info(f'Start test: {test_name}') + + +def generate_test_info(node): + pure_path = SystemPath(node.location[0]).parts[1:] + test_path = SystemPath(*pure_path).with_suffix('') + test_name = node.originalname + test_params = re.sub('[^a-zA-Z0-9\n\-_]', '', node.name.strip(test_name)) + return test_path, test_name, test_params + + +@pytest.fixture(scope='session') +def prepare_test_directory(): + keep_results = 5 + run_name_pattern = 'run_????????_??????' + runs = list(sorted(configs.testpath.RESULTS.glob(run_name_pattern))) + if len(runs) > keep_results: + del_runs = runs[:len(runs) - keep_results] + for run in del_runs: + SystemPath(run).rmtree(ignore_errors=True) + _logger.info(f"Remove old test run directory: {run.relative_to(configs.testpath.ROOT)}") + configs.testpath.RUN.mkdir(parents=True, exist_ok=True) + _logger.info(f"Created new test run directory: {configs.testpath.RUN.relative_to(configs.testpath.ROOT)}") diff --git a/test/e2e/tests/fixtures/squish.py b/test/e2e/tests/fixtures/squish.py new file mode 100644 index 0000000000..7b767c9151 --- /dev/null +++ b/test/e2e/tests/fixtures/squish.py @@ -0,0 +1,20 @@ +import pytest + +from driver.server import SquishServer + + +@pytest.fixture(scope='session') +def start_squish_server(): + squish_server = SquishServer() + squish_server.stop() + attempt = 3 + while True: + try: + squish_server.start() + break + except AssertionError as err: + attempt -= 1 + if not attempt: + pytest.exit(err) + yield squish_server + squish_server.stop() diff --git a/test/e2e/tests/fixtures/testrail.py b/test/e2e/tests/fixtures/testrail.py new file mode 100644 index 0000000000..ecdf090e04 --- /dev/null +++ b/test/e2e/tests/fixtures/testrail.py @@ -0,0 +1,125 @@ +import logging +import typing + +import pytest +from testrail_api import TestRailAPI + +import configs + +_logger = logging.getLogger(__name__) + +testrail_api = None + +PASS = 1 +FAIL = 5 +RETEST = 4 + + +@pytest.fixture(scope='session') +def init_testrail_api(request): + global testrail_api + if configs.testrail.TESTRAIL_RUN_ID: + _logger.info('TestRail API initializing') + testrail_api = TestRailAPI( + configs.testrail.TESTRAIL_URL, + configs.testrail.TESTRAIL_USER, + configs.testrail.TESTRAIL_PWD + ) + test_case_ids = get_test_ids_in_session(request) + for test_case_id in test_case_ids: + if is_test_case_in_run(test_case_id): + _update_result(test_case_id, RETEST) + _logger.info(f'Test: "{test_case_id}" marked as "Retest"') + else: + _logger.info(f'Report result for test case: {test_case_id} skipped, not in test run') + else: + _logger.info('TestRail report skipped') + + +@pytest.fixture +def check_result(request): + yield + if configs.testrail.TESTRAIL_RUN_ID: + item = request.node + test_case_ids = _find_test_case_id_markers(request) + for test_case_id in test_case_ids: + if is_test_case_in_run(test_case_id): + current_test_status = _get_test_case_status(test_case_id) + if item.rep_call.failed: + if current_test_status != FAIL: + _update_result(test_case_id, FAIL) + _update_comment(test_case_id, f"{request.node.name} FAILED") + else: + if current_test_status != FAIL: + _update_result(test_case_id, PASS) + _update_comment(test_case_id, f"{request.node.name} SUCCESS") + + +def _update_result(test_case_id: int, result: int): + testrail_api.results.add_result_for_case( + run_id=configs.testrail.TESTRAIL_RUN_ID, + case_id=test_case_id, + status_id=result, + ) + + +def _update_comment(test_case_id: int, comment: str): + testrail_api.results.add_result_for_case( + run_id=configs.testrail.TESTRAIL_RUN_ID, + case_id=test_case_id, + comment=comment + ) + + +def _find_test_case_id_markers(request) -> typing.List[int]: + for marker in request.node.own_markers: + if marker.name == 'case': + test_case_ids = marker.args + return test_case_ids + return [] + + +def _get_test_case_status(test_case_id: int) -> int: + test_case_results = testrail_api.results.get_results_for_case(configs.testrail.TESTRAIL_RUN_ID, test_case_id) + try: + result = 0 + while True: + last_test_case_status = test_case_results['results'][result]['status_id'] + if last_test_case_status is None: + result += 1 + else: + return last_test_case_status + except: + return RETEST + + +def is_test_case_in_run(test_case_id: int) -> bool: + try: + testrail_api.results.get_results_for_case(configs.testrail.TESTRAIL_RUN_ID, test_case_id) + except Exception as err: + return False + else: + return True + + +def _get_test_cases(): + results = [] + limit = 250 + chunk = 0 + while True: + tests = testrail_api.tests.get_tests(configs.testrail.TESTRAIL_RUN_ID, offset=chunk)['tests'] + results.extend(tests) + if len(tests) == limit: + chunk += limit + else: + return results + + +def get_test_ids_in_session(request): + tests = request.session.items + ids = [] + for test in tests: + for marker in getattr(test, 'own_markers', []): + if getattr(marker, 'name', '') == 'case': + ids.extend(list(marker.args)) + return set(ids) diff --git a/test/e2e/tests/test_communities.py b/test/e2e/tests/test_communities.py new file mode 100644 index 0000000000..836a3cf784 --- /dev/null +++ b/test/e2e/tests/test_communities.py @@ -0,0 +1,198 @@ +from datetime import datetime + +import allure +import pytest +from allure_commons._allure import step + +import configs.testpath +import constants.user +from gui.main_window import MainWindow +from gui.screens.community import CommunityScreen +from scripts.tools import image + +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]) +def test_create_community(user_account, main_screen: MainWindow, params): + with step('Create community'): + communities_portal = main_screen.left_panel.open_communities_portal() + create_community_form = communities_portal.open_create_community_popup() + community_screen = create_community_form.create(params) + + with step('Verify community parameters in community overview'): + with step('Icon is correct'): + community_icon = main_screen.left_panel.get_community_logo(params['name']) + image.compare(community_icon, 'button_logo.png', timout_sec=5) + with step('Name is correct'): + assert community_screen.left_panel.name == params['name'] + with step('Members count is correct'): + assert '1' in community_screen.left_panel.members + with step('Logo is correct'): + image.compare(community_screen.left_panel.logo, 'logo.png') + + with step('Verify community parameters in community settings view'): + community_setting = community_screen.left_panel.open_community_settings() + overview_setting = community_setting.left_panel.open_overview() + with step('Name is correct'): + assert overview_setting.name == params['name'] + with step('Description is correct'): + assert overview_setting.description == params['description'] + with step('Members count is correct'): + members_settings = community_setting.left_panel.open_members() + assert user_account.name in members_settings.members + + 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 = community_settings.get_community_info(params['name']) + assert community.name == params['name'] + assert community.description == params['description'] + assert '1' in community.members + image.compare(community.image, 'logo_in_settings.png') + + +@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', + }, + +]) +def test_edit_community_separately(main_screen, community: dict, community_params): + + with step('Edit community name'): + community_screen = main_screen.left_panel.select_community(community['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']}) + + with step('Name is correct'): + 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'] + + with step('Edit community name'): + edit_community_form = overview_setting.open_edit_community_view() + edit_community_form.edit({'description': community_params['description']}) + + with step('Name is correct'): + overview_setting = community_setting.left_panel.open_overview() + assert overview_setting.name == community_params['name'] + with step('Description is correct'): + assert overview_setting.description == community_params['description'] + + +@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', + 'description': 'Updated Description', + 'logo': {'fp': configs.testpath.TEST_FILES / 'banner.png', 'zoom': None, 'shift': None}, + 'banner': {'fp': configs.testpath.TEST_FILES / 'tv_signal.png', 'zoom': None, 'shift': None}, + 'color': '#ff7d46', + 'tags': ['Ethereum'], + 'intro': 'Updated Intro', + 'outro': 'Updated Outro' + } +]) +def test_edit_community(main_screen: MainWindow, community: dict, params): + + with step('Edit community'): + community_screen = main_screen.left_panel.select_community(community['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) + + with step('Verify community parameters on settings overview'): + overview_setting = community_setting.left_panel.open_overview() + with step('Name is correct'): + assert overview_setting.name == params['name'] + with step('Description is correct'): + assert overview_setting.description == params['description'] + + with step('Verify community parameters in community screen'): + community_setting.left_panel.back_to_community() + with step('Icon is correct'): + community_icon = main_screen.left_panel.get_community_logo(params['name']) + image.compare(community_icon, 'button_updated_logo.png') + with step('Name is correct'): + assert community_screen.left_panel.name == params['name'] + with step('Logo is correct'): + image.compare(community_screen.left_panel.logo, 'updated_logo.png') + + 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_info = community_settings.communities[0] + assert community_info.name == params['name'] + assert community_info.description == params['description'] + assert '1' in community_info.members + image.compare(community_info.image, 'logo_in_settings_updated.png') + + +@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']) + community_screen.create_channel(channel_name, channel_description, channel_emoji) + + with step('Verify channel'): + community_screen.verify_channel( + channel_name, + channel_description, + 'channel_icon_in_list.png', + 'channel_icon_in_toolbar.png', + 'channel_icon_in_chat.png' + ) + + +@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): + community_screen = CommunityScreen() + + with step('Verify General channel'): + community_screen.verify_channel( + 'general', + 'General channel for the community', + 'general_channel_icon_in_list.png', + 'general_channel_icon_in_toolbar.png', + 'general_channel_icon_in_chat.png' + ) + + community_screen.edit_channel('general', channel_name, channel_description, channel_emoji) + + with step('Verify General channel'): + community_screen.verify_channel( + channel_name, + channel_description, + 'channel_icon_in_list.png', + 'channel_icon_in_toolbar.png', + 'channel_icon_in_chat.png' + ) + + +@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): + with step('Delete channel'): + CommunityScreen().delete_channel('general') + + with step('Verify channel is not exists'): + assert not CommunityScreen().left_panel.channels diff --git a/test/e2e/tests/test_onboarding.py b/test/e2e/tests/test_onboarding.py new file mode 100755 index 0000000000..bf49abe19c --- /dev/null +++ b/test/e2e/tests/test_onboarding.py @@ -0,0 +1,132 @@ +import logging + +import allure +import pytest +from allure import step + +import configs.timeouts +import constants +import driver +from gui.components.onboarding.before_started_popup import BeforeStartedPopUp +from gui.components.onboarding.welcome_status_popup import WelcomeStatusPopup +from gui.components.picture_edit_popup import shift_image +from gui.components.splash_screen import SplashScreen +from gui.screens.onboarding import AllowNotificationsView, WelcomeView, TouchIDAuthView, KeysView +from scripts.tools import image + +_logger = logging.getLogger(__name__) +pytestmark = allure.suite("Onboarding") + + +@pytest.fixture +def keys_screen(main_window) -> KeysView: + with step('Open Generate new keys view'): + if configs.system.IS_MAC: + AllowNotificationsView().wait_until_appears().allow() + BeforeStartedPopUp().get_started() + wellcome_screen = WelcomeView().wait_until_appears() + return wellcome_screen.get_keys() + + +@allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703421', 'Generate new keys') +@pytest.mark.case(703421) +@pytest.mark.parametrize('user_name, password, user_image, zoom, shift', [ + pytest.param('Test-User _1', '*P@ssw0rd*', None, None, None), + pytest.param('Test-User', '*P@ssw0rd*', 'tv_signal.png', 5, shift_image(0, 0, 0, 0)), + pytest.param('_1Test-User', '*P@ssw0rd*', 'tv_signal.jpeg', 5, shift_image(0, 1000, 1000, 0), + marks=pytest.mark.smoke), +]) +def test_generate_new_keys(main_window, keys_screen, user_name: str, password, user_image: str, zoom: int, shift): + with step(f'Setup profile with name: {user_name} and image: {user_image}'): + + profile_view = keys_screen.generate_new_keys() + profile_view.set_display_name(user_name) + if user_image is not None: + profile_picture_popup = profile_view.set_user_image(configs.testpath.TEST_FILES / user_image) + profile_picture_popup.make_picture(zoom=zoom, shift=shift) + assert not profile_view.error_message + + with step('Open Profile details view and verify user info'): + + details_view = profile_view.next() + if user_image is None: + assert not details_view.is_user_image_background_white() + assert driver.waitFor( + lambda: details_view.is_user_image_contains(user_name[:2]), + configs.timeouts.UI_LOAD_TIMEOUT_MSEC + ) + else: + image.compare( + details_view.cropped_profile_image, + f'{user_image.split(".")[1]}_onboarding.png', + threshold=0.9 + ) + chat_key = details_view.chat_key + emoji_hash = details_view.emoji_hash + assert details_view.is_identicon_ring_visible + + with step('Finalize onboarding and open main screen'): + + create_password_view = details_view.next() + assert not create_password_view.is_create_password_button_enabled + confirm_password_view = create_password_view.create_password(password) + confirm_password_view.confirm_password(password) + if configs.system.IS_MAC: + TouchIDAuthView().wait_until_appears().prefer_password() + SplashScreen().wait_until_appears().wait_until_hidden() + if not configs.DEV_BUILD: + WelcomeStatusPopup().confirm() + + with step('Open User Canvas and verify user info'): + + user_canvas = main_window.left_panel.open_user_canvas() + assert user_canvas.user_name == user_name + if user_image is None: + assert driver.waitFor( + lambda: user_canvas.is_user_image_contains(user_name[:2]), + configs.timeouts.UI_LOAD_TIMEOUT_MSEC + ) + + with step('Open Profile popup and verify user info'): + + profile_popup = user_canvas.open_profile_popup() + assert profile_popup.user_name == user_name + assert profile_popup.chat_key == chat_key + assert profile_popup.emoji_hash.compare(emoji_hash.view, threshold=0.9) + if user_image is None: + assert driver.waitFor( + lambda: profile_popup.is_user_image_contains(user_name[:2]), + configs.timeouts.UI_LOAD_TIMEOUT_MSEC + ) + else: + image.compare( + profile_popup.cropped_profile_image, + f'{user_image.split(".")[1]}_profile.png', + threshold=0.9 + ) + + +@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]) +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() + profile_view = input_view.input_seed_phrase(user_account.seed_phrase) + profile_view.set_display_name(user_account.name) + + with step('Finalize onboarding and open main screen'): + details_view = profile_view.next() + create_password_view = details_view.next() + confirm_password_view = create_password_view.create_password(user_account.password) + confirm_password_view.confirm_password(user_account.password) + if configs.system.IS_MAC: + TouchIDAuthView().wait_until_appears().prefer_password() + SplashScreen().wait_until_appears().wait_until_hidden() + if not configs.DEV_BUILD: + WelcomeStatusPopup().confirm() + + with step('Verify that the user logged in via seed phrase correctly'): + user_canvas = main_window.left_panel.open_user_canvas() + profile_popup = user_canvas.open_profile_popup() + assert profile_popup.user_name == user_account.name diff --git a/test/e2e/tests/test_self.py b/test/e2e/tests/test_self.py new file mode 100755 index 0000000000..251eeaf63a --- /dev/null +++ b/test/e2e/tests/test_self.py @@ -0,0 +1,12 @@ +import logging + +import allure + +import driver + +_logger = logging.getLogger(__name__) +pytestmark = allure.suite("Self") + + +def test_start_aut(): + driver.context.detach() diff --git a/test/e2e/tests/test_wallet.py b/test/e2e/tests/test_wallet.py new file mode 100644 index 0000000000..af6116fe1e --- /dev/null +++ b/test/e2e/tests/test_wallet.py @@ -0,0 +1,253 @@ +import time + +import allure +import pytest +from allure import step + +import configs.timeouts +import constants +import driver +from gui.components.authenticate_popup import AuthenticatePopup +from gui.components.signing_phrase_popup import SigningPhrasePopup +from gui.main_window import MainWindow + +pytestmark = allure.suite("Wallet") + + +@allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703021', 'Manage a saved address') +@pytest.mark.case(703021) +@pytest.mark.parametrize('name, address, new_name', [ + pytest.param('Saved address name before', '0x8397bc3c5a60a1883174f722403d63a8833312b7', 'Saved address name after'), + pytest.param('Ens name before', 'nastya.stateofus.eth', 'Ens name after') +]) +def test_manage_saved_address(main_screen: MainWindow, name: str, address: str, new_name: str): + with step('Add new address'): + wallet = main_screen.left_panel.open_wallet() + SigningPhrasePopup().wait_until_appears().confirm_phrase() + wallet.left_panel.open_saved_addresses().open_add_address_popup().add_saved_address(name, address) + + with step('Verify that saved address is in the list of saved addresses'): + assert driver.waitFor( + lambda: name in wallet.left_panel.open_saved_addresses().address_names, + configs.timeouts.UI_LOAD_TIMEOUT_MSEC), f'Address: {name} not found' + + with step('Edit saved address to new name'): + wallet.left_panel.open_saved_addresses().open_edit_address_popup(name).edit_saved_address(new_name, address) + + with step('Verify that saved address with new name is in the list of saved addresses'): + assert driver.waitFor( + lambda: new_name in wallet.left_panel.open_saved_addresses().address_names, + configs.timeouts.UI_LOAD_TIMEOUT_MSEC), f'Address: {new_name} not found' + + with step('Delete address with new name'): + wallet.left_panel.open_saved_addresses().delete_saved_address(new_name) + + with step('Verify that saved address with new name is not in the list of saved addresses'): + assert driver.waitFor( + lambda: new_name not in wallet.left_panel.open_saved_addresses().address_names, + configs.timeouts.UI_LOAD_TIMEOUT_MSEC), f'Address: {new_name} not found' + + +@allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703022', 'Edit default wallet account') +@pytest.mark.case(703022) +@pytest.mark.parametrize('name, new_name, new_color, new_emoji, new_emoji_unicode', [ + pytest.param('Status account', 'MyPrimaryAccount', '#216266', 'sunglasses', '1f60e') +]) +def test_edit_default_wallet_account(main_screen: MainWindow, name: str, new_name: str, new_color: str, new_emoji: str, + new_emoji_unicode: str): + with step('Select wallet account'): + wallet = main_screen.left_panel.open_wallet() + SigningPhrasePopup().wait_until_appears().confirm_phrase() + wallet.left_panel.select_account(name) + + with step('Edit wallet account'): + account_popup = wallet.left_panel.open_edit_account_popup(name) + account_popup.set_name(new_name).set_emoji(new_emoji).set_color(new_color).save() + + with step('Verify that the account is correctly displayed in accounts list'): + expected_account = constants.user.account_list_item(new_name, new_color.lower(), new_emoji_unicode) + started_at = time.monotonic() + while expected_account not in wallet.left_panel.accounts: + time.sleep(1) + if time.monotonic() - started_at > 15: + raise LookupError(f'Account {expected_account} not found in {wallet.left_panel.accounts}') + + +@allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703026', 'Manage a watch-only account') +@pytest.mark.case(703026) +@pytest.mark.parametrize('address, name, color, emoji, emoji_unicode, new_name, new_color,' + 'new_emoji, new_emoji_unicode', [ + pytest.param('0xea123F7beFF45E3C9fdF54B324c29DBdA14a639A', 'AccWatch1', '#2a4af5', + 'sunglasses', '1f60e', 'AccWatch1edited', '#216266', 'thumbsup', '1f44d') + ]) +def test_manage_watch_only_account(main_screen: MainWindow, address: str, color: str, emoji: str, emoji_unicode: str, + name: str, new_name: str, new_color: str, new_emoji: str, new_emoji_unicode: str): + with step('Create watch-only wallet account'): + wallet = main_screen.left_panel.open_wallet() + SigningPhrasePopup().wait_until_appears().confirm_phrase() + account_popup = wallet.left_panel.open_add_account_popup() + account_popup.set_name(name).set_emoji(emoji).set_color(color).set_origin_eth_address(address).save() + account_popup.wait_until_hidden() + + with step('Verify that the account is correctly displayed in accounts list'): + expected_account = constants.user.account_list_item(name, color.lower(), emoji_unicode) + started_at = time.monotonic() + while expected_account not in wallet.left_panel.accounts: + time.sleep(1) + if time.monotonic() - started_at > 15: + raise LookupError(f'Account {expected_account} not found in {wallet.left_panel.accounts}') + + with step('Delete wallet account'): + wallet.left_panel.delete_account(name).confirm() + + with step('Verify that the account is not displayed in accounts list'): + assert driver.waitFor(lambda: name not in [account.name for account in wallet.left_panel.accounts], 10000), \ + f'Account with {name} is still displayed even it should not be' + + with step('Create watch-only wallet account via context menu'): + account_popup = wallet.left_panel.open_add_watch_only_account_popup() + account_popup.set_name(name).set_emoji(emoji).set_color(color).set_eth_address(address).save() + account_popup.wait_until_hidden() + + with step('Edit wallet account'): + account_popup = wallet.left_panel.open_edit_account_popup(name) + account_popup.set_name(new_name).set_emoji(new_emoji).set_color(new_color).save() + + with step('Verify that the account is correctly displayed in accounts list'): + expected_account = constants.user.account_list_item(new_name, new_color.lower(), new_emoji_unicode) + started_at = time.monotonic() + while expected_account not in wallet.left_panel.accounts: + time.sleep(1) + if time.monotonic() - started_at > 15: + raise LookupError(f'Account {expected_account} not found in {wallet.left_panel.accounts}') + + +@allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703033', 'Manage a generated account') +@pytest.mark.case(703033) +@pytest.mark.parametrize('user_account', [constants.user.user_account_default]) +@pytest.mark.parametrize('name, color, emoji, emoji_unicode, ' + 'new_name, new_color, new_emoji, new_emoji_unicode', [ + pytest.param('GenAcc1', '#2a4af5', 'sunglasses', '1f60e', + 'GenAcc1edited', '#216266', 'thumbsup', '1f44d') + ]) +def test_manage_generated_account(main_screen: MainWindow, user_account, + color: str, emoji: str, emoji_unicode: str, + name: str, new_name: str, new_color: str, new_emoji: str, new_emoji_unicode: str): + with step('Create generated wallet account'): + wallet = main_screen.left_panel.open_wallet() + SigningPhrasePopup().wait_until_appears().confirm_phrase() + account_popup = wallet.left_panel.open_add_account_popup() + account_popup.set_name(name).set_emoji(emoji).set_color(color).save() + AuthenticatePopup().wait_until_appears().authenticate(user_account.password) + account_popup.wait_until_hidden() + + with step('Verify that the account is correctly displayed in accounts list'): + expected_account = constants.user.account_list_item(name, color.lower(), emoji_unicode) + started_at = time.monotonic() + while expected_account not in wallet.left_panel.accounts: + time.sleep(1) + if time.monotonic() - started_at > 15: + raise LookupError(f'Account {expected_account} not found in {wallet.left_panel.accounts}') + + with step('Edit wallet account'): + account_popup = wallet.left_panel.open_edit_account_popup(name) + account_popup.set_name(new_name).set_emoji(new_emoji).set_color(new_color).save() + + with step('Verify that the account is correctly displayed in accounts list'): + expected_account = constants.user.account_list_item(new_name, new_color.lower(), new_emoji_unicode) + started_at = time.monotonic() + while expected_account not in wallet.left_panel.accounts: + time.sleep(1) + if time.monotonic() - started_at > 15: + raise LookupError(f'Account {expected_account} not found in {wallet.left_panel.accounts}') + + with step('Delete wallet account with agreement'): + wallet.left_panel.delete_account(new_name).agree_and_confirm() + + with step('Verify that the account is not displayed in accounts list'): + assert driver.waitFor(lambda: new_name not in [account.name for account in wallet.left_panel.accounts], 10000), \ + f'Account with {new_name} is still displayed even it should not be' + + +@allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703028', 'Manage a custom generated account') +@pytest.mark.case(703028) +@pytest.mark.parametrize('user_account', [constants.user.user_account_default]) +@pytest.mark.parametrize('derivation_path, generated_address_index, name, color, emoji, emoji_unicode', [ + pytest.param('Ethereum', '5', 'Ethereum', '#216266', 'sunglasses', '1f60e'), + pytest.param('Ethereum Testnet (Ropsten)', '10', 'Ethereum Testnet ', '#7140fd', 'sunglasses', '1f60e'), + pytest.param('Ethereum (Ledger)', '15', 'Ethereum Ledger', '#2a799b', 'sunglasses', '1f60e'), + pytest.param('Ethereum (Ledger Live/KeepKey)', '20', 'Ethereum Ledger Live', '#7140fd', 'sunglasses', '1f60e'), + pytest.param('N/A', '95', 'Custom path', '#216266', 'sunglasses', '1f60e') +]) +def test_manage_custom_generated_account(main_screen: MainWindow, user_account, + derivation_path: str, generated_address_index: int, + name: str, color: str, emoji: str, emoji_unicode: str): + with step('Create generated wallet account'): + wallet = main_screen.left_panel.open_wallet() + SigningPhrasePopup().wait_until_appears().confirm_phrase() + account_popup = wallet.left_panel.open_add_account_popup() + account_popup.set_name(name).set_emoji(emoji).set_color(color).set_derivation_path(derivation_path, generated_address_index, user_account.password).save() + + with step('Verify that the account is correctly displayed in accounts list'): + expected_account = constants.user.account_list_item(name, color.lower(), emoji_unicode) + started_at = time.monotonic() + while expected_account not in wallet.left_panel.accounts: + time.sleep(1) + if time.monotonic() - started_at > 15: + raise LookupError(f'Account {expected_account} not found in {wallet.left_panel.accounts}') + + with step('Delete wallet account with agreement'): + wallet.left_panel.delete_account(name).agree_and_confirm() + + with step('Verify that the account is not displayed in accounts list'): + assert driver.waitFor(lambda: name not in [account.name for account in wallet.left_panel.accounts], 10000), \ + f'Account with {name} is still displayed even it should not be' + + +@allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703029', 'Manage a private key imported account') +@pytest.mark.case(703029) +@pytest.mark.parametrize('user_account', [constants.user.user_account_default]) +@pytest.mark.parametrize('name, color, emoji, emoji_unicode, ' + 'new_name, new_color, new_emoji, new_emoji_unicode, private_key', [ + pytest.param('PrivKeyAcc1', '#2a4af5', 'sunglasses', '1f60e', + 'PrivKeyAcc1edited', '#216266', 'thumbsup', '1f44d', + '2daa36a3abe381a9c01610bf10fda272fbc1b8a22179a39f782c512346e3e470') + ]) +def test_private_key_imported_account(main_screen: MainWindow, user_account, + name: str, color: str, emoji: str, emoji_unicode: str, + new_name: str, new_color: str, new_emoji: str, new_emoji_unicode: str, + private_key: str): + with step('Create generated wallet account'): + wallet = main_screen.left_panel.open_wallet() + SigningPhrasePopup().wait_until_appears().confirm_phrase() + account_popup = wallet.left_panel.open_add_account_popup() + account_popup.set_name(name).set_emoji(emoji).set_color(color).set_origin_private_key(private_key).save() + AuthenticatePopup().wait_until_appears().authenticate(user_account.password) + account_popup.wait_until_hidden() + + with step('Verify that the account is correctly displayed in accounts list'): + expected_account = constants.user.account_list_item(name, color.lower(), emoji_unicode) + started_at = time.monotonic() + while expected_account not in wallet.left_panel.accounts: + time.sleep(1) + if time.monotonic() - started_at > 15: + raise LookupError(f'Account {expected_account} not found in {wallet.left_panel.accounts}') + + with step('Edit wallet account'): + account_popup = wallet.left_panel.open_edit_account_popup(name) + account_popup.set_name(new_name).set_emoji(new_emoji).set_color(new_color).save() + + with step('Verify that the account is correctly displayed in accounts list'): + expected_account = constants.user.account_list_item(new_name, new_color.lower(), new_emoji_unicode) + started_at = time.monotonic() + while expected_account not in wallet.left_panel.accounts: + time.sleep(1) + if time.monotonic() - started_at > 15: + raise LookupError(f'Account {expected_account} not found in {wallet.left_panel.accounts}') + + with step('Delete wallet account with agreement'): + wallet.left_panel.delete_account(new_name).confirm() + + with step('Verify that the account is not displayed in accounts list'): + assert driver.waitFor(lambda: new_name not in [account.name for account in wallet.left_panel.accounts], 10000), \ + f'Account with {new_name} is still displayed even it should not be'