├── .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 | --------------------------------------------------------------------------------