├── META-INF └── MANIFEST.MF ├── README.md ├── output └── README.md ├── pom.xml ├── settings.xml ├── src └── main │ └── java │ ├── META-INF │ └── MANIFEST.MF │ └── com │ └── ios │ └── monkey │ ├── MathRandom.java │ ├── Monkey.java │ ├── MonkeyBackEvent.java │ ├── MonkeyContentEvent.java │ ├── MonkeyEvent.java │ ├── MonkeyHomeKeyEvent.java │ ├── MonkeyLaunchEvent.java │ ├── MonkeySubmitEvent.java │ ├── MonkeySwipeEvent.java │ ├── MonkeyTapEvent.java │ ├── MonkeyTapSpecialPointEvent.java │ ├── screenshot │ ├── ModifyPic.java │ └── Screenshot.java │ └── util │ ├── Shell.java │ └── Utils.java └── target ├── README.MD └── iosMonkey-1.1.jar /META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.pingan.monkey.Monkey 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # iosMonkey 3 | 4 | 5 | 现在客户端是使用同步的方式处理发送的事件,效率稍低 6 | 7 | 经实测: 8 | 9 | iPhone 6plus 10.2.1,每分钟可以执行大约40次事件; 10 | 11 | iPhone 7 10.2,每分钟可执行大约60次事件; 12 | 13 | 通过修改代码实现了异步http请求,发送事件的请求数量可以大幅提升,但是很多请求会阻塞,没有执行,实际效果不好,遂放弃。 14 | 15 | ### 更新 16 | 17 | 2017.3.17: 原版本呼起app的指令是当成事件,按照一定的频率执行,不管现在是否是在测试APP页面都会执行。现在改为守护进程的模式,只有当前页面跳出测试APP时候才会执行打开APP的指令。 18 | 19 | 2017.4.5: 20 | 21 | 1.修复app守护进程的bug,由于时间问题导致的 22 | 23 | 2.增加返回homescreen的事件,事件概率是2%,返回手机主页5s后重新打开app 24 | 25 | 2017.6.20: 26 | 27 | 1.支持模拟器 28 | 29 | 2.支持自定义运行时长 30 | 31 | 3.每一步自动保存截图,并标注事件发生的位置(红色的“这里这里”文字,保存截图偶尔会失败) 32 | 33 | # 0、简介 34 | 35 | - 1.修改了原版的构建方式,maven bulid(其实没看到之前构建的配置) 36 | 37 | - 2.解决了idevicedebug 命令呼起app无效的问题 38 | 39 | - 3.根据app实际情况,修改了特殊点的处理(在有些页面,点击固定的位置才能返回,不是通用的右上角) 40 | 41 | - 4.如果不修改直接使用,直接使用target下面的jar包,如果实现自己的方法,需要重新编译 42 | 43 | # 1、功能 44 | - 1.可以模拟android monkey执行的方式,在IOS APP页面点击 滑动操等作,支持 ios 9和ios10 45 | 46 | - 2.支持多台ios一起执行 47 | 48 | - 3.计划保存截图 49 | 50 | - 4.计划自动收集crash日志(未完成) 51 | 52 | 53 | # 2、准备macaca环境 54 | 55 | ### 安装usbmuxd 56 | 57 | $ brew install usbmuxd 58 | 59 | ### 安装ios_webkit_debug_proxy 60 | 61 | $ brew install ios_webkit_debug_proxy 62 | 63 | ### 安装ios-deploy 64 | 65 | $ npm i ios-deploy -g 66 | 67 | ### 安装ideviceinstaller 68 | 69 | $ brew install ideviceinstaller 70 | 71 | ### 编译libimobiledevice 72 | 73 | libimobiledevice需要自己编译,证实直接安装的不能用 74 | 75 | https://github.com/libimobiledevice/libimobiledevice 76 | 77 | 下载到本地 78 | 79 | Before building, try setting: 80 | 81 | ``` 82 | LD_LIBRARY_PATH=/usr/local/opt/openssl/lib:"${LD_LIBRARY_PATH}" 83 | CPATH=/usr/local/opt/openssl/include:"${CPATH}" 84 | PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig:"${PKG_CONFIG_PATH}" 85 | export LD_LIBRARY_PATH CPATH PKG_CONFIG_PATH 86 | You might want to add that to your .bash_profile. 87 | ``` 88 | 89 | CPATH and PKG_CONFIG_PATH are for compiling. LD_LIBRARY_PATH is for runtime. See also https://gist.github.com/samrocketman/70dff6ebb18004fc37dc5e33c259a0fc 90 | 91 | 92 | Then to compile run: 93 | 94 | ./autogen.sh 95 | make 96 | sudo make install 97 | 98 | 99 | ### 安装carthage 100 | $ brew install carthage 101 | 102 | ### 安装macaca套件 包括,macaca-cli macaca-ios 103 | 104 | ``` 105 | npm install macaca-cli -g 106 | 107 | npm install macaca-ios -g 108 | 109 | ``` 110 | MAC全局安装的路径分别如下: 111 | 112 | ``` 113 | /usr/local/lib/node_modules/macaca-cli 114 | /usr/local/lib/node_modules/macaca-ios 115 | ``` 116 | 117 | ### 检测macaca环境,无报错 118 | $ macaca doctor 119 | 120 | # 3、WebDriverAgent项目重签名 121 | 按照项目 122 | 123 | https://github.com/baozhida/MacacaAutomation 124 | 125 | 操作项目重签名 126 | 127 | # 4、帮助命令 128 | 如果需要多个iOS设备一起执行,需要指定 proxyport ,同时macaca使用不同的端口 129 | ``` 130 | $ java -jar iosMonkey-1.0.jar -h 131 | -u:设备的UDID 132 | -t:运行时长(分) 133 | -b:测试App的Bundle 134 | -port:macaca服务的端口,默认3456 135 | -proxyport:usb代理端口,默认8900 136 | ``` 137 | 138 | # 5、执行iosMonkey 139 | 开一个窗口执行 140 | 141 | $ macaca server --verbose 142 | 143 | 在一个新窗口执行 144 | 145 | $ java -jar [iosMonkey.jar Path] -u [设备的UDID] -b [测试App的BundleID] -port [macaca服务端口,可选] -proxyport[usb代理端口,可选] 146 | # 6、修改源码重新打包方法 147 | 148 | 如果需要源码实现自定义的功能,在项目目录下执行 149 | 150 | $ mvn assembly:assembly 151 | 152 | 最后提示如下,标示打包成功,target下生成iosMonkey-1.0.jar,可以使用最新的包 153 | ``` 154 | INFO] ------------------------------------------------------------------------ 155 | [INFO] BUILD SUCCESS 156 | [INFO] ------------------------------------------------------------------------ 157 | [INFO] Total time: 7.350 s 158 | [INFO] Finished at: 2017-03-06T17:01:30+08:00 159 | [INFO] Final Memory: 20M/324M 160 | [INFO] ------------------------------------------------------------------------ 161 | ``` 162 | -------------------------------------------------------------------------------- /output/README.md: -------------------------------------------------------------------------------- 1 | # 截图输出目录 2 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | iosMonkey 7 | iosMonkey 8 | 1.1 9 | 10 | 11 | 12 | maven-assembly-plugin 13 | 14 | false 15 | 16 | jar-with-dependencies 17 | 18 | 19 | 20 | com.ios.monkey.Monkey 21 | 22 | 23 | 24 | 25 | 26 | make-assembly 27 | package 28 | 29 | assembly 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | com.alibaba 39 | fastjson 40 | 1.2.15 41 | 42 | 43 | 44 | org.testng 45 | testng 46 | 6.9.6 47 | 48 | 49 | 50 | org.apache.httpcomponents 51 | httpclient 52 | 4.5.2 53 | 54 | 55 | 56 | 57 | org.apache.httpcomponents 58 | httpasyncclient 59 | 4.1.3 60 | 61 | 62 | 63 | log4j 64 | log4j 65 | 1.2.17 66 | 67 | 68 | 69 | org.hamcrest 70 | hamcrest-library 71 | 1.2 72 | test 73 | 74 | 75 | 76 | macaca.webdriver.client 77 | macacaclient 78 | 2.0.7 79 | 80 | 81 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.pingan.monkey.Monkey 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MathRandom.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | /** 4 | * Created by hujiachun on 16/12/23. 5 | */ 6 | public class MathRandom { 7 | 8 | /** 9 | * 0出现的概率为%40 10 | */ 11 | public static double EVENT_TYPE_TAP = 0.38; 12 | /** 13 | * 1出现的概率为%30 14 | */ 15 | public static double EVENT_TYPE_SWIPE = 0.35; 16 | /** 17 | * 2出现的概率为%10 18 | */ 19 | public static double EVENT_TYPE_BACK = 0.10; 20 | 21 | public static double EVENT_TYPE_SUBMIT = 0.05; 22 | 23 | public static double EVENT_TYPE_CONTENT = 0.05; 24 | 25 | public static double EVENT_TYPE_SPECIAL_POINT = 0.05; 26 | 27 | public static double EVENT_TYPE_HOMEKEY = 0.02; 28 | 29 | 30 | 31 | public int PercentageRandom() { 32 | double randomNumber; 33 | randomNumber = Math.random(); 34 | if (randomNumber >= 0 && randomNumber <= EVENT_TYPE_TAP) { 35 | return 0; 36 | } else if (randomNumber >= EVENT_TYPE_TAP / 100 && randomNumber <= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE) { 37 | return 1; 38 | } else if (randomNumber >= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE 39 | && randomNumber <= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK) { 40 | return 2; 41 | } else if (randomNumber >= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK 42 | && randomNumber <= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK + EVENT_TYPE_SUBMIT) { 43 | return 3; 44 | } else if (randomNumber >= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK + EVENT_TYPE_SUBMIT 45 | && randomNumber <= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK + EVENT_TYPE_SUBMIT 46 | + EVENT_TYPE_CONTENT) { 47 | return 4; 48 | }else if (randomNumber >= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK + EVENT_TYPE_SUBMIT + EVENT_TYPE_CONTENT 49 | && randomNumber <= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK + EVENT_TYPE_SUBMIT 50 | + EVENT_TYPE_CONTENT + EVENT_TYPE_SPECIAL_POINT) { 51 | return 5; 52 | }else if (randomNumber >= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK + EVENT_TYPE_SUBMIT + EVENT_TYPE_CONTENT 53 | + EVENT_TYPE_SPECIAL_POINT 54 | && randomNumber <= EVENT_TYPE_TAP + EVENT_TYPE_SWIPE + EVENT_TYPE_BACK + EVENT_TYPE_SUBMIT 55 | + EVENT_TYPE_CONTENT + EVENT_TYPE_SPECIAL_POINT + EVENT_TYPE_HOMEKEY) { 56 | return 6; 57 | } 58 | return -1; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/Monkey.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.ios.monkey.screenshot.Screenshot; 5 | import com.ios.monkey.util.Shell; 6 | 7 | import macaca.client.MacacaClient; 8 | 9 | import java.io.IOException; 10 | import java.text.NumberFormat; 11 | import java.util.Random; 12 | 13 | /** 14 | * Created by hujiachun on 16/12/21. 15 | */ 16 | public class Monkey { 17 | 18 | //idevicecrashreport -u ecab65eca01ae1d42874c26e645a33aee78296b6 -e -k /Users/hujiachun/Downloads/carshlog 19 | 20 | private MacacaClient driver; 21 | private int width, height, submitX_mim, submitX_max, submitY_mim, submitY_max, contentX_min, contentX_max, contentY_mim, contentY_max, special_point_x, special_point_y; 22 | private static boolean needhelp = false; 23 | private static String UDID, BUNDLEID; 24 | 25 | // 默认允许180分钟 26 | private static String TIMING = "180"; 27 | private static String PORT = "3456"; 28 | private static String PROXYPORT = "8900"; 29 | private int backX = 25, backY = 40; 30 | private int eventcount = 0; 31 | 32 | 33 | public static void main(String[] args) throws IOException, InterruptedException { 34 | executeParameter(args); 35 | 36 | } 37 | 38 | 39 | private static void executeParameter(String[] args) { 40 | int optSetting = 0; 41 | 42 | for (; optSetting < args.length; optSetting++) { 43 | if ("-u".equals(args[optSetting])) { 44 | UDID = args[++optSetting]; 45 | } else if ("-b".equals(args[optSetting])) { 46 | BUNDLEID = args[++optSetting]; 47 | } else if ("-t".equals(args[optSetting])) { 48 | TIMING = args[++optSetting]; 49 | } else if ("-port".equals(args[optSetting])) { 50 | PORT = args[++optSetting]; 51 | } else if ("-proxyport".equals(args[optSetting])) { 52 | PROXYPORT = args[++optSetting]; 53 | } else if ("-h".equals(args[optSetting])) { 54 | needhelp = true; 55 | System.out.println( 56 | "-u:设备的UDID\n" + 57 | "-b:测试App的Bundle\n"+ 58 | "-t:运行时长(分),可选,默认180分钟\n"+ 59 | "-port:macaca服务的端口,默认3456\n" + 60 | "-proxyport:usb代理端口,默认8900"); 61 | break; 62 | } 63 | 64 | } 65 | if (!needhelp) { 66 | try { 67 | System.out.println("测试设备:" + UDID + "\n" + "测试App:" + BUNDLEID); 68 | org.testng.Assert.assertTrue((!UDID.equals(null)) && (!BUNDLEID.equals(null))); 69 | } catch (Exception e) { 70 | System.out.println("请确认参数配置,需要帮助请输入 java -jar iosMonkey.jar -h\n" 71 | + "ERROR信息"+ e.toString()); 72 | } 73 | 74 | try { 75 | new Monkey().run(); 76 | } catch (Exception e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | } 81 | 82 | 83 | private void run() throws Exception { 84 | System.out.println("本次设定的运行时长【" + TIMING + "】分钟"); 85 | long startTime = System.currentTimeMillis(); 86 | init(startTime); 87 | width = (Integer) driver.getWindowSize().get("width"); 88 | height = (Integer) driver.getWindowSize().get("height"); 89 | NumberFormat numberFormat = NumberFormat.getInstance(); 90 | numberFormat.setMaximumFractionDigits(2); 91 | 92 | submitX_max = width - 1; 93 | submitX_mim = width / 10; 94 | submitY_max = height - 1; 95 | submitY_mim = height / 10 * 9; 96 | 97 | contentX_max = width - width / 10; 98 | contentX_min = width / 10; 99 | contentY_max = height / 2 + height / 10; 100 | contentY_mim = height / 2 - height / 10; 101 | special_point_x = width / 2; 102 | special_point_y = (int) (height * 0.94); 103 | 104 | while (true) { 105 | switch (new MathRandom().PercentageRandom()) { 106 | case 0: { 107 | double x = Math.ceil(Math.random() * (width - 1)); 108 | double y = Math.ceil(Math.random() * (height - 1)); 109 | 110 | Screenshot.screenshot(UDID, new Double(x).intValue(), new Double(y).intValue()); 111 | new MonkeyTapEvent(driver, x, y).injectEvent(); 112 | eventcount = eventcount+1; 113 | System.out.println("---EVENT执行了:"+eventcount+"次---"); 114 | break; 115 | } 116 | case 1: { 117 | double startX = Math.ceil(Math.random() * (width - 1)); 118 | double startY = Math.ceil(Math.random() * (height - 1)); 119 | double endY = Math.ceil(Math.random() * (height - 1)); 120 | 121 | Screenshot.screenshot(UDID, new Double(startX).intValue(), new Double(endY).intValue()); 122 | new MonkeySwipeEvent(driver, startX, startY, endY).injectEvent(); 123 | eventcount = eventcount+1; 124 | System.out.println("---EVENT执行了:"+eventcount+"次---"); 125 | break; 126 | } 127 | case 2: { 128 | Screenshot.screenshot(UDID, backX, backY); 129 | new MonkeyBackEvent(driver, backX, backY).injectEvent(); 130 | eventcount = eventcount+1; 131 | System.out.println("---EVENT执行了:"+eventcount+"次---"); 132 | break; 133 | } 134 | case 3: { 135 | Random random = new Random(); 136 | int x = random.nextInt(submitX_max) % (submitX_max - submitX_mim + 1) + submitX_mim; 137 | int y = random.nextInt(submitY_max) % (submitY_max - submitY_mim + 1) + submitY_mim; 138 | 139 | Screenshot.screenshot(UDID, x, y); 140 | new MonkeySubmitEvent(driver, x, y).injectEvent(); 141 | eventcount = eventcount+1; 142 | System.out.println("---EVENT执行了:"+eventcount+"次---"); 143 | break; 144 | } 145 | case 4: { 146 | Random random = new Random(); 147 | int x = random.nextInt(contentX_max) % (contentX_max - contentX_min + 1) + contentX_min; 148 | int y = random.nextInt(contentY_max) % (contentY_max - contentY_mim + 1) + contentY_mim; 149 | 150 | Screenshot.screenshot(UDID, x, y); 151 | new MonkeyContentEvent(driver, x, y).injectEvent(); 152 | eventcount = eventcount+1; 153 | System.out.println("---EVENT执行了:"+eventcount+"次---"); 154 | break; 155 | } 156 | case 5: { 157 | Screenshot.screenshot(UDID, special_point_x, special_point_y); 158 | new MonkeyTapSpecialPointEvent(driver, special_point_x, special_point_y).injectEvent(); 159 | eventcount = eventcount+1; 160 | System.out.println("---EVENT执行了:"+eventcount+"次---"); 161 | break; 162 | } 163 | case 6: { 164 | new MonkeyHomeKeyEvent(driver,UDID,BUNDLEID).injectEvent(); 165 | eventcount = eventcount+1; 166 | System.out.println("---EVENT执行了:"+eventcount+"次---"); 167 | break; 168 | } 169 | } 170 | 171 | long endTime = System.currentTimeMillis(); 172 | if((endTime - startTime) > (Integer.parseInt(TIMING) * 60 * 1000)) { 173 | System.out.println("已运行" + (endTime - startTime)/60/1000 + "分钟,任务即将结束"); 174 | break; 175 | } 176 | 177 | } 178 | } 179 | 180 | 181 | private void init(long startTime) throws IOException, InterruptedException { 182 | driver = new MacacaClient(); 183 | JSONObject porps = new JSONObject(); 184 | porps.put("platformName", "ios"); 185 | porps.put("reuse", 3); 186 | porps.put("bundleId", BUNDLEID); 187 | porps.put("udid", UDID); 188 | porps.put("autoAcceptAlerts", true); 189 | porps.put("proxyPort", Integer.parseInt(PROXYPORT)); 190 | JSONObject desiredCapabilities = new JSONObject(); 191 | desiredCapabilities.put("desiredCapabilities", porps); 192 | desiredCapabilities.put("host", "127.0.0.1"); 193 | desiredCapabilities.put("port", Integer.parseInt(PORT)); 194 | try { 195 | driver.initDriver(desiredCapabilities); 196 | } catch (Exception e) { 197 | System.out.println("*******************************************\n\n\n" + 198 | "请在命令行输入 macaca server --verbose 启动服务\n\n\n" + 199 | "*******************************************\n"); 200 | } 201 | //启动app守护进程 202 | Shell.launchAPP(UDID, BUNDLEID, startTime, TIMING); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeyBackEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import macaca.client.MacacaClient; 5 | 6 | /** 7 | * Created by hujiachun on 16/12/23. 8 | */ 9 | public class MonkeyBackEvent extends MonkeyEvent { 10 | private int backX, backY; 11 | private MacacaClient driver; 12 | 13 | public MonkeyBackEvent(MacacaClient driver, int width, int height) { 14 | super(MonkeyEvent.EVENT_TYPE_BACK); 15 | this.backX = width; 16 | this.backY = height; 17 | this.driver = driver; 18 | 19 | } 20 | 21 | public int injectEvent() throws Exception { 22 | System.out.println("sending Event : Back->(" + backX + "," + backY + ")"); 23 | JSONObject jSONObject = new JSONObject(); 24 | jSONObject.put("x", backX); 25 | jSONObject.put("y", backY); 26 | driver.touch("tap", jSONObject); 27 | //driver.touchAsync("tap", jSONObject); 28 | return MonkeyEvent.INJECT_SUCCESS; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeyContentEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import macaca.client.MacacaClient; 4 | 5 | import com.alibaba.fastjson.JSONObject; 6 | 7 | /** 8 | * Created by hujiachun on 16/12/23. 9 | */ 10 | public class MonkeyContentEvent extends MonkeyEvent{ 11 | private MacacaClient driver; 12 | private int x, y; 13 | 14 | public MonkeyContentEvent(MacacaClient driver, int x, int y) { 15 | super(MonkeyEvent.EVENT_TYPE_CONTENT); 16 | this.x = x; 17 | this.y = y; 18 | this.driver = driver; 19 | 20 | } 21 | 22 | public int injectEvent() throws Exception { 23 | System.out.println("sending Event : Content->(" + x + "," + y + ")"); 24 | JSONObject jSONObject = new JSONObject(); 25 | jSONObject.put("x", x); 26 | jSONObject.put("y", y); 27 | driver.touch("tap", jSONObject); 28 | //driver.touchAsync("tap", jSONObject); 29 | return MonkeyEvent.INJECT_SUCCESS; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeyEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | /** 4 | * abstract class for monkey event 5 | */ 6 | public abstract class MonkeyEvent { 7 | protected int eventType; 8 | public static final int EVENT_TYPE_TAP = 0; 9 | public static final int EVENT_TYPE_SWIPE = 1; 10 | public static final int EVENT_TYPE_LAUNCH = 2; 11 | public static final int EVENT_TYPE_BACK = 3; 12 | public static final int EVENT_TYPE_SUBMIT = 4; 13 | public static final int EVENT_TYPE_CONTENT = 5; 14 | public static final int EVENT_TYPE_HOMEKEY = 6; 15 | 16 | public static final int INJECT_SUCCESS = 1; 17 | public static final int INJECT_FAIL = 0; 18 | 19 | 20 | public MonkeyEvent(int type) { 21 | eventType = type; 22 | } 23 | 24 | /** 25 | * @return event type 26 | */ 27 | public int getEventType() { 28 | return eventType; 29 | } 30 | 31 | /** 32 | * @return true if it is safe to throttle after this event, and false otherwise. 33 | */ 34 | public boolean isThrottlable() { 35 | return true; 36 | } 37 | 38 | 39 | /** 40 | * @return INJECT_SUCCESS if it goes through, and INJECT_FAIL if it fails 41 | * in the case of exceptions, return its corresponding error code 42 | */ 43 | public abstract int injectEvent() throws Exception; 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeyHomeKeyEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import java.io.IOException; 4 | 5 | import macaca.client.MacacaClient; 6 | 7 | import com.ios.monkey.util.Shell; 8 | 9 | public class MonkeyHomeKeyEvent extends MonkeyEvent{ 10 | private String UDID, BUNDLEID; 11 | private MacacaClient driver; 12 | 13 | public MonkeyHomeKeyEvent(MacacaClient driver,String udid, String bundleid) { 14 | super(MonkeyEvent.EVENT_TYPE_HOMEKEY); 15 | this.driver = driver; 16 | this.UDID = udid; 17 | this.BUNDLEID = bundleid; 18 | 19 | } 20 | 21 | public int injectEvent() throws Exception { 22 | System.out.println("sending HOMEKEY Event."); 23 | driver.keys("\uE105"); 24 | new Thread(new Runnable() { 25 | public void run() { 26 | try { 27 | Shell.exec("pkill idevicedebug"); 28 | System.out.println("idevicedebug stop"); 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } catch (InterruptedException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | }).start(); 36 | System.out.println("启动App: " + BUNDLEID); 37 | if(UDID.contains("-")) { 38 | Runtime.getRuntime().exec("xcrun simctl launch booted " + BUNDLEID); 39 | } else { 40 | try { 41 | Shell.exec("idevicedebug -u " + UDID + " run " + BUNDLEID); 42 | Thread.sleep(3000); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } catch (InterruptedException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | return MonkeyEvent.INJECT_SUCCESS; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeyLaunchEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import com.ios.monkey.util.Shell; 4 | 5 | import java.io.IOException; 6 | 7 | /** 8 | * Created by hujiachun on 16/12/23. 9 | */ 10 | public class MonkeyLaunchEvent extends MonkeyEvent { 11 | private String UDID, BUNDLEID; 12 | 13 | public MonkeyLaunchEvent(String udid, String bundleid) { 14 | super(MonkeyEvent.EVENT_TYPE_LAUNCH); 15 | this.UDID = udid; 16 | this.BUNDLEID = bundleid; 17 | 18 | } 19 | 20 | public int injectEvent() throws Exception { 21 | 22 | new Thread(new Runnable() { 23 | public void run() { 24 | try { 25 | Thread.sleep(2000); 26 | Shell.exec("pkill idevicedebug"); 27 | System.out.println("idevicedebug stop"); 28 | } catch (IOException e) { 29 | e.printStackTrace(); 30 | } catch (InterruptedException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | }).start(); 35 | System.out.println("launch App:" + BUNDLEID); 36 | // new Thread(new Runnable() { 37 | // public void run() { 38 | try { 39 | Shell.exec("idevicedebug -u " + UDID + " run " + BUNDLEID); 40 | Thread.sleep(3000); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | } catch (InterruptedException e) { 44 | e.printStackTrace(); 45 | } 46 | // } 47 | // }).start(); 48 | 49 | 50 | return MonkeyEvent.INJECT_SUCCESS; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeySubmitEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import macaca.client.MacacaClient; 4 | 5 | import com.alibaba.fastjson.JSONObject; 6 | 7 | /** 8 | * Created by hujiachun on 16/12/23. 9 | */ 10 | public class MonkeySubmitEvent extends MonkeyEvent { 11 | private MacacaClient driver; 12 | private int x, y; 13 | 14 | public MonkeySubmitEvent(MacacaClient driver, int x, int y) { 15 | super(MonkeyEvent.EVENT_TYPE_SUBMIT); 16 | this.x = x; 17 | this.y = y; 18 | this.driver = driver; 19 | 20 | } 21 | 22 | public int injectEvent() throws Exception { 23 | System.out.println("sending Event : Submit->(" + x + "," + y + ")"); 24 | JSONObject jSONObject = new JSONObject(); 25 | jSONObject.put("x", x); 26 | jSONObject.put("y", y); 27 | driver.touch("tap", jSONObject); 28 | //driver.touchAsync("tap", jSONObject); 29 | return MonkeyEvent.INJECT_SUCCESS; 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeySwipeEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import macaca.client.MacacaClient; 5 | 6 | /** 7 | * Created by hujiachun on 16/12/23. 8 | */ 9 | public class MonkeySwipeEvent extends MonkeyEvent { 10 | private double startX, startY, endY; 11 | private MacacaClient driver; 12 | 13 | 14 | public MonkeySwipeEvent(MacacaClient driver, double startX, double startY, double endY) { 15 | super(MonkeyEvent.EVENT_TYPE_SWIPE); 16 | this.startX = startX; 17 | this.startY = startY; 18 | this.endY = endY; 19 | this.driver = driver; 20 | 21 | } 22 | 23 | public int injectEvent() throws Exception { 24 | System.out.println("sending Swipe Event : Swipe-> [start(" + startX + "," + startY + "), end(" + startX + "," + endY+")]"); 25 | JSONObject jSONObject = new JSONObject(); 26 | jSONObject.put("fromX", startX); 27 | jSONObject.put("fromY", startY); 28 | jSONObject.put("toX", startX); 29 | jSONObject.put("toY", endY); 30 | jSONObject.put("duration", 1); 31 | driver.touch("tap", jSONObject); 32 | //driver.touchAsync("tap", jSONObject); 33 | return MonkeyEvent.INJECT_SUCCESS; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeyTapEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import macaca.client.MacacaClient; 5 | 6 | /** 7 | * Created by hujiachun on 16/12/23. 8 | */ 9 | public class MonkeyTapEvent extends MonkeyEvent{ 10 | private double x, y; 11 | private MacacaClient driver; 12 | 13 | 14 | public MonkeyTapEvent(MacacaClient driver, double x, double y) { 15 | super(MonkeyEvent.EVENT_TYPE_TAP); 16 | this.x = x; 17 | this.y = y; 18 | this.driver = driver; 19 | 20 | } 21 | 22 | 23 | public int injectEvent() throws Exception { 24 | System.out.println("sending Tap Event : Tap->(" + x + ", " + y + ")"); 25 | JSONObject jSONObject = new JSONObject(); 26 | jSONObject.put("x", x); 27 | jSONObject.put("y", y); 28 | driver.touch("tap", jSONObject); 29 | //driver.touchAsync("tap", jSONObject); 30 | return MonkeyEvent.INJECT_SUCCESS; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/MonkeyTapSpecialPointEvent.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import macaca.client.MacacaClient; 5 | 6 | /** 7 | * Created by bzd on 2017.3.8 途牛app特殊的需要 8 | */ 9 | public class MonkeyTapSpecialPointEvent extends MonkeyEvent{ 10 | private int width, height; 11 | private MacacaClient driver; 12 | 13 | 14 | public MonkeyTapSpecialPointEvent(MacacaClient driver, int width, int height) { 15 | super(MonkeyEvent.EVENT_TYPE_TAP); 16 | this.width = width; 17 | this.height = height; 18 | this.driver = driver; 19 | 20 | } 21 | 22 | 23 | public int injectEvent() throws Exception { 24 | System.out.println("sending SpecialPoint Tap Event : Tap->(" + this.width + ", " + this.height + ")"); 25 | JSONObject jSONObject = new JSONObject(); 26 | jSONObject.put("x", this.width); 27 | jSONObject.put("y", this.height); 28 | driver.touch("tap", jSONObject); 29 | //driver.touchAsync("tap", jSONObject); 30 | return MonkeyEvent.INJECT_SUCCESS; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/screenshot/ModifyPic.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey.screenshot; 2 | 3 | import java.awt.Color; 4 | import java.awt.Font; 5 | import java.awt.Graphics2D; 6 | import java.awt.image.BufferedImage; 7 | import java.io.File; 8 | import java.io.IOException; 9 | 10 | import javax.imageio.ImageIO; 11 | 12 | public class ModifyPic { 13 | private Font font = new Font("", Font.PLAIN, 20);// 添加字体的属性设置 14 | private Graphics2D g = null; 15 | private int fontsize; 16 | private int x; 17 | private int y; 18 | private String imgSrcPath; 19 | private String imgDesPath; 20 | private String content; 21 | private String fontStyle; 22 | private Color color; 23 | 24 | /** 25 | * 导入本地图片到缓冲区 26 | */ 27 | public BufferedImage loadImageLocal(String imgSrcPath) { 28 | try { 29 | // System.out.println(imgSrcPath); 30 | this.imgSrcPath = imgSrcPath; 31 | return ImageIO.read(new File(this.imgSrcPath)); 32 | } catch (IOException e) { 33 | System.out.println(e.getMessage()); 34 | } 35 | return null; 36 | } 37 | 38 | /** 39 | * 生成新图片到本地 40 | */ 41 | public void writeImageLocal(String imgDesPath, BufferedImage img) { 42 | if (imgDesPath != null && img != null) { 43 | try { 44 | // System.out.println(imgDesPath); 45 | this.imgDesPath = imgDesPath; 46 | File outputfile = new File(this.imgDesPath); 47 | // System.out.println("创建对象成功,准备写文件"); 48 | ImageIO.write(img, "jpg", outputfile); 49 | // System.out.println("文件写成功"); 50 | } catch (IOException e) { 51 | System.out.println(e.getMessage()); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * 设定文字的字体等 58 | */ 59 | public void setFont(String fontStyle, int fontSize) { 60 | this.fontsize = fontSize; 61 | this.fontStyle = fontStyle; 62 | this.font = new Font(this.fontStyle, Font.PLAIN, this.fontsize); 63 | } 64 | 65 | /** 66 | * 设定文字在图片中的位置以及文字的颜色 67 | * @param x 68 | * @param y 69 | * @param color 70 | */ 71 | public void setLocalColor(int x,int y,Color color){ 72 | this.x = x; 73 | this.y = y; 74 | this.color=color; 75 | } 76 | 77 | /** 78 | * 修改图片,返回修改后的图片缓冲区(只输出一行文本) 79 | */ 80 | public BufferedImage modifyImage(BufferedImage img, String content) { 81 | try { 82 | int w = img.getWidth(); 83 | int h = img.getHeight(); 84 | g = img.createGraphics(); 85 | //g.setBackground(Color.WHITE); 86 | g.setColor(this.color); 87 | if (this.font != null) 88 | g.setFont(this.font); 89 | // 验证输出位置的纵坐标和横坐标 90 | if (x >= h || y >= w) { 91 | this.x = h - this.fontsize + 2; 92 | this.y = w; 93 | } 94 | this.content=content; 95 | if (content != null) { 96 | g.drawString(this.content, this.x, this.y); 97 | } 98 | g.dispose(); 99 | } catch (Exception e) { 100 | System.out.println(e.getMessage()); 101 | } 102 | return img; 103 | } 104 | 105 | public ModifyPic(String imgSrcPath, String imgDesPath, String content, 106 | String fontStyle, int fontsize, int x, int y,Color color) { 107 | super(); 108 | this.fontsize = fontsize; 109 | this.x = x; 110 | this.y = y; 111 | this.imgSrcPath = imgSrcPath; 112 | this.imgDesPath = imgDesPath; 113 | this.content = content; 114 | this.fontStyle = fontStyle; 115 | this.color=color; 116 | setFont(fontStyle, fontsize); 117 | writeImageLocal(imgDesPath, modifyImage(loadImageLocal(imgSrcPath), 118 | content)); 119 | } 120 | 121 | public ModifyPic(String fontStyle, int fontsize, int x, int y,Color color) { 122 | super(); 123 | this.fontsize = fontsize; 124 | this.x = x; 125 | this.y = y; 126 | this.fontStyle = fontStyle; 127 | this.color=color; 128 | setFont(fontStyle, fontsize); 129 | setLocalColor(x,y,color); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/screenshot/Screenshot.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey.screenshot; 2 | 3 | import java.awt.Color; 4 | import java.io.File; 5 | import java.io.IOException; 6 | 7 | import org.testng.annotations.Test; 8 | 9 | import com.ios.monkey.util.Utils; 10 | 11 | public class Screenshot { 12 | 13 | public static void screenshot(String UDID, int x, int y) { 14 | String path = System.getProperty("user.dir"); 15 | String screenshotPath = path + "/output/ios_monkey.jpeg"; 16 | String screenshotPath2 = path + "/output/ios_monkey_" + Utils.currentTime() + "_" + x + "_" + y +".jpeg"; 17 | if(UDID.contains("-")) { 18 | try { 19 | Process pp = Runtime.getRuntime().exec("xcrun simctl io booted screenshot --type=jpeg " + screenshotPath); 20 | pp.waitFor(); 21 | } catch (IOException e) { 22 | e.printStackTrace(); 23 | } catch (InterruptedException e) { 24 | e.printStackTrace(); 25 | } 26 | } else { 27 | try { 28 | Runtime.getRuntime().exec("idevicescreenshot -u " + UDID + " " + screenshotPath); 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | File file = new File(screenshotPath); 35 | if(file.exists() && file.length() > 0) { 36 | new ModifyPic(screenshotPath, screenshotPath2, "这里这里", "STYLE_ITALIC", 50, x, y, Color.RED); 37 | System.out.println("create screenshot : " + screenshotPath2); 38 | file.delete(); 39 | } else if (file.exists() && file.length() <= 0){ 40 | file.delete(); 41 | } else { 42 | System.out.println("截图失败,并没有找到截图文件!"); 43 | } 44 | } 45 | 46 | @Test 47 | public void f() { 48 | screenshot("1C0B46D2-E3A8-4151-843B-E12193169967", 600, 900); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/util/Shell.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.util.concurrent.Executors; 7 | import java.util.concurrent.ScheduledExecutorService; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * Created by hujiachun on 16/12/21. 12 | */ 13 | public class Shell { 14 | 15 | public static void launchAPP(final String UDID, final String BUNDLEID, final long startTime, final String TIMING) throws IOException, InterruptedException { 16 | final ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); 17 | Runnable runnable = new Runnable() { 18 | public void run() { 19 | try { 20 | long endTime = System.currentTimeMillis(); 21 | if((endTime - startTime) > (Integer.parseInt(TIMING) * 60 * 1000)) { 22 | service.shutdownNow(); 23 | } 24 | 25 | if(UDID.contains("-")) { 26 | System.out.println("=======启动模拟器的app守护进程======="); 27 | String catLog = "tail -n 3 -f ~/Library/Logs/CoreSimulator/" + UDID + "/system.log"; 28 | Process pp = Runtime.getRuntime().exec(catLog); 29 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(pp.getInputStream())); 30 | 31 | String line = bufferedReader.readLine(); 32 | // while ((line = bufferedReader.readLine()) != null) { 33 | if(line.contains("testMacdeiMac")){ 34 | String str = line.split(" ")[4]; 35 | String curbundleid = str.split("\\[")[0]; 36 | if(curbundleid.contains("LuoJiFM-IOS")) { 37 | 38 | } else if (curbundleid.contains("CoreSimulatorBridge")) { 39 | 40 | } else { 41 | System.out.println("==当前启动的APP bundleid是【"+curbundleid+"】,非测试APP,重新呼起测试APP===="); 42 | Runtime.getRuntime().exec("xcrun simctl launch booted " + BUNDLEID); 43 | } 44 | } 45 | // } 46 | 47 | bufferedReader.close(); 48 | 49 | } else { 50 | System.out.println("=======启动真机的app守护进程======="); 51 | Process pp = Runtime.getRuntime().exec("/usr/local/bin/idevicesyslog -u "+UDID); 52 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(pp.getInputStream())); 53 | 54 | String line; 55 | String curbundleid = BUNDLEID; 56 | while ((line = bufferedReader.readLine()) != null) { 57 | System.out.println("=============="+line); 58 | if(line.contains("HW kbd: currently")){ 59 | System.out.println("=============="+line); 60 | if(line.split(" ")[8].equals("currently")){ 61 | curbundleid = line.split(" ")[9]; 62 | }else{ 63 | curbundleid = line.split(" ")[10]; 64 | } 65 | //System.out.println("=============="+curbundleid); 66 | 67 | if(!curbundleid.equals("LuoJiFM-IOS")){ 68 | System.out.println("==当前启动的APP bundleid是<"+curbundleid+">,非测试APP,重新呼起测试APP===="); 69 | Runtime.getRuntime().exec("/usr/local/bin/idevicedebug -u " + UDID + " run " + BUNDLEID); 70 | } 71 | } 72 | } 73 | 74 | bufferedReader.close(); 75 | } 76 | 77 | }catch (IOException e) { 78 | // TODO Auto-generated catch block 79 | e.printStackTrace(); 80 | } 81 | } 82 | }; 83 | // 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间 84 | service.scheduleAtFixedRate(runnable, 30, 10, TimeUnit.SECONDS); 85 | } 86 | 87 | 88 | 89 | public static void exec(String command) throws IOException, InterruptedException { 90 | Process p; 91 | p = Runtime.getRuntime().exec(command); 92 | // BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream())); 93 | // String line; 94 | // while ((line = bufferedReader.readLine()) != null) { 95 | // System.out.println(line); 96 | // } 97 | 98 | // bufferedReader.close(); 99 | p.waitFor(); 100 | p.destroy(); 101 | 102 | } 103 | 104 | 105 | public static void main(String[] args) throws IOException, Exception { 106 | // launchAPP("1C0B46D2-E3A8-4151-843B-E12193169967","com.luojilab.LuoJiFM-IOS"); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/ios/monkey/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.ios.monkey.util; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Calendar; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * 生成唯一序列号 10 | * @author sun 11 | * 12 | */ 13 | public class Utils { 14 | 15 | /** 16 | * 用当前日期生成唯一序列号 17 | * 格式:年+月+日+小时+分钟 20150421113300 18 | * @return 19 | */ 20 | public static String currentTime(){ 21 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 22 | Calendar cal = Calendar.getInstance(); 23 | String times = format.format(cal.getTime()).substring(0,19); 24 | Pattern pattern = Pattern.compile("[^0-9]"); 25 | Matcher matcher = pattern.matcher(times); 26 | StringBuffer sbr = new StringBuffer(); 27 | while (matcher.find()) { 28 | matcher.appendReplacement(sbr,""); 29 | } 30 | matcher.appendTail(sbr); 31 | // System.out.println(sbr.toString()); 32 | return sbr.toString(); 33 | } 34 | 35 | public static void sleep(double d) { 36 | try { 37 | d *= 1000; 38 | Thread.sleep((int)d); 39 | } catch (Exception e){} 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /target/README.MD: -------------------------------------------------------------------------------- 1 | jar包 2 | -------------------------------------------------------------------------------- /target/iosMonkey-1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luojilab/iosMonkey/64032865fad2b30816cb74c8926835891ee151b8/target/iosMonkey-1.1.jar --------------------------------------------------------------------------------