Macaca Macaca 多台 IOS 真机并行执行 UI 自动化

Baozhida · 2017年02月28日 · 最后由 xdf 回复于 2019年09月06日 · 116 次阅读

说明

非真的并行,多个设备初始化,加了等待时间,没有真的同时并发,两台设备可以执行相同的 case
下面有说 多台设备(3 台 或者 4 台)同时执行 Xcodebuild 指令会有很多报错,有时不能全部初始化成功,原因未知

### 8900 端口错误
macaca-ios 的代码里面,多台初始化时候每次全部指定 8900 端口,造成无法并行执行。查看源代码,发现判断端口是否占用的脚本实际上不生效
this.proxyPort = yield detect(this.proxyPort)
源码这个原本的意图是判断端口时候占用,占用时候分配一个新端口,实际上使用的方式不对,导致判断无效,而且 xctest-client 默认使用 8900 端口,所以不管多少台 ios 真机设备,初始化都会是 8900,导致错误。

我改了初始化 ios driver 的逻辑,支持传入 proxy 端口,指定端口就不会出现上面的问题
需要替换 macaca-ios 默认安装包里的文件
https://github.com/baozhida/macaca-ios/blob/master/lib/macaca-ios.js

2016.3.7 更新

官方的版本已经合并我提交的代码,直接更新即可,不需要手动替换 macaca-ios.js

此脚本可以指定 proxyPort,和 reuse 的使用方式一样,传数字即可,见下面的例子。

初始化日志

第一台设备
>> macaca-ios.js:153:12 [master] pid:22732 Trying to start wda server...
>> xctest-client start with port: 8900
>> xctest-client.js:234:14 [master] pid:22732 xcode version: 8.2.1
>> WebDriverAgent version: 1.0.41
>> xctest-client.js:170:14 [master] pid:22732 2017-02-28 10:41:49.009 xcodebuild[23231:309181]  IDETestOperationsObserverDebug: Writing diagnostic log for test session to:

第二台设备
>> macaca-ios.js:153:12 [master] pid:21863 Trying to start wda server...
>> xctest-client start with port: 8910
>> xctest-client.js:234:14 [master] pid:21863 xcode version: 8.2.1
>> WebDriverAgent version: 1.0.41
>> xctest-client.js:170:14 [master] pid:21863 2017-02-28 10:11:01.207 xcodebuild[21895:291402]  IDETestOperationsObserverDebug: Writing diagnostic log for test session to:


报告

报告加上了截屏,上图

点击 ScreenShot 跳转到截图页面

github 项目

项目地址具见 github,下载即可用
https://github.com/baozhida/macaca-multi-iosdriver

下载项目,打开两个终端,分别在项目的目录下执行,请根据自己的实际情况修改 app 名称 udid 等等信息

macaca server --verbose -p 3456

macaca server --verbose -p 3457

java 代码

栗子是基于 wd.java 编写的测试用例,testng 控制设备和端口号对应关系,并发执行,详细的见代码吧,欢迎试用

java 初始化代码, porps.put("proxyPort", Integer.parseInt(proxyport)); 这个端口是 testng 参数传入的,

public MacacaClient initDriver() throws Exception {
        initcount = initcount +1;
        System.out.println("-----设备"+udid+"---第"+initcount+"次初始化-------------");
        String platform = "IOS";

        JSONObject porps = new JSONObject();
        porps.put("platformName", platform);
        porps.put("app", "./app/xxx.app");
        //0: 启动并安装 app。1 (默认): 卸载并重装 app。 2: 仅重装 app。3: 在测试结束后保持 app 状态。
        porps.put("reuse", 3);
        porps.put("udid", udid);
        porps.put("proxyPort", Integer.parseInt(proxyport));
        JSONObject desiredCapabilities = new JSONObject();
        desiredCapabilities.put("desiredCapabilities", porps);
        desiredCapabilities.put("host", "127.0.0.1");
        desiredCapabilities.put("port", Integer.parseInt(port)); 

        if(port.equals("3457")){
            driver.sleep(2000);
        }
        return driver.initDriver(desiredCapabilities); 
    }

testng 配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="IOS" parallel="tests" thread-count="2">
    <test name="IOSTest1">
        <parameter name="port" value="3456" />
        <parameter name="proxyport" value="8900" />
        <parameter name="udid" value="xxxxxxxxxxxxxxxxxxxxxxxx" />
        <classes>
            <class name="testngcase.ios.IOSAppTest" />
        </classes>
    </test>

    <test name="IOSTest2">
            <parameter name="port" value="3457" />
            <parameter name="proxyport" value="8910" />
            <parameter name="udid" value="xxxxxxxxxxxxxxxxxxxxxx" />
        <classes>
            <class name="testngcase.ios.IOSAppTest" />
        </classes>
    </test>
</suite>
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 44 条回复 时间 点赞
Baozhida 关于 Macaca 的并发测试 (iOS) 中提及了此贴 02月28日 03:53
Baozhida 回复

哈哈,不错,我当初也是看到这行代码,还转门测试过,确实无效。提个分支到 github,让他们合并去啊~

xdf 回复

早前加过别的,这个已经加进去了

是不是加了 proxyPort 就不需要延时等待了?(这个算我创立的用法😀

adfghzhang 回复

还是等待了 2s

8楼 已删除
Baozhida 回复

那还是继续等吧

10楼 已删除
adfghzhang 回复

它提的问题是 两台 ios 以上并行,底层组件macaca-ios 下的 xctest-client 的 proxyPort

以前弄得只是 macaca server 的 port 和以前的并不太是一个问题 以前只是 server 端的 port 延时等待。

@utopia 这种方法我测试过,直接在测试脚本里面 把 proxy 的代理端口指定,但是即使是这样,还是会有 macaca 启动失败的问题,然后就一直启动不了

AllocAndInit 回复

默认是不能传这个参数的,不改 macaca-ios.js 的逻辑,传了这个参数也不会使用,还是默认的 8900

看看改的 macaca-ios.js 大概就知道了。

@CodeToSurvive 应该有解决办法吧

Baozhida 回复

你可以按照我的这个思路试试看,直接在 测试脚本 desiredCapabilities 里面指定 proxy 的代理端口,然后在下面的的截图中指定即可,但是即使是这样,有些时候还是不能正常启动 macaca 的服务,你可以试试看

这是测试脚本里 desiredCapabilities 的内容

AllocAndInit 回复

挨个备注很给力啊,分析了不少源码,但是要是升级的话,就有点麻烦

我改的脚本,上面有地址,两台真机试了很多次,都能够成功并行执行。
能成功,说明端口传递正常了。有时会失败,这个要看具体的日志。

Baozhida 回复

@xdf @Lihuazhang @junhe
对于 macaca 能稳定的跑两台设备,这个我没有什么疑问,按照我自己的思路也是一样,但是目前问题就出现在 2 台以上设备并行的情况,比如 3 台 或者 4 台 设备同时运行,不知道是 macaca 本身的原因还是其他的原因,总之即使是这样指定了相应的端口,问题还是相当的多,猜测应该是 在执行 xcodebuild 的时候出的问题

AllocAndInit 回复

不能真的并行,每个设备隔几秒试试,先不让报错
我觉得多个设备间隔 1 秒触发,还是能接受的

Baozhida 回复

经过实际测试,即使间隔 超过 1 秒,也不会有多大的效果,当测试设备超过 2 台 ,比如 3 台或者 4 台设备的时候,在执行 Xcodebuild 指令启动 webdriveragent 的时候,总是会报各种各样的错

比如: 1. target WebDriverAgentRunner encountered an error (Early unexpected exit, operation never finished bootstrapping - no restart will be attempted)

  1. 还有各种 codeSign 的错误
AllocAndInit 回复

两台设备我也看见很多这种错误
启动了 wda 没有正常关闭,必然出现 test failed ,然后会重新启动。
上面的错误在见过,原因未知。
第二个错误是 Linux 返回的,进程相关的

Baozhida 回复

如果多台设备(3 台 或者 4 台)同时执行 Xcodebuild 指令,前面已经执行完 Xcodebuild 指令的设备会被最后执行 Xcodebuild 指令的的设备挤掉,然后就会报上面的 的那个错(Early unexpected exit, operation never finished bootstrapping - no restart will be attempted),至于 CodeSign 的错,我自己猜想应该还是 跟 执行 Xcodebuild 有关系

AllocAndInit 回复

延迟多加一些,20s,我没这么多设备试验

Macaca 团队之前说也会开发自己的替换掉 wda,不知道什么进度了@xdf

23楼 已删除

@utopia 这层很薄。wda 目前还是比较稳定的,wda 有问题之前肯定会替换掉的。

@utopia @xdf 我用的 PY,多机并行有方法传不同的 proxyPort 吗?

#!/usr/bin/python
#-*- coding: UTF-8 -*-
import unittest
import urllib2
import os
import time
from macaca import WebDriver
from multiprocessing.pool import Pool

desired_caps = {
'platformName': 'iOS',
'platformVersion': '10.2.1',
'bundleId':'xxxxxx',
'udid': 'xxxxxxxxxxxxxxx',
}

server_url = {
    'hostname': '127.0.0.1',
    'port': 3457
}

desired_caps1 = {
    'platformName': 'iOS',
    'platformVersion': '10.1.0',
    'reuse' : '3',
    'bundleId':'xxxxxxxx',
    'udid':'xxxxxxxxxxxxxxxxxxxx',
}

server_url1 = {
    'hostname': '127.0.0.1',
    'port': 3456
}
devices = [desired_caps,desired_caps1]
port_list = [3456,3457]

def setUpClass():
    pool = Pool(processes=2)
    for i in range(2):
        pool.apply_async(run_server, args=(devices[i], port_list[i]))
        time.sleep(5)
    pool.close()
    pool.join()

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

setUpClass()

孟德功 回复

目前替换我修改的 macaca-ios.js

传值和 udid 一样,应该可以实现

Baozhida 回复

macaca-ios.js /usr/local/lib/node_modules/macaca-ios/lib/macaca-ios.js 更改为和你的一样的代码.
执行后,两个服务都显示这个http://127.0.0.1:8900/session:POST ,并未有启动 8910
现象仍是一个手机上启动两次应用程序,另外一个手机未启动应用程序

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

import time
from macaca import WebDriver
from multiprocessing.pool import Pool
import multiprocessing

desired_caps = {
    'platformName': 'iOS',
    'platformVersion': '10.2.1',
    'bundleId':'xxxxx',
    'proxyPort':'8910',
    'udid': 'xxxxxxxxxxxxxxxxxxxxx',
}

desired_caps1 = {
    'platformName': 'iOS',
    'platformVersion': '10.1.0',
    'reuse' : '3',
    'bundleId':'xxxxxxxxxxxx',
    'proxyPort':'8900',
    'udid':'xxxxxxxxxxxxxxxxxxxxxxxxx',
}

devices = [desired_caps,desired_caps1]
port_list = [3457,3456]

def setUpClass():
    for i in range(2):
        p = multiprocessing.Process(target = run_server, args = (devices[i], port_list[i]))
        print devices[i]
        print port_list[i]
        p.start()

def run_server(device, port):
    server_url = {
        'hostname': '127.0.0.1',
        'port': port,
    }
    driver = WebDriver(device, server_url)
    driver.init()
    time.sleep(2)

setUpClass()
孟德功 回复

那应该是传值未生效,macaca-ios.js 你是怎么修改的,覆盖,还是手写改的?

AllocAndInit 回复

我这边三台设备执行 case 正常,每个设备初始化加了 5s 等待。

30楼 已删除
Baozhida 回复

已经解决了,分享一下我遇到问题.

我的电脑中存在三个同名的 macaca-ios.js

/Users/degongmeng/.nvm/versions/node/v6.9.5/lib/node_modules/macaca-ios/lib/macaca-ios.js

/Users/degongmeng/Downloads/macaca-multi-iosdriver-master/macaca-ios.js

/usr/local/lib/node_modules/macaca-ios/lib/macaca-ios.js

覆盖前两个文件后,并未解决问题,实际调用的是第三个 (所以要找出电脑中有的 macaca-ios.js 文件很重要😂 ),将第三个文件修改后多部 IOS 手机是可以运行的,代码同我 #27(只是一个 Demo😈 )

为了区分是否覆盖的文件被调用 macaca-ios.js 第 101 行后增加

logger.debug('传入的代理端口号是 ${this.args.proxyPort}');

运行脚本后,若在 macaca server 控制台看到对应日志,文件便被调用

感谢@utopia 的技术分享和技术指导

Baozhida 回复

你的并发测试是怎么执行的?是自己写的 python 脚本吗?你在 3 台设备并发测试的时候,难道没有出现过我上述所说的问题?你把每台设备的启动时间间隔设置为 5 秒是不是有点短啊?我现在吧每台设备的启动时间间隔设置为 10 秒 还是会出现上述我说的那些问题

@utopia bin 目录下没有看见这个文件

charles 回复

现在传了

小白问个问题

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.0:compile (default-compile) on project MacacaAutomation: Compilation failure: Compilation failure: 
[ERROR] /Users/lanpan/Desktop/macaca-multi-iosdriver/src/testngcase/ios/IOSAppTest.java:[11,30] 程序包org.testng.annotations���存在
[ERROR] /Users/lanpan/Desktop/macaca-multi-iosdriver/src/testngcase/ios/IOSAppTest.java:[12,30] 程序包org.testng.annotations不存在
[ERROR] /Users/lanpan/Desktop/macaca-multi-iosdriver/src/testngcase/ios/IOSAppTest.java:[13,30] 程序包org.testng.annotations不存在
[ERROR] /Users/lanpan/Desktop/macaca-multi-iosdriver/src/testngcase/ios/IOSAppTest.java:[14,30] 程序包org.testng.annotations不存在
[ERROR] /Users/lanpan/Desktop/macaca-multi-iosdriver/src/testngcase/ios/IOSAppTest.java:[15,30] 程序包org.testng.annotations不存在
[ERROR] /Users/lanpan/Desktop/macaca-multi-iosdriver/src/testngcase/ios/IOSAppTest.java:[16,30] 程序包org.testng.annotations���存在
[ERROR] /Users/lanpan/Desktop/macaca-multi-iosdriver/src/testngcase/ios/IOSAppTest.java:[17,30] 程序包org.testng.annotations���存在
[ERROR] /Users/lanpan/Desktop/macaca-multi-iosdriver/src/testngcase/ios/IOSAppTest.java:[57,6] 找不到符号
[ERROR]   符号:    BeforeTest
[ERROR]   位置:  testngcase.ios.IOSAppTest

这个怎么解决,我不是专门搞测试的

蓝畔湖光 回复

testng 的 jar 包没找到

Baozhida 回复

😳 是不是还要另外配置什么,本来我 eclipse 没装那个 testng,现在装了,但还是报这个 testng 包没找到的错。但 pom 依赖里不是已经下了这个包吗

Baozhida 回复

😂 可以了。不知道为什么我这 lib 文件里的变成 6.9.101.jar。然后 build_testng.xml 里的改下就好了。弄了我好久

Baozhida 回复

😂 弱弱的问下,该怎么运行,maven install 编译成功后。build_testng.xml 这个 run_as Ant 吗?还是 testng.xml 这个 run_as testng suite

蓝畔湖光 回复

build_testng.xml 这个是 ant 执行的入口文件,执行这个就行

Baozhida 回复

我这边的步骤是这样的,不知道对不对。试了下,说连接不上,望指教。
有什么我这边需要在工程中修改的吗?除了 testng 文件
1、
macaca server --verbose -p 3456
macaca server --verbose -p 3457
2、
iproxy 3456 8900 对应设备 UDID
iproxy 3457 8910 对应设备 UDID
3、
ant 执行 build_testng.xml

蓝畔湖光 回复

代理端口不需要手动执行,不成功要具体看日志

Baozhida 回复

😊 可以了,BUILD SUCCESSFUL 了。
不过好像一直在本地路径找 app。😓

Send Error Respone to Client: Error: App path /Users/lanpan/com.lanpan.meiph does not exist!

😅 如果要跑手机的某个本地 app,跑某个脚本怎么办,在 IOSAppTest.java 文件中修改吗?
就实现类似笔者你所写的 IOSMonkey 那样的效果。

蓝畔湖光 回复

两种方式 porps.put("app", "./app/xxx.app"); 注意是.app 格式
porps.put("bundleId","com.xx.");

Baozhida 回复

😘 谢谢大神。好像可以了。有个我昨天试了下把 XCTestWD 的签名改了的问题。改回来能跑起 XCTestWD。
但除了这个问题,还在解决
Testing failed:
Test target XCTestWDUITests encountered an error (Early unexpected exit, operation never finished bootstrapping - no restart will be attempted)

😂 现在跟楼上各位大神一样,多台会有问题,单台可以稳定运行。
(已修改 macaca-ios.js) 多台运行的情况下,另一台失败会提示 xctest client proxy error with: Error: socket hang up

多机并行测试 webview 如何控制 chromedriver 的端口?

使用帖子中脚本,测试多设备并发时一直都连不上端口,查了半天才发现 Macaca client 在 createSession 的时候,是从 desiredCapabilities 中获取 host 和 port 的 😅

JSONObject desiredCapabilities = jsonObj.getJSONObject("desiredCapabilities");
if (desiredCapabilities.get("host") != null) {
    String host = (String) desiredCapabilities.get("host");
    this.driver.setRemoteHost(host);
}

https://github.com/macacajs/wd.java/blob/master/src/main/java/macaca/client/commands/Session.java#L21

可以辛苦大佬把代码改下嘛,为了看到这篇帖子的人能不再花多余的时间去调试脚本😂

porps.put("proxyPort", Integer.parseInt(proxyport));
JSONObject desiredCapabilities = new JSONObject();
desiredCapabilities.put("desiredCapabilities", porps);
desiredCapabilities.put("host", "127.0.0.1");
desiredCapabilities.put("port", Integer.parseInt(port)); 

改为

porps.put("proxyPort", Integer.parseInt(proxyport));
porps.put("host", "127.0.0.1");
porps.put("port", Integer.parseInt(port));
JSONObject desiredCapabilities = new JSONObject();
desiredCapabilities.put("desiredCapabilities", porps);
50楼 已删除
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册