Macaca Macaca Android 和 iOS 并行测试

woyebuzhidaowoshishei · 2016年12月20日 · 最后由 luobs 回复于 2017年07月12日 · 3310 次阅读
本帖已被设为精华帖!

前言

从 appium 迁移到了 macaca,研究了一下多机并行跑 case,总结一下,和大家一起讨论一下~~~

1. 获取连接到电脑上的机器

  • Android :利用 adb devices 获取连接上的所有安卓设备。
  • iOS:利用 instruments -s devices 获取连接上的所有 iOS 设备

    class InitDevice:
    """
    获取连接的设备的信息
    """
    def __init__(self):
        self.GET_ANDROID = "adb devices"
        self.GET_IOS = "instruments -s devices"
    
    def get_device(self):
        value = os.popen(self.GET_ANDROID)
    
        device = []
    
        for v in value.readlines():
            android = {}
            s_value = str(v).replace("\n", "").replace("\t", "")
            if s_value.rfind('device') != -1 and (not s_value.startswith("List")) and s_value != "":
                android['platformName'] = 'Android'
                android['udid'] = s_value[:s_value.find('device')].strip()
                android['package'] = 'xxxx'
                android['activity'] = 'xxxxxx'
                device.append(android)
    
        value = os.popen(self.GET_IOS)
    
        for v in value.readlines():
            iOS = {}
    
            s_value = str(v).replace("\n", "").replace("\t", "").replace(" ", "")
    
            if v.rfind('Simulator') != -1:
                continue
            if v.rfind("(") == -1:
                continue
    
            iOS['platformName'] = 'iOS'
            iOS['platformVersion'] = re.compile(r'\((.*)\)').findall(s_value)[0]
            iOS['deviceName'] = re.compile(r'(.*)\(').findall(s_value)[0]
            iOS['udid'] = re.compile(r'\[(.*?)\]').findall(s_value)[0]
            iOS['bundleId'] = 'xxxx'
    
            device.append(iOS)
    
        return device
    

    2. 动态获取 3456 端口后的空闲端口,获取的端口数根据上个方法中获取的设备数

  • 判断端口号是否被占用是去执行 netstat -an | grep port这条命令判断端口号是否被占用

def is_using(port):
    """
    判断端口号是否被占用
    :param port:
    :return:
    """
    cmd = "netstat -an | grep %s" % port

    if os.popen(cmd).readlines():
        return True
    else:
        return False

def get_port(count):
    """
    获得3456端口后一系列free port
    :param count:
    :return:
    """
    port = 3456
    port_list = []
    while True:
        if len(port_list) == count:
            break

        if not is_using(port) and (port not in port_list):
            port_list.append(port)
        else:
            port += 1

    return port_list

3. 开启 macaca 服务,为每一个 service 动态分布一个端口

  • start_server: 开启一个进程池,每一个 device 对应一个 macaca server
  • run_server:运行 macaca server
  • is_running:判断 server 是否有开启成功,判断的方法为:去访问每个 server 对应的http://127.0.0.1:port/wd/hub/status地址,看看返回的状态码是不是以 2 开头。
  • run_test:运行脚本
class macacaServer():
    def __init__(self, devices):

        self.devices = devices
        self.count = len(devices)
        self.url = 'http://127.0.0.1:%s/wd/hub/status'

    def start_server(self):

        pool = Pool(processes=self.count)
        port_list = get_port(self.count)

        for i in range(self.count):
            pool.apply_async(self.run_server, args=(self.devices[i], port_list[i]))

        pool.close()
        pool.join()

    def run_server(self, device, port):

        r = RunServer(port)
        r.start()

        while not self.is_running(port):
            sleep(1)

        server_url = {
            'hostname': "ununtrium.local",
            'port': port,
        }
        driver = WebDriver(device, server_url)
        driver.init()

        DRIVER.set_driver(driver)
        DRIVER.set_OS(device.get("platformName"))

        self.run_test()

    def run_test(self):
        """运行测试
        """
        all_test = AllTests()
        all_test.run_case()

    def is_running(self, port):
        """Determine whether server is running
        :return:True or False
        """
        url = self.url % port
        response = None
        try:
            response = requests.get(url, timeout=0.01)

            if str(response.status_code).startswith('2'):

                # data = json.loads((response.content).decode("utf-8"))

                # if data.get("staus") == 0:
                return True

            return False
        except requests.exceptions.ConnectionError:
            return False
        except ReadTimeout:
            return False
        finally:
            if response:
                response.close()


class RunServer(threading.Thread):

    def __init__(self, port):
        threading.Thread.__init__(self)
        self.cmd = 'macaca server -p %s --verbose' % port

    def run(self):
        os.system(self.cmd)

最后

本来想放个视频看看效果的,但是发现不会搞~~~ 我还是早点睡觉吧。。。。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 86 条回复 时间 点赞
花开 ATX UI 自动化学习-偏 python 语法知识 中提及了此贴 10月22日 00:51
91楼 已删除
王明海 回复

在本地 host 里加入 127.0.0.1 localhost 就好啦。

luobs 回复

xctest client proxy error with: Error: socket hang up 这个问题现在有解决么

>> proxy.js:62:22 [master] pid:2740 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2740 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2740 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2740 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2740 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2740 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2740 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2740 xctest client proxy error with: Error: socket hang up
>> xctest-client.js:257:14 [master] pid:2740 Fail to start xctest: Error: socket hang up
>> xctest-client.js:265:14 [master] pid:2740 killing deviceLogProc pid: 3163
>> xctest-client.js:270:14 [master] pid:2740 killing runnerProc pid: 3166
>> xctest-client.js:276:14 [master] pid:2740 killing iproxyProc pid: 3165
>> responseHandler.js:54:12 [master] pid:2740 Send Error Respone to Client: Error: socket hang up

我的不会重连,然后报错,有解决方案么?

#84 楼 @tongshanshanshan

>> xctest-client.js:171:14 [master] pid:2654 please check project: /usr/local/lib/node_modules/macaca-ios/node_modules/webdriveragent/WebDriverAgent/WebDriverAgent.xcodeproj
>> proxy.js:56:14 [master] pid:2654 Proxy: /session:POST to http://127.0.0.1:8900/session:POST with body: {"desiredCapabilities":{"bundleId":"","platformVersion":"9.1","platformName":"iOS"}}
>> proxy.js:62:22 [master] pid:2654 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2654 xctest client proxy error with: Error: socket hang up
>> xctest-client.js:170:14 [master] pid:2654 writeDictToFile:1278 ==== Successfully wrote Manifest cache to /var/folders/0q/d31vt2395c3gd028gzysbx3r0000gp/C/com.apple.DeveloperTools/All/Xcode/EmbeddedAppDeltas/3c13836573226534d387cb31a1801fd8/aa7cee10b392d3661bd40d4e46778e89e35a6511/ManifestCache.plist

>> xctest-client.js:171:14 [master] pid:2654 please check project: /usr/local/lib/node_modules/macaca-ios/node_modules/webdriveragent/WebDriverAgent/WebDriverAgent.xcodeproj
>> proxy.js:62:22 [master] pid:2654 xctest client proxy error with: Error: socket hang up
>> proxy.js:62:22 [master] pid:2654 xctest client proxy error with: Error: socket hang up
>> proxy.js:84:20 [master] pid:2654 Got response with status 200: {"value":{"sessionId":"87BB2534-5D9B-433F-B6BE-3CC2B74F5E4F","capabilities":{"device":"iphone","browserName":"Mobi","sdkVersion":"9.1","CFBundleIdentifier":"com.btcc.mobiEntStaging"}},"sessionId":n...
>> responseHandler.js:47:14 [master] pid:2654 Send HTTP Respone to Client: {"sessionId":"38401d3f-62a8-4c4e-af07-8d930d8bf65d","status":0,"value":"{\"bundleId\":\"com.btcc.mobiEntStaging\",\"platformVersion\":\"9.1\",\"deviceName\":\"BTCCiPodtouch\",\"platformName\":\"iOS\",\"udid\":\"aa7cee10b392d3661bd40d4e46778e89e35a6511\"}"}
>> responseHandler.js:11:12 [master] pid:2654 Recieve HTTP Request from Client: method: POST url: /wd/hub/session/38401d3f-62a8-4c4e-af07-8d930d8bf65d/element, jsonBody: {"using":"name","value":"me"}

#83 楼 @Test_Sir 我试了一下 2 台机子并行,会偶先 socket hang up 这个错误,但是不是必现的,出现这个状况后,等待,服务会重新连接。然后我重新连接的,都能正常运行。你是一直都是这个错误,不会重连么?

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from macaca import WebDriver
import time
from parameters.conf_server import *
import threading
import requests
from multiprocessing.pool import Pool
from middleware.all_case import *
from parameters.run import *
from parameters.configure import *
from parameters.conf_driver import *
from parameters.conf_client import *

# class macacaServer():
#     def __init__(self, devices):
#
#         self.devices = devices
#         self.count = len(devices)
#         self.url = 'http://127.0.0.1:%s/wd/hub/status'

def start_server(devices):
    # 进程池
    count = len(devices)
    pool = Pool(processes=count)
    port_list = get_port(count)

    for i in range(count):
        pool.apply_async(run_server, args=(devices[i], port_list[i]))
        time.sleep(3)
        #self.run_server(self.devices[i],port_list[i])

    pool.close()
    pool.join()

def run_server(device, port):
    r = RunServer(port)
    r.start()

    while not is_running(port):
        time.sleep(1)

    server_url = {
        'hostname': server_ip,
        'port': port,
    }
    driver = WebDriver(device, server_url)
    driver.init()

    DRIVER.set_driver(driver)
    DRIVER.set_OS(device.get("platformName"))

    run_test(driver)

def run_test(driver):
    """
        运行测试
    """
    test_run(driver,login_pages)

def is_running(port):
    """Determine whether server is running
    :return:True or False
    """
    url = 'http://127.0.0.1:%s/wd/hub/status'
    url1 = url % port
    response = None
    try:
        response = requests.get(url1, timeout=0.01)

        if str(response.status_code).startswith('2'):
            return True

        return False
    except requests.exceptions.ConnectionError:
        return False
    except requests.exceptions.ReadTimeout:
        return False
    finally:
        if response:
            response.close()


class RunServer(threading.Thread):
    def __init__(self, port):
        threading.Thread.__init__(self)
        self.cmd = 'macaca server -p %s --verbose' % port

    def run(self):
        os.system(self.cmd)

if __name__ == "__main__":
    b = InitDevice()
    start_server(b.get_device())

#81 楼 @Test_Sir 是否可以提供一下代码?

#80 楼 @tongshanshanshan 不加入并行执行的代码,只用我写的简单的 Demo 两个手机分别执行没有问题。但是用并行执行的代码,只能一个手机能够启动应用程序。是这样的

#76 楼 @Test_Sir
单个手机运行时,同样的代码不同的手机,但是其他测试机在我的项目里运行启动不起来,我的手机怎么启动都可以,
两个手机单独还能正常执行
这有点矛盾啊?

老马 关于 Macaca 的并发测试 (iOS) 中提及了此贴 02月20日 10:04

#71 楼 @tongshanshanshan 单个手机运行时,同样的代码不同的手机,但是其他测试机在我的项目里运行启动不起来,我的手机怎么启动都可以,什么原因呢. 不加入你的并发执行的代码,两个手机单独还能正常执行

#71 楼 @tongshanshanshan 楼主,两部手机只有一个能启动起来

#73 楼 @Tank007 就是和你一样,但我用的是 macaca,开启两个服务端,分别指定不同的端口。但是,客户端出现的情况和你的一模一样

匿名 #20 · 2017年02月17日

#72 楼 @sixleaves 你的问题具体是什么,还没搞明白,是同一个端口么?

关于 macaca 的并发测试https://testerhome.com/topics/7499

我这边也出现了和上边你们讨论的和 Appium 一模一样的问题。不知道为什么? @Tank007

#70 楼 @Test_Sir 将 driver 做成一个类的属性,这样你在获取的时候就直接 DRIVER.driver 这样用,不管在哪个文件里面都是这样调用,进程间互相不影响。

楼主,我的执行以后发现会先执行给 element.py 二次封装事件中的 driver= DRIVER.driver 先赋值 None,后续不再赋值了,导致调用 driver.element('','') 时报错,能把你的框架思路简单说下嘛,如何在其他文件里面正常调用 driver

#62 楼 @Test_Sir 关于 element.py 二次封装的操作事件,driver = DRIVER.driver,这样获取会有问题,获取的是值我打断点得到的是 None,楼主有方法解嘛,现在就卡在获取 driver


打完断点,都没有经过 run_server 方法,就结束了,也没有报错

#66 楼 @Test_Sir 请上完整的 log。

楼主,按照你的这种代码,我运行了一个 demo,调用 start_server 会先执行 case,再搜索手机,端口信息

except requests.exceptions.ReadTimeout:
            return False

#63 楼 @tongshanshanshan 导入我的 import ReadTimeout ,会提示 ReadTimeout 不存在,是需要导入到 pycharm 什么文件呢

#62 楼 @Test_Sir 是的, driver 就直接获取, ReadTimeout 导入就可。

操作事件我都封装到 element 里面了,如果里面想用 driver 是不是 driver = DVIVER.driver 就可以了
except ReadTimeout 这个是需要导入,还是自己创建一个 py 文件

#60 楼 @Test_Sir 进程池啊。

from multiprocessing.pool import Pool
pool = Pool(processes=self.count)

这里的 Pool 是什么?

#28 楼 @tongshanshanshan 楼主的文章很好,就是对于小白来说很多看不懂,可以教教我怎么快速上手进行真机测试吗?非常想学,可是没头绪,谢谢楼主。

@tongshanshanshan 请问楼主 ,你有试过在 多台 iOS 真机设备上并发执行测试用例过吗?是否了解 Macaca 有一个最大的并发数?我自己现在在 两台 iOS 真机设备上并行测试没什么问题,但是一旦添加一台设备以后,也就是 3 台设备同时测试的时候,总会出现一些莫名其妙的问题,而且也总是 有一台设备无法正常启动测试用例,而且还会 一直报 xctest client proxy error with: Error: socket hang up 这样的错误 ,请问楼主有碰到过这样的问题吗?

#56 楼 @carl 支持的。appium 我没有试过,你可以跟 @Tank007 他讨论一下,他在研究呢。

#55 楼 @tongshanshanshan
请问是不是也支持一台 mac 同时连接多台 iOS 啊?据说 appium 不支持一台 mac 连接多台 iOS 设备测试
感谢

这是一台 iOS 和一台 Android 同时测试,还是支持多台 iOS 同时测试啊?

匿名 #40 · 2017年01月12日

#52 楼 @tongshanshanshan 目前从日志来看,是这样的

#51 楼 @Tank007 所以他默认就是 8100,同时起 2 个还是 8100?

匿名 #42 · 2017年01月12日

#49 楼 @tongshanshanshan wda 启动成功后的日志

2017-01-12 16:49:00.609 XCTRunner[10928:159660] Running tests...
2017-01-12 16:49:02.465 XCTRunner[10928:159660] Continuing to run tests in the background with task ID 1
Test Suite 'Selected tests' started at 2017-01-12 16:49:03.066
Test Suite 'WebDriverAgentRunner.xctest' started at 2017-01-12 16:49:03.067
Test Suite 'UITestingUITests' started at 2017-01-12 16:49:03.068
Test Case '-[UITestingUITests testRunner]' started.
    t =     0.00s     Start Test at 2017-01-12 16:49:03.069
    t =     0.00s     Set Up
2017-01-12 16:49:03.077 XCTRunner[10928:159660] Built at Jan  6 2017 09:14:36
2017-01-12 16:49:03.136 XCTRunner[10928:159660] ServerURLHere->http://10.180.185.159:8100<-ServerURLHere
匿名 #50 · 2017年01月12日

#49 楼 @tongshanshanshan 跟你启动 macaca 差不多,都是一条命令,appium -a ip -p port,这个 port 就是启动的 appium 的端口,目前还没看到过指定 wda 的端口

#48 楼 @Tank007 我觉得应该是使用不同的端口。appium 在启动的时候只能指定 appium server 的端口么,不能知道 wda 的端口?

匿名 #45 · 2017年01月12日

@seveniruby 请教一个问题,多台设备并发使用 appium 的服务,所有设备都使用一个端口(8100),还是使用不同的端口,macaca 是用不同的端口耶

匿名 #46 · 2017年01月12日

#44 楼 @tongshanshanshan 是并行的 log,我分别手动开了两个 appium 的服务,然后分别截取的日志

匿名 #47 · 2017年01月12日

@tongshanshanshan 这两个设备用的 wda 的同一个端口进行通信,都是 8100,是这个问题么?

匿名 #48 · 2017年01月12日

#43 楼 @tongshanshanshan 嗯,那么问题来了,我怀疑是我这边用的是同一个端口,我再仔细看看

#42 楼 @Tank007
你这个是并行的 log 吧,如果是,我感觉就是这个问题。

#42 楼 @Tank007 我刚刚运行了一下,发现用的是 2 个不同的端口。

>> xctest-client.js:56:14 [master] pid:10556 project path: /usr/local/lib/node_modules/macaca-ios/node_modules/webdriveragent/WebDriverAgent/WebDriverAgent.xcodeproj
>> xctest-client.js:56:14 [master] pid:10555 project path: /usr/local/lib/node_modules/macaca-ios/node_modules/webdriveragent/WebDriverAgent/WebDriverAgent.xcodeproj
>> macaca-ios.js:147:10 [master] pid:10556 {
    "bundleId": "com.btcc.mobiEntStaging",
    "platformVersion": "9.3.5",
    "platformName": "iOS"
}
>> macaca-ios.js:147:10 [master] pid:10555 {
    "bundleId": "com.btcc.mobiEntStaging",
    "platformVersion": "10.0.1",
    "platformName": "iOS"
}
>> macaca-ios.js:151:12 [master] pid:10556 Trying to start wda server...
>> macaca-ios.js:151:12 [master] pid:10555 Trying to start wda server...
(node:10556) DeprecationWarning: Calling an asynchronous function without callback is deprecated.
(node:10555) DeprecationWarning: Calling an asynchronous function without callback is deprecated.
(node:10555) DeprecationWarning: Calling an asynchronous function without callback is deprecated.
(node:10556) DeprecationWarning: Calling an asynchronous function without callback is deprecated.
BundleId com.apple.test.WebDriverAgentRunner-Runner does not exist.
>> xctest-client start with port: 8900
>> xctest-client.js:233:14 [master] pid:10556 xcode version: 8.2
>> WebDriverAgent version: 1.0.38
>> xctest-client start with port: 8901
>> xctest-client.js:233:14 [master] pid:10555 xcode version: 8.2
>> WebDriverAgent version: 1.0.38
匿名 #51 · 2017年01月12日

#41 楼 @tongshanshanshan 一个道理啊,我也是只要设置 appium 运行的端口就可以了,不应该啊,macaca 也调用 wda 的,也有默认端口的呀

没有,我只要设置 macaca server 运行的端口就行了。

匿名 #53 · 2017年01月12日

#39 楼 @tongshanshanshan 你那边运行的时候,有这种端口问题么?用 macaca 的时候

#38 楼 @Tank007 那你可以修改 wda 的端口么? 2 个分别用不同的端口呢。

匿名 #38 · 2017年01月12日

#37 楼 @tongshanshanshan 我也不知道啊,这个是 wda 的默认端口,会不会和 appium 的端口没有关系?单独跑的时候也没见跑不了啊

#36 楼 @Tank007 这边啊
[debug] [JSONWP Proxy] Proxying [POST /session] to [POST http://localhost:8100/session] with b 为啥是都是 8100 端口,你这里不是设置的端口号是 4724 么?

匿名 #36 · 2017年01月12日

@tongshanshanshan 而且更奇葩的是,我出现过不少次,感觉线程堵塞了,就是设备用例跑不起来,杀掉 node 都不行,需要重启电脑才可以,还有把我 5s 手机给跑的发烫,然后卡的要死,也要重启才会好。从开始弄多线程,都快疯了都。。。

匿名 #58 · 2017年01月12日

#33 楼 @tongshanshanshan 这个方法应该是没什么问题,获取的信息也是对的,单独调试这个方法可以获取到正确的设备信息。从我调试的结果来看,driver 初始化的时候,这里的日期打印时间,两个设备不是同一个时间,其他包括启动 appium 的服务,获取设备信息都是同一个时间(差几秒),所以,是不是 driver 这里有问题?

匿名 #59 · 2017年01月12日

#33 楼 @tongshanshanshan 这个方法基本是按照你的样例写的

def get_device(self):
    '''
    获取真实设备列表
    :param deviceName:
    :param deviceType:
    :return:
    '''
    # 存储设备信息
    device = []
    # value_ios = os.popen(self.Get_iOS)
    isMonitor = eval(read_config('appium', 'isMonitor'))
    # 获取模拟器设备列表
    if isMonitor:
        iOS_Monitor = {}
        deviceName = read_config('appium', 'deviceName')
        platformVersion = read_config('appium', 'platformVersion')
        iOS_Monitor['deviceName'] = deviceName
        # deviceName = str(deviceName).replace(' ','')
        for dev in os.popen(self.Get_iOS).readlines():
            dev_value = str(dev).replace("\n", "").replace("\t", "").replace(" ", "")
            # print dev_value
            if dev_value.rfind(deviceName) == -1:
                continue
            if dev_value.rfind(platformVersion) == -1:
                continue
            # print dev_value
            re_deviceName = re.compile(r'(.*)\(').findall(dev_value)[0]
            re_deviceName2 = re.compile(r'(.*)\(').findall(re_deviceName)[0]
            # print 're_deviceName2: ' , re_deviceName2
            # print 'deviceName: ',deviceName
            if re_deviceName2 != deviceName:
                continue
            # if re_deviceName != deviceName:
            #   continue
            iOS_Monitor['udid'] = re.compile(r'\[(.*?)\]').findall(dev_value)[0]
            iOS_Monitor['platformVersion'] = platformVersion
            iOS_Monitor['platformName'] = 'iOS'
        device.append(iOS_Monitor)
    else:
        pass
    # 是否运行真机
    isRealDevice = eval(read_config('appium', 'isRealDevice'))
    if isRealDevice:
        # 获取iOS真实设备列表
        for dev in os.popen(self.Get_iOS).readlines():
            iOS = {}
            Android = {}
            dev_value = str(dev).replace("\n", "").replace("\t", "").replace(" ", "")
            if dev.rfind('Simulator') != -1:
                continue
            if dev.rfind('(') == -1:
                continue
            # print dev_value
            # iOS['platformName'] = read_config('appium', 'platformName')
            # iOS['bundleId'] = read_config('appium','bundleId')
            iOS['deviceName'] = re.compile(r'(.*)\(').findall(dev_value)[0]
            iOS['platformVersion'] = re.compile(r'\((.*)\)').findall(dev_value)[0]
            iOS['udid'] = re.compile(r'\[(.*?)\]').findall(dev_value)[0]
            iOS['platformName'] = 'iOS'

            device.append(iOS)
        # print iOS

        # 命令获取Android设备列表
        for dev in os.popen(self.Get_Android).readlines():
            dev_value = str(dev).replace("\n", "").replace("\t", "")
            if dev_value.rfind('device') != -1 and (not dev_value.startswith("List")) and dev_value != "":
                Android['udid'] = dev_value[:dev_value.find('device')].strip()
                Android['platformName'] = 'Android'
                device.append(Android)
    else:
        pass

    print time.ctime(), ' [', __name__, '::', AppiumServer.get_device.__name__, '] :', ' device =  ', device
    return device

#32 楼 @Tank007
你好,我可以看看你的 devices = appium_server.get_device() 这个的 devices 的具体代码么?

匿名 #32 · 2017年01月12日

#30 楼 @tongshanshanshan 还是得请教你,那个 pid 的问题已经解决了。现在问题好奇怪,我一台模拟器,一台手机,结果设置参数串了,目前单独跑都没问题,一并行跑,就只能跑一台,但是两台的 wda 都能 build 上,其中真机系统版本 10.0.2,模拟器 10.2,但是真机日志中发现出现了 10.2 的系统版本,有点懵了,你能看出问题么?
代码:


def Run_one(device,port):
    print 'Run task %s (%s) at %s' % (str(port), os.getpid(), time.ctime())
    S.set_device(device)

    from src.lib.Log import LogSignleton
    logsignleton = LogSignleton(device)
    logger = logsignleton.logger
    L.set_logger(logger)
    print '*' * 80
    print time.ctime(),' [', __name__, '::', Run_one.__name__, '] :', ' logger =  ', logger

    # 启动appium 服务
    appium_server.start_server(device, port)

    # 实例化Dirver
    Dr = Driver(device, port)
    Dr.init()  # 初始化driver
    driver = Dr.getDriver()
    D.set_driver(driver)
    print '*' * 80
    print time.ctime(),' [', __name__, '::', Run_one.__name__, '] :', ' driver =  ', driver

    # 生成runner
    runner = unittest.TextTestRunner()
    # 循环遍历测试用例列表
    for case_list in case_sheet1[1:]:
        test_suite = __get_test_suite(case_list)
        runner.run(test_suite)

    # 生成测试报告
    RunExcel.get_html_report()

    # run_mode() # 运行模式

if __name__ == '__main__':
    appium_server = AppiumServer()
    devices = appium_server.get_device()
    count = len(devices)
    ports = appium_server.get_port(count)
    print 'Run task pid: (%s) at: %s' % ( os.getpid(), time.ctime())
    print '*'*80
    p = Pool(count)
    for i in range(count):
        p.apply_async(Run_one,(devices[i],ports[i],))

    p.close()
    p.join()
    print 'All finished'
真机
appium -a 127.0.0.1 -p 4724 -U bcf1530f7fae062a4433b658b75f4bde43aaf15f

[debug] [XCUITest] Waiting up to 60000ms for WebDriverAgent to start
[debug] [XCUITest] Log file for xcodebuild test: /Users/zengyuanchen/Library/Developer/Xcode/DerivedData/WebDriverAgent-fuwmnzdhnozghbgmpkthbcubvqlr/Logs/Test/0F3BAEF0-6655-49DC-9B93-A987D6305E75/Session-WebDriverAgentRunner-2017-01-12_114216-Yga1Hy.log
[debug] [XCUITest] WebDriverAgent successfully started after 14389ms
[debug] [iProxy] recv failed: Resource temporarily unavailable
[debug] [iProxy] recv failed: Operation not permitted
[debug] [XCUITest] Sending createSession command to WDA
[debug] [JSONWP Proxy] Proxying [POST /session] to [POST http://localhost:8100/session] with body: {"desiredCapabilities":{"bundleId":"rym.pingan.rympush","arguments":[],"environment":{},"shouldWaitForQuiescence":true}}
[debug] [JSONWP Proxy] Got response with status 200: {"value":{"sessionId":"9DDA03DA-857B-4768-957D-C81CD9B1C2A4","capabilities":{"device":"iphone","browserName":"任意门","sdkVersion":"10.0.2","CFBundleIdentifier":"rym.pingan.rympush"}},"sessionId":"9DDA03DA-857B-4768-957D-C81CD9B1C2A4","status":0}
[debug] [XCUITest] Setting initial orientation to 'PORTRAIT'
[debug] [JSONWP Proxy] Proxying [POST /orientation] to [POST http://localhost:8100/session/9DDA03DA-857B-4768-957D-C81CD9B1C2A4/orientation] with body: {"orientation":"PORTRAIT"}
[debug] [iProxy] recv failed: Resource temporarily unavailable
[debug] [iProxy] recv failed: Operation not permitted
[debug] [JSONWP Proxy] Got response with status 200: {"value":{},"sessionId":"9DDA03DA-857B-4768-957D-C81CD9B1C2A4","status":0}
[Appium] New XCUITestDriver session created successfully, session 23b7a53e-9784-43c8-8b25-f9b6db07c151 added to master session list
[debug] [MJSONWP] Responding to client with driver.createSession() result: {"webStorageEnabled":false,"locationContextEnabled":false,"browserName":"","platform":"MAC","javascriptEnabled":true,"databaseEnabled":false,"takesScreenshot":true,"networkConnectionEnabled":false,"deviceName":"Rym'iPhone","udid":"bcf1530f7fae062a4433b658b75f4bde43aaf15f","autoAcceptAlerts":true,"automationName":"XCUITest","noReset":true,"WaitForAppScript":true,"platformVersion":"10.2","platformName":"iOS","bundleId":"rym.pingan.rympush"}
[HTTP] <-- POST /wd/hub/session 200 21123 ms - 514 

下面是模拟器

模拟器
appium -a 127.0.0.1 -p 4723 -U 32E6D124-29B6-48A2-9B38-0A9D54121E10  

[debug] [XCUITest] WebDriverAgent successfully started after 829ms
[debug] [XCUITest] Sending createSession command to WDA
[debug] [JSONWP Proxy] Proxying [POST /session] to [POST http://localhost:8100/session] with body: {"desiredCapabilities":{"bundleId":"rym.pingan.rympush","arguments":[],"environment":{},"shouldWaitForQuiescence":true}}
[debug] [JSONWP Proxy] Got response with status 200: {"value":{"sessionId":"A85E029D-38D3-407F-96E0-44020D1279C7","capabilities":{"device":"iphone","browserName":"任意门","sdkVersion":"10.0.2","CFBundleIdentifier":"rym.pingan.rympush"}},"sessionId":"A85E029D-38D3-407F-96E0-44020D1279C7","status":0}
[debug] [XCUITest] Setting initial orientation to 'PORTRAIT'
[debug] [JSONWP Proxy] Proxying [POST /orientation] to [POST http://localhost:8100/session/A85E029D-38D3-407F-96E0-44020D1279C7/orientation] with body: {"orientation":"PORTRAIT"}
[debug] [JSONWP Proxy] Got response with status 200: {"value":{},"sessionId":"A85E029D-38D3-407F-96E0-44020D1279C7","status":0}
[Appium] New XCUITestDriver session created successfully, session 128bc2f7-3c59-4495-a9b5-e77cf62e5565 added to master session list
[debug] [MJSONWP] Responding to client with driver.createSession() result: {"webStorageEnabled":false,"locationContextEnabled":false,"browserName":"","platform":"MAC","javascriptEnabled":true,"databaseEnabled":false,"takesScreenshot":true,"networkConnectionEnabled":false,"deviceName":"iPhone6s","udid":"32E6D124-29B6-48A2-9B38-0A9D54121E10","autoAcceptAlerts":true,"automationName":"XCUITest","noReset":true,"WaitForAppScript":true,"platformVersion":"10.2","platformName":"iOS","bundleId":"rym.pingan.rympush"}
[HTTP] <-- POST /wd/hub/session 200 22330 ms - 508 
匿名 #31 · 2017年01月10日

#30 楼 @tongshanshanshan 你看上面的日志,有打印啊,print res.get() 这样用会报错 “cPickle.PicklingError: Can't pickle : attribute lookup builtin.instancemethod failed”

#29 楼 @Tank007
print 'All subprocesses done.'
for res in result:
print res.get

这个也没有打印出来么?
还有啊,是 print res.get()

匿名 #29 · 2017年01月10日

#28 楼 @tongshanshanshan 确实哦,没注意看,现在把打印加入到 run 里面了,但是没有日志输出

进程数:  2
端口列表:  [4723, 4724]
All subprocesses done.
<bound method ApplyResult.get of <multiprocessing.pool.ApplyResult object at 0x10bc72310>>
<bound method ApplyResult.get of <multiprocessing.pool.ApplyResult object at 0x10bc72390>>
appium 相关进程列表 = ['60663\n'] 
CleanProcess:Darwin:kill appium

#27 楼 @Tank007 你这样打印当然就是一个进程了,你这里打印出来的都是主进程的 pid。去 self.run 里面打印。

匿名 #27 · 2017年01月10日

#26 楼 @tongshanshanshan 现在改用 pool.apply_async,我把进程 id 也给打印出来,发现两次的进程 id 居然是一样的,不科学啊。

result = []
        for i in range(self.count):
            print 'device = %s,port: %s' % (self.devices[i],str(pool_list[i]))
            print 'Run task %s (%s)...' % (i, os.getpid())
            res_pool = pool.apply_async(self.run, (self.devices[i], pool_list[i]))
            result.append(res_pool)

        pool.close()
        pool.join()
        print 'All subprocesses done.'
        for res in result:
            print res.get

日志

进程数:  2
端口列表:  [4723, 4724]
Run task 0 (59622)...
Run task 1 (59622)...
All subprocesses done.
<bound method ApplyResult.get of <multiprocessing.pool.ApplyResult object at 0x1059bc310>>
<bound method ApplyResult.get of <multiprocessing.pool.ApplyResult object at 0x1059bc390>>
appium 相关进程列表 = ['59652\n'] 
CleanProcess:Darwin:kill appium

#25 楼 @Tank007

driver = WebDriver(device, server_url)
driver.init()

这个方法不是我实现的,是 macaca 自己封装好,用来初始化 driver 的。

pool.apply_async(self.run, (self.devices[i], pool_list[i])) 并行执行不了,一运行就结束了

这个是因为在在执行 self.run 这个方法的过程中报错了,所以提前结束。但是并没有把报错信息给打印出来。你可以添加以下代码,就可以看见报错信息了。

result = []
result.append(pool.apply_async(self.run_server, args=(self.devices[i], port_list[i]))
for res in result:
        print(res.get())

至于最后一个 [ ERROR ] Message: Session does not exist:
你确定所有服务都起来之后才运行用例的么?

匿名 #68 · 2017年01月10日

#15 楼 @tongshanshanshan

def run_server(self, device, port):

        r = RunServer(port)
        r.start()

        while not self.is_running(port):
            sleep(1)

        server_url = {
            'hostname': "ununtrium.local",
            'port': port,
        }
        driver = WebDriver(device, server_url)
        driver.init()

        DRIVER.set_driver(driver)
        DRIVER.set_OS(device.get("platformName"))

        self.run_test()

在这个方法里面,你是如何实现的

driver = WebDriver(device, server_url)
driver.init()

我现在遇到的问题是,pool.apply_async(self.run, (self.devices[i], pool_list[i])) 并行执行不了,一运行就结束了,换了种方法

threadpool = []
    for i in range(self.count):
        t = RunThread(self.devices[i],pool_list[i])
        threadpool.append(t)

    for j in threadpool:
        j.start()

    for k in threadpool:
        k.join()

现在手机和模拟器的 WDA 都能自动 build 上去,但是运行用例的时候,只能跑一台机器,有一个会 [ ERROR ] Message: Session does not exist ,请问是哪里用错了么?

#23 楼 @zyyuyu123

  • apply_async 运行时根本没有调用 run_server 方法 没有报错么?如果是直接结束了 ,可以定义一个 result,将报错信息打印出来
result = []
result.append(pool.apply_async(self.run_server, args=(self.devices[i], port_list[i]))
for res in result:
        print(res.get())
  • macaca server -p %s --verbose 是否需要守护进程去调用,服务会一直保持启动,apply_async 不会回到主进程 会一直保持启动。会回到主进程
pool.apply_async(self.run_server, args=(self.devices[i], port_list[i])

class RunServer(threading.Thread):

    def __init__(self, port):
        threading.Thread.__init__(self)
        self.cmd = 'macaca server -p %s --verbose' % port

    def run(self):
        os.system(self.cmd)

这两段启动 macaca server port 的代码,我运行没有成功,apply_async 运行时根本没有调用 run_server 方法。还有 macaca server -p %s --verbose 是否需要守护进程去调用,服务会一直保持启动,apply_async 不会回到主进程?

#21 楼 @tongshanshanshan 看来自动化测试服务器 还是要一步到位弄个 mac 机 这样既可以连 ios 机又可以连 android 机....

是的,这可以的。但是我的电脑是 mac。

#18 楼 @tongshanshanshan 就是跑用例的自动化测试服务环境,我们目前就是一台电脑 ubuntu16.04,部署了 macaca-cli 和 macaca-android 还有就是 android-sdk 执行环境,只能跑安卓 app 的用例。要是跑 ios app 的话那不是得 mac 系统环境吗?你这套脚本可以同时一个环境下去检测连接到这个环境的 ios 和 android?同时在这个环境下去并行安卓和苹果 app 用例?

楼主可以给个 windows,安卓版本用的软件么?Node.js 和需要 Node.js 安装的东西

#17 楼 @harsayer 不好意思啊,你这里的 “测试执行服务环境” 指的是什么?我不太明白哎。。我是在一台电脑上同时根据不同的端口开启了多个 macaca server。

@tongshanshanshan 你好,咨询下,有点没看懂你这么做的含义.意思是 你这套 py 脚本 是跑在一个 "测试执行服务环境" 的吗?
那么 这个"测试执行服务环境" 是一个什么平台下的环境 是支持 android APP 测试的 环境 还是支持 ios APP 测试的环境?

这个测试执行服务环境 不可能既是 android 又是 ios 的吧?

还是 你这个脚本 是去两套测试执行环境下去分别驱动 andriod 和 ios 的 否则 怎么叫 Android 和 iOS 并行测试.

这个并行 是在一套测试环境下的 还是 分别两套安卓和苹果环境下的?

匿名 #77 · 2016年12月28日

#15 楼 @tongshanshanshan 我现在的做法是,直接在 Element 这里取 driver,其他地方不再取了,不过还没驱动起来,😂 ,既然确定了设计思路没问题,那就再试试,多谢楼主,我再调试一下,实在不行,再跟你请教

#14 楼 @Tank007 会,在你封装的类里面直接取 DRIVER.driver 就可以了,这里取的就是你设置的 driver。

匿名 #79 · 2016年12月28日

#13 楼 @tongshanshanshan 这个肯定是没问题的,其实我想知道的是,比如我进行 api 的一些二次封装,我肯定要传 driver 进来的,我现在遇到的问题是,比如我的二次封装类为 Element,初始化的时候 self.driver = DRIVER.driver,那么会取到 set_driver 传进来的 driver 么?

#12 楼 @Tank007 case 刚开始的时候,获取 driver = DRIVER.driver, 后面使用的时候,可以直接 driver.find_element_by_id() 这样使用。

匿名 #12 · 2016年12月28日

#11 楼 @tongshanshanshan 嗯,差不多啊,我也是这样用的,

class DRIVER:

    driver = None

传值的时候,DRIVER.driver = driver(后面的 driver 为获取到的),那么,driver 到这里后,api 调用的时候呢?

#10 楼 @Tank007 我是定义了一个 DRIVER 类,具体内容为

class DRIVER:

    driver = None
    OS = None

    @classmethod
    def set_driver(cls, driver):
        cls.driver = driver

    @classmethod
    def set_OS(cls, OS):
        cls.OS = OS

在 case 中这样获取 DRIVER.driver

匿名 #10 · 2016年12月28日
def run_server(self, device, port):

       r = RunServer(port)
       r.start()

       while not self.is_running(port):
           sleep(1)

       server_url = {
           'hostname': "ununtrium.local",
           'port': port,
       }
       driver = WebDriver(device, server_url)
       driver.init()

       DRIVER.set_driver(driver)
       DRIVER.set_OS(device.get("platformName"))

       self.run_test()

楼主,你的 DRIVER.set_driver(driver),driver 是怎么传递的?这个地方能详细解释一下吗?

请更新微信或者微信二维码. testerhomer 要打赏

#6 楼 @AllocAndInit 启动的时候有没有指定端口,测试运行的时候有没有指定相应的端口?你这样问我只能猜,能不能上脚本和相关 log?

我想在真机上进行 iOS 的并行测试,然后我启动了两个终端窗口,分别去执行相应的测试用例,为什么会出现两个测试用例的运行日志只在一个终端窗口输出?并且当其中一个测试用例执行完毕以后,另外一个测试用例如果即使只执行到一半,马上也会跟着一起被终止?请问这是什么情况了?

mark,这个确实可以,干货

这个厉害~有点意思,哈哈

又有新的东西可以学习了,赞

恒温 将本帖设为了精华贴 12月21日 00:20
恒温 将本帖设为了精华贴 12月21日 00:20
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册