def uninstall(self, bundle_id): '''卸载设备上的APP :param bundle_id: 要卸载的APP的Bundle_ID, 例如"tencent.com.sng.test.gn" :type bundle_id: str :returns: boolean -- if uninstall app successfully, False if not ''' dt = DT() return dt.uninstall(bundle_id, self.udid)
def install(self, ipa_path): '''在设备上安装制定路径下的APP :param ipa_path: 要安装的app的路径 :type ipa_path: str :returns: boolean -- True if successfully installed, False if not ''' dt = DT() return dt.install(ipa_path, self.udid)
def uninstall(self, bundle_id): '''通过udid指定设备卸载指定bundle_id的APP :param bundle_id: APP的bundle_id,例如"tencent.com.sng.test.gn" :type bundle_id: str :returns: boolean -- if uninstall app successfully, False if not ''' dt = DT() if dt.uninstall(bundle_id, self.udid): return True raise RuntimeError("uninstall error: %s" % dt.uninstall_error.encode('utf8'))
def reboot(self): '''重启手机 :param device_udid: 设备的udid :type device_udid: str ''' return DT().reboot(self.udid)
def capture_screen(self): '''截屏 :returns: str -- base64编码后截图数据 ''' try: base64_img = self.agent.capture_screen() if base64_img is None: base64_img = self.agent.execute(Command.SCREENSHOT)['value'].encode('ascii') else: base64_img = base64_img['value'] except: filename = '/tmp/%s.png' % uuid.uuid1() if self.agent.type == EnumDevice.Simulator: version = DT().get_xcode_version() if version >= '8': cmd = ['xcrun', 'simctl', 'io', self.udid,'screenshot',filename] p = subprocess.Popen(cmd,stdin=subprocess.PIPE,stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=False) p.communicate() retcode = p.poll() if retcode != 0: raise Exception("simulator screenshot error") else: raise Exception("simulator screenshot error: Xcode version less than 8 does not support") elif not mobiledevice.get_screenshot(self.udid, filename): raise Exception("mobiledevice screenshot error") with open(filename, "rb") as fd: base64_img = base64.b64encode(fd.read()) os.remove(filename) return base64_img
def list_files(self, bundle_id, file_path): '''列出手机上指定app中的文件或者目录 :param bundle_id: app的bundle id :type bundle_id: str :param file_path: sandbox的目录,例如: /Documents/ :type file_path: str ''' response = self.agent.execute(Command.QTA_SANDBOX_LIST, {'path':file_path}) if not isinstance(response, dict) or response['status'] == ErrorCode.QT4I_STUB_NOT_EXIST: file_list = DT().get_sandbox_path_files(self.udid, bundle_id, file_path) if not DT().is_simulator(self.udid): DT().close_sandbox_client() else: file_list = response['value'] return file_list
def capture_screen(self): '''截屏 :returns: str -- base64编码后截图数据 ''' try: filename = '/tmp/%s.png' % uuid.uuid1() command = self.create_json_command('uia.target.capture_screen', filename) self._instruments.exec_command(command) except: device = DT().get_device_by_udid(self.udid) if device["simulator"]: raise Exception("instruments screenshot error") if not mobiledevice.get_screenshot(self.udid, filename): raise Exception("mobiledevice screenshot error") start_time = time.time() while time.time() - start_time < 15: if os.path.isfile(filename): break time.sleep(0.05) else: raise Exception('capture_screen error') with open(filename, "rb") as fd: base64_img = base64.b64encode(fd.read()) return base64_img
def __init__(self, bundle_id='com.apple.WebKit.WebContent', udid=None): super(SimulatorProtocol, self).__init__(bundle_id, udid) if DT.compare_xcode_version('9.3') >= 0: self._sock = self._get_webinspector_socket_above_xcode93(udid) else: self._sock = self._get_webinspector_socket_below_xcode93(udid) self._sock.setblocking(False) self._sock.settimeout(3)
def execute(self, args): '''执行过程 ''' from qt4i.driver.tools.dt import DT import pprint for device in DT().get_devices(): pprint.pprint(device)
def start_simulator(self, udid=None): '''start iOS simulator of rpc server host :param udid: udid of iOS simulator :type udid: str :returns: boolean ''' return DT().start_simulator(udid)
def get_crash_log(self, procname): '''通过设备的udid的获取指定进程的最新的crash日志 :param proc_name: app的进程名,可通过xcode查看 :type proc_name: str :returns: str ''' return DT().get_crash_log(self.udid, procname)
def get_device_by_udid(self, udid): '''inquire device by udid :param udid: udid of device :type udid: str :returns: dict or None ''' return DT().get_device_by_udid(udid)
def is_sandbox_path_dir(self, bundle_id, file_path): '''判断一个sandbox路径是否是一个目录 :param bundle_id: 应用的bundle_id :type bundle_id: str :param file_path: 沙盒目录 :type file_path: str ''' return DT().is_sandbox_path_dir(self.udid, bundle_id, file_path)
def get_sandbox_file_content(self, bundle_id, file_path): '''获取sandbox中文本文件的内容 :param bundle_id: 应用的bundle_id :type bundle_id: str :param file_path: 沙盒目录 :type file_path: str ''' return DT().get_sandbox_file_content(self.udid, bundle_id, file_path)
def remove_files(self, bundle_id, file_path): '''删除手机上指定app中的文件或者目录 :param bundle_id: app的bundle id :type bundle_id: str :param file_path: 待删除的文件或者目录,例如: /Documents/test.log :type file_path: str ''' return DT().remove_files(self.udid, bundle_id, file_path)
def __init__(self, bundle_id, udid): self.app_bundle_id = bundle_id self.udid = udid self.logger = get_logger("driverserver_%s" % self.udid) self.seq = 0 # webkit调试命令的序号 self.conn_id = str(uuid.uuid1()).upper() self.sender_key = str(uuid.uuid4()).upper() self.retry = 5 # 初始化消息的重试次数 self.msgbuf = [] self.cached_pages = [] self.app_id = None self.page_id = None self.host_app_ids = [] self.target_id = None self._ios_version = DT().get_device_by_udid(self.udid)['ios'] self._is_target_domain = DT.compare_version(self._ios_version, '12.2') >= 0 self.is_target_wrapped = False
def get_sandbox_path_files(self, bundle_id, file_path): '''返回真机或者模拟器的沙盒路径 :param bundle_id: 应用的bundle_id :type bundle_id: str :param file_path: 沙盒目录 :type file_path: str ''' return DT().get_sandbox_path_files(self.udid, bundle_id, file_path)
def start(self): lockdown = LockdownClient(self.udid) ios_version = lockdown.allValues['ProductVersion'] if DT.compare_version(ios_version, '11.0') >= 0: # iOS11真机以上不支持数据分割 self._partial_supported = False self.web_inspector = lockdown.startService("com.apple.webinspector") self._sock = self.web_inspector.s self._sock.setblocking(False) self._sock.settimeout(5) self.init_inspector()
def start_app(self, bundle_id, app_params=None, env=None, trace_template=None, trace_output=None, retry=3, timeout=55): '''启动设备上的指定APP :param bundle_id: 要启动的APP的Bundle ID :type bundle_id: str :param app_params: APP 使用的参数 :type app_params: dict :param trace_template: 专项测试使用trace_template的路径 :type trace_template: str :param trace_output: 专项测试使用trace_output :type trace_output: str :param retry: 启动失败重试次数 :type retry: int :param timeout: 单次启动超时 (秒) :type timeout: int :returns: boolean -- True if start successfully , or raise exception ''' desired_caps = {} desired_caps['bundleId'] = bundle_id ios_version = DT().get_device_by_udid(self.udid)['ios'] if DT.compare_version(ios_version, '12.0') >= 0: app_list = [] for app in DT().list_apps_with_fbsimctl(self.udid, 'all'): app_list.extend(app.keys()) if bundle_id not in app_list: raise Exception("Failed to launch application, %s not exist" % bundle_id) if app_params: params_list = [] for k in app_params: params_list.append("-%s %s" % (k, app_params[k])) desired_caps['arguments'] = params_list for _ in range(retry): try: self.agent.execute(Command.START, desired_caps) break except XCTestAgentTimeoutException: self.restart_agent() else: raise XCTestAgentTimeoutException('Failed to start app: %s' % bundle_id) if env: self.agent.execute(Command.QTA_ALERT_RULES_UPDATE, env) return True
def get_page_id(self, bundle_id, title, url): ub = self.WKRDP_SEPARATOR.join([self.udid,bundle_id]) self.logger.info('current app sessions:%s' % WebInspector.WKRDP) if ub not in WebInspector.WKRDP: if not DT().is_simulator(self.udid): WebInspector.WKRDP[ub] = RealDeviceProtocol(bundle_id, self.udid) else: WebInspector.WKRDP[ub] = SimulatorProtocol(bundle_id, self.udid) self.logger.info('[%s] wkrdp session starting' % ub) WebInspector.WKRDP[ub].start() return WebInspector.WKRDP[ub].request_current_page_id(title, url)
def __init__(self, bundle_id, udid): super(RealDeviceProtocol, self).__init__(bundle_id, udid) self._partial_supported = True lockdown = LockdownClient(self.udid) ios_version = lockdown.allValues['ProductVersion'] if DT.compare_version(ios_version, '11.0') >= 0: # iOS11真机以上不支持数据分割 self._partial_supported = False self.web_inspector = lockdown.startService("com.apple.webinspector") self._sock = self.web_inspector.s self._sock.setblocking(False) self._sock.settimeout(5)
def remove_files(self, bundle_id, file_path): '''删除手机上指定app中的文件或者目录 :param bundle_id: app的bundle id :type bundle_id: str :param file_path: 待删除的文件或者目录,例如: /Documents/test.log :type file_path: str ''' response = self.agent.execute(Command.QTA_SANDBOX_REMOVE, {'path':file_path}) if not isinstance(response, dict) or response['status'] == ErrorCode.QT4I_STUB_NOT_EXIST: DT().remove_files(self.udid, bundle_id, file_path)
def push_file(self, bundle_id, localpath, remotepath): '''拷贝Mac本地文件到iOS设备sandbox的指定目录 :param bundle_id: app的bundle id :type bundle_id: str :param localpath: Mac上的文件路径 :type localpath: str :param remotepath: sandbox上的目录,例如:/Library/Caches/test/ :type remotepath: str :returns: boolean ''' return DT().push_file(self.udid, bundle_id, localpath, remotepath)
def install(self, file_path): '''通过udid指定真机安装IPA或模拟器安装APP(注意:真机所用IPA不能在模拟器上安装和使用,IPA与APP相互不兼容) :param file_path: IPA或APP安装包的路径(注意:当前Mac机必须能访问到该路径) :type file_path: str :returns: boolean -- True if successfully installed, False if not ''' try : dt = DT() dt.install(file_path, self.udid) return True except (AttributeError, lockdown.StartServiceError) : # 删除 ~/.pymobiledevice中的plist文件 dir_user = os.path.expanduser('~') dir_plist = os.path.join(dir_user,'.pymobiledevice') for root ,dirs ,files in os.walk(dir_plist): #@UnusedVariable for name in files : if name.startswith(self.udid): os.remove(os.path.join(root, name)) try: if not self._isagent_working() : raise RuntimeError('start agent error') dt.install(file_path, self.udid) return True except lockdown.NotTrustedError : rules = [ # 处理信任弹框 { 'message_text' : '要信任此电脑吗', 'button_text' : '^信任$|^Allow$|^不信任$' }, ] env = { 'rules_of_alert_auto_handle' : rules, 'flag_alert_auto_handled' : True } self.agent.execute(Command.QTA_ALERT_DISMISS, env) if dt.install(file_path, self.udid) : return True raise except : if dt.install_error == "" : import traceback dt.install_error = traceback.format_exc() raise RuntimeError("install error: %s" % dt.install_error.encode('utf8'))
def pull_file(self, bundle_id, remotepath, localpath='/tmp', is_dir=False, is_delete = False): '''拷贝手机中sandbox指定目录的文件到Mac本地 :param bundle_id: app的bundle id :type bundle_id: str :param remotepath: sandbox上的目录或者文件,例如:/Library/Caches/test/ :type remotepath: str :param localpath: 本地的目录 :type localpath: str :param device_udid: 设备的udid :type device_udid: str :param is_dir: remotepath是否为目录,默认为单个文件 :type is_dir: bool :param is_delete: 是否删除 :type is_delete: bool :returns: list or None ''' return DT().pull_file(self.udid, bundle_id, remotepath, localpath, is_dir, is_delete)
def get_syslog(self, watchtime, processName=None): '''获取手机系统日志 :parm watchtime : 观察时间 :type watchtime : int :parm processName : 手机服务名称(默认为全部查看) :type processName : str :returns: str ''' if not os.path.isdir(TMP_DIR_PATH): os.mkdir(TMP_DIR_PATH) logFile = os.path.join(TMP_DIR_PATH, "sys_%s.log" %self.udid) DT().get_syslog(watchtime, logFile, processName, self.udid) log = '' with open(logFile, 'rb') as fd: for line in fd: log += line return log
def list_simulators(self): '''list all iOS simulators of rpc server host :returns: list ''' return DT().get_simulators()
def list_real_devices(self): '''list all real iOS devices of rpc server host :returns: list ''' return DT().get_real_devices()
def list_devices(self): '''list all devices of rpc server host :return: list ''' return DT().get_devices()
return DT().start_simulator(udid) @rpc_method def get_device_by_udid(self, udid): '''inquire device by udid :param udid: udid of device :type udid: str :returns: dict or None ''' return DT().get_device_by_udid(udid) @rpc_method def echo(self): '''test xmlrpc server started :returns: boolean ''' return True @rpc_method def stop_all_agents(self): ''' stop all agents ''' XCUITestAgentManager.stop_all_agents() if __name__ == '__main__': print DT().get_devices()