From 830e24bc41d0f9939f771bf34e57b57e2ae98bc3 Mon Sep 17 00:00:00 2001
From: jiangyitao <451988022@163.com>
Date: Thu, 23 Jul 2020 18:28:21 +0800
Subject: [PATCH 1/8] 0.7.5
---
.gitignore | 2 +-
pom.xml | 2 +-
.../com/daxiang/action/AndroidAction.java | 14 +-
.../java/com/daxiang/action/BaseAction.java | 199 +++---
.../java/com/daxiang/action/IosAction.java | 7 +-
.../java/com/daxiang/action/MobileAction.java | 184 ++----
.../java/com/daxiang/action/PCWebAction.java | 9 +-
src/main/java/com/daxiang/action/action.sql | 586 ------------------
.../com/daxiang/core/AgentStartRunner.java | 9 +
.../core/action/BasicActionScanner.java | 110 ++++
.../core/action/annotation/Action.java | 23 +
.../daxiang/core/action/annotation/Param.java | 17 +
.../java/com/daxiang/model/action/Action.java | 9 +-
.../java/com/daxiang/model/action/Param.java | 3 +
.../daxiang/model/action/PossibleValue.java | 12 +
src/main/resources/codetemplate/actions.ftl | 4 +-
16 files changed, 351 insertions(+), 839 deletions(-)
delete mode 100644 src/main/java/com/daxiang/action/action.sql
create mode 100644 src/main/java/com/daxiang/core/action/BasicActionScanner.java
create mode 100644 src/main/java/com/daxiang/core/action/annotation/Action.java
create mode 100644 src/main/java/com/daxiang/core/action/annotation/Param.java
create mode 100644 src/main/java/com/daxiang/model/action/PossibleValue.java
diff --git a/.gitignore b/.gitignore
index 6cf7a33..4826a1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,4 +14,4 @@ target
test-output/
*.mp4
*.jpg
-spring.log
+spring.log*
diff --git a/pom.xml b/pom.xml
index ae15218..e35eedc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.daxiang
agent
- 0.7.3
+ 0.7.5
jar
diff --git a/src/main/java/com/daxiang/action/AndroidAction.java b/src/main/java/com/daxiang/action/AndroidAction.java
index 348a2db..1151e44 100644
--- a/src/main/java/com/daxiang/action/AndroidAction.java
+++ b/src/main/java/com/daxiang/action/AndroidAction.java
@@ -1,5 +1,7 @@
package com.daxiang.action;
+import com.daxiang.core.action.annotation.Action;
+import com.daxiang.core.action.annotation.Param;
import com.daxiang.core.mobile.android.AndroidDevice;
import com.daxiang.core.mobile.android.AndroidUtil;
import com.daxiang.core.mobile.android.IDeviceExecuteShellCommandException;
@@ -21,19 +23,15 @@ public AndroidAction(AndroidDevice androidDevice) {
this.androidDevice = androidDevice;
}
- /**
- * 2000.清除apk数据
- */
- public void clearApkData(String packageName) throws IDeviceExecuteShellCommandException {
+ @Action(id = 2000, name = "清除apk数据", platforms = 1)
+ public void clearApkData(@Param(description = "包名") String packageName) throws IDeviceExecuteShellCommandException {
Assert.hasText(packageName, "包名不能为空");
AndroidUtil.clearApkData(androidDevice.getIDevice(), packageName);
}
- /**
- * 2001.启动/重启 apk
- */
- public void restartApk(String packageName, String launchActivity) throws IDeviceExecuteShellCommandException {
+ @Action(id = 2001, name = "启动/重启apk", platforms = 1)
+ public void restartApk(@Param(description = "包名") String packageName, @Param(description = "启动Activity名") String launchActivity) throws IDeviceExecuteShellCommandException {
Assert.hasText(packageName, "包名不能为空");
Assert.hasText(launchActivity, "启动Activity不能为空");
diff --git a/src/main/java/com/daxiang/action/BaseAction.java b/src/main/java/com/daxiang/action/BaseAction.java
index dbcb1bc..32a4757 100644
--- a/src/main/java/com/daxiang/action/BaseAction.java
+++ b/src/main/java/com/daxiang/action/BaseAction.java
@@ -1,5 +1,7 @@
package com.daxiang.action;
+import com.daxiang.core.action.annotation.Action;
+import com.daxiang.core.action.annotation.Param;
import com.daxiang.core.Device;
import io.appium.java_client.MobileBy;
import org.openqa.selenium.By;
@@ -22,6 +24,21 @@
public class BaseAction {
public static final int EXECUTE_JAVA_CODE_ID = 1;
+ public static final String FIND_BY_POSSIBLE_VALUES = "[" +
+ "{'value':'id','description':'By.id'}," +
+ "{'value':'AccessibilityId','description':'By.AccessibilityId'}," +
+ "{'value':'xpath','description':'By.xpath'}," +
+ "{'value':'AndroidUIAutomator','description':'By.AndroidUIAutomator'}," +
+ "{'value':'iOSClassChain','description':'By.iOSClassChain'}," +
+ "{'value':'iOSNsPredicateString','description':'By.iOSNsPredicateString'}," +
+ "{'value':'image','description':'By.image'}," +
+ "{'value':'className','description':'By.className'}," +
+ "{'value':'name','description':'By.name'}," +
+ "{'value':'cssSelector','description':'By.cssSelector'}," +
+ "{'value':'linkText','description':'By.linkText'}," +
+ "{'value':'partialLinkText','description':'By.partialLinkText'}," +
+ "{'value':'tagName','description':'By.tagName'}" +
+ "]";
protected Device device;
@@ -29,69 +46,36 @@ public BaseAction(Device device) {
this.device = device;
}
- /**
- * 1.执行java代码
- *
- * @param code
- */
- public void executeJavaCode(String code) {
+ @Action(id = 1, name = "执行java代码")
+ public void executeJavaCode(@Param(description = "java代码") String code) {
Assert.hasText(code, "code不能为空");
}
- /**
- * 2.休眠
- *
- * @param ms 毫秒
- * @throws InterruptedException
- */
- public void sleep(String ms) throws InterruptedException {
+ @Action(id = 2, name = "休眠")
+ public void sleep(@Param(description = "休眠时长,单位:毫秒") String ms) throws InterruptedException {
Thread.sleep(parseLong(ms));
}
- /**
- * 7.点击
- *
- * @param findBy
- * @param value
- * @return
- */
- public WebElement click(String findBy, String value) {
+ @Action(id = 7, name = "点击")
+ public WebElement click(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value) {
WebElement element = findElement(findBy, value);
element.click();
return element;
}
- /**
- * 8.查找元素
- *
- * @param findBy
- * @param value
- * @return
- */
- public WebElement findElement(String findBy, String value) {
+ @Action(id = 8, name = "查找元素")
+ public WebElement findElement(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value) {
return device.getDriver().findElement(createBy(findBy, value));
}
- /**
- * 9.查找元素
- *
- * @param findBy
- * @param value
- * @return 返回所有匹配的元素
- */
- public List findElements(String findBy, String value) {
+ @Action(id = 9, name = "查找元素列表")
+ public List findElements(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value) {
return device.getDriver().findElements(createBy(findBy, value));
}
- /**
- * 10.sendKeys
- *
- * @param findBy
- * @param value
- * @param content
- * @return
- */
- public WebElement sendKeys(String findBy, String value, String content) {
+ @Action(id = 10, name = "输入")
+ public WebElement sendKeys(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value,
+ @Param(description = "输入内容") String content) {
WebElement element = device.getDriver().findElement(createBy(findBy, value));
if (content == null) {
content = "";
@@ -100,60 +84,33 @@ public WebElement sendKeys(String findBy, String value, String content) {
return element;
}
- /**
- * 11.设置隐式等待时间
- *
- * @param seconds
- */
- public void setImplicitlyWaitTime(String seconds) {
+ @Action(id = 11, name = "设置隐式等待时间")
+ public void setImplicitlyWaitTime(@Param(description = "隐式等待时间,单位:秒") String seconds) {
device.getDriver().manage().timeouts().implicitlyWait(parseLong(seconds), TimeUnit.SECONDS);
}
- /**
- * 12.等待元素可见
- *
- * @param findBy
- * @param value
- * @param timeoutInSeconds
- * @return
- */
- public WebElement waitForElementVisible(String findBy, String value, String timeoutInSeconds) {
+ @Action(id = 12, name = "等待元素可见")
+ public WebElement waitForElementVisible(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value,
+ @Param(description = "最大等待时间,单位:秒") String timeoutInSeconds) {
return new WebDriverWait(device.getDriver(), parseLong(timeoutInSeconds))
.until(ExpectedConditions.visibilityOfElementLocated(createBy(findBy, value)));
}
- /**
- * 13.等待元素在DOM里出现,不一定可见
- * 可用于检查toast是否显示
- *
- * @param findBy
- * @param value
- * @param timeoutInSeconds
- * @return
- */
- public WebElement waitForElementPresence(String findBy, String value, String timeoutInSeconds) {
+ @Action(id = 13, name = "等待元素出现", description = "等待元素在DOM里出现,不一定可见。移动端可用于检测toast")
+ public WebElement waitForElementPresence(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value,
+ @Param(description = "最大等待时间,单位:秒") String timeoutInSeconds) {
return new WebDriverWait(device.getDriver(), parseLong(timeoutInSeconds))
.until(ExpectedConditions.presenceOfElementLocated(createBy(findBy, value)));
}
- /**
- * 14.[web]访问url
- *
- * @param url
- */
+ @Action(id = 14, name = "[web]访问url")
public void getUrl(String url) {
Assert.hasText(url, "url不能为空");
device.getDriver().get(url);
}
- /**
- * 17. 元素是否显示
- *
- * @param findBy
- * @param value
- * @return
- */
- public boolean isElementDisplayed(String findBy, String value) {
+ @Action(id = 17, name = "元素是否显示")
+ public boolean isElementDisplayed(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value) {
try {
return isElementDisplayed(findElement(findBy, value));
} catch (Exception e) {
@@ -161,12 +118,7 @@ public boolean isElementDisplayed(String findBy, String value) {
}
}
- /**
- * 18. 元素是否显示
- *
- * @param element
- * @return
- */
+ @Action(id = 18, name = "元素是否显示")
public boolean isElementDisplayed(WebElement element) {
Assert.notNull(element, "element不能为空");
@@ -177,21 +129,14 @@ public boolean isElementDisplayed(WebElement element) {
}
}
- /**
- * 19.等待元素可见
- */
- public WebElement waitForElementVisible(WebElement element, String timeoutInSeconds) {
+ @Action(id = 19, name = "等待元素可见")
+ public WebElement waitForElementVisible(WebElement element, @Param(description = "最大等待时间,单位:秒") String timeoutInSeconds) {
return new WebDriverWait(device.getDriver(), parseLong(timeoutInSeconds))
.until(ExpectedConditions.visibilityOf(element));
}
- /**
- * 20.获取当前时间
- *
- * @param pattern
- * @return
- */
- public String now(String pattern) {
+ @Action(id = 20, name = "获取当前时间")
+ public String now(@Param(description = "默认yyyy-MM-dd HH:mm:ss") String pattern) {
if (StringUtils.isEmpty(pattern)) {
pattern = "yyyy-MM-dd HH:mm:ss";
}
@@ -202,6 +147,58 @@ public String now() {
return now("yyyy-MM-dd HH:mm:ss");
}
+ @Action(id = 21, name = "accept对话框")
+ public boolean acceptAlert() {
+ return device.acceptAlert();
+ }
+
+ @Action(id = 22, name = "异步accept对话框")
+ public void asyncAcceptAlert(@Param(description = "超时时间,单位:秒") String timeoutInSeconds,
+ @Param(description = "是否只处理一次, true or false") String once) {
+ boolean _once = parseBoolean(once);
+ long timeoutInMs = parseLong(timeoutInSeconds) * 1000;
+
+ new Thread(() -> {
+ long startTime = System.currentTimeMillis();
+ while (System.currentTimeMillis() - startTime < timeoutInMs) {
+ if (_once && acceptAlert()) {
+ break;
+ }
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+
+ }
+ }
+ }).start();
+ }
+
+ @Action(id = 23, name = "dismiss对话框")
+ public boolean dismissAlert() {
+ return device.dismissAlert();
+ }
+
+ @Action(id = 24, name = "异步dismiss对话框")
+ public void asyncDismissAlert(@Param(description = "超时时间,单位:秒") String timeoutInSeconds,
+ @Param(description = "是否只处理一次, true or false") String once) {
+ boolean _once = parseBoolean(once);
+ long timeoutInMs = parseLong(timeoutInSeconds) * 1000;
+
+ new Thread(() -> {
+ long startTime = System.currentTimeMillis();
+ while (System.currentTimeMillis() - startTime < timeoutInMs) {
+ if (_once && dismissAlert()) {
+ break;
+ }
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+
+ }
+ }
+ }).start();
+ }
+
public By createBy(String findBy, String value) {
Assert.hasText(findBy, "findBy不能为空");
Assert.hasText(value, "value不能为空");
diff --git a/src/main/java/com/daxiang/action/IosAction.java b/src/main/java/com/daxiang/action/IosAction.java
index 1decdad..98cb20d 100644
--- a/src/main/java/com/daxiang/action/IosAction.java
+++ b/src/main/java/com/daxiang/action/IosAction.java
@@ -1,5 +1,6 @@
package com.daxiang.action;
+import com.daxiang.core.action.annotation.Action;
import com.daxiang.core.mobile.ios.IosDevice;
import com.daxiang.core.mobile.ios.IosUtil;
import lombok.extern.slf4j.Slf4j;
@@ -18,11 +19,7 @@ public IosAction(IosDevice iosDevice) {
super(iosDevice);
}
- /**
- * 3000.启动/重启 app
- *
- * @param bundleId
- */
+ @Action(id = 3000, name = "启动/重启app", platforms = 2)
public void restartIosApp(String bundleId) {
Assert.hasText(bundleId, "bundleId不能为空");
diff --git a/src/main/java/com/daxiang/action/MobileAction.java b/src/main/java/com/daxiang/action/MobileAction.java
index bf8834b..54b0293 100644
--- a/src/main/java/com/daxiang/action/MobileAction.java
+++ b/src/main/java/com/daxiang/action/MobileAction.java
@@ -1,6 +1,8 @@
package com.daxiang.action;
import com.alibaba.fastjson.JSONObject;
+import com.daxiang.core.action.annotation.Action;
+import com.daxiang.core.action.annotation.Param;
import com.daxiang.core.mobile.MobileDevice;
import com.daxiang.utils.HttpUtil;
import io.appium.java_client.AppiumDriver;
@@ -26,6 +28,13 @@
public class MobileAction extends BaseAction {
private static final long DEFAULT_SWIPE_DURATION_IN_MS = 100L;
+ public static final String POINT_POSSIBLE_VALUES = "[" +
+ "{'value':'{x:0.5, y:0.5}','description':'屏幕中心点'}," +
+ "{'value':'{x:0.8, y:0.5}','description':''}," +
+ "{'value':'{x:0.2, y:0.5}','description':''}," +
+ "{'value':'{x:0.5, y:0.8}','description':''}," +
+ "{'value':'{x:0.5, y:0.2}','description':''}," +
+ "]";
private MobileDevice mobileDevice;
@@ -38,23 +47,14 @@ private AppiumDriver getAppiumDriver() {
return (AppiumDriver) device.getDriver();
}
- /**
- * 1000.切换context
- *
- * @param context
- */
- public void switchContext(String context) {
+ @Action(id = 1000, name = "切换context", platforms = {1, 2})
+ public void switchContext(@Param(possibleValues = "[{'value': 'NATIVE_APP', 'description': '原生'}]") String context) {
Assert.hasText(context, "context不能为空");
getAppiumDriver().context(context);
}
- /**
- * 1001.安装app
- *
- * @param appDownloadUrl
- * @throws Exception
- */
- public void installApp(String appDownloadUrl) throws IOException {
+ @Action(id = 1001, name = "安装App", platforms = {1, 2})
+ public void installApp(@Param(description = "app下载地址") String appDownloadUrl) throws IOException {
Assert.hasText(appDownloadUrl, "appDownloadUrl不能为空");
File app = HttpUtil.downloadFile(appDownloadUrl);
@@ -65,25 +65,16 @@ public void installApp(String appDownloadUrl) throws IOException {
}
}
- /**
- * 1002.卸载app
- *
- * @param app
- * @throws Exception
- */
- public void uninstallApp(String app) throws Exception {
+ @Action(id = 1002, name = "卸载App", platforms = {1, 2})
+ public void uninstallApp(@Param(description = "android: packageName, iOS: bundleId") String app) throws Exception {
Assert.hasText(app, "apk包名或ios bundleId不能为空");
mobileDevice.uninstallApp(app);
}
- /**
- * 1003.滑动屏幕
- *
- * @param startPoint {x: 0.5, y: 0.5} => 屏幕宽/高 1/2的位置
- * @param endPoint
- * @param durationInMs 滑动耗时
- */
- public void swipe(String startPoint, String endPoint, String durationInMs) {
+ @Action(id = 1003, name = "滑动屏幕", platforms = {1, 2})
+ public void swipe(@Param(description = "起点", possibleValues = POINT_POSSIBLE_VALUES) String startPoint,
+ @Param(description = "终点", possibleValues = POINT_POSSIBLE_VALUES) String endPoint,
+ @Param(description = "滑动时间,单位:ms。时间越短,滑的距离越长") String durationInMs) {
Dimension window = getAppiumDriver().manage().window().getSize();
Point start = createPoint(startPoint, window);
Point end = createPoint(endPoint, window);
@@ -91,19 +82,13 @@ public void swipe(String startPoint, String endPoint, String durationInMs) {
swipe(start, end, durationInMs);
}
- /**
- * 1004.滑动屏幕查找元素
- *
- * @param findBy
- * @param value
- * @param startPoint {x: 0.5, y: 0.5} => 屏幕宽/高 1/2的位置
- * @param endPoint
- * @param maxSwipeCount 最大滑动次数
- * @param onceDurationInMs 滑动一次耗时
- * @return
- */
- public WebElement swipeToFindElement(String findBy, String value,
- String startPoint, String endPoint, String maxSwipeCount, String onceDurationInMs) {
+ @Action(id = 1004, name = "滑动屏幕查找元素", platforms = {1, 2})
+ public WebElement swipeToFindElement(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy,
+ String value,
+ @Param(description = "起点", possibleValues = POINT_POSSIBLE_VALUES) String startPoint,
+ @Param(description = "终点", possibleValues = POINT_POSSIBLE_VALUES) String endPoint,
+ @Param(description = "最大滑动次数") String maxSwipeCount,
+ @Param(description = "滑动一次的时间,单位: ms。时间越短,滑的距离越长") String onceDurationInMs) {
By by = createBy(findBy, value);
AppiumDriver driver = getAppiumDriver();
try {
@@ -129,24 +114,23 @@ public WebElement swipeToFindElement(String findBy, String value,
return driver.findElement(by);
}
- /**
- * 1005.在容器元素内滑动
- *
- * @param container
- * @param startPoint {x: 0.5, y: 0.5} => 容器元素宽/高 1/2的位置
- * @param endPoint
- * @param onceDurationInMs 滑动一次耗时
- */
- public void swipeInContainer(WebElement container, String startPoint, String endPoint, String onceDurationInMs) {
+ @Action(id = 1005, name = "容器内滑动", platforms = {1, 2})
+ public void swipeInContainer(@Param(description = "容器元素") WebElement container,
+ @Param(description = "起点", possibleValues = POINT_POSSIBLE_VALUES) String startPoint,
+ @Param(description = "终点", possibleValues = POINT_POSSIBLE_VALUES) String endPoint,
+ @Param(description = "滑动一次的时间,单位: ms。时间越短,滑的距离越长") String onceDurationInMs) {
Point[] points = createStartAndEndPointInContainer(container, startPoint, endPoint);
swipe(points[0], points[1], onceDurationInMs);
}
- /**
- * 1006.在容器元素内滑动查找元素
- */
- public WebElement swipeInContainerToFindElement(WebElement container, String findBy, String value,
- String startPoint, String endPoint, String maxSwipeCount, String onceDurationInMs) {
+ @Action(id = 1006, name = "容器内滑动查找元素", platforms = {1, 2})
+ public WebElement swipeInContainerToFindElement(@Param(description = "容器元素") WebElement container,
+ @Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy,
+ String value,
+ @Param(description = "起点", possibleValues = POINT_POSSIBLE_VALUES) String startPoint,
+ @Param(description = "终点", possibleValues = POINT_POSSIBLE_VALUES) String endPoint,
+ @Param(description = "最大滑动次数") String maxSwipeCount,
+ @Param(description = "滑动一次的时间,单位: ms。时间越短,滑的距离越长") String onceDurationInMs) {
By by = createBy(findBy, value);
AppiumDriver driver = getAppiumDriver();
try {
@@ -170,82 +154,34 @@ public WebElement swipeInContainerToFindElement(WebElement container, String fin
return driver.findElement(by);
}
- /**
- * 1007.弹窗 允许/接受/...
- * // todo 转移到BaseAction
- */
+ @Deprecated
+ @Action(id = 1007, name = "accept对话框", platforms = {1, 2})
public boolean acceptAlert() {
- return device.acceptAlert();
+ return super.acceptAlert();
}
- /**
- * 1008.异步accept alert
- *
- * @param timeoutInSeconds 超时处理时间
- * @param once 是否只处理一次
- * // todo 转移到BaseAction
- */
- public void asyncAcceptAlert(String timeoutInSeconds, String once) {
- long _timeoutInSeconds = parseLong(timeoutInSeconds);
- boolean _once = parseBoolean(once);
-
- new Thread(() -> {
- long startTime = System.currentTimeMillis();
- boolean success;
- while (System.currentTimeMillis() - startTime < _timeoutInSeconds * 1000) {
- success = acceptAlert();
- if (_once && success) {
- break;
- }
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
-
- }
- }
- }).start();
+ @Deprecated
+ @Action(id = 1008, name = "异步accept对话框", platforms = {1, 2})
+ public void asyncAcceptAlert(@Param(description = "超时时间,单位:秒") String timeoutInSeconds,
+ @Param(description = "是否只处理一次, true or false") String once) {
+ super.asyncAcceptAlert(timeoutInSeconds, once);
}
- /**
- * 1009.弹窗 拒绝/取消/...
- * // todo 转移到BaseAction
- */
+ @Deprecated
+ @Action(id = 1009, name = "dismiss对话框", platforms = {1, 2})
public boolean dismissAlert() {
- return device.dismissAlert();
+ return super.dismissAlert();
}
- /**
- * 1010.异步dismiss alert
- *
- * @param timeoutInSeconds 超时处理时间
- * @param once 是否只处理一次
- * // todo 转移到BaseAction
- */
- public void asyncDismissAlert(String timeoutInSeconds, String once) {
- long _timeoutInSeconds = parseLong(timeoutInSeconds);
- boolean _once = parseBoolean(once);
-
- new Thread(() -> {
- long startTime = System.currentTimeMillis();
- boolean success;
- while (System.currentTimeMillis() - startTime < _timeoutInSeconds * 1000) {
- success = dismissAlert();
- if (_once && success) {
- break;
- }
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
-
- }
- }
- }).start();
+ @Deprecated
+ @Action(id = 1010, name = "异步dismiss对话框", platforms = {1, 2})
+ public void asyncDismissAlert(@Param(description = "超时时间,单位:秒") String timeoutInSeconds,
+ @Param(description = "是否只处理一次, true or false") String once) {
+ super.asyncDismissAlert(timeoutInSeconds, once);
}
- /**
- * 1011.通过TouchAction点击
- */
- public WebElement clickByTouchAction(String findBy, String value) {
+ @Action(id = 1011, name = "(TouchAction)点击", platforms = {1, 2})
+ public WebElement clickByTouchAction(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value) {
WebElement element = findElement(findBy, value);
PointOption center = getElementCenter(element);
@@ -253,10 +189,8 @@ public WebElement clickByTouchAction(String findBy, String value) {
return element;
}
- /**
- * 1012.长按元素
- */
- public void longPressElement(WebElement element, String durationInMs) {
+ @Action(id = 1012, name = "长按元素", platforms = {1, 2})
+ public void longPressElement(WebElement element, @Param(description = "长按时间,单位:ms") String durationInMs) {
Assert.hasText(durationInMs, "durationInMs不能为空");
PointOption center = getElementCenter(element);
diff --git a/src/main/java/com/daxiang/action/PCWebAction.java b/src/main/java/com/daxiang/action/PCWebAction.java
index 5d76d34..a9c47a9 100644
--- a/src/main/java/com/daxiang/action/PCWebAction.java
+++ b/src/main/java/com/daxiang/action/PCWebAction.java
@@ -1,5 +1,6 @@
package com.daxiang.action;
+import com.daxiang.core.action.annotation.Action;
import com.daxiang.core.pc.web.BrowserDevice;
import org.openqa.selenium.WebElement;
import org.springframework.util.Assert;
@@ -15,16 +16,12 @@ public PCWebAction(BrowserDevice browserDevice) {
super(browserDevice);
}
- /**
- * 4000.窗口最大化
- */
+ @Action(id = 4000, name = "窗口最大化", platforms = 3)
public void windowMaximize() {
device.getDriver().manage().window().maximize();
}
- /**
- * 4001.鼠标光标移动到element上
- */
+ @Action(id = 4001, name = "光标移动到元素上", platforms = 3)
public void mouseOver(WebElement element) {
Assert.notNull(element, "element不能为空");
diff --git a/src/main/java/com/daxiang/action/action.sql b/src/main/java/com/daxiang/action/action.sql
deleted file mode 100644
index a5e0f24..0000000
--- a/src/main/java/com/daxiang/action/action.sql
+++ /dev/null
@@ -1,586 +0,0 @@
-set @findbyAndValue = '{"name":"findBy","type":"String","description":"查找方式","possibleValues":[{"value":"id","description":"By.id"},{"value":"AccessibilityId","description":"By.AccessibilityId"},{"value":"xpath","description":"By.xpath"},{"value":"AndroidUIAutomator","description":"By.AndroidUIAutomator"},{"value":"iOSClassChain","description":"By.iOSClassChain"},{"value":"iOSNsPredicateString","description":"By.iOSNsPredicateString"},{"value":"image","description":"By.image"},{"value":"className","description":"By.className"},{"value":"name","description":"By.name"},{"value":"cssSelector","description":"By.cssSelector"},{"value":"linkText","description":"By.linkText"},{"value":"partialLinkText","description":"By.partialLinkText"},{"value":"tagName","description":"By.tagName"}]},{"name":"value","type":"String","description":"查找值"}';
--- 1~999 BaseAction platforms = null
-
--- 1.executeJavaCode
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 1,
- '执行java代码',
- '$.executeJavaCode',
- 'void',
- '[{"name":"code","type":"String","description":"java代码"}]'
-);
-
--- 2.sleep
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 2,
- '休眠',
- '$.sleep',
- 'void',
- '[{"name":"ms","type":"String","description": "休眠时长(毫秒)"}]'
-);
-
--- 7.click
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 7,
- '点击',
- '$.click',
- 'WebElement',
- REPLACE('[#]','#',@findbyAndValue)
-);
-
--- 8.findElement
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 8,
- '查找元素',
- '$.findElement',
- 'WebElement',
- REPLACE('[#]','#',@findbyAndValue)
-);
-
--- 9.findElements
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 9,
- '查找元素列表',
- '$.findElements',
- 'List',
- REPLACE('[#]','#',@findbyAndValue)
-);
-
--- 10.sendKeys
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 10,
- '输入',
- '$.sendKeys',
- 'WebElement',
- REPLACE('[#,{"name":"content","type":"String","description":"输入内容"}]','#',@findbyAndValue)
-);
-
--- 11.setImplicitlyWaitTime
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 11,
- '设置隐式等待时间',
- '$.setImplicitlyWaitTime',
- 'void',
- '[{"name":"seconds","type":"String","description":"隐式等待时间(秒)"}]'
-);
-
--- 12.waitForElementVisible
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 12,
- '等待元素可见',
- '$.waitForElementVisible',
- 'WebElement',
- REPLACE('[#,{"name":"timeoutInSeconds","type":"String","description":"最大等待时间(秒)"}]','#',@findbyAndValue)
-);
-
--- 13.waitForElementPresence
-INSERT INTO `action` (
- `id`,
- `name`,
- `description`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 13,
- '等待元素出现',
- '等待元素在Page DOM里出现,不一定可见。移动端可用于检测toast',
- '$.waitForElementPresence',
- 'WebElement',
- REPLACE('[#,{"name":"timeoutInSeconds","type":"String","description":"最大等待时间(秒)"}]','#',@findbyAndValue)
-);
-
--- 14.getUrl
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 14,
- '[web]访问url',
- '$.getUrl',
- 'void',
- '[{"name":"url","type":"String","description":"要访问的url"}]'
-);
-
--- 17.isElementDisplayed
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 17,
- '元素是否显示',
- '$.isElementDisplayed',
- 'boolean',
- REPLACE('[#]','#',@findbyAndValue)
-);
-
--- 18.isElementDisplayed
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 18,
- '元素是否显示',
- '$.isElementDisplayed',
- 'boolean',
- '[{"name":"element","type":"WebElement"}]'
-);
-
--- 19.waitForElementVisible
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 19,
- '等待元素可见',
- '$.waitForElementVisible',
- 'WebElement',
- '[{"name":"element","type":"WebElement"},{"name":"timeoutInSeconds","type":"String","description":"最大等待时间(秒)"}]'
-);
-
--- 20.now
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`
-)
-VALUES
-(
- 20,
- '获取当前时间',
- '$.now',
- 'String',
- '[{"name":"pattern","type":"String","description": "默认yyyy-MM-dd HH:mm:ss"}]'
-);
-
--- 1000~1999 MobileAction platforms = [1,2]
-
--- 1000.switchContext
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1000,
- '切换context',
- '$.switchContext',
- 'void',
- '[{"name":"context","type":"String","description":"context","possibleValues":[{"value":"NATIVE_APP","description":"原生"}]}]',
- '[1,2]'
-);
-
--- 1001.installApp
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1001,
- '安装App',
- '$.installApp',
- 'void',
- '[{"name":"appDownloadUrl","type":"String","description":"app下载地址"}]',
- '[1,2]'
-);
-
--- 1002.uninstallApp
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1002,
- '卸载App',
- '$.uninstallApp',
- 'void',
- '[{"name":"app","type":"String","description":"android: packageName, iOS: bundleId"}]',
- '[1,2]'
-);
-
--- 1003.swipe
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1003,
- '滑动屏幕',
- '$.swipe',
- 'void',
- '[{"name":"startPoint","type":"String","description":"起点,如: {x:0.5,y:0.5} => 屏幕中心点"},{"name":"endPoint","type":"String","description":"终点,如: {x:0.5,y:0.5} => 屏幕中心点"},{"name":"durationInMs","type":"String","description":"滑动一次的时间,单位: ms。时间越短,滑的距离越长"}]',
- '[1,2]'
-);
-
--- 1004.swipeToFindElement
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1004,
- '滑动屏幕查找元素',
- '$.swipeToFindElement',
- 'WebElement',
- REPLACE('[#,{"name":"startPoint","type":"String","description":"起点,如: {x:0.5,y:0.5} => 屏幕中心点"},{"name":"endPoint","type":"String","description":"终点,如: {x:0.5,y:0.5} => 屏幕中心点"},{"name":"maxSwipeCount","type":"String","description":"最大滑动次数"},{"name":"onceDurationInMs","type":"String","description":"滑动一次的时间,单位: ms。时间越短,滑的距离越长"}]','#',@findbyAndValue),
- '[1,2]'
-);
-
--- 1005.swipeInContainer
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1005,
- '容器内滑动',
- '$.swipeInContainer',
- 'void',
- '[{"name":"container","type":"WebElement","description":"容器元素"},{"name":"startPoint","type":"String","description":"起点,如: {x:0.5,y:0.5} => 容器中心点"},{"name":"endPoint","type":"String","description":"终点,如: {x:0.5,y:0.5} => 容器中心点"},{"name":"onceDurationInMs","type":"String","description":"滑动一次的时间,单位: ms。时间越短,滑的距离越长"}]',
- '[1,2]'
-);
-
--- 1006.swipeInContainerToFindElement
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1006,
- '容器内滑动查找元素',
- '$.swipeInContainerToFindElement',
- 'WebElement',
- REPLACE('[{"name":"container","type":"WebElement","description":"容器元素"},#,{"name":"startPoint","type":"String","description":"起点,如: {x:0.5,y:0.5} => 容器中心点"},{"name":"endPoint","type":"String","description":"终点,如: {x:0.5,y:0.5} => 容器中心点"},{"name":"maxSwipeCount","type":"String","description":"最大滑动次数"},{"name":"onceDurationInMs","type":"String","description":"滑动一次的时间,单位: ms。时间越短,滑的距离越长"}]','#',@findbyAndValue),
- '[1,2]'
-);
-
--- 1007.acceptAlert
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `platforms`
-)
-VALUES
-(
- 1007,
- 'accept对话框',
- '$.acceptAlert',
- 'boolean',
- '[1,2]'
-);
-
--- 1008.asyncAcceptAlert
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1008,
- '异步accept对话框',
- '$.asyncAcceptAlert',
- 'void',
- '[{"name":"timeoutInSeconds","type":"String","description":"超时时间,单位: 秒"},{"name":"once","type":"String","description":"是否只处理一次, true or false"}]',
- '[1,2]'
-);
-
--- 1009.dismissAlert
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `platforms`
-)
-VALUES
-(
- 1009,
- 'dismiss对话框',
- '$.dismissAlert',
- 'boolean',
- '[1,2]'
-);
-
--- 1010.asyncDismissAlert
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1010,
- '异步dismiss对话框',
- '$.asyncDismissAlert',
- 'void',
- '[{"name":"timeoutInSeconds","type":"String","description":"超时时间,单位: 秒"},{"name":"once","type":"String","description":"是否只处理一次, true or false"}]',
- '[1,2]'
-);
-
--- 1011.clickByTouchAction
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1011,
- '(TouchAction)点击',
- '$.clickByTouchAction',
- 'WebElement',
- REPLACE('[#]','#',@findbyAndValue),
- '[1,2]'
-);
-
--- 1012.longPressElement
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 1012,
- '长按元素',
- '$.longPressElement',
- 'void',
- '[{"name":"element","type":"WebElement","description":""},{"name":"durationInMs","type":"String","description":"长按时间,单位: ms"}]',
- '[1,2]'
-);
-
--- 2000~2999 AndroidAction platforms = [1]
-
--- 2000.clearApkData
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 2000,
- '清除apk数据',
- '$.clearApkData',
- 'void',
- '[{"name":"packageName","type":"String","description":"包名"}]',
- '[1]'
-);
-
--- 2001.restartApk
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 2001,
- '启动/重启apk',
- '$.restartApk',
- 'void',
- '[{"name":"packageName","type":"String","description":"包名"},{"name":"launchActivity","type":"String","description":"启动Activity名"}]',
- '[1]'
-);
-
--- 3000~3999 IosAction platforms = [2]
-
--- 3000.restartIosApp
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 3000,
- '启动/重启app',
- '$.restartIosApp',
- 'void',
- '[{"name":"bundleId","type":"String","description": "app bundleId"}]',
- '[2]'
-);
-
--- 4000~4999 PCWebAction platforms = [3]
-
--- 4000.windowMaximize
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `platforms`
-)
-VALUES
-(
- 4000,
- '窗口最大化',
- '$.windowMaximize',
- 'void',
- '[3]'
-);
-
--- 4001.mouseOver
-INSERT INTO `action` (
- `id`,
- `name`,
- `invoke`,
- `return_value`,
- `params`,
- `platforms`
-)
-VALUES
-(
- 4001,
- '光标移动到元素上',
- '$.mouseOver',
- 'void',
- '[{"name":"element","type":"WebElement","description":""}]',
- '[3]'
-);
diff --git a/src/main/java/com/daxiang/core/AgentStartRunner.java b/src/main/java/com/daxiang/core/AgentStartRunner.java
index 5c66a6c..4df008b 100644
--- a/src/main/java/com/daxiang/core/AgentStartRunner.java
+++ b/src/main/java/com/daxiang/core/AgentStartRunner.java
@@ -1,5 +1,6 @@
package com.daxiang.core;
+import com.daxiang.core.action.BasicActionScanner;
import com.daxiang.core.mobile.android.ADB;
import com.daxiang.core.mobile.android.AndroidDeviceChangeListener;
import com.daxiang.core.mobile.appium.AppiumServer;
@@ -7,6 +8,7 @@
import com.daxiang.core.mobile.ios.IosDeviceMonitor;
import com.daxiang.core.pc.web.Browser;
import com.daxiang.core.pc.web.BrowserInitializer;
+import com.daxiang.model.action.Action;
import com.daxiang.utils.Terminal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -17,6 +19,7 @@
import org.springframework.stereotype.Component;
import java.io.IOException;
+import java.util.List;
/**
* Created by jiangyitao.
@@ -25,6 +28,8 @@
@Component
public class AgentStartRunner implements ApplicationRunner {
+ public static final String BASIC_ACTION_PACKAGE = "com.daxiang.action";
+
@Autowired
private AndroidDeviceChangeListener androidDeviceChangeListener;
@Autowired
@@ -85,6 +90,10 @@ public void run(ApplicationArguments args) throws IOException, InterruptedExcept
// ffmpeg
Terminal.execute("ffmpeg -version");
+
+ BasicActionScanner basicActionScanner = new BasicActionScanner();
+ List basicActions = basicActionScanner.scan(BASIC_ACTION_PACKAGE);
+ // todo 发送到server
}
private void checkAppiumVersion(String appiumVersion) {
diff --git a/src/main/java/com/daxiang/core/action/BasicActionScanner.java b/src/main/java/com/daxiang/core/action/BasicActionScanner.java
new file mode 100644
index 0000000..202d119
--- /dev/null
+++ b/src/main/java/com/daxiang/core/action/BasicActionScanner.java
@@ -0,0 +1,110 @@
+package com.daxiang.core.action;
+
+import com.alibaba.fastjson.JSONArray;
+import com.daxiang.model.action.Action;
+import com.daxiang.model.action.Param;
+import com.daxiang.model.action.PossibleValue;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.primitives.Ints;
+import com.google.common.reflect.ClassPath;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Created by jiangyitao.
+ */
+public class BasicActionScanner {
+
+ // 废弃的action分类id
+ private static final int DEPRECATED_ACTION_CATEGORY_ID = 10000;
+ // basic action最大id
+ private static final int BASIC_ACTION_MAX_ID = 10000;
+
+ public List scan(String packageName) throws IOException {
+ Assert.hasText(packageName, "packageName must has text");
+
+ List actions = new ArrayList<>();
+
+ // 扫描packageName目录及子目录
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ ImmutableSet classInfos = ClassPath.from(classLoader)
+ .getTopLevelClassesRecursive(packageName);
+
+ for (ClassPath.ClassInfo classInfo : classInfos) {
+ Class clazz = classInfo.load();
+ Method[] methods = clazz.getDeclaredMethods();
+ for (Method method : methods) {
+ Action action = createAction(clazz.getName(), method);
+ if (action != null) {
+ actions.add(action);
+ }
+ }
+ }
+
+ return actions;
+ }
+
+ private Set cachedActionIds = new HashSet<>();
+
+ private Action createAction(String className, Method method) {
+ com.daxiang.core.action.annotation.Action actionAnno = method
+ .getAnnotation(com.daxiang.core.action.annotation.Action.class);
+ // 只创建带action注解的方法
+ if (actionAnno == null) {
+ return null;
+ }
+
+ int actionId = actionAnno.id();
+ if (actionId > BASIC_ACTION_MAX_ID) {
+ throw new RuntimeException(String.format("actionId: %d不能大于%d", actionId, BASIC_ACTION_MAX_ID));
+ }
+ if (cachedActionIds.contains(actionId)) {
+ throw new RuntimeException(String.format("actionId: %d重复", actionId));
+ }
+ cachedActionIds.add(actionId);
+
+ Action action = new Action();
+ action.setId(actionId);
+ action.setType(Action.TYPE_BASE);
+ action.setPlatforms(Ints.asList(actionAnno.platforms()));
+ action.setReturnValueType(method.getReturnType().getSimpleName());
+
+ if (method.getAnnotation(Deprecated.class) != null) {
+ // 废弃的action,添加到废弃分类
+ action.setCategoryId(DEPRECATED_ACTION_CATEGORY_ID);
+ }
+
+ String methodName = method.getName();
+ String actionName = StringUtils.isEmpty(actionAnno.name()) ? methodName : actionAnno.name();
+ action.setName(actionName);
+
+ // 默认使用$.methodName调用,否则使用全类名.methodName调用
+ String actionInvoke = actionAnno.invoke() == 1 ? "$." + methodName : className + "." + methodName;
+ action.setInvoke(actionInvoke);
+
+ List params = Stream.of(method.getParameters()).map(parameter -> {
+ Param param = new Param();
+ param.setType(parameter.getType().getSimpleName());
+ param.setName(parameter.getName());
+
+ com.daxiang.core.action.annotation.Param paramAnno = parameter
+ .getAnnotation(com.daxiang.core.action.annotation.Param.class);
+ if (paramAnno != null) {
+ param.setDescription(paramAnno.description());
+ List possibleValues = JSONArray.parseArray(paramAnno.possibleValues(), PossibleValue.class);
+ param.setPossibleValues(possibleValues);
+ }
+
+ return param;
+ }).collect(Collectors.toList());
+ action.setParams(params);
+
+ return action;
+ }
+}
diff --git a/src/main/java/com/daxiang/core/action/annotation/Action.java b/src/main/java/com/daxiang/core/action/annotation/Action.java
new file mode 100644
index 0000000..4a4bebf
--- /dev/null
+++ b/src/main/java/com/daxiang/core/action/annotation/Action.java
@@ -0,0 +1,23 @@
+package com.daxiang.core.action.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by jiangyitao.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Action {
+ int id();
+
+ String name() default ""; // 默认为方法名
+
+ String description() default "";
+
+ int invoke() default 1; // 1. $.methodName 2. ClassName.methodName
+
+ int[] platforms() default {}; // 1.android 2.ios 3.web 空:所有平台通用
+}
diff --git a/src/main/java/com/daxiang/core/action/annotation/Param.java b/src/main/java/com/daxiang/core/action/annotation/Param.java
new file mode 100644
index 0000000..b070983
--- /dev/null
+++ b/src/main/java/com/daxiang/core/action/annotation/Param.java
@@ -0,0 +1,17 @@
+package com.daxiang.core.action.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by jiangyitao.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.PARAMETER)
+public @interface Param {
+ String description() default "";
+
+ String possibleValues() default "[]";
+}
diff --git a/src/main/java/com/daxiang/model/action/Action.java b/src/main/java/com/daxiang/model/action/Action.java
index 77090d6..98d7d24 100644
--- a/src/main/java/com/daxiang/model/action/Action.java
+++ b/src/main/java/com/daxiang/model/action/Action.java
@@ -11,14 +11,13 @@
public class Action {
public static final int TYPE_BASE = 1;
- public static final int HAS_RETURN_VALUE = 1;
private Integer id;
private String name;
private Integer type;
- private Integer platform;
+ private List platforms;
private String invoke;
- private String returnValue;
+ private String returnValueType;
private String returnValueDesc;
private List params;
private List localVars;
@@ -26,4 +25,6 @@ public class Action {
private List javaImports;
private List importActions;
private List depends;
-}
\ No newline at end of file
+ private Integer categoryId;
+ private Integer projectId;
+}
diff --git a/src/main/java/com/daxiang/model/action/Param.java b/src/main/java/com/daxiang/model/action/Param.java
index 1d68340..99b9c35 100644
--- a/src/main/java/com/daxiang/model/action/Param.java
+++ b/src/main/java/com/daxiang/model/action/Param.java
@@ -2,6 +2,8 @@
import lombok.Data;
+import java.util.List;
+
/**
* Created by jiangyitao.
*/
@@ -10,5 +12,6 @@ public class Param {
private String type;
private String name;
private String description;
+ private List possibleValues;
}
diff --git a/src/main/java/com/daxiang/model/action/PossibleValue.java b/src/main/java/com/daxiang/model/action/PossibleValue.java
new file mode 100644
index 0000000..1c64a15
--- /dev/null
+++ b/src/main/java/com/daxiang/model/action/PossibleValue.java
@@ -0,0 +1,12 @@
+package com.daxiang.model.action;
+
+import lombok.Data;
+
+/**
+ * Created by jiangyitao.
+ */
+@Data
+public class PossibleValue {
+ private String value;
+ private String description;
+}
diff --git a/src/main/resources/codetemplate/actions.ftl b/src/main/resources/codetemplate/actions.ftl
index 6bc857b..d7712a6 100644
--- a/src/main/resources/codetemplate/actions.ftl
+++ b/src/main/resources/codetemplate/actions.ftl
@@ -6,7 +6,7 @@
<#lt> // ${action.name}
#if>
<#-- 方法前缀 -->
- <#lt> public ${action.returnValue} ${actionPrefix}${action.id?c}(<#rt>
+ <#lt> public ${action.returnValueType} ${actionPrefix}${action.id?c}(<#rt>
<#-- 方法参数 -->
<#if action.params?? && (action.params?size>0)>
<#list action.params as param>
@@ -16,7 +16,7 @@
<#-- 方法体 -->
<#-- 基础action -->
<#if action.type==1>
- <#lt> <#if action.returnValue!='void'>return #if>${action.invoke}(<#rt>
+ <#lt> <#if action.returnValueType!='void'>return #if>${action.invoke}(<#rt>
<#if action.params?? && (action.params?size>0)>
<#list action.params as param>
<#lt>${param.name}<#sep>, <#rt>
From a585ef8cdf33619e6955b1e93776cf94686b1e95 Mon Sep 17 00:00:00 2001
From: jiangyitao <451988022@163.com>
Date: Fri, 24 Jul 2020 08:32:48 +0800
Subject: [PATCH 2/8] 0.7.5
---
src/main/java/com/daxiang/action/BaseAction.java | 2 +-
src/main/java/com/daxiang/core/AgentStartRunner.java | 6 +++++-
.../com/daxiang/core/action/BasicActionScanner.java | 1 +
.../com/daxiang/core/action/annotation/Action.java | 2 ++
src/main/java/com/daxiang/model/action/Action.java | 1 +
src/main/java/com/daxiang/server/ServerClient.java | 11 +++++++++++
6 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/daxiang/action/BaseAction.java b/src/main/java/com/daxiang/action/BaseAction.java
index 32a4757..8009984 100644
--- a/src/main/java/com/daxiang/action/BaseAction.java
+++ b/src/main/java/com/daxiang/action/BaseAction.java
@@ -46,7 +46,7 @@ public BaseAction(Device device) {
this.device = device;
}
- @Action(id = 1, name = "执行java代码")
+ @Action(id = EXECUTE_JAVA_CODE_ID, name = "执行java代码")
public void executeJavaCode(@Param(description = "java代码") String code) {
Assert.hasText(code, "code不能为空");
}
diff --git a/src/main/java/com/daxiang/core/AgentStartRunner.java b/src/main/java/com/daxiang/core/AgentStartRunner.java
index 4df008b..9c9f166 100644
--- a/src/main/java/com/daxiang/core/AgentStartRunner.java
+++ b/src/main/java/com/daxiang/core/AgentStartRunner.java
@@ -9,6 +9,7 @@
import com.daxiang.core.pc.web.Browser;
import com.daxiang.core.pc.web.BrowserInitializer;
import com.daxiang.model.action.Action;
+import com.daxiang.server.ServerClient;
import com.daxiang.utils.Terminal;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -30,6 +31,9 @@ public class AgentStartRunner implements ApplicationRunner {
public static final String BASIC_ACTION_PACKAGE = "com.daxiang.action";
+ @Autowired
+ private ServerClient serverClient;
+
@Autowired
private AndroidDeviceChangeListener androidDeviceChangeListener;
@Autowired
@@ -93,7 +97,7 @@ public void run(ApplicationArguments args) throws IOException, InterruptedExcept
BasicActionScanner basicActionScanner = new BasicActionScanner();
List basicActions = basicActionScanner.scan(BASIC_ACTION_PACKAGE);
- // todo 发送到server
+ serverClient.resetBasicAction(basicActions);
}
private void checkAppiumVersion(String appiumVersion) {
diff --git a/src/main/java/com/daxiang/core/action/BasicActionScanner.java b/src/main/java/com/daxiang/core/action/BasicActionScanner.java
index 202d119..e0eed7c 100644
--- a/src/main/java/com/daxiang/core/action/BasicActionScanner.java
+++ b/src/main/java/com/daxiang/core/action/BasicActionScanner.java
@@ -72,6 +72,7 @@ private Action createAction(String className, Method method) {
Action action = new Action();
action.setId(actionId);
action.setType(Action.TYPE_BASE);
+ action.setState(actionAnno.state());
action.setPlatforms(Ints.asList(actionAnno.platforms()));
action.setReturnValueType(method.getReturnType().getSimpleName());
diff --git a/src/main/java/com/daxiang/core/action/annotation/Action.java b/src/main/java/com/daxiang/core/action/annotation/Action.java
index 4a4bebf..62e61c3 100644
--- a/src/main/java/com/daxiang/core/action/annotation/Action.java
+++ b/src/main/java/com/daxiang/core/action/annotation/Action.java
@@ -20,4 +20,6 @@
int invoke() default 1; // 1. $.methodName 2. ClassName.methodName
int[] platforms() default {}; // 1.android 2.ios 3.web 空:所有平台通用
+
+ int state() default 2; // 0.禁用 1.草稿 2.发布
}
diff --git a/src/main/java/com/daxiang/model/action/Action.java b/src/main/java/com/daxiang/model/action/Action.java
index 98d7d24..49763cb 100644
--- a/src/main/java/com/daxiang/model/action/Action.java
+++ b/src/main/java/com/daxiang/model/action/Action.java
@@ -27,4 +27,5 @@ public class Action {
private List depends;
private Integer categoryId;
private Integer projectId;
+ private Integer state;
}
diff --git a/src/main/java/com/daxiang/server/ServerClient.java b/src/main/java/com/daxiang/server/ServerClient.java
index b0d4199..7f845a3 100644
--- a/src/main/java/com/daxiang/server/ServerClient.java
+++ b/src/main/java/com/daxiang/server/ServerClient.java
@@ -6,6 +6,7 @@
import com.daxiang.core.mobile.Mobile;
import com.daxiang.model.Response;
import com.daxiang.model.UploadFile;
+import com.daxiang.model.action.Action;
import com.daxiang.model.devicetesttask.DeviceTestTask;
import com.daxiang.model.devicetesttask.Testcase;
import com.daxiang.utils.Terminal;
@@ -44,6 +45,9 @@ public class ServerClient {
@Autowired
private RestTemplate restTemplate;
+ @Value("${server}/action/resetBasicAction")
+ private String resetBasicActionUrl;
+
@Value("${server}/upload/file/{fileType}")
private String uploadFileUrl;
@Value("${server}/project/list")
@@ -71,6 +75,13 @@ public static ServerClient getInstance() {
return INSTANCE;
}
+ public void resetBasicAction(List actions) {
+ Response response = restTemplate.postForObject(resetBasicActionUrl, actions, Response.class);
+ if (!response.isSuccess()) {
+ throw new RuntimeException(response.getMsg());
+ }
+ }
+
public Capabilities getCapabilitiesByProjectId(Integer projectId) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
From c45a7b1e7c10ea3caf638e37866e1ba4c598d9b9 Mon Sep 17 00:00:00 2001
From: jiangyitao <451988022@163.com>
Date: Fri, 24 Jul 2020 11:30:39 +0800
Subject: [PATCH 3/8] 0.7.5
---
.../core/action/BasicActionScanner.java | 26 +++++++++++++------
.../core/action/annotation/Action.java | 4 +++
.../java/com/daxiang/model/action/Action.java | 9 ++++---
3 files changed, 27 insertions(+), 12 deletions(-)
diff --git a/src/main/java/com/daxiang/core/action/BasicActionScanner.java b/src/main/java/com/daxiang/core/action/BasicActionScanner.java
index e0eed7c..4ee5272 100644
--- a/src/main/java/com/daxiang/core/action/BasicActionScanner.java
+++ b/src/main/java/com/daxiang/core/action/BasicActionScanner.java
@@ -71,23 +71,32 @@ private Action createAction(String className, Method method) {
Action action = new Action();
action.setId(actionId);
+
+ String methodName = method.getName();
+ String actionName = StringUtils.isEmpty(actionAnno.name()) ? methodName : actionAnno.name();
+ action.setName(actionName);
+
+ action.setDescription(actionAnno.description());
action.setType(Action.TYPE_BASE);
- action.setState(actionAnno.state());
- action.setPlatforms(Ints.asList(actionAnno.platforms()));
+
+ // 默认使用$.methodName调用,否则使用全类名.methodName调用
+ String actionInvoke = actionAnno.invoke() == 1 ? "$." + methodName : className + "." + methodName;
+ action.setInvoke(actionInvoke);
+
action.setReturnValueType(method.getReturnType().getSimpleName());
+ action.setReturnValueDesc(actionAnno.returnValueDesc());
if (method.getAnnotation(Deprecated.class) != null) {
// 废弃的action,添加到废弃分类
action.setCategoryId(DEPRECATED_ACTION_CATEGORY_ID);
}
- String methodName = method.getName();
- String actionName = StringUtils.isEmpty(actionAnno.name()) ? methodName : actionAnno.name();
- action.setName(actionName);
+ // -1为默认值
+ if (actionAnno.projectId() != -1) {
+ action.setProjectId(actionAnno.projectId());
+ }
- // 默认使用$.methodName调用,否则使用全类名.methodName调用
- String actionInvoke = actionAnno.invoke() == 1 ? "$." + methodName : className + "." + methodName;
- action.setInvoke(actionInvoke);
+ action.setState(actionAnno.state());
List params = Stream.of(method.getParameters()).map(parameter -> {
Param param = new Param();
@@ -105,6 +114,7 @@ private Action createAction(String className, Method method) {
return param;
}).collect(Collectors.toList());
action.setParams(params);
+ action.setPlatforms(Ints.asList(actionAnno.platforms()));
return action;
}
diff --git a/src/main/java/com/daxiang/core/action/annotation/Action.java b/src/main/java/com/daxiang/core/action/annotation/Action.java
index 62e61c3..805ea4c 100644
--- a/src/main/java/com/daxiang/core/action/annotation/Action.java
+++ b/src/main/java/com/daxiang/core/action/annotation/Action.java
@@ -22,4 +22,8 @@
int[] platforms() default {}; // 1.android 2.ios 3.web 空:所有平台通用
int state() default 2; // 0.禁用 1.草稿 2.发布
+
+ String returnValueDesc() default "";
+
+ int projectId() default -1;
}
diff --git a/src/main/java/com/daxiang/model/action/Action.java b/src/main/java/com/daxiang/model/action/Action.java
index 49763cb..d68b66a 100644
--- a/src/main/java/com/daxiang/model/action/Action.java
+++ b/src/main/java/com/daxiang/model/action/Action.java
@@ -14,18 +14,19 @@ public class Action {
private Integer id;
private String name;
+ private String description;
private Integer type;
- private List platforms;
private String invoke;
private String returnValueType;
private String returnValueDesc;
+ private Integer categoryId;
+ private Integer projectId;
+ private Integer state;
private List params;
private List localVars;
private List steps;
private List javaImports;
private List importActions;
+ private List platforms;
private List depends;
- private Integer categoryId;
- private Integer projectId;
- private Integer state;
}
From e7e419b4cd17e726b75acb6a7643937e9c406958 Mon Sep 17 00:00:00 2001
From: jiangyitao <451988022@163.com>
Date: Fri, 24 Jul 2020 12:17:56 +0800
Subject: [PATCH 4/8] fullType
---
.../java/com/daxiang/core/action/BasicActionScanner.java | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/java/com/daxiang/core/action/BasicActionScanner.java b/src/main/java/com/daxiang/core/action/BasicActionScanner.java
index 4ee5272..c480190 100644
--- a/src/main/java/com/daxiang/core/action/BasicActionScanner.java
+++ b/src/main/java/com/daxiang/core/action/BasicActionScanner.java
@@ -7,6 +7,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.google.common.reflect.ClassPath;
+import org.apache.commons.lang3.reflect.TypeUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -83,7 +84,7 @@ private Action createAction(String className, Method method) {
String actionInvoke = actionAnno.invoke() == 1 ? "$." + methodName : className + "." + methodName;
action.setInvoke(actionInvoke);
- action.setReturnValueType(method.getReturnType().getSimpleName());
+ action.setReturnValueType(TypeUtils.toString(method.getGenericReturnType()));
action.setReturnValueDesc(actionAnno.returnValueDesc());
if (method.getAnnotation(Deprecated.class) != null) {
@@ -100,7 +101,7 @@ private Action createAction(String className, Method method) {
List params = Stream.of(method.getParameters()).map(parameter -> {
Param param = new Param();
- param.setType(parameter.getType().getSimpleName());
+ param.setType(TypeUtils.toString(parameter.getParameterizedType()));
param.setName(parameter.getName());
com.daxiang.core.action.annotation.Param paramAnno = parameter
From 6dd124bc3b0445514ba2670ca94a001dcb2c2646 Mon Sep 17 00:00:00 2001
From: jiangyitao <451988022@163.com>
Date: Fri, 24 Jul 2020 12:24:34 +0800
Subject: [PATCH 5/8] returnValueDesc sample
---
src/main/java/com/daxiang/action/BaseAction.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/com/daxiang/action/BaseAction.java b/src/main/java/com/daxiang/action/BaseAction.java
index 8009984..5f3e86b 100644
--- a/src/main/java/com/daxiang/action/BaseAction.java
+++ b/src/main/java/com/daxiang/action/BaseAction.java
@@ -68,7 +68,7 @@ public WebElement findElement(@Param(description = "查找方式", possibleValue
return device.getDriver().findElement(createBy(findBy, value));
}
- @Action(id = 9, name = "查找元素列表")
+ @Action(id = 9, name = "查找元素列表", returnValueDesc = "查找到的元素列表")
public List findElements(@Param(description = "查找方式", possibleValues = FIND_BY_POSSIBLE_VALUES) String findBy, String value) {
return device.getDriver().findElements(createBy(findBy, value));
}
From 60c8e06d598a4deedb486e99ea7e3b6b6b6528e6 Mon Sep 17 00:00:00 2001
From: jiangyitao <451988022@163.com>
Date: Fri, 24 Jul 2020 14:20:48 +0800
Subject: [PATCH 6/8] custom action sample
---
.../com/daxiang/action/YourCustomAction.java | 25 +++++++++++++++++++
1 file changed, 25 insertions(+)
create mode 100644 src/main/java/com/daxiang/action/YourCustomAction.java
diff --git a/src/main/java/com/daxiang/action/YourCustomAction.java b/src/main/java/com/daxiang/action/YourCustomAction.java
new file mode 100644
index 0000000..1f60a23
--- /dev/null
+++ b/src/main/java/com/daxiang/action/YourCustomAction.java
@@ -0,0 +1,25 @@
+package com.daxiang.action;
+
+import com.daxiang.core.action.annotation.Action;
+import com.daxiang.core.action.annotation.Param;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Created by jiangyitao.
+ * id: 5000 - 10000
+ */
+public class YourCustomAction {
+
+ /**
+ * invoke = 2 将通过YourCustomAction.randomAlphanumeric 进行调用
+ */
+ @Action(id = 5000, name = "随机字符串(数字 & 字母)", invoke = 2)
+ public static String randomAlphanumeric(@Param(description = "字符串长度") String count) {
+ if (StringUtils.isEmpty(count)) {
+ count = "10";
+ }
+ return RandomStringUtils.randomAlphanumeric(Integer.parseInt(count));
+ }
+
+}
From dbba6f672c51ec2821def3b23787eff672e1c4ae Mon Sep 17 00:00:00 2001
From: jiangyitao <451988022@163.com>
Date: Fri, 24 Jul 2020 16:18:11 +0800
Subject: [PATCH 7/8] paramValue -> arg
---
.../daxiang/core/testng/TestNGCodeConverter.java | 15 +++++++--------
.../java/com/daxiang/model/action/ParamValue.java | 13 -------------
src/main/java/com/daxiang/model/action/Step.java | 2 +-
src/main/resources/codetemplate/actions.ftl | 8 ++++----
4 files changed, 12 insertions(+), 26 deletions(-)
delete mode 100644 src/main/java/com/daxiang/model/action/ParamValue.java
diff --git a/src/main/java/com/daxiang/core/testng/TestNGCodeConverter.java b/src/main/java/com/daxiang/core/testng/TestNGCodeConverter.java
index 44e4277..068b623 100644
--- a/src/main/java/com/daxiang/core/testng/TestNGCodeConverter.java
+++ b/src/main/java/com/daxiang/core/testng/TestNGCodeConverter.java
@@ -213,7 +213,7 @@ private void handleGlobalVarValue(List globalVars) {
}
/**
- * 处理action localVar value & step paramValue
+ * 处理action localVar value & step args
*/
private void handleActionValue() {
Collection actions = cachedActions.values();
@@ -226,13 +226,12 @@ private void handleActionValue() {
List steps = action.getSteps();
if (!CollectionUtils.isEmpty(steps)) {
for (Step step : steps) {
- List paramValues = step.getParamValues();
- if (!CollectionUtils.isEmpty(paramValues)) {
- for (ParamValue paramValue : paramValues) {
- // ExecuteJavaCode直接嵌入模版,无需处理
- if (step.getActionId() != BaseAction.EXECUTE_JAVA_CODE_ID) {
- paramValue.setParamValue(handleValue(paramValue.getParamValue()));
- }
+ // ExecuteJavaCode直接嵌入模版,无需处理
+ if (step.getActionId() != BaseAction.EXECUTE_JAVA_CODE_ID) {
+ List args = step.getArgs();
+ if (!CollectionUtils.isEmpty(args)) {
+ List newArgs = args.stream().map(this::handleValue).collect(Collectors.toList());
+ step.setArgs(newArgs);
}
}
}
diff --git a/src/main/java/com/daxiang/model/action/ParamValue.java b/src/main/java/com/daxiang/model/action/ParamValue.java
deleted file mode 100644
index 5b70ce6..0000000
--- a/src/main/java/com/daxiang/model/action/ParamValue.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.daxiang.model.action;
-
-import lombok.Data;
-
-/**
- * Created by jiangyitao.
- */
-@Data
-public class ParamValue {
- private String paramName;
- private String paramType;
- private String paramValue;
-}
diff --git a/src/main/java/com/daxiang/model/action/Step.java b/src/main/java/com/daxiang/model/action/Step.java
index 5a72a00..206aafe 100644
--- a/src/main/java/com/daxiang/model/action/Step.java
+++ b/src/main/java/com/daxiang/model/action/Step.java
@@ -27,5 +27,5 @@ public class Step {
*/
private Integer handleException;
private Integer number;
- private List paramValues;
+ private List args;
}
diff --git a/src/main/resources/codetemplate/actions.ftl b/src/main/resources/codetemplate/actions.ftl
index d7712a6..fef0789 100644
--- a/src/main/resources/codetemplate/actions.ftl
+++ b/src/main/resources/codetemplate/actions.ftl
@@ -46,15 +46,15 @@
#if>
<#-- 直接嵌入java代码 -->
<#if step.actionId==executeJavaCodeActionId>
- <#list step.paramValues[0].paramValue?split("\n") as code>
+ <#list step.args[0]?split("\n") as code>
<#lt><#if step.handleException??> #if> ${code}
#list>
<#else>
<#-- 非嵌入代码,步骤赋值,方法调用 -->
<#lt><#if step.handleException??> #if> <#if step.evaluation?? && step.evaluation!=''>${step.evaluation} = #if>${actionPrefix}${step.actionId?c}(<#rt>
- <#if step.paramValues?? && (step.paramValues?size>0)>
- <#list step.paramValues as paramValue>
- <#lt>${paramValue.paramValue}<#sep>, <#rt>
+ <#if step.args?? && (step.args?size>0)>
+ <#list step.args as arg>
+ <#lt>${arg}<#sep>, <#rt>
#list>
#if><#lt>);
#if>
From 3bb4b1f6867ff354d8d3d41c4af5b120c4a419a6 Mon Sep 17 00:00:00 2001
From: jiangyitao <451988022@163.com>
Date: Sat, 25 Jul 2020 11:29:38 +0800
Subject: [PATCH 8/8] agent version
---
src/main/java/com/daxiang/core/AgentStartRunner.java | 5 +++++
src/main/resources/application.properties | 4 +++-
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/daxiang/core/AgentStartRunner.java b/src/main/java/com/daxiang/core/AgentStartRunner.java
index 9c9f166..f305879 100644
--- a/src/main/java/com/daxiang/core/AgentStartRunner.java
+++ b/src/main/java/com/daxiang/core/AgentStartRunner.java
@@ -34,6 +34,9 @@ public class AgentStartRunner implements ApplicationRunner {
@Autowired
private ServerClient serverClient;
+ @Value("${version}")
+ private String version;
+
@Autowired
private AndroidDeviceChangeListener androidDeviceChangeListener;
@Autowired
@@ -50,6 +53,8 @@ public class AgentStartRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws IOException, InterruptedException {
+ System.setProperty("agent.version", version);
+
// 移动端
if (enableAndroid || enableIos) {
String appiumVersion = AppiumServer.getVersion();
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 6e6b9a0..da046d5 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -10,4 +10,6 @@ spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
#ip取代hostname
-spring.boot.admin.client.instance.prefer-ip=true
\ No newline at end of file
+spring.boot.admin.client.instance.prefer-ip=true
+
+version=@project.version@
\ No newline at end of file