def __init__(self, device, localForwardPort=None): self._device = device self._localForwardPort = localForwardPort if localForwardPort is not None \ else WxWebSocketDebugUrlFetcher.DEFAULT_LOCAL_FORWARD_PORT self._webSocketDebugUrl = None # 通过dic来管理页面 self.pageUrlDict = bidict.bidict() self.logger = Log().getLogger() self.mode = MODE_NORMAL
def __init__(self, device, localForwardPort=None): self._device = device self._localForwardPort = localForwardPort if localForwardPort is not None \ else QQWebSocketDebugUrlFetcher.DEFAULT_LOCAL_FORWARD_PORT self._webSocketDebugUrl = None self.type = '' self.pageUrlDict = bidict.bidict() self.logger = Log().getLogger()
def __init__(self, d,device=None): self._device = device self._urlFetcher = None self._webSocketDataTransfer = None self._vmShutdownHandler = VMShutDownHandler() self._networkHandler = None self._executor = SingleThreadExecutor() self.logger = Log().getLogger() self._pageOperator = WxPageOperator() self._hasInit = False self.d = d self.html = None
def _fetchWebSocketDebugUrl(self, localPort): import json import urllib2 errorMsg = None resultUrl = None localUrl = "http://localhost:%s/json" % localPort # 去掉代理 urllib2.getproxies = lambda: {} try: nums = 0 while None == resultUrl and nums < 8: response = urllib2.urlopen(localUrl) responseJson = json.loads(response.read()) if len(responseJson) != 0: for responseItem in responseJson: descriptionDict = json.loads(responseItem['description']) if descriptionDict['empty'] == False: resultUrl = responseItem["webSocketDebuggerUrl"] return resultUrl else: nums = nums + 1 Log().getLogger().debug('retry fetch num ---> ' + str(nums)) time.sleep(1) except Exception: errorMsg = ErrorMsgManager().errorCodeToString(ERROR_CODE_CONFIG_PROXY) if not errorMsg: errorMsg = ErrorMsgManager().errorCodeToString(ERROR_CODE_NOT_ENTER_H5) raise RuntimeError(errorMsg)
def __init__(self, webSocketDataTransfer, executor, driver): super(ShortLiveWebSocket, self).__init__() self._webSocketDataTransfer = webSocketDataTransfer self._executor = executor self._webSocketDataTransfer.registerCallback(self) self._readWriteSyncEvent = threading.Event() self._connectSyncEvent = threading.Event() self._id = AtomicInteger() self._currentRequest = None self._quit = False self._retryEvent = threading.Event() self.driver = driver self.logger = Log().getLogger()
def _fetchWebSocketDebugUrl(self, localPort): self.logger.debug('') import json import urllib2 errorMsg = None resultUrl = None localUrl = "http://localhost:%s/json" % localPort # 去掉代理 urllib2.getproxies = lambda: {} try: nums = 0 while None == resultUrl and nums < 8: response = urllib2.urlopen(localUrl) responseJson = json.loads(response.read()) if len(responseJson) != 0: resultUrl = self._handleAndReturnWebSocketDebugUrl( responseJson) self.logger.debug('websocket --> ' + resultUrl) return resultUrl else: nums = nums + 1 Log().getLogger().debug('retry fetch num ---> ' + str(nums)) time.sleep(1) except Exception: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_CONFIG_PROXY) if not errorMsg: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_NOT_ENTER_H5) raise RuntimeError(errorMsg)
def initDriver(self): if self._hasInit: return self._executor = SingleThreadExecutor() self._vmShutdownHandler.registerToVM() UncaughtExceptionHandler.init() UncaughtExceptionHandler.registerUncaughtExceptionCallback(self._onUncaughtException) self._selectDevice() self._urlFetcher = H5WebSocketDebugUrlFetcher(device=self._device,app=self._app) url = self._urlFetcher.fetchWebSocketDebugUrl() self._webSocketDataTransfer = WebSocketDataTransfer(url=url) self._networkHandler = ShortLiveWebSocket(self._webSocketDataTransfer, self._executor, self) self._networkHandler.connect() self._executor.put(self._networkHandler.receive) self.logger = Log().getLogger() self.initPageDisplayData() self._hasInit = True self.logger.info('url ----> ' + url)
def _getProcessInfo(cmd, device): nums = 0 retry = True stdout = '' while (retry and nums < 8): try: stdout, stdError = runCommand(AdbHelper.specifyDeviceOnCmd(cmd, device)) retry = False except: nums = nums + 1 time.sleep(1) Log().getLogger().debug('open port mapping ---> retry ' + str(nums)) return stdout
def _getPageFeature(self, url): import time retry = True nums = 0 message = '' while (retry and nums < 8): try: webSocketConn = create_connection(url=url) webSocketConn.send( '{"id": 1,"method": "Runtime.evaluate","params": {"expression": "var allNodeList = [];function getChildNode(node){var nodeList = node.childNodes;for(var i = 0;i < nodeList.length;i++){var childNode = nodeList[i];allNodeList.push(childNode);getChildNode(childNode);}};getChildNode(document.body);"}}' ) webSocketConn.recv() webSocketConn.send( '{"id": 2,"method": "Runtime.evaluate","params": {"expression": "allNodeEle=\'\'"}}' ) webSocketConn.recv() webSocketConn.send( '{"id": 3,"method": "Runtime.evaluate","params": {"expression": "for(j = 0; j < allNodeList.length; j++) {allNodeEle = allNodeEle+allNodeList[j];}"}}' ) results = webSocketConn.recv() self.logger.debug(results) result = json.loads(results) retry = True if result.get('result').get('wasThrown') is True or \ result.get('result').get('result').get('value') == u'' or \ result.get('result').get('result').get('type') == u'undefined' else False if retry: time.sleep(2) nums = nums + 1 webSocketConn.close() Log().getLogger().info('retry getFeatur ---> ' + str(nums)) continue else: message = result['result']['result']['value'] retry = False webSocketConn.close() return message except: continue if retry or message == '': raise AttributeError()
class QQWebSocketDebugUrlFetcher(object): DEFAULT_LOCAL_FORWARD_PORT = 9222 def __init__(self, device, localForwardPort=None): self._device = device self._localForwardPort = localForwardPort if localForwardPort is not None \ else QQWebSocketDebugUrlFetcher.DEFAULT_LOCAL_FORWARD_PORT self._webSocketDebugUrl = None self.type = '' self.pageUrlDict = bidict.bidict() self.logger = Log().getLogger() def fetchWebSocketDebugUrl(self, refetch=False): # 如果没有获取或者需要重新获取debug url,那么获取一次,否则直接返回 if self._webSocketDebugUrl is None or refetch: self._fetchInner() return self._webSocketDebugUrl def getDevice(self): return self._device def getForwardPort(self): return self._localForwardPort def _fetchInner(self): self.logger.debug('') pid = QQWebSocketDebugUrlFetcher._fetchQQToolsProcessPid( device=self._device) QQWebSocketDebugUrlFetcher._forwardLocalPort(self._localForwardPort, pid, device=self._device) # 获取本地http://localhost:{重定向端口}/json返回的json数据,提取里面的webSocketDebuggerUrl字段值 self._webSocketDebugUrl = self._fetchWebSocketDebugUrl( self._localForwardPort) @staticmethod def _fetchQQToolsProcessPid(device=None): osName = OS.getPlatform() cmd = _ADB_FIND_QQ_STR_CMD[osName] stdout = QQWebSocketDebugUrlFetcher._getProcessInfo(cmd, device) QQProcessInfoLine = None for processInfo in stdout.split("\r\r\n"): if "com.tencent.mobileqq:tool" in processInfo: QQProcessInfoLine = processInfo break if QQProcessInfoLine is None: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_NOT_FOUND_WEIXIN_TOOLS_PROCESS) raise RuntimeError(errorMsg) QQProcessInfo = QQProcessInfoLine.split() return QQWebSocketDebugUrlFetcher._handlePhoneCompat(QQProcessInfo) @staticmethod def _getProcessInfo(cmd, device): nums = 0 retry = True stdout = '' while (retry and nums < 8): try: stdout, stdError = runCommand( AdbHelper.specifyDeviceOnCmd(cmd, device)) retry = False except: nums = nums + 1 time.sleep(1) Log().getLogger().debug('open port mapping ---> retry ' + str(nums)) return stdout @staticmethod def _handlePhoneCompat(processInfo): # 这里建立ps命令返回结果行字段数和pid字段index的映射 pidIndexMap = { 9: 1, # 三星的手机是9元祖,第二列为pid index 5: 0, # 华为的手机是5元祖,第一列为pid index 10: 5, # 当微信出现异常时可能出现两个tools,那么选择第二个。 18: 10 } fieldCount = len(processInfo) pidIndex = pidIndexMap.get(fieldCount, -1) if pidIndex >= 0: return int(processInfo[pidIndex]) else: raise RuntimeError(ErrorMsgManager().errorCodeToString( ERROR_CODE_NOT_ENABLE_DEBUG_MODE)) @staticmethod def _forwardLocalPort(localPort, pid, device=None): cmd = "adb forward tcp:%s localabstract:webview_devtools_remote_%s" % ( localPort, pid) runCommand(AdbHelper.specifyDeviceOnCmd(cmd, device)) def _fetchWebSocketDebugUrl(self, localPort): self.logger.debug('') import json import urllib2 errorMsg = None resultUrl = None localUrl = "http://localhost:%s/json" % localPort # 去掉代理 urllib2.getproxies = lambda: {} try: nums = 0 while None == resultUrl and nums < 8: response = urllib2.urlopen(localUrl) responseJson = json.loads(response.read()) if len(responseJson) != 0: resultUrl = self._handleAndReturnWebSocketDebugUrl( responseJson) self.logger.debug('websocket --> ' + resultUrl) return resultUrl else: nums = nums + 1 Log().getLogger().debug('retry fetch num ---> ' + str(nums)) time.sleep(1) except Exception: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_CONFIG_PROXY) if not errorMsg: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_NOT_ENTER_H5) raise RuntimeError(errorMsg) # 按顺序记录打开页面,key为打开页面的顺序,value为打开页面的websocketUrl。 def _handleAndReturnWebSocketDebugUrl(self, responseJson): import json responseLenth = 0 # 只记录有效页面的数量 for i in range(len(responseJson)): response = responseJson[i] if response['type'] == u'page' and json.loads( response['description'])['empty'] == False: responseLenth = responseLenth + 1 pageUrlDictLength = len(self.pageUrlDict) if pageUrlDictLength == 0: self.logger.debug('pageUrlDictLength == 0:') for i in range(responseLenth): response = responseJson[i] if response['type'] == u'page' and json.loads( response['description'])['empty'] == False: self.pageUrlDict.update({ pageUrlDictLength + 1: response['webSocketDebuggerUrl'] }) return response['webSocketDebuggerUrl'] elif responseLenth > pageUrlDictLength: self.logger.debug('responseLenth > pageUrlDictLength') for i in range(responseLenth): response = responseJson[i] if response['type'] == u'page' and json.loads( response['description'])['empty'] == False: if self.pageUrlDict.inv.get( response['webSocketDebuggerUrl']) is None: self.pageUrlDict.update({ pageUrlDictLength + 1: response['webSocketDebuggerUrl'] }) return response['webSocketDebuggerUrl'] elif responseLenth < pageUrlDictLength: self.logger.debug('responseLenth < pageUrlDictLength') sencondLastPageUrl = self.pageUrlDict.get(pageUrlDictLength - 1) del self.pageUrlDict[pageUrlDictLength] return sencondLastPageUrl else: self.logger.debug('responseLenth == pageUrlDictLength') lastPageUrl = self.pageUrlDict.get(pageUrlDictLength) return lastPageUrl def needSwitchNextPage(self): import json import urllib2 localUrl = "http://localhost:9223/json" # 去掉代理 urllib2.getproxies = lambda: {} response = urllib2.urlopen(localUrl) responseJson = json.loads(response.read()) return len(responseJson) != len(self.pageUrlDict) def getType(self): return self.type
class WxWebSocketDebugUrlFetcher(object): DEFAULT_LOCAL_FORWARD_PORT = 9223 # 根据dict中存储的数量来获得新添加页面的名称 pageMap = { 1: 'event', 2: 'first', 3: 'second', 4: 'third', 5: 'fourth', 6: 'fifth' } def __init__(self, device, localForwardPort=None): self._device = device self._localForwardPort = localForwardPort if localForwardPort is not None \ else WxWebSocketDebugUrlFetcher.DEFAULT_LOCAL_FORWARD_PORT self._webSocketDebugUrl = None # 通过dic来管理页面 self.pageUrlDict = bidict.bidict() self.logger = Log().getLogger() self.mode = MODE_NORMAL def fetchWebSocketDebugUrl(self, refetch=False): # 如果没有获取或者需要重新获取debug url,那么获取一次,否则直接返回 if self._webSocketDebugUrl is None: self._fetchInner() if refetch: self._webSocketDebugUrl = self._fetchWebSocketDebugUrl( self._localForwardPort) return self._webSocketDebugUrl def getDevice(self): return self._device def getForwardPort(self): return self._localForwardPort def _fetchInner(self): # 先获取微信appbrand进程Pid pid = WxWebSocketDebugUrlFetcher._fetchWeixinToolsProcessPid( device=self._device) # 重定向端口 WxWebSocketDebugUrlFetcher._forwardLocalPort(self._localForwardPort, pid, device=self._device) # 获取本地http://localhost:{重定向端口}/json返回的json数据,提取里面的webSocketDebuggerUrl字段值 self._webSocketDebugUrl = self._fetchWebSocketDebugUrl( self._localForwardPort) @staticmethod def _fetchWeixinToolsProcessPid(device=None): osName = OS.getPlatform() cmd = _ADB_GET_TOP_ACTIVITY_CMD[osName] stdout, stdError = runCommand(AdbHelper.specifyDeviceOnCmd( cmd, device)) print "fetchWeixinToolsProcessActivity:\n", stdout mmActivity = stdout.split('com.tencent.mm/') pid = 0 for activity in mmActivity: if '.plugin.appbrand.ui.AppBrandInToolsUI' in activity: # 小米 strlist = activity.split('pid=') pid = strlist[1].split("\r\n")[0] break elif '.plugin.appbrand.ui.AppBrandUI' in activity: # VIVO strlist = activity.split('pid=') pid = strlist[1].split("\r\n")[0] break webviewCmd = _ADB_GET_WEBVIEW_TOOLS_CMD[osName] % (pid) print "pid:", pid if pid == 0: # 维持原有逻辑 strlist = stdout.split('pid=') pid = strlist[1].split("\r\n")[0] # 验证是否启动了小程序webview try: webStdout, webStdError = runCommand( AdbHelper.specifyDeviceOnCmd(webviewCmd, device)) except: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_NOT_ENTER_XCX) raise RuntimeError(errorMsg) return pid @staticmethod def _forwardLocalPort(localPort, pid, device=None): cmd = "adb forward tcp:%s localabstract:webview_devtools_remote_%s" % ( localPort, pid) runCommand(AdbHelper.specifyDeviceOnCmd(cmd, device)) def _fetchWebSocketDebugUrl(self, localPort): import time localUrl = "http://localhost:%s/json" % localPort errorMsg = None resultUrl = None # 去掉代理 urllib2.getproxies = lambda: {} try: nums = 0 while None == resultUrl and nums < 8: response = urllib2.urlopen(localUrl) responseJson = json.loads(response.read()) if len(responseJson) != 1: self._cleanJsonData(responseJson) resultUrl = self._handleAndReturnWebSocketDebugUrl( responseJson) self.logger.debug('websocket --> ' + resultUrl) return resultUrl else: nums = nums + 1 self.logger.debug('retry fetch num ---> ' + str(nums)) time.sleep(1) except IndexError: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_NOT_ENABLE_DEBUG_MODE) except LookupError: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_NOT_ENTER_XCX) except AttributeError: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_NOT_GET_XCX_PAGE_INFO) except Exception: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_CONFIG_PROXY) raise RuntimeError(errorMsg) # 去掉file://开头的脏数据 def _cleanJsonData(self, responseJson): removeList = [] for response in responseJson: if u'file:///' in response['url']: removeList.append(response) for i in removeList: responseJson.remove(i) def _handleAndReturnWebSocketDebugUrl(self, responseJson): responseLength = len(responseJson) pageUrlDictLength = len(self.pageUrlDict) # 如果第一次进入小程序,当前加载的dict为空 if pageUrlDictLength == 0: # 考虑正常的小程序,有两个链接,一个是空的server,一个是首页面。 if responseLength == 2: self.mode = MODE_NORMAL return self._initWebSocketUrlWithTwoPage(responseJson) # 如果是小程序内嵌H5的情况,有三个链接,其中两个ServiceWeChat的URL,一个H5的URL。 if responseLength == 3: self.mode = MODE_EMBEDDED_H5 return self._initWebSocketUrlWithThreePage(responseJson) # 如果返回的大于当前打开的,说明要开启新的页面 # 有两种情况,一种是打开正常的小程序页面,启动一个新链接,另一种是打开嵌套H5的小程序页面,会启动两个链接 elif responseLength > pageUrlDictLength: if responseLength - pageUrlDictLength == 1: for i in range(responseLength): message = self._getPageFeature( responseJson[i]["webSocketDebuggerUrl"]) if self.pageUrlDict.inv.get(message) is None: self.pageUrlDict.update( {self.pageMap.get(pageUrlDictLength + 1): message}) return responseJson[i]["webSocketDebuggerUrl"] elif responseLength - pageUrlDictLength == 2: h5Url = '' for i in range(responseLength): message = self._getPageFeature( responseJson[i]["webSocketDebuggerUrl"]) if self.pageUrlDict.inv.get(message) is None: self.pageUrlDict.update( {self.pageMap.get(pageUrlDictLength + 1): message}) if 'https://servicewechat.com/' not in responseJson[i][ "url"]: h5Url = responseJson[i]["webSocketDebuggerUrl"] return h5Url else: raise AttributeError() # 如果返回的小于当前打开的,说明要删除一个页面 # 有两种情况,一种是关闭正常的小程序页面,删除一个链接,另一种是关闭嵌套H5的小程序页面,需要删除两个链接 elif responseLength < pageUrlDictLength: if pageUrlDictLength - responseLength == 1: sencondLastPageMessage = self.pageUrlDict.get( self.pageMap.get(pageUrlDictLength - 1)) shouldReturnUrl = None del self.pageUrlDict[self.pageMap.get(pageUrlDictLength)] for i in range(responseLength): message = self._getPageFeature( responseJson[i]["webSocketDebuggerUrl"]) if message == sencondLastPageMessage: shouldReturnUrl = responseJson[i][ "webSocketDebuggerUrl"] return shouldReturnUrl elif pageUrlDictLength - responseLength == 2: sencondLastPageMessage = self.pageUrlDict.get( self.pageMap.get(pageUrlDictLength - 2)) shouldReturnUrl = None del self.pageUrlDict[self.pageMap.get(pageUrlDictLength)] del self.pageUrlDict[self.pageMap.get(pageUrlDictLength - 1)] for i in range(responseLength): message = self._getPageFeature( responseJson[i]["webSocketDebuggerUrl"]) if message == sencondLastPageMessage: shouldReturnUrl = responseJson[i][ "webSocketDebuggerUrl"] return shouldReturnUrl # 如果返回的等于当前打开的,有两种情况。一种是两个URL时,返回最后一个webSocketUrl。一种是当有三个URL,小程序内嵌H5 URL时,返回内嵌的H5 URL。 elif responseLength == pageUrlDictLength: if self.mode == MODE_NORMAL: lastPageMessage = self.pageUrlDict.get( self.pageMap.get(pageUrlDictLength)) for i in range(responseLength): message = self._getPageFeature( responseJson[i]["webSocketDebuggerUrl"]) if message == lastPageMessage: return responseJson[i]["webSocketDebuggerUrl"] # 如果都为空,则在当前页面进行了跳转,因此要更新dict中的特征 # 依次连接返回的所有page的websocketurl,找到一个更新后的页面 for response in responseJson: websocketUrl = response["webSocketDebuggerUrl"] pageFeature = self._getPageFeature(websocketUrl) if self.pageUrlDict.inv.get(pageFeature) is None: self.pageUrlDict.update( {self.pageMap.get(pageUrlDictLength): pageFeature}) return websocketUrl else: return self.pageUrlDict.get(1) def _initWebSocketUrlWithTwoPage(self, responseJson): eventData = u'[object Text][object HTMLDivElement][object Text]' responseLength = 2 pageUrlDictLength = len(self.pageUrlDict) for i in range(responseLength): message = self._getPageFeature( responseJson[i]["webSocketDebuggerUrl"]) if message == eventData: self.pageUrlDict.update( {self.pageMap.get(pageUrlDictLength + 1): message}) if i == 0: firstMessage = self._getPageFeature( responseJson[1]["webSocketDebuggerUrl"]) self.pageUrlDict.update({ self.pageMap.get(pageUrlDictLength + 2): firstMessage }) return responseJson[1]["webSocketDebuggerUrl"] else: firstMessage = self._getPageFeature( responseJson[0]["webSocketDebuggerUrl"]) self.pageUrlDict.update({ self.pageMap.get(pageUrlDictLength + 2): firstMessage }) return responseJson[0]["webSocketDebuggerUrl"] def _initWebSocketUrlWithThreePage(self, responseJson): responseWesocketUrlDict = {} responseUrlList = [] serviceUrlList = [] h5Url = [] responseLength = 3 for i in range(responseLength): response = responseJson[i] url = response['url'] webSocketUrl = response['webSocketDebuggerUrl'] responseUrlList.append(url) responseWesocketUrlDict[url] = webSocketUrl for url in responseUrlList: if u"servicewechat" in url: serviceUrlList.append(url) else: h5Url.append(url) if len(serviceUrlList) != 2 or len(h5Url) != 1: raise AttributeError() else: url = '' for url in serviceUrlList: webSocketUrl = responseWesocketUrlDict.get(url) print webSocketUrl webSocketConn = create_connection(url=webSocketUrl) webSocketConn.send( '{"id": 1,"method": "Runtime.evaluate","params": {"expression": "srcs = document.body.childNodes[0].getAttribute(\'src\')"}}' ) results = webSocketConn.recv() result = json.loads(results) webSocketConn.close() if result.get('result').get('result').get('type') == u'string': url = result.get('result').get('result').get('value') break if url not in h5Url[0]: raise AttributeError() else: self.pageUrlDict.forceupdate( {1: responseWesocketUrlDict.get(h5Url[0])}) self.pageUrlDict.forceupdate( {2: responseWesocketUrlDict.get(serviceUrlList[0])}) self.pageUrlDict.forceupdate( {3: responseWesocketUrlDict.get(serviceUrlList[1])}) return responseWesocketUrlDict.get(h5Url[0]) def _getPageFeature(self, url): import time retry = True nums = 0 message = '' while (retry and nums < 8): try: webSocketConn = create_connection(url=url) webSocketConn.send( '{"id": 1,"method": "Runtime.evaluate","params": {"expression": "var allNodeList = [];function getChildNode(node){var nodeList = node.childNodes;for(var i = 0;i < nodeList.length;i++){var childNode = nodeList[i];allNodeList.push(childNode);getChildNode(childNode);}};getChildNode(document.body);"}}' ) webSocketConn.recv() webSocketConn.send( '{"id": 2,"method": "Runtime.evaluate","params": {"expression": "allNodeEle=\'\'"}}' ) webSocketConn.recv() webSocketConn.send( '{"id": 3,"method": "Runtime.evaluate","params": {"expression": "for(j = 0; j < allNodeList.length; j++) {allNodeEle = allNodeEle+allNodeList[j];}"}}' ) results = webSocketConn.recv() self.logger.debug(results) result = json.loads(results) retry = True if result.get('result').get('wasThrown') is True or \ result.get('result').get('result').get('value') == u'' or \ result.get('result').get('result').get('type') == u'undefined' else False if retry: time.sleep(2) nums = nums + 1 webSocketConn.close() Log().getLogger().info('retry getFeatur ---> ' + str(nums)) continue else: message = result['result']['result']['value'] retry = False webSocketConn.close() return message except: continue if retry or message == '': raise AttributeError() def needSwitchNextPage(self): import json import urllib2 localUrl = "http://localhost:9223/json" # 去掉代理 urllib2.getproxies = lambda: {} response = urllib2.urlopen(localUrl) responseJson = json.loads(response.read()) return len(responseJson) != len(self.pageUrlDict)
def setDebugLogMode(self): ''' 开启debug的日志模式 ''' Log().setDebugLogMode()
class WxDriver(object): def initDriver(self): if self._hasInit: return self._urlFetcher = WxWebSocketDebugUrlFetcher(device=self._device) url = self._urlFetcher.fetchWebSocketDebugUrl() dirPath = os.path.split(os.path.realpath(__file__))[0] PLUG_SRC = os.path.join(dirPath, 'apk', 'inputPlug.apk') if not AdbHelper.hasApkInstalled(packageName='com.tencent.fat.wxinputplug'): AdbHelper.installApk(PLUG_SRC, device=self._device, installOverride=True) self.logger.info('install ---> ' + PLUG_SRC) self._vmShutdownHandler.registerToVM() UncaughtExceptionHandler.init() UncaughtExceptionHandler.registerUncaughtExceptionCallback(self._onUncaughtException) self._selectDevice() self._webSocketDataTransfer = WebSocketDataTransfer(url=url) self._networkHandler = ShortLiveWebSocket(self._webSocketDataTransfer, self._executor, self) self._networkHandler.connect() self._executor.put(self._networkHandler.receive) self.initPageDisplayData() self._hasInit = True # 为了输入中文要先安装一个插件,./wx/apk/inputPlug.apk def __init__(self, d,device=None): self._device = device self._urlFetcher = None self._webSocketDataTransfer = None self._vmShutdownHandler = VMShutDownHandler() self._networkHandler = None self._executor = SingleThreadExecutor() self.logger = Log().getLogger() self._pageOperator = WxPageOperator() self._hasInit = False self.d = d self.html = None def getDriverType(self): return constant.WXDRIVER def initPageDisplayData(self): driverInfo = self.d.info displayDp = driverInfo.get('displaySizeDpY') displayPx = driverInfo.get('displayHeight') self.scale = (displayPx - 0.5) / displayDp windowHeight = self.getWindowHeight() self.appTitleHeight = displayDp - windowHeight def changeDp2Px(self, xDp, yDp): xPx, yPx = self._pageOperator.changeDp2Px(xDp, yDp, self.scale, self.appTitleHeight) return xPx, yPx def needSwitchNextPage(self): return self._urlFetcher.needSwitchNextPage() def _selectDevice(self): devicesList = AdbHelper.listDevices(ignoreUnconnectedDevices=True) # 假如没有指定设备,那么判断当前机器是否连接1个设备 if self._device is None: devicesCount = len(devicesList) errorMsg = None if devicesCount <= 0: errorMsg = ErrorMsgManager().errorCodeToString(ERROR_CODE_DEVICE_NOT_CONNECT) elif devicesCount == 1: self._device = devicesList[0] else: errorMsg = ErrorMsgManager().errorCodeToString(ERROR_CODE_MULTIPLE_DEVICE) if errorMsg is not None: raise RuntimeError(errorMsg) def switchToNextPage(self): """ 需要重新获取当前页面的websocket的url """ self._networkHandler.disconnect() url = self._urlFetcher.fetchWebSocketDebugUrl(refetch=True) self.logger.debug('url -> ' + url) self._webSocketDataTransfer.setUrl(url) self._networkHandler.connect() self.wait(WAIT_REFLESH_05_SECOND) def returnLastPage(self): self.wait(WAIT_REFLESH_05_SECOND) self.logger.info('') self._networkHandler.disconnect() self.switchToNextPage() self.wait(WAIT_REFLESH_05_SECOND) self._networkHandler.disconnect() self.d.press('back') self.wait(WAIT_REFLESH_05_SECOND) self.switchToNextPage() def getDocument(self): """ 获得getHtml中需要的nodeId 在调用getHtml之前必须先调用这个方法 """ sendStr = self._pageOperator.getDocument() return self._networkHandler.send(sendStr) def getHtml(self, nodeId=1): """ 获得指定nodeId的Html代码。在一条websocket连接中,方法只能够执行一次。 :param nodeId: getDocument方法返回的nodeId,当为1时,返回整个body的代码 """ self.logger.info('') self.switchToNextPage() self.getDocument() sendStr = self._pageOperator.getHtml(nodeId) self.html = self._networkHandler.send(sendStr).getResponse()[0]['result']['outerHTML'] return self.html def isElementExist(self, xpath): self.logger.info('xpath ---> ' + xpath) getExistCmd = self._pageOperator.isElementExist(xpath) response = self._networkHandler.send(getExistCmd).getResponse() self.logger.debug(response) resultValueDict = response[0] resultType = resultValueDict['result']['result']['subtype'] num = 0 while resultType == 'null' and num < 3: self.wait(WAIT_REFLESH_3_SECOND) self.switchToNextPage() getExistCmd = self._pageOperator.isElementExist(xpath) resultValueDict = self._networkHandler.send(getExistCmd).getResponse()[0] resultType = resultValueDict['result']['result']['subtype'] num = num + 1 self.logger.debug('isElementExist Response --> ' + str(resultValueDict)) return resultType != 'null' def getElementTextByXpath(self, xpath): time.sleep(WAIT_REFLESH_1_SECOND) self.logger.info('xpath ---> ' + xpath) self.switchToNextPage() if self.isElementExist(xpath): getTextCmd = self._pageOperator.getElementTextByXpath(xpath) resultValueDict = self._networkHandler.send(getTextCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'].encode("utf-8") else: resultValue = None return resultValue def getElementSrcByXpath(self, xpath): time.sleep(WAIT_REFLESH_1_SECOND) self.logger.info('xpath ---> ' + xpath) self.switchToNextPage() if self.isElementExist(xpath): getSrcCmd = self._pageOperator.getElementSrcByXpath(xpath) resultValueDict = self._networkHandler.send(getSrcCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'].encode("utf-8") else: resultValue = None return resultValue def getElementByXpath(self, xpath): """ :param:目标的xpath :return:返回lxml包装过的element对象,可以使用lxml语法获得对象的信息 例如:可以使用element.get("attrs")来拿到属性的数据 可以用element.text拿到它的文字 当element中含有列表时,使用for循环读取每一个item """ time.sleep(WAIT_REFLESH_1_SECOND) self.logger.info('xpath ---> ' + xpath) htmlData = self.getHtml() if htmlData is not None: html = etree.HTML(htmlData) elementList = html.xpath(xpath) if len(elementList) != 0: return elementList[0] else: self.logger.info('找不到xpath为: ' + xpath + ' 的控件') return '' else: self.logger.info('获取到的html为空') return '' def textElementByXpath(self, xpath, text, needClick=False): """ :param needClick:如果为true,会先对控件进行一次点击,以获得焦点 输入前会先保存当前默认的输入法 然后将输入法切换到adb插件 输入中文,输入后讲输入法转换回默认输入发 needClick代表是否需要先对xpath的控件进行一次点击 """ self.logger.info('xpath ---> ' + xpath + ' text ---> ' + text) if needClick: self.clickElementByXpath(xpath, byUiAutomator=True) if self.isElementExist(xpath): ADB_SHELL = 'adb shell ' self.logger.debug('textElementByXpath xpath exist') if self._device: ADB_SHELL = ADB_SHELL.replace("adb", "adb -s %s" % self._device) GET_DEFAULT_INPUT_METHOD = ADB_SHELL + 'settings get secure default_input_method' SET_INPUT_METHOD = ADB_SHELL + 'ime set {0}' CHINESE_INPUT_METHOD = 'com.tencent.fat.wxinputplug/.XCXIME' INPUT_TEXT = ADB_SHELL + "am broadcast -a INPUT_TEXT --es TEXT '{0}'" DEFAULT_INPUT_METHOD = commandHelper.runCommand(GET_DEFAULT_INPUT_METHOD)[0].replace("\r\n", " ") osName = OS.getPlatform() INPUT_TEXT_CMD = { "Darwin": INPUT_TEXT.format(text), "Windows": INPUT_TEXT.format(text).decode('utf-8').replace(u'\xa0', u' ').encode('GBK'), "Linux": INPUT_TEXT.format(text) } self.logger.debug(SET_INPUT_METHOD.format(CHINESE_INPUT_METHOD)) commandHelper.runCommand(SET_INPUT_METHOD.format(CHINESE_INPUT_METHOD)) self.wait(WAIT_REFLESH_05_SECOND) self.logger.debug(INPUT_TEXT_CMD.get(osName)) commandHelper.runCommand(INPUT_TEXT_CMD.get(osName)) self.logger.debug(SET_INPUT_METHOD.format(DEFAULT_INPUT_METHOD)) commandHelper.runCommand(SET_INPUT_METHOD.format(DEFAULT_INPUT_METHOD)) self.wait(WAIT_REFLESH_05_SECOND) def clickElementByXpath(self, xpath, visibleItemXpath=None, byUiAutomator=False): """ 默认滑动点为屏幕的中心,且边距为整个屏幕。当有container时,传入container中任意一个当前可见item的xpath,之后会将目标滑到该可见item的位置 :param xpath: 要滑动到屏幕中控件的xpath :param visibleItemXpath: container中当前可见的一个xpath :return: """ self.logger.info('xpath ---> ' + xpath) if self.isElementExist(xpath): self.scrollToElementByXpath(xpath, visibleItemXpath) sendStr = self._pageOperator.getElementRect(xpath) self._networkHandler.send(sendStr) x = self._getRelativeDirectionValue('x') y = self._getRelativeDirectionValue('y') self.logger.debug('clickElementByXpath x:' + str(x) + ' y:' + str(y)) if not byUiAutomator: clickCommand = self._pageOperator.clickElementByXpath(x, y) return self._networkHandler.send(clickCommand) else: xPx, yPx = self.changeDp2Px(x, y) self.d.click(xPx, yPx) def clickFirstElementByText(self, text, visibleItemXpath=None, byUiAutomator=False): """ 通过text来搜索,点击第一个text相符合的控件。参数同clickElementByXpath() """ self.clickElementByXpath('.//*[text()="' + text + '"]', visibleItemXpath, byUiAutomator) def getElementCoordinateByXpath(self, elementXpath): ''' 获得Element的坐标 :param elementXpath:待获取坐标的元素的xpath :return:element相对于整个屏幕的x、y坐标,单位为px ''' self.logger.info( 'elementXpathXpath ---> ' + elementXpath) if self.isElementExist(elementXpath): sendStr = self._pageOperator.getElementRect(elementXpath) self._networkHandler.send(sendStr) x = self._getRelativeDirectionValue('x') y = self._getRelativeDirectionValue('y') xPx, yPx = self.changeDp2Px(x, y) return xPx, yPx def scrollToElementByXpath(self, xpath, visibleItemXpath=None, speed=600): """ 默认滑动点为屏幕的中心,且边距为整个屏幕。当有container时,传入container中任意一个当前可见item的xpath,之后会将目标滑到该可见item的位置 :param xpath: 要滑动到屏幕中控件的xpath :param visibleItemXpath: container中当前可见的一个xpath :return: """ self.logger.info('xpath ---> ' + xpath) sendStr = self._pageOperator.getElementRect(xpath) self._networkHandler.send(sendStr) top = self._getRelativeDirectionValue('topp') bottom = self._getRelativeDirectionValue('bottom') left = self._getRelativeDirectionValue('left') right = self._getRelativeDirectionValue('right') self.logger.debug('scrollToElementByXpath -> top:bottom:left:right = ' + str(top) + " :" + str(bottom) \ + " :" + str(left) + " :" + str(right)) if visibleItemXpath is None: endTop = 0 endLeft = 0 endBottom = self.getWindowHeight() endRight = self.getWindowWidth() else: sendStr = self._pageOperator.getElementRect(visibleItemXpath) self._networkHandler.send(sendStr) endTop = self._getRelativeDirectionValue('topp') endBottom = self._getRelativeDirectionValue('bottom') endLeft = self._getRelativeDirectionValue('left') endRight = self._getRelativeDirectionValue('right') self.logger.debug( 'scrollToElementByXpath -> toendTop:endBottom:endLeft:endRight = ' + str(endTop) + " :" + str( endBottom) \ + " :" + str(endLeft) + " :" + str(endRight)) ''' 竖直方向的滑动 ''' if endBottom < bottom: scrollYDistance = endBottom - bottom elif top < 0: scrollYDistance = -(top - endTop) else: scrollYDistance = 0 if scrollYDistance < 0: self.scrollWindow(int((endLeft + endRight) / 2), int((endTop + endBottom) / 2), 0, scrollYDistance - 80, speed) elif scrollYDistance > 0: self.scrollWindow(int((endLeft + endRight) / 2), int((endTop + endBottom) / 2), 0, scrollYDistance + 80, speed) else: self.logger.debug('y方向不需要滑动') ''' 水平方向的滑动 ''' if right > endRight: scrollXDistance = endRight - right elif left < 0: scrollXDistance = -(left - endLeft) else: scrollXDistance = 0 if scrollXDistance != 0: self.scrollWindow(int((endLeft + endRight) / 2), int((endTop + endBottom) / 2), scrollXDistance, 0, speed) else: self.logger.debug('x方向不需要滑动') def scrollWindow(self, x, y, xDistance, yDistance, speed=800): self.logger.info('') sendStr = self._pageOperator.scrollWindow(x, y, xDistance, yDistance, speed) return self._networkHandler.send(sendStr) def _getRelativeDirectionValue(self, directionKey='topp'): ''' 获取相关的方向数据参数值 :param directionKey: key值 :return: ''' directionCommand = self._pageOperator.getJSValue(directionKey) directionResponse = self._networkHandler.send(directionCommand).getResponse()[0] directionValue = directionResponse['result']['result']['value'] return directionValue ''' 性能数据 ''' def getMemoryInfo(self): ''' 获得小程序进程占用内存信息 :return: 小程序进程占用内存信息 ''' self.logger.info('') ADB_SHELL = 'adb shell ' if self._device: ADB_SHELL = ADB_SHELL.replace("adb", "adb -s %s" % self._device) GET_MEMORY_INFO = ADB_SHELL + 'dumpsys meminfo com.tencent.mm:appbrand0' return commandHelper.runCommand(GET_MEMORY_INFO) def getCPUInfo(self): ''' 获得小程序进程占用CPU信息 :return: 小程序进程占用CPU信息 ''' self.logger.info('') ADB_SHELL = 'adb shell ' if self._device: ADB_SHELL = ADB_SHELL.replace("adb", "adb -s %s" % self._device) GET_CPU_INFO = ADB_SHELL + ' top -n 1 | grep com.tencent.mm:appbrand0' return commandHelper.runCommand(GET_CPU_INFO) def getPageHeight(self): getPageHeightCmd = self._pageOperator.getPageHeight() resultValueDict = self._networkHandler.send(getPageHeightCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def getWindowWidth(self): ''' :return:手机屏幕的宽度 ''' getWindowWidthCmd = self._pageOperator.getWindowWidth() resultValueDict = self._networkHandler.send(getWindowWidthCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def getWindowHeight(self): ''' :return:手机屏幕的高度 ''' getWindowHeightCmd = self._pageOperator.getWindowHeight() resultValueDict = self._networkHandler.send(getWindowHeightCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def wait(self, seconds): time.sleep(seconds) def addShutDownHook(self, func, *args, **kwargs): ''' 添加当程序正常关闭时的操作,可以进行一些环境清理操作等 ''' self._vmShutdownHandler.registerVMShutDownCallback(func, *args, **kwargs) def _onUncaughtException(self, exctype, value, tb): self.close() def close(self): if self._networkHandler is not None: self._networkHandler.quit() if self._executor is not None: self._executor.shutDown() UncaughtExceptionHandler.removeHook() def setDebugLogMode(self): ''' 开启debug的日志模式 ''' Log().setDebugLogMode()
# -*- coding: utf-8 -*- ''' Tencent is pleased to support the open source community by making FAutoTest available. Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved. Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://opensource.org/licenses/BSD-3-Clause Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' import subprocess from fastAutoTest.core.common.errormsgmanager import * from fastAutoTest.utils.logger import Log logger = Log().getLogger() def runCommand(cmd, printDetails=False, cwd=None): logger.info('cmd ----> ' + cmd) p = subprocess.Popen(cmd, cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) stdout, stdError = p.communicate() if printDetails: print("runCommand --> " + stdout) if printDetails and stdError:
class QQDriver(object): def initDriver(self): if self._hasInit: return self._urlFetcher = QQWebSocketDebugUrlFetcher(device=self._device) url = self._urlFetcher.fetchWebSocketDebugUrl() self._vmShutdownHandler.registerToVM() UncaughtExceptionHandler.init() UncaughtExceptionHandler.registerUncaughtExceptionCallback( self._onUncaughtException) self._selectDevice() self._webSocketDataTransfer = WebSocketDataTransfer(url=url) self._networkHandler = ShortLiveWebSocket(self._webSocketDataTransfer, self._executor, self) self._networkHandler.connect() self._executor.put(self._networkHandler.receive) self.initPageDisplayData() self.logger = Log().getLogger() self._hasInit = True def __init__(self, device=None): self._device = device self._urlFetcher = None self._webSocketDataTransfer = None self._vmShutdownHandler = VMShutDownHandler() self._networkHandler = None self._executor = SingleThreadExecutor() self._pageOperator = H5PageOperator() self._hasInit = False self.d = uiautomator.Device(self._device) self.html = None def getDriverType(self): return constant.QQDRIVER def initPageDisplayData(self): driverInfo = self.d.info displayDp = driverInfo.get('displaySizeDpY') displayPx = driverInfo.get('displayHeight') self.scale = (displayPx - 0.5) / displayDp windowHeight = self.getWindowHeight() self.appTitleHeight = displayDp - windowHeight def changeDp2Px(self, xDp, yDp): xPx, yPx = self._pageOperator.changeDp2Px(xDp, yDp, self.scale, self.appTitleHeight) return xPx, yPx def needSwitchNextPage(self): return self._urlFetcher.needSwitchNextPage() def _selectDevice(self): devicesList = AdbHelper.listDevices(ignoreUnconnectedDevices=True) # 假如没有指定设备,那么判断当前机器是否连接1个设备 if self._device is None: devicesCount = len(devicesList) errorMsg = None if devicesCount <= 0: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_DEVICE_NOT_CONNECT) elif devicesCount == 1: self._device = devicesList[0] else: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_MULTIPLE_DEVICE) if errorMsg is not None: raise RuntimeError(errorMsg) def switchToNextPage(self): """ 需要重新获取当前页面的websocket的url """ self._networkHandler.disconnect() url = self._urlFetcher.fetchWebSocketDebugUrl(refetch=True) self.logger.debug('url -> ' + url) self._webSocketDataTransfer.setUrl(url) self._networkHandler.connect() self.wait(WAIT_REFLESH_05_SECOND) def returnLastPage(self): self.logger.info('') self.wait(WAIT_REFLESH_1_SECOND) self._networkHandler.disconnect() self.switchToNextPage() self._networkHandler.disconnect() self.d.press('back') self.wait(WAIT_REFLESH_1_SECOND) self.switchToNextPage() def getDocument(self): """ 获得getHtml中需要的nodeId 在调用getHtml之前必须先调用这个方法 """ sendStr = self._pageOperator.getDocument() return self._networkHandler.send(sendStr) def getHtml(self, nodeId=1): """ 获得指定nodeId的Html代码。在一条websocket连接中,方法只能够执行一次。 :param nodeId: getDocument方法返回的nodeId,当为1时,返回整个body的代码 """ self.logger.info('') self.switchToNextPage() self.getDocument() sendStr = self._pageOperator.getHtml(nodeId) self.html = self._networkHandler.send( sendStr).getResponse()[0]['result']['outerHTML'] return self.html def isElementExist(self, xpath): self.logger.info('xpath ---> ' + xpath) getExistCmd = self._pageOperator.isElementExist(xpath) resultValueDict = self._networkHandler.send( getExistCmd).getResponse()[0] resultType = resultValueDict['result']['result']['subtype'] if resultType == 'null': self.wait(WAIT_REFLESH_3_SECOND) self.switchToNextPage() getExistCmd = self._pageOperator.isElementExist(xpath) resultValueDict = self._networkHandler.send( getExistCmd).getResponse()[0] resultType = resultValueDict['result']['result']['subtype'] self.logger.debug('isElementExist Response --> ' + str(resultValueDict)) return resultType != 'null' def getElementTextByXpath(self, xpath): time.sleep(WAIT_REFLESH_1_SECOND) self.logger.info('xpath ---> ' + xpath) self.switchToNextPage() if self.isElementExist(xpath): getTextCmd = self._pageOperator.getElementTextByXpath(xpath) resultValueDict = self._networkHandler.send( getTextCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'].encode( "utf-8") else: resultValue = None return resultValue def getElementSrcByXpath(self, xpath): time.sleep(WAIT_REFLESH_1_SECOND) self.logger.info('xpath ---> ' + xpath) self.switchToNextPage() if self.isElementExist(xpath): getSrcCmd = self._pageOperator.getElementSrcByXpath(xpath) resultValueDict = self._networkHandler.send( getSrcCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'].encode( "utf-8") else: resultValue = None return resultValue def getElementByXpath(self, xpath): """ :param:目标的xpath :return:返回lxml包装过的element对象,可以使用lxml语法获得对象的信息 例如:可以使用element.get("attrs")来拿到属性的数据 可以用element.text拿到它的文字 当element中含有列表时,使用for循环读取每一个item """ time.sleep(WAIT_REFLESH_1_SECOND) self.logger.info('xpath ---> ' + xpath) htmlData = self.getHtml() if htmlData is not None: html = etree.HTML(htmlData) elementList = html.xpath(xpath) if len(elementList) != 0: return elementList[0] else: self.logger.info('找不到xpath为: ' + xpath + ' 的控件') return '' else: self.logger.info('获取到的html为空') return '' def textElementByXpath(self, xpath, text): """ 先获取输入框的焦点, 再使用Chrome debug协议的输入api,再输入文字内容 :param xpath:输入框的xpath :parm text:要输入的文字 """ self.logger.info('xpath ---> ' + xpath + ' text ---> ' + text) self.focusElementByXpath(xpath) sendStrList = self._pageOperator.textElementByXpath(text) for command in sendStrList: self._networkHandler.send(command) self.wait(WAIT_REFLESH_05_SECOND) def clickElementByXpath(self, xpath, visibleItemXpath=None, byUiAutomator=False): """ 默认滑动点为屏幕的中心,且边距为整个屏幕。当有container时,传入container中任意一个当前可见item的xpath,之后会将目标滑到该可见item的位置 :param xpath: 要滑动到屏幕中控件的xpath :param visibleItemXpath: container中当前可见的一个xpath :return: """ self.logger.info('xpath ---> ' + xpath) self.scrollToElementByXpath(xpath, visibleItemXpath) sendStr = self._pageOperator.getElementRect(xpath) self._networkHandler.send(sendStr) x = self._getRelativeDirectionValue('x') y = self._getRelativeDirectionValue('y') self.logger.debug('clickElementByXpath x:' + str(x) + ' y:' + str(y)) if not byUiAutomator: clickCommand = self._pageOperator.clickElementByXpath(x, y) return self._networkHandler.send(clickCommand) else: xPx, yPx = self.changeDp2Px(x, y) self.d.click(xPx, yPx) def clickFirstElementByText(self, text, visibleItemXpath=None, byUiAutomator=False): """ 通过text来搜索,点击第一个text相符合的控件。参数同clickElementByXpath() """ self.clickElementByXpath('.//*[text()="' + text + '"]', visibleItemXpath, byUiAutomator) def focusElementByXpath(self, xpath): """ 调用目标的focus()方法。 :param xpath:目标的xpath """ executeCmd = self._pageOperator.focusElementByXpath(xpath) self._networkHandler.send(executeCmd) def getElementCoordinateByXpath(self, elementXpath): ''' 获得Element的坐标 :param elementXpath:待获取坐标的元素的xpath :return:element相对于整个屏幕的x、y坐标,单位为px ''' self.logger.info('elementXpathXpath ---> ' + elementXpath) sendStr = self._pageOperator.getElementRect(elementXpath) self._networkHandler.send(sendStr) x = self._getRelativeDirectionValue('x') y = self._getRelativeDirectionValue('y') xPx, yPx = self.changeDp2Px(x, y) return xPx, yPx def scrollToElementByXpath(self, xpath, visibleItemXpath=None, speed=600): """ 默认滑动点为屏幕的中心,且边距为整个屏幕。当有container时,传入container中任意一个当前可见item的xpath,之后会将目标滑到该可见item的位置 :param xpath: 要滑动到屏幕中控件的xpath :param visibleItemXpath: container中当前可见的一个xpath :return: """ self.logger.info('xpath ---> ' + xpath) sendStr = self._pageOperator.getElementRect(xpath) self._networkHandler.send(sendStr) top = self._getRelativeDirectionValue('topp') bottom = self._getRelativeDirectionValue('bottom') left = self._getRelativeDirectionValue('left') right = self._getRelativeDirectionValue('right') self.logger.debug('scrollToElementByXpath -> top:bottom:left:right = ' + str(top) + " :" + str(bottom) \ + " :" + str(left) + " :" + str(right)) if visibleItemXpath is None: endTop = 0 endLeft = 0 endBottom = self.getWindowHeight() endRight = self.getWindowWidth() else: sendStr = self._pageOperator.getElementRect(visibleItemXpath) self._networkHandler.send(sendStr) endTop = self._getRelativeDirectionValue('topp') endBottom = self._getRelativeDirectionValue('bottom') endLeft = self._getRelativeDirectionValue('left') endRight = self._getRelativeDirectionValue('right') self.logger.debug( 'scrollToElementByXpath -> toendTop:endBottom:endLeft:endRight = ' + str(endTop) + " :" + str( endBottom) \ + " :" + str(endLeft) + " :" + str(endRight)) ''' 竖直方向的滑动 ''' if endBottom < bottom: scrollYDistance = endBottom - bottom elif top < 0: scrollYDistance = -(top - endTop) else: scrollYDistance = 0 if scrollYDistance != 0: self.scrollWindow(int((endLeft + endRight) / 2), int((endTop + endBottom) / 2), 0, scrollYDistance, speed) else: self.logger.debug('y方向不需要滑动') ''' 水平方向的滑动 ''' if right > endRight: scrollXDistance = endRight - right elif left < 0: scrollXDistance = -(left - endLeft) else: scrollXDistance = 0 if scrollXDistance != 0: self.scrollWindow(int((endLeft + endRight) / 2), int((endTop + endBottom) / 2), scrollXDistance, 0, speed) else: self.logger.debug('x方向不需要滑动') def scrollWindow(self, x, y, xDistance, yDistance, speed=800): self.logger.info('') sendStr = self._pageOperator.scrollWindow(x, y, xDistance, yDistance, speed) return self._networkHandler.send(sendStr) def _getRelativeDirectionValue(self, directionKey='topp'): ''' 获取相关的方向数据参数值 :param directionKey: key值 :return: ''' directionCommand = self._pageOperator.getJSValue(directionKey) directionResponse = self._networkHandler.send( directionCommand).getResponse()[0] directionValue = directionResponse['result']['result']['value'] return directionValue ''' 性能数据 ''' def getMemoryInfo(self): ''' 获得小程序进程占用内存信息 :return: 小程序进程占用内存信息 ''' self.logger.info('') ADB_SHELL = 'adb shell ' if self._device: ADB_SHELL = ADB_SHELL.replace("adb", "adb -s %s" % self._device) GET_MEMORY_INFO = ADB_SHELL + 'dumpsys meminfo com.tencent.mm:appbrand0' return commandHelper.runCommand(GET_MEMORY_INFO) def getCPUInfo(self): ''' 获得小程序进程占用CPU信息 :return: 小程序进程占用CPU信息 ''' self.logger.info('') ADB_SHELL = 'adb shell ' if self._device: ADB_SHELL = ADB_SHELL.replace("adb", "adb -s %s" % self._device) GET_CPU_INFO = ADB_SHELL + ' top -n 1 | grep com.tencent.mm:appbrand0' return commandHelper.runCommand(GET_CPU_INFO) def getPageHeight(self): getPageHeightCmd = self._pageOperator.getPageHeight() resultValueDict = self._networkHandler.send( getPageHeightCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def getWindowWidth(self): ''' :return:手机屏幕的宽度 ''' getWindowWidthCmd = self._pageOperator.getWindowWidth() resultValueDict = self._networkHandler.send( getWindowWidthCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def getWindowHeight(self): ''' :return:手机屏幕的高度 ''' getWindowHeightCmd = self._pageOperator.getWindowHeight() resultValueDict = self._networkHandler.send( getWindowHeightCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def wait(self, seconds): time.sleep(seconds) def addShutDownHook(self, func, *args, **kwargs): ''' 添加当程序正常关闭时的操作,可以进行一些环境清理操作等 ''' self._vmShutdownHandler.registerVMShutDownCallback( func, *args, **kwargs) def _onUncaughtException(self, exctype, value, tb): self.close() def close(self): if self._networkHandler is not None: self._networkHandler.quit() if self._executor is not None: self._executor.shutDown() UncaughtExceptionHandler.removeHook() def setDebugLogMode(self): ''' 开启debug的日志模式 ''' Log().setDebugLogMode()
class ShortLiveWebSocket(DataTransferCallback): def __init__(self, webSocketDataTransfer, executor, driver): super(ShortLiveWebSocket, self).__init__() self._webSocketDataTransfer = webSocketDataTransfer self._executor = executor self._webSocketDataTransfer.registerCallback(self) self._readWriteSyncEvent = threading.Event() self._connectSyncEvent = threading.Event() self._id = AtomicInteger() self._currentRequest = None self._quit = False self._retryEvent = threading.Event() self.driver = driver self.logger = Log().getLogger() def setUrl(self, url): self._webSocketDataTransfer.setUrl(url) def isConnected(self): return self._webSocketDataTransfer.isConnected() def connect(self): self._webSocketDataTransfer.connect() self._connectSyncEvent.set() def disconnect(self): self._webSocketDataTransfer.disconnect() self._connectSyncEvent.clear() def receive(self): while not self.isQuit(): try: self._waitForConnectOrThrow() self._webSocketDataTransfer.receive() except WebSocketConnectionClosedException: if self.isQuit(): print('already quit') else: pass except Exception: pass def send(self, data, timeout=int(1 * 60)): self._waitForConnectOrThrow() # 微信小程序和QQ公粽号都需要切换页面 if self.driver.getDriverType( ) == WXDRIVER or self.driver.getDriverType == QQDRIVER: # 只有点击事件才会导致页面的切换 if 'x=Math.round((left+right)/2)' in data: time.sleep(WAIT_REFLESH_1_SECOND) if self.driver.needSwitchNextPage(): self.driver.switchToNextPage() self._currentRequest = _NetWorkRequset(self._id.getAndIncrement(), data) currentRequestToJsonStr = self._currentRequest.toSendJsonString() self.logger.debug(' ---> ' + currentRequestToJsonStr) # scroll操作需要滑动到位置才会有返回,如果是scroll操作则等待,防止超时退出 if 'synthesizeScrollGesture' in data: self._webSocketDataTransfer.send(currentRequestToJsonStr) self._retryEvent.wait(WAIT_REFLESH_40_SECOND) else: for num in range(0, SEND_DATA_ATTEMPT_TOTAL_NUM): self.logger.debug(" ---> attempt num: " + str(num)) if num != 3 and num != 5: self._webSocketDataTransfer.send(currentRequestToJsonStr) self._retryEvent.wait(3) else: self.driver.switchToNextPage() time.sleep(WAIT_REFLESH_2_SECOND) self.logger.debug('switch when request: ' + currentRequestToJsonStr) self._webSocketDataTransfer.send(currentRequestToJsonStr) self._retryEvent.wait(WAIT_REFLESH_2_SECOND) if self._readWriteSyncEvent.isSet(): break self._readWriteSyncEvent.wait(timeout=timeout) self._readWriteSyncEvent.clear() self._retryEvent.clear() self._checkReturnOrThrow(self._currentRequest) return self._currentRequest def quit(self): self._quit = True self.disconnect() def isQuit(self): return self._quit def _checkReturnOrThrow(self, netWorkRequest): needThrown = (netWorkRequest.getResponse() is None and netWorkRequest.getException() is None) if needThrown: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_BAD_REQUEST) raise RuntimeError("%s, request data {%s}" % (errorMsg, netWorkRequest.getRequestData())) def _waitForConnectOrThrow(self): if self._webSocketDataTransfer.isConnected(): return self._connectSyncEvent.wait(WAIT_REFLESH_60_SECOND) if not self._webSocketDataTransfer.isConnected(): errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_CONNECT_CLOSED) raise RuntimeError( "connect %s timeout, %s" % (self._webSocketDataTransfer.getUrl(), errorMsg)) def onMessage(self, message): if self._currentRequest is None: raise RuntimeError("current request is None") if message is not None: self._currentRequest.addResponse(message) # 只有当response带有id,所有response才接收完 if json.loads(message).get('id') is not None: self._retryEvent.set() self._readWriteSyncEvent.set() self.logger.debug(' ---> ' + message) else: self.logger.debug(' --->' + 'message is None')
#!/usr/bin/env python # -*- coding: utf-8 -*- import time,os,sys,subprocess,json from appium import webdriver from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions from selenium.webdriver.common.by import By from fastAutoTest.utils.logger import Log from config import * log = Log() log.setLevel(Log.INFO) logger = log.getLogger() class AppiumDriver(object): driver = None def __init__(self): self.device = devices_name self.url = "127.0.0.1" self.port = "4725" self.package_name = package_name self.lanuch_activity = lanuch_activity def init_capability(self): ''' 启动配置文件 :return: ''' desired_caps = { "platformName": "Android",
def setDebugLogMode(self): Log().setDebugLogMode()
class H5Driver(object): def __init__(self, d, device=None): self._device = device self._urlFetcher = None self._webSocketDataTransfer = None self._vmShutdownHandler = VMShutDownHandler() self._networkHandler = None self._pageOperator = H5PageOperator() self.d = d self._hasInit = False self.html = None self.bodyNode = None def initDriver(self): if self._hasInit: return self._executor = SingleThreadExecutor() self._vmShutdownHandler.registerToVM() UncaughtExceptionHandler.init() UncaughtExceptionHandler.registerUncaughtExceptionCallback( self._onUncaughtException) self._selectDevice() self._urlFetcher = H5WebSocketDebugUrlFetcher(device=self._device) url = self._urlFetcher.fetchWebSocketDebugUrl() self._webSocketDataTransfer = WebSocketDataTransfer(url=url) self._networkHandler = ShortLiveWebSocket(self._webSocketDataTransfer, self._executor, self) self._networkHandler.connect() self._executor.put(self._networkHandler.receive) self.logger = Log().getLogger() self.initPageDisplayData() self._hasInit = True self.logger.info('url ----> ' + url) def _selectDevice(self): devicesList = AdbHelper.listDevices(ignoreUnconnectedDevices=True) # 假如没有指定设备,那么判断当前机器是否连接1个设备 if self._device is None: devicesCount = len(devicesList) errorMsg = None if devicesCount <= 0: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_DEVICE_NOT_CONNECT) elif devicesCount == 1: self._device = devicesList[0] else: errorMsg = ErrorMsgManager().errorCodeToString( ERROR_CODE_MULTIPLE_DEVICE) if errorMsg is not None: raise RuntimeError(errorMsg) def wait(self, seconds=1): time.sleep(seconds) def _onUncaughtException(self, exctype, value, tb): self.close() def close(self): if self._networkHandler is not None: self._networkHandler.quit() if self._executor is not None: self._executor.shutDown() self._hasInit = False UncaughtExceptionHandler.removeHook() def addShutDownHook(self, func, *args, **kwargs): self._vmShutdownHandler.registerVMShutDownCallback( func, *args, **kwargs) def getDriverType(self): return constant.H5DRIVER def switchToNextPage(self): """ 把之前缓存的html置为none 再重新连接websocket """ self.logger.debug('') self.html = None self._networkHandler.disconnect() self._networkHandler.connect() def initPageDisplayData(self): driverInfo = self.d.info displayDp = driverInfo.get('displaySizeDpY') displayPx = driverInfo.get('displayHeight') self.scale = (displayPx - 0.5) / displayDp windowHeight = self.getWindowHeight() self.appTitleHeight = displayDp - windowHeight def changeDp2Px(self, xDp, yDp): xPx, yPx = self._pageOperator.changeDp2Px(xDp, yDp, self.scale, self.appTitleHeight) return xPx, yPx def clickElementByXpath(self, xpath, visibleItemXpath=None, duration=50, tapCount=1): """ 默认滑动点为屏幕的中心,且边距为整个屏幕。当有container时,传入container中任意一个当前可见item的xpath,之后会将目标滑到该可见item的位置 :param xpath: 要滑动到屏幕中控件的xpath :param visibleItemXpath: container中当前可见的一个xpath :return: """ self.logger.info('xpath ---> ' + xpath) # 防止websocket未失效但页面已经开始跳转 self.wait(WAIT_REFLESH_05_SECOND) if self.isElementExist(xpath): self.scrollToElementByXpath(xpath, visibleItemXpath) sendStr = self._pageOperator.getElementRect(xpath) self._networkHandler.send(sendStr) x = self._getRelativeDirectionValue("x") y = self._getRelativeDirectionValue("y") self.logger.debug('clickElementByXpath --> x:' + str(x) + ' y:' + str(y)) clickCommand = self._pageOperator.clickElementByXpath( x, y, duration, tapCount) return self._networkHandler.send(clickCommand) else: raise TypeError('未找到控件') def clickFirstElementByText(self, text, visibleItemXpath=None, duration=50, tapCount=1): """ 通过text来搜索,点击第一个text相符合的控件。参数同clickElementByXpath() """ self.clickElementByXpath('.//*[text()="' + text + '"]', visibleItemXpath, duration, tapCount) def longClickElementByXpath(self, xpath, visibleItemXpath=None, duration=2000, tapCount=1): self.clickElementByXpath(xpath, visibleItemXpath, duration, tapCount) def repeatedClickElementByXpath(self, xpath, visibleItemXpath=None, duration=50, tapCount=2): self.clickElementByXpath(xpath, visibleItemXpath, duration, tapCount) def scrollToElementByXpath(self, xpath, visibleItemXpath=None, speed=400): """ 滑动屏幕,使指定xpath的控件可见 默认滑动点为屏幕的中心,且边距为整个屏幕。当有container时,传入container中任意一个当前可见item的xpath,之后会将目标滑到该可见item的位置 :param xpath: 要滑动到屏幕中控件的xpath :param visibleItemXpath: container中当前可见的一个xpath """ self.logger.info('xpath ---> ' + xpath) sendStr = self._pageOperator.getElementRect(xpath) self._networkHandler.send(sendStr) top = self._getRelativeDirectionValue("topp") bottom = self._getRelativeDirectionValue("bottom") left = self._getRelativeDirectionValue("left") right = self._getRelativeDirectionValue("right") if visibleItemXpath is None: endTop = 0 endLeft = 0 endBottom = self.getWindowHeight() endRight = self.getWindowWidth() else: containerSendStr = self._pageOperator.getElementRect( visibleItemXpath) self._networkHandler.send(containerSendStr) endTop = self._getRelativeDirectionValue("topp") endBottom = self._getRelativeDirectionValue("bottom") endLeft = self._getRelativeDirectionValue("left") endRight = self._getRelativeDirectionValue("right") ''' 竖直方向的滑动 ''' if bottom > endBottom: scrollYDistance = endBottom - bottom elif top < 0: scrollYDistance = -(top - endTop) else: scrollYDistance = 0 if scrollYDistance < 0: self.scrollWindow(int((endLeft + endRight) / 2), int((endTop + endBottom) / 2), 0, scrollYDistance - 80, speed) elif scrollYDistance > 0: self.scrollWindow(int((endLeft + endRight) / 2), int((endTop + endBottom) / 2), 0, scrollYDistance + 80, speed) else: self.logger.debug('y方向不需要滑动') ''' 水平方向的滑动 ''' if right > endRight: scrollXDistance = endRight - right elif left < 0: scrollXDistance = -(left - endLeft) else: scrollXDistance = 0 if scrollXDistance != 0: self.scrollWindow(int((endLeft + endRight) / 2), int((endTop + endBottom) / 2), scrollXDistance, 0, speed) else: self.logger.debug('y方向不需要滑动') def getElementCoordinateByXpath(self, elementXpath): ''' 获得Element的坐标 :param elementXpath:待获取坐标的元素的xpath :return:element相对于整个屏幕的x、y坐标,单位为px ''' self.logger.info('elementXpathXpath ---> ' + elementXpath) # 防止websocket未失效但页面已经开始跳转 self.wait(WAIT_REFLESH_05_SECOND) if self.isElementExist(elementXpath): sendStr = self._pageOperator.getElementRect(elementXpath) self._networkHandler.send(sendStr) x = self._getRelativeDirectionValue("x") y = self._getRelativeDirectionValue("y") x, y = self.changeDp2Px(x, y) return x, y errorMessage = ErrorMsgManager().errorCodeToString( ERROR_CODE_GETCOORDINATE) raise RuntimeError(errorMessage) def clearInputTextByXpath(self, xpath): ''' 清空输入框的文字 :param xpath:input框的xpath ''' self.logger.info('xpath ---> ' + xpath) clearInputTextSendStr = self._pageOperator.clearInputTextByXpath(xpath) self._networkHandler.send(clearInputTextSendStr) def getWindowHeight(self): ''' :return:手机屏幕的高度 ''' getWindowHeightCmd = self._pageOperator.getWindowHeight() resultValueDict = self._networkHandler.send( getWindowHeightCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def getWindowWidth(self): ''' :return:手机屏幕的宽度 ''' getWindowWidthCmd = self._pageOperator.getWindowWidth() resultValueDict = self._networkHandler.send( getWindowWidthCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def scrollWindow(self, x, y, xDistance, yDistance, speed=800): """ 通过坐标来滑动(屏幕的左上角为(0,0),向下和向右坐标逐渐增大) :param x: 滑动的起始X点坐标 :param y: 滑动的起始Y点坐标 :param xDistance: X方向滑动的距离 :param yDistance: Y方向滑动的距离 :param speed: 滑动的速度 """ sendStr = self._pageOperator.scrollWindow(x, y, xDistance, yDistance, speed) return self._networkHandler.send(sendStr) def _getRelativeDirectionValue(self, directionKey='topp', contextId=None): ''' 获取相关的方向数据参数值 :param directionKey: 获取的方向 :return: ''' directionCommand = self._pageOperator.getJSValue( directionKey, contextId) directionResponse = self._networkHandler.send( directionCommand).getResponse()[0] directionValue = directionResponse['result']['result']['value'] return directionValue def textElementByXpath(self, xpath, text): """ 先获取输入框的焦点, 再使用Chrome debug协议的输入api,再输入文字内容 :param xpath:输入框的xpath :parm text:要输入的文字 """ self.logger.info('xpath ---> ' + xpath + ' text ---> ' + text) self.focusElementByXpath(xpath) sendStrList = self._pageOperator.textElementByXpath(text) for command in sendStrList: self._networkHandler.send(command) self.wait(WAIT_REFLESH_05_SECOND) def focusElementByXpath(self, xpath): """ 调用目标的focus()方法。 :param xpath:目标的xpath """ executeCmd = self._pageOperator.focusElementByXpath(xpath) self._networkHandler.send(executeCmd) def getHtml(self, nodeId=1): """ 获得指定nodeId的Html代码。在一条websocket连接中,方法只能够执行一次。 :param nodeId: getDocument方法返回的nodeId,当为1时,返回整个body的代码 """ self.logger.info('') if self.html is not None: self.switchToNextPage() self.getDocument() sendStr = self._pageOperator.getHtml(nodeId) self.html = self._networkHandler.send( sendStr).getResponse()[0]['result']['outerHTML'] return self.html def getDocument(self): """ 获得getHtml中需要的nodeId 在调用getHtml之前必须先调用这个方法 """ sendStr = self._pageOperator.getDocument() return self._networkHandler.send(sendStr) def closeWindow(self): """ 关闭整个h5页面 """ sendStr = self._pageOperator.closeWindow() return self._networkHandler.send(sendStr) def returnLastPage(self): """ 返回上一页 """ self.logger.info('') self.wait(WAIT_REFLESH_05_SECOND) sendStr = self._pageOperator.returnLastPage() self._networkHandler.send(sendStr) self.wait(WAIT_REFLESH_05_SECOND) def scrollPickerByXpath(self, xpath): """ 滑动选择picker的选项 要获取四个变量,所以发送了四次消息。 1.先定位到要点击的item 2.找到它的parent(即整个picker的content区域),并且获得要点击item的index,和总共item的个数 3.获得picker的BoundingClientRect。再计算每个item的位置,要滑动的距离 4.因为picker窗口弹出时,默认选择上一次的item,所以先进行一次滑动到顶端。 (为了避免滑动时的惯性,需要设置一个speed属性,控制速度) :param xpath: 要选择的item """ self.logger.info('xpath ---> ' + xpath) sendStr = self._pageOperator.getPickerRect(xpath) self._networkHandler.send(sendStr) startScrollX = self._getRelativeDirectionValue("start_scroll_x") startScrollY = self._getRelativeDirectionValue("start_scroll_y") # 获取整个列表的高度 height = self._getRelativeDirectionValue("height") # 获取要滑动的距离 dex = self._getRelativeDirectionValue("dex") scrollWindowCmd = self._pageOperator.scrollWindow( startScrollX, startScrollY, startScrollX, height) self._networkHandler.send(scrollWindowCmd) scrollWIndowWithSpeedCmd = self._pageOperator.scrollWindow( startScrollX, startScrollY, startScrollX, -dex, speed=72) self._networkHandler.send(scrollWIndowWithSpeedCmd) def getPageHeight(self): getPageHeightCmd = self._pageOperator.getPageHeight() resultValueDict = self._networkHandler.send( getPageHeightCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'] return resultValue def getElementTextByXpath(self, xpath): ''' :param xpath: 目标的xpath :return: 获取到的目标text内容 ''' self.logger.info('xpath ---> ' + xpath) if self.isElementExist(xpath): getTextCmd = self._pageOperator.getElementTextByXpath(xpath) resultValueDict = self._networkHandler.send( getTextCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'].encode( "utf-8") else: resultValue = None return resultValue def getElementSrcByXpath(self, xpath): """ :param xpath: 目标的xpath :return: 获取到的目标src内容 """ self.logger.info('xpath ---> ' + xpath) if self.isElementExist(xpath): getSrcCmd = self._pageOperator.getElementSrcByXpath(xpath) resultValueDict = self._networkHandler.send( getSrcCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'].encode( "utf-8") else: resultValue = None return resultValue def getElementClassNameByXpath(self, xpath): ''' :param xpath:目标的xpath :return: 目标的className ''' self.logger.info('xpath ---> ' + xpath) if self.isElementExist(xpath): getClassNameCmd = self._pageOperator.getElementClassNameByXpath( xpath) resultValueDict = self._networkHandler.send( getClassNameCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'].encode( "utf-8") else: resultValue = None return resultValue def getElementByXpath(self, xpath): """ :param:目标的xpath :return:返回lxml包装过的element对象,可以使用lxml语法获得对象的信息 例如:可以使用element.get("attrs")来拿到属性的数据 可以用element.text拿到它的文字 当element中含有列表时,使用for循环读取每一个item """ self.logger.info('xpath ---> ' + xpath) html = etree.HTML(self.getHtml()) return html.xpath(xpath)[0] def isElementExist(self, xpath, contextId=None): """ :param xpath: 目标的xpath :return: 返回一个boolean,该Element是否存在 """ self.logger.info('xpath ---> ' + xpath) getExistCmd = self._pageOperator.isElementExist(xpath, contextId) resultValueDict = self._networkHandler.send( getExistCmd).getResponse()[0] resultType = resultValueDict['result']['result']['subtype'] num = 0 while resultType == 'null' and num < 3: self.wait(WAIT_REFLESH_2_SECOND) getExistCmd = self._pageOperator.isElementExist(xpath, contextId) resultValueDict = self._networkHandler.send( getExistCmd).getResponse()[0] resultType = resultValueDict['result']['result']['subtype'] num = num + 1 return resultType != 'null' def navigateToPage(self, url): """ 跳转到指定url,在某些微信版本上不生效 :param url: 要跳转的url """ self.logger.info('url ---> ' + url) navigateCmd = self._pageOperator.navigateToPage(url) self._networkHandler.send(navigateCmd) def executeScript(self, script): """ 手动发送js命令并执行 :param script:要执行的js指令 :return: 执行结果 """ executeCmd = self._pageOperator.executeScript(script) resultValueDict = self._networkHandler.send( executeCmd).getResponse()[0] return resultValueDict def getCurrentPageUrl(self): """ 获得当前页面的url :return: """ executeCmd = self._pageOperator.getCurrentPageUrl() resultValueDict = self._networkHandler.send( executeCmd).getResponse()[0] resultValue = resultValueDict['result']['result']['value'].encode( "utf-8") return resultValue ''' 针对跨域IFrame进行的操作 ''' def _getAllNodeId(self): ''' 获得body标签中所有的包含IFrame数据的NodeId :return: ''' nodeIdList = [] nodesList = self.getBodyNode()['params']['nodes'] for node in nodesList: if node.get('contentDocument') is not None: nodeIdList.append(node.get('contentDocument').get('nodeId')) return nodeIdList def getBodyNode(self): ''' :return:获得body中所有node的frameId ''' self.switchToNextPage() self.wait(WAIT_REFLESH_2_SECOND) self.getDocument() executeCmd = self._pageOperator.getBodyNode() resultValueDict = self._networkHandler.send( executeCmd).getResponse()[0] return resultValueDict def requestChildNodes(self, nodeId=5): ''' :param nodeId:指定的nodeId :return: ''' self.switchToNextPage() self.wait(WAIT_REFLESH_2_SECOND) self.getDocument() executeCmd = self._pageOperator.requestChildNodes(nodeId) resultValueDict = self._networkHandler.send( executeCmd).getResponse()[0] return resultValueDict def getIFrameContextId(self): ''' 当body标签中只存在一个IFrame调用 :return:ContextId ''' time.sleep(5) frameIdList = self.getAllIFrameNode() contextList = self.getAllContext() if len(frameIdList) == 1: for contextInfo in contextList: if contextInfo["frameId"] == frameIdList[0]: return contextInfo["contextId"] def getAllContext(self): ''' 获得所有的Context :return: 所有的ContextId,域以及FrameId ''' executeCmd = self._pageOperator.getAllContext() resultValueDict = self._networkHandler.send(executeCmd).getResponse() resultDictList = [] for dict in resultValueDict: if dict.get('result') is None: resultDict = {} if dict.get('params').get('context') is not None: context = dict.get('params').get('context') origin = context.get('origin') contextId = context.get('id') frameId = context.get('auxData').get('frameId') resultDict['origin'] = origin resultDict['contextId'] = contextId resultDict['frameId'] = frameId resultDictList.append(resultDict) return resultDictList def getAllIFrameNode(self): ''' 获得body标签中所有的IFrameNode :return: ''' try: iFrameNodeList = [] nodesList = self.getBodyNode()['params']['nodes'] for node in nodesList: if node['nodeName'] == 'IFRAME': iFrameNodeList.append(node) frameIdList = [] for iFrameNode in iFrameNodeList: frameIdList.append(iFrameNode['frameId']) return frameIdList except: errorMessage = ErrorMsgManager().errorCodeToString( ERROR_CODE_SETUP_FRAME_PAGE) raise RuntimeError(errorMessage) def getIFrameNodeId(self): ''' 当body标签中只存在一个IFrame调用 :return:IFrame的NodeId,可以通过它获得html ''' nodeIdList = self._getAllNodeId() if len(nodeIdList) == 1: return nodeIdList[0] def getIFrameElementCoordinateByXpath(self, elementXpath, iFrameXpath, contextId): ''' 获得IFrame中元素的坐标 :param elementXpath:待获取坐标的元素的xpath :param iFrameXpath: 外层iFrame的xpath :param contextId: iFrame的ContextId :return:element相对于整个屏幕的x、y坐标,单位为px ''' self.logger.info('elementXpathXpath ---> ' + elementXpath + ' iFrameXpath ---> ' + iFrameXpath + ' contextId ---> ' + str(contextId)) # 防止websocket未失效但页面已经开始跳转 self.wait(WAIT_REFLESH_05_SECOND) if self.isElementExist(iFrameXpath): sendStr = self._pageOperator.getElementRect(iFrameXpath) self._networkHandler.send(sendStr) iframeLeft = self._getRelativeDirectionValue("left") iframeTop = self._getRelativeDirectionValue("topp") if self.isElementExist(elementXpath, contextId): sendStr = self._pageOperator.getElementRect( elementXpath, contextId) self._networkHandler.send(sendStr) x = self._getRelativeDirectionValue("x", contextId) y = self._getRelativeDirectionValue("y", contextId) x = iframeLeft + x y = iframeTop + y x, y = self.changeDp2Px(x, y) return x, y errorMessage = ErrorMsgManager().errorCodeToString( ERROR_CODE_GETCOORDINATE) raise RuntimeError(errorMessage) def getIFrameHtml(self, nodeId=None): ''' :param nodeId: :return: 获得指定nodeId的Html ''' self.logger.info('') self.switchToNextPage() self.getDocument() self.wait(WAIT_REFLESH_05_SECOND) sendStr = self._pageOperator.requestChildNodes() self._networkHandler.send(sendStr) if nodeId is None: nodeId = self.getIFrameNodeId() sendStr = self._pageOperator.getHtml(nodeId) result = self._networkHandler.send(sendStr).getResponse()[0] self.html = result.get('result').get('outerHTML') return self.html def getIFrameElementByXpath(self, xpath, nodeId): ''' :param xpath:element的Xpath :param nodeId: element所在页面的nodeId :return: lxml的ELement对象 ''' self.logger.info('xpath ---> ' + xpath) html = etree.HTML(self.getIFrameHtml(nodeId)) self.logger.info(etree.tostring(html)) return html.xpath(xpath)[0] ''' 性能数据 ''' def getMemoryInfo(self): ''' 获得H5进程占用内存信息 :return: H5进程占用内存信息 ''' self.logger.info('') ADB_SHELL = 'adb shell ' if self._device: ADB_SHELL = ADB_SHELL.replace("adb", "adb -s %s" % self._device) GET_MEMORY_INFO = ADB_SHELL + 'dumpsys meminfo com.tencent.mm:tools' return commandHelper.runCommand(GET_MEMORY_INFO) def getCPUInfo(self): ''' 获得H5进程占用CPU信息 :return: H5进程占用CPU信息 ''' self.logger.info('') ADB_SHELL = 'adb shell ' if self._device: ADB_SHELL = ADB_SHELL.replace("adb", "adb -s %s" % self._device) GET_CPU_INFO = ADB_SHELL + ' top -n 1 | grep com.tencent.mm:tools' return commandHelper.runCommand(GET_CPU_INFO) def setDebugLogMode(self): Log().setDebugLogMode()