From 735ab61d4a3668e1a7ef074c17ce9e2eeb606f48 Mon Sep 17 00:00:00 2001 From: Yevheniia Berdnyk Date: Thu, 24 Oct 2024 01:15:05 +0300 Subject: [PATCH] e2e: LambdaTest trial --- ci/tests/Jenkinsfile.e2e-prs | 5 + test/appium/support/lambda_test.py | 28 +++++ test/appium/tests/base_test_case.py | 103 ++++++++++++----- test/appium/tests/conftest.py | 107 ++++++++---------- .../critical/chats/test_1_1_public_chats.py | 1 + .../tests/critical/chats/test_group_chat.py | 1 + test/appium/tests/marks.py | 2 + test/appium/views/base_view.py | 2 +- 8 files changed, 159 insertions(+), 90 deletions(-) create mode 100644 test/appium/support/lambda_test.py diff --git a/ci/tests/Jenkinsfile.e2e-prs b/ci/tests/Jenkinsfile.e2e-prs index 844a0eb549..8bfa64ed6f 100644 --- a/ci/tests/Jenkinsfile.e2e-prs +++ b/ci/tests/Jenkinsfile.e2e-prs @@ -115,6 +115,11 @@ pipeline { usernameVariable: 'SAUCE_USERNAME', passwordVariable: 'SAUCE_ACCESS_KEY' ), + usernamePassword( + credentialsId: 'lambda-test-api', + usernameVariable: 'LAMBDA_TEST_USERNAME', + passwordVariable: 'LAMBDA_TEST_ACCESS_KEY' + ), string( credentialsId: 'etherscan-api-key', variable: 'ETHERSCAN_API_KEY' diff --git a/test/appium/support/lambda_test.py b/test/appium/support/lambda_test.py new file mode 100644 index 0000000000..458c54d875 --- /dev/null +++ b/test/appium/support/lambda_test.py @@ -0,0 +1,28 @@ +import requests + +from conftest import lambda_test_username, lambda_test_access_key +from tests import test_suite_data + +session = requests.Session() +session.auth = (lambda_test_username, lambda_test_access_key) + + +def upload_apk(apk_file_path): + resp = session.post( + # url="https://manual-api.lambdatest.com/app/upload/realDevice", + url="https://manual-api.lambdatest.com/app/upload/virtualDevice", + files={'appFile': open(apk_file_path, 'rb')}, + data={'name': test_suite_data.apk_name} + ) + assert resp.status_code == 200 + return resp.json()['app_url'] + + +def update_session(session_id, session_name, status): + resp = session.get( + url="https://mobile-api.lambdatest.com/mobile-automation/api/v1/sessions/%s" % session_id, + data={ + "name": session_name #, "status_ind": status + } + ) + assert resp.status_code == 200 diff --git a/test/appium/tests/base_test_case.py b/test/appium/tests/base_test_case.py index bc9e016368..6041b3874a 100644 --- a/test/appium/tests/base_test_case.py +++ b/test/appium/tests/base_test_case.py @@ -19,9 +19,11 @@ from urllib3.exceptions import MaxRetryError, ProtocolError from support.api.network_api import NetworkApi from tests import test_suite_data, start_threads, appium_container, pytest_config_global, transl -from tests.conftest import sauce_username, sauce_access_key, apibase, github_report, run_name +from tests.conftest import sauce_username, sauce_access_key, apibase, github_report, run_name, option, \ + lambda_test_username, lambda_test_access_key executor_sauce_lab = 'https://%s:%s@ondemand.%s:443/wd/hub' % (sauce_username, sauce_access_key, apibase) +executor_lambda_test = 'https://%s:%s@mobile-hub.lambdatest.com/wd/hub' % (lambda_test_username, lambda_test_access_key) executor_local = 'http://localhost:4723/wd/hub' @@ -88,7 +90,6 @@ def get_capabilities_sauce_lab(): caps['sauce:options']['name'] = test_suite_data.current_test.name caps['sauce:options']['maxDuration'] = 3600 caps['sauce:options']['idleTimeout'] = 1000 - # caps['sauce:options']['android.gpu.mode'] = 'hardware' options = AppiumOptions() options.load_capabilities(caps) @@ -96,10 +97,50 @@ def get_capabilities_sauce_lab(): return options -# def update_capabilities_sauce_lab(new_capabilities: dict): -# caps = get_capabilities_sauce_lab().copy() -# caps.update(new_capabilities) -# return caps +def get_lambda_test_capabilities_real_device(): + capabilities = { + "lt:options": { + "w3c": True, + "platformName": "android", + "deviceName": "Pixel 8", + "platformVersion": "14", + "app": "lt://APP10160471311729636675434695", # lambda_test_apk_url, + "devicelog": True, + "visual": True, + # "network": True, + "video": True, + "build": run_name, + "name": test_suite_data.current_test.group_name, + "idleTimeout": 1000, + "isRealMobile": True + } + } + options = AppiumOptions() + options.load_capabilities(capabilities) + return options + + +def get_lambda_test_capabilities_emulator(): + capabilities = { + "lt:options": { + "w3c": True, + "platformName": "android", + "deviceName": "Pixel 6", + "appiumVersion": "2.11.0", + "platformVersion": "14", + "app": "lt://APP10160522181729681886587724", #option.lambda_test_apk_url, + "devicelog": True, + "visual": True, + # "network": True, + "video": True, + "build": run_name, + "name": test_suite_data.current_test.group_name, + "idleTimeout": 1000 + } + } + options = AppiumOptions() + options.load_capabilities(capabilities) + return options def get_app_path(): @@ -305,14 +346,15 @@ def create_shared_drivers(quantity): else: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - print('SC Executor: %s' % executor_sauce_lab) + print('LT Executor: %s' % executor_lambda_test) try: drivers = loop.run_until_complete(start_threads(test_suite_data.current_test.name, quantity, Driver, drivers, - command_executor=executor_sauce_lab, - options=get_capabilities_sauce_lab())) + command_executor=executor_lambda_test, + options=get_lambda_test_capabilities_emulator())) + # options=get_lambda_test_capabilities_real_device())) if len(drivers) < quantity: test_suite_data.current_test.testruns[-1].error = "Not all %s drivers are created" % quantity @@ -368,8 +410,8 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase): def setup_method(self, method): if not self.drivers: pytest.fail(test_suite_data.current_test.testruns[-1].error) - for _, driver in self.drivers.items(): - driver.execute_script("sauce:context=Started %s" % method.__name__) + # for _, driver in self.drivers.items(): + # driver.execute_script("sauce:context=Started %s" % method.__name__) jobs = test_suite_data.current_test.testruns[-1].jobs if not jobs: for index, driver in self.drivers.items(): @@ -400,6 +442,7 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase): @pytest.fixture(scope='class', autouse=True) def prepare(self, request): + test_suite_data.current_test.group_name = request.cls.__name__ try: request.cls.prepare_devices(request) finally: @@ -410,7 +453,7 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase): def teardown_class(cls): from tests.conftest import sauce requests_session = requests.Session() - requests_session.auth = (sauce_username, sauce_access_key) + requests_session.auth = (lambda_test_username, lambda_test_access_key) if test_suite_data.tests[0].testruns[-1].error and 'setup failed' in test_suite_data.tests[0].testruns[ -1].error: group_setup_failed = True @@ -425,28 +468,34 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase): log_contents.append(pull_requests_log(driver=driver)) log_names.append('%s_requests%s.log' % (cls.__name__, i)) session_id = driver.session_id + from support.lambda_test import update_session try: - sauce.jobs.update_job(username=sauce_username, job_id=session_id, name=cls.__name__) + # sauce.jobs.update_job(username=sauce_username, job_id=session_id, name=cls.__name__) + update_session( + session_id=session_id, + session_name=cls.__name__, + status="failed" if group_setup_failed else "passed" + ) except (RemoteDisconnected, SauceException, requests.exceptions.ConnectionError): pass try: driver.quit() except WebDriverException: pass - url = 'https://api.%s/rest/v1/%s/jobs/%s/assets/%s' % (apibase, sauce_username, session_id, "log.json") - try: - WebDriverWait(driver, 60, 2).until(lambda _: requests_session.get(url).status_code == 200) - commands = requests_session.get(url).json() - for command in commands: - try: - if command['message'].startswith("Started "): - for test in test_suite_data.tests: - if command['message'] == "Started %s" % test.name: - test.testruns[-1].first_commands[session_id] = commands.index(command) + 1 - except KeyError: - continue - except (RemoteDisconnected, requests.exceptions.ConnectionError, TimeoutException): - pass + # url = 'https://api.%s/rest/v1/%s/jobs/%s/assets/%s' % (apibase, sauce_username, session_id, "log.json") + # try: + # WebDriverWait(driver, 60, 2).until(lambda _: requests_session.get(url).status_code == 200) + # commands = requests_session.get(url).json() + # for command in commands: + # try: + # if command['message'].startswith("Started "): + # for test in test_suite_data.tests: + # if command['message'] == "Started %s" % test.name: + # test.testruns[-1].first_commands[session_id] = commands.index(command) + 1 + # except KeyError: + # continue + # except (RemoteDisconnected, requests.exceptions.ConnectionError, TimeoutException): + # pass except AttributeError: pass finally: diff --git a/test/appium/tests/conftest.py b/test/appium/tests/conftest.py index a100d43044..b306b87873 100644 --- a/test/appium/tests/conftest.py +++ b/test/appium/tests/conftest.py @@ -20,6 +20,10 @@ from tests import test_suite_data, appium_container sauce_username = environ.get('SAUCE_USERNAME') sauce_access_key = environ.get('SAUCE_ACCESS_KEY') + +lambda_test_username = environ.get('LAMBDA_TEST_USERNAME') +lambda_test_access_key = environ.get('LAMBDA_TEST_ACCESS_KEY') + github_token = environ.get('GIT_HUB_TOKEN') @@ -141,6 +145,7 @@ def pytest_addoption(parser): @dataclass class Option: datacenter: str = None + lambda_test_apk_url: str = None option = Option() @@ -151,15 +156,19 @@ sauce = None run_name = None +# lambda_test_apk_url = None + + def is_master(config): return not hasattr(config, 'workerinput') def is_uploaded(): - stored_files = sauce.storage.files() - for i in range(len(stored_files)): - if stored_files[i].name == test_suite_data.apk_name: - return True + return False # ToDo: add verification + # stored_files = sauce.storage.files() + # for i in range(len(stored_files)): + # if stored_files[i].name == test_suite_data.apk_name: + # return True @contextmanager @@ -182,21 +191,22 @@ class UploadApkException(Exception): def _upload_and_check_response(apk_file_path): + from support.lambda_test import upload_apk with _upload_time_limit(1000): - resp = sauce.storage.upload(apk_file_path) - - try: - if resp.name != test_suite_data.apk_name: - raise UploadApkException("Incorrect apk was uploaded to Sauce storage, response:\n%s" % resp) - except AttributeError: - raise UploadApkException("Error when uploading apk to Sauce storage, response:\n%s" % resp) + # # resp = sauce.storage.upload(apk_file_path) + return upload_apk(apk_file_path) + # try: + # if resp.name != test_suite_data.apk_name: + # raise UploadApkException("Incorrect apk was uploaded to Sauce storage, response:\n%s" % resp) + # except AttributeError: + # raise UploadApkException("Error when uploading apk to Sauce storage, response:\n%s" % resp) def _upload_and_check_response_with_retries(apk_file_path, retries=3): for _ in range(retries): try: - _upload_and_check_response(apk_file_path) - break + return _upload_and_check_response(apk_file_path) + # break except (ConnectionError, RemoteDisconnected, c_er): time.sleep(10) @@ -254,8 +264,8 @@ def pytest_configure(config): else: raise NotImplementedError("Unknown SauceLabs datacenter") - global sauce - sauce = SauceLab('https://api.' + apibase + '/', sauce_username, sauce_access_key) + # global sauce + # sauce = SauceLab('https://api.' + apibase + '/', sauce_username, sauce_access_key) if config.getoption('log_steps'): import logging logging.basicConfig(level=logging.INFO) @@ -271,6 +281,18 @@ def pytest_configure(config): else: run_name = get_run_name(config, new_one=False) + if is_master(config): + apk_src = config.getoption('apk') + if apk_src.startswith('http'): + apk_path = _download_apk(apk_src) + else: + apk_path = apk_src + + # global lambda_test_apk_url + option.lambda_test_apk_url = _upload_and_check_response(apk_path) + if apk_src.startswith('http'): + os.remove(apk_path) + if not is_master(config): return @@ -285,16 +307,9 @@ def pytest_configure(config): description='e2e tests are running' ) - if config.getoption('env') == 'sauce' and not is_uploaded(): - apk_src = config.getoption('apk') - if apk_src.startswith('http'): - apk_path = _download_apk(apk_src) - else: - apk_path = apk_src - _upload_and_check_response_with_retries(apk_path) - if apk_src.startswith('http'): - os.remove(apk_path) +def pytest_configure_node(node): + node.workerinput['lambda_test_apk_url'] = node.config.option.lambda_test_apk_url def pytest_unconfigure(config): @@ -366,10 +381,10 @@ def pytest_runtest_makereport(item, call): test_suite_data.current_test.group_name = item.instance.__class__.__name__ error = catch_error() final_error = '%s %s' % (error_intro, error) - if is_sauce_env: - update_sauce_jobs(test_suite_data.current_test.group_name, - test_suite_data.current_test.testruns[-1].jobs, - report.passed) + # if is_sauce_env: + # update_sauce_jobs(test_suite_data.current_test.group_name, + # test_suite_data.current_test.testruns[-1].jobs, + # report.passed) if error: test_suite_data.current_test.testruns[-1].error = final_error github_report.save_test(test_suite_data.current_test) @@ -386,8 +401,8 @@ def pytest_runtest_makereport(item, call): current_test.testruns[-1].run = False if error: current_test.testruns[-1].error = '%s [[%s]]' % (error, report.wasxfail) - if is_sauce_env: - update_sauce_jobs(current_test.name, current_test.testruns[-1].jobs, report.passed) + # if is_sauce_env: + # update_sauce_jobs(current_test.name, current_test.testruns[-1].jobs, report.passed) if item.config.getoption('docker'): device_stats = appium_container.get_device_stats() if item.config.getoption('bugreport'): @@ -450,35 +465,3 @@ def pytest_runtest_protocol(item, nextitem): break # rerun else: return True # no need to rerun - - -# @pytest.fixture(scope="session", autouse=False) -# def faucet_for_senders(): -# network_api = NetworkApi() -# for user in transaction_senders.values(): -# network_api.faucet(address=user['address']) - - -@pytest.fixture -def messages_number(request): - return int(request.config.getoption('messages_number')) - - -@pytest.fixture -def message_wait_time(request): - return int(request.config.getoption('message_wait_time')) - - -@pytest.fixture -def participants_number(request): - return int(request.config.getoption('participants_number')) - - -@pytest.fixture -def chat_name(request): - return request.config.getoption('chat_name') - - -@pytest.fixture -def user_public_key(request): - return request.config.getoption('user_public_key') diff --git a/test/appium/tests/critical/chats/test_1_1_public_chats.py b/test/appium/tests/critical/chats/test_1_1_public_chats.py index 4233776f2f..dbe95b3463 100644 --- a/test/appium/tests/critical/chats/test_1_1_public_chats.py +++ b/test/appium/tests/critical/chats/test_1_1_public_chats.py @@ -14,6 +14,7 @@ from views.sign_in_view import SignInView @pytest.mark.xdist_group(name="new_one_2") @marks.nightly +@marks.lt class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase): def prepare_devices(self): diff --git a/test/appium/tests/critical/chats/test_group_chat.py b/test/appium/tests/critical/chats/test_group_chat.py index e6ca294da7..4506d594bb 100644 --- a/test/appium/tests/critical/chats/test_group_chat.py +++ b/test/appium/tests/critical/chats/test_group_chat.py @@ -13,6 +13,7 @@ from views.sign_in_view import SignInView @pytest.mark.xdist_group(name="new_one_3") @marks.nightly +@marks.lt class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase): def prepare_devices(self): diff --git a/test/appium/tests/marks.py b/test/appium/tests/marks.py index 2addc25340..3abb6c6af0 100644 --- a/test/appium/tests/marks.py +++ b/test/appium/tests/marks.py @@ -13,3 +13,5 @@ upgrade = pytest.mark.upgrade skip = pytest.mark.skip xfail = pytest.mark.xfail secured = pytest.mark.secured + +lt = pytest.mark.lt # temp diff --git a/test/appium/views/base_view.py b/test/appium/views/base_view.py index 60c833db81..68ce150664 100644 --- a/test/appium/views/base_view.py +++ b/test/appium/views/base_view.py @@ -410,7 +410,7 @@ class BaseView(object): def just_fyi(self, some_str): self.driver.info('# STEP: %s' % some_str, device=False) - self.driver.execute_script("sauce:context=STEP: %s" % some_str) + # self.driver.execute_script("sauce:context=STEP: %s" % some_str) def hide_keyboard_if_shown(self): if self.driver.is_keyboard_shown():