def __init__(self, xmlrpc_uri, rpc_method, device_udid, result, timeout): self.xmlrpc_uri = xmlrpc_uri self.rpc_method = rpc_method self.result = result self.timeout = timeout self.rpc = RPCClientProxy(self.xmlrpc_uri, allow_none=True, encoding='UTF-8') self.rpc_working = RPCClientProxy(''.join( [self.xmlrpc_uri.split("device")[0], 'host/']), allow_none=True, encoding='UTF-8') socket.setdefaulttimeout(self.timeout + 3)
def _wait_for_started(self, timeout=60): begin_time = time.time() while time.time() - begin_time <= timeout: try: server = RPCClientProxy('/'.join( ["http://%s:%s" % (self._addr, self._port), 'host/']), allow_none=True, encoding=Encoding) if server.echo(): print('server is ready') return True except: print('server is not ready...') time.sleep(0.2) raise Exception('DeviceServer failed to start.')
def cleanup(self): '''清理环境 device中的stop_all_agents接口需要udid,请使用host中的对应接口 ''' from qt4i.driver.rpc import RPCClientProxy if self._udid: driver = RPCClientProxy('%s/device/%s/' % (self._driver_url, str(self._udid)), allow_none=True, encoding='UTF-8') driver.device.stop_all_agents() else: host = RPCClientProxy('%s/host/' % (self._driver_url), allow_none=True, encoding='UTF-8') host.stop_all_agents()
def get_devices_from_driver(driver_ip, driver_port=12306): driver = RPCClientProxy('http://%s:%d/host/' % (driver_ip, driver_port), allow_none=True, encoding=Encoding) driver_devices = driver.list_devices() devices = [] index = 0 for dev in driver_devices: dev['id'] = dev['udid'] dev['host'] = driver_ip dev['port'] = driver_port dev['is_simulator'] = dev['simulator'] dev.pop('simulator', None) dev['version'] = dev['ios'] dev.pop('ios', None) if dev['is_simulator']: devices.append(dev) else: devices.insert(index, dev) index += 1 return devices
class Rpc(object): def __init__(self, xmlrpc_uri, rpc_method, device_udid, result, timeout): self.xmlrpc_uri = xmlrpc_uri self.rpc_method = rpc_method self.result = result self.timeout = timeout self.rpc = RPCClientProxy(self.xmlrpc_uri, allow_none=True, encoding='UTF-8') self.rpc_working = RPCClientProxy(''.join( [self.xmlrpc_uri.split("device")[0], 'host/']), allow_none=True, encoding='UTF-8') socket.setdefaulttimeout(self.timeout + 3) def rpc_is_working(self): try: return self.rpc_working.echo() except: return False def send_result(self): if self.rpc_is_working(): self.rpc.ins.send_result(self.result) def send_result_and_get_next(self): # //<-- return : {"id": 1, "result": "JSON", "error": null} # //--> command : {"id": 1, "method": "echo", "params": ["Hello JSON"]} if self.rpc_is_working(): return self.rpc.ins.send_result_and_get_next( self.result, self.timeout) return '{"id": -1, "method": "release"}' def rpc_exec(self): if self.rpc_method == 'send_result_and_get_next': return self.send_result_and_get_next() if self.rpc_method == 'send_result': self.send_result()
def __init__(self, attrs={}, devicemanager=None): '''Device构造函数 :param attrs: 设备UDID字符串|设备属性字典 keys: udid - 设备UDID host - 设备主机ip is_simulator - 是否为模拟器 :type attrs: str|dict :param devicemanager: 设备管理类 :type devicemanager: DeviceManager ''' cond = {} if isinstance(attrs, six.string_types): cond['udid'] = attrs elif isinstance(attrs, dict): cond = attrs.copy() else: raise Exception('Device attributes type error: %s' % type(attrs)) ct = None if devicemanager: self._device_resource = devicemanager.acquire_device(cond) else: ct = context.current_testcase() if ct is None: self._test_resources = TestResourceManager( LocalResourceManagerBackend()).create_session() else: self._test_resources = ct.test_resources self._device_resource = self._test_resources.acquire_resource( "ios", condition=cond) if self._device_resource is None: raise Exception('无可用的真机和模拟器: %s' % str(cond)) props = self._device_resource if 'properties' in self._device_resource: # online mode devprops = self._device_resource['properties'] props = { p['name'].encode(Encoding): p['value'].encode(Encoding) for p in devprops } props['id'] = self._device_resource['id'] if 'csst_uri' not in props or props['csst_uri'] == 'None': props['csst_uri'] = None self._device_resource = DeviceResource(props['host'], int( props['port']), props['udid'], props['is_simulator'], props['name'], props['version'], props['csst_uri'], props['id']) self._base_url = self._device_resource.driver_url self._ws_uri = self._device_resource.ws_uri self._host = RPCClientProxy('/'.join([self._base_url, 'host/']), self._ws_uri, allow_none=True, encoding=Encoding) self._device_udid = self._device_resource.udid self._device_name = self._device_resource.name self._device_ios = self._device_resource.version if isinstance(self._device_resource.is_simulator, bool): self._device_simulator = self._device_resource.is_simulator else: self._device_simulator = self._device_resource.is_simulator == str( True) if self._device_simulator: self._host.start_simulator(self._device_udid) self._app_started = False Device.Devices.append(self) url = '/'.join([self._base_url, 'device', '%s/' % self._device_udid]) self._driver = RPCClientProxy(url, self._ws_uri, allow_none=True, encoding=Encoding) self._keyboard = Keyboard(self) logger.info('[%s] Device - Connect - %s - %s (%s)' % (datetime.datetime.fromtimestamp( time.time()), self.name, self.udid, self.ios_version)) # 申请设备成功后,对弹窗进行处理 rule = settings.get('QT4I_ALERT_DISMISS', DEFAULT_ALERT_RULE) if rule: try: self._dismiss_alert(rule) except: logger.exception('dismiss alert %s' % rule)
class Device(object): '''iOS设备基类(包含基于设备的UI操作接口) ''' Devices = [] @classmethod def release_all(cls): devices = cls.Devices[:] for device in devices: device.release() del cls.Devices[:] @classmethod def cleanup_all_log(cls): for device in cls.Devices: device.cleanup_log() def __init__(self, attrs={}, devicemanager=None): '''Device构造函数 :param attrs: 设备UDID字符串|设备属性字典 keys: udid - 设备UDID host - 设备主机ip is_simulator - 是否为模拟器 :type attrs: str|dict :param devicemanager: 设备管理类 :type devicemanager: DeviceManager ''' cond = {} if isinstance(attrs, six.string_types): cond['udid'] = attrs elif isinstance(attrs, dict): cond = attrs.copy() else: raise Exception('Device attributes type error: %s' % type(attrs)) ct = None if devicemanager: self._device_resource = devicemanager.acquire_device(cond) else: ct = context.current_testcase() if ct is None: self._test_resources = TestResourceManager( LocalResourceManagerBackend()).create_session() else: self._test_resources = ct.test_resources self._device_resource = self._test_resources.acquire_resource( "ios", condition=cond) if self._device_resource is None: raise Exception('无可用的真机和模拟器: %s' % str(cond)) props = self._device_resource if 'properties' in self._device_resource: # online mode devprops = self._device_resource['properties'] props = { p['name'].encode(Encoding): p['value'].encode(Encoding) for p in devprops } props['id'] = self._device_resource['id'] if 'csst_uri' not in props or props['csst_uri'] == 'None': props['csst_uri'] = None self._device_resource = DeviceResource(props['host'], int( props['port']), props['udid'], props['is_simulator'], props['name'], props['version'], props['csst_uri'], props['id']) self._base_url = self._device_resource.driver_url self._ws_uri = self._device_resource.ws_uri self._host = RPCClientProxy('/'.join([self._base_url, 'host/']), self._ws_uri, allow_none=True, encoding=Encoding) self._device_udid = self._device_resource.udid self._device_name = self._device_resource.name self._device_ios = self._device_resource.version if isinstance(self._device_resource.is_simulator, bool): self._device_simulator = self._device_resource.is_simulator else: self._device_simulator = self._device_resource.is_simulator == str( True) if self._device_simulator: self._host.start_simulator(self._device_udid) self._app_started = False Device.Devices.append(self) url = '/'.join([self._base_url, 'device', '%s/' % self._device_udid]) self._driver = RPCClientProxy(url, self._ws_uri, allow_none=True, encoding=Encoding) self._keyboard = Keyboard(self) logger.info('[%s] Device - Connect - %s - %s (%s)' % (datetime.datetime.fromtimestamp( time.time()), self.name, self.udid, self.ios_version)) # 申请设备成功后,对弹窗进行处理 rule = settings.get('QT4I_ALERT_DISMISS', DEFAULT_ALERT_RULE) if rule: try: self._dismiss_alert(rule) except: logger.exception('dismiss alert %s' % rule) @property def driver(self): '''设备所在的driver :rtype: RPCClientProxy ''' return self._driver @property def udid(self): '''设备的udid :rtype: str ''' result = self._device_udid if PY2 and isinstance(result, six.text_type): return result.encode(Encoding) return result @property def name(self): '''设备名 :rtype: str ''' result = self._device_name if PY2 and isinstance(result, six.text_type): return result.encode(Encoding) return result @property def ios_version(self): '''iOS版本 :rtype: str ''' result = self._device_ios if PY2 and isinstance(result, six.text_type): return result.encode(Encoding) return result @property def simulator(self): '''是否模拟器 :rtype: boolean ''' return self._device_simulator @property def rect(self): '''屏幕大小 :rtype: Rectangle ''' rect = self._driver.device.get_rect() origin = rect['origin'] size = rect['size'] return Rectangle(origin['x'], origin['y'], origin['x'] + size['width'], origin['y'] + size['height']) @property def keyboard(self): '''获取键盘 ''' return self._keyboard def start_app(self, bundle_id, app_params, env, trace_template=None, trace_output=None, retry=5, timeout=55): '''启动APP :param bundle_id: APP Bundle ID :type bundle_id: str :param app_params: app启动参数 :type app_params: dict :param env: app的环境变量 :type env: dict :param trace_template: 专项使用trace_template路径,或已配置的项 :type trace_template: str :param trace_output: 专项使用trace_output路径 :type trace_output: str :param retry: 重试次数(建议大于等于2次) :type retry: int :param timeout: 单次启动超时(秒) :type timeout: int :param instruments_timeout: 闲置超时(秒) :type instruments_timeout: int :rtype: boolean ''' if not self._app_started: if self._driver.device.start_app(bundle_id, app_params, env, trace_template, trace_output, retry, timeout): self._app_started = True self.bundle_id = bundle_id elif 'app_name' in app_params and self.get_foreground_app_name( ) != app_params['app_name']: try: elements = self._driver.element.find_elements( app_params['app_name'], 2, 0.05, 'id') element_id = elements['elements'][-1]['element'] self._driver.element.click(element_id) self.bundle_id = bundle_id self._app_started = True except: raise Exception('未找到名为[%s]的App' % app_params['app_name']) return self._app_started == True def stop_app(self): '''终止APP :rtype: boolean ''' if self._app_started: if self._driver.device.stop_app(self.bundle_id): self._app_started = False return self._app_started == False def release(self): '''释放设备 ''' try: self.stop_app() self._driver.web.release() if hasattr(self, 'nlc_flag') and self.nlc_flag: #恢复网络设置 from qt4i.app import NLCType self.switch_network(5, NLCType.NONE) if hasattr(self, 'wifi') and self.wifi: #关闭host代理 self.reset_host_proxy() except Exception: traceback.print_exc() finally: try: self._test_resources.release_resource( "ios", self._device_resource.resource_id) except: pass Device.Devices.remove(self) logger.info('[%s] Device - Release - %s - %s (%s)' % (datetime.datetime.fromtimestamp(time.time()), self.name, self.udid, self.ios_version)) def get_foreground_app_name(self): '''获取前台app的名称 ''' return self._driver.device.get_foreground_app_name() def get_foreground_app_pid(self): '''获取前台app的PID ''' return self._driver.device.get_foreground_app_pid() def _check_app_started(self): '''检测APP是否已启动 :raises: Exception ''' if not self._app_started: raise Exception('app not started') def screenshot(self, image_path=None): '''截屏,返回元组:截屏是否成功,图片保存路径 :param image_path: 截屏图片的存放路径 :type image_path: str :rtype: tuple (boolean, str) ''' try: base64_img = self._driver.device.capture_screen() if not image_path: image_path = os.path.join( QT4i_LOGS_PATH, "p%s_%s.png" % (os.getpid(), uuid.uuid1())) with open(os.path.abspath(image_path), "wb") as fd: if PY2: fd.write(base64.decodestring(base64_img)) else: fd.write(base64.decodebytes(base64_img.encode('utf-8'))) return os.path.isfile(image_path), image_path except: logger.error('screenshot failed: %s' % traceback.format_exc()) return False, "" def print_uitree(self, need_back=False): '''打印界面树 :param need_back: 是否需要返回UI Tree :type need_back: boolean :return: 控件树 :rtype: dict or None ''' self._check_app_started() _ui_tree = self._driver.device.get_element_tree() def _print(_tree, _spaces='', _indent='|---'): _line = _spaces + '{ ' + ', '.join([ 'classname: "%s"' % _tree['classname'], 'label: %s' % ('"%s"' % _tree['label'] if _tree['label'] else 'null'), 'name: %s' % ('"%s"' % _tree['name'] if _tree['name'] else 'null'), 'value: %s' % ('"%s"' % _tree['value'] if _tree['value'] else 'null'), 'visible: %s' % ('true' if _tree['visible'] else 'false'), 'enabled: %s' % ('true' if _tree['enabled'] else 'false'), 'rect: %s' % _tree['rect'] if 'rect' in _tree else '' ]) + ' }' print(_line) for _child in _tree['children']: _print(_child, _spaces + _indent, _indent) _print(_ui_tree) if need_back: return _ui_tree def click(self, x=0.5, y=0.5): '''点击屏幕 :param x: 横向坐标(从左向右,屏幕百分比) :type x: float :param y: 纵向坐标(从上向下,屏幕百分比) :type y: float ''' self._check_app_started() self._driver.device.click(x, y) def click2(self, element): '''基于控件坐标点击屏幕(用于直接点击控件无效的场景,尽量少用) :param element: 控件对象 :type element: Element ''' self._check_app_started() element_rect = element.rect device_rect = self.rect x = (element_rect.left + element_rect.right) / (2.0 * device_rect.width) y = (element_rect.top + element_rect.bottom) / (2.0 * device_rect.height) self._driver.device.click(x, y) def long_click(self, x, y, duration=3): '''长按屏幕 :param x: 横向坐标(从左向右,屏幕百分比) :type x: float :param y: 纵向坐标(从上向下,屏幕百分比) :type y: float :param duration: 持续时间(秒) :type duration: float ''' self._check_app_started() self._driver.device.long_click(x, y, duration) def double_click(self, x, y): '''双击屏幕 :param x: 横向坐标(从左向右计算,屏幕百分比) :type x: float :param y: 纵向坐标(从上向下计算,屏幕百分比) :type y: float ''' self._check_app_started() self._driver.device.double_click(x, y) def _drag_from_to_for_duration(self, from_point, to_point, duration=0.5, repeat=1, interval=0): '''全屏拖拽 :attention: 如有过场动画,需要等待动画完毕 :param from_point : 起始坐标偏移百分比 :type from_point : dict : { x: 0.5, y: 0.8 } :param to_point : 结束坐标偏移百分比 :type to_point : dict : { x: 0.5, y: 0.1 } :param duration : 持续时间(秒) :type duration : float :param repeat : 重复该操作 :type repeat : int :param interval : 重复该操作的间隙时间(秒) :type interval : float ''' self._check_app_started() raise NotImplementedError def drag(self, from_x=0.9, from_y=0.5, to_x=0.1, to_y=0.5, duration=0.5): '''回避屏幕边缘,全屏拖拽(默认在屏幕中央从右向左拖拽) :param from_x: 起点 x偏移百分比(从左至右为0.0至1.0) :type from_x: float :param from_y: 起点 y偏移百分比(从上至下为0.0至1.0) :type from_y: float :param to_x: 终点 x偏移百分比(从左至右为0.0至1.0) :type to_x: float :param to_y: 终点 y偏移百分比(从上至下为0.0至1.0) :type to_y: float :param duration: 持续时间(秒) :type duration: float ''' self._check_app_started() self._driver.device.drag(from_x, from_y, to_x, to_y, duration) def drag2(self, direct=EnumDirect.Left): '''回避屏幕边缘,全屏在屏幕中央拖拽 :param direct: 拖拽的方向 :type direct: EnumDirect.Left|EnumDirect.Right|EnumDirect.Up|EnumDirect.Down ''' self._check_app_started() if direct == EnumDirect.Left: self._driver.device.drag(0.5, 0.5, 0.1, 0.5, 0.5) if direct == EnumDirect.Right: self._driver.device.drag(0.5, 0.5, 0.9, 0.5, 0.5) if direct == EnumDirect.Up: self._driver.device.drag(0.5, 0.5, 0.5, 0.1, 0.5) if direct == EnumDirect.Down: self._driver.device.drag(0.5, 0.5, 0.5, 0.9, 0.5) def flick(self, from_x=0.9, from_y=0.5, to_x=0.1, to_y=0.5): '''回避屏幕边缘,全屏滑动/拂去(默认从右向左滑动/拂去) 该接口比drag的滑动速度快,如果滚动距离大,建议用此接口 :param from_x: 起点 x偏移百分比(从左至右为0.0至1.0) :type from_x: float :param from_y: 起点 y偏移百分比(从上至下为0.0至1.0) :type from_y: float :param to_x: 终点 x偏移百分比(从左至右为0.0至1.0) :type to_x: float :param to_y: 终点 y偏移百分比(从上至下为0.0至1.0) :type to_y: float ''' self._check_app_started() self._driver.device.drag(from_x, from_y, to_x, to_y, 0) def flick2(self, direct=EnumDirect.Left): '''回避屏幕边缘,全屏在屏幕中央滑动/拂去 :param direct: 滑动/拂去的方向 :type direct: EnumDirect.Left|EnumDirect.Right|EnumDirect.Up|EnumDirect.Down ''' self._check_app_started() if direct == EnumDirect.Left: self._driver.device.drag(0.5, 0.5, 0.1, 0.5, 0) if direct == EnumDirect.Right: self._driver.device.drag(0.4, 0.5, 0.9, 0.5, 0) if direct == EnumDirect.Up: self._driver.device.drag(0.5, 0.5, 0.5, 0.1, 0) if direct == EnumDirect.Down: self._driver.device.drag(0.5, 0.5, 0.5, 0.9, 0) def flick3(self, from_x=0.5, from_y=0.8, to_x=0.5, to_y=0.2, repeat=1, interval=0.5, velocity=1000): '''全屏连续滑动(默认从下向滑动) 该接口比flick2的滑动速度快,适用于性能测试 :param from_x: 起点 x偏移百分比(从左至右为0.0至1.0) :type from_x: float :param from_y: 起点 y偏移百分比(从上至下为0.0至1.0) :type from_y: float :param to_x: 终点 x偏移百分比(从左至右为0.0至1.0) :type to_x: float :param to_y: 终点 y偏移百分比(从上至下为0.0至1.0) :type to_y: float :param repeat: 滑动的次数 :type repeat: int :param interval: 滑动的间隔时间(秒) :type interval: double :param velocity: 滑动的速度 :type velocity: double ''' self._check_app_started() self._driver.device.drag(from_x, from_y, to_x, to_y, 0, repeat, interval, velocity) def deactivate_app_for_duration(self, seconds=3): '''将App置于后台一定时间 :param seconds: 秒,若为-1,则模拟按Home键的效果,也即app切到后台后必须点击app才能再次唤起 :type seconds: int :return: boolean ''' return self._driver.device.background_app(seconds) def install(self, app_path): '''安装应用程序 :param app_path: ipa或app安装包的路径(注意:真机和模拟器的安装包互不兼容) :type app_path: str :rtype: boolean ''' begin_time = time.time() result = self._driver.device.install(app_path) count_time = time.time() - begin_time logger.info("安装被测应用耗时:%.3fs" % count_time) return result def uninstall(self, bundle_id): '''卸载应用程序 :param bundle_id: APP的bundle_id,例如:com.tencent.qq.dailybuild.test :type bundle_id: str :rtype: boolean ''' return self._driver.device.uninstall(bundle_id) def get_crash_log(self, procname): '''获取指定进程的最新的crash日志 :param proc_name: app的进程名,可通过xcode查看 :type proc_name: str :return: crash日志路径 :rtype: string or None ''' crash_log = self._driver.device.get_crash_log(procname) if crash_log: crash_log_path = os.path.join( QT4i_LOGS_PATH, "%s_%s.crash" % (procname, uuid.uuid1())) with open(os.path.abspath(crash_log_path), "wb") as fd: fd.write(crash_log) return crash_log_path else: return None def pull_file(self, bundle_id, remotepath, localpath=None, is_dir=False, is_delete=True): '''拷贝手机中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 is_dir: remotepath是否为目录,默认为单个文件 :type is_dir: boolean :return: 拷贝到本地的文件列表 :rtype: list or None ''' if localpath is None: localpath = QT4i_LOGS_PATH filepaths = [] if self._device_resource.host != DEFAULT_ADDR: files = self._driver.device.pull_file(bundle_id, remotepath, '/tmp', is_dir, is_delete) for f in files: filepath = os.path.join(localpath, os.path.basename(f)) with open(filepath, "wb") as fd: data = self._host.pull_file_data(f, 0) index = 1 while data is not None: if PY2: fd.write(base64.decodestring(data)) else: fd.write(base64.decodebytes(data.encode('utf-8'))) data = self._host.pull_file_data(f, index) index += 1 filepaths.append(filepath) else: filepaths = self._driver.device.pull_file(bundle_id, remotepath, localpath, is_dir, is_delete) return filepaths copy_to_local = pull_file def push_file(self, bundle_id, localpath, remotepath): '''拷贝Mac本地文件到手机中sandbox的指定目录地 :param bundle_id: app的bundle id :type bundle_id: str :param localpath: 文件路径,支持本地文件路径和http路径 :type localpath: str :param remotepath: iPhone上的目录或者文件,例如:/Documents/ :type remotepath: str :rtype: boolean ''' if localpath.startswith('http'): return self._driver.device.download_file_and_push( bundle_id, localpath, remotepath) else: if self._device_resource.host != DEFAULT_ADDR: with open(localpath, "rb") as fd: data = base64.b64encode(fd.read()) if PY3: data = data.decode('ascii') filepath = os.path.join('/tmp', os.path.basename(localpath)) self._host.push_file_data(data, filepath) localpath = filepath return self._driver.device.push_file(bundle_id, localpath, remotepath) def list_files(self, bundle_id, file_path): '''列出手机上app中的文件或者目录 :param bundle_id: app的bundle id :type bundle_id: str :param file_path: sandbox上的目录或者文件,例如:/Library/Caches/test/ :type file_path: str ''' return self._driver.device.list_files(bundle_id, file_path) def download_file(self, file_url, dst_path): '''从网上下载指定文件到本地 :param file_url: 文件的url路径,支持http和https路径, 需要对app插桩 :type file_url: str :param dst_path: 文件在手机上的存储路径,例如:/Documents :type dst_path: str ''' method = 'createFile:withPath:size:' params = [file_url, dst_path, 1] return self.call_qt4i_stub(method, params) def remove_files(self, bundle_id, file_path): '''删除手机上app中的文件或者目录(主要用于app的日志或者缓存的清理) :param bundle_id: app的bundle id :type bundle_id: str :param file_path: sandbox上的目录或者文件,例如:/Library/Caches/test/ :type file_path: str ''' self._driver.device.remove_files(bundle_id, file_path) remove_file = remove_files def reboot(self): '''重启手机 ''' self._driver.device.reboot() def get_driver_log(self, start_time=None): '''获取driver日志 :param start_time 用例执行的开始时间 :type str try-catch 说明:为了兼容client以及server端,对取失败用例日志的操作(后续接口更新可删除) ''' try: return self._driver.device.get_driver_log(start_time) except: return self._driver.device.get_driver_log() def get_log(self, start_time=None): '''获取交互日志 :param start_time 用例执行的开始时间 :type str try-catch 说明:为了兼容client以及server端,对取失败用例日志的操作(后续接口更新可删除) ''' try: return self._driver.device.get_log(start_time) except: return self._driver.device.get_log() def get_syslog(self, watchtime, process_name=None): '''获取手机系统日志 ''' return self._driver.device.get_syslog(watchtime, process_name) def cleanup_log(self): '''清理交互日志 ''' self._driver.device.cleanup_log() logger.info('[%s] Device - Clean Up Logger - %s - %s (%s)' % (datetime.datetime.fromtimestamp( time.time()), self.name, self.udid, self.ios_version)) def get_app_list(self, app_type="user"): '''获取设备上的app列表 :param app_type: app的类型(user/system/all) :type app_type: str :return: list :rtype: app列表,例如: [{'com.tencent.demo': 'Demo'}] ''' return self._driver.device.get_app_list(app_type) def lock(self): '''锁定设备(灭屏) ''' self._driver.device.lock() def unlock(self): '''解锁设备 ''' self._driver.device.unlock() def _volume(self, cmd): '''音量调节 :param cmd: 音量调节命令 :type str : 'up'|'down'|'silent' ''' self._driver.device.volume(cmd) def _screen_direction(self, direct): '''设置屏幕方向 :param direct: 屏幕调节命令 :type str : 'up'|'down'|'left'|'right' ''' self._driver.device.screen_direction(direct) def _siri(self, cmd): '''siri交互 :param cmd: 与siri交互文本 :type str : 譬如打开一个app,直接cmd='app名' ''' self._driver.device.siri(cmd) def _dismiss_alert(self, rule): '''弹窗处理 :param rule: 弹窗处理 :type list: rules ''' self._driver.device.dismiss_alert(rule) def switch_network(self, network_type, nlc_type): '''实现网络切换 :param network_type: 网络类型,例如: 0:无WIFI无xG 1:无WIFi有xG 2:有WIFI无xG 3:有WIFI有xG 4:飞行模式 :type network_type: int :param nlc_type: 模拟弱网络类型 :type nlc_type: NLCType ''' self.nlc_flag = True from qt4i.app import Preferences if self._app_started: self._active_app_name = self.get_foreground_app_name() self.deactivate_app_for_duration(-1) else: self._active_app_name = None pre = Preferences(self) pre.switch_network(network_type, nlc_type) if self._active_app_name: self.deactivate_app_for_duration(-1) self.start_app('', {'app_name': self._active_app_name}, None) else: self.stop_app() def set_host_proxy(self, server, port, wifi): '''设置host代理 :param server: 服务器名 :type server: str :param port: 端口号 :type port: int :param wifi: wifi名 :type wifi: str ''' self.wifi = wifi from qt4i.app import Preferences if self._app_started: self._active_app_name = self.get_foreground_app_name() self.deactivate_app_for_duration(-1) else: self._active_app_name = None pre = Preferences(self) pre.set_host_proxy(server, port, wifi) if self._active_app_name: self.deactivate_app_for_duration(-1) self.start_app('', {'app_name': self._active_app_name}, None) else: self.stop_app() def reset_host_proxy(self): '''关闭host代理 ''' from qt4i.app import Preferences pre = Preferences(self) pre.reset_host_proxy() def upload_photo(self, photo_path, album_name, cleared=True): '''上传照片到系统相册 :param photo_path: 本地照片路径 :type photo_path: str :param album_name: 系统相册名 :type album_name: str :param cleared: 是否清空已有的同名相册 :type cleared: boolean :rtype: boolean ''' with open(photo_path, 'rb') as fd: img_data = base64.b64encode(fd.read()) return self._driver.device.upload_photo(img_data, album_name, cleared) def call_qt4i_stub(self, method, params, clazz=None): '''QT4i Stub通用接口 :param method: 函数名 :type method: str :param params: 函数参数 :type params: list :param clazz: 类名 :type clazz: str :return: 插桩接口返回值 :rtype: str ''' return self._driver.device.call_qt4i_stub(method, params, clazz) def get_device_detail(self): '''获取设备型号,颜色,内存大小等详细信息 ''' return self._driver.device.get_device_detail() def get_icon_badge(self, app_name): '''获取设备中app消息未读数 ''' if self._app_started: self._active_app_name = self.get_foreground_app_name() self.deactivate_app_for_duration(-1) else: self._active_app_name = None elements = self._driver.element.find_elements(app_name, 2, 0.05, 'id') element_id = elements['elements'][-1]['element'] value = self._driver.element.get_element_attr(element_id, 'value') if value == None: value = 0 else: value = value.split(' ') value = value[0] if self._active_app_name: self.deactivate_app_for_duration(-1) self.start_app('', {'app_name': self._active_app_name}, None) else: self.stop_app() return value
def __init__(self, **args): # self.logger = Logger(os.path.join('/tmp', 'instruments.log')) self.logger = logger.get_logger("instruments") self.args = args if self.args.get('device_udid') is None: raise Exception('device_udid is None.') if self.args.get('device_udid') is None: raise Exception('bundle_id is None.') self.device_udid = self.args['device_udid'] self.environment = { 'device_udid': self.args['device_udid'], 'device_simulator': self.args.get('device_simulator', False), 'bundle_id': self.args['bundle_id'], 'trace_template': self.args.get('trace_template'), 'trace_output': self.args.get('trace_output'), 'uia_script': os.path.abspath( self.args.get( 'uia_script', os.path.join(self.CurrentDirectoryPath, '_bootstrap.js'))), 'uia_results_path': os.path.join('/tmp', '_uiaresults'), 'screen_shot_path': os.path.join('/tmp', '_screenshot'), 'cwd': self.CurrentDirectoryPath, 'timeout': self.args.get('timeout', 20 * 60), 'xmlrpc_uri': self.args.get( 'xmlrpc_uri', '/'.join([ 'http://0.0.0.0:12306', 'device', '%s' % self.device_udid ])), 'cmd_fetch_delegate': os.path.join(self.CurrentDirectoryPath, '_cmd_fetch_delegate.py'), 'cmd_fetch_delegate_timeout': self.args.get('cmd_fetch_delegate_timeout', 10), 'ignore_cmd_error': self.args.get('ignore_cmd_error', True), 'python_path': self.PythonPath } self.environment.update( eval(urllib.unquote(self.args.get('environment')))) self.args.update(self.environment) self.args.update({ 'started_callback': self.__started_callback__, 'stdout_line_callback': self.__stdout_line_callback__, 'stderr_line_callback': self.__stderr_line_callback__, 'return_code_callback': self.__return_code_callback__ }) self._clean_instrumentscli_n_trace( self.environment.get('trace_output')) self._renew_output_path() self._update_environment_file() self.out = list() self.err = list() self.rpc = RPCClientProxy(self.environment.get('xmlrpc_uri'), allow_none=True) self.instruments = Xcode().start_instruments(**self.args) self.logger.info(self.instruments.command) self.instruments_uia_result_path = None self.logger.info("Instruments timeout %d" % self.environment.get('timeout')) self.instruments_timeout_daemon = ThreadTimer( timeout=self.environment.get('timeout'), callback=self.__timeout_kill_instruments__) self.instruments_pid = self.instruments.pid self.instruments_trace_complete = False self.instruments.wait()