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