458 lines
17 KiB
Python
458 lines
17 KiB
Python
import asyncio
|
|
import base64
|
|
import logging
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from abc import ABCMeta, abstractmethod
|
|
from http.client import RemoteDisconnected
|
|
|
|
import pytest
|
|
import requests
|
|
from appium import webdriver
|
|
from appium.options.common import AppiumOptions
|
|
from appium.webdriver.common.mobileby import MobileBy
|
|
from appium.webdriver.connectiontype import ConnectionType
|
|
from selenium.common.exceptions import NoSuchElementException, WebDriverException
|
|
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 github_report, run_name, lambda_test_username, lambda_test_access_key
|
|
|
|
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'
|
|
|
|
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_lambda_test_capabilities_real_device():
|
|
capabilities = {
|
|
"lt:options": {
|
|
"w3c": True,
|
|
"platformName": "android",
|
|
"deviceName": "Pixel 8",
|
|
"platformVersion": "14",
|
|
"app": pytest_config_global['lt_apk_url'],
|
|
"devicelog": True,
|
|
"visual": 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.1.3",
|
|
"platformVersion": "14",
|
|
"app": pytest_config_global['lt_apk_url'],
|
|
"devicelog": True,
|
|
"visual": True,
|
|
"video": True,
|
|
"build": run_name,
|
|
"name": test_suite_data.current_test.group_name,
|
|
"idleTimeout": 1000,
|
|
# "enableImageInjection": True,
|
|
# "uploadMedia": ["lt://MEDIA2b3e34e2b0ee4928b9fc38c603f98191",], # "lt://MEDIAcfc1b4f1af0740759254404186bbe4f1"]
|
|
},
|
|
"appium:options": {
|
|
"automationName": "UiAutomator2",
|
|
"hideKeyboard": True
|
|
}
|
|
}
|
|
options = AppiumOptions()
|
|
options.load_capabilities(capabilities)
|
|
return options
|
|
|
|
|
|
def get_app_path():
|
|
app_folder = 'im.status.ethereum'
|
|
apk = pytest_config_global['apk']
|
|
if re.findall(r'pr\d\d\d\d\d', apk) or re.findall(r'\d\d\d\d\d.apk', apk):
|
|
app_folder += '.pr'
|
|
app_path = '/storage/emulated/0/Android/data/%s/files/Download/' % app_folder
|
|
return app_path
|
|
|
|
|
|
def pull_geth(driver):
|
|
result = driver.pull_file(get_app_path() + 'geth.log')
|
|
return base64.b64decode(result)
|
|
|
|
|
|
def pull_requests_log(driver):
|
|
result = driver.pull_file(get_app_path() + 'api.log')
|
|
return base64.b64decode(result)
|
|
|
|
|
|
class AbstractTestCase:
|
|
__metaclass__ = ABCMeta
|
|
|
|
def print_lt_session_info(self, driver):
|
|
sys.stdout = sys.stderr
|
|
print("LambdaTestSessionID=%s job-name=%s" % (driver.session_id, run_name))
|
|
|
|
def get_translation_by_key(self, key):
|
|
return transl[key]
|
|
|
|
@abstractmethod
|
|
def setup_method(self, method):
|
|
raise NotImplementedError('Should be overridden from a child class')
|
|
|
|
@abstractmethod
|
|
def teardown_method(self, method):
|
|
raise NotImplementedError('Should be overridden from a child class')
|
|
|
|
@property
|
|
def environment(self):
|
|
return pytest_config_global['env']
|
|
|
|
network_api = NetworkApi()
|
|
|
|
@staticmethod
|
|
def get_alert_text(driver):
|
|
try:
|
|
return driver.find_element(MobileBy.ID, 'android:id/message').text
|
|
except NoSuchElementException:
|
|
return None
|
|
|
|
def add_alert_text_to_report(self, driver):
|
|
try:
|
|
alert_text = self.get_alert_text(driver)
|
|
if alert_text:
|
|
test_suite_data.current_test.testruns[-1].error = "%s; also Unexpected Alert is shown: '%s'" % (
|
|
test_suite_data.current_test.testruns[-1].error, alert_text
|
|
)
|
|
except (RemoteDisconnected, ProtocolError):
|
|
test_suite_data.current_test.testruns[-1].error = "%s; \n RemoteDisconnected" % \
|
|
test_suite_data.current_test.testruns[-1].error
|
|
|
|
|
|
class Driver(webdriver.Remote):
|
|
|
|
@property
|
|
def number(self):
|
|
return test_suite_data.current_test.testruns[-1].jobs[self.session_id]
|
|
|
|
def info(self, text: str, device=True):
|
|
if device:
|
|
text = 'Device %s: %s ' % (self.number, text)
|
|
logging.info(text)
|
|
test_suite_data.current_test.testruns[-1].steps.append(text)
|
|
|
|
def fail(self, text: str):
|
|
pytest.fail('Device %s: %s' % (self.number, text))
|
|
|
|
def update_lt_session_status(self, index, status):
|
|
data = {
|
|
"action": "setTestStatus",
|
|
"arguments": {
|
|
"status": status,
|
|
"remark": "Device %s" % index
|
|
}
|
|
}
|
|
self.execute_script("lambda-hook: %s" % str(data).replace("'", "\""))
|
|
|
|
|
|
class Errors(object):
|
|
def __init__(self):
|
|
self.errors = list()
|
|
|
|
def append(self, text=str()):
|
|
self.errors.append(text)
|
|
|
|
def verify_no_errors(self):
|
|
if self.errors:
|
|
pytest.fail('\n '.join([self.errors.pop(0) for _ in range(len(self.errors))]))
|
|
|
|
|
|
class SingleDeviceTestCase(AbstractTestCase):
|
|
|
|
def setup_method(self, method, **kwargs):
|
|
if pytest_config_global['docker']:
|
|
appium_container.start_appium_container(pytest_config_global['docker_shared_volume'])
|
|
appium_container.connect_device(pytest_config_global['device_ip'])
|
|
|
|
# (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(implicit_wait)
|
|
self.errors = Errors()
|
|
|
|
if pytest_config_global['docker']:
|
|
appium_container.reset_battery_stats()
|
|
|
|
def teardown_method(self, method):
|
|
if self.environment == 'sauce':
|
|
self.print_lt_session_info(self.driver)
|
|
try:
|
|
self.add_alert_text_to_report(self.driver)
|
|
geth_content = pull_geth(self.driver)
|
|
self.driver.quit()
|
|
if pytest_config_global['docker']:
|
|
appium_container.stop_container()
|
|
except (WebDriverException, AttributeError):
|
|
pass
|
|
finally:
|
|
github_report.save_test(test_suite_data.current_test,
|
|
{'%s_geth.log' % test_suite_data.current_test.name: geth_content})
|
|
|
|
|
|
class LocalMultipleDeviceTestCase(AbstractTestCase):
|
|
|
|
def setup_method(self, method):
|
|
self.drivers = dict()
|
|
self.errors = Errors()
|
|
|
|
def create_drivers(self, quantity):
|
|
capabilities = self.add_local_devices_to_capabilities()
|
|
for driver in range(quantity):
|
|
self.drivers[driver] = Driver(self.executor_local, capabilities[driver])
|
|
test_suite_data.current_test.testruns[-1].jobs[self.drivers[driver].session_id] = driver + 1
|
|
self.drivers[driver].implicitly_wait(self.implicitly_wait)
|
|
|
|
def teardown_method(self, method):
|
|
for driver in self.drivers:
|
|
try:
|
|
self.add_alert_text_to_report(self.drivers[driver])
|
|
self.drivers[driver].quit()
|
|
except WebDriverException:
|
|
pass
|
|
|
|
|
|
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
|
|
return drivers, loop
|
|
else:
|
|
loop = asyncio.new_event_loop()
|
|
asyncio.set_event_loop(loop)
|
|
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_lambda_test,
|
|
options=get_lambda_test_capabilities_emulator()))
|
|
if len(drivers) < quantity:
|
|
test_suite_data.current_test.testruns[-1].error = "Not all %s drivers are created" % quantity
|
|
|
|
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)
|
|
drivers[i].update_settings({"enforceXPath1": True})
|
|
drivers[i].set_network_connection(ConnectionType.WIFI_ONLY)
|
|
return drivers, loop
|
|
except (MaxRetryError, AttributeError) as e:
|
|
test_suite_data.current_test.testruns[-1].error = str(e)
|
|
for i, driver in drivers.items():
|
|
try:
|
|
driver.update_lt_session_status(i + 1, "failed")
|
|
driver.quit()
|
|
except (WebDriverException, AttributeError):
|
|
pass
|
|
raise e
|
|
|
|
|
|
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
|
|
|
|
@pytest.fixture(scope='class', autouse=True)
|
|
def prepare(self, request):
|
|
try:
|
|
request.cls.prepare_devices(request)
|
|
finally:
|
|
for item, value in request.__dict__.items():
|
|
setattr(request.cls, item, value)
|
|
|
|
@classmethod
|
|
def teardown_class(cls):
|
|
for driver in cls.drivers:
|
|
try:
|
|
cls.drivers[driver].quit()
|
|
except WebDriverException:
|
|
pass
|
|
|
|
|
|
class LambdaTestSharedMultipleDeviceTestCase(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("lambda-testCase-start=%s" % method.__name__)
|
|
driver.log_event("appium", "Started %s" % method.__name__)
|
|
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()
|
|
test_suite_data.current_test.group_name = self.__class__.__name__
|
|
|
|
def teardown_method(self, method):
|
|
log_names, log_contents = [], []
|
|
for driver in self.drivers:
|
|
try:
|
|
self.print_lt_session_info(self.drivers[driver])
|
|
self.add_alert_text_to_report(self.drivers[driver])
|
|
log_names.append(
|
|
'%s_geth%s.log' % (test_suite_data.current_test.name, str(self.drivers[driver].number)))
|
|
log_contents.append(pull_geth(self.drivers[driver]))
|
|
log_names.append(
|
|
'%s_requests%s.log' % (test_suite_data.current_test.name, str(self.drivers[driver].number)))
|
|
log_contents.append(pull_requests_log(self.drivers[driver]))
|
|
except (WebDriverException, AttributeError, RemoteDisconnected, ProtocolError):
|
|
pass
|
|
finally:
|
|
try:
|
|
logs = {log_names[i]: log_contents[i] for i in range(len(log_names))}
|
|
test_suite_data.current_test.logs_paths = github_report.save_logs(logs)
|
|
except IndexError:
|
|
pass
|
|
|
|
@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:
|
|
for item, value in request.__dict__.items():
|
|
setattr(request.cls, item, value)
|
|
|
|
@classmethod
|
|
def teardown_class(cls):
|
|
requests_session = requests.Session()
|
|
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
|
|
else:
|
|
group_setup_failed = False
|
|
log_contents, log_names = list(), list()
|
|
try:
|
|
for i, driver in cls.drivers.items():
|
|
if group_setup_failed:
|
|
log_contents.append(pull_geth(driver=driver))
|
|
log_names.append('%s_geth%s.log' % (cls.__name__, i))
|
|
log_contents.append(pull_requests_log(driver=driver))
|
|
log_names.append('%s_requests%s.log' % (cls.__name__, i))
|
|
lt_session_status = "failed"
|
|
else:
|
|
lt_session_status = "passed"
|
|
try:
|
|
driver.update_lt_session_status(i + 1, lt_session_status)
|
|
driver.quit()
|
|
except (WebDriverException, RemoteDisconnected):
|
|
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:
|
|
try:
|
|
cls.loop.close()
|
|
except AttributeError:
|
|
pass
|
|
|
|
logs = dict(zip(log_names, log_contents))
|
|
logs_paths = github_report.save_logs(logs)
|
|
|
|
for test in test_suite_data.tests:
|
|
if group_setup_failed:
|
|
test.logs_paths = logs_paths
|
|
github_report.save_test(test)
|
|
|
|
|
|
if pytest_config_global['env'] == 'local':
|
|
MultipleSharedDeviceTestCase = LocalSharedMultipleDeviceTestCase
|
|
else:
|
|
MultipleSharedDeviceTestCase = LambdaTestSharedMultipleDeviceTestCase
|
|
|
|
|
|
class NoDeviceTestCase(AbstractTestCase):
|
|
|
|
def setup_method(self, method, **kwargs):
|
|
pass
|
|
|
|
def teardown_method(self, method):
|
|
github_report.save_test(test_suite_data.current_test)
|