diff --git a/test/appium/pytest.ini b/test/appium/pytest.ini index 3e06c88f43..a3f350dbbc 100644 --- a/test/appium/pytest.ini +++ b/test/appium/pytest.ini @@ -1,6 +1,6 @@ [pytest] norecursedirs = .git views -addopts = -s -v --tb=line --junitxml=result.xml +addopts = -s -v --tb=line --junitxml=result.xml --dist loadgroup -n 5 junit_family=legacy markers = critical: TCs by priority diff --git a/test/appium/requirements.txt b/test/appium/requirements.txt index ec8ee7d79b..5f75884004 100644 --- a/test/appium/requirements.txt +++ b/test/appium/requirements.txt @@ -30,9 +30,9 @@ pycryptodome==3.9.9 pyethash==0.1.27 pyparsing==2.4.7 pysha3==1.0.2 -pytest==6.1.2 +pytest==6.2.0 pytest-forked==1.3.0 -pytest-xdist==2.2.0 +pytest-xdist==2.5.0 python-dateutil==2.8.1 pytz==2020.4 PyYAML==5.4 diff --git a/test/appium/tests/atomic/chats/test_public.py b/test/appium/tests/atomic/chats/test_public.py index 8cfc09381b..478bc1d0ca 100644 --- a/test/appium/tests/atomic/chats/test_public.py +++ b/test/appium/tests/atomic/chats/test_public.py @@ -1,26 +1,35 @@ -import emoji import random -from dateutil import parser -from tests import marks -from tests.base_test_case import MultipleDeviceTestCase, SingleDeviceTestCase -from views.sign_in_view import SignInView from datetime import timedelta from time import sleep +import emoji +import pytest +from dateutil import parser -class TestPublicChatMultipleDevice(MultipleDeviceTestCase): +from tests import marks +from tests.base_test_case import MultipleDeviceTestCase, SingleDeviceTestCase, create_shared_drivers, \ + MultipleSharedDeviceTestCase +from views.home_view import HomeView +from views.sign_in_view import SignInView + + +@pytest.mark.xdist_group(name="public_chat") +class TestPublicChatMultipleDeviceMerged(MultipleSharedDeviceTestCase): + + @classmethod + def setup_class(cls): + cls.drivers, cls.loop = create_shared_drivers(2) + device_1, device_2 = SignInView(cls.drivers[0]), SignInView(cls.drivers[1]) + home_1, home_2 = device_1.create_user(), device_2.create_user() + profile_1 = home_1.profile_button.click() + cls.username_1 = profile_1.default_username_text.text + profile_1.home_button.click() + home_2.home_button.click() @marks.testrail_id(5313) @marks.critical def test_public_chat_messaging_emojis_timestamps(self): - self.create_drivers(2) - device_1, device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1]) - home_1, home_2 = device_1.create_user(), device_2.create_user() - profile_1 = home_1.profile_button.click() - default_username_1 = profile_1.default_username_text.text - profile_1.home_button.click() - home_2.home_button.click() - + home_1, home_2 = HomeView(self.drivers[0]), HomeView(self.drivers[1]) home_1.just_fyi("Check preselected chats, redirect to status chat") home_1.plus_button.click_until_presence_of_element(home_1.join_public_chat_button) home_1.join_public_chat_button.click() @@ -50,8 +59,8 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase): if timestamp not in sent_time_variants: self.errors.append( "Timestamp is not shown, expected '%s', in fact '%s'" % (sent_time_variants.join(','), timestamp)) - if chat_2.chat_element_by_text(message).username.text != default_username_1: - self.errors.append("Default username '%s' is not shown next to the received message" % default_username_1) + if chat_2.chat_element_by_text(message).username.text != self.username_1: + self.errors.append("Default username '%s' is not shown next to the received message" % self.username_1) chat_1.send_message(emoji_message) for chat in chat_1, chat_2: @@ -63,13 +72,11 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase): @marks.testrail_id(5360) @marks.critical def test_unread_messages_counter_public_chat(self): - self.create_drivers(2) - driver_2 = self.drivers[1] - home_1, home_2 = SignInView(self.drivers[0]).create_user(), SignInView(self.drivers[1]).create_user() - profile_1 = home_1.profile_button.click() - username_1 = profile_1.default_username_text.text - profile_1.home_button.click() - + home_1, home_2 = HomeView(self.drivers[0]), HomeView(self.drivers[1]) + home_1.get_back_to_home_view() + home_2.get_back_to_home_view() + home_1.home_button.click() + home_2.home_button.click() chat_name = home_1.get_random_chat_name() chat_1, chat_2 = home_1.join_public_chat(chat_name), home_2.join_public_chat(chat_name) chat_1.send_message('пиу') @@ -87,7 +94,7 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase): home_1.just_fyi("Check unread message counter when mentioned in public chat") chat_2 = home_2.get_chat_view() - chat_2.select_mention_from_suggestion_list(username_1, username_1[:2]) + chat_2.select_mention_from_suggestion_list(self.username_1, self.username_1[:2]) chat_2.send_message_button.click() chat_element.new_messages_counter.wait_for_element(30) chat_element.new_messages_counter.wait_for_element_text("1", 60) @@ -104,6 +111,7 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase): chat_2.chat_element_by_text(message_2).wait_for_element(20) home_2.just_fyi("Check that unread message indicator is not reappeared after relogin") + driver_2 = self.drivers[1] driver_2.close_app() driver_2.launch_app() SignInView(driver_2).sign_in() @@ -112,6 +120,9 @@ class TestPublicChatMultipleDevice(MultipleDeviceTestCase): self.errors.append('New messages counter is shown after relogin') self.errors.verify_no_errors() + +class TestPublicChatMultipleDevice(MultipleDeviceTestCase): + @marks.testrail_id(6270) @marks.medium def test_mark_all_messages_as_read_public_chat(self): diff --git a/test/appium/tests/base_test_case.py b/test/appium/tests/base_test_case.py index fd84c58cc0..2556371c11 100644 --- a/test/appium/tests/base_test_case.py +++ b/test/appium/tests/base_test_case.py @@ -19,47 +19,89 @@ from tests import test_suite_data, start_threads, appium_container, pytest_confi import base64 from re import findall +sauce_username = environ.get('SAUCE_USERNAME') + + +sauce_access_key = environ.get('SAUCE_ACCESS_KEY') + +executor_sauce_lab = 'http://%s:%s@ondemand.saucelabs.com:80/wd/hub' % (sauce_username, sauce_access_key) + +executor_local = 'http://localhost:4723/wd/hub' + +implicit_wait = 5 + + +def get_capabilities_local(): + desired_caps = dict() + if pytest_config_global['docker']: + # apk is in shared volume directory + apk = '/root/shared_volume/%s' % pytest_config_global['apk'] + else: + apk = pytest_config_global['apk'] + desired_caps['app'] = apk + desired_caps['deviceName'] = 'nexus_5' + desired_caps['platformName'] = 'Android' + desired_caps['appiumVersion'] = '1.9.1' + desired_caps['platformVersion'] = '10.0' + desired_caps['newCommandTimeout'] = 600 + desired_caps['fullReset'] = False + desired_caps['unicodeKeyboard'] = True + desired_caps['automationName'] = 'UiAutomator2' + desired_caps['setWebContentDebuggingEnabled'] = True + return desired_caps + + +def add_local_devices_to_capabilities(): + updated_capabilities = list() + raw_out = re.split(r'[\r\\n]+', str(subprocess.check_output(['adb', 'devices'])).rstrip()) + for line in raw_out[1:]: + serial = re.findall(r"(([\d.\d:]*\d+)|\bemulator-\d+)", line) + if serial: + capabilities = get_capabilities_local() + capabilities['udid'] = serial[0][0] + updated_capabilities.append(capabilities) + return updated_capabilities + + +def get_capabilities_sauce_lab(): + desired_caps = dict() + desired_caps['app'] = 'sauce-storage:' + test_suite_data.apk_name + + desired_caps['build'] = pytest_config_global['build'] + desired_caps['name'] = test_suite_data.current_test.name + desired_caps['platformName'] = 'Android' + desired_caps['appiumVersion'] = '1.18.1' + desired_caps['platformVersion'] = '10.0' + desired_caps['deviceName'] = 'Android GoogleAPI Emulator' + desired_caps['deviceOrientation'] = "portrait" + desired_caps['commandTimeout'] = 600 + desired_caps['idleTimeout'] = 600 + desired_caps['unicodeKeyboard'] = True + desired_caps['automationName'] = 'UiAutomator2' + desired_caps['setWebContentDebuggingEnabled'] = True + desired_caps['ignoreUnimportantViews'] = False + desired_caps['enableNotificationListener'] = True + desired_caps['maxDuration'] = 1800 + return desired_caps + + +def update_capabilities_sauce_lab(new_capabilities: dict): + caps = get_capabilities_sauce_lab().copy() + caps.update(new_capabilities) + return caps + class AbstractTestCase: __metaclass__ = ABCMeta - @property - def sauce_username(self): - return environ.get('SAUCE_USERNAME') - - @property - def sauce_access_key(self): - return environ.get('SAUCE_ACCESS_KEY') - - @property - def executor_sauce_lab(self): - return 'http://%s:%s@ondemand.saucelabs.com:80/wd/hub' % (self.sauce_username, self.sauce_access_key) - - @property - def executor_local(self): - return 'http://localhost:4723/wd/hub' - - @staticmethod - def print_sauce_lab_info(driver): + def print_sauce_lab_info(self, driver): sys.stdout = sys.stderr print("SauceOnDemandSessionID=%s job-name=%s" % (driver.session_id, pytest_config_global['build'])) - @staticmethod - def get_translation_by_key(key): + def get_translation_by_key(self, key): return transl[key] - def add_local_devices_to_capabilities(self): - updated_capabilities = list() - raw_out = re.split(r'[\r\\n]+', str(subprocess.check_output(['adb', 'devices'])).rstrip()) - for line in raw_out[1:]: - serial = re.findall(r"(([\d.\d:]*\d+)|\bemulator-\d+)", line) - if serial: - capabilities = self.capabilities_local - capabilities['udid'] = serial[0][0] - updated_capabilities.append(capabilities) - return updated_capabilities - @property def app_path(self): app_path = '/storage/emulated/0/Android/data/im.status.ethereum.pr/files/Download/' if findall(r'pr\d\d\d\d\d', @@ -71,53 +113,6 @@ class AbstractTestCase: def geth_path(self): return self.app_path + 'geth.log' - @property - def capabilities_sauce_lab(self): - desired_caps = dict() - desired_caps['app'] = 'sauce-storage:' + test_suite_data.apk_name - - desired_caps['build'] = pytest_config_global['build'] - desired_caps['name'] = test_suite_data.current_test.name - desired_caps['platformName'] = 'Android' - desired_caps['appiumVersion'] = '1.18.1' - desired_caps['platformVersion'] = '10.0' - desired_caps['deviceName'] = 'Android GoogleAPI Emulator' - desired_caps['deviceOrientation'] = "portrait" - desired_caps['commandTimeout'] = 600 - desired_caps['idleTimeout'] = 600 - desired_caps['unicodeKeyboard'] = True - desired_caps['automationName'] = 'UiAutomator2' - desired_caps['setWebContentDebuggingEnabled'] = True - desired_caps['ignoreUnimportantViews'] = False - desired_caps['enableNotificationListener'] = True - desired_caps['maxDuration'] = 1800 - return desired_caps - - def update_capabilities_sauce_lab(self, new_capabilities: dict): - caps = self.capabilities_sauce_lab.copy() - caps.update(new_capabilities) - return caps - - @property - def capabilities_local(self): - desired_caps = dict() - if pytest_config_global['docker']: - # apk is in shared volume directory - apk = '/root/shared_volume/%s' % pytest_config_global['apk'] - else: - apk = pytest_config_global['apk'] - desired_caps['app'] = apk - desired_caps['deviceName'] = 'nexus_5' - desired_caps['platformName'] = 'Android' - desired_caps['appiumVersion'] = '1.9.1' - desired_caps['platformVersion'] = pytest_config_global['platform_version'] - desired_caps['newCommandTimeout'] = 600 - desired_caps['fullReset'] = False - desired_caps['unicodeKeyboard'] = True - desired_caps['automationName'] = 'UiAutomator2' - desired_caps['setWebContentDebuggingEnabled'] = True - return desired_caps - @abstractmethod def setup_method(self, method): raise NotImplementedError('Should be overridden from a child class') @@ -130,10 +125,6 @@ class AbstractTestCase: def environment(self): return pytest_config_global['env'] - @property - def implicitly_wait(self): - return 5 - network_api = NetworkApi() github_report = GithubHtmlReport() @@ -197,13 +188,13 @@ class SingleDeviceTestCase(AbstractTestCase): appium_container.start_appium_container(pytest_config_global['docker_shared_volume']) appium_container.connect_device(pytest_config_global['device_ip']) - (executor, capabilities) = (self.executor_sauce_lab, self.capabilities_sauce_lab) if \ - self.environment == 'sauce' else (self.executor_local, self.capabilities_local) + (executor, capabilities) = (executor_sauce_lab, get_capabilities_sauce_lab()) if \ + self.environment == 'sauce' else (executor_local, get_capabilities_local()) for key, value in kwargs.items(): capabilities[key] = value self.driver = Driver(executor, capabilities) test_suite_data.current_test.testruns[-1].jobs[self.driver.session_id] = 1 - self.driver.implicitly_wait(self.implicitly_wait) + self.driver.implicitly_wait(implicit_wait) self.errors = Errors() if pytest_config_global['docker']: @@ -241,7 +232,7 @@ class LocalMultipleDeviceTestCase(AbstractTestCase): def teardown_method(self, method): for driver in self.drivers: try: - self.add_alert_text_to_report(driver) + self.add_alert_text_to_report(self.drivers[driver]) self.drivers[driver].quit() except WebDriverException: pass @@ -263,12 +254,12 @@ class SauceMultipleDeviceTestCase(AbstractTestCase): self.drivers = self.loop.run_until_complete(start_threads(quantity, Driver, self.drivers, - self.executor_sauce_lab, - self.update_capabilities_sauce_lab(capabilities))) + executor_sauce_lab, + update_capabilities_sauce_lab(capabilities))) for driver in range(quantity): test_suite_data.current_test.testruns[-1].jobs[self.drivers[driver].session_id] = driver + 1 self.drivers[driver].implicitly_wait( - custom_implicitly_wait if custom_implicitly_wait else self.implicitly_wait) + custom_implicitly_wait if custom_implicitly_wait else implicit_wait) def teardown_method(self, method): geth_names, geth_contents = [], [] @@ -290,10 +281,97 @@ class SauceMultipleDeviceTestCase(AbstractTestCase): cls.loop.close() +def create_shared_drivers(quantity): + drivers = dict() + if pytest_config_global['env'] == 'local': + capabilities = add_local_devices_to_capabilities() + for i in range(quantity): + driver = Driver(executor_local, capabilities[i]) + test_suite_data.current_test.testruns[-1].jobs[driver.session_id] = i + 1 + driver.implicitly_wait(implicit_wait) + drivers[i] = driver + loop = None + else: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + capabilities = {'maxDuration': 1800} + drivers = loop.run_until_complete(start_threads(quantity, + Driver, + drivers, + executor_sauce_lab, + update_capabilities_sauce_lab(capabilities))) + for i in range(quantity): + test_suite_data.current_test.testruns[-1].jobs[drivers[i].session_id] = i + 1 + drivers[i].implicitly_wait(implicit_wait) + return drivers, loop + + +class LocalSharedMultipleDeviceTestCase(AbstractTestCase): + + def setup_method(self, method): + jobs = test_suite_data.current_test.testruns[-1].jobs + if not jobs: + for index, driver in self.drivers.items(): + jobs[driver.session_id] = index + 1 + self.errors = Errors() + + def teardown_method(self, method): + for driver in self.drivers: + try: + self.add_alert_text_to_report(self.drivers[driver]) + except WebDriverException: + pass + + @classmethod + def teardown_class(cls): + for driver in cls.drivers: + try: + cls.drivers[driver].quit() + except WebDriverException: + pass + + +class SauceSharedMultipleDeviceTestCase(AbstractTestCase): + + def setup_method(self, method): + jobs = test_suite_data.current_test.testruns[-1].jobs + if not jobs: + for index, driver in self.drivers.items(): + jobs[driver.session_id] = index + 1 + self.errors = Errors() + + def teardown_method(self, method): + geth_names, geth_contents = [], [] + for driver in self.drivers: + try: + self.print_sauce_lab_info(self.drivers[driver]) + self.add_alert_text_to_report(self.drivers[driver]) + geth_names.append( + '%s_geth%s.log' % (test_suite_data.current_test.name, str(self.drivers[driver].number))) + geth_contents.append(self.pull_geth(self.drivers[driver])) + + except (WebDriverException, AttributeError): + pass + finally: + geth = {geth_names[i]: geth_contents[i] for i in range(len(geth_names))} + self.github_report.save_test(test_suite_data.current_test, geth) + + @classmethod + def teardown_class(cls): + for driver in cls.drivers: + try: + cls.drivers[driver].quit() + except WebDriverException: + pass + cls.loop.close() + + if pytest_config_global['env'] == 'local': MultipleDeviceTestCase = LocalMultipleDeviceTestCase + MultipleSharedDeviceTestCase = LocalSharedMultipleDeviceTestCase else: MultipleDeviceTestCase = SauceMultipleDeviceTestCase + MultipleSharedDeviceTestCase = SauceSharedMultipleDeviceTestCase class NoDeviceTestCase(AbstractTestCase): diff --git a/test/appium/tests/conftest.py b/test/appium/tests/conftest.py index 68a7aca1b8..53b064de01 100644 --- a/test/appium/tests/conftest.py +++ b/test/appium/tests/conftest.py @@ -282,7 +282,8 @@ def pytest_runtest_protocol(item, nextitem): for i in range(rerun_count): reports = runtestprotocol(item, nextitem=nextitem) for report in reports: - if report.failed and should_rerun_test(report.longreprtext): + is_in_group = [i for i in item.iter_markers(name='xdist_group')] + if report.failed and should_rerun_test(report.longreprtext) and not is_in_group: break # rerun else: return True # no need to rerun