e2e: configurable datacenter

This commit is contained in:
Churikova Tetiana 2022-11-03 12:26:53 +01:00
parent f17f57cc5e
commit add00a5609
No known key found for this signature in database
GPG Key ID: 0D4EA7B33B47E6D8
8 changed files with 125 additions and 47 deletions

View File

@ -7,7 +7,7 @@ from sauceclient import SauceException
import re import re
from support.test_data import SingleTestData from support.test_data import SingleTestData
from tests.cloudbase_test_api import sauce, apibase
class BaseTestReport: class BaseTestReport:
TEST_REPORT_DIR = "%s/../report" % os.path.dirname(os.path.abspath(__file__)) TEST_REPORT_DIR = "%s/../report" % os.path.dirname(os.path.abspath(__file__))
@ -102,7 +102,7 @@ class BaseTestReport:
def get_sauce_job_url(self, job_id, first_command=0): def get_sauce_job_url(self, job_id, first_command=0):
token = self.get_sauce_token(job_id) token = self.get_sauce_token(job_id)
url = 'https://eu-central-1.saucelabs.com/jobs/%s?auth=%s' % (job_id, token) url = 'https://%s/jobs/%s?auth=%s' % (apibase, job_id, token)
if first_command > 0: if first_command > 0:
url += "#%s" % first_command url += "#%s" % first_command
return url return url
@ -114,11 +114,10 @@ class BaseTestReport:
def get_sauce_final_screenshot_url(self, job_id): def get_sauce_final_screenshot_url(self, job_id):
token = self.get_sauce_token(job_id) token = self.get_sauce_token(job_id)
from tests.cloudbase_test_api import sauce
for _ in range(10): for _ in range(10):
try: try:
scr_number = sauce.jobs.get_job_assets(job_id)['screenshots'][-1] scr_number = sauce.jobs.get_job_assets(job_id)['screenshots'][-1]
return 'https://assets.eu-central-1.saucelabs.com/jobs/%s/%s?auth=%s' % (job_id, scr_number, token) return 'https://assets.%s/jobs/%s/%s?auth=%s' % (apibase, job_id, scr_number, token)
except SauceException: except SauceException:
time.sleep(3) time.sleep(3)

View File

@ -310,8 +310,12 @@ class TestrailReport(BaseTestReport):
return None return None
def get_not_executed_tests(self, test_run_id): def get_not_executed_tests(self, test_run_id):
try:
results = self.get("get_tests/%s&status_id=3" % test_run_id) results = self.get("get_tests/%s&status_id=3" % test_run_id)
return [result['case_id'] for result in results["tests"]] return [result['case_id'] for result in results["tests"]]
except KeyError:
print('Cannot extract result for %s' % test_run_id)
pass
@staticmethod @staticmethod
def make_error_with_gh_issue_link(error, issue_id): def make_error_with_gh_issue_link(error, issue_id):

View File

@ -1,4 +1,5 @@
import asyncio import asyncio
import base64
import logging import logging
import re import re
import subprocess import subprocess
@ -6,6 +7,7 @@ import sys
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from http.client import RemoteDisconnected from http.client import RemoteDisconnected
from os import environ from os import environ
from re import findall
import pytest import pytest
import requests import requests
@ -16,21 +18,18 @@ from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import WebDriverException from selenium.common.exceptions import WebDriverException
from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support.wait import WebDriverWait
from tests import transl from tests.conftest import option
from support.api.network_api import NetworkApi from support.api.network_api import NetworkApi
from support.github_report import GithubHtmlReport from support.github_report import GithubHtmlReport
from tests import test_suite_data, start_threads, appium_container, pytest_config_global from tests import test_suite_data, start_threads, appium_container, pytest_config_global
import base64 from tests import transl
from re import findall from tests.cloudbase_test_api import sauce, apibase
from tests.cloudbase_test_api import sauce
sauce_username = environ.get('SAUCE_USERNAME') sauce_username = environ.get('SAUCE_USERNAME')
sauce_access_key = environ.get('SAUCE_ACCESS_KEY') sauce_access_key = environ.get('SAUCE_ACCESS_KEY')
executor_sauce_lab = 'https://%s:%s@ondemand.eu-central-1.saucelabs.com:443/wd/hub' % (sauce_username, sauce_access_key) executor_sauce_lab = 'https://%s:%s@ondemand.%s:443/wd/hub' % (sauce_username, sauce_access_key, apibase)
executor_local = 'http://localhost:4723/wd/hub' executor_local = 'http://localhost:4723/wd/hub'
@ -303,6 +302,7 @@ def create_shared_drivers(quantity):
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
capabilities = {'maxDuration': 3600} capabilities = {'maxDuration': 3600}
print('SC Executor: %s' % executor_sauce_lab)
drivers = loop.run_until_complete(start_threads(quantity, drivers = loop.run_until_complete(start_threads(quantity,
Driver, Driver,
drivers, drivers,
@ -397,7 +397,10 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
driver.quit() driver.quit()
except WebDriverException: except WebDriverException:
pass pass
if option.datacenter == 'eu-central-1':
url = 'https://eu-central-1.saucelabs.com/rest/v1/%s/jobs/%s/assets/%s' % (sauce_username, session_id, "log.json") url = 'https://eu-central-1.saucelabs.com/rest/v1/%s/jobs/%s/assets/%s' % (sauce_username, session_id, "log.json")
else:
url = sauce.jobs.get_job_asset_url(job_id=session_id, filename="log.json")
WebDriverWait(driver, 60, 2).until(lambda _: requests_session.get(url).status_code == 200) WebDriverWait(driver, 60, 2).until(lambda _: requests_session.get(url).status_code == 200)
commands = requests_session.get(url).json() commands = requests_session.get(url).json()
for command in commands: for command in commands:

View File

@ -6,6 +6,8 @@ from time import sleep
from sauceclient import SauceClient, SauceException from sauceclient import SauceClient, SauceException
from tests.conftest import option
try: try:
import http.client as http_client import http.client as http_client
from urllib.parse import urlencode from urllib.parse import urlencode
@ -18,7 +20,12 @@ sauce_username = environ.get('SAUCE_USERNAME')
sauce_access_key = environ.get('SAUCE_ACCESS_KEY') sauce_access_key = environ.get('SAUCE_ACCESS_KEY')
sauce = SauceClient(sauce_username, sauce_access_key) sauce = SauceClient(sauce_username, sauce_access_key)
apibase = 'eu-central-1.saucelabs.com' if option.datacenter == 'us-west-1':
apibase = 'saucelabs.com'
elif option.datacenter == 'eu-central-1':
apibase = 'eu-central-1.saucelabs.com'
else:
raise NotImplementedError("Unknown SauceLabs datacenter")
def request(method, url, body=None, content_type='application/json'): def request(method, url, body=None, content_type='application/json'):
@ -34,9 +41,11 @@ def request(method, url, body=None, content_type='application/json'):
response.status, response.reason), response=response) response.status, response.reason), response=response)
return json.loads(data.decode('utf-8')) return json.loads(data.decode('utf-8'))
sauce.request = request sauce.request = request
def upload_from_url(apk_path = str()):
def upload_from_url(apk_path=str()):
response = requests.get(apk_path, stream=True) response = requests.get(apk_path, stream=True)
response.raise_for_status() response.raise_for_status()
apk_name = apk_path.split("/")[-1] apk_name = apk_path.split("/")[-1]
@ -44,7 +53,7 @@ def upload_from_url(apk_path = str()):
del response del response
for _ in range(3): for _ in range(3):
try: try:
requests.post('https://eu-central-1.saucelabs.com/rest/v1/storage/' requests.post('https://' + apibase + '/rest/v1/storage/'
+ sauce_username + '/' + apk_name + '?overwrite=true', + sauce_username + '/' + apk_name + '?overwrite=true',
auth=(sauce_username, sauce_access_key), auth=(sauce_username, sauce_access_key),
data=file, data=file,

View File

@ -1,31 +1,26 @@
import time
import requests
import pytest
import re import re
from _pytest.runner import runtestprotocol from dataclasses import dataclass
from datetime import datetime
from http.client import RemoteDisconnected from http.client import RemoteDisconnected
from os import environ
import pytest
from _pytest.runner import runtestprotocol
from sauceclient import SauceClient, SauceException
import tests
from support.device_stats_db import DeviceStatsDB from support.device_stats_db import DeviceStatsDB
from support.test_rerun import should_rerun_test from support.test_rerun import should_rerun_test
from tests import test_suite_data, appium_container from tests import test_suite_data, appium_container
from datetime import datetime # from support.testrail_report import TestrailReport
from os import environ #
from io import BytesIO # testrail_report = TestrailReport()
from sauceclient import SauceClient, SauceException
import tests.cloudbase_test_api
from support.api.network_api import NetworkApi
from support.github_report import GithubHtmlReport
from support.testrail_report import TestrailReport
from tests.users import transaction_senders
import tests
sauce_username = environ.get('SAUCE_USERNAME') sauce_username = environ.get('SAUCE_USERNAME')
sauce_access_key = environ.get('SAUCE_ACCESS_KEY') sauce_access_key = environ.get('SAUCE_ACCESS_KEY')
github_token = environ.get('GIT_HUB_TOKEN') github_token = environ.get('GIT_HUB_TOKEN')
sauce = SauceClient(sauce_username, sauce_access_key) sauce = SauceClient(sauce_username, sauce_access_key)
github_report = GithubHtmlReport()
testrail_report = TestrailReport()
def pytest_addoption(parser): def pytest_addoption(parser):
@ -41,6 +36,10 @@ def pytest_addoption(parser):
action='store', action='store',
default='sauce', default='sauce',
help='Specify environment: local/sauce/api') help='Specify environment: local/sauce/api')
parser.addoption('--datacenter',
action='store',
default='eu-central-1',
help='For sauce only: us-west-1, eu-central-1')
parser.addoption('--platform_version', parser.addoption('--platform_version',
action='store', action='store',
default='8.0', default='8.0',
@ -139,18 +138,37 @@ def pytest_addoption(parser):
help='Database name for device stats db') help='Database name for device stats db')
@dataclass
class Option:
datacenter: str = None
option = Option()
testrail_report = None
github_report = None
def is_master(config): def is_master(config):
return not hasattr(config, 'workerinput') return not hasattr(config, 'workerinput')
def is_uploaded(): def is_uploaded():
stored_files = tests.cloudbase_test_api.sauce.storage.get_stored_files() from tests.cloudbase_test_api import sauce
stored_files = sauce.storage.get_stored_files()
for i in range(len(stored_files['files'])): for i in range(len(stored_files['files'])):
if stored_files['files'][i]['name'] == test_suite_data.apk_name: if stored_files['files'][i]['name'] == test_suite_data.apk_name:
return True return True
def pytest_configure(config): def pytest_configure(config):
global option
option = config.option
from support.testrail_report import TestrailReport
global testrail_report
testrail_report = TestrailReport()
from support.github_report import GithubHtmlReport
global github_report
github_report = GithubHtmlReport()
tests.pytest_config_global = vars(config.option) tests.pytest_config_global = vars(config.option)
config.addinivalue_line("markers", "testrail_id(name): empty") config.addinivalue_line("markers", "testrail_id(name): empty")
if config.getoption('log_steps'): if config.getoption('log_steps'):
@ -176,10 +194,11 @@ def pytest_configure(config):
description='e2e tests are running') description='e2e tests are running')
if config.getoption('env') == 'sauce': if config.getoption('env') == 'sauce':
if not is_uploaded(): if not is_uploaded():
from tests.cloudbase_test_api import sauce, upload_from_url
if 'http' in config.getoption('apk'): if 'http' in config.getoption('apk'):
tests.cloudbase_test_api.upload_from_url(config.getoption('apk')) upload_from_url(config.getoption('apk'))
else: else:
tests.cloudbase_test_api.sauce.storage.upload_file(config.getoption('apk')) sauce.storage.upload_file(config.getoption('apk'))
def pytest_unconfigure(config): def pytest_unconfigure(config):
@ -251,7 +270,8 @@ def pytest_runtest_makereport(item, call):
report.passed) report.passed)
if error: if error:
test_suite_data.current_test.testruns[-1].error = final_error test_suite_data.current_test.testruns[-1].error = final_error
github_report.save_test(test_suite_data.current_test) from support.github_report import GithubHtmlReport
GithubHtmlReport().save_test(test_suite_data.current_test)
if report.when == 'call': if report.when == 'call':
current_test = test_suite_data.current_test current_test = test_suite_data.current_test
@ -290,9 +310,10 @@ def pytest_runtest_makereport(item, call):
def update_sauce_jobs(test_name, job_ids, passed): def update_sauce_jobs(test_name, job_ids, passed):
from tests.cloudbase_test_api import sauce
for job_id in job_ids.keys(): for job_id in job_ids.keys():
try: try:
tests.cloudbase_test_api.sauce.jobs.update_job(job_id, name=test_name, passed=passed) sauce.jobs.update_job(job_id, name=test_name, passed=passed)
except (RemoteDisconnected, SauceException): except (RemoteDisconnected, SauceException):
pass pass

View File

@ -328,6 +328,7 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
self.errors.verify_no_errors() self.errors.verify_no_errors()
@marks.testrail_id(695843) @marks.testrail_id(695843)
# moved without edit
def test_1_1_chat_text_message_edit_delete_push_disappear(self): def test_1_1_chat_text_message_edit_delete_push_disappear(self):
self.device_2.just_fyi( self.device_2.just_fyi(
"Device 1 sends text message and edits it in 1-1 chat. Device2 checks edited message is shown") "Device 1 sends text message and edits it in 1-1 chat. Device2 checks edited message is shown")
@ -1218,7 +1219,7 @@ class TestEnsStickersMultipleDevicesMerged(MultipleSharedDeviceTestCase):
@pytest.mark.xdist_group(name="one_2") @pytest.mark.xdist_group(name="one_2")
@marks.new_ui_critical @marks.new_ui_critical
class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase): class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
def prepare_devices(self): def prepare_devices(self):
self.drivers, self.loop = create_shared_drivers(2) self.drivers, self.loop = create_shared_drivers(2)
@ -1227,6 +1228,7 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
self.home_2 = self.device_2.create_user(enable_notifications=True) self.home_2 = self.device_2.create_user(enable_notifications=True)
self.profile_1 = self.home_1.get_profile_view() self.profile_1 = self.home_1.get_profile_view()
self.public_key_1, self.default_username_1 = self.home_1.get_public_key_and_username(return_username=True) self.public_key_1, self.default_username_1 = self.home_1.get_public_key_and_username(return_username=True)
self.profile_1.switch_push_notifications()
self.public_key_2, self.default_username_2 = self.home_2.get_public_key_and_username(return_username=True) self.public_key_2, self.default_username_2 = self.home_2.get_public_key_and_username(return_username=True)
self.profile_1.chats_tab.click() self.profile_1.chats_tab.click()
self.chat_1 = self.home_1.add_contact(self.public_key_2) self.chat_1 = self.home_1.add_contact(self.public_key_2)
@ -1271,13 +1273,10 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
self.chat_2.just_fyi("Send messages with non-latin symbols") self.chat_2.just_fyi("Send messages with non-latin symbols")
messages = ['hello', '¿Cómo estás tu año?', 'ё, доброго вечерочка', '® æ ç ♥'] messages = ['hello', '¿Cómo estás tu año?', 'ё, доброго вечерочка', '® æ ç ♥']
[self.chat_2.send_message(message) for message in messages]
for message in messages:
self.chat_2.send_message(message)
if not self.chat_1.chat_message_input.is_element_displayed(): if not self.chat_1.chat_message_input.is_element_displayed():
self.chat_1.click_system_back_button_until_element_is_shown() self.chat_1.click_system_back_button_until_element_is_shown()
self.home_1.get_chat(self.default_username_2).click() self.home_1.get_chat(self.default_username_2).click()
for message in messages: for message in messages:
if not self.chat_1.chat_element_by_text(message).is_element_displayed(): if not self.chat_1.chat_element_by_text(message).is_element_displayed():
self.errors.append("Message with test '%s' was not received" % message) self.errors.append("Message with test '%s' was not received" % message)
@ -1287,7 +1286,6 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
sent_time_variants = self.chat_2.convert_device_time_to_chat_timestamp() sent_time_variants = self.chat_2.convert_device_time_to_chat_timestamp()
if timestamp not in sent_time_variants: if timestamp not in sent_time_variants:
self.errors.append('Timestamp on message %s does not correspond expected [%s]' % (timestamp, *sent_time_variants)) self.errors.append('Timestamp on message %s does not correspond expected [%s]' % (timestamp, *sent_time_variants))
for message in [messages[1], messages[2]]: for message in [messages[1], messages[2]]:
if self.chat_2.chat_element_by_text(message).member_photo.is_element_displayed(): if self.chat_2.chat_element_by_text(message).member_photo.is_element_displayed():
self.errors.append('%s is not stack to 1st(they are sent in less than 5 minutes)!' % message) self.errors.append('%s is not stack to 1st(they are sent in less than 5 minutes)!' % message)
@ -1314,3 +1312,41 @@ class TestOneToOneChatMultipleSharedDevices(MultipleSharedDeviceTestCase):
if self.chat_2.chat_element_by_text(message).member_photo.is_element_differs_from_template("member.png", diff=5): if self.chat_2.chat_element_by_text(message).member_photo.is_element_differs_from_template("member.png", diff=5):
self.errors.append("Image of user in 1-1 chat is too different from template!") self.errors.append("Image of user in 1-1 chat is too different from template!")
self.errors.verify_no_errors() self.errors.verify_no_errors()
@marks.testrail_id(702733)
def test_1_1_chat_text_message_delete_push_disappear(self):
if not self.chat_1.chat_message_input.is_element_displayed():
self.home_1.get_chat(self.default_username_2).click()
self.device_2.just_fyi("Verify Device1 can not edit and delete received message from Device2")
message_after_edit_1_1 = 'smth I should edit'
self.chat_2.send_message(message_after_edit_1_1)
chat_1_element = self.chat_1.chat_element_by_text(message_after_edit_1_1)
chat_1_element.long_press_element()
for action in ("edit", "delete-for-everyone"):
if self.chat_1.element_by_translation_id(action).is_element_displayed():
self.errors.append('Option to %s someone else message available!' % action)
self.home_1.click_system_back_button()
self.device_2.just_fyi("Delete message for everyone and check it is not shown in chat preview on home")
self.chat_2.delete_message_in_chat(message_after_edit_1_1)
for chat in (self.chat_2, self.chat_1):
if chat.chat_element_by_text(message_after_edit_1_1).is_element_displayed(30):
self.errors.append("Deleted message is shown in chat view for 1-1 chat")
self.chat_1.click_system_back_button_until_element_is_shown()
if self.home_1.element_by_text(message_after_edit_1_1).is_element_displayed(30):
self.errors.append("Deleted message is shown on chat element on home screen")
self.device_2.just_fyi("Send one more message and check that PN will be deleted with message deletion")
message_to_delete = 'DELETE ME'
self.home_1.put_app_to_background()
self.chat_2.send_message(message_to_delete)
self.home_1.open_notification_bar()
if not self.home_1.get_pn(message_to_delete):
self.errors.append("Push notification doesn't appear")
self.chat_2.delete_message_in_chat(message_to_delete)
pn_to_disappear = self.home_1.get_pn(message_to_delete)
if pn_to_disappear:
if not pn_to_disappear.is_element_disappeared(30):
self.errors.append("Push notification was not removed after initial message deletion")
self.errors.verify_no_errors()

View File

@ -847,10 +847,11 @@ class ChatView(BaseView):
self.chat_message_input.send_keys(message_to_update) self.chat_message_input.send_keys(message_to_update)
self.send_message_button.click() self.send_message_button.click()
def delete_message_in_chat(self, message): def delete_message_in_chat(self, message, everyone=True):
self.driver.info("Looking for message '%s' to delete it" % message) self.driver.info("Looking for message '%s' to delete it" % message)
self.element_by_text_part(message).long_press_element() self.element_by_text_part(message).long_press_element()
self.element_by_translation_id("delete").click() for_everyone, for_me = self.element_by_translation_id("delete-for-everyone"), self.element_by_translation_id("delete-for-me")
for_everyone.click() if everyone else for_me.click()
def copy_message_text(self, message_text): def copy_message_text(self, message_text):
self.driver.info("Copying '%s' message via long press" % message_text) self.driver.info("Copying '%s' message via long press" % message_text)

View File

@ -372,6 +372,11 @@ class ProfileView(BaseView):
chat.confirm_until_presence_of_element(self.add_new_contact_button) chat.confirm_until_presence_of_element(self.add_new_contact_button)
self.click_system_back_button_until_element_is_shown() self.click_system_back_button_until_element_is_shown()
def switch_push_notifications(self):
self.driver.info("Enabling push notifications via Profile")
self.profile_notifications_button.scroll_and_click()
self.profile_notifications_toggle_button.click()
self.click_system_back_button_until_element_is_shown()
def add_custom_network(self, rpc_url: str, name: str, symbol: str, netwrok_id:str): def add_custom_network(self, rpc_url: str, name: str, symbol: str, netwrok_id:str):
self.driver.info("## Add custom network", device=False) self.driver.info("## Add custom network", device=False)