diff --git a/test/appium/support/base_test_report.py b/test/appium/support/base_test_report.py
new file mode 100644
index 0000000000..2d289c9089
--- /dev/null
+++ b/test/appium/support/base_test_report.py
@@ -0,0 +1,63 @@
+import json
+import hmac
+import os
+from hashlib import md5
+from tests import SingleTestData
+
+
+class BaseTestReport:
+
+ TEST_REPORT_DIR = "%s/../report" % os.path.dirname(os.path.abspath(__file__))
+
+ def __init__(self, sauce_username, sauce_access_key):
+ self.sauce_username = sauce_username
+ self.sauce_access_key = sauce_access_key
+ self.init_report()
+
+ def init_report(self):
+ if not os.path.exists(self.TEST_REPORT_DIR):
+ os.makedirs(self.TEST_REPORT_DIR)
+ # delete all old files in report dir
+ file_list = [f for f in os.listdir(self.TEST_REPORT_DIR)]
+ for f in file_list:
+ os.remove(os.path.join(self.TEST_REPORT_DIR, f))
+
+ def get_test_report_file_path(self, test_name):
+ file_name = "%s.json" % test_name
+ return os.path.join(self.TEST_REPORT_DIR, file_name)
+
+ def save_test(self, test):
+ file_path = self.get_test_report_file_path(test.name)
+ json.dump(test.__dict__, open(file_path, 'w'))
+
+ def get_all_tests(self):
+ tests = list()
+ file_list = [f for f in os.listdir(self.TEST_REPORT_DIR)]
+ for file_name in file_list:
+ file_path = os.path.join(self.TEST_REPORT_DIR, file_name)
+ test_dict = json.load(open(file_path))
+ tests.append(SingleTestData(name=test_dict['name'], steps=test_dict['steps'],
+ jobs=test_dict['jobs'], error=test_dict['error'],
+ testrail_case_id=test_dict['testrail_case_id']))
+ return tests
+
+ def get_failed_tests(self):
+ tests = self.get_all_tests()
+ failed = list()
+ for test in tests:
+ if test.error is not None:
+ failed.append(test)
+ return failed
+
+ def get_passed_tests(self):
+ tests = self.get_all_tests()
+ passed = list()
+ for test in tests:
+ if test.error is None:
+ passed.append(test)
+ return passed
+
+ def get_sauce_job_url(self, job_id):
+ token = hmac.new(bytes(self.sauce_username + ":" + self.sauce_access_key, 'latin-1'),
+ bytes(job_id, 'latin-1'), md5).hexdigest()
+ return "https://saucelabs.com/jobs/%s?auth=%s" % (job_id, token)
\ No newline at end of file
diff --git a/test/appium/support/github_test_report.py b/test/appium/support/github_report.py
similarity index 60%
rename from test/appium/support/github_test_report.py
rename to test/appium/support/github_report.py
index 3426cb056d..05ce8b2264 100644
--- a/test/appium/support/github_test_report.py
+++ b/test/appium/support/github_report.py
@@ -1,31 +1,13 @@
-import json
-from hashlib import md5
-import hmac
import os
-
-from tests import SingleTestData
+from support.base_test_report import BaseTestReport
-class GithubHtmlReport:
+class GithubHtmlReport(BaseTestReport):
TEST_REPORT_DIR = "%s/../report" % os.path.dirname(os.path.abspath(__file__))
def __init__(self, sauce_username, sauce_access_key):
- self.sauce_username = sauce_username
- self.sauce_access_key = sauce_access_key
- self.init_report()
-
- def init_report(self):
- if not os.path.exists(self.TEST_REPORT_DIR):
- os.makedirs(self.TEST_REPORT_DIR)
- # delete all old files in report dir
- file_list = [f for f in os.listdir(self.TEST_REPORT_DIR)]
- for f in file_list:
- os.remove(os.path.join(self.TEST_REPORT_DIR, f))
-
- def get_test_report_file_path(self, test_name):
- file_name = "%s.json" % test_name
- return os.path.join(self.TEST_REPORT_DIR, file_name)
+ super(GithubHtmlReport, self).__init__(sauce_username, sauce_access_key)
def build_html_report(self):
tests = self.get_all_tests()
@@ -49,36 +31,6 @@ class GithubHtmlReport:
else:
return None
- def save_test(self, test):
- file_path = self.get_test_report_file_path(test.name)
- json.dump(test.__dict__, open(file_path, 'w'))
-
- def get_all_tests(self):
- tests = list()
- file_list = [f for f in os.listdir(self.TEST_REPORT_DIR)]
- for file_name in file_list:
- file_path = os.path.join(self.TEST_REPORT_DIR, file_name)
- test_dict = json.load(open(file_path))
- tests.append(SingleTestData(name=test_dict['name'], steps=test_dict['steps'],
- jobs=test_dict['jobs'], error=test_dict['error']))
- return tests
-
- def get_failed_tests(self):
- tests = self.get_all_tests()
- failed = list()
- for test in tests:
- if test.error is not None:
- failed.append(test)
- return failed
-
- def get_passed_tests(self):
- tests = self.get_all_tests()
- passed = list()
- for test in tests:
- if test.error is None:
- passed.append(test)
- return passed
-
def build_tests_table_html(self, tests, failed_tests=False):
tests_type = "Failed tests" if failed_tests else "Passed tests"
html = "
%s (%d)
" % (tests_type, len(tests))
@@ -121,11 +73,6 @@ class GithubHtmlReport:
html += ""
return html
- def get_sauce_job_url(self, job_id):
- token = hmac.new(bytes(self.sauce_username + ":" + self.sauce_access_key, 'latin-1'),
- bytes(job_id, 'latin-1'), md5).hexdigest()
- return "https://saucelabs.com/jobs/%s?auth=%s" % (job_id, token)
-
def build_device_sessions_html(self, jobs):
html = "Device sessions:"
html += ""
diff --git a/test/appium/support/testrail_report.py b/test/appium/support/testrail_report.py
new file mode 100644
index 0000000000..0ad9031236
--- /dev/null
+++ b/test/appium/support/testrail_report.py
@@ -0,0 +1,72 @@
+import json
+import requests
+import base64
+from os import environ
+from support.base_test_report import BaseTestReport
+
+
+class TestrailReport(BaseTestReport):
+
+ def __init__(self, sauce_username, sauce_access_key):
+ super(TestrailReport, self).__init__(sauce_username, sauce_access_key)
+
+ self.password = environ.get('TESTRAIL_PASS')
+ self.user = environ.get('TESTRAIL_USER')
+
+ self.run_id = None
+ self.suite_id = 42
+ self.project_id = 9
+
+ self.outcomes = {
+ 'passed': 1,
+ 'undefined_fail': 10}
+
+ self.headers = dict()
+ self.headers['Authorization'] = 'Basic %s' % str(
+ base64.b64encode(bytes('%s:%s' % (self.user, self.password), 'utf-8')), 'ascii').strip()
+ self.headers['Content-Type'] = 'application/json'
+
+ self.url = 'https://ethstatus.testrail.net/index.php?/api/v2/'
+
+ def get(self, method):
+ raw_response = requests.get(self.url + method, headers=self.headers).text
+ return json.loads(raw_response)
+
+ def post(self, method, data):
+ data = bytes(json.dumps(data), 'utf-8')
+ raw_response = requests.post(self.url + method, data=data, headers=self.headers).text
+ return json.loads(raw_response)
+
+ def get_suites(self):
+ return self.get('get_suites/%s' % self.project_id)
+
+ def get_tests(self):
+ return self.get('get_tests/%s' % self.run_id)
+
+ def get_milestones(self):
+ return self.get('get_milestones/%s' % self.project_id)
+
+ @property
+ def actual_milestone_id(self):
+ return self.get_milestones()[-1]['id']
+
+ def add_run(self, run_name):
+ request_body = {'suite_id': self.suite_id,
+ 'name': run_name,
+ 'milestone_id': self.actual_milestone_id}
+ run = self.post('add_run/%s' % self.project_id, request_body)
+ self.run_id = run['id']
+
+ def add_results(self):
+ for test in self.get_all_tests():
+ test_steps = "# Steps: \n"
+ devices = str()
+ method = 'add_result_for_case/%s/%s' % (self.run_id, test.testrail_case_id)
+ for step in test.steps:
+ test_steps += step + "\n"
+ for i, device in enumerate(test.jobs):
+ devices += "# [Device %d](%s) \n" % (i + 1, self.get_sauce_job_url(device))
+ data = {'status_id': self.outcomes['undefined_fail'] if test.error else self.outcomes['passed'],
+ 'comment': '%s' % ('# Error: \n %s \n' % test.error) + devices + test_steps if test.error
+ else devices + test_steps}
+ self.post(method, data=data)
diff --git a/test/appium/tests/__init__.py b/test/appium/tests/__init__.py
index 462dc866b9..4360dae462 100644
--- a/test/appium/tests/__init__.py
+++ b/test/appium/tests/__init__.py
@@ -24,7 +24,8 @@ def info(text: str):
class SingleTestData(object):
- def __init__(self, name, steps=list(), jobs=list(), error=None):
+ def __init__(self, name, steps=list(), jobs=list(), error=None, testrail_case_id=None):
+ self.testrail_case_id = testrail_case_id
self.name = name
self.steps = steps
self.jobs = jobs
diff --git a/test/appium/tests/conftest.py b/test/appium/tests/conftest.py
index a574c53f23..bb522484eb 100644
--- a/test/appium/tests/conftest.py
+++ b/test/appium/tests/conftest.py
@@ -5,7 +5,8 @@ from datetime import datetime
from os import environ
from io import BytesIO
from sauceclient import SauceClient
-from support.github_test_report import GithubHtmlReport
+from support.github_report import GithubHtmlReport
+from support.testrail_report import TestrailReport
sauce_username = environ.get('SAUCE_USERNAME')
sauce_access_key = environ.get('SAUCE_ACCESS_KEY')
@@ -13,6 +14,7 @@ github_token = environ.get('GIT_HUB_TOKEN')
sauce = SauceClient(sauce_username, sauce_access_key)
github_report = GithubHtmlReport(sauce_username, sauce_access_key)
+testrail_report = TestrailReport(sauce_username, sauce_access_key)
def pytest_addoption(parser):
@@ -36,6 +38,10 @@ def pytest_addoption(parser):
action='store',
default=None,
help='Pull Request number')
+ parser.addoption('--nightly',
+ action='store',
+ default=False,
+ help='boolean; For running extended test suite against nightly build')
def is_master(config):
@@ -55,31 +61,34 @@ def pytest_configure(config):
logging.basicConfig(level=logging.INFO)
test_suite_data.apk_name = ([i for i in [i for i in config.getoption('apk').split('/')
if '.apk' in i]])[0]
- if is_master(config) and config.getoption('env') == 'sauce':
- if config.getoption('pr_number'):
- with open('github_comment.txt', 'w') as _:
- pass
- if not is_uploaded():
- if 'http' in config.getoption('apk'):
- response = requests.get(config.getoption('apk'), stream=True)
- response.raise_for_status()
- file = BytesIO(response.content)
- del response
- requests.post('http://saucelabs.com/rest/v1/storage/'
- + sauce_username + '/' + test_suite_data.apk_name + '?overwrite=true',
- auth=(sauce_username, sauce_access_key),
- data=file,
- headers={'Content-Type': 'application/octet-stream'})
- else:
- sauce.storage.upload_file(config.getoption('apk'))
+ if is_master(config):
+ if config.getoption('nightly'):
+ testrail_report.add_run(test_suite_data.apk_name)
+ if config.getoption('env') == 'sauce':
+ if not is_uploaded():
+ if 'http' in config.getoption('apk'):
+ response = requests.get(config.getoption('apk'), stream=True)
+ response.raise_for_status()
+ file = BytesIO(response.content)
+ del response
+ requests.post('http://saucelabs.com/rest/v1/storage/'
+ + sauce_username + '/' + test_suite_data.apk_name + '?overwrite=true',
+ auth=(sauce_username, sauce_access_key),
+ data=file,
+ headers={'Content-Type': 'application/octet-stream'})
+ else:
+ sauce.storage.upload_file(config.getoption('apk'))
def pytest_unconfigure(config):
- if is_master(config) and config.getoption('pr_number'):
- from github import Github
- repo = Github(github_token).get_user('status-im').get_repo('status-react')
- pull = repo.get_pull(int(config.getoption('pr_number')))
- pull.create_issue_comment(github_report.build_html_report())
+ if is_master(config):
+ if config.getoption('pr_number'):
+ from github import Github
+ repo = Github(github_token).get_user('status-im').get_repo('status-react')
+ pull = repo.get_pull(int(config.getoption('pr_number')))
+ pull.create_issue_comment(github_report.build_html_report())
+ if config.getoption('nightly'):
+ testrail_report.add_results()
@pytest.mark.hookwrapper
@@ -101,5 +110,10 @@ def update_sauce_jobs(test_name, job_ids, passed):
sauce.jobs.update_job(job_id, name=test_name, passed=passed)
+def get_testrail_case_id(obj):
+ if 'testrail_case_id' in obj.keywords._markers:
+ return obj.keywords._markers['testrail_case_id'].args[0]
+
+
def pytest_runtest_setup(item):
- test_suite_data.add_test(SingleTestData(item.name))
+ test_suite_data.add_test(SingleTestData(item.name, testrail_case_id=get_testrail_case_id(item)))
diff --git a/test/appium/tests/marks.py b/test/appium/tests/marks.py
new file mode 100644
index 0000000000..d7ce52e9be
--- /dev/null
+++ b/test/appium/tests/marks.py
@@ -0,0 +1,7 @@
+import pytest
+
+pr = pytest.mark.pr
+testrail_case_id = pytest.mark.testrail_case_id
+chat = pytest.mark.chat
+all = pytest.mark.all
+transaction = pytest.mark.transaction
diff --git a/test/appium/tests/test_messaging.py b/test/appium/tests/test_messaging.py
index 2a35f81095..e1c736f4f8 100644
--- a/test/appium/tests/test_messaging.py
+++ b/test/appium/tests/test_messaging.py
@@ -4,7 +4,7 @@ import pytest
import emoji
from tests.base_test_case import MultipleDeviceTestCase
-from tests import group_chat_users, get_current_time
+from tests import group_chat_users, get_current_time, marks
from views.sign_in_view import SignInView
unicode_text_message = '%s%s%s%s %s%s%s%s%s%s%s' % (chr(355), chr(275), chr(353), chr(539), chr(1084), chr(949),
@@ -17,11 +17,12 @@ emoji_unicode_1 = emoji.EMOJI_UNICODE[emoji_name_1]
message_with_new_line = 'message' '\n' 'with new line'
-@pytest.mark.all
-@pytest.mark.chat
+@marks.all
+@marks.chat
class TestMessages(MultipleDeviceTestCase):
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3390)
def test_one_to_one_chat_messages(self):
self.create_drivers(2)
device_1, device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
@@ -77,7 +78,8 @@ class TestMessages(MultipleDeviceTestCase):
self.verify_no_errors()
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3391)
def test_group_chat_messages_and_delete_chat(self):
self.create_drivers(3)
@@ -139,7 +141,8 @@ class TestMessages(MultipleDeviceTestCase):
self.verify_no_errors()
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3392)
def test_public_chat(self):
self.create_drivers(2)
device_1, device_2 = SignInView(self.drivers[0]), SignInView(self.drivers[1])
diff --git a/test/appium/tests/test_profile.py b/test/appium/tests/test_profile.py
index aa8bf7980b..3b2532ab8a 100644
--- a/test/appium/tests/test_profile.py
+++ b/test/appium/tests/test_profile.py
@@ -3,14 +3,14 @@ import emoji
import pytest
import time
from tests.base_test_case import SingleDeviceTestCase
-from tests import basic_user
+from tests import basic_user, marks
from views.sign_in_view import SignInView
-@pytest.mark.all
+@marks.all
class TestProfileView(SingleDeviceTestCase):
- @pytest.mark.testrail_case_id(3395)
+ @marks.testrail_case_id(3395)
def test_qr_code_and_its_value(self):
sign_in_view = SignInView(self.driver)
sign_in_view.create_user()
@@ -27,12 +27,12 @@ class TestProfileView(SingleDeviceTestCase):
wallet_view.qr_code_image.wait_for_element()
key_value = wallet_view.address_text.text
key_value_from_qr = wallet_view.get_text_from_qr()
- if key_value_from_qr != "ethereum:%s'" % key_value:
+ if key_value not in key_value_from_qr:
self.errors.append(
"Wallet QR code value '%s' doesn't match wallet address '%s'" % (key_value_from_qr, key_value))
self.verify_no_errors()
- @pytest.mark.pr
+ @marks.pr
@pytest.mark.testrail_case_id(3396)
def test_contact_profile_view(self):
sign_in_view = SignInView(self.driver)
@@ -51,7 +51,8 @@ class TestProfileView(SingleDeviceTestCase):
chat_view.profile_send_transaction.click()
assert chat_view.chat_message_input.text.strip() == '/send'
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3397)
def test_network_switch(self):
sign_in_view = SignInView(self.driver)
sign_in_view.create_user()
@@ -100,7 +101,7 @@ class TestProfileView(SingleDeviceTestCase):
profile_view.logout_button.click()
profile_view.confirm_logout_button.click()
recover_access_view = sign_in_view.add_existing_account_button.click()
- recover_access_view.passphrase_input.set_value(' '.join(seed_phrase.values()))
+ recover_access_view.passphrase_input.set_value(' '.join(seed_phrase[key] for key in sorted(seed_phrase)))
recover_access_view.password_input.set_value('qwerty1234')
recover_access_view.sign_in_button.click()
sign_in_view.do_not_share.click()
diff --git a/test/appium/tests/test_transaction.py b/test/appium/tests/test_transaction.py
index 6eea61a188..1728384f2d 100644
--- a/test/appium/tests/test_transaction.py
+++ b/test/appium/tests/test_transaction.py
@@ -1,16 +1,18 @@
import pytest
import time
from tests.base_test_case import SingleDeviceTestCase, MultipleDeviceTestCase
-from tests import transaction_users, api_requests, get_current_time, transaction_users_wallet
+from tests import transaction_users, api_requests, get_current_time, transaction_users_wallet, marks
from selenium.common.exceptions import TimeoutException
from views.sign_in_view import SignInView
-@pytest.mark.all
+@marks.all
+@marks.transaction
class TestTransaction(SingleDeviceTestCase):
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3401)
def test_transaction_send_command_one_to_one_chat(self):
recipient = transaction_users['B_USER']
sign_in_view = SignInView(self.driver)
@@ -32,7 +34,8 @@ class TestTransaction(SingleDeviceTestCase):
transactions_view = wallet_view.transactions_button.click()
transactions_view.transactions_table.find_transaction(amount=transaction_amount)
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3402)
def test_transaction_send_command_wrong_password(self):
sender = transaction_users['A_USER']
recipient = transaction_users['B_USER']
@@ -53,7 +56,8 @@ class TestTransaction(SingleDeviceTestCase):
send_transaction_view.sign_transaction_button.click()
send_transaction_view.find_full_text('Wrong password', 20)
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3403)
def test_transaction_send_command_group_chat(self):
recipient = transaction_users['A_USER']
sign_in_view = SignInView(self.driver)
@@ -72,7 +76,8 @@ class TestTransaction(SingleDeviceTestCase):
chat_view.send_transaction_in_group_chat(transaction_amount, 'qwerty1234', recipient)
api_requests.verify_balance_is_updated(initial_recipient_balance, recipient['address'])
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3404)
def test_send_transaction_from_daap(self):
sender = transaction_users['B_USER']
sign_in_view = SignInView(self.driver)
@@ -100,7 +105,8 @@ class TestTransaction(SingleDeviceTestCase):
api_requests.verify_balance_is_updated(initial_balance, address)
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3405)
def test_send_eth_from_wallet_sign_later(self):
sender = transaction_users_wallet['B_USER']
recipient = transaction_users_wallet['A_USER']
@@ -134,7 +140,8 @@ class TestTransaction(SingleDeviceTestCase):
transactions_view.history_tab.click()
transactions_view.transactions_table.find_transaction(amount=amount)
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3406)
def test_send_stt_from_wallet_via_enter_recipient_address(self):
sender = transaction_users_wallet['A_USER']
recipient = transaction_users_wallet['B_USER']
@@ -159,7 +166,8 @@ class TestTransaction(SingleDeviceTestCase):
send_transaction.sign_transaction_button.click()
send_transaction.got_it_button.click()
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3407)
def test_send_eth_from_wallet_sign_now(self):
recipient = transaction_users['F_USER']
sender = transaction_users['E_USER']
@@ -184,10 +192,12 @@ class TestTransaction(SingleDeviceTestCase):
send_transaction.got_it_button.click()
-@pytest.mark.all
+@marks.all
+@marks.transaction
class TestTransactions(MultipleDeviceTestCase):
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3408)
def test_send_eth_to_request_in_group_chat(self):
recipient = transaction_users['E_USER']
sender = self.senders['f_user'] = transaction_users['F_USER']
@@ -216,7 +226,8 @@ class TestTransactions(MultipleDeviceTestCase):
device_2_chat.send_eth_to_request(request_button, sender['password'])
api_requests.verify_balance_is_updated(initial_balance_recipient, recipient['address'])
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3409)
def test_send_eth_to_request_in_one_to_one_chat(self):
recipient = transaction_users['C_USER']
sender = self.senders['d_user'] = transaction_users['D_USER']
@@ -255,7 +266,8 @@ class TestTransactions(MultipleDeviceTestCase):
transactions_view = device_2_wallet.transactions_button.click()
transactions_view.transactions_table.find_transaction(amount=amount)
- @pytest.mark.pr
+ @marks.pr
+ @marks.testrail_case_id(3410)
def test_send_eth_to_request_from_wallet(self):
recipient = transaction_users_wallet['D_USER']
sender = self.senders['c_user'] = transaction_users['C_USER']
diff --git a/test/appium/views/base_view.py b/test/appium/views/base_view.py
index 780d80a28f..044be08793 100644
--- a/test/appium/views/base_view.py
+++ b/test/appium/views/base_view.py
@@ -208,8 +208,8 @@ class BaseView(object):
'k': 39, 'l': 40, 'm': 41, 'n': 42, 'o': 43, 'p': 44, 'q': 45, 'r': 46, 's': 47, 't': 48,
'u': 49, 'v': 50, 'w': 51, 'x': 52, 'y': 53, 'z': 54}
time.sleep(3)
+ info("Enter '%s' using native keyboard" % string)
for i in string:
- info("Tap '%s' on native keyboard" % i)
if type(keys[i]) is list:
keycode, metastate = keys[i][0], keys[i][1]
else: