web-automation-pytest-demo/Lib/Mobile.py

256 lines
8.3 KiB
Python

from appium.webdriver import Remote as browser_webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.common.touch_action import TouchAction
from appium.webdriver.webelement import WebElement
import random
import json
import time
class AndroidKeyCode:
"""
Android Keycodes.
"""
MENU, HOME, BACK = 82, 3, 4
SWITCH = 187
VOLUME_UP, VOLUME_DOWN = 24, 25
POWER = 26
SCREENSHOT = 120
class Browser(browser_webdriver):
"""
Browser web driver.
"""
def __init__(
self,
remote: str = 'http://127.0.0.1:4723/wd/hub',
platform: str = 'Android',
driver_name: str = 'UiAutomator2',
device_id: str = '',
app: str = '',
package: str = '',
activity: str = '',
reset: bool = False,
caps: dict = None
):
caps = caps or {}
capabilities = {
'platformName': platform,
"automationName": driver_name,
'udid': device_id,
'noReset': not reset,
'app': app,
'appPackage': package,
'appActivity': activity
}
for k, v in dict(capabilities).items():
v == '' and capabilities.pop(k)
for k, v in dict(caps.items()):
capabilities[k] = v
super().__init__(remote[:-1] if remote[-1] == '/' else remote, capabilities)
self.platform = None
match self.caps['platformName']:
case 'Android':
self.platform = 0
case 'iOS':
self.platform = 1
case 'Windows':
self.platform = 2
case _:
pass
@staticmethod
def input(element: WebElement, text: str):
"""
Input text.
"""
element.send_keys(text)
@staticmethod
def clear(element: WebElement):
"""
Clear text.
"""
element.clear()
def click(self, element: WebElement, long=0, x_offset=0, y_offset=0, random_offset=False):
"""
Click or long-click the element.
"""
w, h = element.size['width'], element.size['height']
if random_offset:
x_offset = random.randint(0, int(w * 0.2)) * random.choice([-1, 1])
y_offset = random.randint(0, int(h * 0.2)) * random.choice([-1, 1])
if x_offset or y_offset:
x_add_center = int(round(w / 2, 0)) + x_offset
y_add_center = int(round(h / 2, 0)) + y_offset
else:
x_add_center = 0
y_add_center = 0
if long:
if long < 750:
raise Exception('The minimum request for long pressing time is 750ms')
TouchAction(self).long_press(element, x=x_add_center or None, y=y_add_center or None, duration=long - 750 + 1).release().perform()
else:
TouchAction(self).tap(element, x=x_add_center or None, y=y_add_center or None).perform()
def slide(self, direction: str):
"""
Sliding screen.
"""
screen_size = self.get_window_size()
w, h = screen_size['width'], screen_size['height']
match direction.lower():
case 'u':
self.swipe(w * 0.5, h * 0.80, w * 0.5, h * 0.22, duration=155)
case 'd':
self.swipe(w * 0.5, h * 0.22, w * 0.5, h * 0.80, duration=155)
case 'l':
self.swipe(w * 0.8, h * 0.52, w * 0.2, h * 0.52, duration=88)
case 'r':
self.swipe(w * 0.2, h * 0.52, w * 0.8, h * 0.52, duration=88)
case _:
raise Exception('Unknown sliding instruction')
def slide_element(self, element: WebElement, direction: str):
"""
Sliding element.
"""
rect = element.rect
l, t, r, b = rect['x'], rect['y'], rect['x'] + rect['width'], rect['y'] + rect['height']
w, h = rect['width'], rect['height']
match direction.lower():
case 'u':
self.swipe(l + (w / 2), b - (h * 0.03), l + (w / 2), t, duration=int(1.2 * h))
case 'd':
self.swipe(l + (w / 2), t + (h * 0.03), l + (w / 2), b, duration=int(1.2 * h))
case 'l':
self.swipe(r - (w * 0.03), t + (h / 2), l, t + (h / 2), duration=int(1.2 * h))
case 'r':
self.swipe(l + (w * 0.03), t + (h / 2), r, t + (h / 2), duration=int(1.2 * h))
case _:
raise Exception('Unknown sliding instruction')
def menu(self):
self.platform == 0 and self.press_keycode(AndroidKeyCode.MENU)
def home(self):
self.platform == 0 and self.press_keycode(AndroidKeyCode.HOME)
def back(self):
super().back()
def volu(self):
self.platform == 0 and self.press_keycode(AndroidKeyCode.VOLUME_UP)
def vold(self):
self.platform == 0 and self.press_keycode(AndroidKeyCode.VOLUME_DOWN)
def shot(self):
self.platform == 0 and self.press_keycode(AndroidKeyCode.SCREENSHOT)
def task(self):
self.platform == 0 and self.press_keycode(AndroidKeyCode.SWITCH)
def exec_shell(self, command: str):
"""
Execute the terminal shell.
"""
return self.execute_script('mobile: shell', {'command': command})
def find(self, *args, **kwargs):
"""
Use UiAutomator to find an element.
"""
convert = {
'checkable': 'checkable',
'checked': 'checked',
'class': 'className',
'class-matches': 'classNameMatches',
'clickable': 'clickable',
'content-desc': 'description',
'content-desc-matches': 'descriptionMatches',
'desc': 'description',
'desc-matches': 'descriptionMatches',
'desc-starts-with': 'descriptionStartsWith',
'enabled': 'enabled',
'focusable': 'focusable',
'focused': 'focused',
'id': 'resourceId',
'id-matches': 'resourceIdMatches',
'index': 'index',
'instance': 'instance',
'long-clickable': 'longClickable',
'package': 'packageName',
'package-matches': 'packageNameMatches',
'resource-id': 'resourceId',
'resource-id-matches': 'resourceIdMatches',
'scrollable': 'scrollable',
'selected': 'selected',
'text': 'text',
'text-matches': 'textMatches',
'text-starts-with': 'textStartsWith'
}
# For more information, please go to https://edeveloper.android.google.cn/reference/androidx/test/uiautomator/uiselector
convert_attr_list = convert.keys()
b = self
t = len(args)
for i in range(t):
matches = args[i]
convert_data = []
for k, v in matches.items():
if k not in convert_attr_list:
raise Exception('Unknown matching attributes "%s"' % k)
convert_data.append('%s(%s)' % (convert[k], json.dumps(v, ensure_ascii=False)))
find_args = (AppiumBy.ANDROID_UIAUTOMATOR, '%s' % '.'.join(convert_data))
if (t - 1) == i:
return b.find_elements(*find_args) if 'mult' in kwargs.keys() and kwargs['mult'] else b.find_element(*find_args)
b = b.find_element(*find_args)
def find_mult(self, *args) -> list:
"""
Use UiAutomator to find elements.
"""
return self.find(*args, mult=True)
@staticmethod
def wait(secs: int | float = 1):
"""
Will sleep waiting.
"""
number_int = int(secs)
number_float = secs - number_int
for i in range(number_int):
time.sleep(1)
else:
time.sleep(number_float)
@staticmethod
def attr(element: WebElement, attribute: str):
"""
Get the attribute of the element.
"""
return element.get_attribute(attribute)
def screenshot(self) -> bytes:
"""
Screenshot as bytes.
"""
return self.get_screenshot_as_png()
class WebDriver(Browser):
"""
Get a browser driver object.
"""
def __init__(self):
super().__init__(remote='http://127.0.0.1:4723/wd/hub')
if __name__ == '__main__':
# e.g. Test it can work normally.
driver = WebDriver()
driver.home()
driver.quit()