2018-05-02 16:01:17 +00:00
|
|
|
import base64
|
|
|
|
import os
|
2018-05-16 19:59:36 +00:00
|
|
|
import time
|
2023-03-09 05:35:08 +00:00
|
|
|
from io import BytesIO
|
2018-06-21 16:40:27 +00:00
|
|
|
from timeit import timeit
|
|
|
|
|
2023-03-09 05:35:08 +00:00
|
|
|
import emoji
|
|
|
|
import imagehash
|
2021-05-28 15:39:33 +00:00
|
|
|
from PIL import Image, ImageChops, ImageStat
|
2018-01-14 17:43:36 +00:00
|
|
|
from appium.webdriver.common.mobileby import MobileBy
|
2023-08-12 18:04:28 +00:00
|
|
|
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException
|
2024-09-13 16:38:35 +00:00
|
|
|
from selenium.webdriver import ActionChains
|
2017-08-28 10:02:20 +00:00
|
|
|
from selenium.webdriver.support import expected_conditions
|
2023-03-09 05:35:08 +00:00
|
|
|
from selenium.webdriver.support.wait import WebDriverWait
|
|
|
|
|
2020-11-11 15:37:27 +00:00
|
|
|
from tests import transl
|
2017-08-28 10:02:20 +00:00
|
|
|
|
|
|
|
|
|
|
|
class BaseElement(object):
|
2021-01-25 16:35:40 +00:00
|
|
|
def __init__(self, driver, **kwargs):
|
2017-08-28 10:02:20 +00:00
|
|
|
self.driver = driver
|
2021-01-25 16:35:40 +00:00
|
|
|
self.by = MobileBy.XPATH
|
2017-08-28 10:02:20 +00:00
|
|
|
self.locator = None
|
2021-01-25 16:35:40 +00:00
|
|
|
self.xpath = None
|
|
|
|
self.accessibility_id = None
|
|
|
|
self.translation_id = None
|
|
|
|
self.uppercase = None
|
2021-11-18 15:16:48 +00:00
|
|
|
self.prefix = ''
|
2021-01-25 16:35:40 +00:00
|
|
|
self.suffix = None
|
|
|
|
self.id = None
|
2021-02-05 11:29:35 +00:00
|
|
|
self.class_name = None
|
2021-02-23 12:19:55 +00:00
|
|
|
self.AndroidUIAutomator = None
|
2021-01-25 16:35:40 +00:00
|
|
|
self.webview = None
|
|
|
|
|
|
|
|
self.__dict__.update(kwargs)
|
|
|
|
self.set_locator()
|
|
|
|
|
|
|
|
def set_locator(self):
|
|
|
|
if self.xpath:
|
|
|
|
self.locator = self.xpath
|
|
|
|
elif self.accessibility_id:
|
|
|
|
self.by = MobileBy.ACCESSIBILITY_ID
|
|
|
|
self.locator = self.accessibility_id
|
|
|
|
elif self.translation_id:
|
|
|
|
text = transl[self.translation_id]
|
|
|
|
self.locator = '//*[@text="%s"]' % text
|
|
|
|
if self.uppercase:
|
|
|
|
self.locator = '//*[@text="%s" or @text="%s"]' % (text, text.upper())
|
|
|
|
if self.suffix:
|
|
|
|
self.locator += self.suffix
|
|
|
|
elif self.id:
|
|
|
|
self.by = MobileBy.ID
|
|
|
|
self.locator = self.id
|
2021-02-05 11:29:35 +00:00
|
|
|
elif self.class_name:
|
|
|
|
self.by = MobileBy.CLASS_NAME
|
|
|
|
self.locator = self.class_name
|
2021-02-23 12:19:55 +00:00
|
|
|
elif self.AndroidUIAutomator:
|
|
|
|
self.by = MobileBy.ANDROID_UIAUTOMATOR
|
|
|
|
self.locator = self.AndroidUIAutomator
|
2021-01-25 16:35:40 +00:00
|
|
|
elif self.webview:
|
2021-01-27 10:19:03 +00:00
|
|
|
self.locator = '//*[@text="{0}"] | //*[@content-desc="{desc}"]'.format(self.webview, desc=self.webview)
|
2021-01-25 16:35:40 +00:00
|
|
|
if self.prefix:
|
|
|
|
self.locator = self.prefix + self.locator
|
|
|
|
return self
|
2017-08-28 10:02:20 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return self.__class__.__name__
|
|
|
|
|
|
|
|
def navigate(self):
|
|
|
|
return None
|
|
|
|
|
|
|
|
def find_element(self):
|
2018-06-14 10:57:04 +00:00
|
|
|
for _ in range(3):
|
|
|
|
try:
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Find `%s` by `%s`: `%s`" % (self.name, self.by, self.exclude_emoji(self.locator)))
|
2021-01-25 16:35:40 +00:00
|
|
|
return self.driver.find_element(self.by, self.locator)
|
2018-08-10 10:09:19 +00:00
|
|
|
except NoSuchElementException:
|
2018-08-15 12:51:52 +00:00
|
|
|
raise NoSuchElementException(
|
2021-11-18 15:16:48 +00:00
|
|
|
"Device %s: %s by %s: `%s` is not found on the screen" % (
|
|
|
|
self.driver.number, self.name, self.by, self.locator)) from None
|
2018-08-10 10:09:19 +00:00
|
|
|
except Exception as exception:
|
2023-08-12 18:04:28 +00:00
|
|
|
# if 'Internal Server Error' in str(exception):
|
|
|
|
raise exception
|
2017-08-28 10:02:20 +00:00
|
|
|
|
2017-10-05 19:41:17 +00:00
|
|
|
def find_elements(self):
|
2021-01-25 16:35:40 +00:00
|
|
|
return self.driver.find_elements(self.by, self.locator)
|
2017-10-05 19:41:17 +00:00
|
|
|
|
2018-06-28 18:46:51 +00:00
|
|
|
def click(self):
|
2024-02-20 12:07:03 +00:00
|
|
|
element = self.find_element()
|
|
|
|
try:
|
|
|
|
element.click()
|
|
|
|
except AttributeError:
|
|
|
|
raise Exception("Element: %s\n Element type: %s" % (element, type(element)))
|
2021-10-22 17:23:27 +00:00
|
|
|
self.driver.info('Tap on found: %s' % self.name)
|
2021-01-25 16:35:40 +00:00
|
|
|
return self.navigate()
|
2018-06-28 18:46:51 +00:00
|
|
|
|
2023-08-10 11:36:46 +00:00
|
|
|
def wait_and_click(self, sec=30):
|
|
|
|
self.driver.info("Wait for element `%s` for max %ss and click when it is available" % (self.name, sec))
|
|
|
|
self.wait_for_visibility_of_element(sec)
|
|
|
|
self.click()
|
|
|
|
|
2021-12-29 16:10:22 +00:00
|
|
|
def click_until_presence_of_element(self, desired_element, attempts=4):
|
|
|
|
counter = 0
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Click until `%s` by `%s`: `%s` will be presented" % (
|
2021-12-29 16:10:22 +00:00
|
|
|
desired_element.name, desired_element.by, desired_element.locator))
|
2022-10-04 15:44:55 +00:00
|
|
|
while not desired_element.is_element_displayed(1) and counter <= attempts:
|
2021-12-29 16:10:22 +00:00
|
|
|
try:
|
2024-02-20 12:07:03 +00:00
|
|
|
el = self.find_element()
|
|
|
|
try:
|
|
|
|
el.click()
|
|
|
|
except AttributeError:
|
|
|
|
raise Exception("Element: %s\n Element type: %s" % (el, type(el)))
|
2021-12-29 16:10:22 +00:00
|
|
|
return self.navigate()
|
|
|
|
except (NoSuchElementException, TimeoutException):
|
|
|
|
counter += 1
|
|
|
|
else:
|
|
|
|
self.driver.info("%s element not found" % desired_element.name)
|
|
|
|
|
2021-01-27 16:13:47 +00:00
|
|
|
def double_click(self):
|
2021-10-22 17:23:27 +00:00
|
|
|
self.driver.info('Double tap on: %s' % self.name)
|
2021-01-27 16:13:47 +00:00
|
|
|
[self.find_element().click() for _ in range(2)]
|
|
|
|
|
2017-08-31 13:39:41 +00:00
|
|
|
def wait_for_element(self, seconds=10):
|
2018-01-26 11:07:09 +00:00
|
|
|
try:
|
2018-05-02 16:01:17 +00:00
|
|
|
return WebDriverWait(self.driver, seconds) \
|
2021-01-25 16:35:40 +00:00
|
|
|
.until(expected_conditions.presence_of_element_located((self.by, self.locator)))
|
2018-08-10 10:09:19 +00:00
|
|
|
except TimeoutException:
|
2018-08-15 12:51:52 +00:00
|
|
|
raise TimeoutException(
|
2022-03-31 15:34:34 +00:00
|
|
|
"Device `%s`: `%s` by` %s`: `%s` is not found on the screen after wait_for_element" % (
|
2021-11-18 15:16:48 +00:00
|
|
|
self.driver.number, self.name, self.by, self.locator)) from None
|
2018-08-29 14:04:12 +00:00
|
|
|
|
|
|
|
def wait_for_elements(self, seconds=10):
|
|
|
|
try:
|
|
|
|
return WebDriverWait(self.driver, seconds) \
|
2021-01-25 16:35:40 +00:00
|
|
|
.until(expected_conditions.presence_of_all_elements_located((self.by, self.locator)))
|
2018-08-29 14:04:12 +00:00
|
|
|
except TimeoutException:
|
|
|
|
raise TimeoutException(
|
2022-03-31 15:34:34 +00:00
|
|
|
"Device %s: %s by %s:`%s` is not found on the screen after wait_for_elements" % (
|
2021-11-18 15:16:48 +00:00
|
|
|
self.driver.number, self.name, self.by, self.locator)) from None
|
2017-08-28 10:02:20 +00:00
|
|
|
|
2018-07-19 09:57:45 +00:00
|
|
|
def wait_for_visibility_of_element(self, seconds=10, ignored_exceptions=None):
|
2018-03-15 20:01:08 +00:00
|
|
|
try:
|
2018-07-19 09:57:45 +00:00
|
|
|
return WebDriverWait(self.driver, seconds, ignored_exceptions=ignored_exceptions) \
|
2021-01-25 16:35:40 +00:00
|
|
|
.until(expected_conditions.visibility_of_element_located((self.by, self.locator)))
|
2018-08-10 10:09:19 +00:00
|
|
|
except TimeoutException:
|
2018-08-15 12:51:52 +00:00
|
|
|
raise TimeoutException(
|
2022-03-31 15:34:34 +00:00
|
|
|
"Device %s: %s by %s:`%s` is not found on the screen after wait_for_visibility_of_element" % (
|
2021-11-18 15:16:48 +00:00
|
|
|
self.driver.number, self.name, self.by, self.locator)) from None
|
2018-03-15 20:01:08 +00:00
|
|
|
|
2018-05-25 17:29:07 +00:00
|
|
|
def wait_for_invisibility_of_element(self, seconds=10):
|
|
|
|
try:
|
|
|
|
return WebDriverWait(self.driver, seconds) \
|
2021-01-25 16:35:40 +00:00
|
|
|
.until(expected_conditions.invisibility_of_element_located((self.by, self.locator)))
|
2018-08-10 10:09:19 +00:00
|
|
|
except TimeoutException:
|
2022-11-21 04:49:53 +00:00
|
|
|
raise TimeoutException(
|
|
|
|
"Device %s: %s by %s: `%s` is still visible on the screen after %s seconds after wait_for_invisibility_of_element" % (
|
|
|
|
self.driver.number, self.name, self.by, self.locator, seconds)) from None
|
2018-05-25 17:29:07 +00:00
|
|
|
|
2023-08-12 18:04:28 +00:00
|
|
|
def wait_for_rendering_ended_and_click(self, attempts=3):
|
|
|
|
for i in range(attempts):
|
|
|
|
try:
|
2023-12-07 14:30:39 +00:00
|
|
|
self.wait_for_visibility_of_element(20)
|
2023-08-12 18:04:28 +00:00
|
|
|
self.click()
|
|
|
|
self.driver.info("Attempt %s is successful clicking %s" % (i, self.locator))
|
|
|
|
return
|
|
|
|
except StaleElementReferenceException:
|
|
|
|
time.sleep(1)
|
|
|
|
raise StaleElementReferenceException(
|
|
|
|
msg="Device %s: continuous rendering, can't click an element by %s: %s" % (
|
|
|
|
self.driver.number, self.by, self.locator))
|
|
|
|
|
2022-11-21 04:49:53 +00:00
|
|
|
def wait_for_element_text(self, text, wait_time=30, message=None):
|
2023-08-12 18:04:28 +00:00
|
|
|
if not isinstance(text, str):
|
|
|
|
text = str(text)
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Wait for text element `%s` to be equal to `%s`" % (self.name, text))
|
2023-08-12 18:04:28 +00:00
|
|
|
element_text = str()
|
|
|
|
counter = 0
|
|
|
|
while counter < wait_time:
|
|
|
|
try:
|
|
|
|
element_text = self.find_element().text.strip()
|
|
|
|
except StaleElementReferenceException:
|
|
|
|
time.sleep(1)
|
|
|
|
element_text = self.find_element().text.strip()
|
|
|
|
if element_text == text:
|
2021-10-22 17:23:27 +00:00
|
|
|
self.driver.info('Element %s text is equal to %s' % (self.name, text))
|
2021-02-04 12:51:01 +00:00
|
|
|
return
|
2023-08-12 18:04:28 +00:00
|
|
|
counter += 10
|
|
|
|
time.sleep(10)
|
|
|
|
self.driver.fail(message if message else "`%s` is not equal to expected `%s` in %s sec" % (
|
|
|
|
element_text, text, wait_time))
|
2021-02-04 12:51:01 +00:00
|
|
|
|
2024-04-02 14:59:56 +00:00
|
|
|
def scroll_to_element(self, depth: int = 9, direction='down', down_start_y=0.4, down_end_y=0.05):
|
2021-10-22 17:23:27 +00:00
|
|
|
self.driver.info('Scrolling %s to %s' % (direction, self.name))
|
2018-11-21 10:44:30 +00:00
|
|
|
for _ in range(depth):
|
2017-08-31 13:39:41 +00:00
|
|
|
try:
|
2017-10-11 20:10:57 +00:00
|
|
|
return self.find_element()
|
2017-08-31 13:39:41 +00:00
|
|
|
except NoSuchElementException:
|
2020-07-09 15:24:19 +00:00
|
|
|
size = self.driver.get_window_size()
|
2018-11-21 10:44:30 +00:00
|
|
|
if direction == 'down':
|
2024-04-02 14:59:56 +00:00
|
|
|
self.driver.swipe(500, size["height"] * down_start_y, 500, size["height"] * down_end_y)
|
2018-11-21 10:44:30 +00:00
|
|
|
else:
|
2021-11-18 15:16:48 +00:00
|
|
|
self.driver.swipe(500, size["height"] * 0.25, 500, size["height"] * 0.8)
|
2019-07-23 23:10:55 +00:00
|
|
|
else:
|
|
|
|
raise NoSuchElementException(
|
2021-11-18 15:16:48 +00:00
|
|
|
"Device %s: %s by %s: `%s` is not found on the screen" % (
|
|
|
|
self.driver.number, self.name, self.by, self.locator)) from None
|
2017-08-31 13:39:41 +00:00
|
|
|
|
2021-01-29 14:08:48 +00:00
|
|
|
def scroll_and_click(self, direction='down'):
|
|
|
|
self.scroll_to_element(direction=direction)
|
2020-10-06 14:48:08 +00:00
|
|
|
self.click()
|
|
|
|
|
2022-10-04 15:44:55 +00:00
|
|
|
# def is_element_present(self, sec=5):
|
|
|
|
# try:
|
|
|
|
# return self.wait_for_element(sec)
|
|
|
|
# except TimeoutException:
|
|
|
|
# return False
|
2018-03-15 20:01:08 +00:00
|
|
|
|
2018-07-19 09:57:45 +00:00
|
|
|
def is_element_displayed(self, sec=5, ignored_exceptions=None):
|
2018-03-15 20:01:08 +00:00
|
|
|
try:
|
2018-07-19 09:57:45 +00:00
|
|
|
return self.wait_for_visibility_of_element(sec, ignored_exceptions=ignored_exceptions)
|
2017-09-13 14:34:42 +00:00
|
|
|
except TimeoutException:
|
|
|
|
return False
|
|
|
|
|
2022-02-22 14:26:46 +00:00
|
|
|
def click_if_shown(self, sec=5):
|
|
|
|
if self.is_element_displayed(sec=sec):
|
|
|
|
self.click()
|
|
|
|
|
2021-10-29 09:11:39 +00:00
|
|
|
def is_element_disappeared(self, sec=20):
|
|
|
|
try:
|
|
|
|
return self.wait_for_invisibility_of_element(sec)
|
|
|
|
except TimeoutException:
|
|
|
|
return False
|
|
|
|
|
2017-09-21 17:01:04 +00:00
|
|
|
@property
|
|
|
|
def text(self):
|
|
|
|
return self.find_element().text
|
|
|
|
|
2018-05-02 16:01:17 +00:00
|
|
|
@property
|
|
|
|
def template(self):
|
|
|
|
try:
|
|
|
|
return self.__template
|
|
|
|
except FileNotFoundError:
|
|
|
|
raise FileNotFoundError('Please add %s image as template' % self.name)
|
|
|
|
|
|
|
|
@template.setter
|
|
|
|
def template(self, value):
|
|
|
|
self.__template = Image.open(os.sep.join(__file__.split(os.sep)[:-1]) + '/elements_templates/%s' % value)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def image(self):
|
|
|
|
return Image.open(BytesIO(base64.b64decode(self.find_element().screenshot_as_base64)))
|
|
|
|
|
2019-10-03 16:08:06 +00:00
|
|
|
def attribute_value(self, value):
|
2019-10-08 08:28:52 +00:00
|
|
|
attribute_value = self.find_element().get_attribute(value)
|
2019-10-09 13:00:49 +00:00
|
|
|
if attribute_value.lower() == 'true':
|
|
|
|
attribute_state = True
|
|
|
|
elif attribute_value.lower() == 'false':
|
|
|
|
attribute_state = False
|
2019-10-08 08:28:52 +00:00
|
|
|
else:
|
2019-10-09 13:00:49 +00:00
|
|
|
attribute_state = attribute_value
|
|
|
|
return attribute_state
|
2019-10-03 16:08:06 +00:00
|
|
|
|
2020-03-17 17:27:10 +00:00
|
|
|
# Method-helper for renew screenshots in case if changed
|
2020-11-06 11:23:12 +00:00
|
|
|
def save_new_screenshot_of_element(self, name: str):
|
|
|
|
full_path_to_file = os.sep.join(__file__.split(os.sep)[:-1]) + '/elements_templates/%s' % name
|
2020-03-17 17:27:10 +00:00
|
|
|
screen = Image.open(BytesIO(base64.b64decode(self.find_element().screenshot_as_base64)))
|
|
|
|
screen.save(full_path_to_file)
|
|
|
|
|
2021-11-18 15:16:48 +00:00
|
|
|
def is_element_image_equals_template(self, file_name: str = ''):
|
2018-05-16 19:59:36 +00:00
|
|
|
if file_name:
|
|
|
|
self.template = file_name
|
2018-05-02 16:01:17 +00:00
|
|
|
return not ImageChops.difference(self.image, self.template).getbbox()
|
|
|
|
|
2021-05-28 15:39:33 +00:00
|
|
|
def is_element_differs_from_template(self, file_name: str = '', diff: int = 0):
|
|
|
|
if file_name:
|
|
|
|
self.template = file_name
|
|
|
|
result = False
|
|
|
|
difference = ImageChops.difference(self.image, self.template)
|
|
|
|
stat = ImageStat.Stat(difference)
|
|
|
|
diff_ratio = sum(stat.mean) / (len(stat.mean) * 255)
|
2021-11-26 14:15:26 +00:00
|
|
|
self.driver.info('Image differs from template to %s percents' % str(diff_ratio * 100))
|
2021-11-18 15:16:48 +00:00
|
|
|
if diff_ratio * 100 > diff:
|
2021-05-28 15:39:33 +00:00
|
|
|
result = True
|
|
|
|
return result
|
|
|
|
|
2021-02-05 11:29:35 +00:00
|
|
|
def is_element_image_similar_to_template(self, template_path: str = ''):
|
|
|
|
image_template = os.sep.join(__file__.split(os.sep)[:-1]) + '/elements_templates/%s' % template_path
|
|
|
|
template = imagehash.average_hash(Image.open(image_template))
|
|
|
|
element_image = imagehash.average_hash(self.image)
|
2021-11-18 15:16:48 +00:00
|
|
|
return not bool(template - element_image)
|
2021-02-05 11:29:35 +00:00
|
|
|
|
2023-03-13 13:29:18 +00:00
|
|
|
def get_element_coordinates(self):
|
2018-05-16 19:59:36 +00:00
|
|
|
element = self.find_element()
|
2023-03-13 13:29:18 +00:00
|
|
|
location = element.location
|
|
|
|
size = element.size
|
|
|
|
return location, size
|
|
|
|
|
|
|
|
def swipe_left_on_element(self):
|
|
|
|
self.driver.info("Swiping left on element %s" % self.name)
|
|
|
|
location, size = self.get_element_coordinates()
|
2018-05-16 19:59:36 +00:00
|
|
|
x, y = location['x'], location['y']
|
|
|
|
width, height = size['width'], size['height']
|
2018-07-03 18:50:18 +00:00
|
|
|
self.driver.swipe(start_x=x + width * 0.75, start_y=y + height / 2, end_x=x, end_y=y + height / 2)
|
2018-05-16 19:59:36 +00:00
|
|
|
|
2024-04-02 14:59:56 +00:00
|
|
|
def swipe_right_on_element(self, width_percentage=0.9, start_x=0):
|
2023-03-13 13:29:18 +00:00
|
|
|
self.driver.info("Swiping right on element %s" % self.name)
|
|
|
|
location, size = self.get_element_coordinates()
|
|
|
|
x, y = location['x'], location['y']
|
|
|
|
width, height = size['width'], size['height']
|
2024-04-02 14:59:56 +00:00
|
|
|
self.driver.swipe(start_x=x + start_x, start_y=y + height / 2, end_x=x + width * width_percentage,
|
|
|
|
end_y=y + height / 2)
|
2023-03-13 13:29:18 +00:00
|
|
|
|
2020-03-26 11:36:41 +00:00
|
|
|
def swipe_to_web_element(self, depth=700):
|
2019-05-17 14:40:35 +00:00
|
|
|
element = self.find_element()
|
|
|
|
location = element.location
|
|
|
|
x, y = location['x'], location['y']
|
2020-02-24 11:30:45 +00:00
|
|
|
self.driver.swipe(start_x=x, start_y=y, end_x=x, end_y=depth)
|
2019-05-17 14:40:35 +00:00
|
|
|
|
2024-09-13 16:38:35 +00:00
|
|
|
def long_press_element(self, element_to_release_on=None):
|
2018-05-16 19:59:36 +00:00
|
|
|
element = self.find_element()
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Long press on `%s`" % self.name)
|
2024-09-13 16:38:35 +00:00
|
|
|
action = ActionChains(self.driver)
|
|
|
|
action.click_and_hold(element).perform()
|
|
|
|
time.sleep(2)
|
|
|
|
if element_to_release_on:
|
|
|
|
action.release(element_to_release_on.find_element()).perform()
|
|
|
|
else:
|
|
|
|
action.release(element).perform()
|
2018-05-16 19:59:36 +00:00
|
|
|
|
2023-02-24 19:05:30 +00:00
|
|
|
def long_press_until_element_is_shown(self, expected_element):
|
|
|
|
element = self.find_element()
|
|
|
|
self.driver.info("Long press on `%s` until expected element is shown" % self.name)
|
2024-09-13 16:38:35 +00:00
|
|
|
action = ActionChains(self.driver)
|
2023-02-24 19:05:30 +00:00
|
|
|
for _ in range(3):
|
2024-09-13 16:38:35 +00:00
|
|
|
action.click_and_hold(element).perform()
|
|
|
|
time.sleep(2)
|
|
|
|
action.release(element).perform()
|
2023-02-24 19:05:30 +00:00
|
|
|
if expected_element.is_element_displayed():
|
|
|
|
return
|
|
|
|
|
2023-03-09 05:35:08 +00:00
|
|
|
def long_press_element_by_coordinate(self, rel_x=0.8, rel_y=0.8):
|
|
|
|
element = self.find_element()
|
|
|
|
location = element.location
|
|
|
|
size = element.size
|
|
|
|
x = int(location['x'] + size['width'] * rel_x)
|
|
|
|
y = int(location['y'] + size['height'] * rel_y)
|
2024-09-13 16:38:35 +00:00
|
|
|
action = ActionChains(self.driver)
|
|
|
|
action.move_to_element_with_offset(to_element=element, xoffset=x, yoffset=y).click_and_hold().perform()
|
|
|
|
action.release(element).perform()
|
2023-03-09 05:35:08 +00:00
|
|
|
|
2018-06-21 16:40:27 +00:00
|
|
|
def measure_time_before_element_appears(self, max_wait_time=30):
|
|
|
|
def wrapper():
|
|
|
|
return self.wait_for_visibility_of_element(max_wait_time)
|
2018-06-28 18:46:51 +00:00
|
|
|
|
2018-06-21 16:40:27 +00:00
|
|
|
return timeit(wrapper, number=1)
|
|
|
|
|
|
|
|
def measure_time_while_element_is_shown(self, max_wait_time=30):
|
|
|
|
def wrapper():
|
|
|
|
return self.wait_for_invisibility_of_element(max_wait_time)
|
2018-06-28 18:46:51 +00:00
|
|
|
|
2018-06-21 16:40:27 +00:00
|
|
|
return timeit(wrapper, number=1)
|
|
|
|
|
2021-07-05 19:28:55 +00:00
|
|
|
def click_inside_element_by_coordinate(self, rel_x=0.8, rel_y=0.8, times_to_click=1):
|
2023-03-13 13:29:18 +00:00
|
|
|
location, size = self.get_element_coordinates()
|
2021-07-05 19:28:55 +00:00
|
|
|
x = int(location['x'] + size['width'] * rel_x)
|
|
|
|
y = int(location['y'] + size['height'] * rel_y)
|
|
|
|
[self.driver.tap([(x, y)], 150) for _ in range(times_to_click)]
|
|
|
|
|
2021-01-25 16:35:40 +00:00
|
|
|
@staticmethod
|
|
|
|
def get_translation_by_key(key):
|
|
|
|
return transl[key]
|
2017-08-28 10:02:20 +00:00
|
|
|
|
2021-10-05 14:30:40 +00:00
|
|
|
@staticmethod
|
|
|
|
def exclude_emoji(value):
|
|
|
|
return 'emoji' if value in emoji.UNICODE_EMOJI else value
|
|
|
|
|
2024-06-07 18:21:29 +00:00
|
|
|
def get_child_element_by_text(self, text: str):
|
|
|
|
return BaseElement(self.driver, prefix=self.locator, xpath="//*[@text='%s']" % text)
|
|
|
|
|
2017-08-28 10:02:20 +00:00
|
|
|
|
2021-01-25 16:35:40 +00:00
|
|
|
class EditBox(BaseElement):
|
|
|
|
|
|
|
|
def __init__(self, driver, **kwargs):
|
|
|
|
super(EditBox, self).__init__(driver, **kwargs)
|
2017-08-28 10:02:20 +00:00
|
|
|
|
|
|
|
def send_keys(self, value):
|
|
|
|
self.find_element().send_keys(value)
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Type `%s` to `%s`" % (self.exclude_emoji(value), self.name))
|
2017-08-28 10:02:20 +00:00
|
|
|
|
2017-10-30 11:11:58 +00:00
|
|
|
def clear(self):
|
|
|
|
self.find_element().clear()
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Clear text in `%s`" % self.name)
|
2017-10-30 11:11:58 +00:00
|
|
|
|
2018-05-16 19:59:36 +00:00
|
|
|
def delete_last_symbols(self, number_of_symbols_to_delete: int):
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Delete last `%s` symbols from `%s`" % (number_of_symbols_to_delete, self.name))
|
2018-05-16 19:59:36 +00:00
|
|
|
self.click()
|
|
|
|
for _ in range(number_of_symbols_to_delete):
|
|
|
|
time.sleep(1)
|
|
|
|
self.driver.press_keycode(67)
|
|
|
|
|
|
|
|
def paste_text_from_clipboard(self):
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Paste text from clipboard into `%s`" % self.name)
|
2018-05-16 19:59:36 +00:00
|
|
|
self.long_press_element()
|
|
|
|
time.sleep(2)
|
2024-09-13 16:38:35 +00:00
|
|
|
action = ActionChains(self.driver)
|
|
|
|
element = self.find_element()
|
|
|
|
location = element.location
|
2018-05-16 19:59:36 +00:00
|
|
|
x, y = location['x'], location['y']
|
2024-09-13 16:38:35 +00:00
|
|
|
action.move_by_offset(xoffset=x + 25, yoffset=y - 50).click().perform()
|
|
|
|
action.release(element).perform()
|
2018-05-16 19:59:36 +00:00
|
|
|
|
|
|
|
def cut_text(self):
|
2022-03-23 16:27:58 +00:00
|
|
|
self.driver.info("Cut text in `%s`" % self.name)
|
2024-09-13 16:38:35 +00:00
|
|
|
element = self.find_element()
|
|
|
|
location = element.location
|
2018-05-16 19:59:36 +00:00
|
|
|
x, y = location['x'], location['y']
|
2024-09-13 16:38:35 +00:00
|
|
|
action = ActionChains(self.driver)
|
|
|
|
action.move_by_offset(xoffset=x, yoffset=y).click_and_hold().perform()
|
|
|
|
action.release(element).perform()
|
2018-05-16 19:59:36 +00:00
|
|
|
time.sleep(2)
|
2024-09-13 16:38:35 +00:00
|
|
|
action.move_by_offset(xoffset=x + 50, yoffset=y - 50).click().perform()
|
|
|
|
action.release(element).perform()
|
2018-05-16 19:59:36 +00:00
|
|
|
|
2017-08-28 10:02:20 +00:00
|
|
|
|
2021-01-25 16:35:40 +00:00
|
|
|
class Text(BaseElement):
|
|
|
|
def __init__(self, driver, **kwargs):
|
|
|
|
super(Text, self).__init__(driver, **kwargs)
|
2017-08-28 10:02:20 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def text(self):
|
2017-09-21 17:01:04 +00:00
|
|
|
text = self.find_element().text
|
2022-03-23 16:27:58 +00:00
|
|
|
self.driver.info("`%s` is `%s`" % (self.name, text))
|
2017-09-21 17:01:04 +00:00
|
|
|
return text
|
2017-08-28 10:02:20 +00:00
|
|
|
|
|
|
|
|
2021-01-25 16:35:40 +00:00
|
|
|
class Button(BaseElement):
|
2017-08-28 10:02:20 +00:00
|
|
|
|
2021-01-25 16:35:40 +00:00
|
|
|
def __init__(self, driver, **kwargs):
|
|
|
|
super(Button, self).__init__(driver, **kwargs)
|
2018-02-09 15:16:07 +00:00
|
|
|
|
2023-06-22 14:59:56 +00:00
|
|
|
def click_until_absense_of_element(self, desired_element, attempts=3, timeout=1):
|
2019-11-04 13:41:18 +00:00
|
|
|
counter = 0
|
2022-03-22 16:37:47 +00:00
|
|
|
self.driver.info("Click until `%s` by `%s`: `%s` is NOT presented" % (
|
2021-11-18 15:16:48 +00:00
|
|
|
desired_element.name, desired_element.by, desired_element.locator))
|
2023-06-22 14:59:56 +00:00
|
|
|
while desired_element.is_element_displayed(timeout) and counter <= attempts:
|
2019-11-04 13:41:18 +00:00
|
|
|
try:
|
|
|
|
self.find_element().click()
|
|
|
|
counter += 1
|
2022-12-29 13:49:00 +00:00
|
|
|
except (NoSuchElementException, TimeoutException, StaleElementReferenceException):
|
2019-11-04 13:41:18 +00:00
|
|
|
return self.navigate()
|
2021-01-25 16:35:40 +00:00
|
|
|
|
2021-11-18 15:16:48 +00:00
|
|
|
|
2021-01-25 16:35:40 +00:00
|
|
|
class SilentButton(Button):
|
|
|
|
def find_element(self):
|
|
|
|
for _ in range(3):
|
|
|
|
try:
|
|
|
|
return self.driver.find_element(self.by, self.locator)
|
|
|
|
except NoSuchElementException:
|
|
|
|
raise NoSuchElementException(
|
2022-03-22 16:37:47 +00:00
|
|
|
"Device %s: `%s` by `%s`:`%s` not found on the screen" % (
|
2021-11-18 15:16:48 +00:00
|
|
|
self.driver.number, self.name, self.by, self.locator)) from None
|
2021-01-25 16:35:40 +00:00
|
|
|
except Exception as exception:
|
|
|
|
if 'Internal Server Error' in str(exception):
|
|
|
|
continue
|
|
|
|
|
|
|
|
def click(self):
|
|
|
|
self.find_element().click()
|
|
|
|
return self.navigate()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def text(self):
|
|
|
|
text = self.find_element().text
|
2021-11-18 15:16:48 +00:00
|
|
|
return text
|
2021-12-16 16:25:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
class CheckBox(Button):
|
2021-12-21 13:57:13 +00:00
|
|
|
def __init__(self, driver, **kwargs):
|
|
|
|
super(Button, self).__init__(driver, **kwargs)
|
|
|
|
|
2022-01-14 16:51:54 +00:00
|
|
|
def __define_desired_element(self, elem_accessibility):
|
2022-01-17 19:23:38 +00:00
|
|
|
desired_element_accessibility_id = elem_accessibility
|
|
|
|
if self.accessibility_id is not None and ':' in self.accessibility_id:
|
|
|
|
desired_element_accessibility_id = ':%s' % elem_accessibility
|
|
|
|
return desired_element_accessibility_id
|
2022-01-14 16:51:54 +00:00
|
|
|
|
2021-12-21 13:57:13 +00:00
|
|
|
def enable(self):
|
2022-01-14 16:51:54 +00:00
|
|
|
self.click_until_presence_of_element(Button(self.driver,
|
|
|
|
accessibility_id=self.__define_desired_element("checkbox-on")))
|
2021-12-16 16:25:42 +00:00
|
|
|
return self.navigate()
|
2021-12-21 13:57:13 +00:00
|
|
|
|
|
|
|
def disable(self):
|
2022-01-14 16:51:54 +00:00
|
|
|
self.click_until_presence_of_element(Button(self.driver,
|
|
|
|
accessibility_id=self.__define_desired_element("checkbox-off")))
|
2021-12-21 13:57:13 +00:00
|
|
|
return self.navigate()
|