Appium Appium Wrapper for Java

Bluesky Yao · 2015年01月14日 · 最后由 woyebuzhidaowoshishei 回复于 2016年06月30日 · 2354 次阅读
本帖已被设为精华帖!

基于 Appium 的移动测试框架,Appium 的 Wrapper 很关键。Talk is cheap, Java 版本代码如下:

package com.ctrip.cap.lanucher;

/**
 * A service wrapper for Appium Server
 * 
 * @author ltyao
 *
 */
public class AppiumServer {

    private static final Logger logger = LoggerFactory
            .getLogger(AppiumServer.class);
    private static final long START_TIMEOUT_MILLISECONDS = 30000;
    private static final HttpClient httpClient = HttpClients.createDefault();
    private static final String STATUS_PATH = "/wd/hub/status";
    private static final String PATH = "/wd/hub";
    private String ip = "localhost";

    // private volatile boolean started = false;

    private Process process;

    private int appiumPort = -1;
    private int bootstrapPort = -1;
    private int selendroidPort = -1;
    private int chromeDriverPort = -1;
    private int robotPort = -1;

    private String appiumLog;

    private AndroidDevice device;

    public AppiumServer() {
    }

    public AppiumServer(AndroidDevice device) {
        this.device = device;
    }

    public void stopAppium() {
        try {
            WinProcess winp = new WinProcess(process);
            logger.warn("try to kill process {} Recursively", winp.getPid());
            winp.killRecursively();
        } catch (Exception e) {
            logger.warn("stopAppium", e);
        }
    }


    public void startAppium() {
        startAppium(START_TIMEOUT_MILLISECONDS);
    }

    public void startAppium(long milliseconds) {
        List<String> cmds = buildCmds();

        final ProcessBuilder pb = new ProcessBuilder(cmds)
                .redirectErrorStream(true);

        logger.debug("start appium with {}", cmds.toString());

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    process = pb.start();

                    int exitvalue = process.waitFor();

                    logger.warn("appium server exitvalue {}", exitvalue);
                } catch (Exception e) {
                    logger.warn("startAppium", e);
                }

            }
        }).start();

        long start = System.currentTimeMillis();
        boolean state = isRunning();
        while (!state) {
            long end = System.currentTimeMillis();
            if (end - start > milliseconds) {
                this.stopAppium();
                throw new AppiumTimeoutException("Appium can't be lanuched in "
                        + milliseconds + " seconds");
            }
            state = isRunning();
        }

        logger.warn("started appium server {}", this);
    }

    public void restartAppium() {
        this.stopAppium();
        this.startAppium();
    }

    public boolean isRunning() {
        try {
            URI uri = new URIBuilder().setScheme("http").setHost(ip)
                    .setPort(appiumPort).setPath(STATUS_PATH).build();

            HttpGet httpget;
            HttpResponse response;
            httpget = new HttpGet(uri);
            response = httpClient.execute(httpget);
            HttpEntity entity = response.getEntity();
            String rs = EntityUtils.toString(entity);
            JsonElement json = new JsonParser().parse(rs);
            int status = json.getAsJsonObject().get("status").getAsInt();
            return status == 0;

        } catch (Exception e) {
            // logger.warn("isRunning", e);
            return false;
        }

    }

    public URL getURL() {
        URI uri;
        try {
            uri = new URIBuilder().setScheme("http").setHost(ip)
                    .setPort(appiumPort).setPath(PATH).build();
            return uri.toURL();
        } catch (URISyntaxException | MalformedURLException e) {
            throw new CapException("getURL", e);
        }

    }


    public int getPid() {
        try {
            WinProcess winp = new WinProcess(process);
            return winp.getPid();
        } catch (Exception e) {
            logger.warn("", e);
            return -1;
        }
    }

    /**
     * need to be checked
     * 
     * @return
     */
    private List<String> buildCmds() {

        appiumPort = AvailablePortFinder.getNextAvailable();
        chromeDriverPort = AvailablePortFinder.getNextAvailable();
        bootstrapPort = AvailablePortFinder.getNextAvailable();
        selendroidPort = AvailablePortFinder.getNextAvailable();

        List<String> cmds = new LinkedList<>();
        cmds.add("appium.cmd");
        cmds.add(String.format("--port=%d", appiumPort));
        cmds.add(String.format("--chromedriver-port=%d", chromeDriverPort));
        cmds.add(String.format("--selendroid-port=%d", selendroidPort));
        cmds.add(String.format("--bootstrap-port=%d", bootstrapPort));
        this.appiumLog = Environment.appiumLog(device.getSerialNumber());
        String qappiumLog = StringUtils.quoteArgument(this.appiumLog);
        cmds.add(String.format("--log=%s", qappiumLog));
        cmds.add("--log-timestamp");

        // switch (config.getDriverType()) {
        // case Selendroid:
        // selendroidPort = AvailablePortFinder.getNextAvailable();
        // cmds.add(String.format("--selendroid-port=%d", selendroidPort));
        //
        // break;
        // case ChromeDriver:
        // chromeDriverPort = AvailablePortFinder.getNextAvailable();
        // cmds.add(String.format("--chromedriver-port=%d", chromeDriverPort));
        //
        // default:
        // break;
        // }
        return cmds;
    }

    public int getAppiumPort() {
        return appiumPort;
    }

    public void setAppiumPort(int appiumPort) {
        this.appiumPort = appiumPort;
    }

    public int getBootstrapPort() {
        return bootstrapPort;
    }

    public void setBootstrapPort(int bootstrapPort) {
        this.bootstrapPort = bootstrapPort;
    }

    public int getSelendroidPort() {
        return selendroidPort;
    }

    public void setSelendroidPort(int selendroidPort) {
        this.selendroidPort = selendroidPort;
    }

    public int getChromeDriverPort() {
        return chromeDriverPort;
    }

    public void setChromeDriverPort(int chromeDriverPort) {
        this.chromeDriverPort = chromeDriverPort;
    }

    public int getRobotPort() {
        return robotPort;
    }

    public void setRobotPort(int robotPort) {
        this.robotPort = robotPort;
    }

    public String getAppiumLog() {
        return appiumLog;
    }

    public Device getDevice() {
        return device;
    }

    public void setDevice(AndroidDevice device) {
        this.device = device;
    }

    @Override
    public String toString() {
        return "AppiumServer [pid=" + getPid() + ",ip=" + ip + ", appiumPort="
                + appiumPort + ", bootstrapPort=" + bootstrapPort
                + ", selendroidPort=" + selendroidPort + ", chromeDriverPort="
                + chromeDriverPort + ", appiumLog=" + appiumLog + "]";
    }

}
共收到 21 条回复 时间 点赞

Appium 的项目,Java 跑起来顺吗?

#1 楼 @anikikun 必须顺畅,我们同时运行两台设备木有问题

#2 楼 @cosyman 能否请你下次来参加我们的分享呢?大家都希望看到真正的落地呢。

#2 楼 @cosyman 哈哈。我说的顺不是这个顺畅。 想知道的是这个项目以 Java+TestNG 的这种形式跑,有没有感觉有非常明显的好处或坏处。 同希望得到分享。

#4 楼 @anikikun 不知道明显的好处和坏处和什么比较。因为 TestNG,JUnit 也好,跑 Appium 和传统 WebDriver 区别不大,主要是 Appium 本身的问题,一个是 App 有点慢,一个是不够特别稳定(1.3.4 已经表现良好)

#3 楼 @lihuazhang 恩我得先问问老大。可以先写个 ppt 什么的,其实 Appium 一路走来还是蛮苦的

#6 楼 @cosyman 嗯,的确能把 appium 啃下来 不容易的。

#6 楼 @cosyman 楼主有没有研究过 robotium ,我现在也要开始安卓自动化了,但是纠结 appium 和 robotium ,不知道选择哪个框架比较好?求楼主分享下 你的观点 多谢

#8 楼 @young 你顶这个贴的理由和这个帖子无关。如果要私聊就请私底下联系。

hi,遇到个问题,想咨询你 一下。
我看你这里拿 appiumPort 的时候是用

appiumPort = AvailablePortFinder.getNextAvailable();

那当你两个 case 启动的时间差非常短的时候,会不会出现拿到的 port 是相同的情况?

#10 楼 @anikikun AvailablePortFinder 根据 Socket 绑定判断 port 是否可用,每次都会自增 1,这个 port 可用是很大的一个范围,使用 AtomicInteger 不会出现并发问题。其实这个判断端口的方法,Selenium 中有个比较靠谱的,具体你找找吧,研究 Appium 顺便把 WebDriver API 一并看了

#11 楼 @cosyman
嗯,我现在写的也是通过 Socket 判定 port 是否可用的,被占用就 +1。
我所说的情况是这样的:

case1 进来,判断 4723 可用,那么开始启动 appium
这时候 case2 刚好进来,case1 还在启动 appium 的过程中(4723 还未被占),socket 告诉 case2 说 4723 还没被使用,你可以使用。

大致是这么一个情况。
还说其中也是要用线程锁来共享端口数据?

不需要绝对正确,端口范围很大,不会出问题就行

/**
 * A simple rather userful port finder
 * 
 * @author ctrip
 *
 */
public class AvailablePortFinder {
    public static final int MIN_PORT_NUMBER = 40000;
    public static final int MAX_PORT_NUMBER = 60000;
    private static int current = MIN_PORT_NUMBER;

    private AvailablePortFinder() {
    }
    public synchronized static int getNextAvailable() {
        if (++current == MAX_PORT_NUMBER) {
            current = MIN_PORT_NUMBER;
        }
        return getNextAvailable(current);
    }
    private synchronized static int getNextAvailable(int fromPort) {
        if ((fromPort < MIN_PORT_NUMBER) || (fromPort > MAX_PORT_NUMBER)) {
            throw new IllegalArgumentException("Invalid start port: "
                    + fromPort);
        }

        for (int i = fromPort; i <= MAX_PORT_NUMBER; i++) {
            if (available(i)) {
                return i;
            }
            current++;
        }

        throw new NoSuchElementException("Could not find an available port "
                + "above " + fromPort);
    }
    private static boolean available(int port) {
        if ((port < MIN_PORT_NUMBER) || (port > MAX_PORT_NUMBER)) {
            throw new IllegalArgumentException("Invalid start port: " + port);
        }

        ServerSocket ss = null;
        DatagramSocket ds = null;
        try {
            ss = new ServerSocket(port);
            ss.setReuseAddress(true);
            ds = new DatagramSocket(port);
            ds.setReuseAddress(true);
            return true;
        } catch (IOException e) {
        } finally {
            if (ds != null) {
                ds.close();
            }

            if (ss != null) {
                try {
                    ss.close();
                } catch (IOException e) {
                    /* should not be thrown */
                }
            }
        }

        return false;
    }

}

#13 楼 @cosyman 哇擦,没 @ 我,我以为你没回复。受教。

有个问题请教下楼主,如何在所有用例执行前调用 startAppium,所有用例执行后或者异常时执行 stopAppium

问题解决了,测试通过。
@RunWith(Cucumber.class)
@CucumberOptions(format = { "json:target/cucumber.json" }, tags = {}, features = { "src/test/resources/features" })
public class RunCukesTest {
@BeforeClass
public static void beforeClass() {
AppUtils.getDriver();
System.out.println("bbbbb1" + System.currentTimeMillis() );
AppiumServer.getInstance().startAppium();
System.out.println("bbbbb2" + System.currentTimeMillis());
}

@AfterClass
public static void afterClass() {
AppUtils.quit();
System.out.println("eeeeee1" + System.currentTimeMillis());
AppiumServer.getInstance().stopAppium();
System.out.println("eeeeee2" + System.currentTimeMillis());
}
}

请教楼主,虽然可以启停 appium,但是运行 appium 后,连接不上设备,是不是在同一个 java 进展里,不能启服务和操作 device 啊?

appium 和设备之间没有什么直接的关系。我们一台电脑启动两个 appium instance, 分别连一个 device 没有问题。有问题也是 appium 本身对多进程的支持小 bug,但不影响你问的问题

#17 楼 @springs412 你先试试adb devices能不能找到设备。adb 能找到 appium 一般都能连到。

如今 java-client 3.2 也提供了类似的服务https://github.com/appium/java-client/blob/master/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java

使用 builder 模式 api 更友好,但在 kill process 方便更相信 appium 本身

能看下你 import 那个代码吗,我这里太多包了,搞的有点乱了,想和你的对比下。谢谢了

22楼 已删除
woyebuzhidaowoshishei [该话题已被删除] 中提及了此贴 06月30日 16:56
woyebuzhidaowoshishei Appium+python 框架 (二) 中提及了此贴 12月01日 14:04
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册