class AndroidUiautomationPoco(Poco): """ Poco Android implementation for testing **Android native apps**. Args: device (:py:obj:`Device`): :py:obj:`airtest.core.device.Device` instance provided by ``airtest``. leave the parameter default and the default device will be chosen. more details refer to ``airtest doc`` using_proxy (:py:obj:`bool`): whether use adb forward to connect the Android device or not force_restart (:py:obj:`bool`): whether always restart the poco-service-demo running on Android device or not options: see :py:class:`poco.pocofw.Poco` Examples: The simplest way to initialize AndroidUiautomationPoco instance and no matter your device network status:: from poco.drivers.android.uiautomation import AndroidUiautomationPoco poco = AndroidUiautomationPoco() poco('android:id/title').click() ... """ def __init__(self, device=None, using_proxy=True, force_restart=False, use_airtest_input=False, **options): # 加这个参数为了不在最新的pocounit方案中每步都截图 self.screenshot_each_action = True if options.get('screenshot_each_action') is False: self.screenshot_each_action = False self.device = device or current_device() if not self.device: self.device = connect_device("Android:///") self.adb_client = self.device.adb if using_proxy: self.device_ip = self.adb_client.host or "127.0.0.1" else: self.device_ip = self.device.get_ip_address() # save current top activity (@nullable) current_top_activity_package = self.device.get_top_activity_name() if current_top_activity_package is not None: current_top_activity_package = current_top_activity_package.split( '/')[0] # install ime self.ime = YosemiteIme(self.adb_client) self.ime.start() # install self._instrument_proc = None self._install_service() # forward if using_proxy: p0, _ = self.adb_client.setup_forward("tcp:10080") p1, _ = self.adb_client.setup_forward("tcp:10081") else: p0 = 10080 p1 = 10081 # start if self._is_running('com.github.uiautomator'): warnings.warn( '{} should not run together with "uiautomator". "uiautomator" will be killed.' .format(self.__class__.__name__)) self.adb_client.shell( ['am', 'force-stop', 'com.github.uiautomator']) ready = self._start_instrument(p0, force_restart=force_restart) if not ready: # 启动失败则需要卸载再重启,instrument的奇怪之处 uninstall(self.adb_client, PocoServicePackage) self._install_service() ready = self._start_instrument(p0) if current_top_activity_package is not None: current_top_activity2 = self.device.get_top_activity_name() if current_top_activity2 is None or current_top_activity_package not in current_top_activity2: self.device.start_app(current_top_activity_package, activity=True) if not ready: raise RuntimeError("unable to launch AndroidUiautomationPoco") endpoint = "http://{}:{}".format(self.device_ip, p1) agent = AndroidPocoAgent(endpoint, self.ime, use_airtest_input) super(AndroidUiautomationPoco, self).__init__(agent, **options) def _install_service(self): updated = install( self.adb_client, os.path.join(this_dir, 'lib', 'pocoservice-debug.apk')) install( self.adb_client, os.path.join(this_dir, 'lib', 'pocoservice-debug-androidTest.apk'), updated) return updated def _is_running(self, package_name): processes = self.adb_client.shell(['ps']).splitlines() for ps in processes: ps = ps.strip() if ps.endswith(package_name): return True return False # def _keep_running_instrumentation(self): # def loop(): # while True: # proc = self.adb_client.shell([ # 'am', 'instrument', '-w', '-e', 'class', # '{}.InstrumentedTestAsLauncher#launch'.format(PocoServicePackage), # '{}.test/android.support.test.runner.AndroidJUnitRunner'.format(PocoServicePackage)], # not_wait=True) # stdout, stderr = proc.communicate() # print(stdout) # print(stderr) # time.sleep(1) # t = threading.Thread(target=loop) # t.daemon = True # t.start() def _start_instrument(self, port_to_ping, force_restart=False): if not force_restart: try: state = requests.get( 'http://{}:{}/uiautomation/connectionState'.format( self.device_ip, port_to_ping), timeout=10) state = state.json() if state.get('connected'): # skip starting instrumentation if UiAutomation Service already connected. return True except: pass if self._instrument_proc is not None: if self._instrument_proc.poll() is None: self._instrument_proc.kill() self._instrument_proc = None ready = False self.adb_client.shell(['am', 'force-stop', PocoServicePackage]) # 启动instrument之前,先把主类activity启动起来,不然instrumentation可能失败 self.adb_client.shell( 'am start -n {}/.TestActivity'.format(PocoServicePackage)) instrumentation_cmd = [ 'am', 'instrument', '-w', '-e', 'debug', 'false', '-e', 'class', '{}.InstrumentedTestAsLauncher'.format(PocoServicePackage), '{}.test/android.support.test.runner.AndroidJUnitRunner'.format( PocoServicePackage) ] self._instrument_proc = self.adb_client.start_shell( instrumentation_cmd) atexit.register(self._instrument_proc.kill) time.sleep(2) for i in range(10): try: requests.get('http://{}:{}'.format(self.device_ip, port_to_ping), timeout=10) ready = True break except requests.exceptions.Timeout: break except requests.exceptions.ConnectionError: if self._instrument_proc.poll() is not None: warnings.warn("instrument server process is not alive") output = self._instrument_proc.stdout.read() print(output) time.sleep(1) print("still waiting for uiautomation ready.") continue return ready def on_pre_action(self, action, ui, args): if self.screenshot_each_action: # airteset log用 from airtest.core.api import snapshot msg = repr(ui) if not isinstance(msg, six.text_type): msg = msg.decode('utf-8') snapshot(msg=msg)
class AndroidUiautomationPoco(Poco): def __init__(self, device=None, using_proxy=True, **options): if not device: try: # new version from airtest.core.api import connect_device, device as current_device if not current_device(): connect_device("Android:///") except ImportError: # old version from airtest.cli.runner import device as current_device from airtest.core.main import set_serialno if not current_device(): set_serialno() self.android = current_device() else: self.android = device self.adb_client = self.android.adb if using_proxy: self.device_ip = self.adb_client.host or "127.0.0.1" else: if new_airtest_api: self.device_ip = self.adb_client.get_ip_address() else: self.device_ip = get_ip_address(self.adb_client) # save current top activity (@nullable) current_top_activity_package = self.android.get_top_activity_name() if current_top_activity_package is not None: current_top_activity_package = current_top_activity_package.split( '/')[0] # install ime self.ime = YosemiteIme(self.adb_client) self.ime.start() # install self._instrument_proc = None self._install_service() # forward if using_proxy: p0, _ = self.adb_client.setup_forward("tcp:10080") p1, _ = self.adb_client.setup_forward("tcp:10081") else: p0 = 10080 p1 = 10081 # start if self._is_running('com.github.uiautomator'): warnings.warn( '{} should not run together with "uiautomator". "uiautomator" will be killed.' .format(self.__class__.__name__)) self.adb_client.shell( ['am', 'force-stop', 'com.github.uiautomator']) ready = self._start_instrument(p0) if not ready: # 启动失败则需要卸载再重启,instrument的奇怪之处 uninstall(self.adb_client, PocoServicePackage) self._install_service() ready = self._start_instrument(p0) if current_top_activity_package is not None: current_top_activity2 = self.android.get_top_activity_name() if current_top_activity2 is None or current_top_activity_package not in current_top_activity2: self.android.start_app(current_top_activity_package, activity=True) if not ready: raise RuntimeError("unable to launch AndroidUiautomationPoco") endpoint = "http://{}:{}".format(self.device_ip, p1) agent = AndroidPocoAgent(endpoint, self.ime) super(AndroidUiautomationPoco, self).__init__(agent, **options) def _install_service(self): updated = install( self.adb_client, os.path.join(this_dir, 'lib', 'pocoservice-debug.apk')) install( self.adb_client, os.path.join(this_dir, 'lib', 'pocoservice-debug-androidTest.apk'), updated) return updated def _is_running(self, package_name): processes = self.adb_client.shell(['ps']).splitlines() for ps in processes: ps = ps.strip() if ps.endswith(package_name): return True return False # def _keep_running_instrumentation(self): # def loop(): # while True: # proc = self.adb_client.shell([ # 'am', 'instrument', '-w', '-e', 'class', # '{}.InstrumentedTestAsLauncher#launch'.format(PocoServicePackage), # '{}.test/android.support.test.runner.AndroidJUnitRunner'.format(PocoServicePackage)], # not_wait=True) # stdout, stderr = proc.communicate() # print(stdout) # print(stderr) # time.sleep(1) # t = threading.Thread(target=loop) # t.daemon = True # t.start() def _start_instrument(self, port_to_ping): if self._instrument_proc is not None: self._instrument_proc.kill() self._instrument_proc = None ready = False self.adb_client.shell(['am', 'force-stop', PocoServicePackage]) # 启动instrument之前,先把主类activity启动起来,不然instrumentation可能失败 self.adb_client.shell( 'am start -n {}/.TestActivity'.format(PocoServicePackage)) instrumentation_cmd = [ 'am', 'instrument', '-w', '-e', 'debug', 'false', '-e', 'class', '{}.InstrumentedTestAsLauncher'.format(PocoServicePackage), '{}.test/android.support.test.runner.AndroidJUnitRunner'.format( PocoServicePackage) ] if new_airtest_api: self._instrument_proc = self.adb_client.start_shell( instrumentation_cmd) else: self._instrument_proc = self.adb_client.shell(instrumentation_cmd, not_wait=True) time.sleep(2) for i in range(10): try: requests.get('http://{}:{}'.format(self.device_ip, port_to_ping), timeout=10) ready = True break except requests.exceptions.Timeout: break except requests.exceptions.ConnectionError: time.sleep(1) print("still waiting for uiautomation ready.") continue return ready def on_pre_action(self, action, proxy, args): # airteset log用 try: from airtest.core.api import snapshot except ImportError: # 兼容旧airtest from airtest.core.main import snapshot snapshot(msg=unicode(proxy))
class AndroidUiautomationPoco(Poco): """ Poco Android implementation for testing **Android native apps**. Args: device (:py:obj:`Device`): :py:obj:`airtest.core.device.Device` instance provided by ``airtest``. leave the parameter default and the default device will be chosen. more details refer to ``airtest doc`` using_proxy (:py:obj:`bool`): whether use adb forward to connect the Android device or not force_restart (:py:obj:`bool`): whether always restart the poco-service-demo running on Android device or not options: see :py:class:`poco.pocofw.Poco` Examples: The simplest way to initialize AndroidUiautomationPoco instance and no matter your device network status:: from poco.drivers.android.uiautomation import AndroidUiautomationPoco poco = AndroidUiautomationPoco() poco('android:id/title').click() ... """ def __init__(self, device=None, using_proxy=True, force_restart=False, use_airtest_input=False, **options): # 加这个参数为了不在最新的pocounit方案中每步都截图 self.screenshot_each_action = True if options.get('screenshot_each_action') is False: self.screenshot_each_action = False self.device = device or current_device() if not self.device: self.device = connect_device("Android:///") self.adb_client = self.device.adb if using_proxy: self.device_ip = self.adb_client.host or "127.0.0.1" else: self.device_ip = self.device.get_ip_address() # save current top activity (@nullable) current_top_activity_package = self.device.get_top_activity_name() if current_top_activity_package is not None: current_top_activity_package = current_top_activity_package.split('/')[0] # install ime self.ime = YosemiteIme(self.adb_client) self.ime.start() # install self._instrument_proc = None self._install_service() # forward if using_proxy: p0, _ = self.adb_client.setup_forward("tcp:10080") p1, _ = self.adb_client.setup_forward("tcp:10081") else: p0 = 10080 p1 = 10081 # start if self._is_running('com.github.uiautomator'): warnings.warn('{} should not run together with "uiautomator". "uiautomator" will be killed.' .format(self.__class__.__name__)) self.adb_client.shell(['am', 'force-stop', 'com.github.uiautomator']) ready = self._start_instrument(p0, force_restart=force_restart) if not ready: # 之前启动失败就卸载重装,现在改为尝试kill进程或卸载uiautomator self._kill_uiautomator() ready = self._start_instrument(p0) if current_top_activity_package is not None: current_top_activity2 = self.device.get_top_activity_name() if current_top_activity2 is None or current_top_activity_package not in current_top_activity2: self.device.start_app(current_top_activity_package, activity=True) if not ready: raise RuntimeError("unable to launch AndroidUiautomationPoco") if ready: # 首次启动成功后,在后台线程里监控这个进程的状态,保持让它不退出 self._keep_running_thread = KeepRunningInstrumentationThread(self, p0) self._keep_running_thread.start() endpoint = "http://{}:{}".format(self.device_ip, p1) agent = AndroidPocoAgent(endpoint, self.ime, use_airtest_input) super(AndroidUiautomationPoco, self).__init__(agent, **options) def _install_service(self): updated = install(self.adb_client, os.path.join(this_dir, 'lib', 'pocoservice-debug.apk')) install(self.adb_client, os.path.join(this_dir, 'lib', 'pocoservice-debug-androidTest.apk'), updated) return updated def _is_running(self, package_name): """ use ps |grep to check whether the process exists :param package_name: package name(e.g., com.github.uiautomator) or regular expression(e.g., poco\|airtest\|uiautomator\|airbase) :return: pid or None """ cmd = r' |echo $(grep -E {package_name})'.format(package_name=package_name) if self.device.sdk_version > 25: cmd = r'ps -A' + cmd else: cmd = r'ps' + cmd processes = self.adb_client.shell(cmd).splitlines() for ps in processes: if ps: ps = ps.split() return ps[1] return None def _start_instrument(self, port_to_ping, force_restart=False): if not force_restart: try: state = requests.get('http://{}:{}/uiautomation/connectionState'.format(self.device_ip, port_to_ping), timeout=10) state = state.json() if state.get('connected'): # skip starting instrumentation if UiAutomation Service already connected. return True except: pass if self._instrument_proc is not None: if self._instrument_proc.poll() is None: self._instrument_proc.kill() self._instrument_proc = None ready = False self.adb_client.shell(['am', 'force-stop', PocoServicePackage]) # 启动instrument之前,先把主类activity启动起来,不然instrumentation可能失败 self.adb_client.shell('am start -n {}/.TestActivity'.format(PocoServicePackage)) instrumentation_cmd = [ 'am', 'instrument', '-w', '-e', 'debug', 'false', '-e', 'class', '{}.InstrumentedTestAsLauncher'.format(PocoServicePackage), '{}.test/android.support.test.runner.AndroidJUnitRunner'.format(PocoServicePackage)] self._instrument_proc = self.adb_client.start_shell(instrumentation_cmd) def cleanup_proc(proc): def wrapped(): try: proc.kill() except: pass return wrapped atexit.register(cleanup_proc(self._instrument_proc)) time.sleep(2) for i in range(10): try: requests.get('http://{}:{}'.format(self.device_ip, port_to_ping), timeout=10) ready = True break except requests.exceptions.Timeout: break except requests.exceptions.ConnectionError: if self._instrument_proc.poll() is not None: warnings.warn("[pocoservice.apk] instrumentation test server process is no longer alive") stdout = self._instrument_proc.stdout.read() stderr = self._instrument_proc.stderr.read() print('[pocoservice.apk] stdout: {}'.format(stdout)) print('[pocoservice.apk] stderr: {}'.format(stderr)) time.sleep(1) print("still waiting for uiautomation ready.") continue return ready def _kill_uiautomator(self): """ poco-service无法与其他instrument启动的apk同时存在,因此在启动前,需要杀掉一些可能的进程: 比如 io.appium.uiautomator2.server, com.github.uiautomator, com.netease.open.pocoservice等 :return: """ pid = self._is_running("uiautomator") if pid: warnings.warn('{} should not run together with "uiautomator". "uiautomator" will be killed.' .format(self.__class__.__name__)) self.adb_client.shell(['am', 'force-stop', PocoServicePackage]) try: self.adb_client.shell(['kill', pid]) except AdbShellError: # 没有root权限 uninstall(self.adb_client, UiAutomatorPackage) def on_pre_action(self, action, ui, args): if self.screenshot_each_action: # airteset log用 from airtest.core.api import snapshot msg = repr(ui) if not isinstance(msg, six.text_type): msg = msg.decode('utf-8') snapshot(msg=msg) def stop_running(self): print('[pocoservice.apk] stopping PocoService') self._keep_running_thread.stop() self._keep_running_thread.join(3) self.adb_client.shell(['am', 'force-stop', PocoServicePackage])