From c38512512afe1e36bd085e84efccea3be0a560e6 Mon Sep 17 00:00:00 2001
From: Yevheniia Berdnyk
Date: Thu, 24 Oct 2024 18:59:53 +0300
Subject: [PATCH] e2e: LambdaTest
---
ci/tests/Jenkinsfile.e2e-nightly | 22 +--
ci/tests/Jenkinsfile.e2e-prs | 6 -
test/appium/requirements.txt | 1 +
test/appium/support/base_test_report.py | 31 +---
test/appium/support/github_report.py | 2 +-
test/appium/support/lambda_test.py | 14 +-
test/appium/support/testrail_report.py | 11 +-
test/appium/tests/base_test_case.py | 165 +++++-------------
test/appium/tests/conftest.py | 95 +++-------
.../critical/chats/test_1_1_public_chats.py | 35 ++--
.../tests/critical/chats/test_group_chat.py | 48 ++---
.../chats/test_public_chat_browsing.py | 10 +-
test/appium/tests/critical/test_fallback.py | 2 +-
test/appium/tests/marks.py | 2 -
test/appium/tests/upgrade/test_upgrade.py | 4 +-
test/appium/views/base_element.py | 19 +-
test/appium/views/base_view.py | 1 +
test/appium/views/chat_view.py | 53 +++---
.../elements_templates/blank_camera_image.png | Bin 390 -> 0 bytes
.../views/elements_templates/block_dark.png | Bin 9568 -> 0 bytes
.../elements_templates/collectible_pic.png | Bin 15282 -> 0 bytes
.../elements_templates/collectible_pic_2.png | Bin 15322 -> 0 bytes
.../elements_templates/dark_sauce_logo.png | Bin 11362 -> 0 bytes
.../discovery_general_channel.png | Bin 1791 -> 0 bytes
.../discovery_join_button.png | Bin 6403 -> 0 bytes
.../elements_templates/enabled_toggle.png | Bin 2679 -> 0 bytes
.../views/elements_templates/gallery_1.png | Bin 161453 -> 0 bytes
.../views/elements_templates/group_logo.png | Bin 8409 -> 0 bytes
.../elements_templates/highligted_preview.png | Bin 1576 -> 0 bytes
.../highligted_preview_group.png | Bin 3651 -> 0 bytes
.../elements_templates/image_1_chat_view.png | Bin 0 -> 330090 bytes
.../image_1_gallery_view.png | Bin 0 -> 125758 bytes
.../elements_templates/image_2_chat_view.png | Bin 0 -> 1055126 bytes
.../image_2_gallery_view.png | Bin 0 -> 338949 bytes
.../image_sent_in_community.png | Bin 176913 -> 0 bytes
.../elements_templates/images_gallery.png | Bin 0 -> 507825 bytes
.../elements_templates/logo_chats_view.png | Bin 12040 -> 0 bytes
.../elements_templates/logo_chats_view_2.png | Bin 11325 -> 0 bytes
.../views/elements_templates/logo_new.png | Bin 16018 -> 0 bytes
.../views/elements_templates/member.png | Bin 8122 -> 0 bytes
.../views/elements_templates/member2.png | Bin 6876 -> 0 bytes
.../views/elements_templates/member3.png | Bin 7478 -> 0 bytes
.../views/elements_templates/ment_new_1.png | Bin 3170 -> 0 bytes
.../views/elements_templates/mentioned.png | Bin 4761 -> 0 bytes
.../multi_account_color.png | Bin 295 -> 0 bytes
.../elements_templates/new_profile_online.png | Bin 23255 -> 0 bytes
.../profile_image_in_1_1_chat.png | Bin 0 -> 18536 bytes
.../sauce_dark_image_gallery.png | Bin 65237 -> 0 bytes
.../views/elements_templates/sauce_logo.png | Bin 21928 -> 0 bytes
.../elements_templates/sauce_logo_profile.png | Bin 15978 -> 0 bytes
.../sauce_logo_profile_2.png | Bin 11918 -> 0 bytes
.../sauce_logo_profile_picture.png | Bin 23031 -> 0 bytes
.../elements_templates/sauce_logo_red.png | Bin 19937 -> 0 bytes
.../sauce_logo_red_profile.png | Bin 23384 -> 0 bytes
.../elements_templates/saucelabs_sauce.png | Bin 18841 -> 0 bytes
.../saucelabs_sauce_chat.png | Bin 256056 -> 0 bytes
.../saucelabs_sauce_gallery.png | Bin 97964 -> 0 bytes
.../saucelabs_sauce_group_chat.png | Bin 256056 -> 0 bytes
.../saucelabs_sauce_profile.png | Bin 22169 -> 0 bytes
.../appium/views/elements_templates/saved.png | Bin 209118 -> 0 bytes
.../status_community_join_button.png | Bin 6802 -> 9989 bytes
.../status_community_logo.png | Bin 11060 -> 19095 bytes
test/appium/views/elements_templates/url1.png | Bin 31798 -> 0 bytes
test/appium/views/elements_templates/url2.png | Bin 51354 -> 0 bytes
test/appium/views/elements_templates/url3.png | Bin 125038 -> 0 bytes
test/appium/views/home_view.py | 21 ++-
test/appium/views/profile_view.py | 3 +-
test/appium/views/wallet_view.py | 5 +-
68 files changed, 205 insertions(+), 345 deletions(-)
delete mode 100644 test/appium/views/elements_templates/blank_camera_image.png
delete mode 100644 test/appium/views/elements_templates/block_dark.png
delete mode 100644 test/appium/views/elements_templates/collectible_pic.png
delete mode 100644 test/appium/views/elements_templates/collectible_pic_2.png
delete mode 100644 test/appium/views/elements_templates/dark_sauce_logo.png
delete mode 100644 test/appium/views/elements_templates/discovery_general_channel.png
delete mode 100644 test/appium/views/elements_templates/discovery_join_button.png
delete mode 100644 test/appium/views/elements_templates/enabled_toggle.png
delete mode 100644 test/appium/views/elements_templates/gallery_1.png
delete mode 100644 test/appium/views/elements_templates/group_logo.png
delete mode 100644 test/appium/views/elements_templates/highligted_preview.png
delete mode 100644 test/appium/views/elements_templates/highligted_preview_group.png
create mode 100644 test/appium/views/elements_templates/image_1_chat_view.png
create mode 100644 test/appium/views/elements_templates/image_1_gallery_view.png
create mode 100644 test/appium/views/elements_templates/image_2_chat_view.png
create mode 100644 test/appium/views/elements_templates/image_2_gallery_view.png
delete mode 100644 test/appium/views/elements_templates/image_sent_in_community.png
create mode 100644 test/appium/views/elements_templates/images_gallery.png
delete mode 100644 test/appium/views/elements_templates/logo_chats_view.png
delete mode 100644 test/appium/views/elements_templates/logo_chats_view_2.png
delete mode 100644 test/appium/views/elements_templates/logo_new.png
delete mode 100644 test/appium/views/elements_templates/member.png
delete mode 100644 test/appium/views/elements_templates/member2.png
delete mode 100644 test/appium/views/elements_templates/member3.png
delete mode 100644 test/appium/views/elements_templates/ment_new_1.png
delete mode 100644 test/appium/views/elements_templates/mentioned.png
delete mode 100644 test/appium/views/elements_templates/multi_account_color.png
delete mode 100644 test/appium/views/elements_templates/new_profile_online.png
create mode 100644 test/appium/views/elements_templates/profile_image_in_1_1_chat.png
delete mode 100644 test/appium/views/elements_templates/sauce_dark_image_gallery.png
delete mode 100644 test/appium/views/elements_templates/sauce_logo.png
delete mode 100644 test/appium/views/elements_templates/sauce_logo_profile.png
delete mode 100644 test/appium/views/elements_templates/sauce_logo_profile_2.png
delete mode 100644 test/appium/views/elements_templates/sauce_logo_profile_picture.png
delete mode 100644 test/appium/views/elements_templates/sauce_logo_red.png
delete mode 100644 test/appium/views/elements_templates/sauce_logo_red_profile.png
delete mode 100644 test/appium/views/elements_templates/saucelabs_sauce.png
delete mode 100644 test/appium/views/elements_templates/saucelabs_sauce_chat.png
delete mode 100644 test/appium/views/elements_templates/saucelabs_sauce_gallery.png
delete mode 100644 test/appium/views/elements_templates/saucelabs_sauce_group_chat.png
delete mode 100644 test/appium/views/elements_templates/saucelabs_sauce_profile.png
delete mode 100644 test/appium/views/elements_templates/saved.png
delete mode 100644 test/appium/views/elements_templates/url1.png
delete mode 100644 test/appium/views/elements_templates/url2.png
delete mode 100644 test/appium/views/elements_templates/url3.png
diff --git a/ci/tests/Jenkinsfile.e2e-nightly b/ci/tests/Jenkinsfile.e2e-nightly
index 5cc8afc1bc..ffce079a77 100644
--- a/ci/tests/Jenkinsfile.e2e-nightly
+++ b/ci/tests/Jenkinsfile.e2e-nightly
@@ -73,9 +73,9 @@ pipeline {
passwordVariable: 'TESTRAIL_PASS'
),
usernamePassword(
- credentialsId: 'sauce-labs-api',
- usernameVariable: 'SAUCE_USERNAME',
- passwordVariable: 'SAUCE_ACCESS_KEY'
+ credentialsId: 'lambda-test-api',
+ usernameVariable: 'LAMBDA_TEST_USERNAME',
+ passwordVariable: 'LAMBDA_TEST_ACCESS_KEY'
),
string(
credentialsId: 'etherscan-api-key',
@@ -96,7 +96,6 @@ pipeline {
sh """
python3 -m pytest \
--numprocesses 8 \
- --rerun_count=2 \
--testrail_report=True \
-m testrail_id \
-m \"nightly\" \
@@ -110,21 +109,6 @@ pipeline {
}
post {
- always {
- script {
- sauce('sauce-labs-cred') {
- saucePublisher()
- }
- }
- }
- success {
- script {
- junit(
- testDataPublishers: [[$class: 'SauceOnDemandReportPublisher', jobVisibility: 'public']],
- testResults: 'test/appium/tests/*.xml'
- )
- }
- }
cleanup {
sh 'make purge'
}
diff --git a/ci/tests/Jenkinsfile.e2e-prs b/ci/tests/Jenkinsfile.e2e-prs
index 8bfa64ed6f..869061bcfc 100644
--- a/ci/tests/Jenkinsfile.e2e-prs
+++ b/ci/tests/Jenkinsfile.e2e-prs
@@ -110,11 +110,6 @@ pipeline {
usernameVariable: 'TESTRAIL_USER',
passwordVariable: 'TESTRAIL_PASS'
),
- usernamePassword(
- credentialsId: 'sauce-labs-api',
- usernameVariable: 'SAUCE_USERNAME',
- passwordVariable: 'SAUCE_ACCESS_KEY'
- ),
usernamePassword(
credentialsId: 'lambda-test-api',
usernameVariable: 'LAMBDA_TEST_USERNAME',
@@ -139,7 +134,6 @@ pipeline {
sh """
python3 -m pytest \
--numprocesses 8 \
- --rerun_count=2 \
--testrail_report=True \
-k \"${params.KEYWORD_EXPRESSION}\" \
--apk=${params.APK_URL ?: apk_path} \
diff --git a/test/appium/requirements.txt b/test/appium/requirements.txt
index ea7c26bd41..bdee7abec6 100644
--- a/test/appium/requirements.txt
+++ b/test/appium/requirements.txt
@@ -17,6 +17,7 @@ eth-account==0.7.0
eth-hash==0.3.2
eth-keys
execnet==1.7.1
+filelock==3.16.1
flaky==3.7.0
future==0.18.2
hexbytes==0.2.2
diff --git a/test/appium/support/base_test_report.py b/test/appium/support/base_test_report.py
index 1ab6337022..917e724f23 100644
--- a/test/appium/support/base_test_report.py
+++ b/test/appium/support/base_test_report.py
@@ -1,10 +1,9 @@
-import hmac
import json
import os
import re
import urllib
-from hashlib import md5
+from support.lambda_test import get_session_info
from support.test_data import SingleTestData
@@ -12,8 +11,6 @@ class BaseTestReport:
TEST_REPORT_DIR = "%s/../report" % os.path.dirname(os.path.abspath(__file__))
def __init__(self):
- self.sauce_username = os.environ.get('SAUCE_USERNAME')
- self.sauce_access_key = os.environ.get('SAUCE_ACCESS_KEY')
self.init_report()
def init_report(self):
@@ -97,17 +94,8 @@ class BaseTestReport:
failed.append(test)
return passed, failed, xfailed
- def get_sauce_token(self, job_id):
- return hmac.new(bytes(self.sauce_username + ":" + self.sauce_access_key, 'latin-1'),
- bytes(job_id, 'latin-1'), md5).hexdigest()
-
- def get_sauce_job_url(self, job_id, first_command=0):
- token = self.get_sauce_token(job_id)
- from tests.conftest import apibase
- url = 'https://%s/jobs/%s?auth=%s' % (apibase, job_id, token)
- if first_command > 0:
- url += "#%s" % first_command
- return url
+ def get_lambda_test_job_url(self, job_id, first_command=0):
+ return "https://appautomation.lambdatest.com/test?testID=" + get_session_info(job_id)['test_id']
@staticmethod
def get_jenkins_link_to_rerun_e2e(branch_name="develop", pr_id="", tr_case_ids=""):
@@ -115,19 +103,6 @@ class BaseTestReport:
return 'https://ci.status.im/job/status-mobile/job/e2e/job/status-app-prs-rerun/parambuild/' \
'?BRANCH_NAME=%s&PR_ID=%s&APK_NAME=%s.apk&TR_CASE_IDS=%s' % (branch_name, pr_id, pr_id, tr_case_ids)
- def get_sauce_final_screenshot_url(self, job_id):
- return 'https://media.giphy.com/media/9M5jK4GXmD5o1irGrF/giphy.gif'
- # temp blocked, no sense with groups
- # from tests.conftest import sauce, apibase
- # token = self.get_sauce_token(job_id)
- # username = sauce.accounts.account_user.get_active_user().username
- # for _ in range(10):
- # try:
- # scr_number = sauce.jobs.get_job_assets(username=username, job_id=job_id)['screenshots'][-1]
- # return 'https://assets.%s/jobs/%s/%s?auth=%s' % (apibase, job_id, scr_number, token)
- # except SauceException:
- # time.sleep(3)
-
@staticmethod
def is_test_successful(test):
# Test passed if last testrun has passed
diff --git a/test/appium/support/github_report.py b/test/appium/support/github_report.py
index 9a1a37a868..5c8de23a3c 100644
--- a/test/appium/support/github_report.py
+++ b/test/appium/support/github_report.py
@@ -166,7 +166,7 @@ class GithubHtmlReport(BaseTestReport):
first_command = 0
else:
first_command = 0
- html += "Steps, video, logs" % self.get_sauce_job_url(job_id, first_command)
+ html += "Steps, video, logs" % self.get_lambda_test_job_url(job_id, first_command)
# if test_run.error:
# html += "Failure screenshot" % self.get_sauce_final_screenshot_url(job_id)
html += "
"
diff --git a/test/appium/support/lambda_test.py b/test/appium/support/lambda_test.py
index 458c54d875..ff5c11b243 100644
--- a/test/appium/support/lambda_test.py
+++ b/test/appium/support/lambda_test.py
@@ -9,7 +9,6 @@ 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}
@@ -22,7 +21,18 @@ 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
+ "name": session_name # , "status_ind": status
}
)
assert resp.status_code == 200
+
+
+def get_session_info(session_id):
+ resp = session.get(url="https://mobile-api.lambdatest.com/mobile-automation/api/v1/sessions/%s" % session_id)
+ assert resp.status_code == 200
+ return resp.json()['data']
+
+
+def upload_image():
+ 'curl -u "yevheniia:fZaXHEAFEWOVCZLnSVrwHY11eJGsWAknibtG572PiZsvT1h57V" -X POST https://mobile-mgm.lambdatest.com/mfs/v1.0/media/upload -F media_file=@/Users/yberdnyk/Downloads/aaa.png -F type=image -F custom_id=SampleImage'
+ pass
diff --git a/test/appium/support/testrail_report.py b/test/appium/support/testrail_report.py
index ff944e088a..912a38f7cd 100644
--- a/test/appium/support/testrail_report.py
+++ b/test/appium/support/testrail_report.py
@@ -167,10 +167,10 @@ class TestrailReport(BaseTestReport):
else:
first_command = 0
try:
- devices += "# [Device %d](%s) \n" % (i + 1, self.get_sauce_job_url(job_id=device,
- first_command=first_command))
+ devices += "# [Device %d](%s) \n" % (i + 1, self.get_lambda_test_job_url(job_id=device,
+ first_command=first_command))
except KeyError:
- devices += "# Device %s: SauceLabs session was not found \n" % (i + 1)
+ devices += "# Device %s: LambdaTest session was not found \n" % (i + 1)
comment = str()
if test.group_name:
comment += "# Class: %s \n" % test.group_name
@@ -274,9 +274,8 @@ class TestrailReport(BaseTestReport):
first_command = 0
else:
first_command = 0
- job_url = self.get_sauce_job_url(job_id=job_id, first_command=first_command)
- case_info = "Logs for device %d: [steps](%s), [failure screenshot](%s)" \
- % (f, job_url, self.get_sauce_final_screenshot_url(job_id))
+ job_url = self.get_lambda_test_job_url(job_id=job_id, first_command=first_command)
+ case_info = "Logs for device %d: [steps](%s)" % (f, job_url)
if test.group_name:
group_blocks[test.group_name] += case_title + error + case_info
diff --git a/test/appium/tests/base_test_case.py b/test/appium/tests/base_test_case.py
index 6041b3874a..7dc9262ebe 100644
--- a/test/appium/tests/base_test_case.py
+++ b/test/appium/tests/base_test_case.py
@@ -12,17 +12,14 @@ import requests
from appium import webdriver
from appium.options.common import AppiumOptions
from appium.webdriver.common.mobileby import MobileBy
-from sauceclient import SauceException
-from selenium.common.exceptions import NoSuchElementException, TimeoutException, WebDriverException
-from selenium.webdriver.support.wait import WebDriverWait
+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 sauce_username, sauce_access_key, apibase, github_report, run_name, option, \
- lambda_test_username, lambda_test_access_key
+from tests.conftest import github_report, run_name, 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'
@@ -62,41 +59,6 @@ def add_local_devices_to_capabilities():
return updated_capabilities
-def get_capabilities_sauce_lab():
- caps = dict()
- caps['platformName'] = 'Android'
- caps['idleTimeout'] = 1000
- caps['appium:app'] = 'storage:filename=' + test_suite_data.apk_name
- caps['appium:deviceName'] = 'Android GoogleAPI Emulator'
- caps['appium:deviceOrientation'] = 'portrait'
- caps['appium:platformVersion'] = '14.0'
- caps['appium:automationName'] = 'UiAutomator2'
- caps['appium:newCommandTimeout'] = 600
- caps['appium:idleTimeout'] = 1000
- caps['appium:hideKeyboard'] = True
- caps['appium:automationName'] = 'UiAutomator2'
- caps['appium:setWebContentDebuggingEnabled'] = True
- caps['appium:ignoreUnimportantViews'] = False
- caps['ignoreUnimportantViews'] = False
- caps['appium:enableNotificationListener'] = True
- caps['enableNotificationListener'] = True
- caps['appium:enforceXPath1'] = True
- caps['enforceXPath1'] = True
- caps['sauce:options'] = dict()
- caps['sauce:options']['appiumVersion'] = '2.11.0'
- caps['sauce:options']['username'] = sauce_username
- caps['sauce:options']['accessKey'] = sauce_access_key
- caps['sauce:options']['build'] = run_name
- caps['sauce:options']['name'] = test_suite_data.current_test.name
- caps['sauce:options']['maxDuration'] = 3600
- caps['sauce:options']['idleTimeout'] = 1000
-
- options = AppiumOptions()
- options.load_capabilities(caps)
-
- return options
-
-
def get_lambda_test_capabilities_real_device():
capabilities = {
"lt:options": {
@@ -104,10 +66,9 @@ def get_lambda_test_capabilities_real_device():
"platformName": "android",
"deviceName": "Pixel 8",
"platformVersion": "14",
- "app": "lt://APP10160471311729636675434695", # lambda_test_apk_url,
+ "app": pytest_config_global['lt_apk_url'],
"devicelog": True,
"visual": True,
- # "network": True,
"video": True,
"build": run_name,
"name": test_suite_data.current_test.group_name,
@@ -126,16 +87,21 @@ def get_lambda_test_capabilities_emulator():
"w3c": True,
"platformName": "android",
"deviceName": "Pixel 6",
- "appiumVersion": "2.11.0",
+ "appiumVersion": "2.1.3",
"platformVersion": "14",
- "app": "lt://APP10160522181729681886587724", #option.lambda_test_apk_url,
+ "app": pytest_config_global['lt_apk_url'],
"devicelog": True,
"visual": True,
- # "network": True,
"video": True,
"build": run_name,
"name": test_suite_data.current_test.group_name,
- "idleTimeout": 1000
+ "idleTimeout": 1000,
+ # "enableImageInjection": True,
+ # "uploadMedia": ["lt://MEDIA2b3e34e2b0ee4928b9fc38c603f98191",], # "lt://MEDIAcfc1b4f1af0740759254404186bbe4f1"]
+ },
+ "appium:options": {
+ "automationName": "UiAutomator2",
+ "hideKeyboard": True
}
}
options = AppiumOptions()
@@ -165,9 +131,9 @@ def pull_requests_log(driver):
class AbstractTestCase:
__metaclass__ = ABCMeta
- def print_sauce_lab_info(self, driver):
+ def print_lt_session_info(self, driver):
sys.stdout = sys.stderr
- print("SauceOnDemandSessionID=%s job-name=%s" % (driver.session_id, run_name))
+ print("LambdaTestSessionID=%s job-name=%s" % (driver.session_id, run_name))
def get_translation_by_key(self, key):
return transl[key]
@@ -220,6 +186,16 @@ class Driver(webdriver.Remote):
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):
@@ -240,11 +216,11 @@ 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) = (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)
+ # (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()
@@ -254,7 +230,7 @@ class SingleDeviceTestCase(AbstractTestCase):
def teardown_method(self, method):
if self.environment == 'sauce':
- self.print_sauce_lab_info(self.driver)
+ self.print_lt_session_info(self.driver)
try:
self.add_alert_text_to_report(self.driver)
geth_content = pull_geth(self.driver)
@@ -290,48 +266,6 @@ class LocalMultipleDeviceTestCase(AbstractTestCase):
pass
-class SauceMultipleDeviceTestCase(AbstractTestCase):
-
- @classmethod
- def setup_class(cls):
- cls.loop = asyncio.new_event_loop()
- asyncio.set_event_loop(cls.loop)
-
- def setup_method(self, method):
- self.drivers = dict()
- self.errors = Errors()
-
- def create_drivers(self, quantity=2, max_duration=1800, custom_implicitly_wait=None):
- self.drivers = self.loop.run_until_complete(start_threads(quantity,
- Driver,
- self.drivers,
- executor_sauce_lab,
- get_capabilities_sauce_lab()))
- 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 implicit_wait)
-
- 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(pull_geth(self.drivers[driver]))
- self.drivers[driver].quit()
- except (WebDriverException, AttributeError):
- pass
- geth = {geth_names[i]: geth_contents[i] for i in range(len(geth_names))}
- github_report.save_test(test_suite_data.current_test, geth)
-
- @classmethod
- def teardown_class(cls):
- cls.loop.close()
-
-
def create_shared_drivers(quantity):
drivers = dict()
if pytest_config_global['env'] == 'local':
@@ -354,18 +288,20 @@ def create_shared_drivers(quantity):
drivers,
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
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 _, driver in drivers.items():
+ for i, driver in drivers.items():
try:
+ driver.update_lt_session_status(i + 1, "failed")
driver.quit()
except (WebDriverException, AttributeError):
pass
@@ -405,13 +341,14 @@ class LocalSharedMultipleDeviceTestCase(AbstractTestCase):
pass
-class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
+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("sauce:context=Started %s" % method.__name__)
+ 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():
@@ -423,7 +360,7 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
log_names, log_contents = [], []
for driver in self.drivers:
try:
- self.print_sauce_lab_info(self.drivers[driver])
+ 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)))
@@ -451,7 +388,6 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
@classmethod
def teardown_class(cls):
- from tests.conftest import sauce
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[
@@ -467,20 +403,13 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
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))
- 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__)
- 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
+ lt_session_status = "failed"
+ else:
+ lt_session_status = "passed"
try:
+ driver.update_lt_session_status(i + 1, lt_session_status)
driver.quit()
- except WebDriverException:
+ except (WebDriverException, RemoteDisconnected):
pass
# url = 'https://api.%s/rest/v1/%s/jobs/%s/assets/%s' % (apibase, sauce_username, session_id, "log.json")
# try:
@@ -514,11 +443,9 @@ class SauceSharedMultipleDeviceTestCase(AbstractTestCase):
if pytest_config_global['env'] == 'local':
- MultipleDeviceTestCase = LocalMultipleDeviceTestCase
MultipleSharedDeviceTestCase = LocalSharedMultipleDeviceTestCase
else:
- MultipleDeviceTestCase = SauceMultipleDeviceTestCase
- MultipleSharedDeviceTestCase = SauceSharedMultipleDeviceTestCase
+ MultipleSharedDeviceTestCase = LambdaTestSharedMultipleDeviceTestCase
class NoDeviceTestCase(AbstractTestCase):
diff --git a/test/appium/tests/conftest.py b/test/appium/tests/conftest.py
index b306b87873..14d42f2abe 100644
--- a/test/appium/tests/conftest.py
+++ b/test/appium/tests/conftest.py
@@ -1,3 +1,4 @@
+import json
import os
import re
import signal
@@ -11,6 +12,7 @@ from os import environ
import pytest
import requests
from _pytest.runner import runtestprotocol
+from filelock import FileLock
from requests.exceptions import ConnectionError as c_er
import tests
@@ -18,9 +20,6 @@ from support.device_stats_db import DeviceStatsDB
from support.test_rerun import should_rerun_test
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')
@@ -38,12 +37,8 @@ def pytest_addoption(parser):
help='Url or local path to apk')
parser.addoption('--env',
action='store',
- default='sauce',
- 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')
+ default='lt',
+ help='Specify environment: local/lt/api')
parser.addoption('--platform_version',
action='store',
default='8.0',
@@ -60,10 +55,6 @@ def pytest_addoption(parser):
action='store',
default=False,
help='boolean; For creating testrail report per run')
- parser.addoption('--network',
- action='store',
- default='ropsten',
- help='string; ropsten or rinkeby')
parser.addoption('--rerun_count',
action='store',
default=0,
@@ -145,32 +136,18 @@ def pytest_addoption(parser):
@dataclass
class Option:
datacenter: str = None
- lambda_test_apk_url: str = None
option = Option()
testrail_report = None
github_report = None
-apibase = None
-sauce = None
run_name = None
-# lambda_test_apk_url = None
-
-
def is_master(config):
return not hasattr(config, 'workerinput')
-def is_uploaded():
- 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
def _upload_time_limit(seconds):
def signal_handler(signum, frame):
@@ -193,20 +170,13 @@ 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)
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:
return _upload_and_check_response(apk_file_path)
- # break
except (ConnectionError, RemoteDisconnected, c_er):
time.sleep(10)
@@ -252,20 +222,10 @@ def pytest_configure(config):
testrail_report = TestrailReport()
from support.github_report import GithubHtmlReport
global github_report
- from saucelab_api_client.saucelab_api_client import SauceLab
github_report = GithubHtmlReport()
tests.pytest_config_global = vars(config.option)
config.addinivalue_line("markers", "testrail_id(name): empty")
- global apibase
- if config.getoption('datacenter') == 'us-west-1':
- apibase = 'us-west-1.saucelabs.com'
- elif config.getoption('datacenter') == 'eu-central-1':
- apibase = 'eu-central-1.saucelabs.com'
- else:
- raise NotImplementedError("Unknown SauceLabs datacenter")
- # global sauce
- # sauce = SauceLab('https://api.' + apibase + '/', sauce_username, sauce_access_key)
if config.getoption('log_steps'):
import logging
logging.basicConfig(level=logging.INFO)
@@ -281,18 +241,6 @@ 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
@@ -308,8 +256,23 @@ def pytest_configure(config):
)
-def pytest_configure_node(node):
- node.workerinput['lambda_test_apk_url'] = node.config.option.lambda_test_apk_url
+@pytest.fixture(scope='session', autouse=True)
+def upload_apk(tmp_path_factory):
+ fn = tmp_path_factory.getbasetemp().parent / "lt_apk.json"
+ with FileLock(str(fn) + ".lock"):
+ if fn.is_file():
+ data = json.loads(fn.read_text())
+ tests.pytest_config_global['lt_apk_url'] = data['lambda_test_apk_url']
+ else:
+ apk_src = tests.pytest_config_global['apk']
+ if apk_src.startswith('http'):
+ apk_path = _download_apk(apk_src)
+ else:
+ apk_path = apk_src
+ tests.pytest_config_global['lt_apk_url'] = _upload_and_check_response(apk_path)
+ fn.write_text(json.dumps({'lambda_test_apk_url': tests.pytest_config_global['lt_apk_url']}))
+ if apk_src.startswith('http'):
+ os.remove(apk_path)
def pytest_unconfigure(config):
@@ -342,7 +305,6 @@ def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
- is_sauce_env = item.config.getoption('env') == 'sauce'
case_ids_set = item.config.getoption("run_testrail_ids")
def catch_error():
@@ -381,10 +343,6 @@ 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 error:
test_suite_data.current_test.testruns[-1].error = final_error
github_report.save_test(test_suite_data.current_test)
@@ -401,8 +359,6 @@ 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 item.config.getoption('docker'):
device_stats = appium_container.get_device_stats()
if item.config.getoption('bugreport'):
@@ -426,15 +382,6 @@ def pytest_runtest_makereport(item, call):
device_stats_db.save_stats(build_name, item.name, test_group, not report.failed, device_stats)
-def update_sauce_jobs(test_name, job_ids, passed):
- from sauceclient import SauceException
- for job_id in job_ids.keys():
- try:
- sauce.jobs.update_job(username=sauce_username, job_id=job_id, name=test_name, passed=passed)
- except (RemoteDisconnected, SauceException, c_er):
- pass
-
-
def get_testrail_case_id(item):
testrail_id = item.get_closest_marker('testrail_id')
if testrail_id:
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 dbe95b3463..220ed4c993 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,7 +14,6 @@ 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):
@@ -61,13 +60,13 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
"Receiver also sets 'thumbs-up' emoji and verifies counter on received message in 1-1 chat")
message_receiver = self.chat_2.chat_element_by_text(message_from_sender)
message_receiver.emojis_below_message(emoji="thumbs-up").wait_for_element_text(1, 90)
- self.chat_2.add_remove_same_reaction(message_from_sender)
+ self.chat_2.add_remove_same_reaction()
message_receiver.emojis_below_message(emoji="thumbs-up").wait_for_element_text(2)
message_sender.emojis_below_message(emoji="thumbs-up").wait_for_element_text(2, 90)
self.device_2.just_fyi(
"Receiver removes 'thumbs-up' emoji and verify that counter will decrease for both users")
- self.chat_2.add_remove_same_reaction(message_from_sender)
+ self.chat_2.add_remove_same_reaction()
message_receiver.emojis_below_message(emoji="thumbs-up").wait_for_element_text(1)
message_sender.emojis_below_message(emoji="thumbs-up").wait_for_element_text(1, 90)
@@ -79,21 +78,20 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
message_sender.emojis_below_message(emoji="love").wait_for_element_text(1, 90)
self.device_1.just_fyi("Sender votes for 'love' reaction. Check reactions counters")
- self.chat_1.add_remove_same_reaction(message_from_sender, emoji="love")
+ self.chat_1.add_remove_same_reaction(emoji="love")
message_receiver.emojis_below_message(emoji="thumbs-up").wait_for_element_text(1)
message_sender.emojis_below_message(emoji="thumbs-up").wait_for_element_text(1)
message_receiver.emojis_below_message(emoji="love").wait_for_element_text(2, 90)
message_sender.emojis_below_message(emoji="love").wait_for_element_text(2)
self.device_1.just_fyi("Check emojis info")
- message_sender.emojis_below_message(emoji="love").long_press_until_element_is_shown(
- self.chat_1.authors_for_reaction(emoji="love"))
+ message_sender.emojis_below_message(emoji="love").long_press_without_release()
if not self.chat_1.user_list_element_by_name(
self.username_1).is_element_displayed() or not self.chat_1.user_list_element_by_name(
self.username_2).is_element_displayed():
self.errors.append("Incorrect users are shown for 'love' reaction.")
- self.chat_1.authors_for_reaction(emoji="thumbs-up").click()
+ self.chat_1.authors_for_reaction(emoji="thumbs-up").double_click()
if not self.chat_1.user_list_element_by_name(
self.username_1).is_element_displayed() or self.chat_1.user_list_element_by_name(
self.username_2).is_element_displayed():
@@ -232,8 +230,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
self.errors.append("Can pin more than 3 messages in chat")
else:
unpin_element = self.chat_1.element_by_translation_id('unpin-from-chat')
- self.chat_1.pinned_messages_list.message_element_by_text(self.message_2).long_press_element(
- element_to_release_on=unpin_element)
+ self.chat_1.pinned_messages_list.message_element_by_text(self.message_2).long_press_without_release()
self.home_1.just_fyi("Unpin one message so that another could be pinned")
unpin_element.click_until_absense_of_element(desired_element=unpin_element)
self.chat_1.pin_message(self.message_4, 'pin-to-chat')
@@ -254,7 +251,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
pinned_message = self.chat_1.pinned_messages_list.message_element_by_text(self.message_4)
unpin_element = self.chat_1.element_by_translation_id("unpin-from-chat")
- pinned_message.long_press_element(element_to_release_on=unpin_element)
+ pinned_message.long_press_without_release()
unpin_element.click_until_absense_of_element(unpin_element)
# try:
# self.chat_2.chat_element_by_text(self.message_4).pinned_by_label.wait_for_invisibility_of_element()
@@ -276,7 +273,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
def test_1_1_chat_non_latin_messages_stack_update_profile_photo(self):
self.home_1.navigate_back_to_home_view()
self.home_1.profile_button.click()
- self.profile_1.edit_profile_picture(image_index=2)
+ self.profile_1.edit_profile_picture(image_index=0)
self.chat_2.just_fyi("Send messages with non-latin symbols")
self.home_1.click_system_back_button()
@@ -315,8 +312,8 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
self.chat_1.just_fyi("Go back to chat view and checking that profile photo is updated")
if not self.chat_2.chat_message_input.is_element_displayed():
self.home_2.get_chat(self.username_1).click()
- if self.chat_2.chat_element_by_text(message).member_photo.is_element_differs_from_template("member3.png",
- diff=7):
+ if self.chat_2.chat_element_by_text(message).member_photo.is_element_differs_from_template(
+ "profile_image_in_1_1_chat.png", diff=7):
self.errors.append("Image of user in 1-1 chat is too different from template!")
self.errors.verify_no_errors()
@@ -415,14 +412,14 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
self.chat_1.just_fyi("Device 1 sends an image")
image_description = "test image"
- self.chat_1.send_images_with_description(description=image_description, indexes=[2])
+ self.chat_1.send_images_with_description(description=image_description, indexes=[0])
self.chat_2.just_fyi("Device 2 checks image message")
if not self.chat_2.chat_element_by_text(image_description).is_element_displayed(30):
self.chat_2.hide_keyboard_if_shown()
self.chat_2.chat_element_by_text(image_description).wait_for_visibility_of_element(30)
if not self.chat_2.chat_element_by_text(
- image_description).image_in_message.is_element_image_similar_to_template('saucelabs_sauce_chat.png'):
+ image_description).image_in_message.is_element_image_similar_to_template('image_1_chat_view.png'):
self.errors.append("Not expected image is shown to the receiver.")
for chat in self.chat_1, self.chat_2:
@@ -460,7 +457,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
chat.just_fyi("Check that image is saved in gallery")
chat.show_images_button.click()
chat.allow_all_button.click_if_shown()
- if not chat.get_image_by_index(0).is_element_image_similar_to_template("saucelabs_sauce_gallery.png"):
+ if not chat.get_image_by_index(0).is_element_image_similar_to_template("image_1_gallery_view.png"):
self.errors.append(
"Image is not saved to gallery for %s." % ("sender" if chat is self.chat_1 else "receiver"))
chat.click_system_back_button()
@@ -485,7 +482,7 @@ class TestOneToOneChatMultipleSharedDevicesNewUi(MultipleSharedDeviceTestCase):
self.chat_2.send_message(message_after_edit_1_1)
self.chat_2.chat_element_by_text(message_after_edit_1_1).wait_for_status_to_be("Delivered")
chat_1_element = self.chat_1.chat_element_by_text(message_after_edit_1_1)
- chat_1_element.long_press_element()
+ chat_1_element.long_press_without_release()
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)
@@ -643,13 +640,13 @@ class TestOneToOneChatMultipleSharedDevicesNewUiTwo(MultipleSharedDeviceTestCase
self.chat_1.just_fyi("Unmute chat")
self.chat_1.navigate_back_to_home_view()
- chat.long_press_element()
+ chat.long_press_without_release()
if self.home_1.mute_chat_button.text != transl["unmute-chat"]:
self.errors.append("Chat is not muted")
expected_text = "Muted until you turn it back on"
if not self.home_1.element_by_text(expected_text).is_element_displayed():
self.errors.append("Text '%s' is not shown for muted chat" % expected_text)
- self.home_1.mute_chat_button.click()
+ self.home_1.mute_chat_button.double_click()
unmuted_message = "after unmute"
self.chat_2.send_message(unmuted_message)
diff --git a/test/appium/tests/critical/chats/test_group_chat.py b/test/appium/tests/critical/chats/test_group_chat.py
index 4506d594bb..016ba33efc 100644
--- a/test/appium/tests/critical/chats/test_group_chat.py
+++ b/test/appium/tests/critical/chats/test_group_chat.py
@@ -13,7 +13,6 @@ 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):
@@ -119,7 +118,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[1].set_reaction(message=message, emoji="love")
self.chats[2].just_fyi("Member_2 sets 2 reactions on the message: 'thumbs-up' and 'laugh'")
- self.chats[2].add_remove_same_reaction(message=message, emoji="thumbs-up")
+ self.chats[2].add_remove_same_reaction(emoji="thumbs-up")
self.chats[2].set_reaction(message=message, emoji="laugh")
def _check_reactions_count(chat_view_index):
@@ -136,14 +135,13 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
)))
self.chats[0].just_fyi("Admin checks info about voted users")
- self.chats[0].chat_element_by_text(message).emojis_below_message(
- emoji="thumbs-up").long_press_until_element_is_shown(self.chats[0].authors_for_reaction(emoji="thumbs-up"))
+ self.chats[0].chat_element_by_text(message).emojis_below_message(emoji="thumbs-up").long_press_without_release()
if not self.chats[0].user_list_element_by_name(
self.usernames[1]).is_element_displayed() or not self.chats[0].user_list_element_by_name(
self.usernames[2]).is_element_displayed():
self.errors.append("Incorrect users are shown for 'thumbs-up' reaction.")
- self.chats[0].authors_for_reaction(emoji="love").click()
+ self.chats[0].authors_for_reaction(emoji="love").double_click()
if not self.chats[0].user_list_element_by_name(
self.usernames[1]).is_element_displayed() or self.chats[0].user_list_element_by_name(
self.usernames[2]).is_element_displayed():
@@ -169,12 +167,12 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[0].navigate_back_to_chat_view()
self.chats[1].just_fyi("Member_1 removes 'thumbs-up' reaction and adds 'sad' one")
- self.chats[1].add_remove_same_reaction(message=message, emoji="thumbs-up")
+ self.chats[1].add_remove_same_reaction(emoji="thumbs-up")
self.chats[1].set_reaction(message=message, emoji="sad")
self.chats[2].just_fyi("Member_2 removes 'laugh' reaction and adds 'sad' one")
- self.chats[2].add_remove_same_reaction(message=message, emoji="laugh")
- self.chats[2].add_remove_same_reaction(message=message, emoji="sad")
+ self.chats[2].add_remove_same_reaction(emoji="laugh")
+ self.chats[2].add_remove_same_reaction(emoji="sad")
def _check_reactions_count_after_change(chat_view_index):
self.chats[chat_view_index].just_fyi(
@@ -212,14 +210,13 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
chat.navigate_back_to_home_view()
self.chats[0].just_fyi("Admin checks info about voted users after relogin")
- message_element.emojis_below_message(
- emoji="thumbs-up").long_press_until_element_is_shown(self.chats[0].authors_for_reaction(emoji="thumbs-up"))
+ message_element.emojis_below_message(emoji="thumbs-up").long_press_without_release()
if self.chats[0].user_list_element_by_name(
self.usernames[1]).is_element_displayed() or not self.chats[0].user_list_element_by_name(
self.usernames[2]).is_element_displayed():
self.errors.append("Incorrect users are shown for 'thumbs-up' reaction after relogin.")
- self.chats[0].authors_for_reaction(emoji="love").click()
+ self.chats[0].authors_for_reaction(emoji="love").double_click()
if not self.chats[0].user_list_element_by_name(
self.usernames[1]).is_element_displayed() or self.chats[0].user_list_element_by_name(
self.usernames[2]).is_element_displayed():
@@ -241,18 +238,18 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[1].just_fyi("Member_1 sends an image")
image_description = "test image"
- self.chats[1].send_images_with_description(description=image_description, indexes=[2])
+ self.chats[1].send_images_with_description(description=image_description, indexes=[1])
self.chats[0].just_fyi("Admin checks image message")
chat_element = self.chats[0].chat_element_by_text(image_description)
chat_element.wait_for_visibility_of_element(60)
- if not chat_element.image_in_message.is_element_image_similar_to_template('saucelabs_sauce_group_chat.png'):
+ if not chat_element.image_in_message.is_element_image_similar_to_template('image_2_chat_view.png'):
self.errors.append("Not expected image is shown to the admin.")
self.chats[2].just_fyi("Member_2 checks image message")
chat_element = self.chats[2].chat_element_by_text(image_description)
chat_element.wait_for_visibility_of_element(60)
- if not chat_element.image_in_message.is_element_image_similar_to_template('saucelabs_sauce_group_chat.png'):
+ if not chat_element.image_in_message.is_element_image_similar_to_template('image_2_chat_view.png'):
self.errors.append("Not expected image is shown to the member_2.")
self.chats[0].just_fyi("Admin opens the image and shares it")
@@ -293,7 +290,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[2].just_fyi("Member_2 checks that image was saved in gallery")
self.chats[2].show_images_button.click()
self.chats[2].allow_all_button.click_if_shown()
- if not self.chats[2].get_image_by_index(0).is_element_image_similar_to_template("saucelabs_sauce_gallery.png"):
+ if not self.chats[2].get_image_by_index(0).is_element_image_similar_to_template("image_2_gallery_view.png"):
self.errors.append("Image is not saved to gallery for member_2.")
self.chats[2].navigate_back_to_home_view()
@@ -374,10 +371,11 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.errors.append("Message 1 is not pinned in group chat!")
self.chats[0].just_fyi("Check that non admin user can not unpin messages")
- self.chats[1].chat_element_by_text(self.message_1).long_press_element()
+ self.chats[1].chat_element_by_text(self.message_1).long_press_without_release()
if self.chats[1].element_by_translation_id("unpin-from-chat").is_element_displayed():
self.errors.append("Unpin option is available for non-admin user")
self.chats[1].tap_by_coordinates(500, 100)
+ self.chats[1].tap_by_coordinates(500, 100)
# not implemented yet :
@@ -406,8 +404,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
self.chats[0].pin_message(self.message_4, 'pin-to-chat')
self.chats[0].view_pinned_messages_button.click_until_presence_of_element(self.chats[0].pinned_messages_list)
unpin_element = self.chats[0].element_by_translation_id('unpin-from-chat')
- self.chats[0].pinned_messages_list.message_element_by_text(self.message_2).long_press_element(
- element_to_release_on=unpin_element)
+ self.chats[0].pinned_messages_list.message_element_by_text(self.message_2).long_press_without_release()
unpin_element.click_until_absense_of_element(desired_element=unpin_element)
self.chats[0].chat_element_by_text(self.message_4).click()
self.chats[0].pin_message(self.message_4, 'pin-to-chat')
@@ -457,7 +454,7 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
"Muted until %s %s" % (exp_time.strftime('%H:%M'), "today" if current_time.hour < 16 else "tomorrow") for
exp_time in expected_times]
chat = self.homes[1].get_chat(self.chat_name)
- chat.long_press_element()
+ chat.long_press_without_release()
if self.homes[1].mute_chat_button.text != transl["unmute-chat"]:
pytest.fail("Chat is not muted")
current_text = self.homes[1].mute_chat_button.unmute_caption_text
@@ -487,15 +484,16 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
if not chat.chat_preview.text.startswith("%s: %s" % (self.usernames[0], muted_message[:25])):
self.errors.append("Message text '%s' is not shown in chat preview after mute" % muted_message)
chat.click()
+ chat.click()
if not self.chats[1].chat_element_by_text(muted_message).is_element_displayed(30):
self.errors.append(
- "Message '%s' is not shown in chat for %s after mute" % (muted_message, self.usernames[1]))
+ "Message '%s' is not shown in chat for %s (Member 1) after mute" % (muted_message, self.usernames[1]))
self.chats[1].navigate_back_to_home_view()
self.chats[1].just_fyi("Member 1 unmutes the chat")
- chat.long_press_element()
- self.homes[1].mute_chat_button.click()
- chat.long_press_element()
+ chat.long_press_without_release()
+ self.homes[1].mute_chat_button.double_click()
+ chat.long_press_without_release()
if self.homes[1].element_starts_with_text("Muted until").is_element_displayed():
self.errors.append("Chat is still muted after being unmuted")
self.errors.verify_no_errors()
@@ -523,8 +521,10 @@ class TestGroupChatMultipleDeviceMergedNewUI(MultipleSharedDeviceTestCase):
if not chat.chat_preview.text.startswith("%s: %s" % (self.usernames[2], unmuted_message)):
self.errors.append("Message text '%s' is not shown in chat preview after unmute" % unmuted_message)
chat.click()
+ chat.click()
if not self.chats[1].chat_element_by_text(unmuted_message).is_element_displayed(30):
self.errors.append(
- "Message '%s' is not shown in chat for %s after unmute" % (self.usernames[1], unmuted_message))
+ "Message '%s' is not shown in chat for %s (Member 1) after unmute" % (
+ unmuted_message, self.usernames[1]))
self.errors.verify_no_errors()
diff --git a/test/appium/tests/critical/chats/test_public_chat_browsing.py b/test/appium/tests/critical/chats/test_public_chat_browsing.py
index 6f34f4e62e..0812212522 100644
--- a/test/appium/tests/critical/chats/test_public_chat_browsing.py
+++ b/test/appium/tests/critical/chats/test_public_chat_browsing.py
@@ -281,11 +281,9 @@ class TestCommunityOneDeviceMerged(MultipleSharedDeviceTestCase):
# if community_name == 'Status':
self.home.just_fyi("Check Status community screen")
card.click()
- self.community_view.join_button.save_new_screenshot_of_element('status_community_join_button_aaa.png')
if self.community_view.join_button.is_element_differs_from_template(
'status_community_join_button.png'):
self.errors.append("Status community Join button is different from expected template.")
- self.community_view.community_logo.save_new_screenshot_of_element('status_community_logo_aaa.png')
if self.community_view.community_logo.is_element_differs_from_template('status_community_logo.png'):
self.errors.append("Status community logo is different from expected template.")
@@ -412,7 +410,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
@marks.testrail_id(703194)
def test_community_several_images_send_reply(self):
self.home_1.just_fyi('Send several images in 1-1 chat from Gallery')
- image_description, file_name = 'gallery', 'gallery_1.png'
+ image_description = 'gallery'
self.channel_1.send_images_with_description(image_description, [0, 1])
self.channel_2.just_fyi("Check gallery on second device")
@@ -423,7 +421,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
try:
chat_element.wait_for_visibility_of_element(120)
received = True
- if chat_element.image_container_in_message.is_element_differs_from_template(file_name, 5):
+ if chat_element.image_container_in_message.is_element_differs_from_template("images_gallery.png", 5):
self.errors.append("Gallery message do not match the template!")
except TimeoutException:
self.errors.append("Gallery message was not received")
@@ -465,7 +463,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
self.channel_2.hide_keyboard_if_shown()
self.channel_2.chat_element_by_text(image_description).wait_for_visibility_of_element(10)
if not self.channel_2.chat_element_by_text(
- image_description).image_in_message.is_element_image_similar_to_template('image_sent_in_community.png'):
+ image_description).image_in_message.is_element_image_similar_to_template('image_1_chat_view.png'):
self.errors.append("Not expected image is shown to the receiver")
if not self.channel_1.chat_element_by_text(image_description).is_element_displayed(60):
@@ -489,7 +487,7 @@ class TestCommunityMultipleDeviceMerged(MultipleSharedDeviceTestCase):
self.channel_1.show_images_button.click()
self.channel_1.allow_all_button.click_if_shown()
if not self.channel_1.get_image_by_index(0).is_element_image_similar_to_template(
- "sauce_dark_image_gallery.png"):
+ "image_1_gallery_view.png"):
self.errors.append('Saved image is not shown in Recent')
self.channel_1.click_system_back_button()
diff --git a/test/appium/tests/critical/test_fallback.py b/test/appium/tests/critical/test_fallback.py
index dcfa880f9f..d8d16af833 100644
--- a/test/appium/tests/critical/test_fallback.py
+++ b/test/appium/tests/critical/test_fallback.py
@@ -271,7 +271,7 @@ class TestFallbackMultipleDevice(MultipleSharedDeviceTestCase):
self.sign_in_2.continue_button.click()
if not self.sign_in_2.password_input.is_element_displayed():
self.errors.append("Can't recover an access with a valid passphrase")
- self.sign_in_2.click_system_back_button()
+ self.sign_in_2.click_system_back_button(times=2)
self.sign_in_2.just_fyi("Device 2: try recovering an account which is already synced")
self.sign_in_2.passphrase_edit_box.clear()
diff --git a/test/appium/tests/marks.py b/test/appium/tests/marks.py
index 3abb6c6af0..2addc25340 100644
--- a/test/appium/tests/marks.py
+++ b/test/appium/tests/marks.py
@@ -13,5 +13,3 @@ 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/tests/upgrade/test_upgrade.py b/test/appium/tests/upgrade/test_upgrade.py
index 0952257097..f60738a964 100644
--- a/test/appium/tests/upgrade/test_upgrade.py
+++ b/test/appium/tests/upgrade/test_upgrade.py
@@ -1,5 +1,5 @@
from tests import marks, pytest_config_global, test_dapp_name
-from tests.base_test_case import SingleDeviceTestCase, MultipleDeviceTestCase
+from tests.base_test_case import SingleDeviceTestCase, MultipleSharedDeviceTestCase
from tests.users import upgrade_users, transaction_recipients, basic_user, ens_user, transaction_senders
from views.sign_in_view import SignInView
import views.dbs.chats.data as chat_data
@@ -245,7 +245,7 @@ class TestUpgradeApplication(SingleDeviceTestCase):
@marks.upgrade
-class TestUpgradeMultipleApplication(MultipleDeviceTestCase):
+class TestUpgradeMultipleApplication(MultipleSharedDeviceTestCase):
@marks.testrail_id(695783)
def test_commands_audio_backward_compatibility_upgrade(self):
diff --git a/test/appium/views/base_element.py b/test/appium/views/base_element.py
index 7bcfa8cee1..24a5aec6bf 100644
--- a/test/appium/views/base_element.py
+++ b/test/appium/views/base_element.py
@@ -10,6 +10,9 @@ from PIL import Image, ImageChops, ImageStat
from appium.webdriver.common.mobileby import MobileBy
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException
from selenium.webdriver import ActionChains
+from selenium.webdriver.common.actions import interaction
+from selenium.webdriver.common.actions.action_builder import ActionBuilder
+from selenium.webdriver.common.actions.pointer_input import PointerInput
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
@@ -324,9 +327,21 @@ class BaseElement(object):
action.click_and_hold(element).perform()
time.sleep(2)
if element_to_release_on:
- action.release(element_to_release_on.find_element()).perform()
+ action.release(element_to_release_on.find_element())
+ action.perform()
else:
- action.release(element).perform()
+ action.release(element)
+ action.perform()
+
+ # actions = ActionChains(self.driver)
+ # actions.w3c_actions = ActionBuilder(self, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
+ # actions.w3c_actions.pointer_action.click_and_hold(element)
+ # actions.w3c_actions.pointer_action.release()
+ # actions.perform()
+
+ def long_press_without_release(self):
+ action = ActionChains(self.driver)
+ action.click_and_hold(self.find_element()).perform()
def long_press_until_element_is_shown(self, expected_element):
element = self.find_element()
diff --git a/test/appium/views/base_view.py b/test/appium/views/base_view.py
index 68ce150664..1b0f4aaa1f 100644
--- a/test/appium/views/base_view.py
+++ b/test/appium/views/base_view.py
@@ -411,6 +411,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.log_event("appium", "STEP: %s" % some_str)
def hide_keyboard_if_shown(self):
if self.driver.is_keyboard_shown():
diff --git a/test/appium/views/chat_view.py b/test/appium/views/chat_view.py
index 273e6da46e..6f8169caab 100644
--- a/test/appium/views/chat_view.py
+++ b/test/appium/views/chat_view.py
@@ -4,6 +4,8 @@ from datetime import datetime, timedelta
from time import sleep
import dateutil.parser
+import pytest
+from appium.webdriver.common.appiumby import AppiumBy
from selenium.common.exceptions import NoSuchElementException, TimeoutException, StaleElementReferenceException, \
InvalidElementStateException
from selenium.webdriver import ActionChains
@@ -232,11 +234,14 @@ class ChatElementByText(Text):
def replied_message_text(self):
class RepliedMessageText(Text):
def __init__(self, driver, parent_locator: str):
- super().__init__(driver, prefix=parent_locator,
- xpath="/preceding::android.widget.TextView[@content-desc='quoted-message']")
+ super().__init__(driver,
+ # prefix=parent_locator,
+ # xpath="/preceding::android.widget.TextView[@content-desc='quoted-message']"
+ accessibility_id='quoted-message')
try:
- return RepliedMessageText(self.driver, self.message_locator).text
+ # return RepliedMessageText(self.driver, self.message_locator).text
+ return self.find_element().find_element(by=AppiumBy.ACCESSIBILITY_ID, value='quoted-message').text
except NoSuchElementException:
return ''
@@ -1011,13 +1016,13 @@ class ChatView(BaseView):
def pin_message(self, message, action="pin"):
self.driver.info("Looking for message '%s' pin" % message)
element = self.element_by_translation_id(action)
- self.chat_element_by_text(message).long_press_until_element_is_shown(element)
+ self.chat_element_by_text(message).long_press_without_release()
element.click_until_absense_of_element(element)
def edit_message_in_chat(self, message_to_edit, message_to_update):
self.driver.info("Looking for message '%s' to edit it" % message_to_edit)
- self.chat_element_by_text(message_to_edit).message_body.long_press_element()
- self.element_by_translation_id("edit-message").click()
+ self.chat_element_by_text(message_to_edit).message_body.long_press_without_release()
+ self.element_by_translation_id("edit-message").double_click()
self.chat_message_input.clear()
self.chat_message_input.send_keys(message_to_update)
self.send_message_button.click()
@@ -1028,40 +1033,34 @@ class ChatView(BaseView):
delete_button = self.element_by_translation_id("delete-for-everyone")
else:
delete_button = self.element_by_translation_id("delete-for-me")
- self.chat_element_by_text(message).message_body.long_press_element()
- delete_button.click()
+ self.chat_element_by_text(message).message_body.long_press_without_release()
+ delete_button.double_click()
def copy_message_text(self, message_text):
self.driver.info("Copying '%s' message via long press" % message_text)
self.chat_element_by_text(message_text).wait_for_visibility_of_element()
- self.chat_element_by_text(message_text).long_press_element()
- self.element_by_translation_id("copy-text").click()
+ self.chat_element_by_text(message_text).long_press_without_release()
+ self.element_by_translation_id("copy-text").double_click()
def quote_message(self, message: str):
self.driver.info("Quoting '%s' message" % message)
element = self.chat_element_by_text(message)
element.wait_for_sent_state()
- element.long_press_until_element_is_shown(self.reply_message_button)
- self.reply_message_button.click()
+ element.long_press_without_release()
+ self.reply_message_button.double_click()
def set_reaction(self, message: str, emoji: str = 'thumbs-up', emoji_message=False):
self.driver.info("Setting '%s' reaction" % emoji)
- # Audio message is obvious should be tapped not on audio-scroll-line
- # so we tap on its below element as exception here (not the case for link/tag message!)
element = Button(self.driver, accessibility_id='reaction-%s' % emoji)
- if message == 'audio':
- self.audio_message_in_chat_timer.long_press_element()
+ if emoji_message:
+ self.element_by_text_part(message).long_press_without_release()
else:
- if not emoji_message:
- self.chat_element_by_text(message).long_press_until_element_is_shown(element)
- else:
- self.element_by_text_part(message).long_press_until_element_is_shown(element)
- # old UI
- # element = Button(self.driver, accessibility_id='pick-emoji-%s' % key)
- element.click()
+ self.chat_element_by_text(message).long_press_without_release()
+ element.wait_for_element()
+ element.double_click()
element.wait_for_invisibility_of_element()
- def add_remove_same_reaction(self, message: str, emoji: str = 'thumbs-up'):
+ def add_remove_same_reaction(self, emoji: str = 'thumbs-up'):
self.driver.info("Adding one more '%s' reaction or removing an added one" % emoji)
key = emojis[emoji]
element = Button(self.driver, accessibility_id='emoji-reaction-%s' % key)
@@ -1212,7 +1211,11 @@ class ChatView(BaseView):
self.show_images_button.click()
self.allow_button.click_if_shown()
self.allow_all_button.click_if_shown()
- [self.get_image_by_index(i).click() for i in indexes]
+ try:
+ [self.get_image_by_index(i).click() for i in indexes]
+ except NoSuchElementException:
+ self.click_system_back_button()
+ pytest.fail("Can't send image(s) with index(es) %s" % indexes)
self.images_confirm_selection_button.click()
self.chat_message_input.send_keys(description)
self.send_message_button.click()
diff --git a/test/appium/views/elements_templates/blank_camera_image.png b/test/appium/views/elements_templates/blank_camera_image.png
deleted file mode 100644
index 1b70e85963de31c501d592adeb66d641401994a2..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 390
zcmeAS@N?(olHy`uVBq!ia0vp^r9fQH!3HF4&+u<&U|{t1ba4!+nDh4H!Om6(5r%`?
zyAuSS$4_@}!ObI*VU?CP@A=KU2c7JB*m>99-4%Ef7`_aiu6{1-oD!M<^v9MS
diff --git a/test/appium/views/elements_templates/block_dark.png b/test/appium/views/elements_templates/block_dark.png
deleted file mode 100644
index 803f20cc36a7aca5672b6f2e87b2d0dfe8dfb93b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 9568
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
diff --git a/test/appium/views/elements_templates/collectible_pic_2.png b/test/appium/views/elements_templates/collectible_pic_2.png
deleted file mode 100644
index 6b7eaf945b32844a5b8acd3f3e21c41cde5bc2a8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 15322
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