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()