256 lines
8.3 KiB
Python
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://developer.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()
|