├── .gitignore ├── README.md ├── pom.xml └── src ├── main └── java │ └── cn │ └── speiyou │ └── wda │ ├── BaseApi.java │ ├── BaseResponse.java │ ├── Error.java │ ├── HttpProxy.java │ ├── ILog.java │ ├── SessionInValidateException.java │ ├── WDAClient.java │ ├── alert │ └── AlertApi.java │ ├── custom │ ├── CustomApi.java │ └── res │ │ ├── ActiveAppInfo.java │ │ └── WDADeviceInfo.java │ ├── element │ ├── ElementApi.java │ └── res │ │ ├── WDARect.java │ │ └── WindowSize.java │ ├── findelement │ ├── FindElementApi.java │ ├── req │ │ ├── QueryInfo.java │ │ └── QueryUsing.java │ └── res │ │ └── Element.java │ ├── orientation │ └── OrientationApi.java │ ├── screenshot │ └── ScreenshotApi.java │ ├── session │ ├── SessionApi.java │ ├── req │ │ ├── AppParam.java │ │ └── FromToParam.java │ └── res │ │ ├── BuildInfo.java │ │ ├── Capabilities.java │ │ ├── CreateSession.java │ │ ├── OSInfo.java │ │ └── Status.java │ └── touch │ ├── TouchApi.java │ └── req │ ├── TouchAction.java │ └── TouchActions.java └── test └── java └── WDATest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | wda-java-client.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 开源WebDriverAgent的Java-client 2 | 3 | WebDriverAgent(wda)是iOS端一个最著名的UI自动化测试框架,Appium目前使用的xctest-driver也是基于WebDriverAgent来做二次封装和调用的,经常逛testerhome社区发现,其实很多公司也并没有直接用appium来做iOS自动化,而是直接调用WebDriverAgent来实现自己的iOS自动化框架。 4 | 5 | 最近我也在做iOS的自动化框架,也是基于它来改造的,wda的代码写非常工整,方便阅读,是学习iOS自动化技术的典范,它内部的实现原理,其实比较简单,就是基于xctest,实现一个内部的http服务器,通过http api接口的形式提供外界调用,我用postman整理了一共71个接口,制作了一个文档:[WebDriverAgent Http Api 文档](https://documenter.getpostman.com/view/1837823/TVmMhJNB),并基于这个api接口,开发了对应的java-client开源调用库,方便大家试用,也欢迎大家试用这个api接口完成Python的调用库,这将非常有价值! 6 | 7 | 关于对WebDriverAgent的理解和使用,参见我这篇文章:[理解和使用WebDriverAgent](https://testerhome.com/articles/27059) 8 | 9 | ### maven仓库地址 10 | 11 | ```xml 12 | todo:maven 仓库 13 | ``` 14 | 15 | ### 用法示例 16 | 17 | ```java 18 | WDAClient client = new WDAClient("127.0.0.1", 8100); 19 | // 检查wda是否健康 20 | client.health(); 21 | client.getSessionApi().healthCheck(); 22 | // 创建Session 23 | BaseResponse res = client.getSessionApi().createSession(); 24 | if (res.isSuccess) { 25 | String sid = res.getValue().getSessionId(); 26 | // 启动应用 27 | BaseResponse r = this.client.getSessionApi().launchApp(sid, "com.apple.Maps"); 28 | assert r.isSuccess(); 29 | } 30 | ``` 31 | 32 | ### API列表 33 | 34 | 假设新建了client对象: 35 | 36 | ```java 37 | WDAClient client = new WDAClient("127.0.0.1", 8100); 38 | ``` 39 | 40 | **全局Api** 41 | 42 | ```java 43 | // 获取控件树 44 | String source = client.getPageSource(); 45 | 46 | // 检查client是否健康 47 | boolean health = client.health(); 48 | 49 | // 关闭wda client 50 | boolean bl = client.shutdown(); 51 | ``` 52 | 53 | **Session会话Api** 54 | 55 | ```java 56 | // 创建Session 57 | BaseResponse res = client.getSessionApi().createSession(); 58 | 59 | // 启动应用 60 | BaseResponse res = client.getSessionApi().launchApp("session id", "bundle id"); 61 | 62 | // 获取应用状态 63 | BaseResponse res = client.getSessionApi().getAppState(); 64 | 65 | // 激活应用(如果引用未启动,则重新启动) 66 | BaseResponse res = client.getSessionApi().activateApp("session id", "bundle id"); 67 | 68 | // 健康检查(wda会按一次home物理键,来检测手机是否卡主,wda是否正常工作) 69 | BaseResponse res = client.getSessionApi().healthCheck(); 70 | ``` 71 | 72 | **Screenshot截图Api** 73 | 74 | ```java 75 | // 截图(成功的话,返回Base64图片数据) 76 | BaseResponse res = client.getScreenshotApi().screenshot(); 77 | ``` 78 | 79 | **Orientation旋转Api** 80 | 81 | ```java 82 | // 获取当前手机屏幕方向 83 | // 横向 - LANDSCAPE 84 | // 竖向 - PORTRAIT 85 | BaseResponse res = client.getOrientationApi().getOrientation("session id"); 86 | ``` 87 | 88 | **Alert对话框Api** 89 | 90 | ```java 91 | // 获取当前alet对话框的文本,如果失败那就是界面没有alert提示框 92 | BaseResponse res = client.getAlertApi().getAlertText(); 93 | 94 | // 获取alert对话框的操作按钮的文本列表 95 | BaseResponse> res = client.getAlertApi().getAlertButtons("session id"); 96 | 97 | // 隐藏alert对话框 98 | BaseResponse res = client.getAlertApi().dismiss("session id"); 99 | 100 | // 点击对话框的某个按钮 101 | BaseResponse res = client.getAlertApi().accept("session id", "alert button name"); 102 | ``` 103 | 104 | **FindElement元素查找Api** 105 | 106 | ```java  107 | // 查找符合某个条件的所有元素 108 | QueryInfo query = new QueryInfo(); 109 | queryInfo.setUsing(QueryUsing.CLASS_NAME); 110 | queryInfo.setValue("XCUIElementTypeStaticText"); 111 | BaseResponse> res = client.getFindElementApi().elements("session id", query); 112 | 113 | // 查找某个元素下的符合条件的所有子元素 114 | BaseResponse> res = client.getFindElementApi().elements("session id", "target element uuid", query); 115 | ``` 116 | 117 | **Element元素Api** 118 | 119 | ```java 120 | // 获取手机窗口大小 121 | BaseResponse res = client.getElementApi().getWindowSize("session id"); 122 | 123 | // 检查某个元素是否禁用了 124 | BaseResponse res = client.getElementApi().enabled("session id", "element uuid"); 125 | 126 | // 获取某个元素的大小 127 | BaseResponse res = client.getElementApi().rect("session id", "element uuid"); 128 | 129 | // 获取某个元素的文本 130 | BaseResponse res = client.getElementApi().text("session id", "element uuid"); 131 | 132 | // 检查某个元素是否展现了 133 | BaseResponse res = client.getElementApi().displayed("session id", "element uuid"); 134 | 135 | // 检查某个元素是否被选择了 136 | BaseResponse res = client.getElementApi().selected("session id", "element uuid"); 137 | 138 | // 获取某个元素的名称 139 | BaseResponse res = client.getElementApi().name("session id", "element uuid"); 140 | 141 | // 对某个控件截图 142 | BaseResponse res = client.getElementApi().screenshot("session id", "element uuid"); 143 | 144 | // 控件输入值 145 | BaseResponse res = client.getElementApi().value("session id", "element uuid", "value"); 146 | 147 | // 点击某个控件 148 | BaseResponse res = client.getElementApi().click("session id", "element uuid"); 149 | 150 | // 清空输入框 151 | BaseResponse res = client.getElementApi().clear("session id", "element uuid"); 152 | 153 | // 滑动某个控件 154 | // 将一个控件往某个方向滑动 155 | // 包含两个参数: 156 | // direction表示滚动方向,可选值有:up、down、left、right 157 | // velocity表示滚动速度,只建议从50-100,值越大速度越快 158 | String direction = "down"; 159 | int velocity = 50; 160 | BaseResponse res = client.getElementApi().swip("session id", "element uuid", direction, velocity); 161 | 162 | // 长按一个控件 163 | // 可以设置时长,时间单位为秒 164 | int duration = 3; 165 | BaseResponse res = client.getElementApi().touchAndHold("session id", "element uuid", duration); 166 | 167 | // 坐标长按 168 | // 基于屏幕指定位置长按 169 | // x 坐标x值 100 double 170 | // y 坐标y值 200 double 171 | // duration 长按时间,单位秒 0.5 double 172 | BaseResponse res = client.getElementApi().touchAndHoldInCoordinate("session id", "element uuid", x, y, duration); 173 | 174 | // 控件拖动 175 | FromToParam param = new FromToParam(); 176 | param.setFromX(100); 177 | param.setFromY(100); 178 | param.setToX(500); 179 | param.setToY(500); 180 | param.setDuration(3); 181 | BaseResponse res = client.getElementApi().dragFromToForDuration("session id", "element uuid", param); 182 | 183 | // 屏幕拖动(基于全屏幕的坐标拖动) 184 | BaseResponse res = client.getElementApi().dragFromToForDurationInCoordinate("session id", param); 185 | 186 | // 拨动滚轮控件 187 | // 找到一个滚动,向上或向下滚动,滚动幅度取值从0.1到0.5,0.1表示一格,最大5格 188 | // 被选定的控件的类型必须为:XCUIElementTypePickerWheel,否则报错。 189 | // { 190 | // "order": "next", // 取值为next或者previous,不区分大小写 191 | // "offset": 0.1 // 滚动幅度取值从0.1到0.5,0.1表示一格,最大5格 192 | // } 193 | BaseResponse res = client.getElementApi().pickWheel("session id", "uuid", "next or pre", 1); 194 | 195 | // 输入文本 196 | // 对有输入焦点的控件输入字符串 197 | // 参数: 198 | // 字段名 含义 示例 199 | // value 要输入的字符串数组 ["hello world", "dddd"] 200 | // frequency 输入速度,整形,数字越大速度越快 10 201 | // 备注: 202 | // 如果当前界面没有输入焦点的控件,这个操作会等待一段时间直到超时或者有焦点的控件出现 203 | int frequency = 10; 204 | List values = new ArrayList<>(); 205 | values.add("hello world"); 206 | values.add("test"); 207 | BaseResponse res = client.getElementApi().keys("session id", frequency, values); 208 | // 或者 209 | BaseResponse res = client.getElementApi().keys("session id", frequency, ...keys); 210 | 211 | // 长按控件 212 | // 支持设定压力和时延,以及控件内的某个位置 213 | // pressure 压力值 0.5 214 | // duration 按压的时间,单位秒 3 215 | // x 该控件内的某个点的x值 216 | // y 该控件内的某个点的y值 217 | int x = 100; 218 | int y = 100; 219 | int duration = 2; 220 | double pressure = 0.5; 221 | BaseResponse res = client.getElementApi().forceTouch("session id", "element uuid", x, y, duration, pressure); 222 | 223 | // 坐标双击 224 | BaseResponse res = client.getElementApi().doubleTap("session id", x, y); 225 | ``` 226 | 227 | **Custom自定义Api** 228 | 229 | ```java 230 | // 重启当前应用 231 | BaseResponse res = client.getCustomApi().deactiveApp("session id"); 232 | 233 | // 锁屏 234 | BaseResponse res = client.getCustomApi().lock(); 235 | 236 | // 解锁 237 | BaseResponse res = client.getCustomApi().unlock(); 238 | 239 | // 判断手机是否锁屏 240 | BaseResponse res = client.getCustomApi().locked(); 241 | 242 | // 获取当前应用信息 243 | BaseResponse res = client.getCustomApi().getActiveAppInfo(); 244 | 245 | // 获取当前设备信息 246 | BaseResponse res = client.getCustomApi().getDeviceInfo(); 247 | 248 | // 手机回到屏幕主页 249 | BaseResponse res = client.getCustomApi().homeScreen(); 250 | ``` 251 | 252 | 253 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cn.speiyou.wda 8 | wda-java-client 9 | 1.5-SNAPSHOT 10 | jar 11 | 12 | 13 | 4.5.3 14 | 3.1 15 | 16 | 17 | 18 | 19 | com.alibaba 20 | fastjson 21 | 1.2.60 22 | 23 | 24 | org.apache.commons 25 | commons-lang3 26 | 3.9 27 | 28 | 29 | org.apache.httpcomponents 30 | httpclient 31 | ${httpclient.version} 32 | 33 | 34 | org.apache.httpcomponents 35 | fluent-hc 36 | ${httpclient.version} 37 | 38 | 39 | commons-httpclient 40 | commons-httpclient 41 | ${commons.httpclient} 42 | 43 | 44 | junit 45 | junit 46 | 4.12 47 | test 48 | 49 | 50 | io.appium 51 | java-client 52 | 7.4.1 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.apache.maven.plugins 60 | maven-compiler-plugin 61 | 62 | 8 63 | 8 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | nexus-releases 83 | nexus-release 84 | http://59.151.109.88:8081/nexus/content/repositories/releases/ 85 | 86 | 87 | nexus-snapshots 88 | nexus-snapshots 89 | http://59.151.109.88:8081/nexus/content/repositories/snapshots/ 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/BaseApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda; 2 | 3 | import cn.speiyou.wda.session.res.CreateSession; 4 | import com.alibaba.fastjson.TypeReference; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | /** 8 | * @author :cmlanche 9 | * @date :Created in 2020/12/8 4:03 下午 10 | */ 11 | public class BaseApi { 12 | 13 | protected WDAClient wda; 14 | private String tag; 15 | 16 | public BaseApi(WDAClient client) { 17 | this.wda = client; 18 | this.tag = getClass().getSimpleName(); 19 | } 20 | 21 | public String getBaseUrl() { 22 | return wda.getBaseUrl(); 23 | } 24 | 25 | public String getBaseUrlWithSession(String sessionId) { 26 | return String.format("%s/session/%s", getBaseUrl(), sessionId); 27 | } 28 | 29 | public String getPathWithUUID(String uuid) { 30 | return "/element/" + uuid; 31 | } 32 | 33 | /** 34 | * 获取SessionId,如果为空,则重建一个 35 | * @param forceCreate 是否强制重建 36 | * @return 37 | */ 38 | private synchronized String getSessionId(boolean forceCreate) { 39 | if (forceCreate || StringUtils.isEmpty(wda.getCurrentSessionId())) { 40 | BaseResponse res = wda.getSessionApi().createSession(); 41 | if (res.isSuccess()) { 42 | wda.setCurrentSessionId(res.getValue().getSessionId()); 43 | return wda.getCurrentSessionId(); 44 | } else { 45 | throw new RuntimeException("创建Session失败!"); 46 | } 47 | } else { 48 | return wda.getCurrentSessionId(); 49 | } 50 | } 51 | 52 | public BaseResponse get(String url, TypeReference> typeReference) { 53 | return wda.getHttpProxy().get(url, typeReference); 54 | } 55 | 56 | /** 57 | * post请求 58 | */ 59 | public BaseResponse post(String url, Object obj, TypeReference> typeReference) { 60 | return wda.getHttpProxy().post(url, obj, typeReference); 61 | } 62 | 63 | /** 64 | * 带Session的get请求 65 | * ps:如果发现Session无效,则重建Session,并重新发送请求 66 | * @param path 67 | * @param typeReference 68 | * @param 69 | * @return 70 | */ 71 | public synchronized BaseResponse getWithSession(String path, TypeReference> typeReference) { 72 | String sessionId = getSessionId(false); 73 | BaseResponse res = get(getBaseUrlWithSession(sessionId) + path, typeReference); 74 | if (!res.isSuccess() && "invalid session id".equals(res.getErr().getError())) { 75 | wda.logInfo(tag, String.format("请求:%s,Session[%s]失效了,重新申请", path, sessionId)); 76 | sessionId = getSessionId(true); 77 | wda.logInfo(tag, "重新申请的SessionId为:" + sessionId); 78 | res = get(getBaseUrlWithSession(sessionId) + path, typeReference); 79 | if (!res.isSuccess()) { 80 | wda.logInfo(tag, String.format("请求:%s,Session[%s]仍然失败,请检查bug", path, sessionId)); 81 | } 82 | return res; 83 | } 84 | return res; 85 | } 86 | 87 | /** 88 | * 带Session的post请求 89 | * ps:如果发现Session无效,则重建Session,并重新发送请求 90 | * @param path 91 | * @param typeReference 92 | * @param 93 | * @return 94 | */ 95 | public synchronized BaseResponse postWithSession(String path, Object obj, TypeReference> typeReference) { 96 | String sessionId = getSessionId(false); 97 | BaseResponse res = post(getBaseUrlWithSession(sessionId) + path, obj, typeReference); 98 | if (!res.isSuccess() && "invalid session id".equals(res.getErr().getError())) { 99 | wda.logInfo(tag, String.format("请求:%s,Session[%s]失效了,重新申请", path, sessionId)); 100 | sessionId = getSessionId(true); 101 | wda.logInfo(tag, "重新申请的SessionId为:" + sessionId); 102 | res = post(getBaseUrlWithSession(sessionId) + path, obj, typeReference); 103 | if (!res.isSuccess()) { 104 | wda.logInfo(tag, String.format("请求:%s,Session[%s]仍然失败,请检查bug", path, sessionId)); 105 | } 106 | return res; 107 | } 108 | return res; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/BaseResponse.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 3:29 下午 6 | */ 7 | public class BaseResponse { 8 | 9 | // 返回值 10 | private T value; 11 | // 当前session id 12 | private String sessionId; 13 | // 是否调用成功 14 | private boolean success; 15 | // 调用失败的错误信息 16 | private Error err; 17 | 18 | public BaseResponse() { 19 | this.success = false; 20 | } 21 | 22 | public BaseResponse(boolean success) { 23 | this.success = success; 24 | } 25 | 26 | public BaseResponse(T value) { 27 | this.success = true; 28 | this.value = value; 29 | } 30 | 31 | public T getValue() { 32 | return value; 33 | } 34 | 35 | public void setValue(T value) { 36 | this.value = value; 37 | } 38 | 39 | public String getSessionId() { 40 | return sessionId; 41 | } 42 | 43 | public void setSessionId(String sessionId) { 44 | this.sessionId = sessionId; 45 | } 46 | 47 | public boolean isSuccess() { 48 | return success; 49 | } 50 | 51 | public void setSuccess(boolean success) { 52 | this.success = success; 53 | } 54 | 55 | public Error getErr() { 56 | return err; 57 | } 58 | 59 | public void setErr(Error err) { 60 | this.err = err; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/Error.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/10 2:59 下午 6 | */ 7 | public class Error { 8 | 9 | // 错误信息 10 | private String error; 11 | // 错误描述 12 | private String message; 13 | // 错误追踪 14 | private String traceback; 15 | 16 | public String getError() { 17 | return error; 18 | } 19 | 20 | public void setError(String error) { 21 | this.error = error; 22 | } 23 | 24 | public String getMessage() { 25 | return message; 26 | } 27 | 28 | public void setMessage(String message) { 29 | this.message = message; 30 | } 31 | 32 | public String getTraceback() { 33 | return traceback; 34 | } 35 | 36 | public void setTraceback(String traceback) { 37 | this.traceback = traceback; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/HttpProxy.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda; 2 | 3 | import cn.speiyou.wda.element.res.WDARect; 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.TypeReference; 6 | import org.apache.commons.lang3.StringUtils; 7 | import org.apache.http.Consts; 8 | import org.apache.http.HttpEntity; 9 | import org.apache.http.HttpResponse; 10 | import org.apache.http.client.fluent.Content; 11 | import org.apache.http.client.fluent.Request; 12 | import org.apache.http.client.fluent.Response; 13 | import org.apache.http.entity.ContentType; 14 | import org.apache.http.util.EntityUtils; 15 | 16 | import java.io.IOException; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** 21 | * @author :cmlanche 22 | * @date :Created in 2020/12/15 10:50 上午 23 | */ 24 | public class HttpProxy { 25 | 26 | private WDAClient client; 27 | private int connectTimeOut = 600000; 28 | private int socketTimeOut = 600000; 29 | // 最近一次的请求时间 30 | private long lastestRequestTime = 0; 31 | // 每次请求的间隔 32 | private long requestDuration = 0; 33 | 34 | public HttpProxy(WDAClient client) { 35 | this.client = client; 36 | } 37 | 38 | /** 39 | * get请求 40 | * @param url 41 | * @param 42 | * @return 43 | */ 44 | public BaseResponse get(String url, TypeReference> typeReference) { 45 | restForRequest(); 46 | Request request = Request.Get(url).connectTimeout(connectTimeOut).socketTimeout(socketTimeOut); 47 | configureHeaders(request); 48 | return execute(request, typeReference); 49 | } 50 | 51 | /** 52 | * post请求 53 | */ 54 | public BaseResponse post(String url, Object obj, TypeReference> typeReference) { 55 | restForRequest(); 56 | Request request = Request.Post(url).connectTimeout(connectTimeOut).socketTimeout(socketTimeOut); 57 | configureHeaders(request); 58 | configureBody(request, obj); 59 | return execute(request, typeReference); 60 | } 61 | 62 | /** 63 | * 检查两次请求的时间间隔,如果太短,则要求休眠 64 | */ 65 | private void restForRequest() { 66 | if (lastestRequestTime > 0) { 67 | if (System.currentTimeMillis() - lastestRequestTime > requestDuration) { 68 | sleep(requestDuration); 69 | } else { 70 | sleep(System.currentTimeMillis() - lastestRequestTime); 71 | } 72 | } 73 | lastestRequestTime = System.currentTimeMillis(); 74 | } 75 | 76 | public void setConnectTimeOut(int connectTimeOut) { 77 | this.connectTimeOut = connectTimeOut; 78 | } 79 | 80 | public void setSocketTimeOut(int socketTimeOut) { 81 | this.socketTimeOut = socketTimeOut; 82 | } 83 | 84 | public void setRequestDuration(long requestDuration) { 85 | this.requestDuration = requestDuration; 86 | } 87 | 88 | /** 89 | * 执行一个请求 90 | * @param request 91 | * @param 92 | * @return 93 | */ 94 | private static BaseResponse execute(Request request, TypeReference> typeReference) { 95 | try { 96 | String s = getResponseContent(request); 97 | if (StringUtils.contains(s, "traceback")) { 98 | return handleError(s); 99 | } 100 | if (typeReference == null) { 101 | BaseResponse r; 102 | if (StringUtils.equals("I-AM-ALIVE", s) || StringUtils.equals("Shutting down", s)) { 103 | r = new BaseResponse(); 104 | } else { 105 | r = JSON.parseObject(s, BaseResponse.class); 106 | } 107 | r.setSuccess(true); 108 | return r; 109 | } 110 | BaseResponse r = JSON.parseObject(s, typeReference); 111 | r.setSuccess(true); 112 | return r; 113 | } catch (Exception e) { 114 | e.printStackTrace(); 115 | return new BaseResponse<>(); 116 | } 117 | } 118 | 119 | protected static void configureHeaders(Request request) { 120 | Map headers = new HashMap<>(); 121 | headers.put("Content-Type", "application/json; charset=utf-8"); 122 | headers.forEach(request::setHeader); 123 | } 124 | 125 | protected static void configureBody(Request request, Object body) { 126 | if (body != null) { 127 | request.bodyString(JSON.toJSONString(body), ContentType.APPLICATION_JSON); 128 | } 129 | } 130 | 131 | protected static String getResponseContent(Request request) throws IOException { 132 | Response response = request.execute(); 133 | HttpResponse httpResponse = response.returnResponse(); 134 | return handleEntity(httpResponse.getEntity()).asString(Consts.UTF_8); 135 | } 136 | 137 | protected static Content handleEntity(HttpEntity entity) throws IOException { 138 | return entity != null ? new Content(EntityUtils.toByteArray(entity), ContentType.getOrDefault(entity)) : Content.NO_CONTENT; 139 | } 140 | 141 | public static BaseResponse handleError(String res) { 142 | BaseResponse errRes = JSON.parseObject(res, new TypeReference>(){}); 143 | BaseResponse r = new BaseResponse<>(); 144 | r.setSuccess(false); 145 | r.setErr(errRes.getValue()); 146 | return r; 147 | } 148 | 149 | private static void sleep(long time) { 150 | try { 151 | if (time > 0) { 152 | Thread.sleep(time); 153 | } 154 | } catch (InterruptedException e) { 155 | e.printStackTrace(); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/ILog.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2021/1/15 4:54 下午 6 | */ 7 | public interface ILog { 8 | 9 | void logInfo(String tag, String info); 10 | 11 | void logError(String tag, String error, Throwable throwable); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/SessionInValidateException.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda; 2 | 3 | /** 4 | * session会话异常错误 5 | * @author :cmlanche 6 | * @date :Created in 2020/12/10 3:19 下午 7 | */ 8 | public class SessionInValidateException extends Exception { 9 | 10 | private Error error; 11 | 12 | public SessionInValidateException(Error error) { 13 | this.error = error; 14 | } 15 | 16 | public Error getError() { 17 | return error; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/WDAClient.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda; 2 | 3 | import cn.speiyou.wda.alert.AlertApi; 4 | import cn.speiyou.wda.custom.CustomApi; 5 | import cn.speiyou.wda.element.ElementApi; 6 | import cn.speiyou.wda.findelement.FindElementApi; 7 | import cn.speiyou.wda.orientation.OrientationApi; 8 | import cn.speiyou.wda.screenshot.ScreenshotApi; 9 | import cn.speiyou.wda.session.SessionApi; 10 | 11 | /** 12 | * WebDriverAgent Client 13 | * @author :cmlanche 14 | * @date :Created in 2020/12/8 3:22 下午 15 | */ 16 | public class WDAClient { 17 | // 服务地址 18 | private String host; 19 | // 服务端口 20 | private int port; 21 | // Session模块Api 22 | private SessionApi sessionApi; 23 | // 截图模块Api 24 | private ScreenshotApi screenshotApi; 25 | // 查找元素模块Api 26 | private FindElementApi findElementApi; 27 | // 屏幕方向Api 28 | private OrientationApi orientationApi; 29 | // 自定义Api 30 | private CustomApi customApi; 31 | // 控件操作Api 32 | private ElementApi elementApi; 33 | // 对话框操作API 34 | private AlertApi alertApi; 35 | 36 | // http请求代理 37 | private HttpProxy httpProxy; 38 | 39 | // 当前请求的SessionId 40 | private String currentSessionId; 41 | 42 | // 日志接口 43 | private ILog log; 44 | 45 | public WDAClient(String host, int port) { 46 | this.host = host; 47 | this.port = port; 48 | this.sessionApi = new SessionApi(this); 49 | this.screenshotApi = new ScreenshotApi(this); 50 | this.findElementApi = new FindElementApi(this); 51 | this.orientationApi = new OrientationApi(this); 52 | this.customApi = new CustomApi(this); 53 | this.elementApi = new ElementApi(this); 54 | this.alertApi = new AlertApi(this); 55 | this.httpProxy = new HttpProxy(this); 56 | } 57 | 58 | /** 59 | * 60 | * @param host 61 | * @param port 62 | * @param restDuration 相邻两次请求不要间隔太短 63 | */ 64 | public WDAClient(String host, int port, int restDuration) { 65 | this(host, port); 66 | this.httpProxy.setRequestDuration(restDuration); 67 | } 68 | 69 | public String getBaseUrl() { 70 | return String.format("http://%s:%d", this.host, this.port); 71 | } 72 | 73 | /** 74 | * 获取控件树 75 | */ 76 | public BaseResponse getPageSource() { 77 | return httpProxy.get(getBaseUrl() + "/source", null); 78 | } 79 | 80 | /** 81 | * 检查wda是否健康 82 | */ 83 | public BaseResponse health() { 84 | return httpProxy.get(getBaseUrl() + "/health", null); 85 | } 86 | 87 | /** 88 | * 关闭WDA 89 | */ 90 | public BaseResponse shutdown() { 91 | return httpProxy.get(getBaseUrl() + "/wda/shutdown", null); 92 | } 93 | 94 | public String getHost() { 95 | return host; 96 | } 97 | 98 | public int getPort() { 99 | return port; 100 | } 101 | 102 | public SessionApi getSessionApi() { 103 | return sessionApi; 104 | } 105 | 106 | public ScreenshotApi getScreenshotApi() { 107 | return screenshotApi; 108 | } 109 | 110 | public FindElementApi getFindElementApi() { 111 | return findElementApi; 112 | } 113 | 114 | public OrientationApi getOrientationApi() { 115 | return orientationApi; 116 | } 117 | 118 | public CustomApi getCustomApi() { 119 | return customApi; 120 | } 121 | 122 | public ElementApi getElementApi() { 123 | return elementApi; 124 | } 125 | 126 | public AlertApi getAlertApi() { 127 | return alertApi; 128 | } 129 | 130 | public HttpProxy getHttpProxy() { 131 | return httpProxy; 132 | } 133 | 134 | public String getCurrentSessionId() { 135 | return currentSessionId; 136 | } 137 | 138 | public void setCurrentSessionId(String currentSessionId) { 139 | this.currentSessionId = currentSessionId; 140 | } 141 | 142 | public ILog getLog() { 143 | return log; 144 | } 145 | 146 | public void setLog(ILog log) { 147 | this.log = log; 148 | } 149 | 150 | public void logInfo(String tag, String info) { 151 | if (log != null) { 152 | log.logInfo(tag, info); 153 | } 154 | } 155 | 156 | public void logError(String tag, String error, Throwable throwable) { 157 | if (log != null) { 158 | log.logError(tag, error, throwable); 159 | } 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/alert/AlertApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.alert; 2 | 3 | import cn.speiyou.wda.BaseApi; 4 | import cn.speiyou.wda.BaseResponse; 5 | import cn.speiyou.wda.WDAClient; 6 | import com.alibaba.fastjson.JSONObject; 7 | import com.alibaba.fastjson.TypeReference; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * @author :cmlanche 13 | * @date :Created in 2020/12/11 4:22 下午 14 | */ 15 | public class AlertApi extends BaseApi { 16 | 17 | public AlertApi(WDAClient client) { 18 | super(client); 19 | } 20 | 21 | /** 22 | * 获取当前alert对话框的信息 23 | * @return 如果失败,则当前界面可能没有alert提示框 24 | */ 25 | public BaseResponse getAlertText() { 26 | return get(getBaseUrl() + "/alert/text", null); 27 | } 28 | 29 | /** 30 | * 获取alert对话框的按钮 31 | * @return 32 | */ 33 | public BaseResponse> getAlertButtons() { 34 | return getWithSession("/wda/alert/buttons", 35 | new TypeReference>>(){}); 36 | } 37 | 38 | /** 39 | * 隐藏alert对话框 40 | * @return 41 | */ 42 | public BaseResponse dismiss() { 43 | return postWithSession("/alert/dismiss", null, null); 44 | } 45 | 46 | /** 47 | * 点击某个按钮,隐藏对话框 48 | * @param name 49 | * @return 50 | */ 51 | public BaseResponse accept(String name) { 52 | JSONObject obj = new JSONObject(); 53 | obj.put("name", name); 54 | return postWithSession("/alert/accept", obj, null); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/custom/CustomApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.custom; 2 | 3 | import cn.speiyou.wda.BaseApi; 4 | import cn.speiyou.wda.BaseResponse; 5 | import cn.speiyou.wda.WDAClient; 6 | import cn.speiyou.wda.custom.res.ActiveAppInfo; 7 | import cn.speiyou.wda.custom.res.WDADeviceInfo; 8 | import com.alibaba.fastjson.TypeReference; 9 | 10 | /** 11 | * @author :cmlanche 12 | * @date :Created in 2020/12/10 6:09 下午 13 | */ 14 | public class CustomApi extends BaseApi { 15 | 16 | public CustomApi(WDAClient client) { 17 | super(client); 18 | } 19 | 20 | /** 21 | * 重启当前应用 22 | * @return 23 | */ 24 | public BaseResponse deactiveApp() { 25 | return postWithSession("/wda/deactivateApp", null, null); 26 | } 27 | 28 | /** 29 | * 解锁手机 30 | * @return 31 | */ 32 | public BaseResponse lock() { 33 | return post(getBaseUrl() + "/wda/lock", null, null); 34 | } 35 | 36 | 37 | /** 38 | * 解锁手机 39 | * @return 40 | */ 41 | public BaseResponse unlock() { 42 | return post(getBaseUrl() + "/wda/unlock", null, null); 43 | } 44 | 45 | /** 46 | * 获取当前活动的应用信息 47 | * @return 48 | */ 49 | public BaseResponse getActiveAppInfo() { 50 | return get(getBaseUrl() + "/wda/activeAppInfo", 51 | new TypeReference>(){}); 52 | } 53 | 54 | /** 55 | * 获取当前活动的应用信息 56 | * @return 57 | */ 58 | public BaseResponse getDeviceInfo() { 59 | return get(getBaseUrl() + "/wda/device/info", 60 | new TypeReference>(){}); 61 | } 62 | 63 | /** 64 | * 回到主页 65 | * @return 66 | */ 67 | public BaseResponse homeScreen() { 68 | return post(getBaseUrl() + "/wda/homescreen", null, null); 69 | } 70 | 71 | /** 72 | * 判断当前手机屏幕是否锁屏 73 | * @return 74 | */ 75 | public BaseResponse locked() { 76 | return get(getBaseUrl() + "/wda/locked", null); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/custom/res/ActiveAppInfo.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.custom.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/10 6:55 下午 6 | */ 7 | public class ActiveAppInfo { 8 | 9 | // 应用名 10 | private String name; 11 | // 应用进程名 12 | private String pid; 13 | // 应用包名 14 | private String bundleId; 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public String getPid() { 25 | return pid; 26 | } 27 | 28 | public void setPid(String pid) { 29 | this.pid = pid; 30 | } 31 | 32 | public String getBundleId() { 33 | return bundleId; 34 | } 35 | 36 | public void setBundleId(String bundleId) { 37 | this.bundleId = bundleId; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/custom/res/WDADeviceInfo.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.custom.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/10 7:01 下午 6 | */ 7 | public class WDADeviceInfo { 8 | 9 | // 时区 10 | private String timeZone; 11 | // 当前设备使用的语言,中文还是英文等 12 | private String currentLocale; 13 | // 手机型号,iphone 14 | private String model; 15 | // 设备编号 16 | private String uuid; 17 | private int userInterfaceIdiom; 18 | // 设备UI风格样式,是白色还是暗黑模式 19 | private String userInterfaceStyle; 20 | // 手机名称 21 | private String name; 22 | // 是否是模拟器 23 | private boolean isSimulator; 24 | 25 | public String getTimeZone() { 26 | return timeZone; 27 | } 28 | 29 | public void setTimeZone(String timeZone) { 30 | this.timeZone = timeZone; 31 | } 32 | 33 | public String getCurrentLocale() { 34 | return currentLocale; 35 | } 36 | 37 | public void setCurrentLocale(String currentLocale) { 38 | this.currentLocale = currentLocale; 39 | } 40 | 41 | public String getModel() { 42 | return model; 43 | } 44 | 45 | public void setModel(String model) { 46 | this.model = model; 47 | } 48 | 49 | public String getUuid() { 50 | return uuid; 51 | } 52 | 53 | public void setUuid(String uuid) { 54 | this.uuid = uuid; 55 | } 56 | 57 | public int getUserInterfaceIdiom() { 58 | return userInterfaceIdiom; 59 | } 60 | 61 | public void setUserInterfaceIdiom(int userInterfaceIdiom) { 62 | this.userInterfaceIdiom = userInterfaceIdiom; 63 | } 64 | 65 | public String getUserInterfaceStyle() { 66 | return userInterfaceStyle; 67 | } 68 | 69 | public void setUserInterfaceStyle(String userInterfaceStyle) { 70 | this.userInterfaceStyle = userInterfaceStyle; 71 | } 72 | 73 | public String getName() { 74 | return name; 75 | } 76 | 77 | public void setName(String name) { 78 | this.name = name; 79 | } 80 | 81 | public boolean isSimulator() { 82 | return isSimulator; 83 | } 84 | 85 | public void setSimulator(boolean simulator) { 86 | isSimulator = simulator; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/element/ElementApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.element; 2 | 3 | import cn.speiyou.wda.BaseApi; 4 | import cn.speiyou.wda.BaseResponse; 5 | import cn.speiyou.wda.WDAClient; 6 | import cn.speiyou.wda.element.res.WDARect; 7 | import cn.speiyou.wda.element.res.WindowSize; 8 | import cn.speiyou.wda.session.req.FromToParam; 9 | import com.alibaba.fastjson.JSONObject; 10 | import com.alibaba.fastjson.TypeReference; 11 | import org.apache.commons.lang3.StringUtils; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * @author :cmlanche 17 | * @date :Created in 2020/12/11 10:23 上午 18 | */ 19 | public class ElementApi extends BaseApi { 20 | 21 | public ElementApi(WDAClient client) { 22 | super(client); 23 | } 24 | 25 | /** 26 | * 获取窗口大小 27 | * @return 28 | */ 29 | public BaseResponse getWindowSize() { 30 | return getWithSession("/window/size", new TypeReference>(){}); 31 | } 32 | 33 | /** 34 | * 检查某元素是否禁用了 35 | * @param elementUUID 36 | * @return 37 | */ 38 | public BaseResponse enabled(String elementUUID) { 39 | return get("/enabled", elementUUID, null); 40 | } 41 | 42 | /** 43 | * 检查某元素的大小 44 | * @param elementUUID 45 | * @return 46 | */ 47 | public BaseResponse rect(String elementUUID) { 48 | return get("/rect", elementUUID, new TypeReference>(){}); 49 | } 50 | 51 | /** 52 | * 检查某元素的文本 53 | * @param elementUUID 54 | * @return 55 | */ 56 | public BaseResponse text(String elementUUID) { 57 | return get("/text", elementUUID, null); 58 | } 59 | 60 | /** 61 | * 获取某个节点的属性值 62 | * @param elementUUID 63 | * @param attrName 64 | * @return 65 | */ 66 | public BaseResponse attrValue(String elementUUID, String attrName) { 67 | return get("/attribute/" + attrName, elementUUID, null); 68 | } 69 | 70 | /** 71 | * 检查某元素是否展现出来 72 | * @param elementUUID 73 | * @return 74 | */ 75 | public BaseResponse displayed(String elementUUID) { 76 | return get("/displayed", elementUUID, null); 77 | } 78 | 79 | /** 80 | * 检查某元素是否被选择 81 | * @param elementUUID 82 | * @return 83 | */ 84 | public BaseResponse selected(String elementUUID) { 85 | return get("/selected", elementUUID, null); 86 | } 87 | 88 | /** 89 | * 检查某元素的名称 90 | * @param elementUUID 91 | * @return 92 | */ 93 | public BaseResponse name(String elementUUID) { 94 | return get("/name", elementUUID, null); 95 | } 96 | 97 | /** 98 | * 对控件截图 99 | * @param elementUUID 100 | * @return 101 | */ 102 | public BaseResponse screenshot(String elementUUID) { 103 | return get("/screenshot", elementUUID, null); 104 | } 105 | 106 | /** 107 | * 针对某个元素调用某方法 108 | * @param elementUUID 109 | * @return 110 | */ 111 | private BaseResponse get(String path, String elementUUID, TypeReference> typeReference) { 112 | return getWithSession(getPathWithUUID(elementUUID) + path, typeReference); 113 | } 114 | 115 | /** 116 | * 给控件设置值 117 | * @param elementUUID 118 | * @return 119 | */ 120 | public BaseResponse value(String elementUUID, String value) { 121 | JSONObject obj = new JSONObject(); 122 | obj.put("value", value); 123 | return postWithSessionAndUUID("/value", elementUUID, obj, null); 124 | } 125 | 126 | /** 127 | * 点击控件 128 | * @param sessionId 129 | * @param elementUUID 130 | * @return 131 | */ 132 | public BaseResponse click(String elementUUID) { 133 | return postWithSessionAndUUID("/click", elementUUID, null, null); 134 | } 135 | 136 | /** 137 | * 清空输入框 138 | * @param sessionId 139 | * @param elementUUID 140 | * @return 141 | */ 142 | public BaseResponse clear(String sessionId, String elementUUID) { 143 | return postWithSessionAndUUID("/clear", elementUUID, null, null); 144 | } 145 | 146 | /** 147 | * 控件滑动 148 | * 将一个控件往某个方向滚动多少距离。 149 | * 包含两个参数: 150 | * direction表示滚动方向,可选值有:up、down、left、right 151 | * velocity表示滚动速度,只建议从50-100,值越大速度越快 152 | * { "direction": "down" } 153 | * @param elementUUID 154 | * @param direction 往某个方向滑动 155 | * @param velocity 滑动速度 156 | * @return 157 | */ 158 | public BaseResponse swip(String elementUUID, String direction, int velocity) { 159 | JSONObject json = new JSONObject(); 160 | json.put("direction", direction); 161 | json.put("velocity", velocity); 162 | return postWithSessionAndUUID("/swip", elementUUID, json, null); 163 | } 164 | 165 | /** 166 | * 长按一个控件,可以设置长按时间,时间单位为秒 167 | * @param elementUUID 168 | * @param duration 169 | * @return 170 | */ 171 | public BaseResponse touchAndHold(String elementUUID, double duration) { 172 | JSONObject json = new JSONObject(); 173 | json.put("duration", duration); 174 | return postWithSessionAndUUID("/touchAndHold", elementUUID, json, null); 175 | } 176 | 177 | /** 178 | * 拖动一个控件从哪个地方到那个地方 179 | * @param elementUUID 180 | * @param param 181 | * @return 182 | */ 183 | public BaseResponse dragFromToForDuration(String elementUUID, FromToParam param) { 184 | return postWithSessionAndUUID("/dragfromtoforduration", elementUUID, param, null); 185 | } 186 | 187 | /** 188 | * 在屏幕上拖动 189 | * @param param 190 | * @return 191 | */ 192 | public BaseResponse dragFromToForDurationInCoordinate(FromToParam param) { 193 | return postWithSession("/wda/dragfromtoforduration", param, null); 194 | } 195 | 196 | /** 197 | * 拨动滚轮控件 198 | * 找到一个滚动,向上或向下滚动,滚动幅度取值从0.1到0.5,0.1表示一格,最大5格 199 | * 200 | * 被选定的控件的类型必须为:XCUIElementTypePickerWheel,否则报错。 201 | * 202 | * { 203 | * "order": "next", // 取值为next或者previous,不区分大小写 204 | * "offset": 0.1 // 滚动幅度取值从0.1到0.5,0.1表示一格,最大5格 205 | * } 206 | * @param sessionId 207 | * @param uuid 208 | * @param order 滚动方向, next or pre 209 | * @param offset 已经变成整形了,取值从1-5 210 | * @return 211 | */ 212 | public BaseResponse pickWheel(String sessionId, String uuid, String order, int offset) { 213 | JSONObject obj = new JSONObject(); 214 | obj.put("order", order); 215 | obj.put("offset", (float) offset / 10); 216 | return postWithSession(String.format("/wda/pickerwheel/%s/select", uuid), obj, null); 217 | } 218 | 219 | /** 220 | * 对有输入焦点的控件输入字符串 221 | * 参数: 222 | * 223 | * 字段名 含义 示例 224 | * value 要输入的字符串数组 ["hello world", "dddd"] 225 | * frequency 输入速度,整形,数字越大速度越快 10 226 | * 备注: 227 | * 228 | * 如果当前界面没有输入焦点的控件,这个操作会等待一段时间直到超时或者有焦点的控件出现 229 | * @param keys 230 | * @param frequency 231 | * @return 232 | */ 233 | public BaseResponse keys(int frequency, List keys) { 234 | JSONObject json = new JSONObject(); 235 | json.put("value", keys); 236 | json.put("frequency", frequency); 237 | return postWithSession("/wda/keys", json, null); 238 | } 239 | 240 | /** 241 | * 对有输入焦点的控件输入字符串 242 | * 参数: 243 | * 244 | * 字段名 含义 示例 245 | * value 要输入的字符串数组 ["hello world", "dddd"] 246 | * frequency 输入速度,整形,数字越大速度越快 10 247 | * 备注: 248 | * 249 | * 如果当前界面没有输入焦点的控件,这个操作会等待一段时间直到超时或者有焦点的控件出现 250 | * @param keys 字符串数组 251 | * @param frequency 输入速度 252 | * @return 253 | */ 254 | public BaseResponse keys(int frequency, String... keys) { 255 | JSONObject json = new JSONObject(); 256 | json.put("value", keys); 257 | json.put("frequency", frequency); 258 | return postWithSession("/wda/keys", json, null); 259 | } 260 | 261 | /** 262 | * 长按某个控件,支持设定压力和时延,以及控件内的某个位置 263 | * 264 | * 参数表: 265 | * 266 | * 字段名称 含义 示例 267 | * pressure 压力值 0.5 268 | * duration 按压的时间,单位秒 3 269 | * x 该控件内的某个点的x值,可选 10 270 | * y 该控件内的某个点的y值,可选 10 271 | * @param elementUUID 272 | * @param x 控件内的x值 273 | * @param y 控件内的y值 274 | * @param duration 时延,单位秒 275 | * @param pressure 压力值,如0.5 276 | * @return 277 | */ 278 | public BaseResponse forceTouch(String session, String elementUUID, int x, int y, int duration, double pressure) { 279 | JSONObject json = new JSONObject(); 280 | json.put("x", x); 281 | json.put("y", y); 282 | json.put("duration", duration); 283 | json.put("pressure", pressure); 284 | return postWithSession(String.format("/wda/element/%s/forceTouch", elementUUID), json, null); 285 | } 286 | 287 | /** 288 | * 以某个元素的左上角为原点,点击某个位置 289 | * @param elementUUID 可以为空,当为空时,相对屏幕左上角为原点,非空时相对此元素的左上角为原点 290 | * @param x 291 | * @param y 292 | * @return 293 | */ 294 | public BaseResponse tap(String elementUUID, double x, double y) { 295 | JSONObject json = new JSONObject(); 296 | json.put("x", x); 297 | json.put("y", y); 298 | String url = "/wda/tap"; 299 | if (StringUtils.isNotEmpty(elementUUID)) { 300 | url += "/" + elementUUID; 301 | } else { 302 | url += "/null"; 303 | } 304 | return postWithSession(url, json, null); 305 | } 306 | 307 | /** 308 | * 坐标双击 309 | * 指定位置双击 310 | * 311 | * 字段名称 含义 示例 类型 312 | * x 坐标x值 100 double 313 | * y 坐标y值 200 double 314 | * @param x 屏幕上x值 315 | * @param y 屏幕上y值 316 | * @return 317 | */ 318 | public BaseResponse doubleTap(int x, int y) { 319 | JSONObject json = new JSONObject(); 320 | json.put("x", x); 321 | json.put("y", y); 322 | return postWithSession("/wda/doubleTap", json, null); 323 | } 324 | 325 | /** 326 | * 坐标长按 327 | * 指定位置长按 328 | * 329 | * 字段名称 含义 示例 类型 330 | * x 坐标x值 100 double 331 | * y 坐标y值 200 double 332 | * duration 长按时间,单位秒 0.5 double 333 | * @param x 334 | * @param y 335 | * @param duration 336 | * @return 337 | */ 338 | public BaseResponse touchAndHoldInCoordinate(int x, int y, int duration) { 339 | JSONObject json = new JSONObject(); 340 | json.put("x", x); 341 | json.put("y", y); 342 | json.put("duration", duration); 343 | return postWithSession("/wda/touchAndHold", json, null); 344 | } 345 | 346 | /** 347 | * 针对某个元素调用某方法 348 | * @param elementUUID 349 | * @return 350 | */ 351 | private BaseResponse postWithSessionAndUUID(String path, String elementUUID, 352 | Object obj, TypeReference> typeReference) { 353 | return postWithSession(getPathWithUUID(elementUUID) + path, obj, typeReference); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/element/res/WDARect.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.element.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/11 10:34 上午 6 | */ 7 | public class WDARect { 8 | 9 | private int x; 10 | private int y; 11 | private int width; 12 | private int height; 13 | 14 | public int getX() { 15 | return x; 16 | } 17 | 18 | public void setX(int x) { 19 | this.x = x; 20 | } 21 | 22 | public int getY() { 23 | return y; 24 | } 25 | 26 | public void setY(int y) { 27 | this.y = y; 28 | } 29 | 30 | public int getWidth() { 31 | return width; 32 | } 33 | 34 | public void setWidth(int width) { 35 | this.width = width; 36 | } 37 | 38 | public int getHeight() { 39 | return height; 40 | } 41 | 42 | public void setHeight(int height) { 43 | this.height = height; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/element/res/WindowSize.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.element.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/11 10:24 上午 6 | */ 7 | public class WindowSize { 8 | 9 | // 宽度 10 | private int width; 11 | 12 | // 高度 13 | private int height; 14 | 15 | public int getWidth() { 16 | return width; 17 | } 18 | 19 | public void setWidth(int width) { 20 | this.width = width; 21 | } 22 | 23 | public int getHeight() { 24 | return height; 25 | } 26 | 27 | public void setHeight(int height) { 28 | this.height = height; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/findelement/FindElementApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.findelement; 2 | 3 | import cn.speiyou.wda.BaseApi; 4 | import cn.speiyou.wda.BaseResponse; 5 | import cn.speiyou.wda.WDAClient; 6 | import cn.speiyou.wda.findelement.req.QueryInfo; 7 | import cn.speiyou.wda.findelement.res.Element; 8 | import com.alibaba.fastjson.TypeReference; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * @author :cmlanche 14 | * @date :Created in 2020/12/8 6:54 下午 15 | */ 16 | public class FindElementApi extends BaseApi { 17 | 18 | public FindElementApi(WDAClient client) { 19 | super(client); 20 | } 21 | 22 | 23 | /** 24 | * 查找符合某个条件的所有元素 25 | * @param queryInfo 26 | * @return 27 | */ 28 | public BaseResponse> elements(QueryInfo queryInfo) { 29 | return postWithSession("/elements", 30 | queryInfo, new TypeReference>>(){}); 31 | } 32 | 33 | /** 34 | * 查找某个元素下的符合某条件的控件 35 | * @param parentElementUUID 36 | * @param queryInfo 37 | * @return 38 | */ 39 | public BaseResponse> elements(String parentElementUUID, QueryInfo queryInfo) { 40 | return postWithSession(getPathWithUUID(parentElementUUID) + "/elements", 41 | queryInfo, new TypeReference>>(){}); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/findelement/req/QueryInfo.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.findelement.req; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 6:56 下午 6 | */ 7 | public class QueryInfo { 8 | 9 | // 用什么方式查找 10 | private String using; 11 | 12 | // 值是多少 13 | private String value; 14 | 15 | public String getUsing() { 16 | return using; 17 | } 18 | 19 | public void setUsing(String using) { 20 | this.using = using; 21 | } 22 | 23 | public String getValue() { 24 | return value; 25 | } 26 | 27 | public void setValue(String value) { 28 | this.value = value; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/findelement/req/QueryUsing.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.findelement.req; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 6:57 下午 6 | */ 7 | public class QueryUsing { 8 | 9 | public static final String CLASS_NAME = "class name"; 10 | public static final String CLASS_CHAIN = "class chain"; 11 | public static final String XPATH = "xpath"; 12 | public static final String PARTIAL_LINK_TEXT = "partial link text"; 13 | public static final String LINK_TEXT = "link text"; 14 | public static final String PREDICATE_STRING = "predicate string"; 15 | public static final String NAME = "name"; 16 | public static final String ID = "id"; 17 | public static final String ACCESSIBILITY = "accessibility id"; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/findelement/res/Element.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.findelement.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 7:05 下午 6 | */ 7 | public class Element { 8 | 9 | // 元素ID 10 | private String element; 11 | 12 | public String getElement() { 13 | return element; 14 | } 15 | 16 | public void setElement(String element) { 17 | this.element = element; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/orientation/OrientationApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.orientation; 2 | 3 | import cn.speiyou.wda.BaseApi; 4 | import cn.speiyou.wda.BaseResponse; 5 | import cn.speiyou.wda.WDAClient; 6 | 7 | /** 8 | * @author :cmlanche 9 | * @date :Created in 2020/12/10 5:37 下午 10 | */ 11 | public class OrientationApi extends BaseApi { 12 | 13 | public OrientationApi(WDAClient client) { 14 | super(client); 15 | } 16 | 17 | /** 18 | * 获取当前手机屏幕的方向 19 | * 横向 - LANDSCAPE 20 | * 竖向 - PORTRAIT 21 | * @param sessionId 22 | * @return 23 | */ 24 | public BaseResponse getOrientation() { 25 | return getWithSession("/orientation", null); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/screenshot/ScreenshotApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.screenshot; 2 | 3 | import cn.speiyou.wda.BaseApi; 4 | import cn.speiyou.wda.BaseResponse; 5 | import cn.speiyou.wda.WDAClient; 6 | 7 | /** 8 | * @author :cmlanche 9 | * @date :Created in 2020/12/8 6:16 下午 10 | */ 11 | public class ScreenshotApi extends BaseApi { 12 | 13 | public ScreenshotApi(WDAClient client) { 14 | super(client); 15 | } 16 | 17 | /** 18 | * 健康检查 19 | * wda会按一次物理键(圆形Home键) 20 | */ 21 | public BaseResponse screenshot() { 22 | return get(getBaseUrl() + "/screenshot", null); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/session/SessionApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.session; 2 | 3 | import cn.speiyou.wda.BaseApi; 4 | import cn.speiyou.wda.BaseResponse; 5 | import cn.speiyou.wda.WDAClient; 6 | import cn.speiyou.wda.session.req.AppParam; 7 | import cn.speiyou.wda.session.res.CreateSession; 8 | import com.alibaba.fastjson.JSONObject; 9 | import com.alibaba.fastjson.TypeReference; 10 | 11 | /** 12 | * @author :cmlanche 13 | * @date :Created in 2020/12/8 3:50 下午 14 | */ 15 | public class SessionApi extends BaseApi { 16 | 17 | 18 | public SessionApi(WDAClient client) { 19 | super(client); 20 | } 21 | 22 | /** 23 | * 创建Session 24 | */ 25 | public BaseResponse createSession() { 26 | JSONObject json = new JSONObject(); 27 | json.put("capabilities", new JSONObject()); 28 | return post(getBaseUrl() + "/session", json, 29 | new TypeReference>(){}); 30 | } 31 | 32 | /** 33 | * 启动应用 34 | * @param bundleId 应用包id 35 | */ 36 | public BaseResponse launchApp(String bundleId) { 37 | AppParam param = new AppParam(); 38 | param.setBundleId(bundleId); 39 | return postWithSession("/wda/apps/launch", param, null); 40 | } 41 | 42 | /** 43 | * 启动应用 44 | * @param bundleId 应用包id 45 | */ 46 | public BaseResponse getAppState(String bundleId) { 47 | AppParam param = new AppParam(); 48 | param.setBundleId(bundleId); 49 | return postWithSession("/wda/apps/state", param, null); 50 | } 51 | 52 | /** 53 | * 激活应用 54 | * @param bundleId 应用包id 55 | */ 56 | public BaseResponse activateApp(String bundleId) { 57 | AppParam param = new AppParam(); 58 | param.setBundleId(bundleId); 59 | return postWithSession("/wda/apps/activate", param, null); 60 | } 61 | 62 | /** 63 | * 关闭某app 64 | * @param bundleId 65 | * @return 66 | */ 67 | public BaseResponse terminateApp(String bundleId) { 68 | AppParam param = new AppParam(); 69 | param.setBundleId(bundleId); 70 | return postWithSession("/wda/apps/terminate", param, null); 71 | } 72 | 73 | /** 74 | * 健康检查 75 | * wda会按一次物理键(圆形Home键) 76 | */ 77 | public BaseResponse healthCheck() { 78 | return get(getBaseUrl() + "/wda/healthcheck", null); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/session/req/AppParam.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.session.req; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 4:33 下午 6 | */ 7 | public class AppParam { 8 | 9 | // 应用id 10 | private String bundleId; 11 | 12 | public String getBundleId() { 13 | return bundleId; 14 | } 15 | 16 | public void setBundleId(String bundleId) { 17 | this.bundleId = bundleId; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/session/req/FromToParam.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.session.req; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/11 3:47 下午 6 | */ 7 | public class FromToParam { 8 | 9 | private int fromX; 10 | private int fromY; 11 | private int toX; 12 | private int toY; 13 | // 在拖动之前按下的时间,单位秒 14 | private double duration; 15 | // 在拖动后,停下的时间 16 | private double holdDuration; 17 | // 拖动速度 18 | private int velocity; 19 | 20 | public int getFromX() { 21 | return fromX; 22 | } 23 | 24 | public void setFromX(int fromX) { 25 | this.fromX = fromX; 26 | } 27 | 28 | public int getFromY() { 29 | return fromY; 30 | } 31 | 32 | public void setFromY(int fromY) { 33 | this.fromY = fromY; 34 | } 35 | 36 | public int getToX() { 37 | return toX; 38 | } 39 | 40 | public void setToX(int toX) { 41 | this.toX = toX; 42 | } 43 | 44 | public int getToY() { 45 | return toY; 46 | } 47 | 48 | public void setToY(int toY) { 49 | this.toY = toY; 50 | } 51 | 52 | public double getDuration() { 53 | return duration; 54 | } 55 | 56 | public void setDuration(double duration) { 57 | this.duration = duration; 58 | } 59 | 60 | public double getHoldDuration() { 61 | return holdDuration; 62 | } 63 | 64 | public void setHoldDuration(double holdDuration) { 65 | this.holdDuration = holdDuration; 66 | } 67 | 68 | public int getVelocity() { 69 | return velocity; 70 | } 71 | 72 | public void setVelocity(int velocity) { 73 | this.velocity = velocity; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/session/res/BuildInfo.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.session.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 5:47 下午 6 | */ 7 | public class BuildInfo { 8 | private String time; 9 | private String productBundleIdentifier; 10 | 11 | public String getTime() { 12 | return time; 13 | } 14 | 15 | public void setTime(String time) { 16 | this.time = time; 17 | } 18 | 19 | public String getProductBundleIdentifier() { 20 | return productBundleIdentifier; 21 | } 22 | 23 | public void setProductBundleIdentifier(String productBundleIdentifier) { 24 | this.productBundleIdentifier = productBundleIdentifier; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/session/res/Capabilities.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.session.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 4:18 下午 6 | */ 7 | public class Capabilities { 8 | // 设备类型,iphone 9 | private String device; 10 | private String browserName; 11 | // iOS版本 12 | private String sdkVersion; 13 | private String CFBundleIdentifier; 14 | 15 | public String getDevice() { 16 | return device; 17 | } 18 | 19 | public void setDevice(String device) { 20 | this.device = device; 21 | } 22 | 23 | public String getBrowserName() { 24 | return browserName; 25 | } 26 | 27 | public void setBrowserName(String browserName) { 28 | this.browserName = browserName; 29 | } 30 | 31 | public String getSdkVersion() { 32 | return sdkVersion; 33 | } 34 | 35 | public void setSdkVersion(String sdkVersion) { 36 | this.sdkVersion = sdkVersion; 37 | } 38 | 39 | public String getCFBundleIdentifier() { 40 | return CFBundleIdentifier; 41 | } 42 | 43 | public void setCFBundleIdentifier(String CFBundleIdentifier) { 44 | this.CFBundleIdentifier = CFBundleIdentifier; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/session/res/CreateSession.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.session.res; 2 | 3 | /** 4 | * 创建Session的响应数据 5 | * @author :cmlanche 6 | * @date :Created in 2020/12/8 4:17 下午 7 | */ 8 | public class CreateSession { 9 | 10 | private String sessionId; 11 | 12 | private Capabilities capabilities; 13 | 14 | public String getSessionId() { 15 | return sessionId; 16 | } 17 | 18 | public void setSessionId(String sessionId) { 19 | this.sessionId = sessionId; 20 | } 21 | 22 | public Capabilities getCapabilities() { 23 | return capabilities; 24 | } 25 | 26 | public void setCapabilities(Capabilities capabilities) { 27 | this.capabilities = capabilities; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/session/res/OSInfo.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.session.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 5:26 下午 6 | */ 7 | public class OSInfo { 8 | private int testmanagerdVersion; 9 | // iOS 10 | private String name; 11 | private String sdkVersion; 12 | private String version; 13 | 14 | public int getTestmanagerdVersion() { 15 | return testmanagerdVersion; 16 | } 17 | 18 | public void setTestmanagerdVersion(int testmanagerdVersion) { 19 | this.testmanagerdVersion = testmanagerdVersion; 20 | } 21 | 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | 30 | public String getSdkVersion() { 31 | return sdkVersion; 32 | } 33 | 34 | public void setSdkVersion(String sdkVersion) { 35 | this.sdkVersion = sdkVersion; 36 | } 37 | 38 | public String getVersion() { 39 | return version; 40 | } 41 | 42 | public void setVersion(String version) { 43 | this.version = version; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/session/res/Status.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.session.res; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2020/12/8 5:24 下午 6 | */ 7 | public class Status { 8 | 9 | // 状态,成功是success 10 | private String state; 11 | // 提示信息 12 | private String message; 13 | // 是否准备好 14 | private boolean ready; 15 | // 手机操作系统信息 16 | private OSInfo os; 17 | // 构建wda的信息 18 | private BuildInfo build; 19 | 20 | public String getState() { 21 | return state; 22 | } 23 | 24 | public void setState(String state) { 25 | this.state = state; 26 | } 27 | 28 | public String getMessage() { 29 | return message; 30 | } 31 | 32 | public void setMessage(String message) { 33 | this.message = message; 34 | } 35 | 36 | public boolean isReady() { 37 | return ready; 38 | } 39 | 40 | public void setReady(boolean ready) { 41 | this.ready = ready; 42 | } 43 | 44 | public OSInfo getOs() { 45 | return os; 46 | } 47 | 48 | public void setOs(OSInfo os) { 49 | this.os = os; 50 | } 51 | 52 | public BuildInfo getBuild() { 53 | return build; 54 | } 55 | 56 | public void setBuild(BuildInfo build) { 57 | this.build = build; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/touch/TouchApi.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.touch; 2 | 3 | import cn.speiyou.wda.BaseApi; 4 | import cn.speiyou.wda.BaseResponse; 5 | import cn.speiyou.wda.WDAClient; 6 | 7 | /** 8 | * 基于w3c协议
9 | * 1. https://www.w3.org/TR/webdriver/#actions
10 | * 2. http://appium.io/docs/en/commands/interactions/actions/index.html
11 | * @author :cmlanche 12 | * @date :Created in 2021/2/1 5:03 下午 13 | */ 14 | public class TouchApi extends BaseApi { 15 | 16 | public TouchApi(WDAClient client) { 17 | super(client); 18 | } 19 | 20 | /** 21 | * 屏幕拖动 22 | * @param startX 23 | * @param startY 24 | * @param endX 25 | * @param endY 26 | * @param duration 27 | * @return 28 | */ 29 | public BaseResponse drag(int startX, int startY, int endX, int endY, int duration) { 30 | return null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/touch/req/TouchAction.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.touch.req; 2 | 3 | /** 4 | * @author :cmlanche 5 | * @date :Created in 2021/2/1 5:06 下午 6 | */ 7 | public class TouchAction { 8 | 9 | public static final String POINTER_MOVE = ""; 10 | public static final String POINTER_DOWN = ""; 11 | public static final String POINTER_UP = ""; 12 | 13 | /** 14 | * 可以是"pointer","key","null" 15 | */ 16 | private String type; 17 | private String value; 18 | private int number; 19 | private int x; 20 | private int y; 21 | 22 | public static String getPointerMove() { 23 | return POINTER_MOVE; 24 | } 25 | 26 | public static String getPointerDown() { 27 | return POINTER_DOWN; 28 | } 29 | 30 | public static String getPointerUp() { 31 | return POINTER_UP; 32 | } 33 | 34 | public String getType() { 35 | return type; 36 | } 37 | 38 | public void setType(String type) { 39 | this.type = type; 40 | } 41 | 42 | public String getValue() { 43 | return value; 44 | } 45 | 46 | public void setValue(String value) { 47 | this.value = value; 48 | } 49 | 50 | public int getNumber() { 51 | return number; 52 | } 53 | 54 | public void setNumber(int number) { 55 | this.number = number; 56 | } 57 | 58 | public int getX() { 59 | return x; 60 | } 61 | 62 | public void setX(int x) { 63 | this.x = x; 64 | } 65 | 66 | public int getY() { 67 | return y; 68 | } 69 | 70 | public void setY(int y) { 71 | this.y = y; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/cn/speiyou/wda/touch/req/TouchActions.java: -------------------------------------------------------------------------------- 1 | package cn.speiyou.wda.touch.req; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * @author :cmlanche 7 | * @date :Created in 2021/2/7 3:51 下午 8 | */ 9 | public class TouchActions { 10 | 11 | private List actions; 12 | 13 | /** 14 | * 可以是"pointer","key","null" 15 | */ 16 | private String type; 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/WDATest.java: -------------------------------------------------------------------------------- 1 | import cn.speiyou.wda.BaseResponse; 2 | import cn.speiyou.wda.WDAClient; 3 | import cn.speiyou.wda.custom.res.ActiveAppInfo; 4 | import cn.speiyou.wda.custom.res.WDADeviceInfo; 5 | import cn.speiyou.wda.element.res.WDARect; 6 | import cn.speiyou.wda.element.res.WindowSize; 7 | import cn.speiyou.wda.findelement.req.QueryInfo; 8 | import cn.speiyou.wda.findelement.req.QueryUsing; 9 | import cn.speiyou.wda.findelement.res.Element; 10 | import cn.speiyou.wda.session.res.CreateSession; 11 | import com.alibaba.fastjson.JSON; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * @author :cmlanche 20 | * @date :Created in 2020/12/8 3:39 下午 21 | */ 22 | public class WDATest { 23 | 24 | private WDAClient client; 25 | private String appleMapPkg = "com.apple.Maps"; 26 | private String targetElementUUID = "27000000-0000-0000-D303-000000000000"; 27 | 28 | @Before 29 | public void init() { 30 | this.client = new WDAClient("127.0.0.1", 8100); 31 | } 32 | 33 | @Test 34 | public void getPageSource() { 35 | BaseResponse res = this.client.getPageSource(); 36 | assert res.isSuccess(); 37 | } 38 | 39 | @Test 40 | public void health() { 41 | // client health check 42 | assert this.client.health().isSuccess(); 43 | 44 | // wda health check 45 | assert this.client.getSessionApi().healthCheck().isSuccess(); 46 | } 47 | 48 | @Test 49 | public void createSession() { 50 | BaseResponse res = this.client.getSessionApi().createSession(); 51 | if (res.isSuccess()) { 52 | assert StringUtils.isNotEmpty(res.getValue().getSessionId()); 53 | System.out.println("创建Session成功:" + res.getValue().getSessionId()); 54 | } 55 | assert res.isSuccess(); 56 | } 57 | 58 | @Test 59 | public void launchApp() { 60 | BaseResponse res = this.client.getSessionApi().launchApp(appleMapPkg); 61 | assert res.isSuccess(); 62 | } 63 | 64 | @Test 65 | public void getAppState() { 66 | BaseResponse res = this.client.getSessionApi().getAppState(appleMapPkg); 67 | assert res.getValue() == 4; 68 | } 69 | 70 | @Test 71 | public void screenshot() { 72 | BaseResponse res = this.client.getScreenshotApi().screenshot(); 73 | System.out.println(res.getValue()); 74 | assert res.isSuccess(); 75 | } 76 | 77 | @Test 78 | public void elements() { 79 | QueryInfo queryInfo = new QueryInfo(); 80 | queryInfo.setUsing(QueryUsing.CLASS_NAME); 81 | queryInfo.setValue("XCUIElementTypeStaticText"); 82 | BaseResponse> res = this.client.getFindElementApi().elements(queryInfo); 83 | System.out.println("查找到元素:" + JSON.toJSONString(res)); 84 | assert res.isSuccess(); 85 | } 86 | 87 | @Test 88 | public void childElements() { 89 | QueryInfo queryInfo = new QueryInfo(); 90 | queryInfo.setUsing(QueryUsing.CLASS_NAME); 91 | queryInfo.setValue("XCUIElementTypeStaticText"); 92 | BaseResponse> res = this.client.getFindElementApi().elements(targetElementUUID, queryInfo); 93 | System.out.println("查找到元素:" + JSON.toJSONString(res)); 94 | assert res.isSuccess(); 95 | } 96 | 97 | @Test 98 | public void getOrientation() { 99 | BaseResponse res = this.client.getOrientationApi().getOrientation(); 100 | System.out.println(JSON.toJSONString(res)); 101 | assert res.isSuccess(); 102 | } 103 | 104 | @Test 105 | public void deactiveApp() { 106 | BaseResponse res = this.client.getCustomApi().deactiveApp(); 107 | System.out.println(JSON.toJSONString(res)); 108 | assert res.isSuccess(); 109 | } 110 | 111 | @Test 112 | public void lock() { 113 | BaseResponse res = this.client.getCustomApi().lock(); 114 | System.out.println(JSON.toJSONString(res)); 115 | assert res.isSuccess(); 116 | } 117 | 118 | @Test 119 | public void unlock() { 120 | BaseResponse res = this.client.getCustomApi().unlock(); 121 | System.out.println(JSON.toJSONString(res)); 122 | assert res.isSuccess(); 123 | } 124 | 125 | @Test 126 | public void getActiveAppInfo() { 127 | BaseResponse res = this.client.getCustomApi().getActiveAppInfo(); 128 | System.out.println(JSON.toJSONString(res)); 129 | assert res.isSuccess(); 130 | } 131 | 132 | @Test 133 | public void getDeviceInfo() { 134 | BaseResponse res = this.client.getCustomApi().getDeviceInfo(); 135 | System.out.println(JSON.toJSONString(res)); 136 | assert res.isSuccess(); 137 | } 138 | 139 | @Test 140 | public void homeScreen() { 141 | BaseResponse res = this.client.getCustomApi().homeScreen(); 142 | System.out.println(JSON.toJSONString(res)); 143 | assert res.isSuccess(); 144 | } 145 | 146 | @Test 147 | public void locked() { 148 | BaseResponse res = this.client.getCustomApi().locked(); 149 | System.out.println(JSON.toJSONString(res)); 150 | assert res.isSuccess(); 151 | } 152 | 153 | @Test 154 | public void getWindowSize() { 155 | BaseResponse res = this.client.getElementApi().getWindowSize(); 156 | System.out.println(JSON.toJSONString(res)); 157 | assert res.isSuccess(); 158 | } 159 | 160 | @Test 161 | public void elementEnabled() { 162 | BaseResponse res = this.client.getElementApi().enabled(targetElementUUID); 163 | System.out.println(JSON.toJSONString(res)); 164 | assert res.isSuccess(); 165 | } 166 | 167 | @Test 168 | public void elementRect() { 169 | BaseResponse res = this.client.getElementApi().rect(targetElementUUID); 170 | System.out.println(JSON.toJSONString(res)); 171 | assert res.isSuccess(); 172 | } 173 | 174 | @Test 175 | public void elementText() { 176 | BaseResponse res = this.client.getElementApi().text(targetElementUUID); 177 | System.out.println(JSON.toJSONString(res)); 178 | assert res.isSuccess(); 179 | } 180 | 181 | @Test 182 | public void elementDisplayed() { 183 | BaseResponse res = this.client.getElementApi().displayed(targetElementUUID); 184 | System.out.println(JSON.toJSONString(res)); 185 | assert res.isSuccess(); 186 | } 187 | 188 | @Test 189 | public void elementSelected() { 190 | BaseResponse res = this.client.getElementApi().selected(targetElementUUID); 191 | System.out.println(JSON.toJSONString(res)); 192 | assert res.isSuccess(); 193 | } 194 | 195 | @Test 196 | public void elementName() { 197 | BaseResponse res = this.client.getElementApi().name(targetElementUUID); 198 | System.out.println(JSON.toJSONString(res)); 199 | assert res.isSuccess(); 200 | } 201 | 202 | @Test 203 | public void getAlertButtons() { 204 | BaseResponse> res = this.client.getAlertApi().getAlertButtons(); 205 | System.out.println(JSON.toJSONString(res)); 206 | assert res.isSuccess(); 207 | } 208 | 209 | @Test 210 | public void getAlertText() { 211 | BaseResponse res = this.client.getAlertApi().getAlertText(); 212 | System.out.println(JSON.toJSONString(res)); 213 | assert res.isSuccess(); 214 | } 215 | } 216 | --------------------------------------------------------------------------------