Example #1
0
 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()
Example #3
0
    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)
Example #5
0
    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)
Example #7
0
    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
Example #9
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()
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
Example #11
0
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)
Example #12
0
 def setDebugLogMode(self):
     '''
     开启debug的日志模式
     '''
     Log().setDebugLogMode()
Example #13
0
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()
Example #14
0
# -*- 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:
Example #15
0
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()
Example #16
0
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')
Example #17
0
#!/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",
Example #18
0
 def setDebugLogMode(self):
     Log().setDebugLogMode()
Example #19
0
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()