def _get_current_window(self): """使用dumpsys命令获取当前窗口 """ if self.adb.get_sdk_version() >= 29: result = self.adb.run_shell_cmd("dumpsys window visible-apps") else: result = self.adb.run_shell_cmd("dumpsys window") if result: line_list = result.split("\n") for line in line_list: if "mCurrentFocus" in line or "mFocusedWindow" in line: result = line break pattern1 = re.compile(r"mCurrentFocus=Window{(.+)}") pattern2 = re.compile(r"mFocusedWindow=Window{(.+)}") for pattern in (pattern1, pattern2): ret = pattern.search(result) if ret: break else: logger.info("Get current window by dumpsys failed: %s" % result) return None result = ret.group(1).split(" ")[-1] if "/" in result: result = result.split("/")[-1] if "Application Not Responding" in ret.group(1): result = "Application Not Responding: %s" % result return result
def _work_thread(self): time0 = time.time() while self._running and time.time() - time0 < self._timeout: infds, outfds, errfds = select.select([ self._sock, ], [], [], 1) if len(infds) > 0: try: buff = self._sock.recv(4096) if len(buff) == 0: self._sock.close() self._sock = None self._running = False self._event.set() return self._stdout.write(buff) except socket.error as e: logger.info("Recv response error: %s" % (e)) self._stdout.write(b" ") # 通知接收方退出 self._sock.close() self._sock = None self._running = False self._event.set() return self._sock.close() self._sock = None
def _work_thread(self): time0 = time.time() while self._running and time.time() - time0 < self._timeout: infds, outfds, errfds = select.select([ self._sock, ], [], [], 1) if len(infds) > 0: try: buff = self._sock.recv(4096) if len(buff) == 0: self._sock.close() self._sock = None self._running = False self._event.set() return self._stdout.write(buff) except socket.error as e: logger.info("接收返回数据错误: %s" % (e)) # import traceback # traceback.print_exc() self._stdout.write(b' ') # 通知接收方退出 self._sock.close() self._sock = None self._running = False self._event.set() return self._sock.close() self._sock = None
def send(self, data): if not self._connect: if not self.connect(): return None try: self._sock.send(data.encode('utf8')) except socket.error as e: logger.info('发送%r错误: %s' % (data, e)) self._sock.close() self._connect = False return None expect_len = self.recv(8) if not expect_len: return None expect_len = int(expect_len, 16) + 1 recv_buff = '' while len(recv_buff) < expect_len: buff = self.recv(expect_len - len(recv_buff)) if not buff: logger.warn('Socket closed when recv rsp for %r' % data) while True: try: recv_buff += buff.decode('utf8') break except UnicodeDecodeError: buff += self.recv(1) return recv_buff
def run_server(self, server_name=''): '''运行测试桩进程,创建服务端 ''' if server_name == '': server_name = self.service_name from qt4a.androiddriver.androiddriver import AndroidDriver port = AndroidDriver.get_process_name_hash(server_name, self.get_device_id()) logger.info('[DeviceDriver] port is %d' % port) addr = '127.0.0.1' if not self._is_local_device: addr = self.adb.host_name if self._client: self._client.close() self._client = None server_type = 'localabstract' if self.adb.is_rooted() and self.adb.is_selinux_opened(): # 创建TCP服务端 server_name = str(self.service_port) server_type = 'tcp' time0 = time.time() timeout = 20 while time.time() - time0 < timeout: self._client = self._create_client(server_name, server_type) if self._client: return self._client ret = self._run_server(server_name) logger.debug('[DeviceDriver] Server %s process created: %s' % (server_name, ret)) time.sleep(0.1) raise RuntimeError('连接系统测试桩超时')
def drag(self, x1, y1, x2, y2, count=5, wait_time=40, send_down_event=True, send_up_event=True): '''滑动 :param x1: 起始横坐标 :type x1: int :param y1: 起始纵坐标 :type y1: int :param x2: 终点横坐标 :type x2: int :param y2: 终点纵坐标 :type y2: int :param count: 步数 :type count: int :param wait_time: 滑动过程中每步之间的间隔时间,ms :type wait_time: int :param send_down_event: 是否发送按下消息 :type send_down_event: boolean :param send_up_event: 是否发送弹起消息 :type send_up_event: boolean ''' if y1 != y2 and abs(y1 - y2) < 60: # 此时滑动距离会很短 # 保证最短滑动距离为40,在索尼ce0682上发现小于40时会变成点击 三星9300上发现有时60像素以下滑动不起来 m = (y1 + y2) // 2 if y1 - y2 > 0: d = 30 else: d = -30 y1 = m + d # TODO:坐标合法性判断 y2 = m - d for _ in range(3): try: result = self.send_command(EnumCommand.CmdDrag, X1=x1, Y1=y1, X2=x2, Y2=y2, StepCount=count, SleepTime=wait_time, SendDownEvent=send_down_event, SendUpEvent=send_up_event) except AndroidSpyError as e: if str(e).find('java.lang.SecurityException') >= 0: logger.info( 'java.lang.SecurityException,current activity:%s' % self._device_driver.get_current_activity()) # 检测是否有弹窗 # 有时操作过快会出现该问题 time.sleep(0.1) else: raise e else: return True logger.error('drag (%s, %s, %s, %s) failed' % (x1, y1, x2, y2)) return False
def is_debug_package(self, package_name): '''是否是debug包 ''' ret = self.run_driver_cmd('isDebugPackage', package_name, root=self.adb.is_rooted()) logger.info('isDebugPackage ret: %s' % ret) if 'NameNotFoundException' in ret: raise RuntimeError('APP: %s not installed' % package_name) return 'true' in ret
def post_test(self): '''清理测试用例 ''' from qt4a.androiddriver.util import logger logger.info('post_test run') super(DemoTestBase, self).post_test() Device.release_all_device() # 释放所有设备 logger.info('postTest complete')
def post_test(self): '''清理测试用例 ''' logger.info('开始执行测试清理。。。') super(GYTestBase, self).post_test() Device.release_all_device() # 释放所有设备 logger.info('测试清理执行完毕。')
def _server_opend(self): """判断测试桩进程是否运行 """ process_name = self.service_name + ":service" pid = self.adb.get_pid(process_name) if pid != 0 and pid != self._server_pid: logger.info("[DeviceDriver] %s pid is %d" % (process_name, pid)) self._server_pid = pid return pid > 0
def get_sim_card_state(self): '''获取sim卡状态 ''' sim_state = self.adb.get_property('gsm.sim.state').strip() if sim_state == 'READY': return 'SIM_STATE_READY' if 'ABSENT' in sim_state or 'NOT_READY' in sim_state: return 'SIM_STATE_ABSENT' logger.info('sim state: %s' % sim_state) return 'SIM_STATE_UNKNOWN'
def _server_opend(self): '''判断测试桩进程是否运行 ''' process_name = self.service_name + ':service' pid = self.adb.get_pid(process_name) if pid != 0 and pid != self._server_pid: logger.info('[DeviceDriver] %s pid is %d' % (process_name, pid)) self._server_pid = pid return pid > 0
def send_back_key(self): '''发送返回按键 ''' if self.device.is_rooted(): self.device.send_key(4) else: res = self.device.send_key(4) if not res: logger.info('send key 4 return False') self.run_shell_cmd('input keyevent 4')
def is_debug_package(self, package_name): """是否是debug包 """ ret = self.run_driver_cmd("isDebugPackage", package_name, root=self.adb.is_rooted()) logger.info("isDebugPackage ret: %s" % ret) if "NameNotFoundException" in ret: raise RuntimeError("APP: %s not installed" % package_name) return "true" in ret
def acquire_device(self, type='Android', device_id='', **kwds): '''申请设备 :param type: 申请的设备类型,目前尚未使用 :type type: string :param device_id: 申请的设备ID,默认不指定设备ID :type device_id: string ''' logger.info('开始申请设备。。。') device = super(GYTestBase, self).acquire_device(device_id, **kwds) device.adb.start_logcat([]) logger.info('设备名称%s' % str(device.device_list)) return device
def install_package(self, pkg_path, overwrite=False): '''安装应用 ''' from qt4a.androiddriver.util import get_file_md5 if not os.path.exists(pkg_path): raise RuntimeError('APK: %r not exist' % pkg_path) pkg_size = os.path.getsize(pkg_path) pkg_md5 = get_file_md5(pkg_path) pkg_name = ADB._get_package_name(pkg_path) if self.is_package_installed(pkg_name, pkg_size, pkg_md5): logger.info('APP %s [%d]%s is installed' % (pkg_name, pkg_size, pkg_md5)) return True self.adb.install_apk(pkg_path, overwrite) return True
def open_app(self, app_name, force_by_search=False): '''打开小程序 ''' found = False if not force_by_search: timeout = 2 time0 = time.time() while time.time() - time0 < timeout: if len(self.Controls["小程序列表"]) > 2: break time.sleep(0.5) for i in range(3): logger.info('第%d次尝试查找小程序' % i) for i in range(1, len(self.Controls["小程序列表"])): it = self.Controls["小程序列表"][i] if it.has('小程序名称'): if general_encode(it['小程序名称'].text) == general_encode(app_name): it.click() found = True break if found: break time.sleep(1) if not found: # 通过搜索进入 self.Controls["搜索"].click() search_panel = MiniProgramSearchPanel(self) search_panel.open_app(app_name) try: self._app.wait_for_activity(TBSDownloadDialog.Activity) except: pass else: dialog = TBSDownloadDialog(self) if dialog.exist(): dialog.upgrade() time.sleep(4) app_panel = MiniProgramPanel(self) try: app_panel.wait_for_exist() except ControlNotFoundError: return self.open_app(app_name) else: return app_panel
def send(self, data): if not self._connect: if not self.connect(): return None try: self._sock.send(data.encode('utf8')) except socket.error as e: logger.info('发送%r错误: %s' % (data, e)) self._sock.close() self._connect = False return None expect_len = self.recv(8) if not expect_len: return None expect_len = int(expect_len, 16) + 1 recv_buff = '' max_utf8_length = 6 while len(recv_buff) < expect_len: buff = self.recv(expect_len - len(recv_buff)) if not buff: logger.warn('Socket closed when recv rsp for %r' % data) while True: try: recv_buff += buff.decode('utf8') break except UnicodeDecodeError: if len(buff) > max_utf8_length: for i in range(max_utf8_length): this_buff = buff[:i - max_utf8_length] try: recv_buff += this_buff.decode('utf8') except UnicodeDecodeError: pass else: buff = buff[i - max_utf8_length:] break else: raise RuntimeError('Invalid utf-8 bytes: %r' % buff) buff += self.recv(1) return recv_buff
def hello(self): ''' ''' result = self._client.hello() if result == None: return if not 'Result' in result: logger.warn('[DeviceDriver] no Result in hello rsp') raise RuntimeError('Server error') logger.info('[DeviceDriver] %s' % result['Result']) items = result['Result'].split(':') if len(items) > 0 and items[-1].isdigit(): if self._server_pid == 0: logger.warn('[DeviceDriver] Server pid is 0') elif self._server_pid != 0 and int(items[-1]) != self._server_pid: logger.warn('[DeviceDriver] Server pid not match: %s' % (int(items[-1]))) raise RuntimeError('Server error %s' % result['Result']) return result
def send_command(self, cmd_type, **kwds): """发送命令 """ curr_thread_id = threading.current_thread().ident self._wait_for_event(curr_thread_id, self._max_block_time) if cmd_type != EnumCommand.CmdHello: self._safe_init_driver() # 确保测试桩连接正常 result = self._client.send_command(cmd_type, **kwds) if result == None: pid = self._adb.get_pid(self._process["name"]) if pid > 0 and pid != self._process["id"]: # 新进程,需要重新注入 logger.info("process %s id changed: %d %d" % (self._process["name"], self._process["id"], pid)) self._is_init = False # 需要重新初始化 self._process["id"] = pid return self.send_command(cmd_type, **kwds) elif pid == 0: raise ProcessExitError("被测进程已退出,确认是否发生Crash") elif cmd_type != EnumCommand.CmdHello: # hello包不重试 logger.debug("socket error, try to reconnect") for _ in range(3): # 为防止由于设备短暂失联导致的连接断开,这里调一次adb forward self._client = self._create_client() result = self._client.send_command(cmd_type, **kwds) if result != None: return result raise SocketError("Connect Failed") else: raise SocketError("Connect Failed") if "Error" in result: if result["Error"] == u"控件已失效" or result[ "Error"] == u"Control expired": raise ControlExpiredError(result["Error"]) elif result["Error"] == u"控件类型错误": control_type = self.get_control_type(kwds["Control"]) raise TypeError("%s,当前控件类型为:%s" % (result["Error"], control_type)) else: raise AndroidSpyError(result["Error"]) return result
def send_command(self, cmd_type, **kwds): '''发送命令 ''' curr_thread_id = threading.current_thread().ident self._wait_for_event(curr_thread_id, self._max_block_time) if cmd_type != EnumCommand.CmdHello: self._safe_init_driver() # 确保测试桩连接正常 result = self._client.send_command(cmd_type, **kwds) if result == None: pid = self._adb.get_pid(self._process['name']) if pid > 0 and pid != self._process['id']: # 新进程,需要重新注入 logger.info('process %s id changed: %d %d' % (self._process['name'], self._process['id'], pid)) self._is_init = False # 需要重新初始化 self._process['id'] = pid return self.send_command(cmd_type, **kwds) elif pid == 0: raise ProcessExitError('被测进程已退出,确认是否发生Crash') elif cmd_type != EnumCommand.CmdHello: # hello包不重试 logger.debug('socket error, try to reconnect') for _ in range(3): # 为防止由于设备短暂失联导致的连接断开,这里调一次adb forward self._client = self._create_client() result = self._client.send_command(cmd_type, **kwds) if result != None: return result raise SocketError('Connect Failed') else: raise SocketError('Connect Failed') if 'Error' in result: if result['Error'] == u'控件已失效': raise ControlExpiredError(result['Error']) elif result['Error'] == u'控件类型错误': control_type = self.get_control_type(kwds['Control']) raise TypeError('%s,当前控件类型为:%s' % (result['Error'], control_type)) else: raise AndroidSpyError(result['Error']) return result
def send_command(self, cmd_type, **kwds): '''send command ''' packet = {} packet['Cmd'] = cmd_type packet['Seq'] = self.seq for key in kwds.keys(): packet[key] = kwds[key] data = json.dumps(packet) + "\n" if six.PY2 and isinstance(data, unicode): data = data.encode('utf8') time0 = time_clock() self._lock.acquire() time1 = time_clock() delta = time1 - time0 if self._enable_log and delta >= 0.05: logger.info('send wait %s S' % delta) if self._enable_log: logger.debug('send: %s' % (data[:512].strip())) time0 = time_clock() try: result = self.send(data) except Exception as e: # 避免因异常导致死锁 logger.exception('send %r error: %s' % (data, e)) result = None self._lock.release() # 解锁 if not result: return None time1 = time_clock() try: rsp = json.loads(result) except: logger.error('json error: %r' % (result)) raise else: if self._enable_log: delta = int(1000 * (time1 - time0)) if 'HandleTime' in rsp: delta -= rsp['HandleTime'] logger.debug('recv: [%d]%s\n' % (delta, result[:512].strip())) return rsp
def hello(self): """ """ result = self._client.hello() if result == None: return if not "Result" in result: logger.warn("[DeviceDriver] no Result in hello rsp") raise RuntimeError("Server error") logger.info("[DeviceDriver] %s" % result["Result"]) items = result["Result"].split(":") if len(items) > 0 and items[-1].isdigit(): if self._server_pid == 0: logger.warn("[DeviceDriver] Server pid is 0") elif self._server_pid != 0 and int(items[-1]) != self._server_pid: logger.warn("[DeviceDriver] Server pid not match: %s" % (int(items[-1]))) raise RuntimeError("Server error %s" % result["Result"]) return result
def _get_current_window(self): '''使用dumpsys命令获取当前窗口 ''' result = self.adb.run_shell_cmd('dumpsys window windows') if result: line_list = result.split('\n') for line in line_list: if 'mCurrentFocus' in line: result = line break pattern = re.compile(r'mCurrentFocus=Window{(.+)}') ret = pattern.search(result) if not ret: logger.info('Get current window by dumpsys failed: %s' % result) return None result = ret.group(1).split(' ')[-1] if '/' in result: result = result.split('/')[-1] if 'Application Not Responding' in ret.group(1): result = 'Application Not Responding: %s' % result return result
def read_logcat(self, tag, process_name_pattern, pattern, num=1): '''查找最近满足条件的一条log :param tag: 期望的Tag :type tag: string :param process_name_pattern: 期望的进程名,传入正则表达式 :type process_name_pattern: string :param pattern: 期望匹配的格式 :type pattern: Pattern :param num: 返回满足条件的日志条数 :type num: int ''' from qt4a.androiddriver.util import logger pat = re.compile( r'^\[(.+)\(\d+\)\]\s+\[.+\]\s+\w/(.+)\(\s*\d+\):\s+(.+)$') log_pat = re.compile(pattern) log_list = self.adb.get_log(False) log_list = [i.decode("utf-8") for i in log_list] result_list = [] k = 0 for i in range(len(log_list) - 1, -1, -1): ret = pat.match(log_list[i]) if not ret: logger.info( 'read_logcat:%s not match ^\[(.+)\(\d+\)\]\s+\[.+\]\s+\w/(.+)\(\s*\d+\):\s+(.+)$' % log_list[i]) continue if not process_name_pattern: continue process_pat = re.compile(process_name_pattern) if not process_pat.match(ret.group(1)): continue if ret.group(2) != tag: continue if log_pat.search(ret.group(3)): if num == 1: return ret.group(3) elif num == 0 or k < num: k += 1 result_list.append(ret.group(3)) else: break return result_list
def copy_android_driver(device_id_or_adb, force=False, root_path=None, enable_acc=True): '''测试前的测试桩拷贝 ''' from qt4a.androiddriver.adb import ADB from qt4a.androiddriver.util import AndroidPackage, version_cmp if isinstance(device_id_or_adb, ADB): adb = device_id_or_adb else: adb = ADB.open_device(device_id_or_adb) if not root_path: current_path = os.path.abspath(__file__) if not os.path.exists(current_path) and '.egg' in current_path: # in egg egg_path = current_path while not os.path.exists(egg_path): egg_path = os.path.dirname(egg_path) assert (egg_path.endswith('.egg')) root_path = os.path.join(tempfile.mkdtemp(), 'tools') extract_from_zipfile(egg_path, 'qt4a/androiddriver/tools', root_path) else: root_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'tools') dst_path = '/data/local/tmp/qt4a/' current_version_file = os.path.join(root_path, 'version.txt') f = open(current_version_file, 'r') current_version = int(f.read()) f.close() if not force: version_file = dst_path + 'version.txt' version = adb.run_shell_cmd('cat %s' % version_file) if version and not 'No such file or directory' in version and current_version <= int( version): # 不需要拷贝测试桩 logger.warn('忽略本次测试桩拷贝:当前版本为%s,设备中版本为%s' % (current_version, int(version))) return try: adb.chmod(dst_path[:-1], '777') except: pass rooted = adb.is_rooted() cpu_abi = adb.get_cpu_abi() print('Current CPU arch: %s' % cpu_abi) use_pie = False if adb.get_sdk_version() >= 21 and cpu_abi != 'arm64-v8a': use_pie = True file_list = [ os.path.join(cpu_abi, 'droid_inject'), os.path.join(cpu_abi, 'libdexloader.so'), os.path.join(cpu_abi, 'screenkit'), os.path.join(cpu_abi, 'libandroidhook.so'), 'inject', 'AndroidSpy.jar', 'SpyHelper.jar', 'SpyHelper.sh' ] if cpu_abi == 'arm64-v8a': file_list.append(os.path.join(cpu_abi, 'droid_inject64')) file_list.append(os.path.join(cpu_abi, 'libdexloader64.so')) file_list.append('inject64') if adb.get_sdk_version() >= 21: file_list.append(os.path.join(cpu_abi, 'libandroidhook_art.so')) if rooted and adb.is_selinux_opened(): # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面 adb.run_shell_cmd('rm -r %s' % dst_path, True) # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibdexloader.so' % dst_path, True) # 恢复文件context,否则拷贝失败 # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibandroidhook.so' % dst_path, True) for file in file_list: file_path = os.path.join(root_path, file) if use_pie and not '.' in file and os.path.exists(file_path + '_pie'): file_path += '_pie' if not os.path.exists(file_path): continue save_name = os.path.split(file)[-1] if save_name.endswith('_art.so'): save_name = save_name.replace('_art', '') adb.push_file(file_path, dst_path + save_name) adb.chmod('%sdroid_inject' % dst_path, 755) adb.chmod('%sinject' % dst_path, 755) adb.chmod('%sscreenkit' % dst_path, 755) adb.run_shell_cmd('ln -s %sscreenkit %sscreenshot' % (dst_path, dst_path)) if cpu_abi == 'arm64-v8a': adb.chmod('%sdroid_inject64' % dst_path, 755) adb.chmod('%sinject64' % dst_path, 755) try: print(adb.run_shell_cmd('rm -R %scache' % dst_path, rooted)) # 删除目录 rm -rf except RuntimeError as e: logger.warn('%s' % e) # logger.info(adb.run_shell_cmd('mkdir %scache' % (dst_path), True)) #必须使用root权限,不然生成odex文件会失败 adb.mkdir('%scache' % (dst_path), 777) qt4a_helper_package = 'com.test.androidspy' apk_path = os.path.join(root_path, 'QT4AHelper.apk') if adb.get_package_path(qt4a_helper_package): # 判断版本 installed_version = adb.get_package_version(qt4a_helper_package) package = AndroidPackage(apk_path) install_version = package.version if version_cmp(install_version, installed_version) > 0: adb.install_apk(apk_path, True) else: adb.install_apk(apk_path) # adb.install_apk(os.path.join(root_path, 'QT4AMockApp.apk'), True) version_file_path = os.path.join(root_path, 'version.txt') dst_version_file_path = dst_path + os.path.split(version_file_path)[-1] adb.push_file(version_file_path, dst_version_file_path + '.tmp') # 拷贝版本文件 if rooted and adb.is_selinux_opened(): # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面 # 获取sdcars context if adb.get_sdk_version() >= 23: import re sdcard_path = adb.get_sdcard_path() result = adb.run_shell_cmd('ls -Z %s' % sdcard_path) # u:object_r:media_rw_data_file:s0 u:object_r:rootfs:s0 pattern = re.compile(r'\s+(u:object_r:.+:s0)\s+') ret = pattern.search(result) if not ret: raise RuntimeError('get sdcard context failed: %s' % result) context = ret.group(1) logger.info('sdcard context is %s' % context) adb.run_shell_cmd('chcon %s %s' % (context, dst_path), True) # make app access adb.run_shell_cmd( 'chcon u:object_r:app_data_file:s0 %sSpyHelper.jar' % dst_path, True) adb.run_shell_cmd( 'chcon u:object_r:app_data_file:s0 %sSpyHelper.sh' % dst_path, True) # 不修改文件context无法加载so adb.run_shell_cmd( 'chcon u:object_r:system_file:s0 %slibdexloader.so' % dst_path, True) adb.run_shell_cmd( 'chcon u:object_r:app_data_file:s0 %slibandroidhook.so' % dst_path, True) adb.run_shell_cmd( 'chcon %s %sAndroidSpy.jar' % (context, dst_path), True) adb.run_shell_cmd('chcon %s %scache' % (context, dst_path), True) else: # 不修改文件context无法加载so adb.run_shell_cmd( 'chcon u:object_r:app_data_file:s0 %slibdexloader.so' % dst_path, True) adb.run_shell_cmd( 'chcon u:object_r:app_data_file:s0 %slibandroidhook.so' % dst_path, True) adb.run_shell_cmd( 'chcon u:object_r:app_data_file:s0 %scache' % dst_path, True) if rooted: if adb.get_sdk_version() < 24: # 7.0以上发现生成的dex与运行时生成的dex有差别,可能导致crash logger.info( adb.run_shell_cmd( 'sh %sSpyHelper.sh loadDex %sAndroidSpy.jar %scache' % (dst_path, dst_path, dst_path), rooted)) adb.chmod('%scache/AndroidSpy.dex' % dst_path, 666) else: if not 'usage:' in adb.run_shell_cmd('sh %sSpyHelper.sh' % dst_path): adb.mkdir('%scache/dalvik-cache' % dst_path, 777) if rooted and adb.is_selinux_opened() and adb.get_sdk_version() >= 23: # 提升权限 try: adb.list_dir('/system/bin/app_process32') except RuntimeError: adb.copy_file('/system/bin/app_process', '%sapp_process' % dst_path) else: adb.copy_file('/system/bin/app_process32', '%sapp_process' % dst_path) adb.chmod('%sapp_process' % dst_path, 755) adb.run_shell_cmd( 'chcon u:object_r:system_file:s0 %sapp_process' % dst_path, True) adb.run_shell_cmd( 'mv %s %s' % (dst_version_file_path + '.tmp', dst_version_file_path), rooted) # 同步手机时间 device_driver = DeviceDriver(adb) try: input_method = 'com.test.androidspy/.service.QT4AKeyboardService' device_driver.modify_system_setting('secure', 'enabled_input_methods', input_method) device_driver.modify_system_setting('secure', 'default_input_method', input_method) if enable_acc: device_driver.modify_system_setting( 'secure', 'enabled_accessibility_services', 'com.test.androidspy/com.test.androidspy.service.QT4AAccessibilityService' ) device_driver.modify_system_setting('secure', 'accessibility_enabled', 1) except: logger.exception('set default input method failed') try: device_driver.modify_system_setting('system', 'time_12_24', 24) device_driver.modify_system_setting('system', 'screen_off_timeout', 600 * 1000) except: logger.exception('set system time failed')
def post_test(self): '''清理测试用例 ''' logger.info('postTest run') super(WxTestBase, self).post_test() logger.info('postTest complete')
def _init_driver(self): """初始化测试桩 """ self._client = self._create_client() if self._client != None: # 字段赋值 self._process["name"] = self._process_name self._process["id"] = 0 # process id may change if self.hello() != None: self._process["id"] = self._adb.get_pid(self._process_name) return timeout = 20 time0 = time.time() proc_exist = False while time.time() - time0 < timeout: if not proc_exist: pid = self._adb.get_pid(self._process_name) if pid > 0: proc_exist = True self._process["name"] = self._process_name self._process["id"] = pid break time.sleep(1) if not proc_exist: raise RuntimeError("进程:%s 在%d秒内没有出现" % (self._process_name, timeout)) inject_file = "inject" if self._adb.is_app_process64( pid if self._adb.is_rooted() else self._process_name): # 64 bit process inject_file += "64" timeout = 30 try: if self._adb.is_art(): # Android 5.0上发现注入容易导致进程退出 self._wait_for_cpu_low(20, 10) time0 = time.time() cmdline = "%s/%s %s" % ( self._get_driver_root_path(), inject_file, self._process_name, ) while time.time() - time0 < timeout: if self._adb.is_rooted(): ret = self._adb.run_shell_cmd(cmdline, True, timeout=120, retry_count=1) else: ret = self._adb.run_as(self._process_name, cmdline, timeout=120, retry_count=1) logger.debug("inject result: %s" % ret) if "not found" in ret: raise QT4ADriverNotInstalled( "QT4A driver damaged, please reinstall QT4A driver") if "Inject Success" in ret: break elif "Operation not permitted" in ret: # 可能是进程处于Trace状态 pid = self._adb.get_pid(self._process_name) status = self._adb.get_process_status(pid) tracer_pid = int(status["TracerPid"]) if tracer_pid > 0: if int(status["PPid"]) == tracer_pid: # 使用TRACEME方式防注入 raise Exception("应用使用了防注入逻辑,注入失败") logger.warn("TracerPid is %d" % tracer_pid) self._adb.kill_process(tracer_pid) elif "Function not implemented" in ret: raise Exception( "Please install repacked app on this device") time.sleep(1) except RuntimeError as e: logger.exception("%s\n%s" % (e, self._adb.run_shell_cmd("ps"))) if self._adb.is_rooted(): logger.info(self._adb.dump_stack(self._process_name)) raise e timeout = 10 time0 = time.time() while time.time() - time0 < timeout: if self._client == None: self._client = self._create_client() if self._client != None and self.hello() != None: return time.sleep(0.1) raise RuntimeError("连接测试桩超时")
def copy_android_driver(device_id_or_adb, force=False, root_path=None, enable_acc=True): """测试前的测试桩拷贝 """ from qt4a.androiddriver.adb import ADB if isinstance(device_id_or_adb, ADB): adb = device_id_or_adb else: adb = ADB.open_device(device_id_or_adb) if not root_path: current_path = os.path.abspath(__file__) if not os.path.exists(current_path) and ".egg" in current_path: # in egg egg_path = current_path while not os.path.exists(egg_path): egg_path = os.path.dirname(egg_path) assert egg_path.endswith(".egg") root_path = os.path.join(tempfile.mkdtemp(), "tools") extract_from_zipfile(egg_path, "qt4a/androiddriver/tools", root_path) else: root_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "tools") dst_path = "/data/local/tmp/qt4a/" current_version_file = os.path.join(root_path, "version.txt") f = open(current_version_file, "r") current_version = int(f.read()) f.close() if not force: version_file = dst_path + "version.txt" version = adb.run_shell_cmd("cat %s" % version_file) if (version and not "No such file or directory" in version and current_version <= int(version)): install_qt4a_helper(adb, root_path) # 避免QT4A助手被意外删除的情况 # 不需要拷贝测试桩 logger.warn("忽略本次测试桩拷贝:当前版本为%s,设备中版本为%s" % (current_version, int(version))) return try: adb.chmod(dst_path[:-1], "777") except: pass rooted = adb.is_rooted() cpu_abi = adb.get_cpu_abi() print("Current CPU arch: %s" % cpu_abi) # use_pie = False # if adb.get_sdk_version() >= 21 and cpu_abi != "arm64-v8a": # use_pie = True file_list = [ os.path.join(cpu_abi, "droid_inject"), os.path.join(cpu_abi, "libdexloader.so"), os.path.join(cpu_abi, "screenkit"), "inject", "AndroidSpy.jar", "SpyHelper.jar", "SpyHelper.sh", ] if cpu_abi == "arm64-v8a": file_list.append(os.path.join(cpu_abi, "droid_inject64")) file_list.append(os.path.join(cpu_abi, "libdexloader64.so")) file_list.append("inject64") if adb.get_sdk_version() >= 21: file_list.append(os.path.join(cpu_abi, "libandroidhook_art.so")) if rooted and adb.is_selinux_opened(): # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面 adb.run_shell_cmd("rm -r %s" % dst_path, True) # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibdexloader.so' % dst_path, True) # 恢复文件context,否则拷贝失败 # adb.run_shell_cmd('chcon u:object_r:shell_data_file:s0 %slibandroidhook.so' % dst_path, True) for file in file_list: file_path = os.path.join(root_path, file) # if use_pie and not "." in file and os.path.exists(file_path + "_pie"): # file_path += "_pie" if not os.path.exists(file_path): continue save_name = os.path.split(file)[-1] if save_name.endswith("_art.so"): save_name = save_name.replace("_art", "") adb.push_file(file_path, dst_path + save_name) adb.chmod("%sdroid_inject" % dst_path, 755) adb.chmod("%sinject" % dst_path, 755) adb.chmod("%sscreenkit" % dst_path, 755) adb.run_shell_cmd("ln -s %sscreenkit %sscreenshot" % (dst_path, dst_path)) if cpu_abi == "arm64-v8a": adb.chmod("%sdroid_inject64" % dst_path, 755) adb.chmod("%sinject64" % dst_path, 755) try: print(adb.run_shell_cmd("rm -R %scache" % dst_path, rooted)) # 删除目录 rm -rf except RuntimeError as e: logger.warn("%s" % e) # logger.info(adb.run_shell_cmd('mkdir %scache' % (dst_path), True)) #必须使用root权限,不然生成odex文件会失败 adb.mkdir("%scache" % (dst_path), 777) install_qt4a_helper(adb, root_path) version_file_path = os.path.join(root_path, "version.txt") dst_version_file_path = dst_path + os.path.split(version_file_path)[-1] adb.push_file(version_file_path, dst_version_file_path + ".tmp") # 拷贝版本文件 if rooted and adb.is_selinux_opened(): # 此时如果还是开启状态说明关闭selinux没有生效,主要是三星手机上面 # 获取sdcars context if adb.get_sdk_version() >= 23: sdcard_path = adb.get_sdcard_path() result = adb.run_shell_cmd("ls -Z %s" % sdcard_path) # u:object_r:media_rw_data_file:s0 u:object_r:rootfs:s0 pattern = re.compile(r"\s+(u:object_r:.+:s0)\s+") ret = pattern.search(result) if not ret: raise RuntimeError("get sdcard context failed: %s" % result) context = ret.group(1) logger.info("sdcard context is %s" % context) adb.run_shell_cmd("chcon %s %s" % (context, dst_path), True) # make app access adb.run_shell_cmd( "chcon u:object_r:app_data_file:s0 %sSpyHelper.jar" % dst_path, True) adb.run_shell_cmd( "chcon u:object_r:app_data_file:s0 %sSpyHelper.sh" % dst_path, True) # 不修改文件context无法加载so adb.run_shell_cmd( "chcon u:object_r:system_file:s0 %slibdexloader.so" % dst_path, True) adb.run_shell_cmd( "chcon u:object_r:app_data_file:s0 %slibandroidhook.so" % dst_path, True) adb.run_shell_cmd( "chcon %s %sAndroidSpy.jar" % (context, dst_path), True) adb.run_shell_cmd("chcon %s %scache" % (context, dst_path), True) else: # 不修改文件context无法加载so adb.run_shell_cmd( "chcon u:object_r:app_data_file:s0 %slibdexloader.so" % dst_path, True) adb.run_shell_cmd( "chcon u:object_r:app_data_file:s0 %slibandroidhook.so" % dst_path, True) adb.run_shell_cmd( "chcon u:object_r:app_data_file:s0 %scache" % dst_path, True) if rooted: if adb.get_sdk_version() < 24: # 7.0以上发现生成的dex与运行时生成的dex有差别,可能导致crash logger.info( adb.run_shell_cmd( "sh %sSpyHelper.sh loadDex %sAndroidSpy.jar %scache" % (dst_path, dst_path, dst_path), rooted, )) adb.chmod("%scache/AndroidSpy.dex" % dst_path, 666) else: if not "usage:" in adb.run_shell_cmd("sh %sSpyHelper.sh" % dst_path): adb.mkdir("%scache/dalvik-cache" % dst_path, 777) if rooted and adb.is_selinux_opened() and adb.get_sdk_version() >= 23: # 提升权限 try: adb.list_dir("/system/bin/app_process32") except RuntimeError: adb.copy_file("/system/bin/app_process", "%sapp_process" % dst_path) else: adb.copy_file("/system/bin/app_process32", "%sapp_process" % dst_path) adb.chmod("%sapp_process" % dst_path, 755) adb.run_shell_cmd( "chcon u:object_r:system_file:s0 %sapp_process" % dst_path, True) adb.run_shell_cmd( "mv %s %s" % (dst_version_file_path + ".tmp", dst_version_file_path), rooted) # 同步手机时间 device_driver = DeviceDriver(adb) try: input_method = "com.test.androidspy/.service.QT4AKeyboardService" device_driver.modify_system_setting("secure", "enabled_input_methods", input_method) device_driver.modify_system_setting("secure", "default_input_method", input_method) if enable_acc: device_driver.modify_system_setting( "secure", "enabled_accessibility_services", "com.test.androidspy/com.test.androidspy.service.QT4AAccessibilityService", ) device_driver.modify_system_setting("secure", "accessibility_enabled", 1) except: logger.exception("set default input method failed") try: device_driver.modify_system_setting("system", "time_12_24", 24) device_driver.modify_system_setting("system", "screen_off_timeout", 600 * 1000) except: logger.exception("set system time failed")
def _init_driver(self): '''初始化测试桩 ''' self._client = self._create_client() if self._client != None: # 字段赋值 self._process['name'] = self._process_name self._process['id'] = 0 # process id may change if self.hello() != None: self._process['id'] = self._adb.get_pid(self._process_name) return timeout = 20 time0 = time.time() proc_exist = False while time.time() - time0 < timeout: if not proc_exist: pid = self._adb.get_pid(self._process_name) if pid > 0: proc_exist = True self._process['name'] = self._process_name self._process['id'] = pid break time.sleep(1) if not proc_exist: raise RuntimeError('进程:%s 在%d秒内没有出现' % (self._process_name, timeout)) inject_file = 'inject' if self._adb.is_app_process64( pid if self._adb.is_rooted() else self._process_name): # 64 bit process inject_file += '64' timeout = 30 try: if self._adb.is_art(): # Android 5.0上发现注入容易导致进程退出 self._wait_for_cpu_low(20, 10) time0 = time.time() cmdline = '%s/%s %s' % (self._get_driver_root_path(), inject_file, self._process_name) while time.time() - time0 < timeout: if self._adb.is_rooted(): ret = self._adb.run_shell_cmd(cmdline, True, timeout=120, retry_count=1) else: ret = self._adb.run_as(self._process_name, cmdline, timeout=120, retry_count=1) logger.debug('inject result: %s' % ret) if 'not found' in ret: raise QT4ADriverNotInstalled( 'QT4A driver damaged, please reinstall QT4A driver') if 'Inject Success' in ret: break elif 'Operation not permitted' in ret: # 可能是进程处于Trace状态 pid = self._adb.get_pid(self._process_name) status = self._adb.get_process_status(pid) tracer_pid = int(status['TracerPid']) if tracer_pid > 0: if int(status['PPid']) == tracer_pid: # 使用TRACEME方式防注入 raise Exception('应用使用了防注入逻辑,注入失败') logger.warn('TracerPid is %d' % tracer_pid) self._adb.kill_process(tracer_pid) time.sleep(1) except RuntimeError as e: logger.exception('%s\n%s' % (e, self._adb.run_shell_cmd('ps'))) if self._adb.is_rooted(): logger.info(self._adb.dump_stack(self._process_name)) raise e timeout = 10 time0 = time.time() while time.time() - time0 < timeout: if self._client == None: self._client = self._create_client() if self._client != None and self.hello() != None: return time.sleep(0.1) raise RuntimeError('连接测试桩超时')