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} <#-- 方法前缀 --> - <#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 ${action.invoke}(<#rt> + <#lt> <#if action.returnValueType!='void'>return ${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 @@ <#-- 直接嵌入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??> ${code} <#else> <#-- 非嵌入代码,步骤赋值,方法调用 --> <#lt><#if step.handleException??> <#if step.evaluation?? && step.evaluation!=''>${step.evaluation} = ${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> <#lt>); 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