|
@ -1 +1,3 @@
|
||||||
# desktop-qa-automation
|
# desktop-qa-automation
|
||||||
|
|
||||||
|
https://www.notion.so/Setup-Environment-e5d88399027042a0992e85fd9b0e5167?pvs=4
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
LOG_LEVEL = logging.DEBUG
|
||||||
|
DEV_BUILD = False
|
||||||
|
|
||||||
|
APP_DIR = os.getenv('APP_DIR')
|
|
@ -0,0 +1,6 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
LOG_LEVEL = logging.DEBUG
|
||||||
|
DEV_BUILD = False
|
||||||
|
|
||||||
|
APP_DIR = None
|
|
@ -0,0 +1 @@
|
||||||
|
AUT_PORT = 61500
|
|
@ -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'
|
|
@ -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'
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,4 @@
|
||||||
|
from . import commands
|
||||||
|
from .colors import *
|
||||||
|
from .tesseract import *
|
||||||
|
from .user import *
|
|
@ -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]
|
||||||
|
]
|
||||||
|
}
|
|
@ -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'
|
|
@ -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'
|
|
@ -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'])
|
|
@ -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)'
|
|
@ -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)
|
|
@ -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}')
|
|
@ -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
|
|
@ -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')
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)])
|
|
@ -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))
|
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 759 KiB |
After Width: | Height: | Size: 1.3 MiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.1 KiB |
|
@ -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
|
|
@ -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('<Escape>')
|
||||||
|
self.wait_until_hidden()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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
|
|
@ -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()
|
|
@ -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()
|
|
@ -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)
|
|
@ -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()
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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()
|
|
@ -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)
|
|
@ -0,0 +1,10 @@
|
||||||
|
import allure
|
||||||
|
|
||||||
|
from .object import NativeObject
|
||||||
|
|
||||||
|
|
||||||
|
class Button(NativeObject):
|
||||||
|
|
||||||
|
@allure.step('Click {0}')
|
||||||
|
def click(self):
|
||||||
|
self.object.Press()
|
|
@ -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()
|
|
@ -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)
|
|
@ -0,0 +1,10 @@
|
||||||
|
import allure
|
||||||
|
|
||||||
|
from .object import NativeObject
|
||||||
|
|
||||||
|
|
||||||
|
class Button(NativeObject):
|
||||||
|
|
||||||
|
@allure.step('Click {0}')
|
||||||
|
def click(self):
|
||||||
|
super().click()
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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'
|
|
@ -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}')
|