diff --git a/pom.xml b/pom.xml index 7e96cc33..016e9094 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.sonic sonic-agent - v1.2.0-release + v1.3.0-beta1 jar diff --git a/src/main/java/com/sonic/agent/automation/AndroidStepHandler.java b/src/main/java/com/sonic/agent/automation/AndroidStepHandler.java index b52e0467..2d2f713f 100644 --- a/src/main/java/com/sonic/agent/automation/AndroidStepHandler.java +++ b/src/main/java/com/sonic/agent/automation/AndroidStepHandler.java @@ -120,6 +120,11 @@ public void startAndroidDriver(String udId) throws InterruptedException { desiredCapabilities.setCapability(AndroidMobileCapabilityType.ADB_EXEC_TIMEOUT, 60000); //UIA2安装超时时间 desiredCapabilities.setCapability("uiautomator2ServerInstallTimeout", 30000); + + //io.appium.uiautomator2.server io.appium.uiautomator2.server.test //io.appium.settings +// desiredCapabilities.setCapability("skipServerInstallation",true); +// desiredCapabilities.setCapability("disableWindowAnimation",true); +// desiredCapabilities.setCapability("skipDeviceInitialization",true); //等待新命令超时时间 desiredCapabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 3600); //不重置应用 @@ -361,13 +366,12 @@ public void startRecord() { } /** - * @param udId * @return void * @author ZhouYiXun * @des 停止录像 * @date 2021/8/16 23:56 */ - public void stopRecord(String udId) { + public void stopRecord() { File recordDir = new File("test-output/record"); if (!recordDir.exists()) { recordDir.mkdirs(); diff --git a/src/main/java/com/sonic/agent/automation/IOSStepHandler.java b/src/main/java/com/sonic/agent/automation/IOSStepHandler.java index 7abeae75..91742b1f 100644 --- a/src/main/java/com/sonic/agent/automation/IOSStepHandler.java +++ b/src/main/java/com/sonic/agent/automation/IOSStepHandler.java @@ -4,20 +4,31 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.sonic.agent.bridge.ios.TIDeviceTool; +import com.sonic.agent.cv.*; +import com.sonic.agent.interfaces.ErrorType; import com.sonic.agent.interfaces.ResultDetailStatus; import com.sonic.agent.interfaces.StepType; import com.sonic.agent.maps.IOSProcessMap; +import com.sonic.agent.maps.IOSSizeMap; +import com.sonic.agent.tools.DownImageTool; import com.sonic.agent.tools.LogTool; import com.sonic.agent.tools.UploadTools; import io.appium.java_client.MobileBy; +import io.appium.java_client.MultiTouchAction; import io.appium.java_client.Setting; +import io.appium.java_client.TouchAction; import io.appium.java_client.android.appmanagement.AndroidTerminateApplicationOptions; import io.appium.java_client.android.nativekey.AndroidKey; import io.appium.java_client.android.nativekey.KeyEvent; +import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; +import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; import io.appium.java_client.ios.IOSDriver; +import io.appium.java_client.ios.IOSStartScreenRecordingOptions; import io.appium.java_client.remote.AutomationName; import io.appium.java_client.remote.IOSMobileCapabilityType; import io.appium.java_client.remote.MobileCapabilityType; +import io.appium.java_client.touch.WaitOptions; +import io.appium.java_client.touch.offset.PointOption; import org.jsoup.Jsoup; import org.jsoup.nodes.Attribute; import org.jsoup.nodes.Document; @@ -27,15 +38,25 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.support.ui.ExpectedConditions; +import org.springframework.util.Base64Utils; +import org.springframework.util.FileCopyUtils; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.time.Duration; +import java.util.Calendar; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; +import static org.testng.Assert.*; + public class IOSStepHandler { public LogTool log = new LogTool(); private IOSDriver iosDriver; + private JSONObject globalParams = new JSONObject(); private String testPackage = ""; private String udId = ""; //测试状态 @@ -49,10 +70,12 @@ public void setTestMode(int caseId, int resultId, String udId, String type, Stri log.sessionId = sessionId; } - public int startIOSDriver(String udId) throws InterruptedException, IOException { + public void setGlobalParams(JSONObject jsonObject) { + globalParams = jsonObject; + } + + public void startIOSDriver(String udId, int wdaPort) throws InterruptedException, IOException { this.udId = udId; - int wdaPort = TIDeviceTool.startWda(udId); - int imgPort = TIDeviceTool.relayImg(udId); Thread.sleep(2000); DesiredCapabilities desiredCapabilities = new DesiredCapabilities(); desiredCapabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, Platform.IOS); @@ -64,8 +87,10 @@ public int startIOSDriver(String udId) throws InterruptedException, IOException desiredCapabilities.setCapability("wdaConnectionTimeout", 60000); desiredCapabilities.setCapability(IOSMobileCapabilityType.WEB_DRIVER_AGENT_URL, "http://127.0.0.1:" + wdaPort); desiredCapabilities.setCapability("useXctestrunFile", false); - desiredCapabilities.setCapability("mjpegServerPort", imgPort); - desiredCapabilities.setCapability(IOSMobileCapabilityType.USE_PREBUILT_WDA, true); + desiredCapabilities.setCapability(IOSMobileCapabilityType.SHOW_IOS_LOG, false); + desiredCapabilities.setCapability(IOSMobileCapabilityType.SHOW_XCODE_LOG, false); + desiredCapabilities.setCapability("skipLogCapture", true); + desiredCapabilities.setCapability(IOSMobileCapabilityType.USE_PREBUILT_WDA, false); try { iosDriver = new IOSDriver(AppiumServer.service.getUrl(), desiredCapabilities); iosDriver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS); @@ -77,7 +102,9 @@ public int startIOSDriver(String udId) throws InterruptedException, IOException setResultDetailStatus(ResultDetailStatus.FAIL); throw e; } - return imgPort; + int width = iosDriver.manage().window().getSize().width; + int height = iosDriver.manage().window().getSize().height; + IOSSizeMap.getMap().put(udId, width + "x" + height); } public void closeIOSDriver() { @@ -112,6 +139,16 @@ public void closeIOSDriver() { } } + public void waitDevice(int waitCount) { + log.sendStepLog(StepType.INFO, "设备非空闲状态!第" + waitCount + "次等待连接...", ""); + } + + public void waitDeviceTimeOut() { + log.sendStepLog(StepType.ERROR, "等待设备超时!测试跳过!", ""); + //测试标记为异常 + setResultDetailStatus(ResultDetailStatus.WARN); + } + public IOSDriver getDriver() { return iosDriver; } @@ -122,6 +159,31 @@ public void setResultDetailStatus(int status) { } } + public void sendStatus() { + log.sendStatusLog(status); + } + + //判断有无出错 + public int getStatus() { + return status; + } + + //调试每次重设状态 + public void resetResultDetailStatus() { + status = 1; + } + + public boolean getBattery() { + double battery = iosDriver.getBatteryInfo().getLevel(); + if (battery <= 0.1) { + log.sendStepLog(StepType.ERROR, "设备电量过低!", "跳过本次测试..."); + return true; + } else { + return false; + } + } + + private int xpathId = 1; public JSONArray getResource() { @@ -170,6 +232,95 @@ public JSONArray getChild(org.jsoup.select.Elements elements, String xpath) { return elementList; } + public void startRecord() { + try { + IOSStartScreenRecordingOptions recordOption = new IOSStartScreenRecordingOptions(); + recordOption.withTimeLimit(Duration.ofMinutes(30)); + recordOption.withVideoQuality(IOSStartScreenRecordingOptions.VideoQuality.LOW); + recordOption.enableForcedRestart(); + recordOption.withFps(20); + recordOption.withVideoType("h264"); + iosDriver.startRecordingScreen(recordOption); + } catch (Exception e) { + log.sendRecordLog(false, "", ""); + } + } + + public void stopRecord() { + File recordDir = new File("./test-output/record"); + if (!recordDir.exists()) {//判断文件目录是否存在 + recordDir.mkdirs(); + } + long timeMillis = Calendar.getInstance().getTimeInMillis(); + String fileName = timeMillis + "_" + udId.substring(0, 4) + ".mp4"; + File uploadFile = new File(recordDir + File.separator + fileName); + try { + synchronized (IOSStepHandler.class) { + FileOutputStream fileOutputStream = new FileOutputStream(uploadFile); + byte[] bytes = Base64Utils.decodeFromString((iosDriver.stopRecordingScreen())); + fileOutputStream.write(bytes); + fileOutputStream.close(); + } + log.sendRecordLog(true, fileName, UploadTools.uploadPatchRecord(uploadFile)); + } catch (Exception e) { + log.sendRecordLog(false, fileName, ""); + } + } + + public void install(HandleDes handleDes, String path) { + handleDes.setStepDes("安装应用"); + handleDes.setDetail("App安装路径: " + path); + try { + iosDriver.installApp(path, new BaseInstallApplicationOptions() { + @Override + public Map build() { + Map map = new HashMap<>(); + map.put("timeout", 180000); + return map; + } + }); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void uninstall(HandleDes handleDes, String appPackage) { + handleDes.setStepDes("卸载应用"); + handleDes.setDetail("App包名: " + appPackage); + try { + iosDriver.removeApp(appPackage); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void terminate(HandleDes handleDes, String packageName) { + handleDes.setStepDes("终止应用"); + handleDes.setDetail("应用包名: " + packageName); + try { + iosDriver.terminateApp(packageName, new BaseTerminateApplicationOptions() { + @Override + public Map build() { + Map map = new HashMap<>(); + map.put("timeout", 2000); + return map; + } + }); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void runBackground(HandleDes handleDes, long time) { + handleDes.setStepDes("后台运行应用"); + handleDes.setDetail("后台运行App " + time + " ms"); + try { + iosDriver.runAppInBackground(Duration.ofMillis(time)); + } catch (Exception e) { + handleDes.setE(e); + } + } + public void openApp(HandleDes handleDes, String appPackage) { handleDes.setStepDes("打开应用"); handleDes.setDetail("App包名: " + appPackage); @@ -199,6 +350,333 @@ public void unLock(HandleDes handleDes) { } } + public void asserts(HandleDes handleDes, String actual, String expect, String type) { + handleDes.setDetail("真实值: " + actual + " 期望值: " + expect); + try { + switch (type) { + case "assertEquals": + handleDes.setStepDes("断言验证(相等)"); + assertEquals(actual, expect); + break; + case "assertTrue": + handleDes.setStepDes("断言验证(包含)"); + assertTrue(actual.contains(expect)); + break; + case "assertNotTrue": + handleDes.setStepDes("断言验证(不包含)"); + assertFalse(actual.contains(expect)); + break; + } + } catch (AssertionError e) { + handleDes.setE(e); + } + } + + public String getText(HandleDes handleDes, String des, String selector, String pathValue) { + String s = ""; + handleDes.setStepDes("获取" + des + "文本"); + handleDes.setDetail("获取" + selector + ":" + pathValue + "文本"); + try { + s = findEle(selector, pathValue).getText(); + log.sendStepLog(StepType.INFO, "", "文本获取结果: " + s); + } catch (Exception e) { + handleDes.setE(e); + } + return s; + } + + public void hideKey(HandleDes handleDes) { + handleDes.setStepDes("隐藏键盘"); + handleDes.setDetail("隐藏弹出键盘"); + try { + iosDriver.hideKeyboard(); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void click(HandleDes handleDes, String des, String selector, String pathValue) { + handleDes.setStepDes("点击" + des); + handleDes.setDetail("点击" + selector + ": " + pathValue); + try { + findEle(selector, pathValue).click(); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void sendKeys(HandleDes handleDes, String des, String selector, String pathValue, String keys) { + if (keys.contains("{{random}}")) { + String random = (int) (Math.random() * 10 + Math.random() * 10 * 2) + 5 + ""; + keys = keys.replace("{{random}}", random); + } + if (keys.contains("{{timestamp}}")) { + String timeMillis = Calendar.getInstance().getTimeInMillis() + ""; + keys = keys.replace("{{timestamp}}", timeMillis); + } + keys = replaceTrans(keys); + handleDes.setStepDes("对" + des + "输入内容"); + handleDes.setDetail("对" + selector + ": " + pathValue + " 输入: " + keys); + try { + findEle(selector, pathValue).sendKeys(keys); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void getTextAndAssert(HandleDes handleDes, String des, String selector, String pathValue, String expect) { + handleDes.setStepDes("获取" + des + "文本"); + handleDes.setDetail("获取" + selector + ":" + pathValue + "文本"); + try { + String s = findEle(selector, pathValue).getText(); + log.sendStepLog(StepType.INFO, "", "文本获取结果: " + s); + try { + expect = replaceTrans(expect); + assertEquals(s, expect); + log.sendStepLog(StepType.INFO, "验证文本", "真实值: " + s + " 期望值: " + expect); + } catch (AssertionError e) { + log.sendStepLog(StepType.ERROR, "验证" + des + "文本失败!", ""); + handleDes.setE(e); + } + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void longPressPoint(HandleDes handleDes, String des, String xy, int time) { + int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); + int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); + handleDes.setStepDes("长按" + des); + handleDes.setDetail("长按坐标" + time + "毫秒 (" + x + "," + y + ")"); + try { + TouchAction ta = new TouchAction(iosDriver); + ta.longPress(PointOption.point(x, y)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(time))).release().perform(); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void keyCode(HandleDes handleDes, String key) { + handleDes.setStepDes("按系统按键" + key + "键"); + try { + iosDriver.executeScript("mobile:pressButton", JSON.parse("{name: \"" + key + "\"}")); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void multiAction(HandleDes handleDes, String des1, String xy1, String des2, String xy2, String des3, String xy3, String des4, String xy4) { + int x1 = Integer.parseInt(xy1.substring(0, xy1.indexOf(","))); + int y1 = Integer.parseInt(xy1.substring(xy1.indexOf(",") + 1)); + int x2 = Integer.parseInt(xy2.substring(0, xy2.indexOf(","))); + int y2 = Integer.parseInt(xy2.substring(xy2.indexOf(",") + 1)); + int x3 = Integer.parseInt(xy3.substring(0, xy3.indexOf(","))); + int y3 = Integer.parseInt(xy3.substring(xy3.indexOf(",") + 1)); + int x4 = Integer.parseInt(xy4.substring(0, xy4.indexOf(","))); + int y4 = Integer.parseInt(xy4.substring(xy4.indexOf(",") + 1)); + String detail = "坐标" + des1 + "( " + x1 + ", " + y1 + " )移动到坐标" + des2 + "( " + x2 + ", " + y2 + " ),同时坐标" + des3 + "( " + x3 + ", " + y3 + " )移动到坐标" + des4 + "( " + x4 + ", " + y4 + " )"; + handleDes.setStepDes("双指操作"); + handleDes.setDetail(detail); + try { + TouchAction hand1 = new TouchAction(iosDriver); + TouchAction hand2 = new TouchAction(iosDriver); + MultiTouchAction multiTouchAction = new MultiTouchAction(iosDriver); + hand1.press(PointOption.point(x1, y1)).moveTo(PointOption.point(x2, y2)).release(); + hand2.press(PointOption.point(x3, y3)).moveTo(PointOption.point(x4, y4)).release(); + multiTouchAction.add(hand1); + multiTouchAction.add(hand2); + multiTouchAction.perform(); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void tap(HandleDes handleDes, String des, String xy) { + int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); + int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); + handleDes.setStepDes("点击" + des); + handleDes.setDetail("点击坐标(" + x + "," + y + ")"); + try { + TouchAction ta = new TouchAction(iosDriver); + ta.tap(PointOption.point(x, y)).perform(); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void swipe(HandleDes handleDes, String des1, String xy1, String des2, String xy2) { + int x1 = Integer.parseInt(xy1.substring(0, xy1.indexOf(","))); + int y1 = Integer.parseInt(xy1.substring(xy1.indexOf(",") + 1)); + int x2 = Integer.parseInt(xy2.substring(0, xy2.indexOf(","))); + int y2 = Integer.parseInt(xy2.substring(xy2.indexOf(",") + 1)); + handleDes.setStepDes("滑动拖拽" + des1 + "到" + des2); + handleDes.setDetail("拖动坐标(" + x1 + "," + y1 + ")到(" + x2 + "," + y2 + ")"); + try { + TouchAction ta = new TouchAction(iosDriver); + ta.press(PointOption.point(x1, y1)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(300))).moveTo(PointOption.point(x2, y2)).release().perform(); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void longPress(HandleDes handleDes, String des, String selector, String pathValue, int time) { + handleDes.setStepDes("长按" + des); + handleDes.setDetail("长按控件元素" + time + "毫秒 "); + try { + TouchAction ta = new TouchAction(iosDriver); + WebElement webElement = findEle(selector, pathValue); + int x = webElement.getLocation().getX(); + int y = webElement.getLocation().getY(); + Duration duration = Duration.ofMillis(time); + ta.longPress(PointOption.point(x, y)).waitAction(WaitOptions.waitOptions(duration)).release().perform(); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void clear(HandleDes handleDes, String des, String selector, String pathValue) { + handleDes.setStepDes("清空" + des); + handleDes.setDetail("清空" + selector + ": " + pathValue); + try { + findEle(selector, pathValue).clear(); + } catch (Exception e) { + handleDes.setE(e); + } + } + + public void getTitle(HandleDes handleDes, String expect) { + String title = iosDriver.getTitle(); + handleDes.setStepDes("验证网页标题"); + handleDes.setDetail("标题:" + title + ",期望值:" + expect); + try { + assertEquals(title, expect); + } catch (AssertionError e) { + handleDes.setE(e); + } + } + + public void clickByImg(HandleDes handleDes, String des, String pathValue) throws Exception { + handleDes.setStepDes("点击图片" + des); + handleDes.setDetail(pathValue); + File file = null; + if (pathValue.startsWith("http")) { + try { + file = DownImageTool.download(pathValue); + } catch (Exception e) { + handleDes.setE(e); + return; + } + } + FindResult findResult = null; + try { + SIFTFinder siftFinder = new SIFTFinder(); + findResult = siftFinder.getSIFTFindResult(file, getScreenToLocal()); + } catch (Exception e) { + log.sendStepLog(StepType.WARN, "SIFT图像算法出错,切换算法中...", + ""); + } + if (findResult != null) { + log.sendStepLog(StepType.INFO, "图片定位到坐标:(" + findResult.getX() + "," + findResult.getY() + ") 耗时:" + findResult.getTime() + " ms", + findResult.getUrl()); + } else { + log.sendStepLog(StepType.INFO, "SIFT算法无法定位图片,切换AKAZE算法中...", + ""); + try { + AKAZEFinder akazeFinder = new AKAZEFinder(); + findResult = akazeFinder.getAKAZEFindResult(file, getScreenToLocal()); + } catch (Exception e) { + log.sendStepLog(StepType.WARN, "AKAZE图像算法出错,切换模版匹配算法中...", + ""); + } + if (findResult != null) { + log.sendStepLog(StepType.INFO, "图片定位到坐标:(" + findResult.getX() + "," + findResult.getY() + ") 耗时:" + findResult.getTime() + " ms", + findResult.getUrl()); + } else { + log.sendStepLog(StepType.INFO, "AKAZE算法无法定位图片,切换模版匹配算法中...", + ""); + try { + TemMatcher temMatcher = new TemMatcher(); + findResult = temMatcher.getTemMatchResult(file, getScreenToLocal()); + } catch (Exception e) { + log.sendStepLog(StepType.WARN, "模版匹配算法出错", + ""); + } + if (findResult != null) { + log.sendStepLog(StepType.INFO, "图片定位到坐标:(" + findResult.getX() + "," + findResult.getY() + ") 耗时:" + findResult.getTime() + " ms", + findResult.getUrl()); + } else { + handleDes.setE(new Exception("图片定位失败!")); + } + } + } + if (findResult != null) { + try { + TouchAction ta = new TouchAction(iosDriver); + ta.tap(PointOption.point(findResult.getX(), findResult.getY())).perform(); + } catch (Exception e) { + log.sendStepLog(StepType.ERROR, "点击" + des + "失败!", ""); + handleDes.setE(e); + } + } + } + + + public void readText(HandleDes handleDes, String language, String text) throws Exception { + TextReader textReader = new TextReader(); + String result = textReader.getTessResult(getScreenToLocal(), language); + log.sendStepLog(StepType.INFO, "", + "图像文字识别结果:
" + result); + String filter = result.replaceAll(" ", ""); + handleDes.setStepDes("图像文字识别"); + handleDes.setDetail("期望包含文本:" + text); + if (!filter.contains(text)) { + handleDes.setE(new Exception("图像文字识别不通过!")); + } + } + + public File getScreenToLocal() { + File file = ((TakesScreenshot) iosDriver).getScreenshotAs(OutputType.FILE); + File resultFile = new File("test-output/" + log.udId + Calendar.getInstance().getTimeInMillis() + ".jpg"); + try { + FileCopyUtils.copy(file, resultFile); + } catch (IOException e) { + e.printStackTrace(); + } + return resultFile; + } + + public String replaceTrans(String text) { + if (text.contains("{{") && text.contains("}}")) { + String tail = text.substring(text.indexOf("{{") + 2); + if (tail.contains("}}")) { + String child = tail.substring(tail.indexOf("}}") + 2); + String middle = tail.substring(0, tail.indexOf("}}")); + text = text.substring(0, text.indexOf("}}") + 2); + if (globalParams.getString(middle) != null) { + text = text.replace("{{" + middle + "}}", globalParams.getString(middle)); + } + text = text + replaceTrans(child); + } + } + return text; + } + + public void checkImage(HandleDes handleDes, String des, String pathValue, double matchThreshold) throws Exception { + log.sendStepLog(StepType.INFO, "开始检测" + des + "兼容", "检测与当前设备截图相似度,期望相似度为" + matchThreshold + "%"); + File file = null; + if (pathValue.startsWith("http")) { + file = DownImageTool.download(pathValue); + } + double score = SimilarityChecker.getSimilarMSSIMScore(file, getScreenToLocal(), true); + handleDes.setStepDes("检测" + des + "图片相似度"); + handleDes.setDetail("相似度为" + score * 100 + "%"); + if (score == 0) { + handleDes.setE(new Exception("图片相似度检测不通过!比对图片分辨率不一致!")); + } else if (score < (matchThreshold / 100)) { + handleDes.setE(new Exception("图片相似度检测不通过!expect " + matchThreshold + " but " + score * 100)); + } + } + public void siriCommand(HandleDes handleDes, String command) { handleDes.setStepDes("siri指令"); handleDes.setDetail("对siri发送指令: " + command); @@ -209,6 +687,20 @@ public void siriCommand(HandleDes handleDes, String command) { } } + public void exceptionLog(Throwable e) { + log.sendStepLog(StepType.WARN, "", "异常信息: " + e.fillInStackTrace().toString()); + } + + public void errorScreen() { + try { + iosDriver.context("NATIVE_APP");//先切换回app + log.sendStepLog(StepType.WARN, "获取异常截图", UploadTools + .upload(((TakesScreenshot) iosDriver).getScreenshotAs(OutputType.FILE), "imageFiles")); + } catch (Exception e) { + log.sendStepLog(StepType.ERROR, "捕获截图失败", ""); + } + } + public String stepScreen(HandleDes handleDes) { handleDes.setStepDes("获取截图"); String url = ""; @@ -222,6 +714,30 @@ public String stepScreen(HandleDes handleDes) { return url; } + public void pause(HandleDes handleDes, int time) { + handleDes.setStepDes("强制等待"); + handleDes.setDetail("等待" + time + " ms"); + try { + Thread.sleep(time); + } catch (InterruptedException e) { + handleDes.setE(e); + } + } + + public void publicStep(HandleDes handleDes, String name, JSONArray stepArray) { + handleDes.setStepDes("执行公共步骤 " + name); + log.sendStepLog(StepType.WARN, "公共步骤 " + name + " 开始执行", ""); + for (Object publicStep : stepArray) { + JSONObject stepDetail = (JSONObject) publicStep; + try { + runStep(stepDetail); + } catch (Throwable e) { + handleDes.setE(e); + break; + } + } + } + public WebElement findEle(String selector, String pathValue) { WebElement we = null; switch (selector) { @@ -261,4 +777,140 @@ public WebElement findEle(String selector, String pathValue) { } return we; } + + public void runStep(JSONObject stepJSON) throws Throwable { + JSONObject step = stepJSON.getJSONObject("step"); + JSONArray eleList = step.getJSONArray("elements"); + HandleDes handleDes = new HandleDes(); + switch (step.getString("stepType")) { + case "siriCommand": + siriCommand(handleDes, step.getString("content")); + break; + case "readText": + readText(handleDes, step.getString("content"), step.getString("text")); + break; + case "clickByImg": + clickByImg(handleDes, eleList.getJSONObject(0).getString("eleName") + , eleList.getJSONObject(0).getString("eleValue")); + break; + case "click": + click(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleType") + , eleList.getJSONObject(0).getString("eleValue")); + break; + case "getTitle": + getTitle(handleDes, step.getString("content")); + break; + case "sendKeys": + sendKeys(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleType") + , eleList.getJSONObject(0).getString("eleValue"), step.getString("content")); + break; + case "getText": + getTextAndAssert(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleType") + , eleList.getJSONObject(0).getString("eleValue"), step.getString("content")); + break; + case "clear": + clear(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleType") + , eleList.getJSONObject(0).getString("eleValue")); + break; + case "longPress": + longPress(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleType") + , eleList.getJSONObject(0).getString("eleValue"), Integer.parseInt(step.getString("content"))); + break; + case "swipe": + swipe(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleValue") + , eleList.getJSONObject(1).getString("eleName"), eleList.getJSONObject(1).getString("eleValue")); + break; + case "tap": + tap(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleValue")); + break; + case "longPressPoint": + longPressPoint(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleValue") + , Integer.parseInt(step.getString("content"))); + break; + case "pause": + pause(handleDes, Integer.parseInt(step.getString("content"))); + break; + case "checkImage": + checkImage(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleValue") + , step.getDouble("content")); + break; + case "stepScreen": + stepScreen(handleDes); + break; + case "openApp": + openApp(handleDes, step.getString("text")); + break; + case "terminate": + terminate(handleDes, step.getString("text")); + break; + case "install": + install(handleDes, step.getString("text")); + break; + case "uninstall": + uninstall(handleDes, step.getString("text")); + break; + case "runBack": + runBackground(handleDes, Long.parseLong(step.getString("content"))); + break; + case "lock": + lock(handleDes); + break; + case "unLock": + unLock(handleDes); + break; + case "zoom": + multiAction(handleDes, eleList.getJSONObject(0).getString("eleName"), eleList.getJSONObject(0).getString("eleValue") + , eleList.getJSONObject(1).getString("eleName"), eleList.getJSONObject(1).getString("eleValue") + , eleList.getJSONObject(2).getString("eleName"), eleList.getJSONObject(2).getString("eleValue") + , eleList.getJSONObject(3).getString("eleName"), eleList.getJSONObject(3).getString("eleValue")); + break; + case "keyCode": + keyCode(handleDes, step.getString("content")); + break; + case "assertEquals": + case "assertTrue": + case "assertNotTrue": + String actual = replaceTrans(step.getString("text")); + String expect = replaceTrans(step.getString("content")); + asserts(handleDes, actual, expect, step.getString("stepType")); + break; + case "getTextValue": + globalParams.put(step.getString("content"), getText(handleDes, eleList.getJSONObject(0).getString("eleName") + , eleList.getJSONObject(0).getString("eleType"), eleList.getJSONObject(0).getString("eleValue"))); + break; + case "hideKey": + hideKey(handleDes); + break; +// case "monkey": +// runMonkey(handleDes, step.getJSONObject("content"), step.getJSONArray("text").toJavaList(JSONObject.class)); +// break; + case "publicStep": + publicStep(handleDes, step.getString("content"), stepJSON.getJSONArray("pubSteps")); + } + switchType(step.getInteger("error"), handleDes.getStepDes(), handleDes.getDetail(), handleDes.getE()); + } + + public void switchType(int error, String step, String detail, Throwable e) throws Throwable { + if (e != null) { + switch (error) { + case ErrorType.IGNORE: + log.sendStepLog(StepType.PASS, step + "异常!已忽略...", detail); + break; + case ErrorType.WARNING: + log.sendStepLog(StepType.WARN, step + "异常!", detail); + setResultDetailStatus(ResultDetailStatus.WARN); + errorScreen(); + exceptionLog(e); + break; + case ErrorType.SHUTDOWN: + log.sendStepLog(StepType.ERROR, step + "异常!", detail); + setResultDetailStatus(ResultDetailStatus.FAIL); + errorScreen(); + exceptionLog(e); + throw e; + } + } else { + log.sendStepLog(StepType.PASS, step, detail); + } + } } diff --git a/src/main/java/com/sonic/agent/bridge/android/AndroidDeviceBridgeTool.java b/src/main/java/com/sonic/agent/bridge/android/AndroidDeviceBridgeTool.java index a95e2cb4..068aa9ca 100644 --- a/src/main/java/com/sonic/agent/bridge/android/AndroidDeviceBridgeTool.java +++ b/src/main/java/com/sonic/agent/bridge/android/AndroidDeviceBridgeTool.java @@ -1,6 +1,7 @@ package com.sonic.agent.bridge.android; import com.android.ddmlib.*; +import com.sonic.agent.tests.android.AndroidTemperThread; import com.sonic.agent.tools.DownImageTool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +27,7 @@ @Component public class AndroidDeviceBridgeTool implements ApplicationListener { private static final Logger logger = LoggerFactory.getLogger(AndroidDeviceBridgeTool.class); - private static AndroidDebugBridge androidDebugBridge = null; + public static AndroidDebugBridge androidDebugBridge = null; @Autowired private AndroidDeviceStatusListener androidDeviceStatusListener; @@ -85,6 +86,7 @@ public void init() { break; } } + new AndroidTemperThread().start(); } /** @@ -164,7 +166,7 @@ public static String getScreenSize(IDevice iDevice) { size = "未知"; } } catch (Exception e) { - logger.info("获取屏幕尺寸失败!"); + logger.info("获取屏幕尺寸失败!拔插瞬间可忽略该错误..."); } return size; } @@ -366,6 +368,15 @@ public static void searchDevice(IDevice iDevice) { executeCommand(iDevice, "am start -n com.sonic.plugins.assist/com.sonic.plugins.assist.SearchActivity"); } + public static void controlBattery(IDevice iDevice, int type) { + if (type == 0) { + executeCommand(iDevice, "dumpsys battery unplug && dumpsys battery set status 1"); + } + if (type == 1) { + executeCommand(iDevice, "dumpsys battery reset"); + } + } + /** * @param udId * @param packageName diff --git a/src/main/java/com/sonic/agent/bridge/ios/IOSDeviceLocalStatus.java b/src/main/java/com/sonic/agent/bridge/ios/IOSDeviceLocalStatus.java index c2c0fca6..960daeeb 100644 --- a/src/main/java/com/sonic/agent/bridge/ios/IOSDeviceLocalStatus.java +++ b/src/main/java/com/sonic/agent/bridge/ios/IOSDeviceLocalStatus.java @@ -3,6 +3,7 @@ import com.alibaba.fastjson.JSONObject; import com.sonic.agent.interfaces.DeviceStatus; import com.sonic.agent.maps.IOSDeviceManagerMap; +import com.sonic.agent.maps.IOSSizeMap; import com.sonic.agent.netty.NettyThreadPool; public class IOSDeviceLocalStatus { @@ -10,6 +11,7 @@ public class IOSDeviceLocalStatus { public static void send(String udId, String status) { JSONObject deviceDetail = new JSONObject(); deviceDetail.put("msg", "deviceDetail"); + deviceDetail.put("size", IOSSizeMap.getMap().get(udId)); deviceDetail.put("udId", udId); deviceDetail.put("status", status); NettyThreadPool.send(deviceDetail); diff --git a/src/main/java/com/sonic/agent/bridge/ios/TIDeviceTool.java b/src/main/java/com/sonic/agent/bridge/ios/TIDeviceTool.java index a89f48b3..c6fbd876 100644 --- a/src/main/java/com/sonic/agent/bridge/ios/TIDeviceTool.java +++ b/src/main/java/com/sonic/agent/bridge/ios/TIDeviceTool.java @@ -4,6 +4,7 @@ import com.sonic.agent.interfaces.PlatformType; import com.sonic.agent.maps.IOSDeviceManagerMap; import com.sonic.agent.maps.IOSProcessMap; +import com.sonic.agent.maps.IOSSizeMap; import com.sonic.agent.netty.NettyThreadPool; import com.sonic.agent.tools.PortTool; import com.sonic.agent.tools.ProcessCommandTool; @@ -90,6 +91,7 @@ public static void sendDisConnectStatus(String udId) { deviceStatus.put("msg", "deviceDetail"); deviceStatus.put("udId", udId); deviceStatus.put("status", "DISCONNECTED"); + deviceStatus.put("size", IOSSizeMap.getMap().get(udId)); logger.info("iOS设备:" + udId + " 下线!"); NettyThreadPool.send(deviceStatus); IOSDeviceManagerMap.getMap().remove(udId); @@ -105,7 +107,7 @@ public static void sendOnlineStatus(String udId) { deviceStatus.put("status", "ONLINE"); deviceStatus.put("platform", PlatformType.IOS); deviceStatus.put("version", info.getString("version")); - deviceStatus.put("size", "未知"); + deviceStatus.put("size", IOSSizeMap.getMap().get(udId)); deviceStatus.put("cpu", info.getString("cpu")); deviceStatus.put("manufacturer", "APPLE"); logger.info("iOS设备:" + udId + " 上线!"); @@ -158,27 +160,29 @@ public static int startWda(String udId) throws IOException, InterruptedException } } int port = PortTool.getPort(); - Process wdaProcess; + Process wdaProcess = null; String commandLine = "tidevice -u " + udId + " wdaproxy" + " -B " + bundleId + " --port " + port; - if (System.getProperty("os.name").contains("Mac")) { - wdaProcess = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", commandLine}); - } else { - wdaProcess = Runtime.getRuntime().exec(new String[]{"cmd", "/C", commandLine}); + String system = System.getProperty("os.name").toLowerCase(); + if (system.contains("win")) { + wdaProcess = Runtime.getRuntime().exec(new String[]{"cmd", "/c", commandLine}); + } else if (system.contains("linux") || system.contains("mac")) { + wdaProcess = Runtime.getRuntime().exec(new String[]{"sh", "-c", commandLine}); + } + BufferedReader stdInput = new BufferedReader(new + InputStreamReader(wdaProcess.getInputStream())); + String s; + while (wdaProcess.isAlive()) { + if ((s = stdInput.readLine()) != null) { + if (s.contains("WebDriverAgent start successfully")) { + break; + } + } else { + Thread.sleep(500); + } + logger.info(s); } -// BufferedReader stdInput = new BufferedReader(new -// InputStreamReader(wdaProcess.getInputStream())); -// String s; -// while ((s = stdInput.readLine()) != null) { -// if (s.contains("WebDriverAgent start successfully")) { -// logger.info(udId + " wda启动完毕!"); -// break; -// } else { -// Thread.sleep(500); -// } -// } - Thread.sleep(3000); processList = new ArrayList<>(); processList.add(wdaProcess); IOSProcessMap.getMap().put(udId, processList); @@ -186,15 +190,16 @@ public static int startWda(String udId) throws IOException, InterruptedException } } - public static int relayImg(String udId) throws IOException { + public static int relayImg(String udId) throws IOException, InterruptedException { int port = PortTool.getPort(); - Process relayProcess; + Process relayProcess = null; String commandLine = "tidevice -u " + udId + " relay " + port + " " + 9100; - if (System.getProperty("os.name").contains("Mac")) { - relayProcess = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", commandLine}); - } else { - relayProcess = Runtime.getRuntime().exec(new String[]{"cmd", "/C", commandLine}); + String system = System.getProperty("os.name").toLowerCase(); + if (system.contains("win")) { + relayProcess = Runtime.getRuntime().exec(new String[]{"cmd", "/c", commandLine}); + } else if (system.contains("linux") || system.contains("mac")) { + relayProcess = Runtime.getRuntime().exec(new String[]{"sh", "-c", commandLine}); } List processList; if (IOSProcessMap.getMap().get(udId) != null) { @@ -204,6 +209,7 @@ public static int relayImg(String udId) throws IOException { } processList.add(relayProcess); IOSProcessMap.getMap().put(udId, processList); + Thread.sleep(1000); return port; } diff --git a/src/main/java/com/sonic/agent/maps/DevicesLockMap.java b/src/main/java/com/sonic/agent/maps/DevicesLockMap.java new file mode 100644 index 00000000..6b1de9bb --- /dev/null +++ b/src/main/java/com/sonic/agent/maps/DevicesLockMap.java @@ -0,0 +1,102 @@ +package com.sonic.agent.maps; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.Assert; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +/** + * 设备逻辑锁 Map + * + * @author JayWenStar + * @date 2021/12/19 19:51 下午 + */ +public class DevicesLockMap { + + private final static Logger logger = LoggerFactory.getLogger(DevicesLockMap.class); + + /** + * key: udId(设备序列号) value: Semaphore + */ + private static Map devicesLockMap = new ConcurrentHashMap<>(); + + /**xxx + * 用设备序列号上锁(无) + * + * @see #lockByUdId(String, Long, TimeUnit) + */ + public static boolean lockByUdId(String udId) throws InterruptedException { + return lockByUdId(udId, null, null); + } + + /** + * 用设备序列号上锁(可设置超时) + * + * @param udId 设备序列号 + * @param timeOut 获取锁超时时间 此参数必须和 timeUnit 同时存在 + * @param timeUnit 时间单位 此参数必须和 timeOut 同时存在 + * @return true: 上锁成功 false: 上锁失败 + */ + public static boolean lockByUdId(String udId, Long timeOut, TimeUnit timeUnit) throws InterruptedException { + // 校验参数 + Assert.hasText(udId, "udId must not be blank"); + if (timeOut != null || timeUnit != null) { + Assert.isTrue( + timeOut != null && timeUnit != null, + "timeOut and timeUnit must not be null at the same time" + ); + } + Semaphore deviceLock; + + // 原子操作,避免put后被别的线程remove造成当前线程lock失效 + synchronized (WebSocketSessionMap.class) { + Semaphore res = devicesLockMap.get(udId); + if (res == null) { + deviceLock = new Semaphore(1); + devicesLockMap.put(udId, deviceLock); + } else { + deviceLock = res; + } + } + + // 无超时获取锁逻辑,直接锁并返回true + if (timeOut == null) { + deviceLock.acquire(); + return true; + } + + // 如果有超时,则成功返回true,超时返回false + try { + return deviceLock.tryAcquire(timeOut, timeUnit); + } catch (InterruptedException e) { + logger.error("获取锁被中断,返回false"); + return false; + } + } + + /** + * 解锁并从map中移除锁 + * + * @param udId 设备序列号 + */ + public static void unlockAndRemoveByUdId(String udId) { + Assert.hasText(udId, "udId must not be blank"); + Semaphore deviceLock; + synchronized (WebSocketSessionMap.class) { + deviceLock = devicesLockMap.get(udId); + removeLockByUdId(udId); + } + if (deviceLock != null) { + deviceLock.release(); + } + } + + public static void removeLockByUdId(String udId) { + devicesLockMap.remove(udId); + } + +} diff --git a/src/main/java/com/sonic/agent/maps/IOSSizeMap.java b/src/main/java/com/sonic/agent/maps/IOSSizeMap.java new file mode 100644 index 00000000..1a89d365 --- /dev/null +++ b/src/main/java/com/sonic/agent/maps/IOSSizeMap.java @@ -0,0 +1,11 @@ +package com.sonic.agent.maps; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class IOSSizeMap { + private static Map sizeMap = new ConcurrentHashMap(); + public static Map getMap() { + return sizeMap; + } +} diff --git a/src/main/java/com/sonic/agent/maps/WebSocketSessionMap.java b/src/main/java/com/sonic/agent/maps/WebSocketSessionMap.java index 78b3a154..a9078f75 100644 --- a/src/main/java/com/sonic/agent/maps/WebSocketSessionMap.java +++ b/src/main/java/com/sonic/agent/maps/WebSocketSessionMap.java @@ -1,17 +1,45 @@ -package com.sonic.agent.maps; - -import javax.websocket.Session; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author ZhouYiXun - * @des webSocket的sessionId与session储存 - * @date 2021/8/16 19:54 - */ -public class WebSocketSessionMap { - private static Map sessionMap = new ConcurrentHashMap(); - public static Map getMap() { - return sessionMap; - } -} +package com.sonic.agent.maps; + +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; + +import javax.websocket.Session; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author ZhouYiXun + * @des webSocket的sessionId与session储存 + * @date 2021/8/16 19:54 + */ +public class WebSocketSessionMap { + + /** + * key: sessionId value: session + */ + private static Map sessionMap = new ConcurrentHashMap<>(); + + + public static Map getSessionMap() { + return sessionMap; + } + + public static void addSession(@NonNull Session session) { + sessionMap.put(session.getId(), session); + } + + public static void removeSession(@NonNull Session session) { + removeSession(session.getId()); + } + + public static void removeSession(String sessionId) { + Assert.hasText(sessionId, "sessionId must not be blank"); + sessionMap.remove(sessionId); + } + + public static Session getSession(String sessionId) { + Assert.hasText(sessionId, "sessionId must not be blank"); + return sessionMap.get(sessionId); + } + +} diff --git a/src/main/java/com/sonic/agent/netty/NettyClient.java b/src/main/java/com/sonic/agent/netty/NettyClient.java index f168e79b..2e216124 100644 --- a/src/main/java/com/sonic/agent/netty/NettyClient.java +++ b/src/main/java/com/sonic/agent/netty/NettyClient.java @@ -5,6 +5,7 @@ import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -35,7 +36,9 @@ public class NettyClient implements ApplicationRunner { @Override public void run(ApplicationArguments args) { - group = new NioEventLoopGroup(); + // todo 根据 https://netty.io/wiki/native-transports.html 可针对不同系统进行优化,server端的netty同理 + DefaultThreadFactory factory = new DefaultThreadFactory("AgentNettyClient", true); + group = new NioEventLoopGroup(factory); bootstrap = new Bootstrap() .group(group) .option(ChannelOption.TCP_NODELAY, true) diff --git a/src/main/java/com/sonic/agent/netty/NettyClientHandler.java b/src/main/java/com/sonic/agent/netty/NettyClientHandler.java index f91fcc02..5d483705 100644 --- a/src/main/java/com/sonic/agent/netty/NettyClientHandler.java +++ b/src/main/java/com/sonic/agent/netty/NettyClientHandler.java @@ -5,6 +5,7 @@ import com.alibaba.fastjson.JSONObject; import com.android.ddmlib.IDevice; import com.sonic.agent.automation.AndroidStepHandler; +import com.sonic.agent.automation.IOSStepHandler; import com.sonic.agent.bridge.android.AndroidDeviceBridgeTool; import com.sonic.agent.bridge.ios.TIDeviceTool; import com.sonic.agent.interfaces.DeviceStatus; @@ -13,6 +14,7 @@ import com.sonic.agent.maps.AndroidPasswordMap; import com.sonic.agent.maps.HandlerMap; import com.sonic.agent.tests.AndroidTests; +import com.sonic.agent.tests.IOSTests; import com.sonic.agent.tests.TaskManager; import com.sonic.agent.tools.SpringTool; import io.netty.channel.Channel; @@ -87,6 +89,20 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception } androidStepHandler.sendStatus(); } + if (jsonObject.getInteger("pf") == PlatformType.IOS) { + IOSStepHandler iosStepHandler = HandlerMap.getIOSMap().get(jsonObject.getString("sessionId")); + iosStepHandler.resetResultDetailStatus(); + iosStepHandler.setGlobalParams(jsonObject.getJSONObject("gp")); + List steps = jsonObject.getJSONArray("steps").toJavaList(JSONObject.class); + for (JSONObject step : steps) { + try { + iosStepHandler.runStep(step); + } catch (Throwable e) { + break; + } + } + iosStepHandler.sendStatus(); + } break; case "suite": List cases = jsonObject.getJSONArray("cases").toJavaList(JSONObject.class); @@ -99,7 +115,12 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception parameters.put("dataInfo", dataInfo.toJSONString()); xmlTest.setParameters(parameters); List classes = new ArrayList<>(); - classes.add(new XmlClass(AndroidTests.class)); + if (jsonObject.getInteger("pf") == PlatformType.ANDROID) { + classes.add(new XmlClass(AndroidTests.class)); + } + if (jsonObject.getInteger("pf") == PlatformType.IOS) { + classes.add(new XmlClass(IOSTests.class)); + } xmlTest.setXmlClasses(classes); } suiteList.add(xmlSuite); @@ -115,7 +136,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception List deviceList = devices.toJavaList(JSONObject.class); for (JSONObject device : deviceList) { String udId = (String) device.get("udId"); - TaskManager.forceStopSuite(resultId, caseId, udId); + TaskManager.forceStopSuite(jsonObject.getInteger("pf"), resultId, caseId, udId); } } break; @@ -126,7 +147,6 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { logger.info("服务器: {} 发生异常 {}", ctx.channel().remoteAddress(), cause.fillInStackTrace()); - ctx.close(); } @Override diff --git a/src/main/java/com/sonic/agent/tests/IOSTests.java b/src/main/java/com/sonic/agent/tests/IOSTests.java index 59cf3b8c..01415c56 100644 --- a/src/main/java/com/sonic/agent/tests/IOSTests.java +++ b/src/main/java/com/sonic/agent/tests/IOSTests.java @@ -1,9 +1,65 @@ package com.sonic.agent.tests; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.sonic.agent.automation.IOSStepHandler; +import com.sonic.agent.bridge.ios.TIDeviceTool; +import com.sonic.agent.interfaces.DeviceStatus; +import com.sonic.agent.tests.ios.IOSTestTaskBootThread; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.ITestContext; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + /** * @author ZhouYiXun * @des iOS测试执行类 * @date 2021/8/25 20:51 */ public class IOSTests { + private final Logger logger = LoggerFactory.getLogger(IOSTests.class); + + @DataProvider(name = "testData", parallel = true) + public Object[][] getTestData(ITestContext context) { + JSONObject dataInfo = JSON.parseObject(context.getCurrentXmlTest().getParameter("dataInfo")); + List dataProvider = new ArrayList<>(); + for (JSONObject device : dataInfo.getJSONArray("device").toJavaList(JSONObject.class)) { + String udId = device.getString("udId"); + if (!TIDeviceTool.getDeviceList().contains(udId)) { + continue; + } + JSONObject deviceTestData = new JSONObject(); + deviceTestData.put("steps", dataInfo.getJSONArray("steps")); + deviceTestData.put("rid", dataInfo.getInteger("rid")); + deviceTestData.put("cid", dataInfo.getInteger("cid")); + deviceTestData.put("gp", dataInfo.getJSONObject("gp")); + deviceTestData.put("device", device); + dataProvider.add(deviceTestData); + } + Object[][] testDataProvider = new Object[dataProvider.size()][]; + for (int i = 0; i < dataProvider.size(); i++) { + testDataProvider[i] = new Object[]{dataProvider.get(i)}; + } + return testDataProvider; + } + + @Test(dataProvider = "testData") + public void run(JSONObject jsonObject) throws IOException { + IOSStepHandler iosStepHandler = new IOSStepHandler(); + int rid = jsonObject.getInteger("rid"); + int cid = jsonObject.getInteger("cid"); + String udId = jsonObject.getJSONObject("device").getString("udId"); + JSONObject gp = jsonObject.getJSONObject("gp"); + iosStepHandler.setGlobalParams(gp); + iosStepHandler.setTestMode(cid, rid, udId, DeviceStatus.TESTING, ""); + + // 启动任务 + IOSTestTaskBootThread bootThread = new IOSTestTaskBootThread(jsonObject, iosStepHandler); + TaskManager.startBootThread(bootThread); + } } diff --git a/src/main/java/com/sonic/agent/tests/TaskManager.java b/src/main/java/com/sonic/agent/tests/TaskManager.java index 135141c8..987a02b3 100644 --- a/src/main/java/com/sonic/agent/tests/TaskManager.java +++ b/src/main/java/com/sonic/agent/tests/TaskManager.java @@ -1,6 +1,8 @@ package com.sonic.agent.tests; +import com.sonic.agent.interfaces.PlatformType; import com.sonic.agent.tests.android.AndroidTestTaskBootThread; +import com.sonic.agent.tests.ios.IOSTestTaskBootThread; import org.springframework.util.CollectionUtils; import java.util.HashSet; @@ -45,7 +47,7 @@ public static void startBootThread(Thread bootThread) { * * @param bootThreads boot线程 */ - public static void startBootThread(Thread...bootThreads) { + public static void startBootThread(Thread... bootThreads) { for (Thread bootThread : bootThreads) { startBootThread(bootThread); } @@ -62,7 +64,7 @@ public static void startChildThread(String key, Thread childThread) { /** * 启动子线程(批量) */ - public static void startChildThread(String key, Thread...childThreads) { + public static void startChildThread(String key, Thread... childThreads) { for (Thread childThread : childThreads) { startChildThread(key, childThread); } @@ -72,8 +74,8 @@ public static void startChildThread(String key, Thread...childThreads) { /** * 添加boot线程 * - * @param key 用boot线程名作为key - * @param bootThread boot线程 + * @param key 用boot线程名作为key + * @param bootThread boot线程 */ public static void addBootThread(String key, Thread bootThread) { clearTerminatedThread(); @@ -153,12 +155,20 @@ public static void clearTerminatedThread() { } /** - * 按照设备序列号强制停止手机正在执行的任务 + * 按照结果id、用例id、设备序列号强制停止手机正在执行的任务 * - * @param udid 设备序列号 + * @param resultId 结果id + * @param caseId 用例id + * @param udId 设备序列号 */ - public static void forceStopSuite(int resultId, int caseId, String udid) { - String key = String.format(AndroidTestTaskBootThread.ANDROID_TEST_TASK_BOOT_PRE, resultId, caseId, udid); + public static void forceStopSuite(int platform, int resultId, int caseId, String udId) { + String key = ""; + if (platform == PlatformType.ANDROID) { + key = String.format(AndroidTestTaskBootThread.ANDROID_TEST_TASK_BOOT_PRE, resultId, caseId, udId); + } + if (platform == PlatformType.IOS) { + key = String.format(IOSTestTaskBootThread.IOS_TEST_TASK_BOOT_PRE, resultId, caseId, udId); + } // 停止boot线程 Thread bootThread = bootThreadsMap.get(key); if (bootThread != null) { diff --git a/src/main/java/com/sonic/agent/tests/android/AndroidRecordThread.java b/src/main/java/com/sonic/agent/tests/android/AndroidRecordThread.java index 5cd33da6..3eae38bb 100644 --- a/src/main/java/com/sonic/agent/tests/android/AndroidRecordThread.java +++ b/src/main/java/com/sonic/agent/tests/android/AndroidRecordThread.java @@ -90,7 +90,7 @@ public void run() { //处理录像 if (isSupportRecord) { if (androidStepHandler.getStatus() == 3) { - androidStepHandler.stopRecord(udId); + androidStepHandler.stopRecord(); return; } else { androidStepHandler.getAndroidDriver().stopRecordingScreen(); diff --git a/src/main/java/com/sonic/agent/tests/android/AndroidTemperThread.java b/src/main/java/com/sonic/agent/tests/android/AndroidTemperThread.java new file mode 100644 index 00000000..4ed375a7 --- /dev/null +++ b/src/main/java/com/sonic/agent/tests/android/AndroidTemperThread.java @@ -0,0 +1,40 @@ +package com.sonic.agent.tests.android; + +import com.alibaba.fastjson.JSONObject; +import com.android.ddmlib.IDevice; +import com.sonic.agent.bridge.android.AndroidDeviceBridgeTool; +import com.sonic.agent.netty.NettyThreadPool; + +import java.util.ArrayList; +import java.util.List; + +public class AndroidTemperThread extends Thread { + @Override + public void run() { + while (AndroidDeviceBridgeTool.androidDebugBridge != null) { + IDevice[] deviceList = AndroidDeviceBridgeTool.getRealOnLineDevices(); + List detail = new ArrayList<>(); + for (IDevice iDevice : deviceList) { + JSONObject jsonObject = new JSONObject(); + String temper = AndroidDeviceBridgeTool + .executeCommand(iDevice, "dumpsys battery"); + if (temper != null && temper.length() > 0) { + String real = temper.substring(temper.indexOf("temperature")).trim(); + int total = Integer.parseInt(real.substring(13, real.indexOf("\n"))); + jsonObject.put("udId", iDevice.getSerialNumber()); + jsonObject.put("tem", total); + detail.add(jsonObject); + } + } + JSONObject result = new JSONObject(); + result.put("msg", "temperature"); + result.put("detail", detail); + NettyThreadPool.send(result); + try { + Thread.sleep(30000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/com/sonic/agent/tests/android/AndroidTestTaskBootThread.java b/src/main/java/com/sonic/agent/tests/android/AndroidTestTaskBootThread.java index 1d9cf8fb..f2f3d804 100644 --- a/src/main/java/com/sonic/agent/tests/android/AndroidTestTaskBootThread.java +++ b/src/main/java/com/sonic/agent/tests/android/AndroidTestTaskBootThread.java @@ -144,6 +144,9 @@ public AndroidTestTaskBootThread setUdId(String udId) { @Override public void run() { + + boolean startTestSuccess = false; + try { int wait = 0; while (!AndroidDeviceLocalStatus.startTest(udId)) { @@ -158,6 +161,7 @@ public void run() { } } + startTestSuccess = true; //启动测试 try { androidStepHandler.startAndroidDriver(udId); @@ -194,8 +198,10 @@ public void run() { log.error("任务异常,中断:{}", e.getMessage()); androidStepHandler.setResultDetailStatus(ResultDetailStatus.FAIL); } finally { - AndroidDeviceLocalStatus.finish(udId); - androidStepHandler.closeAndroidDriver(); + if (startTestSuccess) { + AndroidDeviceLocalStatus.finish(udId); + androidStepHandler.closeAndroidDriver(); + } androidStepHandler.sendStatus(); } } diff --git a/src/main/java/com/sonic/agent/tests/android/OutputSocketThread.java b/src/main/java/com/sonic/agent/tests/android/OutputSocketThread.java index bed56806..c3359ccc 100644 --- a/src/main/java/com/sonic/agent/tests/android/OutputSocketThread.java +++ b/src/main/java/com/sonic/agent/tests/android/OutputSocketThread.java @@ -70,6 +70,9 @@ public void run() { byte[] oldBytes = new byte[0]; int count = 0; while (sendImg.isAlive()) { + if (session == null || !session.isOpen()) { + return; + } Queue dataQueue = sendImg.getDataQueue(); if (dataQueue.isEmpty()) { continue; diff --git a/src/main/java/com/sonic/agent/tests/ios/IOSPerfDataThread.java b/src/main/java/com/sonic/agent/tests/ios/IOSPerfDataThread.java new file mode 100644 index 00000000..b3993396 --- /dev/null +++ b/src/main/java/com/sonic/agent/tests/ios/IOSPerfDataThread.java @@ -0,0 +1,35 @@ +package com.sonic.agent.tests.ios; + +import com.sonic.agent.automation.AndroidStepHandler; +import com.sonic.agent.tests.android.AndroidRunStepThread; +import com.sonic.agent.tests.android.AndroidTestTaskBootThread; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 暂未开放 + */ +public class IOSPerfDataThread extends Thread { + + private final Logger log = LoggerFactory.getLogger(IOSPerfDataThread.class); + + public final static String IOS_PERF_DATA_TASK_PRE = "ios-perf-data-task-%s-%s-%s"; + + private final IOSTestTaskBootThread iosTestTaskBootThread; + + public IOSPerfDataThread(IOSTestTaskBootThread iosTestTaskBootThread) { + this.iosTestTaskBootThread = iosTestTaskBootThread; + + this.setDaemon(true); + this.setName(iosTestTaskBootThread.formatThreadName(IOS_PERF_DATA_TASK_PRE)); + } + + public IOSTestTaskBootThread getIosTestTaskBootThread() { + return iosTestTaskBootThread; + } + + @Override + public void run() { + return; + } +} diff --git a/src/main/java/com/sonic/agent/tests/ios/IOSRecordThread.java b/src/main/java/com/sonic/agent/tests/ios/IOSRecordThread.java new file mode 100644 index 00000000..f6b97d2b --- /dev/null +++ b/src/main/java/com/sonic/agent/tests/ios/IOSRecordThread.java @@ -0,0 +1,77 @@ +package com.sonic.agent.tests.ios; + +import com.android.ddmlib.IDevice; +import com.sonic.agent.automation.AndroidStepHandler; +import com.sonic.agent.automation.IOSStepHandler; +import com.sonic.agent.bridge.android.AndroidDeviceBridgeTool; +import com.sonic.agent.cv.RecordHandler; +import com.sonic.agent.tests.android.AndroidRunStepThread; +import com.sonic.agent.tests.android.AndroidTestTaskBootThread; +import com.sonic.agent.tools.MiniCapTool; +import org.bytedeco.javacv.FrameRecorder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +public class IOSRecordThread extends Thread { + + private final Logger log = LoggerFactory.getLogger(IOSRecordThread.class); + + public final static String IOS_RECORD_TASK_PRE = "ios-record-task-%s-%s-%s"; + + private final IOSTestTaskBootThread iosTestTaskBootThread; + + public IOSRecordThread(IOSTestTaskBootThread iosTestTaskBootThread) { + this.iosTestTaskBootThread = iosTestTaskBootThread; + + this.setDaemon(true); + this.setName(iosTestTaskBootThread.formatThreadName(IOS_RECORD_TASK_PRE)); + } + + public IOSTestTaskBootThread getIosTestTaskBootThread() { + return iosTestTaskBootThread; + } + + @Override + public void run() { + IOSStepHandler iosStepHandler = iosTestTaskBootThread.getIosStepHandler(); + IOSRunStepThread runStepThread = iosTestTaskBootThread.getRunStepThread(); + + while (runStepThread.isAlive()) { + if (iosStepHandler.getDriver() == null) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + continue; + } + try { + iosStepHandler.startRecord(); + } catch (Exception e) { + log.error(e.getMessage()); + } + int w = 0; + while (w < 10 && (runStepThread.isAlive())) { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + log.error(e.getMessage()); + } + w++; + } + //处理录像 + if (iosStepHandler.getStatus() == 3) { + iosStepHandler.stopRecord(); + return; + } else { + iosStepHandler.getDriver().stopRecordingScreen(); + } + } + } +} diff --git a/src/main/java/com/sonic/agent/tests/ios/IOSRunStepThread.java b/src/main/java/com/sonic/agent/tests/ios/IOSRunStepThread.java new file mode 100644 index 00000000..976563f1 --- /dev/null +++ b/src/main/java/com/sonic/agent/tests/ios/IOSRunStepThread.java @@ -0,0 +1,46 @@ +package com.sonic.agent.tests.ios; + +import com.alibaba.fastjson.JSONObject; +import com.sonic.agent.automation.AndroidStepHandler; +import com.sonic.agent.automation.IOSStepHandler; +import com.sonic.agent.tests.android.AndroidTestTaskBootThread; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class IOSRunStepThread extends Thread { + + private final Logger log = LoggerFactory.getLogger(IOSRunStepThread.class); + + public final static String IOS_RUN_STEP_TASK_PRE = "ios-run-step-task-%s-%s-%s"; + + private final IOSTestTaskBootThread iosTestTaskBootThread; + + public IOSRunStepThread(IOSTestTaskBootThread iosTestTaskBootThread) { + this.iosTestTaskBootThread = iosTestTaskBootThread; + + this.setDaemon(true); + this.setName(iosTestTaskBootThread.formatThreadName(IOS_RUN_STEP_TASK_PRE)); + } + + public IOSTestTaskBootThread getIosTestTaskBootThread() { + return iosTestTaskBootThread; + } + + @Override + public void run() { + JSONObject jsonObject = iosTestTaskBootThread.getJsonObject(); + IOSStepHandler iosStepHandler = iosTestTaskBootThread.getIosStepHandler(); + List steps = jsonObject.getJSONArray("steps").toJavaList(JSONObject.class); + + for (JSONObject step : steps) { + try { + iosStepHandler.runStep(step); + } catch (Throwable e) { + break; + } + } + + } +} \ No newline at end of file diff --git a/src/main/java/com/sonic/agent/tests/ios/IOSTestTaskBootThread.java b/src/main/java/com/sonic/agent/tests/ios/IOSTestTaskBootThread.java new file mode 100644 index 00000000..5861e17e --- /dev/null +++ b/src/main/java/com/sonic/agent/tests/ios/IOSTestTaskBootThread.java @@ -0,0 +1,201 @@ +package com.sonic.agent.tests.ios; + +import com.alibaba.fastjson.JSONObject; +import com.sonic.agent.automation.IOSStepHandler; +import com.sonic.agent.bridge.ios.IOSDeviceLocalStatus; +import com.sonic.agent.bridge.ios.TIDeviceTool; +import com.sonic.agent.interfaces.ResultDetailStatus; +import com.sonic.agent.tests.TaskManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.Semaphore; + +public class IOSTestTaskBootThread extends Thread { + + private final Logger log = LoggerFactory.getLogger(IOSTestTaskBootThread.class); + + /** + * ios-test-task-boot-{resultId}-{caseId}-{udid} + */ + public final static String IOS_TEST_TASK_BOOT_PRE = "ios-test-task-boot-%s-%s-%s"; + + /** + * 控制不同线程执行的信号量 + */ + private Semaphore runStepSemaphore = new Semaphore(1); + + + /** + * 一些任务信息 + */ + private JSONObject jsonObject; + + private IOSStepHandler iosStepHandler; + + /** + * 测试步骤线程 + */ + private IOSRunStepThread runStepThread; + + /** + * 性能数据采集线程 + */ + private IOSPerfDataThread perfDataThread; + + /** + * 录像线程 + */ + private IOSRecordThread recordThread; + + /** + * 测试结果id 0表示debug线程 + */ + private int resultId = 0; + + /** + * 测试用例id 0表示debug线程 + */ + private int caseId = 0; + + /** + * 设备序列号 + */ + private String udId; + + public String formatThreadName(String baseFormat) { + return String.format(baseFormat, this.resultId, this.caseId, this.udId); + } + + /** + * debug线程构造 + */ + public IOSTestTaskBootThread() { + this.setName(this.formatThreadName(IOS_TEST_TASK_BOOT_PRE)); + this.setDaemon(true); + } + + /** + * 任务线程构造 + * + * @param jsonObject 任务数据 + * @param iosStepHandler ios步骤执行器 + */ + public IOSTestTaskBootThread(JSONObject jsonObject, IOSStepHandler iosStepHandler) { + this.iosStepHandler = iosStepHandler; + this.jsonObject = jsonObject; + this.resultId = jsonObject.getInteger("rid"); + this.caseId = jsonObject.getInteger("cid"); + this.udId = jsonObject.getJSONObject("device").getString("udId"); + + // 比如:test-task-thread-af80d1e4 + this.setName(String.format(IOS_TEST_TASK_BOOT_PRE, resultId, caseId, udId)); + this.setDaemon(true); + } + + public Semaphore getRunStepSemaphore() { + return runStepSemaphore; + } + + public JSONObject getJsonObject() { + return jsonObject; + } + + public IOSStepHandler getIosStepHandler() { + return iosStepHandler; + } + + public IOSRunStepThread getRunStepThread() { + return runStepThread; + } + + public IOSPerfDataThread getPerfDataThread() { + return perfDataThread; + } + + public IOSRecordThread getRecordThread() { + return recordThread; + } + + public int getResultId() { + return resultId; + } + + public int getCaseId() { + return caseId; + } + + public String getUdId() { + return udId; + } + + public IOSTestTaskBootThread setUdId(String udId) { + this.udId = udId; + return this; + } + + @Override + public void run() { + + boolean startTestSuccess = false; + + try { + int wait = 0; + while (!IOSDeviceLocalStatus.startTest(udId)) { + wait++; + iosStepHandler.waitDevice(wait); + if (wait >= 6 * 10) { + iosStepHandler.waitDeviceTimeOut(); + iosStepHandler.sendStatus(); + return; + } else { + Thread.sleep(10000); + } + } + + startTestSuccess = true; + //启动测试 + try { + int wdaPort = TIDeviceTool.startWda(udId); + iosStepHandler.startIOSDriver(udId, wdaPort); + } catch (Exception e) { + log.error(e.getMessage()); + iosStepHandler.closeIOSDriver(); + iosStepHandler.sendStatus(); + IOSDeviceLocalStatus.finishError(udId); + return; + } + + //电量过低退出测试 + if (iosStepHandler.getBattery()) { + iosStepHandler.closeIOSDriver(); + iosStepHandler.sendStatus(); + IOSDeviceLocalStatus.finish(udId); + return; + } + + //正常运行步骤的线程 + runStepThread = new IOSRunStepThread(this); + //性能数据获取线程 + perfDataThread = new IOSPerfDataThread(this); + //录像线程 + recordThread = new IOSRecordThread(this); + TaskManager.startChildThread(this.getName(), runStepThread, perfDataThread, recordThread); + + + //等待两个线程结束了才结束方法 + while ((recordThread.isAlive()) || (runStepThread.isAlive())) { + Thread.sleep(1000); + } + } catch (InterruptedException e) { + log.error("任务异常,中断:{}", e.getMessage()); + iosStepHandler.setResultDetailStatus(ResultDetailStatus.FAIL); + } finally { + if (startTestSuccess) { + IOSDeviceLocalStatus.finish(udId); + iosStepHandler.closeIOSDriver(); + } + iosStepHandler.sendStatus(); + } + } +} diff --git a/src/main/java/com/sonic/agent/tools/LaunchTool.java b/src/main/java/com/sonic/agent/tools/LaunchTool.java index 3d88647d..35531291 100644 --- a/src/main/java/com/sonic/agent/tools/LaunchTool.java +++ b/src/main/java/com/sonic/agent/tools/LaunchTool.java @@ -11,6 +11,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PreDestroy; +import java.io.File; @Component @DependsOn("nettyMsgInit") @@ -19,6 +20,10 @@ public class LaunchTool implements ApplicationRunner { @Override public void run(ApplicationArguments args) { + File testFile = new File("test-output"); + if (!testFile.exists()) { + testFile.mkdirs(); + } AppiumServer.start(); } diff --git a/src/main/java/com/sonic/agent/tools/LogTool.java b/src/main/java/com/sonic/agent/tools/LogTool.java index db9fddbb..577f58f1 100644 --- a/src/main/java/com/sonic/agent/tools/LogTool.java +++ b/src/main/java/com/sonic/agent/tools/LogTool.java @@ -39,7 +39,7 @@ public void send(JSONObject message) { message.put("rid", resultId); message.put("udId", udId); if (type.equals(DeviceStatus.DEBUGGING)) { - sendToWebSocket(WebSocketSessionMap.getMap().get(sessionId), message); + sendToWebSocket(WebSocketSessionMap.getSession(sessionId), message); } if (type.equals(DeviceStatus.TESTING)) { sendToServer(message); diff --git a/src/main/java/com/sonic/agent/tools/ProcessCommandTool.java b/src/main/java/com/sonic/agent/tools/ProcessCommandTool.java index b2ffa451..c39ae265 100644 --- a/src/main/java/com/sonic/agent/tools/ProcessCommandTool.java +++ b/src/main/java/com/sonic/agent/tools/ProcessCommandTool.java @@ -17,10 +17,11 @@ public static List getProcessLocalCommand(String commandLine) { List sdrResult = new ArrayList(); List sdrErrorResult = new ArrayList(); try { - if (System.getProperty("os.name").contains("Mac")) { - process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", commandLine}); + String system = System.getProperty("os.name").toLowerCase(); + if (system.contains("win")) { + process = Runtime.getRuntime().exec(new String[]{"cmd", "/c", commandLine}); } else { - process = Runtime.getRuntime().exec(new String[]{"cmd", "/C", commandLine}); + process = Runtime.getRuntime().exec(new String[]{"sh", "-c", commandLine}); } inputStreamReader = new InputStreamReader(process.getInputStream()); consoleInput = new LineNumberReader(inputStreamReader); diff --git a/src/main/java/com/sonic/agent/tools/ScrcpyTool.java b/src/main/java/com/sonic/agent/tools/ScrcpyTool.java index 7836a335..84b07da5 100644 --- a/src/main/java/com/sonic/agent/tools/ScrcpyTool.java +++ b/src/main/java/com/sonic/agent/tools/ScrcpyTool.java @@ -17,7 +17,7 @@ public static void main(String[] args) throws IOException, InterruptedException InputStream inputStream; outputStream = capSocket.getOutputStream(); inputStream = capSocket.getInputStream(); -// outputStream.write(0); + outputStream.write(0); int readLength; int naluIndex = 0; diff --git a/src/main/java/com/sonic/agent/websockets/AndroidWSServer.java b/src/main/java/com/sonic/agent/websockets/AndroidWSServer.java index 8dc727af..1f6b531d 100644 --- a/src/main/java/com/sonic/agent/websockets/AndroidWSServer.java +++ b/src/main/java/com/sonic/agent/websockets/AndroidWSServer.java @@ -13,12 +13,12 @@ import com.sonic.agent.bridge.android.AndroidDeviceLocalStatus; import com.sonic.agent.bridge.android.AndroidDeviceThreadPool; import com.sonic.agent.interfaces.DeviceStatus; -import com.sonic.agent.maps.GlobalProcessMap; -import com.sonic.agent.maps.HandlerMap; -import com.sonic.agent.maps.MiniCapMap; -import com.sonic.agent.maps.WebSocketSessionMap; +import com.sonic.agent.maps.*; import com.sonic.agent.netty.NettyThreadPool; -import com.sonic.agent.tools.*; +import com.sonic.agent.tools.MiniCapTool; +import com.sonic.agent.tools.PortTool; +import com.sonic.agent.tools.ProcessCommandTool; +import com.sonic.agent.tools.UploadTools; import org.openqa.selenium.OutputType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,6 +45,7 @@ @Component @ServerEndpoint(value = "/websockets/android/{key}/{udId}/{token}", configurator = MyEndpointConfigure.class) public class AndroidWSServer { + private final Logger logger = LoggerFactory.getLogger(AndroidWSServer.class); @Value("${sonic.agent.key}") private String key; @@ -66,12 +67,21 @@ public void onOpen(Session session, @PathParam("key") String secretKey, logger.info("拦截访问!"); return; } + + session.getUserProperties().put("udId", udId); + boolean lockSuccess = DevicesLockMap.lockByUdId(udId, 30L, TimeUnit.SECONDS); + if (!lockSuccess) { + logger.info("30s内获取设备锁失败,请确保设备无人使用"); + return; + } + logger.info("android上锁udId:{}", udId); + JSONObject jsonDebug = new JSONObject(); jsonDebug.put("msg", "debugUser"); jsonDebug.put("token", token); jsonDebug.put("udId", udId); NettyThreadPool.send(jsonDebug); - WebSocketSessionMap.getMap().put(session.getId(), session); + WebSocketSessionMap.addSession(session); IDevice iDevice = AndroidDeviceBridgeTool.getIDeviceByUdId(udId); if (iDevice == null) { logger.info("设备未连接,请检查!"); @@ -202,7 +212,7 @@ public boolean isCancelled() { e.printStackTrace(); } // 超时就不继续等了,保证其它服务可运行 - if (wait > 6) { + if (wait > 20) { return; } } @@ -256,11 +266,11 @@ public boolean isCancelled() { String system = System.getProperty("os.name").toLowerCase(); Process ps = null; int port = PortTool.getPort(); + String command = String.format("adbkit usb-device-to-tcp -p %d %s", port, udId); if (system.contains("win")) { - ps = Runtime.getRuntime().exec(String.format("cmd /c adbkit usb-device-to-tcp -p %d %s", port, udId)); - } - if (system.contains("linux") || system.contains("mac")) { - ps = Runtime.getRuntime().exec(String.format("sh -c adbkit usb-device-to-tcp -p %d %s", port, udId)); + ps = Runtime.getRuntime().exec(new String[]{"cmd", "/c", command}); + } else if (system.contains("linux") || system.contains("mac")) { + ps = Runtime.getRuntime().exec(new String[]{"sh", "-c", command}); } GlobalProcessMap.getMap().put(processName, ps); JSONObject adbkit = new JSONObject(); @@ -306,7 +316,13 @@ public boolean isCancelled() { @OnClose public void onClose(Session session) { - exit(session); + String udId = (String) session.getUserProperties().get("udId"); + try { + exit(session); + } finally { + DevicesLockMap.unlockAndRemoveByUdId(udId); + logger.info("android解锁udId:{}", udId); + } } @OnError @@ -325,7 +341,7 @@ public void onMessage(String message, Session session) throws InterruptedExcepti case "forwardView": { JSONObject forwardView = new JSONObject(); IDevice iDevice = udIdMap.get(session); - List wList = Arrays.asList("webview", "WebView"); + List wList = Arrays.asList("webview", "WebView", "chrome_devtools_remote", "Terrace_devtools_remote"); List webViewList = new ArrayList<>(); for (String w : wList) { webViewList.addAll(Arrays.asList(AndroidDeviceBridgeTool @@ -398,6 +414,9 @@ public void onMessage(String message, Session session) throws InterruptedExcepti case "find": AndroidDeviceBridgeTool.searchDevice(udIdMap.get(session)); break; + case "battery": + AndroidDeviceBridgeTool.controlBattery(udIdMap.get(session), msg.getInteger("detail")); + break; case "scan": AndroidDeviceBridgeTool.pushToCamera(udIdMap.get(session), msg.getString("url")); break; @@ -445,92 +464,6 @@ public void onMessage(String message, Session session) throws InterruptedExcepti AndroidDeviceBridgeTool.pressKey(udIdMap.get(session), msg.getInteger("detail")); break; case "debug": - AndroidStepHandler androidStepHandler = HandlerMap.getAndroidMap().get(session.getId()); - try { - if (msg.getString("detail").equals("tap")) { - String xy = msg.getString("point"); - int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); - int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); - AndroidDeviceBridgeTool.executeCommand(udIdMap.get(session), "input tap " + x + " " + y); - } - if (msg.getString("detail").equals("longPress")) { - String xy = msg.getString("point"); - int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); - int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); - AndroidDeviceBridgeTool.executeCommand(udIdMap.get(session), "input swipe " + x + " " + y + " " + x + " " + y + " 1500"); - } - if (msg.getString("detail").equals("swipe")) { - String xy1 = msg.getString("pointA"); - String xy2 = msg.getString("pointB"); - int x1 = Integer.parseInt(xy1.substring(0, xy1.indexOf(","))); - int y1 = Integer.parseInt(xy1.substring(xy1.indexOf(",") + 1)); - int x2 = Integer.parseInt(xy2.substring(0, xy2.indexOf(","))); - int y2 = Integer.parseInt(xy2.substring(xy2.indexOf(",") + 1)); - AndroidDeviceBridgeTool.executeCommand(udIdMap.get(session), "input swipe " + x1 + " " + y1 + " " + x2 + " " + y2 + " 200"); - } - } catch (Exception e) { - e.printStackTrace(); - } - if (msg.getString("detail").equals("install")) { - AndroidStepHandler finalAndroidStepHandler = androidStepHandler; - AndroidDeviceThreadPool.cachedThreadPool.execute(() -> { - JSONObject result = new JSONObject(); - result.put("msg", "installFinish"); - HandleDes handleDes = new HandleDes(); - finalAndroidStepHandler.install(handleDes, msg.getString("apk")); - if (handleDes.getE() == null) { - result.put("status", "success"); - } else { - System.out.println(handleDes.getE()); - result.put("status", "fail"); - } - sendText(session, result.toJSONString()); - }); - } - if (msg.getString("detail").equals("tree")) { - androidStepHandler = HandlerMap.getAndroidMap().get(session.getId()); - AndroidStepHandler finalAndroidStepHandler = androidStepHandler; - AndroidDeviceThreadPool.cachedThreadPool.execute(() -> { - try { - JSONObject result = new JSONObject(); - result.put("msg", "tree"); - result.put("detail", finalAndroidStepHandler.getResource()); - HandleDes handleDes = new HandleDes(); - if (!msg.getBoolean("hasScreen")) { - result.put("img", finalAndroidStepHandler.stepScreen(handleDes)); - } - if (handleDes.getE() != null) { - logger.error(handleDes.getE().getMessage()); - JSONObject resultFail = new JSONObject(); - resultFail.put("msg", "treeFail"); - sendText(session, resultFail.toJSONString()); - } else { - result.put("webView", finalAndroidStepHandler.getWebView()); - result.put("activity", finalAndroidStepHandler.getCurrentActivity()); - sendText(session, result.toJSONString()); - } - } catch (Throwable e) { - logger.error(e.getMessage()); - JSONObject result = new JSONObject(); - result.put("msg", "treeFail"); - sendText(session, result.toJSONString()); - } - }); - } - if (msg.getString("detail").equals("eleScreen")) { - androidStepHandler = HandlerMap.getAndroidMap().get(session.getId()); - AndroidStepHandler finalAndroidStepHandler = androidStepHandler; - AndroidDeviceThreadPool.cachedThreadPool.execute(() -> { - JSONObject result = new JSONObject(); - result.put("msg", "eleScreen"); - try { - result.put("img", UploadTools.upload(finalAndroidStepHandler.findEle("xpath", msg.getString("xpath")).getScreenshotAs(OutputType.FILE), "keepFiles")); - } catch (Exception e) { - result.put("errMsg", "获取元素截图失败!"); - } - sendText(session, result.toJSONString()); - }); - } if (msg.getString("detail").equals("runStep")) { JSONObject jsonDebug = new JSONObject(); jsonDebug.put("msg", "findSteps"); @@ -540,6 +473,94 @@ public void onMessage(String message, Session session) throws InterruptedExcepti jsonDebug.put("sessionId", session.getId()); jsonDebug.put("caseId", msg.getInteger("caseId")); NettyThreadPool.send(jsonDebug); + } else { + AndroidStepHandler androidStepHandler = HandlerMap.getAndroidMap().get(session.getId()); + if (androidStepHandler == null || androidStepHandler.getAndroidDriver() == null) { + break; + } + try { + if (msg.getString("detail").equals("tap")) { + String xy = msg.getString("point"); + int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); + int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); + AndroidDeviceBridgeTool.executeCommand(udIdMap.get(session), "input tap " + x + " " + y); + } + if (msg.getString("detail").equals("longPress")) { + String xy = msg.getString("point"); + int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); + int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); + AndroidDeviceBridgeTool.executeCommand(udIdMap.get(session), "input swipe " + x + " " + y + " " + x + " " + y + " 1500"); + } + if (msg.getString("detail").equals("swipe")) { + String xy1 = msg.getString("pointA"); + String xy2 = msg.getString("pointB"); + int x1 = Integer.parseInt(xy1.substring(0, xy1.indexOf(","))); + int y1 = Integer.parseInt(xy1.substring(xy1.indexOf(",") + 1)); + int x2 = Integer.parseInt(xy2.substring(0, xy2.indexOf(","))); + int y2 = Integer.parseInt(xy2.substring(xy2.indexOf(",") + 1)); + AndroidDeviceBridgeTool.executeCommand(udIdMap.get(session), "input swipe " + x1 + " " + y1 + " " + x2 + " " + y2 + " 200"); + } + } catch (Exception e) { + e.printStackTrace(); + } + if (msg.getString("detail").equals("install")) { + AndroidStepHandler finalAndroidStepHandler = androidStepHandler; + AndroidDeviceThreadPool.cachedThreadPool.execute(() -> { + JSONObject result = new JSONObject(); + result.put("msg", "installFinish"); + HandleDes handleDes = new HandleDes(); + finalAndroidStepHandler.install(handleDes, msg.getString("apk")); + if (handleDes.getE() == null) { + result.put("status", "success"); + } else { + System.out.println(handleDes.getE()); + result.put("status", "fail"); + } + sendText(session, result.toJSONString()); + }); + } + if (msg.getString("detail").equals("tree")) { + AndroidStepHandler finalAndroidStepHandler = androidStepHandler; + AndroidDeviceThreadPool.cachedThreadPool.execute(() -> { + try { + JSONObject result = new JSONObject(); + result.put("msg", "tree"); + result.put("detail", finalAndroidStepHandler.getResource()); + HandleDes handleDes = new HandleDes(); + if (!msg.getBoolean("hasScreen")) { + result.put("img", finalAndroidStepHandler.stepScreen(handleDes)); + } + if (handleDes.getE() != null) { + logger.error(handleDes.getE().getMessage()); + JSONObject resultFail = new JSONObject(); + resultFail.put("msg", "treeFail"); + sendText(session, resultFail.toJSONString()); + } else { + result.put("webView", finalAndroidStepHandler.getWebView()); + result.put("activity", finalAndroidStepHandler.getCurrentActivity()); + sendText(session, result.toJSONString()); + } + } catch (Throwable e) { + logger.error(e.getMessage()); + JSONObject result = new JSONObject(); + result.put("msg", "treeFail"); + sendText(session, result.toJSONString()); + } + }); + } + if (msg.getString("detail").equals("eleScreen")) { + AndroidStepHandler finalAndroidStepHandler = androidStepHandler; + AndroidDeviceThreadPool.cachedThreadPool.execute(() -> { + JSONObject result = new JSONObject(); + result.put("msg", "eleScreen"); + try { + result.put("img", UploadTools.upload(finalAndroidStepHandler.findEle("xpath", msg.getString("xpath")).getScreenshotAs(OutputType.FILE), "keepFiles")); + } catch (Exception e) { + result.put("errMsg", "获取元素截图失败!"); + } + sendText(session, result.toJSONString()); + }); + } } break; } @@ -571,10 +592,12 @@ private void exit(Session session) { } outputMap.remove(session); udIdMap.remove(session); - rotationMap.get(session).interrupt(); + if (rotationMap.get(session) != null) { + rotationMap.get(session).interrupt(); + } rotationMap.remove(session); MiniCapMap.getMap().get(session).interrupt(); - WebSocketSessionMap.getMap().remove(session.getId()); + WebSocketSessionMap.removeSession(session); try { session.close(); } catch (IOException e) { @@ -582,4 +605,4 @@ private void exit(Session session) { } logger.info(session.getId() + "退出"); } -} \ No newline at end of file +} diff --git a/src/main/java/com/sonic/agent/websockets/IOSWSServer.java b/src/main/java/com/sonic/agent/websockets/IOSWSServer.java index 9b1f0b61..02ab5b11 100644 --- a/src/main/java/com/sonic/agent/websockets/IOSWSServer.java +++ b/src/main/java/com/sonic/agent/websockets/IOSWSServer.java @@ -1,26 +1,17 @@ package com.sonic.agent.websockets; import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import com.android.ddmlib.IDevice; -import com.sonic.agent.automation.AndroidStepHandler; import com.sonic.agent.automation.HandleDes; import com.sonic.agent.automation.IOSStepHandler; -import com.sonic.agent.automation.RemoteDebugDriver; -import com.sonic.agent.bridge.android.AndroidDeviceBridgeTool; -import com.sonic.agent.bridge.android.AndroidDeviceThreadPool; import com.sonic.agent.bridge.ios.IOSDeviceLocalStatus; import com.sonic.agent.bridge.ios.IOSDeviceThreadPool; import com.sonic.agent.bridge.ios.TIDeviceTool; import com.sonic.agent.interfaces.DeviceStatus; +import com.sonic.agent.maps.DevicesLockMap; import com.sonic.agent.maps.HandlerMap; -import com.sonic.agent.maps.MiniCapMap; import com.sonic.agent.maps.WebSocketSessionMap; import com.sonic.agent.netty.NettyThreadPool; -import com.sonic.agent.tools.MiniCapTool; -import com.sonic.agent.tools.PortTool; -import com.sonic.agent.tools.ProcessCommandTool; import com.sonic.agent.tools.UploadTools; import io.appium.java_client.TouchAction; import io.appium.java_client.touch.WaitOptions; @@ -29,19 +20,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; -import java.io.OutputStream; import java.time.Duration; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.TimeUnit; + +import static com.sonic.agent.tools.AgentTool.sendText; @Component @ServerEndpoint(value = "/websockets/ios/{key}/{udId}/{token}", configurator = MyEndpointConfigure.class) @@ -58,26 +48,41 @@ public void onOpen(Session session, @PathParam("key") String secretKey, logger.info("拦截访问!"); return; } + + session.getUserProperties().put("udId", udId); + boolean lockSuccess = DevicesLockMap.lockByUdId(udId, 30L, TimeUnit.SECONDS); + if (!lockSuccess) { + logger.info("30s内获取设备锁失败,请确保设备无人使用"); + return; + } + logger.info("ios上锁udId:{}", udId); + JSONObject jsonDebug = new JSONObject(); jsonDebug.put("msg", "debugUser"); jsonDebug.put("token", token); jsonDebug.put("udId", udId); NettyThreadPool.send(jsonDebug); - WebSocketSessionMap.getMap().put(session.getId(), session); + WebSocketSessionMap.addSession(session); if (!TIDeviceTool.getDeviceList().contains(udId)) { logger.info("设备未连接,请检查!"); return; } udIdMap.put(session, udId); + int wdaPort = TIDeviceTool.startWda(udId); + int imgPort = TIDeviceTool.relayImg(udId); + JSONObject picFinish = new JSONObject(); + picFinish.put("msg", "picFinish"); + picFinish.put("port", imgPort); + sendText(session, picFinish.toJSONString()); + IOSDeviceThreadPool.cachedThreadPool.execute(() -> { IOSStepHandler iosStepHandler = new IOSStepHandler(); iosStepHandler.setTestMode(0, 0, udId, DeviceStatus.DEBUGGING, session.getId()); JSONObject result = new JSONObject(); try { IOSDeviceLocalStatus.startDebug(udId); - int imgPort = iosStepHandler.startIOSDriver(udId); + iosStepHandler.startIOSDriver(udId, wdaPort); result.put("status", "success"); - result.put("port", imgPort); result.put("width", iosStepHandler.getDriver().manage().window().getSize().width); result.put("height", iosStepHandler.getDriver().manage().window().getSize().height); result.put("detail", "初始化Driver完成!"); @@ -100,7 +105,13 @@ public void onOpen(Session session, @PathParam("key") String secretKey, @OnClose public void onClose(Session session) { - exit(session); + String udId = (String) session.getUserProperties().get("udId"); + try { + exit(session); + } finally { + DevicesLockMap.unlockAndRemoveByUdId(udId); + logger.info("ios解锁udId:{}", udId); + } } @OnError @@ -117,130 +128,125 @@ public void onMessage(String message, Session session) throws InterruptedExcepti logger.info(session.getId() + " 发送 " + msg); switch (msg.getString("type")) { case "debug": - IOSStepHandler iosStepHandler; - try { - if (msg.getString("detail").equals("tap")) { - iosStepHandler = HandlerMap.getIOSMap().get(session.getId()); - TouchAction ta = new TouchAction(iosStepHandler.getDriver()); - String xy = msg.getString("point"); - int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); - int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); - ta.tap(PointOption.point(x, y)).perform(); + if (msg.getString("detail").equals("runStep")) { + JSONObject jsonDebug = new JSONObject(); + jsonDebug.put("msg", "findSteps"); + jsonDebug.put("key", key); + jsonDebug.put("udId", udIdMap.get(session)); + jsonDebug.put("sessionId", session.getId()); + jsonDebug.put("caseId", msg.getInteger("caseId")); + NettyThreadPool.send(jsonDebug); + } else { + IOSStepHandler iosStepHandler = HandlerMap.getIOSMap().get(session.getId()); + if (iosStepHandler == null || iosStepHandler.getDriver() == null) { + break; } - if (msg.getString("detail").equals("longPress")) { - iosStepHandler = HandlerMap.getIOSMap().get(session.getId()); - TouchAction ta = new TouchAction(iosStepHandler.getDriver()); - String xy = msg.getString("point"); - int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); - int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); - ta.longPress(PointOption.point(x, y)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(1500))).release().perform(); - } - if (msg.getString("detail").equals("swipe")) { - iosStepHandler = HandlerMap.getIOSMap().get(session.getId()); - TouchAction ta = new TouchAction(iosStepHandler.getDriver()); - String xy1 = msg.getString("pointA"); - String xy2 = msg.getString("pointB"); - int x1 = Integer.parseInt(xy1.substring(0, xy1.indexOf(","))); - int y1 = Integer.parseInt(xy1.substring(xy1.indexOf(",") + 1)); - int x2 = Integer.parseInt(xy2.substring(0, xy2.indexOf(","))); - int y2 = Integer.parseInt(xy2.substring(xy2.indexOf(",") + 1)); - ta.press(PointOption.point(x1, y1)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(300))).moveTo(PointOption.point(x2, y2)).release().perform(); - } - } catch (Exception e) { - e.printStackTrace(); - } - if (msg.getString("detail").equals("keyEvent")) { - iosStepHandler = HandlerMap.getIOSMap().get(session.getId()); try { - if (msg.getString("key").equals("home") || msg.getString("key").equals("volumeup") || msg.getString("key").equals("volumedown")) { - iosStepHandler.getDriver().executeScript("mobile:pressButton", JSON.parse("{name: \"" + msg.getString("key") + "\"}")); - } else if (msg.getString("key").equals("lock")) { - if (iosStepHandler.getDriver().isDeviceLocked()) { - iosStepHandler.unLock(new HandleDes()); - } else { - iosStepHandler.lock(new HandleDes()); - } - } else { - iosStepHandler.openApp(new HandleDes(), "com.apple." + msg.getString("key")); + if (msg.getString("detail").equals("tap")) { + TouchAction ta = new TouchAction(iosStepHandler.getDriver()); + String xy = msg.getString("point"); + int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); + int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); + ta.tap(PointOption.point(x, y)).perform(); + } + if (msg.getString("detail").equals("longPress")) { + TouchAction ta = new TouchAction(iosStepHandler.getDriver()); + String xy = msg.getString("point"); + int x = Integer.parseInt(xy.substring(0, xy.indexOf(","))); + int y = Integer.parseInt(xy.substring(xy.indexOf(",") + 1)); + ta.longPress(PointOption.point(x, y)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(1500))).release().perform(); } - } catch (Throwable e) { + if (msg.getString("detail").equals("swipe")) { + TouchAction ta = new TouchAction(iosStepHandler.getDriver()); + String xy1 = msg.getString("pointA"); + String xy2 = msg.getString("pointB"); + int x1 = Integer.parseInt(xy1.substring(0, xy1.indexOf(","))); + int y1 = Integer.parseInt(xy1.substring(xy1.indexOf(",") + 1)); + int x2 = Integer.parseInt(xy2.substring(0, xy2.indexOf(","))); + int y2 = Integer.parseInt(xy2.substring(xy2.indexOf(",") + 1)); + ta.press(PointOption.point(x1, y1)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(300))).moveTo(PointOption.point(x2, y2)).release().perform(); + } + } catch (Exception e) { e.printStackTrace(); } - } - if (msg.getString("detail").equals("siri")) { - iosStepHandler = HandlerMap.getIOSMap().get(session.getId()); - IOSStepHandler finalIOSStepHandler = iosStepHandler; - IOSDeviceThreadPool.cachedThreadPool.execute(() -> { - finalIOSStepHandler.siriCommand(new HandleDes(), msg.getString("command")); - }); - } -// if (msg.getString("detail").equals("install")) { -// androidStepHandler = HandlerMap.getAndroidMap().get(session.getId()); -// AndroidStepHandler finalAndroidStepHandler = androidStepHandler; -// AndroidDeviceThreadPool.cachedThreadPool.execute(() -> { -// JSONObject result = new JSONObject(); -// result.put("msg", "installFinish"); -// HandleDes handleDes = new HandleDes(); -// finalAndroidStepHandler.install(handleDes, msg.getString("apk")); -// if (handleDes.getE() == null) { -// result.put("status", "success"); -// } else { -// System.out.println(handleDes.getE()); -// result.put("status", "fail"); -// } -// sendText(session, result.toJSONString()); -// }); -// } - if (msg.getString("detail").equals("tree")) { - iosStepHandler = HandlerMap.getIOSMap().get(session.getId()); - IOSStepHandler finalIOSStepHandler = iosStepHandler; - IOSDeviceThreadPool.cachedThreadPool.execute(() -> { + if (msg.getString("detail").equals("keyEvent")) { try { + if (msg.getString("key").equals("home") || msg.getString("key").equals("volumeup") || msg.getString("key").equals("volumedown")) { + iosStepHandler.getDriver().executeScript("mobile:pressButton", JSON.parse("{name: \"" + msg.getString("key") + "\"}")); + } else if (msg.getString("key").equals("lock")) { + if (iosStepHandler.getDriver().isDeviceLocked()) { + iosStepHandler.unLock(new HandleDes()); + } else { + iosStepHandler.lock(new HandleDes()); + } + } else { + iosStepHandler.openApp(new HandleDes(), "com.apple." + msg.getString("key")); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + if (msg.getString("detail").equals("siri")) { + IOSStepHandler finalIOSStepHandler = iosStepHandler; + IOSDeviceThreadPool.cachedThreadPool.execute(() -> { + finalIOSStepHandler.siriCommand(new HandleDes(), msg.getString("command")); + }); + } + if (msg.getString("detail").equals("install")) { + IOSStepHandler finalIosStepHandler = iosStepHandler; + IOSDeviceThreadPool.cachedThreadPool.execute(() -> { JSONObject result = new JSONObject(); - result.put("msg", "tree"); - result.put("detail", finalIOSStepHandler.getResource()); + result.put("msg", "installFinish"); HandleDes handleDes = new HandleDes(); - result.put("img", finalIOSStepHandler.stepScreen(handleDes)); - if (handleDes.getE() != null) { - logger.error(handleDes.getE().getMessage()); - JSONObject resultFail = new JSONObject(); - resultFail.put("msg", "treeFail"); - sendText(session, resultFail.toJSONString()); + finalIosStepHandler.install(handleDes, msg.getString("ipa")); + if (handleDes.getE() == null) { + result.put("status", "success"); } else { + System.out.println(handleDes.getE()); + result.put("status", "fail"); + } + sendText(session, result.toJSONString()); + }); + } + if (msg.getString("detail").equals("tree")) { + IOSStepHandler finalIOSStepHandler = iosStepHandler; + IOSDeviceThreadPool.cachedThreadPool.execute(() -> { + try { + JSONObject result = new JSONObject(); + result.put("msg", "tree"); + result.put("detail", finalIOSStepHandler.getResource()); + HandleDes handleDes = new HandleDes(); + result.put("img", finalIOSStepHandler.stepScreen(handleDes)); + if (handleDes.getE() != null) { + logger.error(handleDes.getE().getMessage()); + JSONObject resultFail = new JSONObject(); + resultFail.put("msg", "treeFail"); + sendText(session, resultFail.toJSONString()); + } else { + sendText(session, result.toJSONString()); + } + } catch (Throwable e) { + logger.error(e.getMessage()); + JSONObject result = new JSONObject(); + result.put("msg", "treeFail"); sendText(session, result.toJSONString()); } - } catch (Throwable e) { - logger.error(e.getMessage()); + }); + } + if (msg.getString("detail").equals("eleScreen")) { + IOSStepHandler finalIOSStepHandler = iosStepHandler; + IOSDeviceThreadPool.cachedThreadPool.execute(() -> { JSONObject result = new JSONObject(); - result.put("msg", "treeFail"); + result.put("msg", "eleScreen"); + try { + result.put("img", UploadTools.upload(finalIOSStepHandler.findEle("xpath", msg.getString("xpath")).getScreenshotAs(OutputType.FILE), "keepFiles")); + } catch (Exception e) { + result.put("errMsg", "获取元素截图失败!"); + } sendText(session, result.toJSONString()); - } - }); - } - if (msg.getString("detail").equals("eleScreen")) { - iosStepHandler = HandlerMap.getIOSMap().get(session.getId()); - IOSStepHandler finalIOSStepHandler = iosStepHandler; - IOSDeviceThreadPool.cachedThreadPool.execute(() -> { - JSONObject result = new JSONObject(); - result.put("msg", "eleScreen"); - try { - result.put("img", UploadTools.upload(finalIOSStepHandler.findEle("xpath", msg.getString("xpath")).getScreenshotAs(OutputType.FILE), "keepFiles")); - } catch (Exception e) { - result.put("errMsg", "获取元素截图失败!"); - } - sendText(session, result.toJSONString()); - }); + }); + } } -// if (msg.getString("detail").equals("runStep")) { -// JSONObject jsonDebug = new JSONObject(); -// jsonDebug.put("msg", "findSteps"); -// jsonDebug.put("key", key); -// jsonDebug.put("udId", udIdMap.get(session).getSerialNumber()); -// jsonDebug.put("pwd", msg.getString("pwd")); -// jsonDebug.put("sessionId", session.getId()); -// jsonDebug.put("caseId", msg.getInteger("caseId")); -// NettyThreadPool.send(jsonDebug); -// } break; } } @@ -264,7 +270,7 @@ private void exit(Session session) { } finally { HandlerMap.getIOSMap().remove(session.getId()); } - WebSocketSessionMap.getMap().remove(session.getId()); + WebSocketSessionMap.removeSession(session); try { session.close(); } catch (IOException e) { diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 94db2d2c..f4a33965 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,17 +1,17 @@ sonic: agent: - host: 172.25.128.1 + host: 192.168.1.10 port: 7777 key: 29002272-4659-4808-a804-08ce3388b136 server: - host: 172.25.128.1 + host: 192.168.1.10 folder-port: 8094 transport-port: 8095 modules: android: enable: true - use-adbkit: false + use-adbkit: true ios: enable: false wda-bundle-id: com.sonic.wda.xctrunner