├── .classpath
├── .gitignore
├── .project
├── README.md
├── app
└── README.md
├── bin
├── README.md
└── exec_replace.sh
├── build
└── README.md
├── build_testng.xml
├── lib
├── README.md
└── testng-6.10.jar
├── pom.xml
├── report
└── img
│ └── README.md
├── settings.xml
├── src
└── test
│ └── java
│ └── macaca
│ └── testngcase
│ ├── android
│ ├── AndroidAppTest.java
│ └── elementlocation
│ │ └── HomePageElement.java
│ ├── ios
│ └── IOSAppTest.java
│ └── macaca
│ ├── Direction.java
│ ├── MacacaServer.java
│ └── MyMacacaClient.java
└── testng.xml
/.classpath:
--------------------------------------------------------------------------------
1 | ?xml version="1.0" encoding="UTF-8"?>
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .idea
3 | allure-results
4 | target
5 | report
6 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | MacacaAutomation
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.m2e.core.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.m2e.core.maven2Nature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 简介
2 | 支持ios多台设备并行执行相同的case,也支持ios和android并行,或者android多台并行。
3 |
4 | 用Java基于wd.java编写的自动化测试框架,maven工程,集成testng和Ant维护用例和生成测试报告,底层框架全部来自https://github.com/macacajs
5 |
6 | 使用testng控制并发,改造了默认的report结果,在case错误时候,自动截屏保存图片,并把图片添加在报告文件中,方便检查定位。
7 |
8 |
9 | ## 初始化时候传入代理端口
10 |
11 | ```
12 | porps.put("proxyPort", Integer.parseInt(proxyport));
13 |
14 | ```
15 |
16 | # 环境信息
17 |
18 | Eclipse: Mars.2 (4.5.2)
19 |
20 | macaca: 1.2.0
21 |
22 | macacaclient: 2.0.1
23 |
24 | testng:9.6.10
25 |
26 | ant: eclipse自带
27 |
28 | # 下载Macaca套件
29 |
30 | 下载Macaca套件,全局安装,包括,macaca-cli macaca-ios app-inspector macaca-android
31 |
32 | ```
33 | npm install macaca-cli -g
34 |
35 | npm install macaca-ios -g
36 |
37 | npm install macaca-android -g
38 |
39 | npm install app-inspector -g
40 | ```
41 |
42 | MAC全局安装的路径分别如下:
43 |
44 | ```
45 | /usr/local/lib/node_modules/macaca-cli
46 | /usr/local/lib/node_modules/macaca-ios
47 | /usr/local/lib/node_modules/macaca-android
48 | /usr/local/lib/node_modules/app-inspector
49 | ```
50 |
51 | # WebDriverAgent项目重签名
52 |
53 | 查找WebDriverAgent项目发现有两个,都需要使用xcode重新签名
54 |
55 | 我使用的是自己的苹果账号,有个限制就是,有效期是7天,过期之后需要重新签名,有个开发者账号最好,没有也能用,过期了再签一次就可以了。
56 |
57 | ```
58 | /usr/local/lib/node_modules/app-inspector/node_modules/.1.0.41@webdriveragent/WebDriverAgent
59 | /usr/local/lib/node_modules/macaca-ios/node_modules/.1.0.41@webdriveragent/WebDriverAgent
60 | ```
61 | 或者
62 | ```
63 | /usr/local/lib/node_modules/macaca-ios/node_modules/webdriveragent/WebDriverAgent
64 | /usr/local/lib/node_modules/macaca-inspector/node_modules/webdriveragent/WebDriverAgent
65 | ```
66 | 这个目录,由于webdriveragent -> .1.0.41@webdriveragent 是软连接,其实是一样的。
67 |
68 | 第一个目录的项目是inspector功能执行时候,自动化安装WDA到iPhone上,为的是在手机启动WDA,可以查看手机UI控件布局。
69 |
70 | 第二个目录是在UI自动化脚步时候,自动化安装WDA到iPhone上,为的是在手机启动WDA,可以执行ui case的指令。
71 |
72 | 上面两个目录下各自找到项目文件,Xcode打开,下图中的[1][2]team重新选择,原则上就可以直接使用,保险起见,把5处全部修改,保证不出错。
73 |
74 | 接着修改Bundle Identifier,每个项目中能改的全部改掉,换个名字即可,比如把各处的id中的facebook改成abc
75 |
--------------------------------------------------------------------------------
/app/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/bin/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/bin/exec_replace.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | cd ./report/html
3 | echo "Enter report logfile path."
4 | sed -i '' 's/ltltlt//g' *
6 | echo "Exec sed OK!"
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/build_testng.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/lib/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/testng-6.10.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/baozhida/AutoTestMacaca/9dd5c0a0a9608d921692bfbd6f028055bdd468f7/lib/testng-6.10.jar
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | org.tn.qa.automation
5 | MacacaAutomation
6 | 1.0.0
7 |
8 | 1.9.0
9 | UTF-8
10 |
11 |
12 | src/test/java
13 |
14 |
15 | maven-compiler-plugin
16 | 3.0
17 |
18 | 1.8
19 | 1.8
20 | utf8
21 |
22 |
23 |
24 | org.apache.maven.plugins
25 | maven-jar-plugin
26 | 2.4
27 |
28 |
29 | org.apache.maven.plugins
30 | maven-surefire-plugin
31 | 2.21.0
32 |
33 |
34 | testng.xml
35 |
36 |
37 | -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
38 |
39 |
40 |
41 |
42 | org.aspectj
43 | aspectjweaver
44 | ${aspectj.version}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | io.qameta.allure
53 | allure-testng
54 | 2.6.0
55 | test
56 |
57 |
58 | macaca.webdriver.client
59 | macacaclient
60 | 2.0.24
61 |
62 |
63 |
64 | io.appium
65 | java-client
66 | 5.0.4
67 |
68 |
69 | org.testng
70 | testng
71 | 6.10
72 | test
73 |
74 |
75 |
76 | org.uncommons
77 | reportng
78 | 1.1.4
79 | test
80 |
81 |
82 | org.testng
83 | testng
84 |
85 |
86 |
87 |
88 | com.alibaba
89 | fastjson
90 | 1.2.45
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/report/img/README.md:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 | false
11 |
12 | central
13 | bintray
14 | http://jcenter.bintray.com
15 |
16 |
17 |
18 |
19 |
20 | false
21 |
22 | central
23 | bintray-plugins
24 | http://jcenter.bintray.com
25 |
26 |
27 | bintray
28 |
29 |
30 |
31 | bintray
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/test/java/macaca/testngcase/android/AndroidAppTest.java:
--------------------------------------------------------------------------------
1 | package macaca.testngcase.android;
2 |
3 | //import static org.testng.AssertJUnit.assertTrue;
4 | //import static org.testng.AssertJUnit.fail;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.text.SimpleDateFormat;
9 | import java.util.Date;
10 |
11 | import org.testng.annotations.AfterClass;
12 | import org.testng.annotations.AfterMethod;
13 | import org.testng.annotations.BeforeClass;
14 | import org.testng.annotations.BeforeMethod;
15 | import org.testng.annotations.BeforeTest;
16 | import org.testng.annotations.Parameters;
17 | import org.testng.annotations.Test;
18 |
19 | import com.alibaba.fastjson.JSONObject;
20 |
21 | import macaca.testngcase.android.elementlocation.HomePageElement;
22 | import macaca.testngcase.macaca.MacacaServer;
23 | import macaca.testngcase.macaca.MyMacacaClient;
24 |
25 | public class AndroidAppTest {
26 |
27 | private String udid,proxyport,port;
28 | boolean isFail;
29 | int initcount = 0;
30 |
31 | private MyMacacaClient driver = new MyMacacaClient();
32 |
33 | public MyMacacaClient initDriver() throws Exception {
34 | initcount = initcount +1;
35 | System.out.println("-----------第"+initcount+"次初始化-------------");
36 |
37 | String platform = "Android";
38 | JSONObject porps = new JSONObject();
39 | porps.put("platformName", platform);
40 | //porps.put("app", "path_to_app_pkg");
41 | porps.put("package", "com.tuniu.app.ui");
42 | porps.put("activity", "com.tuniu.app.ui.activity.LaunchActivity");
43 | //0: 启动并安装 app。
44 | // 1 (默认): 卸载并重装 app。
45 | // 2: 仅重装 app。
46 | // 3:直接使用已经安装的app,在测试结束后保持 app状态。
47 | porps.put("reuse", 3);
48 | porps.put("udid", udid);
49 | //porps.put("autoAcceptAlerts", true);
50 | porps.put("host", "127.0.0.1");
51 | porps.put("port", Integer.parseInt(port));
52 | JSONObject desiredCapabilities = new JSONObject();
53 | desiredCapabilities.put("desiredCapabilities", porps);
54 | return (MyMacacaClient) driver.initDriver(desiredCapabilities);
55 | }
56 |
57 | @BeforeTest
58 | @Parameters({ "port", "udid" })
59 | public void getpara(String port, String udid) {
60 | this.port = port;
61 | this.udid = udid;
62 | }
63 |
64 | @BeforeClass
65 | public void setUp() throws Exception {
66 | // 判断端口是否已经启动node进程。如果没有,启动macaca server
67 | if(!MacacaServer.isPortRunning(port)){
68 | MacacaServer.runMacacaServer(port);
69 | }
70 | initDriver();
71 | }
72 |
73 | @BeforeMethod
74 | public void beforecase() throws Exception {
75 | //重置isFail,判读是否执行 aftercase方法
76 | isFail = true;
77 | }
78 |
79 | @Test
80 | public void case001_APP首页_正常进入旅游频道() {
81 |
82 | //String courseFile = directory.getCanonicalPath();
83 | try {
84 | driver.waitForElement(HomePageElement.LVYOU_ICON).click();
85 | //判断到达新页面
86 | assertTrueReWrite("LVYOU_CHANNEL_MARK:"+ HomePageElement.LVYOU_CHANNEL_MARK+"元素未找到!",
87 | driver.waitForElement(HomePageElement.LVYOU_CHANNEL_MARK) != null);
88 |
89 | //返回首页
90 | driver.back();
91 | driver.sleep(2000);
92 | isFail = false;
93 | } catch (Exception e) {
94 | failReWrite(e.toString()+e.getLocalizedMessage());
95 | }
96 |
97 | }
98 |
99 | @AfterMethod
100 | public void aftercase() throws Exception {
101 | System.out.println("----进入aftercase方法----");
102 | if(isFail==true){
103 | goHomePage();
104 | }
105 | }
106 |
107 | @AfterClass
108 | public void tearDown() throws Exception {
109 | driver.quit();
110 | }
111 |
112 | public void goHomePage() throws Exception {
113 | //如果用例执行出错,检查是否在首页
114 | for(int i=1;i<=5;i++) {
115 | if (driver.isElementExist(HomePageElement.HOME)) {
116 | break;
117 | } else {
118 | System.out.println("----第"+i+"次back操作----");
119 | driver.back();
120 | }
121 | }
122 | driver.getElement(HomePageElement.HOME).click();
123 | driver.sleep(2000);
124 |
125 | }
126 |
127 |
128 | public void failReWrite(String message) {
129 | String imgurl = screenShot();
130 | if (message == null) {
131 | throw new AssertionError();
132 | }
133 | throw new AssertionError(message+" Check:"+" ltltlta target=_blank href=file://"+imgurl+"gtgtgtScreenShotltltlt/agtgtgt"+" ");
134 | }
135 |
136 |
137 | public void assertTrueReWrite(String message, boolean condition) {
138 | if (!condition) {
139 | failReWrite(message);
140 | }
141 | }
142 |
143 |
144 | public String getDateTime(){
145 | SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
146 | return df.format(new Date());
147 | }
148 |
149 |
150 | public String screenShot(){
151 |
152 | File directory = new File("");
153 | String courseFile;
154 | try {
155 | courseFile = directory.getCanonicalPath();
156 | String imgname = courseFile+"/report/img/img"+getDateTime()+".png";
157 | System.out.println("----imgname----"+imgname);
158 | driver.saveScreenshot(imgname);
159 | return imgname;
160 | } catch (IOException e) {
161 | System.out.println("获取路径失败,报错信息:"+e.getMessage());
162 | return "";
163 | } catch (Exception e1) {
164 | System.out.println("截图失败,报错信息:"+e1.getMessage());
165 | return "";
166 | }
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/test/java/macaca/testngcase/android/elementlocation/HomePageElement.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2006-2018 Tuniu All rights reserved
3 | */
4 | package macaca.testngcase.android.elementlocation;
5 |
6 | /**
7 | * 首页元素定位
8 | * Date: 2018-04-23
9 | *
10 | * @author baozhida
11 | */
12 | public class HomePageElement {
13 |
14 | // 旅游图标,首页上旅游频道的入口
15 | public static final String LVYOU_ICON = "id::com.tuniu.app.ui:id/iv_style5_card_1";
16 |
17 | // 当前页面是旅游频道页判断标识
18 | public static final String LVYOU_CHANNEL_MARK = "xpath:://*[@text='旅游']";
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/java/macaca/testngcase/ios/IOSAppTest.java:
--------------------------------------------------------------------------------
1 | package macaca.testngcase.ios;
2 |
3 | //import static org.testng.AssertJUnit.assertTrue;
4 | //import static org.testng.AssertJUnit.fail;
5 |
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.text.SimpleDateFormat;
9 | import java.util.Date;
10 |
11 | import org.testng.annotations.AfterClass;
12 | import org.testng.annotations.AfterMethod;
13 | import org.testng.annotations.BeforeClass;
14 | import org.testng.annotations.BeforeMethod;
15 | import org.testng.annotations.BeforeTest;
16 | import org.testng.annotations.Parameters;
17 | import org.testng.annotations.Test;
18 |
19 | import com.alibaba.fastjson.JSONObject;
20 |
21 | import macaca.client.commands.Element;
22 | import macaca.testngcase.macaca.MacacaServer;
23 | import macaca.testngcase.macaca.MyMacacaClient;
24 |
25 |
26 | public class IOSAppTest {
27 |
28 | private String udid,proxyport,port;
29 | boolean isFail;
30 | int initcount = 0;
31 |
32 | private MyMacacaClient driver = new MyMacacaClient();
33 |
34 | public MyMacacaClient initDriver() throws Exception {
35 | initcount = initcount +1;
36 | System.out.println("-----设备"+udid+"---第"+initcount+"次初始化-------------");
37 | String platform = "IOS";
38 |
39 | JSONObject porps = new JSONObject();
40 | porps.put("platformName", platform);
41 | porps.put("app", "./app/xxx.app");
42 | //0: 启动并安装 app。1 (默认): 卸载并重装 app。 2: 仅重装 app。3: 在测试结束后保持 app 状态。
43 | porps.put("reuse", 3);
44 | porps.put("udid", udid);
45 | porps.put("proxyPort", Integer.parseInt(proxyport));
46 | porps.put("host", "127.0.0.1");
47 | porps.put("port", Integer.parseInt(port));
48 | JSONObject desiredCapabilities = new JSONObject();
49 | desiredCapabilities.put("desiredCapabilities", porps);
50 |
51 | return (MyMacacaClient) driver.initDriver(desiredCapabilities);
52 | }
53 |
54 | @BeforeTest
55 | @Parameters({ "port", "proxyport", "udid" })
56 | public void getpara(String port, String proxyport, String udid) {
57 | this.port = port;
58 | this.proxyport = proxyport;
59 | this.udid = udid;
60 | }
61 |
62 |
63 | @BeforeClass
64 | public void setUp() throws Exception {
65 | // 判断端口是否已经启动node进程。如果没有,启动macaca server
66 | if(!MacacaServer.isPortRunning(port)){
67 | MacacaServer.runMacacaServer(port);
68 | }
69 | initDriver();
70 | }
71 |
72 | @BeforeMethod
73 | public void beforecase() throws Exception {
74 | //重置isFail,判读是否执行 aftercase方法
75 | isFail = true;
76 | }
77 |
78 | @Test
79 | public void case_001xxx() {
80 | // set screenshot save path
81 | //String courseFile = directory.getCanonicalPath();
82 | try {
83 | driver.sleep(5000);
84 | Element el = driver.elementByXPath("//*[@name='xxx']");
85 | System.out.println("------------旅游控件存在------------------");
86 |
87 | el.click();
88 | //判断到达新页面
89 | assertTrueReWrite("assertTrue:元素未找到!",driver.sleep(5000).isElementExist("xpath", "//*[@name='xxx']"));
90 | //screenShot();
91 | System.out.println("------------控件存在------------------");
92 |
93 | //返回首页
94 | driver.elementByXPath("//*[@name='tab back']").click();
95 | driver.sleep(2000);
96 | isFail = false;
97 | } catch (Exception e) {
98 | failReWrite(e.toString()+e.getLocalizedMessage());
99 | }
100 |
101 | }
102 |
103 |
104 | @AfterMethod
105 | public void aftercase() throws Exception {
106 | System.out.println("------------进入aftercase方法-----------------");
107 | if(isFail==true){
108 | //如果用例执行出错,检查是否在首页,如果不再首页,点击页面右上角的点,返回
109 | for(int i=1;i<=5;i++){
110 | if(driver.isElementExist("xpath", "//*[@name='首页']") == true){
111 | return;
112 | }else{
113 | driver.tap(21, 42);
114 | driver.sleep(2000);
115 | }
116 | }
117 | //如果5次循环都不能到首页,重新初始化driver
118 | initDriver();
119 | }
120 | }
121 |
122 | @AfterClass
123 | public void tearDown() throws Exception {
124 | driver.quit();
125 | }
126 |
127 | public void failReWrite(String message) {
128 | String imgurl = screenShot();
129 | if (message == null) {
130 | throw new AssertionError();
131 | }
132 | throw new AssertionError(message+" Check:"+" ltltlta target=_blank href=file://"+imgurl+"gtgtgtScreenShotltltlt/agtgtgt"+" ");
133 | }
134 |
135 |
136 | public void assertTrueReWrite(String message, boolean condition) {
137 | if (!condition) {
138 | failReWrite(message);
139 | }
140 | }
141 |
142 |
143 | public String getDateTime(){
144 | SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
145 | return df.format(new Date());
146 | }
147 |
148 |
149 | public String screenShot(){
150 |
151 | File directory = new File("");
152 | String courseFile;
153 | try {
154 | courseFile = directory.getCanonicalPath();
155 | String imgname = courseFile+"/report/img/img"+getDateTime()+".png";
156 | System.out.println("-------imgname----------"+imgname);
157 | driver.saveScreenshot(imgname);
158 | return imgname;
159 | } catch (IOException e) {
160 | System.out.println("获取路径失败,报错信息:"+e.getMessage());
161 | return "";
162 | } catch (Exception e1) {
163 | System.out.println("截图失败,报错信息:"+e1.getMessage());
164 | return "";
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/test/java/macaca/testngcase/macaca/Direction.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2006-2018 Tuniu All rights reserved
3 | */
4 | package macaca.testngcase.macaca;
5 |
6 | /**
7 | * 拖拽元素的方向
8 | * Date: 2018-05-07
9 | *
10 | * @author baozhida
11 | */
12 | public enum Direction {
13 | UP,
14 | DOWN,
15 | LEFT,
16 | RIGHT,
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/macaca/testngcase/macaca/MacacaServer.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2006-2018 Tuniu All rights reserved
3 | */
4 | package macaca.testngcase.macaca;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.IOException;
8 | import java.io.InputStreamReader;
9 | import java.util.regex.Matcher;
10 | import java.util.regex.Pattern;
11 |
12 | /**
13 | * Macaca 服务起停进程
14 | * Date: 2018-04-27
15 | *
16 | * @author baozhida
17 | */
18 | public class MacacaServer {
19 |
20 | public static void runMacacaServer(String port) throws InterruptedException {
21 | String MacacaCmd = "cmd /K start macaca server --verbose --port "+port;
22 | //System.out.println(appiumCmd);
23 | runCommand(MacacaCmd);
24 | System.out.println("start macaca server at port "+port);
25 | Thread.sleep(20000);
26 | }
27 |
28 | private static void runCommand(String command){
29 | try {
30 | Runtime.getRuntime().exec(command);
31 | } catch (IOException e) {
32 | e.printStackTrace();
33 | }
34 | }
35 |
36 | public static void killTaskByName(String taskname){
37 | String Command = "taskkill /F /im " + taskname;
38 | System.out.println("kill " + taskname + " task ...");
39 | runCommand(Command);
40 | }
41 |
42 | public static void killTaskByPid(String pid) {
43 | String Command = "taskkill /F /pid " + pid;
44 | System.out.println("kill " + pid + " task ...");
45 | runCommand(Command);
46 | }
47 |
48 | public static boolean isPortRunning(String port){
49 | try {
50 | Process process = Runtime.getRuntime().exec("cmd /c netstat -ano | findstr "+port);
51 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
52 | String lineString = "" ;
53 | boolean flag = false;
54 | while ((lineString = reader.readLine()) != null) {
55 | flag = regexFind(lineString, port);
56 | if(flag){
57 | System.out.println("端口 "+port+" 已经占用 ...");
58 | return flag;
59 | }
60 | }
61 | } catch (IOException e) {
62 |
63 | }
64 | return false;
65 | }
66 |
67 | public static String getProcessPid(String port) {
68 | try {
69 | Process process = Runtime.getRuntime().exec("cmd /c netstat -ano | findstr " + port);
70 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
71 | return reader.readLine().split("\\s+")[5];
72 | } catch (IOException e) {
73 | return null;
74 | }
75 | }
76 |
77 | public static boolean regexFind(String lineString,String port){
78 | String regex=":(\\d+)\\s+\\d+";
79 | Matcher matcher = Pattern.compile(regex).matcher(lineString);
80 | while(matcher.find()){
81 | if(port.trim().equals(matcher.group(1).trim())){
82 | return true;
83 | }
84 | }
85 | return false;
86 | }
87 |
88 |
89 | //获取当前运行页面的ACTIVITY
90 | public static String currentActivity(){
91 | try {
92 | Process process = Runtime.getRuntime().exec("cmd /c adb shell dumpsys activity top | findstr ACTIVITY");
93 | BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
94 | return reader.readLine().split("\\s+")[2];
95 | } catch (IOException e) {
96 | return null;
97 | }
98 | }
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | public static void main(String[] args) throws InterruptedException {
113 | // isPortRunning("4567");
114 | // killTaskByName("node.exe");
115 | // runMacacaServer("5656");
116 | // Thread.sleep(5000);
117 | // String pid = getProcessPid("5656");
118 | // Thread.sleep(5000);
119 | // killTaskByPid(pid);
120 | System.out.println(currentActivity());
121 | }
122 |
123 | }
--------------------------------------------------------------------------------
/src/test/java/macaca/testngcase/macaca/MyMacacaClient.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2006-2018 Tuniu All rights reserved
3 | */
4 | package macaca.testngcase.macaca;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | import com.alibaba.fastjson.JSONObject;
10 |
11 | import macaca.client.MacacaClient;
12 | import macaca.client.commands.Element;
13 |
14 | /**
15 | * 继承自 MacacaClient 新增一些定制方法,比如 id::test 类型当做入参,之类查询元素
16 | * 这种方式比较讨巧,不一定是所有都需要的,所以不提交到官方的项目中
17 | * Date: 2018-04-25
18 | *
19 | * @author baozhida
20 | */
21 | public class MyMacacaClient extends MacacaClient{
22 |
23 | /**
24 | * time interval between finding an element ,valid for waitForElement() function
25 | * paired with waitElementTimeout, if waitElementTimeout = 1000 & waitElementTimeInterval = 200,it means we will find given element per 200ms,until 1000ms passed,
26 | * which means we will find for 5 times
27 | */
28 | // 默认是1000ms
29 | public int waitElementTimeout = 10000;
30 | // 默认是200ms
31 | public int waitElementTimeInterval = 1000;
32 |
33 | public int WindowSizeX;
34 | public int WindowSizeY;
35 |
36 | public MacacaClient initDriver(JSONObject jsonObject) throws Exception {
37 | super.initDriver(jsonObject);
38 | WindowSizeX = (int) super.getWindowSize().get("width");
39 | WindowSizeY = (int) super.getWindowSize().get("height");
40 | return this;
41 | }
42 |
43 | public int getWaitElementTimeout() {
44 | return waitElementTimeout;
45 | }
46 |
47 | public void setWaitElementTimeout(int waitElementTimeout) {
48 | this.waitElementTimeout = waitElementTimeout;
49 | }
50 |
51 | public int getWaitElementTimeInterval() {
52 | return waitElementTimeInterval;
53 | }
54 |
55 | public void setWaitElementTimeInterval(int waitElementTimeInterval) {
56 | this.waitElementTimeInterval = waitElementTimeInterval;
57 | }
58 |
59 |
60 | /**
61 | *
62 | * Search for an element on the page, starting from the document root.
63 | * Support: Android iOS Web(WebView)
64 | *
65 | * @param wayValue The loaction attribute of element,contains the way to find an element,
66 | * contains 4:ID,CSS,XPATH,selector
67 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
68 | * @return true-exist ; false-do not exist
69 | * @throws Exception
70 | */
71 | public boolean isElementExist(String wayValue) throws Exception {
72 | try {
73 | element = getElement(wayValue);
74 | return element != null;
75 | } catch (Exception e) {
76 | return false;
77 | }
78 |
79 | }
80 |
81 | /**
82 | *
83 | * Search for an element on the page, starting from the document root.
84 | * Support: Android iOS Web(WebView)
85 | *
86 | * @param wayValue The loaction attribute of element,contains the way to find an element,
87 | * contains 4:ID,CSS,XPATH,selector
88 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
89 | * @return true-exist ; false-do not exist
90 | * @throws Exception
91 | */
92 | public boolean isElementExist(String wayValue, int index) throws Exception {
93 | try {
94 | element = getElement(wayValue, index);
95 | return element != null;
96 | } catch (Exception e) {
97 | return false;
98 | }
99 | }
100 |
101 | /**
102 | *
103 | * Search for an element on the page, starting from the document root.
104 | * Support: Android iOS Web(WebView)
105 | *
106 | * @param wayValue The loaction attribute of element,contains the way to find an element,
107 | * contains 4:ID,CSS,XPATH,selector
108 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
109 | * @return true-exist ; false-do not exist
110 | * @throws Exception
111 | */
112 | // public boolean isElementsExist(String wayValue) throws Exception {
113 | // try {
114 | // return getElement(wayValue) != null;
115 | // } catch (Exception e) {
116 | // return false;
117 | // }
118 | //
119 | // }
120 |
121 | /**
122 | *
123 | * Search for an element on the page, starting from the document root.
124 | * Support: Android iOS Web(WebView)
125 | *
126 | * @param wayValue The loaction attribute of element,contains the way to find an element,
127 | * contains 4:ID,CSS,XPATH,selector
128 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
129 | * @return return the element to find if it exists,if it does not exist ,return null
130 | * @throws Exception
131 | */
132 | public Element getElement(String wayValue) throws Exception {
133 | String[] tmp = wayValue.split("::");
134 | switch (tmp[0]) {
135 | case "id":
136 | return elementById(tmp[1]);
137 | case "selector":
138 | return elementByCss(tmp[1]);
139 | case "name":
140 | return elementByName(tmp[1]);
141 | case "xpath":
142 | return elementByXPath(tmp[1]);
143 | default:
144 | return null;
145 | }
146 | }
147 |
148 | /**
149 | *
150 | * Search for an element on the page, starting from the document root.
151 | * Support: Android iOS Web(WebView)
152 | *
153 | * @param wayValue The loaction attribute of element,contains the way to find an element,
154 | * contains 4:ID,CSS,XPATH,selector
155 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
156 | * @param index index for target element
157 | * @return return the element to find if it exists,if it does not exist ,return null
158 | * @throws Exception
159 | */
160 | public Element getElement(String wayValue, int index) throws Exception {
161 | String[] tmp = wayValue.split("::");
162 | List elements = new ArrayList();
163 | switch (tmp[0]) {
164 | case "id":
165 | elements = elementsById(tmp[1]);
166 | break;
167 | case "selector":
168 | elements = elementsByCss(tmp[1]);
169 | break;
170 | case "name":
171 | elements = elementsByName(tmp[1]);
172 | break;
173 | case "xpath":
174 | elements = elementsByXPath(tmp[1]);
175 | break;
176 | default:
177 | elements = null;
178 | break;
179 | }
180 |
181 | if (elements != null && elements.size() > (index - 1)) {
182 | element = elements.get(index);
183 | } else {
184 | System.out.println("can't find the element:" + tmp[1] + "[" + index + "]");
185 | return null;
186 | }
187 | return element;
188 | }
189 |
190 |
191 | /**
192 | *
193 | * Search for an element on the page, starting from the document root.
194 | * Support: Android iOS Web(WebView)
195 | *
196 | * @param wayValue The loaction attribute of element,contains the way to find an element,
197 | * contains 4:ID,CSS,XPATH,selector
198 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
199 | * @return return the all elements to find if it exists,if it does not exist ,return null
200 | * @throws Exception
201 | */
202 | public List getElements(String wayValue) throws Exception {
203 | String[] tmp = wayValue.split("::");
204 | switch (tmp[0]) {
205 | case "id":
206 | return elementsById(tmp[1]);
207 | case "selector":
208 | return elementsByCss(tmp[1]);
209 | case "name":
210 | return elementsByName(tmp[1]);
211 | case "xpath":
212 | return elementsByXPath(tmp[1]);
213 | default:
214 | return null;
215 | }
216 | }
217 |
218 |
219 | /**
220 | *
221 | * find target element,if it doesn't exist,keep finding during given time
222 | * (property:waitElementTimeout)
223 | * Support: Android iOS Web(WebView)
224 | * @param wayValue The loaction attribute of element,contains the way to find an element,
225 | * contains 4:ID,CSS,XPATH,selector
226 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
227 | * @return return the element to find if it exists,if it does not exist ,return null
228 | * @throws Exception
229 | */
230 | public Element waitForElement(String wayValue) throws Exception {
231 | String[] tmp = wayValue.split("::");
232 | int count = 0;
233 | int timeLeft = waitElementTimeout;
234 | boolean satisfied = false;
235 | while (timeLeft > 0) {
236 | boolean elementExist = false;
237 | System.out.println(String.format("attempt to search the element for %d times", ++count));
238 | elementExist = isElementExist(wayValue);
239 | if (!elementExist) {
240 | // not find element ,keep searching
241 | this.sleep(waitElementTimeInterval);
242 | timeLeft -= waitElementTimeInterval;
243 | } else {
244 | // finded , break
245 | satisfied = true;
246 | break;
247 | }
248 | }
249 | if (!satisfied) {
250 | System.out.println("can't find the element:" + tmp[1]);
251 | return null;
252 | }
253 | return element;
254 | }
255 |
256 | /**
257 | *
258 | * find target element,if it doesn't exist,keep finding during given time
259 | * (property:waitElementTimeout)
260 | * Support: Android iOS Web(WebView)
261 | *
262 | * @param wayValue The loaction attribute of element,contains the way to find an element,
263 | * contains 4:ID,CSS,XPATH,selector
264 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
265 | * @param index index for target element
266 | * @return return the element to find if it exists,if it does not exist ,return null
267 | * @throws Exception
268 | */
269 | public Element waitForElement(String wayValue, int index) throws Exception {
270 | String[] tmp = wayValue.split("::");
271 | int count = 0;
272 | int timeLeft = waitElementTimeout;
273 | boolean satisfied = false;
274 | while (timeLeft > 0) {
275 | boolean elementExist = false;
276 | System.out.println(String.format("attempt to search the element for %d times", count++));
277 | elementExist = isElementExist(wayValue, index);
278 | if (!elementExist) {
279 | // not find element ,keep searching
280 | this.sleep(waitElementTimeInterval);
281 | timeLeft -= waitElementTimeInterval;
282 | } else {
283 | // finded , break
284 | satisfied = true;
285 | getElement(wayValue, index);
286 | break;
287 | }
288 | }
289 | if (!satisfied) {
290 | System.out.println("can't find the element:" + tmp[1]);
291 | return null;
292 | }
293 | return element;
294 | }
295 |
296 | /**
297 | *
298 | * find target element,if it doesn't exist,keep finding during given time
299 | * (property:waitElementTimeout)
300 | * Support: Android iOS Web(WebView)
301 | * @param wayValue The loaction attribute of element,contains the way to find an element,
302 | * contains 4:ID,CSS,XPATH,selector
303 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
304 | * @return return the all element to find if it exists,if it does not exist ,return null
305 | * @throws Exception
306 | */
307 | public List waitForElements(String wayValue) throws Exception {
308 | String[] tmp = wayValue.split("::");
309 | int count = 0;
310 | int timeLeft = waitElementTimeout;
311 | boolean satisfied = false;
312 | List elements = new ArrayList();;
313 | while (timeLeft > 0) {
314 | boolean elementExist = false;
315 | System.out.println(String.format("attempt to search the element for %d times", count++));
316 | elementExist = isElementExist(wayValue);
317 | if (!elementExist) {
318 | // not find element ,keep searching
319 | this.sleep(waitElementTimeInterval);
320 | timeLeft -= waitElementTimeInterval;
321 | } else {
322 | // finded , break
323 | satisfied = true;
324 | elements = getElements(wayValue);
325 | break;
326 | }
327 | }
328 | if (!satisfied) {
329 | System.out.println("can't find the element:" + tmp[1]);
330 | return null;
331 | }
332 | return elements;
333 | }
334 |
335 |
336 | /**
337 | *
338 | * get count of elements when there exist multiple elements
339 | * Support: Android iOS Web(WebView)
340 | *
341 | * @param wayValue The loaction attribute of element,contains the way to find an element,
342 | * contains 4:ID,CSS,XPATH,selector
343 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
344 | * @return count of target element
345 | * @throws Exception
346 | */
347 | public int countOfElements(String wayValue) throws Exception {
348 |
349 | List elements = getElements(wayValue);
350 | if (elements != null) {
351 | return elements.size();
352 | }
353 |
354 | return 0;
355 | }
356 |
357 | //像素坐标比较,不能超过手机屏幕大小
358 | public Boolean checkPointLocation(double x, double y){
359 | if(x > WindowSizeX || x < 0){
360 | return false;
361 | }
362 | if(y > WindowSizeY || y < 0){
363 | return false;
364 | }
365 | return true;
366 | }
367 |
368 | /**
369 | * drag
370 | * Support: Android iOS
371 | *
372 | * @param fromX drag start x-coordinate
373 | * @param fromY drag start y-coordinate
374 | * @param toX drag end x-coordinate
375 | * @param toY drag end y-coordinate
376 | * @param duration drag duration (valid for iOS and Android,time-unit:s)
377 | * @throws Exception
378 | */
379 | public void drag(double fromX, double fromY, double toX, double toY, double duration) throws Exception {
380 | //像素坐标比较,不能超过手机屏幕大小
381 | if(!checkPointLocation(fromX, fromY)){
382 | throw new RuntimeException("坐标点("+fromX+","+fromY+")越界,请检查,正确的范围是[(0,0),("+WindowSizeX+","+WindowSizeY+")]");
383 | };
384 | if(!checkPointLocation(toX, toY)){
385 | throw new RuntimeException("坐标点("+toX+","+toY+")越界,请检查,正确的范围是[(0,0),("+WindowSizeX+","+WindowSizeY+")]");
386 | };
387 | JSONObject jsonObject = new JSONObject();
388 | jsonObject.put("fromX", fromX);
389 | jsonObject.put("fromY", fromY);
390 | jsonObject.put("toX", toX);
391 | jsonObject.put("toY", toY);
392 | jsonObject.put("duration", duration);
393 | this.touch("drag", jsonObject);
394 | }
395 |
396 | /**
397 | * drag
398 | * Support: Android iOS
399 | *
400 | * @param fromX drag start x-Percentage ex:1000px 80% is 800px
401 | * @param fromY drag start y-Percentage
402 | * @param toX drag end x-Percentage
403 | * @param toY drag end y-Percentage
404 | * @param duration drag duration (valid for iOS and Android,time-unit:s)
405 | * @throws Exception
406 | */
407 | public void swipeByPointPercentage(int fromX, int fromY, int toX, int toY, double duration) throws Exception {
408 | double fX = WindowSizeX * fromX / 100;
409 | double fY = WindowSizeY * fromY / 100;
410 | double tX = WindowSizeX * toX / 100;
411 | double tY = WindowSizeY * toY / 100;
412 |
413 |
414 | if(!checkPointLocation(fX, fY)){
415 | throw new RuntimeException("坐标点("+fX+","+fY+")越界,请检查,正确的范围是[(0,0),("+WindowSizeX+","+WindowSizeY+")]");
416 | };
417 | if(!checkPointLocation(tX, tY)){
418 | throw new RuntimeException("坐标点("+tX+","+tY+")越界,请检查,正确的范围是[(0,0),("+WindowSizeX+","+WindowSizeY+")]");
419 | };
420 |
421 | JSONObject jsonObject = new JSONObject();
422 | jsonObject.put("fromX", fX);
423 | jsonObject.put("fromY", fY);
424 | jsonObject.put("toX", tX);
425 | jsonObject.put("toY", tY);
426 | jsonObject.put("duration", duration);
427 |
428 | // action "tap" "doubleTap" "press" "pinch" "drag"
429 | super.touch("drag", jsonObject);
430 | }
431 |
432 | /**
433 | * 指定的控件元素,滑动到指定的位置
434 | * Support: Android iOS
435 | *
436 | * @param e Element to drag 使用的时候直接传控件即可
437 | * @param toX drag end x-coordinate
438 | * @param toY drag end y-coordinate
439 | * @param duration drag duration (valid for iOS and Android,time-unit:s)
440 | * @throws Exception
441 | */
442 | public void drag(Element e, double toX, double toY, double duration) throws Exception {
443 | //像素坐标比较,不能超过手机屏幕大小
444 | if(!checkPointLocation(toX, toY)){
445 | throw new RuntimeException("坐标点("+toX+","+toY+")越界,请检查,正确的范围是[(0,0),("+WindowSizeX+","+WindowSizeY+")]");
446 | };
447 | JSONObject jsonObject = new JSONObject();
448 | jsonObject.put("fromX", e.getCenterX());
449 | jsonObject.put("fromY", e.getCenterY());
450 | jsonObject.put("toX", toX);
451 | jsonObject.put("toY", toY);
452 | jsonObject.put("duration", duration);
453 | this.touch("drag", jsonObject);
454 | }
455 |
456 | /**
457 | * 指定的控件元素,上下左右滑动
458 | * Support: Android iOS
459 | *
460 | * @param e Element to drag 使用的时候直接传控件即可
461 | * @param direction 拖拽元素的方向
462 | * @param length 拖拽的长度 px像素
463 | * @param duration drag duration (valid for iOS and Android,time-unit:s)
464 | * @throws Exception
465 | */
466 | public void drag(Element e, String direction,int length, double duration) throws Exception {
467 | double fromX = e.getCenterX();
468 | double fromY = e.getCenterY();
469 |
470 | double toX = 0, toY =0;
471 |
472 | if (direction.equals("UP")){
473 | if (checkPointLocation(fromX, fromY - length)){
474 | toX = fromX;
475 | toY = fromY - length;
476 | } else {
477 | toX = fromX;
478 | toY = 0;
479 | }
480 | }else if (direction.equals("DOWN")){
481 | if (checkPointLocation(fromX, fromY + length)){
482 | toX = fromX;
483 | toY = fromY - length;
484 | } else {
485 | toX = fromX;
486 | toY = WindowSizeY;
487 | }
488 | } else if (direction.equals("LEFT")){
489 | if (checkPointLocation(fromX - length, fromY)){
490 | toX = fromX;
491 | toY = fromY - length;
492 | } else {
493 | toX = fromX;
494 | toY = 0;
495 | }
496 | }else if (direction.equals("RIGHT")){
497 | if (checkPointLocation(fromX + length, fromY)){
498 | toX = fromX;
499 | toY = fromY - length;
500 | } else {
501 | toX = fromX;
502 | toY = WindowSizeX;
503 | }
504 | }
505 | // 实际传递的是元素的控件ID
506 | JSONObject jsonObject = new JSONObject();
507 | jsonObject.put("fromX", fromX);
508 | jsonObject.put("fromY", fromY);
509 | jsonObject.put("toX", toX);
510 | jsonObject.put("toY", toY);
511 | jsonObject.put("duration", duration);
512 | this.touch("drag", jsonObject);
513 | }
514 |
515 |
516 | /**
517 | * 滑动屏幕查找元素(默认往上滑屏,最多滑动10次)
518 | *
519 | * @param wayValue The loaction attribute of element,contains the way to find an element,
520 | * contains 4:ID,CSS,XPATH,selector
521 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
522 | * @return return the element to find if it exists,if it does not exist ,return null
523 | * @throws Exception
524 | */
525 | public Element getElementBySwipe(String wayValue) throws Exception{
526 | int i = 0;
527 | element = null;
528 | while (i < 10) {
529 | if (this.isElementExist(wayValue) == true) {
530 | System.out.println("屏幕滑动" + i + "次后找到元素。");
531 | break;
532 | } else {
533 | // 没有找到元素就向上滑屏
534 | System.out.println("第" + i + "次向上滑动查找元素。");
535 | swipeByPointPercentage(50, 80, 50, 30, 2);
536 | }
537 | i++;
538 |
539 | }
540 | if(element != null){
541 | return element;
542 | }else{
543 | throw new RuntimeException("控件未找到。");
544 | }
545 |
546 | }
547 |
548 |
549 | /**
550 | * 滑动屏幕查找元素,方向往上或者往下由入参控制,最多滑动次数由入参控制
551 | *
552 | * @param wayValue The loaction attribute of element,contains the way to find an element,
553 | * contains 4:ID,CSS,XPATH,selector
554 | * for example [id::fl_auto_play_ad] [::]间隔开,查找元素的方式在前
555 | * @param times swipe times
556 | * @param direction swipe direction , default true swipeup or false is swipedown
557 | * @return return the element to find if it exists,if it does not exist ,return null
558 | * @throws Exception
559 | */
560 | public Element getElementBySwipe(String wayValue, int times, boolean direction) throws Exception{
561 | int i = 0;
562 | element = null;
563 | while (i < times) {
564 | element = this.getElement(wayValue);
565 | if (element != null) {
566 | System.out.println("屏幕滑动" + i + "次后找到元素。");
567 | break;
568 | } else if (direction) {
569 | // 向上滑屏
570 | System.out.println("第" + i + "次向上滑动查找元素。");
571 | swipeByPointPercentage(50, 80, 50, 20, 2);
572 | } else {
573 | // 向下滑屏
574 | System.out.println("第" + i + "次向下滑动查找元素。");
575 | swipeByPointPercentage(50, 20, 50, 80, 2);
576 | }
577 | i++;
578 |
579 | }
580 | if(element != null){
581 | return element;
582 | }else{
583 | throw new RuntimeException("控件未找到。");
584 | }
585 |
586 | }
587 |
588 |
589 | /*
590 | * 获取当前界面的activity,并且与预期的activity进行比较
591 | */
592 | public Boolean verifyActivity(String activity) throws Exception {
593 | String currentActivity = MacacaServer.currentActivity();
594 | if (activity.equals(currentActivity)) {
595 | return true;
596 | } else {
597 | return false;
598 | }
599 |
600 | }
601 |
602 | }
603 |
--------------------------------------------------------------------------------
/testng.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------