def reboot(self, wait_cpu_low=True, usage=20, duration=10, timeout=120): '''重启手机 :param wait_cpu_low: 是否等待CPU使用率降低 :type wait_cpu_low: bool :param usage: cpu使用率阈值 :type usage: int :param duration: 持续时间(秒) :type duration: int :param timeout: 超时时间,超市时间到后,无论当前CPU使用率是多少,都会返回 :type timeout: int ''' pattern = re.compile(r'^.+:\d+$') try: ret = self.run_driver_cmd('reboot', True, retry_count=1, timeout=30) if ret == 'false': return except RuntimeError as e: logger.warn('reboot: %r' % e) if pattern.match(self.device_id): try: self.adb.reboot(60) except RuntimeError: logger.warn('reboot: %s' % e) # 虚拟机会出现明明重启成功却一直不返回的情况 else: self.adb.reboot(0) # 不能使用reboot shell命令,有些手机需要root权限才能执行 time.sleep(10) # 防止设备尚未关闭,一般重启不可能在10秒内完成 self.adb.wait_for_boot_complete() self._adb = None # 重启后部分属性可能发生变化,需要重新实例化 if wait_cpu_low == True: self.wait_for_cpu_usage_low(usage, duration, timeout)
def _run_server(self, server_name): '''运行系统测试桩 ''' if not self.adb.is_rooted(): try: self.adb.start_service('%s/.service.HelperService' % server_name, {'serviceName': server_name}) except RuntimeError: logger.warn('start helper server failed') self.adb.start_activity('%s/.activity.StartServiceActivity' % server_name, extra={'serviceName': 'HelperService'}, wait=False) time.sleep(1) return self._server_opend() timeout = 10 time0 = time.time() while time.time() - time0 < timeout: try: ret = self.run_driver_cmd('runServer', server_name, root=self.adb.is_rooted(), retry_count=1, timeout=10) logger.debug('Run server %s' % ret) if 'service run success' in ret: # wait for parent process exit time.sleep(0.5) elif 'service is running' in ret: pass elif 'java.lang.UnsatisfiedLinkError' in ret: raise RuntimeError('启动系统测试桩进程失败:\n%s' % ret) except TimeoutError as e: logger.warn('Run server timeout: %s' % e) if self._server_opend(): return True time.sleep(1) return False
def refresh_media_store(self, file_path=""): """刷新图库,显示新拷贝到手机的图片 """ from qt4a.androiddriver.adb import TimeoutError command = "" if not file_path: if (hasattr(self, "_last_refresh_all") and time.time() - self._last_refresh_all < 2 * 60): logger.warn("[DeviceDriver] 120S内不允许全盘重复刷新") return sdcard_path = self.get_external_sdcard_path() command = ( "am broadcast -a android.intent.action.MEDIA_MOUNTED --ez read-only false -d file://%s" % sdcard_path) self._last_refresh_all = time.time() else: if file_path.startswith("/sdcard/"): file_path = self.get_external_sdcard_path() + file_path[7:] command = ( "am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://%s" % file_path) try: self.adb.run_shell_cmd(command, self.adb.is_rooted()) except TimeoutError: logger.exception("refresh_media_store failed") return False # self.adb.run_shell_cmd('am broadcast -a android.intent.action.BOOT_COMPLETED') if file_path and self.adb.get_sdk_version() >= 16: return self._wait_for_media_available(file_path)
def pull(self, device_id, src_file, dst_file): '''adb pull ''' time0 = time.time() self._transport(device_id) self._send_command('sync:') mode, fsize, ftime = self._sync_read_mode(src_file) if mode == 0: self._sock.close() self._sock = None raise AdbError('remote object %r does not exist' % src_file) src_file = utf8_encode(src_file) data = b'RECV' + struct.pack(b'I', len(src_file)) + src_file self._sock.send(data) f = open(dst_file, 'wb') data_size = 0 last_data = b'' while True: result = self._sock.recv(8) if len(result) != 8: logger.warn('返回数据错误:%r' % result) last_data += result if len(last_data) < 8: continue else: result = last_data[:8] last_data = last_data[8:] psize = struct.unpack(b'I', result[4:])[0] # 每个分包大小 if result[:4] == b'DONE': break elif result[:4] == b'FAIL': raise AdbError(self._sock.recv(psize)) elif result[:4] != b'DATA': raise AdbError('pull_file error') result = self._recv(psize - len(last_data)) result = last_data + result if len(result) >= psize: last_data = result[psize:] result = result[:psize] else: raise ValueError('数据长度不一致,期望值:%d 实际值:%d' % (psize, len(result))) f.write(result) data_size += len(result) f.close() self._sock.send(b'QUIT' + struct.pack(b'I', 0)) time_cost = time.time() - time0 self._sock.close() self._sock = None if data_size > 0: return '%d KB/s (%d bytes in %fs)' % (int( data_size / 1000 / time_cost) if time_cost > 0 else 65535, data_size, time_cost) else: return ''
def click(self, control, x, y, sleep_time=0): if x < 0 or y < 0: raise RuntimeError("坐标错误:(%d, %d)" % (x, y)) try: if not control: result = self.send_command(EnumCommand.CmdClick, X=x, Y=y, SleepTime=int(sleep_time * 1000)) else: result = self.send_command( EnumCommand.CmdClick, Control=control, X=x, Y=y, SleepTime=int(sleep_time * 1000), ) # print (result['Result']) return result["Result"] except AndroidSpyError as e: if str(e).find("java.lang.SecurityException") >= 0: # 有时操作过快会出现该问题 return False elif "ErrControlNotShown" in str(e): return False elif str(e) == "控件已失效": # 认为点击成功了 pic_name = "%s.png" % int(time.time()) logger.warn("%s %s" % (e, pic_name)) self._device_driver.take_screen_shot(pic_name) return True else: raise e
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 wait_for_recv_message(self, timeout=120, interval=0.5): '''等待接收消息完成 ''' from tuia.exceptions import ControlNotFoundError time0 = time.time() recving = self.Controls['左上角微信'] try: recving.wait_for_exist(20, 0.5) except ControlNotFoundError: logger.warn('wait for loading timeout') while time.time() - time0 < timeout: if not recving.exist() or not recving.visible: time.sleep(2) continue if '(' not in self.Controls['左上角微信'].text: time.sleep(1) continue break old_text = self.Controls['左上角微信'].text time0 = time.time() while time.time() - time0 < 5: time.sleep(2) new_text = self.Controls['左上角微信'].text if new_text != old_text: old_text = new_text time.sleep(3) else: break
def _start(self, clear_state=True, kill_process=True, start_extra_params={}): '''启动Android微信 ''' if kill_process == True: self.device.kill_process(self.package_name) # 杀死已有进程 if clear_state == True: # 清登录态,不清数据 logger.warn('Clearing user state') self.run_shell_cmd('rm /data/data/%s/shared_prefs/%s*.xml' % (self.package_name, self.package_name)) self.run_shell_cmd('rm /data/data/%s/MicroMsg/*.cfg' % (self.package_name)) self.run_shell_cmd('rm /data/data/%s/shared_prefs/system_config_prefs.xml' % (self.package_name)) # try: # # 目前还不支持xwalk内核,先强制不使用该内核 # self.run_shell_cmd('rm -r /data/data/%s/app_xwalkconfig' % (self.package_name)) # self.run_shell_cmd('touch /data/data/%s/app_xwalkconfig' % (self.package_name)) # self.run_shell_cmd('chmod 777 /data/data/%s/app_xwalkconfig' % (self.package_name)) # self.run_shell_cmd('rm -r /data/data/%s/app_xwalk_*' % (self.package_name)) # except: # logger.exception('Disable xwalk failed') try: self.device.start_activity(self.startup_activity, extra=start_extra_params) except TimeoutError: logger.exception('Start Android Wechat Failed') self.device.clear_data(self.package_name) self.device.start_activity(self.startup_activity, extra=start_extra_params)
def get_current_activity(self): '''获取当前窗口 ''' timeout = 10 time0 = time.time() result = None while time.time() - time0 < timeout: if not self.adb.is_rooted(): result = self._get_current_window() else: try: result = self._send_command('GetCurrentWindow') except SocketError as e: raise e except RuntimeError as e: logger.warn('GetCurrentWindow error: %s' % e) if not result or result == 'null': # 一般是设备黑屏之类 time.sleep(0.5) continue return result if not self.adb.is_rooted(): return None logger.warn('GetCurrentWindow failed') return self._send_command('GetCurrentActivity')
def get_imei(self): '''获取设备imei号 ''' try: return self.adb.get_device_imei() except RuntimeError as e: logger.warn('Read device imei by dumpsys failed: %s' % e) return self._device_driver.get_device_imei()
def close_activity(self, activity): '''关闭Activity ''' try: return self._close_activity(activity) except ProcessExitError: logger.warn('close_activity error: process %s not exist' % self._process_name) return -3 # 进程退出
def clear_camera_default_app(self): '''清除默认相机应用 ''' from qt4a.androiddriver.util import logger if self.is_rooted(): return self._device_driver.clear_default_app('android.media.action.IMAGE_CAPTURE') else: logger.warn('clear_camera_default_app need root')
def get_system_boot_time(self): '''获取系统启动时间,单位为秒 ''' for _ in range(3): ret = self.run_driver_cmd('getSystemBootTime', root=self.adb.is_rooted()) if ret and ret.isdigit(): return int(ret / 1000) logger.warn('getSystemBootTime return %r' % ret) time.sleep(2) return 0
def close_activity(self, activity): """关闭Activity """ try: return self._close_activity(activity) except ProcessExitError: logger.warn("close_activity error: process %s not exist" % self._process_name) return -3 # 进程退出
def _get_view_id(self, str_id): '''从控件字符串ID获取整型ID ''' if not str_id: raise RuntimeError('控件ID不能为空') try: return self.device._get_view_id(self.package_name, str_id) except RuntimeError, e: logger.warn(str(e)) return None
def take_screen_shot(self, path, quality=90): '''截屏 ''' result = self.adb.run_shell_cmd('%s/screenshot capture -q %s' % (qt4a_path, quality), binary_output=True) # 为避免pull文件耗时,直接写到stdout if len(result) < 256: logger.warn('Take screenshot failed: %s' % result) return False with open(path, 'wb') as fp: fp.write(result) return True
def kill_process(self, package_name): '''杀死进程 ''' try: self.run_driver_cmd('killProcess', package_name, root=self.adb.is_rooted()) except RuntimeError as e: logger.warn('killProcess error: %r' % e) if not self.adb.is_rooted(): return for _ in range(3): ret = self.adb.kill_process(package_name) if ret == None: return True elif ret == False: return False return False
def recv_data(data_len): data = '' while len(data) < data_len: try: buff = sock.recv(data_len - len(data)) if not buff: logger.warn('screenshot socket closed') return data += buff except socket.error as e: logger.warn('recv screenshot data error: %s' % e) return return data
def connect(self): self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # socket.setdefaulttimeout(120) for i in range(3): try: self._sock.connect((self._addr, self._port)) self._sock.settimeout(self._timeout) self._connect = True return True except Exception as e: logger.warn('connect (%s, %s) error: %s' % (self._addr, self._port, e)) if i == 2: return False # 连接失败 time.sleep(1)
def _wait_for_event(self, thread_id, timeout): '''等待事件 ''' if self._get_lock(thread_id).is_set(): return time0 = time.time() ret = self._get_lock(thread_id).wait(timeout) if ret: cost_time = time.time() - time0 logger.debug('thread %s wait %s S' % (thread_id, cost_time)) else: # 超时 logger.warn('thread %s wait for sync event timeout' % thread_id) self.resume_thread(thread_id) # 防止出错后一直等待 return ret
def create_socket(self): '''创建socket对象 ''' self._service_name = 'xweb_devtools_remote_%d' % self._pid try: sock = self._device.adb.create_tunnel(self._service_name, 'localabstract') if sock: return sock except: logger.warn('create socket tunnel %s failed' % self._service_name) self._driver.call_static_method( 'org.xwalk.core.internal.XWalkPreferencesInternal', 'setValue', self.hashcode, '', 'remote-debugging', True) return self.create_socket()
def clear_data(self, package_name): '''清理应用数据 :param package_name: 包名 :type package_name: string ''' if not self.adb.get_package_path(package_name): return True cmdline = 'pm clear %s' % package_name if self.adb.is_rooted(): return 'Success' in self.run_shell_cmd(cmdline, True) else: result = self.run_shell_cmd(cmdline) if 'Success' in result: return True logger.warn('clear %s data failed: %s' % (package_name, result)) return 'Success' in self.run_as(package_name, cmdline)
def _take_screen_shot(self, path, _format='png', quality=90): """ 使用android系统命令截图 # todo:使用android的java接口替换该方案 """ # result = self.adb.run_shell_cmd( # "sh %s/SpyHelper.sh takeScreenshot %s %s %s" % (qt4a_path, path, _format, quality), self.adb.is_rooted()) # if 'true' in result: # return True # logger.warn("Take screenshot by SpyHelper.sh failed: %s" % result) try: result = self.adb.run_shell_cmd("screencap -p", binary_output=True) return result except Exception as e: logger.warn("Take screenshot failed: %s" % traceback.format_exc(e)) return False
def take_screen_shot(self, path, quality=90): """截屏 """ if self.adb.get_sdk_version() >= 29: result = self._take_screen_shot(path) else: result = self.adb.run_shell_cmd("%s/screenshot capture -q %s" % (qt4a_path, quality), binary_output=True) # 为避免pull文件耗时,直接写到stdout if len(result) < 256: logger.warn("Take screenshot failed: %s" % result) return False with open(path, "wb") as fp: fp.write(result) return True
def create_tunnel(self, device_id, remote_addr): """创建与手机中服务端的连接通道 """ self._transport(device_id) self._sock.settimeout(2) try: self._send_command(remote_addr) except AdbError as e: if "closed" == e.args[0]: return "" raise except socket.timeout as e: logger.warn("create_tunnel timeout") return "" sock = self._sock self._sock = None return sock
def create_tunnel(self, device_id, remote_addr): '''创建与手机中服务端的连接通道 ''' self._transport(device_id) self._sock.settimeout(2) try: self._send_command(remote_addr) except AdbError as e: if 'closed' == e.args[0]: return '' raise except socket.timeout as e: logger.warn('create_tunnel timeout') return '' sock = self._sock self._sock = None return sock
def _run_server(self, server_name): """运行系统测试桩 """ if not self.adb.is_rooted(): try: self.adb.start_service( "%s/.service.HelperService" % server_name, {"serviceName": server_name}, ) except RuntimeError: logger.warn("start helper server failed") self.adb.start_activity( "%s/.activity.StartServiceActivity" % server_name, extra={"serviceName": "HelperService"}, wait=False, ) time.sleep(1) return self._server_opend() timeout = 10 time0 = time.time() while time.time() - time0 < timeout: try: ret = self.run_driver_cmd( "runServer", server_name, root=self.adb.is_rooted(), retry_count=1, timeout=10, ) logger.debug("Run server %s" % ret) if "service run success" in ret: # wait for parent process exit time.sleep(0.5) elif "service is running" in ret: pass elif "java.lang.UnsatisfiedLinkError" in ret: raise RuntimeError("启动系统测试桩进程失败:\n%s" % ret) except TimeoutError as e: logger.warn("Run server timeout: %s" % e) if self._server_opend(): return True time.sleep(1) return False
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 push_file(self, src_path, dst_path): '''向手机中拷贝文件 :param src_path: PC上的源路径 :type src_path: string :param dst_path: 手机上的目标路径 :type dst_path: string ''' if not os.path.exists(src_path): raise RuntimeError('File: %s not exist' % src_path) file_size = os.path.getsize(src_path) is_zip = False if file_size >= 5 * 1024 * 1024: is_zip = True zip_file_path = src_path + '.zip' zip_file = zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) if dst_path[-1] == '/': # filename not specified filename = os.path.split(src_path)[-1] else: filename = dst_path.split('/')[-1] zip_file.write(src_path, filename) zip_file.close() src_path = zip_file_path dst_path += '.zip' ret = self.adb.push_file(src_path, dst_path) if is_zip: os.remove(src_path) if not self._device_driver.unzip_file( dst_path, dst_path[:dst_path.rfind('/')]): logger.warn('unzip file %s failed' % dst_path) ret = self.adb.push_file(src_path[:-4], dst_path[:-4]) elif dst_path.startswith('/data/'): self.adb.chmod(dst_path[:-4], '744') self.delete_file(dst_path) dst_path = dst_path[:-4] try: self.run_shell_cmd('touch "%s"' % dst_path) # 修改文件修改时间 except: logger.exception('touch file %s error' % dst_path) return ret
def lock_keyguard(self): '''锁屏 ''' if self.is_keyguard_locked(): return True # 已经是锁屏状态 if self.adb.is_rooted(): self.set_screen_lock_enable(True) if self.adb.get_sdk_version() >= 16: # 发送电源键 self.send_key(KeyCode.KEYCODE_POWER) return True self._last_activity_before_lock = self._get_current_activity(True) # 保存锁屏前的Activity logger.debug('锁屏前Activity为:%r' % self._last_activity_before_lock) ret = self._lock_keyguard() if not ret: logger.warn('lock keyguard failed') self.wake_screen(False) time.sleep(1) self.wake_screen(True) return self.is_keyguard_locked() return True