Appium [求助] Appium 在 iOS 上怎么实现多设备操作

少陵生 · April 08, 2016 · Last by 小小的蜗牛 replied at February 09, 2020 · 3090 hits

一直使用 Appium + Robot Framework 做自动化测试,之前在 Android 设备上,可以通过 Switch Application 方法切换不同设备。现在在 iOS 真机上,可以同时在不同设备上打开 App,但是想做切换的时候,一直会遇到问题,Appium Server 也没用什么有效的 Log。

官方文档在 iOS 多设备测试的时候说得很含糊,只说因为 simulator 的限制,没说真机的情况。
Unfortunately, running local parallel iOS tests aren’t possible. Unlike Android, only one version of the iOS simulator can be launched at a time, making it run multiple tests at once.
If you do want to run parallel iOS tests, you need to use Sauce. Simply upload your Appium test to Sauce, and it can run as many parallel iOS or Android tests as your account allows. See more about running your tests on Sauce here.n

不知道有没有在这方面有经验的,或者提供一些建议也行。

共收到 32 条回复 时间 点赞

用多个苹果机器

#1 楼 @lihuazhang 多个苹果机器,是指多个 Mac 么?

4Floor has deleted

#4 楼 @nostop 就是这样啊,多台苹果电脑组成 grid ,没人再说幽默

#5 楼 @lihuazhang 试了,还真的可以…… 不知道为什么一个 mac 就不行

#5 楼 @lihuazhang 没有使用 grid,直接在 capability 里面制定不同 ip 的 Appium server,switch 操作就没问题了

#6 楼 @shljsh instruments 没法多线程

#4 楼 @nostop 哪儿污了。。。= =

我们在移动端做一个可以远程遥控的 agentAPP 来调用 instrumentation 执行自动化。电脑就可以做到多线程来控制了

一台 mac 机交替运行多台设备可用如下方法,必须指定下 tmp 参数才可行
appium -p 4727 --tmp /tmp/tmp4727
appium -p 4729 --tmp /tmp/tmp4729
并发运行 ios 目前还不支持

少陵生 #12 · May 24, 2016 Author

#11 楼 @greenplum 能够交替运行就解决我的需求了 非常感谢

如果是 Java 的话,Appium 可以结合 TestNG 使得一部 Mac 机器连接多部 iOS 设备实现 test case parallel 运行,甚至可以实现 test case distribute 运行

greenplum 回复

并发运行ios目前还不支持

请问 Appium 暂时还不能并发运行多台 iOS 设备吗?

Anson 回复

我想尝试使用 TestNG.xml 传参实现调配多设备并行运行,但是目前并没有很多关于这方面的博文

传参失败!!

请问你有做过这方面的尝试吗?

sini 回复

iOS 可以多个设备同时执行,但是不支持多个模拟器。传参失败把具体原因写出来把,这个没有难点,多线程或者并发都可以简单实现。

少陵生 回复

TestNG.xml 文件配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="suite" parallel="classes" thread-count="3">
    <test verbose="2" name="Sales">
        <parameter name="port" value="4788"/>
        <parameter name="udid" value="9da82887884423f91840e4a4601862d2821de94a"/>
        <classes>
            <class name="rl.ipos.cucumberTest.CheckStoreCukesTest"/>
        </classes>
    </test>
    <test verbose="2" name="CustomerManage">
        <parameter name="port" value="4777"/>
        <parameter name="udid" value="2600ede04135ffc36df6c6e57cf2b4ad34c52f96"/>
        <classes>
            <class name="rl.ipos.cucumberTest.OpenShopCukesTest"/>
        </classes>
    </test>
</suite> 

iosDriver 初始化

public class Driver {
    private int port;
    private String udid;

    protected static IOSDriver<IOSElement> iosDriver;
    protected static AppiumDriverLocalService service;

    @BeforeTest
    @Parameters({"port", "udid"})
    public void setPortUDID(int port, String udid) {
        this.port = port;
        this.udid = udid;
        System.out.println("\nport:" + port + ",udid:" + udid + "\n");
    }

    private int getPort() {
        return this.port;
    }

    private void setPort(int port) {
        this.port = port;
    }

    private void setUdid(String udid) {
        this.udid = udid;
    }

    private String getUdid() {
        return this.udid;
    }

    public static AppiumDriver getDriver() {

        Driver driver = null;

        if (iosDriver == null) {
//            File file = new File(IPOSConfigPath.DRIVER_CONFIG_FILE);
//            ObjectMapper objectMapper = new ObjectMapper();
//
//            try {
//                driver = objectMapper.readValue(file, Driver.class);
//
//            } catch (IOException e) {
//                System.out.println("配置文件错误");
//            }

            AppiumServiceBuilder builder = new AppiumServiceBuilder();
            builder.usingAnyFreePort();
            builder.withIPAddress("127.0.0.1");
            service = AppiumDriverLocalService.buildService(builder);

            service.start();

            if (service == null || !service.isRunning()) {
                throw new AppiumServerHasNotBeenStartedLocallyException("An appium server node is not started!");
            }

            File appDir = new File(System.getProperty("user.dir"), "/apps/");
            File app = new File(appDir, "rlterm3.app");
            DesiredCapabilities capabilities = new DesiredCapabilities();
            capabilities.setCapability(MobileCapabilityType.BROWSER_NAME, "");
            capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "10.3.0");
            capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, "autotest");
            capabilities.setCapability(MobileCapabilityType.UDID, driver.getUdid());
            capabilities.setCapability(IOSMobileCapabilityType.LAUNCH_TIMEOUT, 500000);
            capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, AutomationName.IOS_XCUI_TEST);
            capabilities.setCapability(MobileCapabilityType.APP, app.getAbsolutePath());
            capabilities.setCapability(MobileCapabilityType.NO_RESET, true);
            capabilities.setCapability("useNewWDA", true);
            iosDriver = new IOSDriver<>(service.getUrl(), capabilities);
            //每次Driver执行 找不到元素都会等待此处设置的时间
            iosDriver.manage().timeouts().implicitlyWait(480, TimeUnit.SECONDS);
            //如果在加载时间还不能加载完页面那会抛出超时异常
            // iosDriver.manage().timeouts().pageLoadTimeout(15, TimeUnit.SECONDS);

        }
        return iosDriver;
    }
}

Appium+cucumber

测试启动时,通过 cucumber 找到对应步骤,每一步骤实例化 driver

protected AppiumDriver driver = Driver.getDriver();
在这里实例化的时候报 driver 类这行代码空指针
capabilities.setCapability(MobileCapabilityType.UDID, driver.getUdid());

也就是说这个 udid 值并没有上传成功

sini 回复

看 xml 文件里面配置了 3 个线程,但是只有 2 个套设备的信息,这块会有问题。
设置为 class 级别的并发,2 个线程只会执行一遍 beforetest 方法,建议把 setPortUDID 这个方法放到 beforemethod 里面做。
另外把 port 这类参数当成 Driver 的内部变量,线程之间不够安全,可能会互相有冲突。建议把 port 这个搞成 list,然后在执行 case 的时候,根据 case 所在线程的名字或者序号去取到不同的线程。

少陵生 回复

这种通过 testng.xml 文件传参,是不是就需要使用Selenium Grid

我一直很混乱这两个方式,这两个是作为一个方式一起使用,还是可单独使用的呢?

sini 回复

两码事,使用 Selenium Grid 就是可以让 appium 和设备不在跑脚本的计算上

少陵生 回复

谢谢,明白了

初始化时 port 使用的是builder.usingAnyFreePort();任意自由 port 应该没有关系,关键是 udid 这个参数并没有通过 TestNG.xml 传过来
可能在某个环节还是出了点儿问题

sini 回复

建议自己发现连接本地电脑的设备,然后自动获取 udid

少陵生 回复

这个应该如何实施呢?

sini 回复

java 脚本调用"idevice_id -l"命令去查询

sini 回复

请参考 testng 的 parameter 传参文档
http://testng.org/doc/documentation-main.html#parameters-testng-xml

如有可能,尽量不要用 testng.xml 这种写死的方法,用 testng 的 virtual testng.xml,
http://testng.org/doc/documentation-main.html#running-testng-programmatically
此方法,对付 selenium,多浏览器支持,多浏览器并发,不同 test case 不同浏览器 (如 TC1 跑 chrome, firefox, safari,TC2 跑 edge, chrom, firefox) 绰绰有余,再加上 Java 的 annotation,分类跑 test case,如 (@Smoke@BVT等) 这样的分组也是绰绰有余,当然,testng 本身是支持 group 的,不用 annotation 也行。

然后照搬到 appium 上面就行了。

少陵生 回复

xcode 9 支持多 simulator 运行。

sini 回复

使用

builder.usingAnyFreePort();

怎么在运行时把 port 传到写死的 testng.xml 里面去?
按照正常流程,初始化 appium 的 ios instance 是在 testng.xml 更新动作之后,更新 testng.xml 因为是在 testng 介入之前,然后,初始化 appium 已经是 testng 整个初始化完成了,开始跑测试了,肯定无法更新 testng.xml,再让 testng 再次读取新的 testng.xml。

Anson 回复

谢谢你的建议,我尝试一下

少陵生 回复

我用的是 Appium+python,Mac 开启了多个端口的 appium server,但是链接俩设备后,不管 desired_caps 怎么指定 udid,都只能调起其中一台设备(最近使用过的那台),这是什么原因呢?

木头 回复

你启动 appium server 的时候可能要多加一个 uiautomator 的端口参数

木头 回复

我也是同样的问题

木头 回复

因为 driver 被覆盖了。

Arvinting 回复

麻烦问下你的问题解决了吗

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up