├── .gitignore ├── CHANGELOG.md ├── README.md ├── pom.xml └── src ├── main ├── java │ ├── com │ │ └── ceshiren │ │ │ └── appcrawler │ │ │ ├── AppiumTouchAction.java │ │ │ ├── Demo.java │ │ │ ├── DemoPlugin.java │ │ │ ├── GetAPKPackage.java │ │ │ ├── diff │ │ │ ├── Report.java │ │ │ └── XPathUtil.java │ │ │ └── report │ │ │ ├── AllureDiffTest.java │ │ │ ├── AllureTest.java │ │ │ ├── GenerateClassByJavassist.java │ │ │ ├── Junit5Report.java │ │ │ ├── LoadYaml.java │ │ │ ├── MvnReplace.java │ │ │ └── ReadYaml.java │ └── io │ │ └── appium │ │ └── java_client │ │ └── Setting.java ├── resources │ ├── 404.png │ ├── log4j2.yaml │ └── template.mustache └── scala │ └── com │ └── ceshiren │ └── appcrawler │ ├── AppCrawler.scala │ ├── Banner.scala │ ├── core │ ├── AppiumSuite.scala │ ├── AutomationSuite.scala │ ├── Crawler.scala │ ├── CrawlerConf.scala │ ├── ReactTestCase.scala │ └── Status.scala │ ├── driver │ ├── AdbDriver.scala │ ├── AppiumClient.scala │ ├── BaseDDT.java │ ├── MockDDT.java │ ├── MockDriver.scala │ ├── ReactWebDriver.scala │ ├── SeleniumDDT.java │ └── SeleniumDriver.scala │ ├── model │ ├── BDDTestCase.scala │ ├── DataRecord.scala │ ├── ElementInfo.scala │ ├── Given.scala │ ├── Node.java │ ├── PageSource.java │ ├── ScreenShot.scala │ ├── Step.scala │ ├── TreeNode.scala │ ├── URIElement.scala │ ├── URIElementStore.scala │ └── When.scala │ ├── plugin │ ├── DemoPlugin.scala │ ├── FlowDiff.scala │ ├── FreeMind.scala │ ├── IDeviceScreenshot.scala │ ├── LogPlugin.scala │ ├── Plugin.scala │ ├── ReportPlugin.scala │ ├── TagLimitPlugin.scala │ ├── junit5 │ │ ├── AllureTemplate.java │ │ ├── FakeData.java │ │ ├── JUnit5Runtime.scala │ │ └── JUnit5RuntimeJava.java │ ├── report │ │ ├── DiffSuite.scala │ │ ├── Report.scala │ │ └── ReportFactory.scala │ └── scalatest │ │ ├── ScalaTestRuntime.scala │ │ ├── ScalaTestTemplate.scala │ │ └── SuiteToClass.scala │ └── utils │ ├── DynamicEval.scala │ ├── GA.scala │ ├── Log.java │ ├── LogicUtils.scala │ ├── TData.scala │ └── XPathUtil.scala └── test ├── java └── com │ └── ceshiren │ └── appcrawler │ ├── AutoGenerateTest.java │ ├── CrawlerTest.java │ ├── DemoTest.java │ ├── SuiteToClassTest.java │ ├── driver │ └── BaseDDTTest.java │ ├── it │ ├── JavaAppCrawlerTest.java │ ├── TestBackDoor.java │ ├── appium │ │ └── SettingsTest.java │ └── xueqiu_conf.yml │ ├── model │ ├── NodeTest.java │ └── PageSourceTest.java │ ├── report │ ├── Junit5ReportTest.java │ ├── LoadYamlTest.java │ ├── MvnReplaceTest.java │ └── generateClass │ │ ├── CountryCodeSelectActivityTests.class │ │ ├── LoginActivityTests.class │ │ ├── LoginOptionActivityTests.class │ │ ├── MyProfileActivityTests.class │ │ ├── id=register_phone_numberTests.class │ │ ├── id=tv_loginTests.class │ │ ├── id=tv_login_by_phone_or_othersTests.class │ │ ├── id=user_profile_iconTests.class │ │ └── 雪球Tests.class │ ├── ut │ ├── AllureTemplateTestcase.java │ ├── BackBtnXpathTest.java │ ├── Context.java │ ├── CountryCodeSelectActivityTest.java │ ├── DiffTest.java │ ├── ElementUrlTest.java │ ├── ExecutingTests.java │ ├── GenerateClassTest.java │ ├── JUnit5ExceptionTest.java │ ├── Junit5TemplateTest.java │ ├── MustcheTest.java │ └── TestCaseTest.java │ └── utils │ └── LogTest.java └── scala └── com └── ceshiren └── appcrawler ├── driver ├── AdbDriverTest.scala ├── MockDriverTest.scala ├── ReactWebDriverTest.scala └── SeleniumDriverTest.scala ├── it ├── AppCrawlerTest.scala ├── TestAppium.scala ├── TestIOS.scala ├── TestImage.scala ├── TestJianShu.scala ├── TestMacaca.scala.bak ├── TestNW.scala ├── TestOCR.scala ├── TestQQ.scala ├── TestSauceLabs.scala ├── TestTesterHome.scala ├── TestUiautomator2Server.scala ├── TestWebDriverAgent.scala ├── TestWebView.scala ├── TestWeixin.scala ├── TestXueQiu.scala ├── keep.yml ├── keep_test.yml ├── maimai.yml ├── meituanwaimai_private.yml ├── qq_automation.yml ├── ruqi_automation.yml ├── shafa_private.yml ├── weixin.yml ├── xiaomi.yml ├── xueqiu_automation.yml ├── xueqiu_base.yml ├── xueqiu_private.yml └── xueqiu_sikuli.yml ├── model ├── BDDTestCaseTest.scala └── ElementInfoTest.scala ├── ut ├── AppCrawlerTest.scala ├── Demo.scala ├── DemoCrawlerSuite.scala ├── PageObjectDemo.java.ssp ├── PageObjectDemoID.java.ssp ├── RequestsTest.scala ├── SuiteToClassTest.scala ├── TestConf.scala ├── TestCrawler.scala ├── TestDataObject.scala ├── TestDataRecord.scala ├── TestDynamicEval.scala ├── TestElementStore.scala ├── TestGA.scala ├── TestGetClassFile.scala ├── TestJUnit5.scala ├── TestJUnit5Launcher.scala ├── TestJava.scala ├── TestReportPlugin.scala ├── TestSpec.scala ├── TestStringTemplate.scala ├── TestSuites.scala ├── TestThread.scala ├── TestTreeNode.scala ├── TestURIElement.scala ├── TestXPathUtil.scala ├── html.xml ├── miniprogram.xml ├── scalate.ssp └── source.json └── utils └── LogicUtilsTest.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | project/target/ 3 | project/project/ 4 | target/ 5 | conf.json 6 | xueqiu.json 7 | *.apk 8 | *.swp 9 | *.iml 10 | allure-* 11 | /.gradle 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.0.0 [TODO] 2 | - 支持从历史数据中寻找最优点击路径 3 | - 支持游戏app遍历 4 | - 支持wetest和mqc 5 | - 改进appium 6 | - 使用节点树模型 7 | - 优化自动重启逻辑 8 | - 简化配置文件 9 | - 完善断言 10 | - 支持性能 11 | - 支持弱网 12 | - 支持接口数据入报告 13 | 14 | # 2.7.0 15 | - 代码重构 16 | - 性能提升 17 | 18 | 仍然不满意,但是应很多公司的使用要求,先提前放出beta版本 19 | 20 | 21 | # 2.6.0 [TODO] 22 | - 支持accessibility service 23 | - 支持rpc dubbo json-rpc 24 | - 支持云插件 25 | - 改进appium的Uiautomator2的性能与bug 26 | 27 | # 2.5 28 | - 支持android native adb遍历 [done] 29 | - 简化遍历逻辑 30 | - 支持根据属性排序遍历 31 | 32 | # 2.4 33 | - 支持java10 [done] 34 | - 支持selenium [done] 35 | 36 | # 2.3.1 37 | - 合并了androidCapability iosCapability到capability [done] 38 | - 支持定位加速[完成] 39 | - 更新到java-client 6.0,坑好多[Done] 40 | 41 | # 2.2.1 42 | - 合并triggerAction firstList lastList为orderList 43 | - 去掉autoCrawl 44 | 45 | # 2.2.0 46 | - 修复自动重启执行afterUrlFinish的bug,增强afterUrlFinish支持条件,支持given when 47 | - 增强assert配置的模型,与testcase保持一致,只设置given then即可 48 | - 修复iOS backButton的bug 49 | - 减少getCurrentActivy api调用,会导致appium不稳定 50 | - 修复swipe,这个api仍然不稳定,moveTo是终点位置 51 | - 增加restartOnError命令 52 | 53 | 54 | # 2.1.2 55 | - 跟进支持appium 1.7[完成] 56 | 57 | # 2.1.0 58 | ### bugfix 59 | mark图片异常的问题 60 | ### 自动化用例 61 | 只是demo. 还有很多细节需要设计的更好. 62 | 支持given when then风格, 也支持简化的xpath action then的简单风格. 63 | ```yaml 64 | #设置这个跳过遍历 65 | autoCrawl: false 66 | #测试用例入口 67 | testcase: 68 | #测试用例名字 69 | name: demo1 70 | steps: 71 | - when: 72 | xpath: //* 73 | action: driver.swipe(0.5, 0.8, 0.5, 0.2) 74 | - when: 75 | xpath: //* 76 | action: driver.swipe(0.5, 0.2, 0.5, 0.8) 77 | #简化风格. 没有when 78 | - xpath: 自选 79 | action: click 80 | then: 81 | - //*[contains(@text, "港股")] 82 | ``` 83 | 所有的xpath的设置都支持如下三种形式 84 | - xpath //*[contains(@resource-id, 'ddd')] 85 | - regex ^确定$ 86 | - contains关系 取消 确定 87 | # 2.0.0 88 | 支持macaca[完成] 89 | 失败重试[完成] 90 | 支持简单的测试用例[完成] 91 | 架构重新设计[完成] 92 | 新老版本对比报告改进[完成] 93 | # 1.9.0 94 | 支持遍历断言[完成] 95 | 支持历史对比断言[完成] 96 | 修正不支持uiautomator2的问题[完成] 97 | 支持yaml自动化测试用例[完成] 98 | action支持长按[完成] 99 | 重构用例生成方式[完成] 100 | 101 | # 1.8.0 102 | 对子菜单的支持, 智能判断是否有子菜单 103 | 支持断点续传机制 104 | 支持自动重启appium机制, 用于防止iOS遍历内存占用太大问题 105 | 分离插件到独立项目 106 | 107 | # 1.7.0 108 | android跳到其他app后自动后退[完成] 109 | 截图复用优化提速 [完成] 110 | 报告增加点击前后的截图 [完成] 111 | 独立的report子命令 [完成] 112 | 配置支持动态指令 [完成] 113 | 配置与老版本不兼容 [重要提醒] 114 | 支持自定义报告title [完成] 115 | 116 | # 1.6.0 [内测] 117 | 增加动态插件 [完成] 118 | 支持beforeElementAction的afterElementAction配置 [完成] 119 | 修复app的http连接支持 [完成] 120 | 支持url白名单 [完成] 121 | 支持defineUrl的xpath属性提取 [完成] 122 | 未遍历控件用测试用例的cancel状态表示 [完成] 123 | 两次back之间的时间间隔设定为不低于4s防止粘连 [完成] 124 | 125 | # 1.5.0 126 | 配置文件内容变更 此版本不再向下兼容, 推荐使用yaml配置文件 127 | 标准的html报告 [完成] 128 | windows下中文编码问题 [完成] 129 | windows下命令行超长问题[完成] 130 | 加入yaml配置格式支持并添加注释 [完成] 131 | startupActions支持scala表达式 [完成] 132 | 133 | # 1.4.0 134 | 元素点击之前开始截图并高亮要点击的控件[完成] 135 | 修复freemind文件无法打开的问题[完成] 136 | # 1.3.1 137 | 增加最大后退尝试次数 138 | 增加跳出app的判断 139 | bugfix: 140 | 解决文件名特殊符号问题 141 | 修复不同界面漏掉截图的问题 142 | 143 | # 1.3.0 144 | 145 | # 1.2.2 146 | 支持相对路径的apk地址. 147 | android的端口指定不再使用4730而是和ios一样 148 | # 1.2.1 149 | url定义优化, 内容变更改进 150 | 支持自动化测试. 添加了兼容性测试的例子 151 | # 1.2.0 152 | 兼容appium1.5去掉了不支持的findElementByName方法 153 | 对xpath元素查找进行了优化 解析dom结构时生成合适的xpath表达式 154 | # 1.1.4 155 | 增加log和tagLimit两个插件 156 | 截图时间超过5s自动跳过 157 | 增加Android和iOS的log输出 158 | 增加scroll方向支持 159 | # 1.1.3 160 | 自动判断页面是否变化. 界面变化才截图. 能减少大量的重复截图 161 | 导出界面dom结构用于diff分析 162 | # 1.1.2 163 | 增加-t参数. 支持最大遍历时间 164 | 增加-o参数, 支持设定结果目录 165 | # 1.1.1 166 | 增加每个url最大滚动的次数 167 | # 1.1.0 168 | 增加思维导图生成 169 | 增加插件支持 170 | 清理大量的无关文件和测试用例 171 | 172 | # 1.0.1 173 | 支持基本遍历 174 | 支持命令行运行方式 175 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/AppiumTouchAction.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.TouchAction; 5 | import io.appium.java_client.touch.TapOptions; 6 | import io.appium.java_client.touch.WaitOptions; 7 | import io.appium.java_client.touch.offset.ElementOption; 8 | import io.appium.java_client.touch.offset.PointOption; 9 | import org.openqa.selenium.WebElement; 10 | 11 | import java.time.Duration; 12 | 13 | public class AppiumTouchAction { 14 | TouchAction action; 15 | int width; 16 | int height; 17 | public AppiumTouchAction(AppiumDriver driver){ 18 | action=new TouchAction(driver); 19 | } 20 | public AppiumTouchAction(AppiumDriver driver, int width, int height){ 21 | action=new TouchAction(driver); 22 | this.width=width; 23 | this.height=height; 24 | } 25 | public AppiumTouchAction swipe(Double startX, Double startY, Double endX, Double endY){ 26 | action.press( 27 | PointOption.point((int)(width*startX), (int)(height*startY))) 28 | .waitAction(WaitOptions.waitOptions(Duration.ofSeconds(1))) 29 | .moveTo(PointOption.point((int)(width*endX), (int)(height*endY))) 30 | .release() 31 | .perform(); 32 | return this; 33 | } 34 | 35 | public AppiumTouchAction tap(WebElement currentElement){ 36 | action.tap( 37 | TapOptions.tapOptions().withElement(ElementOption.element(currentElement)) 38 | ).perform(); 39 | return this; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/Demo.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler; 2 | 3 | public class Demo { 4 | public void hello(){ 5 | 6 | System.out.println("hello"); 7 | AppCrawler$.MODULE$.crawler().conf().resultDir(); 8 | //URIElement element=new URIElement(new HashMap(), "xx"); 9 | 10 | //new URIElementStore().elementStore().get("xxx").get().reqDom() 11 | 12 | } 13 | 14 | public void report(String path){ 15 | //load(); 16 | //analyse(); 17 | //allure(); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/DemoPlugin.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler; 2 | 3 | import com.ceshiren.appcrawler.core.Crawler; 4 | import com.ceshiren.appcrawler.model.URIElement; 5 | import com.ceshiren.appcrawler.plugin.Plugin; 6 | 7 | import static com.ceshiren.appcrawler.utils.Log.log; 8 | 9 | public class DemoPlugin extends Plugin { 10 | @Override 11 | public void init(Crawler crawler) { 12 | System.out.println("demo init"); 13 | log.debug("demo init"); 14 | } 15 | 16 | @Override 17 | public void beforeElementAction(URIElement element) { 18 | log.info(element); 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/GetAPKPackage.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.Objects; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | // 获取APK信息 11 | public class GetAPKPackage { 12 | 13 | // 获取SDK目录下最高版本的文件夹名称 14 | public static String getHighestVersion(String[] subDirs) { 15 | String result = ""; 16 | for (String dirName : subDirs) { 17 | if (result.equals("")) { 18 | result = dirName; 19 | } 20 | int dirVersion = Integer.parseInt(dirName.split("\\.", 2)[0]); 21 | int resultVersion = Integer.parseInt(result.split("\\.", 2)[0]); 22 | if (dirVersion > resultVersion) { 23 | result = dirName; 24 | } 25 | } 26 | return result; 27 | } 28 | 29 | // 获取SDK下的AAPT应用路径 30 | public static String getAAPTPath() throws NoSuchFieldException { 31 | if(System.getenv("ANDROID_HOME")==null){ 32 | return ""; 33 | } 34 | String buildToolsBasePath = System.getenv("ANDROID_HOME") + "/build-tools"; 35 | File buildToolsPath = new File(buildToolsBasePath); 36 | String highestVersionDirName = getHighestVersion(Objects.requireNonNull(buildToolsPath.list())); 37 | return buildToolsBasePath + "/" + highestVersionDirName + "/" + "aapt"; 38 | } 39 | 40 | // 获取APK的package信息,如果返回为null说明未获取到数据 41 | public static String getPackageName(String apkFilePath) throws NoSuchFieldException { 42 | // 如果AAPT位置未获取到,直接返回null 43 | String aaptPath = getAAPTPath(); 44 | if(aaptPath.equals("")){ 45 | return null; 46 | } 47 | String cmd = aaptPath+ " dump badging " + apkFilePath; 48 | Runtime runtime = Runtime.getRuntime(); 49 | try { 50 | Process process = runtime.exec(cmd); 51 | byte[] b = new byte[1000]; 52 | int num = 0; 53 | while ((num = process.getInputStream().read(b)) != -1) { 54 | String infoStr = new String(b, StandardCharsets.UTF_8); 55 | Pattern splitPattern = Pattern.compile("package: name=\\'(.*?)\\'"); 56 | if (infoStr.contains("package")) { 57 | Matcher res = splitPattern.matcher(infoStr); 58 | if (res.find()) { 59 | return res.group(1); 60 | } 61 | } 62 | } 63 | return null; 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | return null; 67 | } 68 | } 69 | 70 | } 71 | 72 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/diff/Report.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.diff; 2 | 3 | import com.ceshiren.appcrawler.model.URIElementStore; 4 | 5 | public class Report { 6 | private boolean showCancel=false; 7 | private String title="AppCrawler"; 8 | private String master=""; 9 | private String candidate=""; 10 | private String reportDir=""; 11 | private URIElementStore store=new URIElementStore(); 12 | 13 | public URIElementStore loadResult() { 14 | return store; 15 | } 16 | 17 | public boolean getShowCancel() { 18 | return this.showCancel; 19 | } 20 | 21 | public void setShowCancel(boolean str) { 22 | this.showCancel=str; 23 | } 24 | 25 | public String getTitle() { 26 | return this.title; 27 | } 28 | 29 | public void setTitle(String str) { 30 | this.title=str; 31 | } 32 | 33 | public String getMaster() { 34 | return this.master; 35 | } 36 | 37 | public void setMaster(String str) { 38 | this.master=str; 39 | } 40 | 41 | public String getCandidate() { 42 | return this.candidate; 43 | } 44 | 45 | public void setCandidate(String str) { 46 | this.candidate=str; 47 | } 48 | 49 | public String getReportDir() { 50 | return this.reportDir; 51 | } 52 | 53 | public void setReportDir(String str) { 54 | this.reportDir=str; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/report/AllureDiffTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import io.qameta.allure.Allure; 4 | import io.qameta.allure.Epic; 5 | import io.qameta.allure.Feature; 6 | import org.junit.jupiter.api.DynamicTest; 7 | import org.junit.jupiter.api.TestFactory; 8 | 9 | import java.io.FileInputStream; 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.stream.Stream; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertNotNull; 17 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 18 | 19 | @Epic("新老版本对比") 20 | public class AllureDiffTest { 21 | String mResImg = null, mReqImg, cResImg, cReqImg; 22 | @Feature("当前页面") 23 | @TestFactory 24 | Stream dynamicTestsExample() throws Exception { 25 | List dynamicTests = new ArrayList<>(); 26 | ReadYaml read = new ReadYaml(); 27 | Map mapRoot = read.convert2Map("/tmp/xueQiu400/reportDiff/diff.yml"); 28 | //System.out.println(mapRoot); 29 | for(Object key : mapRoot.keySet()) { 30 | //System.out.println(key + ":" + mapRoot.get(key)); 31 | Map mapBtn = (HashMap)mapRoot.get(key); 32 | for(Object keyBtn : mapBtn.keySet()){ 33 | //System.out.println(keyBtn + ":" + mapBtn.get(keyBtn)); 34 | Map mapBtnDetail = (HashMap)mapBtn.get(keyBtn); 35 | for(Object keyBtnDetail : mapBtnDetail.keySet()) { 36 | //System.out.println(keyBtnDetail + ":" + mapBtnDetail.get(keyBtnDetail)); 37 | //System.out.println(mapBtnDetail.get(keyBtnDetail)); 38 | List list = (List)mapBtnDetail.get(keyBtnDetail); 39 | //System.out.println(list.get(0) + "," + list.get(1)); 40 | Map list0 = list.get(0); 41 | Map list1 = list.get(1); 42 | 43 | for(Object key0 : list0.keySet()){ 44 | //c:new m:old 45 | 46 | String img = list0.get(key0).toString().equals("") ? "src/main/resources/404.png" : list0.get(key0).toString(); 47 | switch (key0.toString()){ 48 | case "cReqImg": 49 | cReqImg = img; 50 | break; 51 | case "mResImg": 52 | mResImg = img; 53 | break; 54 | case "mReqImg": 55 | mReqImg = img; 56 | break; 57 | case "cResImg": 58 | cResImg = img; 59 | break; 60 | } 61 | 62 | } 63 | 64 | dynamicTests.add(dynamicTest(key + "..." + keyBtnDetail.toString(), 65 | () -> assertNotNull(addHtml("id", "tag") 66 | + lifeCycleAttach(mResImg,"image/png", ".png", new FileInputStream(mResImg)) 67 | + lifeCycleAttach(cResImg,"image/png", ".png", new FileInputStream(cResImg))))); 68 | } 69 | } 70 | } 71 | return dynamicTests.stream(); 72 | } 73 | 74 | public String addHtml(String id, String tag){ 75 | Allure.addDescriptionHtml("
id"+id+"
tag"+tag+"
"); 76 | return null; 77 | } 78 | 79 | public String lifeCycleAttach(String name, String type, String extraName, FileInputStream path){ 80 | Allure.getLifecycle().addAttachment(name, type, extraName, path); 81 | return null; 82 | } 83 | 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/report/GenerateClassByJavassist.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import com.ceshiren.appcrawler.plugin.junit5.AllureTemplate; 4 | import javassist.ClassPool; 5 | import javassist.CtClass; 6 | import javassist.CtConstructor; 7 | 8 | 9 | public class GenerateClassByJavassist { 10 | String ClassName, path; 11 | public GenerateClassByJavassist(String ClassName, String path) throws Exception{ 12 | this.ClassName = ClassName; 13 | this.path = path; 14 | init(path); 15 | } 16 | public void init(String path) throws Exception { 17 | 18 | String activity = "com.xueqiu.android." + ClassName; 19 | System.out.println(activity); 20 | //ClassPool:CtClass对象的容器 21 | ClassPool pool = ClassPool.getDefault(); 22 | 23 | //通过ClassPool生成一个public新类ClassName 24 | CtClass ctClass = pool.makeClass(ClassName + "Tests"); 25 | //声明父类 26 | ctClass.setSuperclass(pool.get(AllureTemplate.class.getName())); 27 | 28 | //添加构造函数 29 | //CtConstructor ctConstructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass); 30 | CtConstructor ctConstructorNull = new CtConstructor(new CtClass[]{}, ctClass); 31 | StringBuffer buffer = new StringBuffer(); 32 | buffer.append("com.ceshiren.appcrawler.plugin.report.Report report=com.ceshiren.appcrawler.ReportFactory.genReport(\"junit5\");\n" + 33 | " com.ceshiren.appcrawler.data.AbstractElementStore store=report.loadResult(\"E://elements.yml\");\n" + 34 | " com.ceshiren.appcrawler.ReportFactory.initStore(store);\n" + 35 | " this.pageName=\""+ activity +"\";\n" + 36 | " com.ceshiren.appcrawler.ReportFactory.showCancel_$eq(true);"); 37 | System.out.println("{\n"+ buffer.toString() +"\n}"); 38 | //ctConstructor.setBody("{"+ buffer.toString() +"}"); 39 | ctConstructorNull.setBody("{"+ buffer.toString() +"}"); 40 | //把构造函数添加到新的类中 41 | ctClass.addConstructor(ctConstructorNull); 42 | //ctClass.addConstructor(ctConstructor); 43 | System.out.println("生成Activity类开始..."); 44 | //把生成的class文件写入文件夹 45 | ctClass.writeFile(path); 46 | System.out.println("生成Activity类完毕..."); 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/report/Junit5Report.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import com.ceshiren.appcrawler.plugin.report.Report; 4 | import org.junit.platform.launcher.Launcher; 5 | import org.junit.platform.launcher.LauncherDiscoveryRequest; 6 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; 7 | import org.junit.platform.launcher.core.LauncherFactory; 8 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener; 9 | import org.junit.platform.launcher.listeners.TestExecutionSummary; 10 | 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; 16 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; 17 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; 18 | 19 | public class Junit5Report extends Report { 20 | 21 | private Map map; 22 | private Set set = new HashSet<>(); 23 | //1.读取.yml文件中的activity 24 | public Junit5Report() throws Exception { 25 | ReadYaml readYaml = new ReadYaml(); 26 | map = readYaml.convert2Map("E://elements.yml"); 27 | Map mapAct = (Map)map.get("elementStore"); 28 | for(Object activity : mapAct.keySet()){ 29 | //截至目前,set中存储的是activity的名字,接下来开始第二步 30 | set.add(activity.toString().split("\\.")[3]); 31 | } 32 | } 33 | 34 | @Override 35 | public void genTestCase(String resultDir) { 36 | super.genTestCase(resultDir); 37 | for(String activityName : set){ 38 | System.out.println(activityName); 39 | try { 40 | new GenerateClassByJavassist(activityName, resultDir); 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | System.out.println("AllureTemplate的子类生成完毕..."); 46 | } 47 | 48 | @Override 49 | public void runTestCase(String namespace) { 50 | super.runTestCase(namespace); 51 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 52 | .selectors( 53 | selectDirectory("D:\\AppCrawler\\src\\test\\java\\com\\ElementInfo\\appcrawler\\report\\generateClass"), 54 | selectPackage("com.ceshiren.appcrawler.report.generateClass") 55 | ) 56 | .filters( 57 | includeClassNamePatterns(".*Tests") 58 | ) 59 | .build(); 60 | 61 | Launcher launcher = LauncherFactory.create(); 62 | 63 | // Register a listener of your choice 64 | SummaryGeneratingListener listener = new SummaryGeneratingListener(); 65 | launcher.registerTestExecutionListeners(listener); 66 | launcher.execute(request); 67 | 68 | TestExecutionSummary summary = listener.getSummary(); 69 | 70 | // Do something with the TestExecutionSummary. 71 | System.out.println("开始时间:" + summary.getTimeStarted()); 72 | System.out.println("结束时间:" + summary.getTimeFinished()); 73 | System.out.println("失败个数:" + summary.getTotalFailureCount()); 74 | System.out.println("成功个数:" + summary.getTestsSucceededCount()); 75 | 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/report/LoadYaml.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import com.ceshiren.appcrawler.model.URIElementStore; 4 | import org.ho.yaml.Yaml; 5 | 6 | import java.io.File; 7 | import java.io.FileNotFoundException; 8 | 9 | public class LoadYaml { 10 | public void loadYaml() throws FileNotFoundException { 11 | File dumpFile = new File("E://elements-test.yml"); 12 | //SimpleElementStore simpleElementStore = (SimpleElementStore) Yaml.load(dumpFile); 13 | URIElementStore simpleElementStore = Yaml.loadType(dumpFile, URIElementStore.class); 14 | System.out.println(simpleElementStore); 15 | //System.out.println(simpleElementStore.elementStore()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/report/MvnReplace.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import com.ceshiren.appcrawler.AppCrawler; 4 | import org.junit.platform.launcher.Launcher; 5 | import org.junit.platform.launcher.LauncherDiscoveryRequest; 6 | import org.junit.platform.launcher.TestExecutionListener; 7 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; 8 | import org.junit.platform.launcher.core.LauncherFactory; 9 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.InputStreamReader; 15 | import java.util.Properties; 16 | 17 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; 18 | 19 | public class MvnReplace { 20 | 21 | public static void setPro() throws Exception { 22 | 23 | System.out.println("setpro"); 24 | Properties pro = new Properties(); 25 | pro.load(MvnReplace.class.getResourceAsStream("/allure.properties")); 26 | 27 | if(AppCrawler.crawler().conf().resultDir().isEmpty()) { 28 | AppCrawler.crawler().conf().resultDir_$eq("."); 29 | } 30 | System.out.println(AppCrawler.crawler().conf().resultDir()); 31 | pro.setProperty("allure.results.directory", AppCrawler.crawler().conf().resultDir() + "/allure-results"); 32 | FileOutputStream out = new FileOutputStream(MvnReplace.class.getResource("/allure.properties").getPath()); 33 | pro.store(out, "new file"); 34 | 35 | } 36 | 37 | public static void runTest() throws Exception { 38 | System.out.println("runtest"); 39 | setPro(); 40 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 41 | .selectors( 42 | selectMethod("com.ceshiren.appcrawler.report.AllureTest", "dynamicTestsExample") 43 | ) 44 | .build(); 45 | 46 | Launcher launcher = LauncherFactory.create(); 47 | 48 | // Register a listener of your choice 49 | TestExecutionListener listener = new SummaryGeneratingListener(); 50 | launcher.registerTestExecutionListeners(listener); 51 | 52 | launcher.execute(request); 53 | } 54 | 55 | public static boolean isExist() { 56 | for (String path : System.getenv("path").split(File.pathSeparator)) { 57 | String allurePath = path + File.separator + "allure"; 58 | if (new File(allurePath).exists()) 59 | return true; 60 | } 61 | return false; 62 | } 63 | 64 | public static void executeCommand(String command) throws Exception { 65 | 66 | Runtime r = Runtime.getRuntime(); 67 | Process p = r.exec(command); 68 | BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); 69 | String inline; 70 | while ((inline = br.readLine()) != null) { 71 | System.out.println(inline); 72 | } 73 | br.close(); 74 | } 75 | } -------------------------------------------------------------------------------- /src/main/java/com/ceshiren/appcrawler/report/ReadYaml.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import org.yaml.snakeyaml.Yaml; 4 | 5 | import java.io.FileInputStream; 6 | import java.util.Map; 7 | 8 | public class ReadYaml { 9 | 10 | public Map convert2Map(String path) throws Exception { 11 | Yaml yaml = new Yaml(); 12 | Map map = (Map) yaml.load(new FileInputStream(path)); 13 | return map; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/io/appium/java_client/Setting.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * See the NOTICE file distributed with this work for additional 5 | * information regarding copyright ownership. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.appium.java_client; 18 | 19 | /** 20 | * Enums defining constants for Appium Settings which can be set and toggled during a test session. 21 | */ 22 | public enum Setting { 23 | 24 | IGNORE_UNIMPORTANT_VIEWS("ignoreUnimportantViews"), 25 | WAIT_FOR_IDLE_TIMEOUT("waitForIdleTimeout"), 26 | WAIT_FOR_SELECTOR_TIMEOUT("waitForSelectorTimeout"), 27 | WAIT_SCROLL_ACKNOWLEDGMENT_TIMEOUT("setScrollAcknowledgmentTimeout"), 28 | WAIT_ACTION_ACKNOWLEDGMENT_TIMEOUT("setActionAcknowledgmentTimeout"), 29 | KEY_INJECTION_DELAY("setKeyInjectionDelay"), 30 | NATIVE_WEB_TAP("nativeWebTap"); 31 | 32 | private String name; 33 | 34 | Setting(String name) { 35 | this.name = name; 36 | } 37 | 38 | public String toString() { 39 | return this.name; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/main/resources/404.png -------------------------------------------------------------------------------- /src/main/resources/log4j2.yaml: -------------------------------------------------------------------------------- 1 | Configuration: 2 | status: warn 3 | name: YAMLConfig 4 | # thresholdFilter: 5 | # level: trace 6 | appenders: 7 | Console: 8 | name: STDOUT 9 | target: SYSTEM_OUT 10 | PatternLayout: 11 | Pattern: "%d{yyyy-MM-dd HH:mm:ss} %p [%C{1}.%L.%M] %m%n" 12 | # File: 13 | # name: File 14 | # fileName: "${sys:logFilename}" 15 | # PatternLayout: 16 | # Pattern: "%d{yyyy-MM-dd HH:mm:ss} %p [%C{1}.%L.%M] %m%n" 17 | 18 | Loggers: 19 | logger: 20 | - name: com.ceshiren.appcrawler.utils.Log 21 | level: all 22 | additivity: false 23 | AppenderRef: 24 | - ref: STDOUT 25 | # - ref: File 26 | - name: com.brsanthu.googleanalytics.GoogleAnalytics 27 | level: fatal 28 | additivity: false 29 | AppenderRef: 30 | - ref: STDOUT 31 | # - ref: File 32 | 33 | Root: 34 | level: error 35 | AppenderRef: 36 | ref: STDOUT -------------------------------------------------------------------------------- /src/main/resources/template.mustache: -------------------------------------------------------------------------------- 1 | {{#items}} 2 | Name: {{name}} 3 | Price: {{price}} 4 | {{#features}} 5 | Feature: {{description}} 6 | {{/features}} 7 | {{/items}} 8 | 9 | 10 | 11 | 12 | {{#items}} 13 | class {{name}}{ 14 | int a={{price}} 15 | {{#features}} 16 | @Test 17 | pubic void test_{{description}} 18 | {{/features}} 19 | } 20 | {{/items}} -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/Banner.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler 2 | 3 | object Banner { 4 | val banner = 5 | """ 6 | |------------------------------------------------- 7 | |appcrawler v2.7.4 全平台自动遍历测试工具 8 | |Q&A: https://ceshiren.com/c/opensource/appcrawler 9 | |author: 思寒_seveniruby 天马 霍格沃兹测试开发学社 10 | |------------------------------------------------- 11 | |""".stripMargin 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/core/AppiumSuite.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.core 2 | 3 | import org.scalatest._ 4 | import org.scalatest.selenium.WebBrowser 5 | /** 6 | * Created by seveniruby on 16/3/26. 7 | */ 8 | class AppiumSuite extends FunSuite 9 | with Matchers 10 | with BeforeAndAfterAll 11 | with BeforeAndAfterEach { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/core/AutomationSuite.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.core 2 | 3 | import com.ceshiren.appcrawler.model.URIElement 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | import com.ceshiren.appcrawler.utils.TData 6 | import org.scalatest 7 | import org.scalatest.{BeforeAndAfterAllConfigMap, ConfigMap, FunSuite, Matchers} 8 | 9 | /** 10 | * Created by seveniruby on 2017/4/17. 11 | */ 12 | class AutomationSuite extends FunSuite with Matchers with BeforeAndAfterAllConfigMap { 13 | var crawler: Crawler = _ 14 | 15 | override def beforeAll(configMap: ConfigMap): Unit = { 16 | log.info("beforeAll") 17 | crawler = configMap.get("crawler").get.asInstanceOf[Crawler] 18 | } 19 | 20 | 21 | //todo: 利用suite排序进入延迟执行 22 | 23 | test("run steps") { 24 | log.info("testcase start") 25 | val conf = crawler.conf 26 | val driver = crawler.driver 27 | 28 | val cp = new scalatest.Checkpoints.Checkpoint 29 | 30 | conf.testcase.steps.foreach(step => { 31 | log.info(TData.toYaml(step)) 32 | val xpath = step.getXPath() 33 | val action = step.getAction() 34 | 35 | driver.getNodeListByKey(xpath).headOption match { 36 | case Some(v) => { 37 | log.debug(v) 38 | val ele = new URIElement(v, "Steps") 39 | ele.setAction(action) 40 | log.debug(ele) 41 | // testcase里的操作也要记录下来 42 | crawler.beforeElementAction(ele) 43 | crawler.doElementAction(ele) 44 | crawler.afterElementAction(ele) 45 | } 46 | case None => { 47 | //用于生成steps的用例 48 | val ele = new URIElement("Steps", "", "", "", "NOT_FOUND", "", "", "", "", "", "xpath", "", "", 0, 0, 0, 0, "") 49 | 50 | ele.setAction("_Log") 51 | // testcase里的操作也要记录下来 52 | crawler.beforeElementAction(ele) 53 | crawler.doElementAction(ele) 54 | crawler.afterElementAction(ele) 55 | withClue("NOT_FOUND") { 56 | log.info(xpath) 57 | fail(s"ELEMENT_NOT_FOUND xpath=${xpath}") 58 | } 59 | } 60 | } 61 | 62 | 63 | if (step.then != null) { 64 | step.then.foreach(existAssert => { 65 | cp { 66 | withClue(s"${existAssert} 不存在\n") { 67 | val result = driver.getNodeListByKey(existAssert) 68 | log.info(s"${existAssert}\n${TData.toJson(result)}") 69 | result.size should be > 0 70 | } 71 | } 72 | }) 73 | } 74 | }) 75 | 76 | cp.reportAll() 77 | log.info("finish run steps") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/core/ReactTestCase.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.core 2 | 3 | import com.ceshiren.appcrawler.model.Step 4 | 5 | case class ReactTestCase(name: String = "", steps: List[Step] = List[Step]()) 6 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/core/Status.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.core 2 | 3 | import com.fasterxml.jackson.core.`type`.TypeReference 4 | 5 | object Status extends Enumeration { 6 | type Status = Value 7 | val READY = Value 8 | val CLICKED = Value 9 | val SKIPPED = Value 10 | } 11 | 12 | 13 | class StatusType extends TypeReference[Status.type] 14 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/driver/BaseDDT.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | 5 | import java.io.IOException; 6 | import java.nio.charset.StandardCharsets; 7 | 8 | import static com.ceshiren.appcrawler.utils.Log.log; 9 | 10 | public class BaseDDT { 11 | public String shell(String cmd) throws IOException, InterruptedException { 12 | log.debug(cmd); 13 | Process process = Runtime.getRuntime().exec(cmd); 14 | process.waitFor(); 15 | StringBuilder r=new StringBuilder(); 16 | String out = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); 17 | if(!out.isEmpty()){ 18 | r.append(out); 19 | } 20 | String err = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8); 21 | if(!err.isEmpty()){ 22 | log.warn(err); 23 | r.append(err); 24 | } 25 | 26 | return r.toString(); 27 | } 28 | 29 | public String format(String format, String... strings){ 30 | return String.format(format, strings); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/driver/MockDDT.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver; 2 | 3 | import org.openqa.selenium.By; 4 | 5 | import static com.ceshiren.appcrawler.utils.Log.log; 6 | 7 | public class MockDDT { 8 | String driver; 9 | String currentElement; 10 | String currentBy; 11 | 12 | public void chrome() { 13 | log.info("driver = new ChromeDriver();"); 14 | } 15 | 16 | public String find(By by) { 17 | log.info("currentElement = driver.findElement(by);"); 18 | return currentElement; 19 | } 20 | 21 | public String id(String value) { 22 | log.info(String.format("currentBy = By.id({0});", value)); 23 | return currentBy; 24 | } 25 | 26 | public void click(){ 27 | 28 | log.info("currentElement.click();"); 29 | 30 | } 31 | 32 | public void click(By by){ 33 | log.info("driver.findElement(by).click();"); 34 | } 35 | 36 | public String get(String attributeName){ 37 | log.info("currentElement.getAttribute(attributeName);"); 38 | String attributeValue = "value demo"; 39 | return attributeValue; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/driver/MockDriver.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver 2 | 3 | import com.ceshiren.appcrawler.core.CrawlerConf 4 | import com.ceshiren.appcrawler.model.URIElement 5 | import com.ceshiren.appcrawler.utils.Log.log 6 | import org.openqa.selenium.Rectangle 7 | 8 | import java.io.File 9 | import scala.io.Source 10 | import scala.sys.process._ 11 | 12 | /** 13 | * Created by seveniruby on 18/10/31. 14 | * 用于测试用途 15 | */ 16 | class MockDriver extends ReactWebDriver { 17 | var conf: CrawlerConf = _ 18 | 19 | var packageName = "" 20 | var activityName = "" 21 | 22 | def this(configMap: Map[String, Any] = Map[String, Any]()) { 23 | this 24 | 25 | val url=configMap.getOrElse("appium", "http://127.0.0.1:4723/wd/hub") 26 | log.info(s"url=${url}") 27 | packageName = configMap.getOrElse("appPackage", "").toString 28 | activityName = configMap.getOrElse("appActivity", "").toString 29 | } 30 | 31 | 32 | override def event(keycode: String): Unit = { 33 | log.error("not implement") 34 | } 35 | 36 | //todo: outside of Raster 问题 37 | override def getDeviceInfo(): Unit = { 38 | log.error("not implement") 39 | } 40 | 41 | 42 | override def swipe(startX: Double = 0.9, startY: Double = 0.1, endX: Double = 0.9, endY: Double = 0.1): Unit = { 43 | log.error("not implement") 44 | } 45 | 46 | 47 | override def screenshot(): File = { 48 | val file = File.createTempFile("tmp", ".png") 49 | log.info(file.getAbsolutePath) 50 | file 51 | } 52 | 53 | override def click(): this.type = { 54 | this 55 | } 56 | 57 | override def tap(): this.type = { 58 | click() 59 | } 60 | 61 | override def tapLocation(x: Int, y: Int): this.type = { 62 | this 63 | } 64 | 65 | override def longTap(): this.type = { 66 | log.error("not implement") 67 | this 68 | } 69 | 70 | override def back(): Unit = { 71 | } 72 | 73 | override def backApp(): Unit = { 74 | } 75 | 76 | override def getPageSource(): String = { 77 | Source.fromFile("src/test/scala/com/ceshiren/appcrawler/ut/miniprogram.xml").mkString 78 | } 79 | 80 | override def getAppName(): String = { 81 | "com.ceshiren.appcrawler.mockapp" 82 | } 83 | 84 | override def getUrl(): String = { 85 | "DemoActivity" 86 | } 87 | 88 | override def getRect(): Rectangle = { 89 | //selenium下还没有正确的赋值,只能通过api获取 90 | null 91 | } 92 | 93 | override def sendKeys(content: String): Unit = { 94 | } 95 | 96 | override def launchApp(): Unit = { 97 | //driver.get(capabilities.getCapability("app").toString) 98 | back() 99 | } 100 | 101 | override def findElements(element: URIElement, findBy: String): List[AnyRef] = { 102 | List(element) 103 | } 104 | 105 | override def sendText(text: String): Unit = { 106 | 107 | } 108 | 109 | def getAdb(): String = { 110 | List(System.getenv("ANDROID_HOME"), "platform-tools/adb").mkString(File.separator) 111 | } 112 | 113 | def shell(cmd: String): String = { 114 | log.info(cmd) 115 | val result = cmd.!! 116 | log.info(result) 117 | result 118 | } 119 | 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/driver/SeleniumDDT.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver; 2 | 3 | import org.openqa.selenium.By; 4 | import org.openqa.selenium.WebDriver; 5 | import org.openqa.selenium.WebElement; 6 | import org.openqa.selenium.chrome.ChromeDriver; 7 | 8 | public class SeleniumDDT { 9 | WebDriver driver; 10 | WebElement currentElement; 11 | By currentBy; 12 | 13 | public void chrome() { 14 | driver = new ChromeDriver(); 15 | } 16 | 17 | public void get(String url) { 18 | driver.get(url); 19 | } 20 | 21 | public WebElement find(By by) { 22 | currentElement = driver.findElement(by); 23 | return currentElement; 24 | } 25 | 26 | public By id(String value) { 27 | currentBy = By.id(value); 28 | return currentBy; 29 | } 30 | 31 | public void click() { 32 | driver.findElement(currentBy).click(); 33 | } 34 | 35 | public void click(By by) { 36 | driver.findElement(by).click(); 37 | } 38 | 39 | public void sendKeys(String text) { 40 | driver.findElement(currentBy).sendKeys(text); 41 | } 42 | 43 | public String attribute(String attributeName) { 44 | return driver.findElement(currentBy).getAttribute(attributeName); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/BDDTestCase.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | import com.ceshiren.appcrawler.driver.{BaseDDT, MockDDT, SeleniumDDT} 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | 6 | import scala.collection.mutable.ListBuffer 7 | 8 | case class BDDTestCase( 9 | name: String = "", 10 | given: List[Map[String, Any]] = List(), 11 | when: List[Map[String, Any]] = List(), 12 | `then`: List[Map[String, Any]] = List() 13 | ) { 14 | private val bddDriverList: ListBuffer[AnyRef] = ListBuffer(new BaseDDT); 15 | 16 | def run(): Unit = { 17 | when.foreach(step => { 18 | runStep(step) 19 | }) 20 | } 21 | 22 | def runStep(step: Map[String, Any]): Any = { 23 | log.debug(step) 24 | val methodName = getMethodName(step) 25 | if (methodName == "driver") { 26 | val arg = getArgs(step).toString 27 | create(arg) 28 | } 29 | 30 | bddDriverList.foreach(driver => { 31 | 32 | driver.getClass.getDeclaredMethods.filter(_.getName == methodName).foreach(method => { 33 | log.debug(method) 34 | if (method.getParameterCount == 0) { 35 | return method.invoke(driver) 36 | } else{ 37 | val args = getArgs(step).asInstanceOf[List[Any]] 38 | 39 | method.getParameterTypes.foreach(paramType=>{ 40 | 41 | }) 42 | 43 | // val firstParamType = method.getParameterTypes.toList.head 44 | // arg match { 45 | // case _ if arg != null && arg.getClass == firstParamType => 46 | // return method.invoke(driver, arg) 47 | // case argStep: Map[String, Any] => 48 | // val r = runStep(argStep) 49 | // log.debug(r) 50 | // return method.invoke(driver, r) 51 | // case _ => 52 | // log.error(s"$method ( $arg) not found") 53 | // } 54 | } 55 | }) 56 | }) 57 | } 58 | 59 | def getMethodName(step: Map[String, Any]): String = { 60 | step.keys.head 61 | } 62 | 63 | def getArgs(step: Map[String, Any]): Any = { 64 | step.values.head 65 | } 66 | 67 | def create(BDDDriver: String): Unit = { 68 | 69 | BDDDriver match { 70 | case "selenium" => { 71 | val driver = new SeleniumDDT() 72 | bddDriverList.append(driver) 73 | } 74 | case "mock" => { 75 | bddDriverList.append(new MockDDT) 76 | } 77 | case default => { 78 | log.warn(s"$default not found") 79 | } 80 | } 81 | if (bddDriverList.isEmpty) { 82 | bddDriverList.append(new MockDDT()) 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/DataRecord.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | import com.ceshiren.appcrawler.utils.Log.log 4 | 5 | import scala.collection.mutable.ListBuffer 6 | 7 | /** 8 | * Created by seveniruby on 16/8/25. 9 | */ 10 | class DataRecord { 11 | val record=ListBuffer[(Long, Any)]() 12 | def append(any: Any): Unit ={ 13 | record.append(System.currentTimeMillis()->any) 14 | } 15 | def intervalMS(): Long ={ 16 | if(record.size<2){ 17 | return 0 18 | }else { 19 | val lastRecords = record.takeRight(2) 20 | lastRecords.last._1 - lastRecords.head._1 21 | } 22 | } 23 | def isDiff(): Boolean ={ 24 | if(record.size<2){ 25 | log.info("just only record return false") 26 | return false 27 | }else { 28 | val lastRecords = record.takeRight(2) 29 | lastRecords.last._2 != lastRecords.head._2 30 | } 31 | } 32 | def last(count: Int): List[Any] ={ 33 | record.takeRight(count).map(_._2).toList 34 | } 35 | def pre(): Any ={ 36 | record.takeRight(2).head._2 37 | } 38 | def last(): Any ={ 39 | record.last._2 40 | } 41 | def pop(): Unit ={ 42 | if(record.size>0) { 43 | record.remove(record.size - 1) 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/ElementInfo.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | import com.ceshiren.appcrawler.core.Status.Status 4 | import com.ceshiren.appcrawler.core.{Status, StatusType} 5 | import com.fasterxml.jackson.module.scala.JsonScalaEnumeration 6 | 7 | case class ElementInfo( 8 | var reqDom: String = "", 9 | var resDom: String = "", 10 | var reqHash: String = "", 11 | var resHash: String = "", 12 | var reqImg: String = "", 13 | var resImg: String = "", 14 | var reqTime: String = "", 15 | var resTime: String = "", 16 | var clickedIndex: Int = -1, 17 | @JsonScalaEnumeration(classOf[StatusType]) 18 | var action: Status.Status = Status.READY, 19 | var element: URIElement =new URIElement() 20 | ) { 21 | def this() { 22 | this("", "", "", "", "", "", "", "", -1, Status.READY, null) 23 | } 24 | 25 | def getElement = { 26 | element 27 | } 28 | 29 | def setElement(element: URIElement): Unit = { 30 | this.element = element 31 | } 32 | 33 | def setAction(status: Status): Unit = { 34 | this.action = status 35 | } 36 | 37 | def setClickedIndex(index: Int): Unit = { 38 | this.clickedIndex = index 39 | } 40 | 41 | def getAction: Status = { 42 | action 43 | } 44 | 45 | def setReqDom(dom: String): Unit = { 46 | this.reqDom = dom 47 | } 48 | 49 | def setReqImg(img: String): Unit = { 50 | this.reqImg = img 51 | } 52 | 53 | def getReqDom: String = { 54 | reqDom 55 | } 56 | 57 | def getReqImg: String = { 58 | reqImg 59 | } 60 | 61 | def setResDom(resDom: String): Unit = { 62 | this.resDom = resDom 63 | } 64 | 65 | def setResImg(resImg: String): Unit = { 66 | this.resImg = resImg 67 | } 68 | 69 | def getResDom: String = { 70 | resDom 71 | } 72 | 73 | def getResImg: String = { 74 | resImg 75 | } 76 | 77 | def setReqHash(reqHash: String): Unit = { 78 | this.reqHash = reqHash 79 | } 80 | 81 | def setResHash(resHash: String): Unit = { 82 | this.resHash = resHash 83 | } 84 | 85 | def getReqHash: String = { 86 | reqHash 87 | } 88 | 89 | def getResHash: String = { 90 | resHash 91 | } 92 | 93 | def getClickedIndex: Int = { 94 | clickedIndex 95 | } 96 | 97 | def setReqTime(reqTime: String): Unit = { 98 | this.reqTime = reqTime 99 | } 100 | 101 | def setResTime(resTime: String): Unit = { 102 | this.resTime = resTime 103 | } 104 | 105 | def getReqTime: String = { 106 | reqTime 107 | } 108 | 109 | def getResTime: String = { 110 | resTime 111 | } 112 | } -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/Given.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | class Given extends { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/Node.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Stack; 6 | 7 | public class Node { 8 | static Stack stack=new Stack<>(); 9 | static Node current; 10 | Object data; 11 | List children=new ArrayList<>(); 12 | 13 | public Node(Object data){ 14 | this.data=data; 15 | } 16 | public void append(Node node){ 17 | Node.current.children.add(node); 18 | Node.stack.push(node); 19 | Node.current=node; 20 | } 21 | 22 | public void back(){ 23 | Node.current=Node.stack.pop(); 24 | } 25 | 26 | public void current(){ 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/PageSource.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model; 2 | 3 | import com.ceshiren.appcrawler.utils.XPathUtil; 4 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; 5 | import org.w3c.dom.Document; 6 | import scala.collection.immutable.List; 7 | import scala.collection.immutable.Map; 8 | 9 | import java.util.Arrays; 10 | 11 | import static com.ceshiren.appcrawler.utils.Log.log; 12 | 13 | public class PageSource { 14 | private String xmlSource; 15 | private Document currentPageDom; 16 | private List> nodeListAll; 17 | private XmlMapper mapper = new XmlMapper(); 18 | 19 | public static PageSource getPagefromDocument(Document document) { 20 | PageSource pageSource = new PageSource(); 21 | pageSource.fromDocument(document); 22 | return pageSource; 23 | } 24 | 25 | public static PageSource getPagefromXML(String xmlSource) { 26 | PageSource pageSource = null; 27 | try { 28 | pageSource = new PageSource(); 29 | pageSource.fromXML(xmlSource); 30 | }catch (Exception e){ 31 | log.error(e.getMessage()); 32 | e.printStackTrace(); 33 | } 34 | return pageSource; 35 | } 36 | 37 | 38 | public void fromDocument(Document document) { 39 | currentPageDom = document; 40 | } 41 | 42 | public void fromXML(String xmlSource) { 43 | this.xmlSource = xmlSource; 44 | this.currentPageDom=XPathUtil.toDocument(xmlSource); 45 | } 46 | 47 | public String toXML() { 48 | return xmlSource; 49 | } 50 | 51 | public void fromJSON(String jsonSource) { 52 | 53 | } 54 | 55 | 56 | public void demo() { 57 | log.trace("page source java demo"); 58 | } 59 | 60 | public List> getNodeListByKey(String key) { 61 | String[] regexPatternList = new String[]{"/.*", "\\(.*", "string\\(/.*\\)"}; 62 | if (Arrays.stream(regexPatternList).anyMatch(key::matches)) { 63 | return XPathUtil.getNodeListByXPath(key, currentPageDom); 64 | } else if (key.startsWith("^") || key.contains(".*")) { 65 | return getNodeListAll().filter(node -> node.get("name").toString().matches(key) || 66 | node.get("label").toString().matches(key) || 67 | node.get("value").toString().matches(key)); 68 | } else { 69 | return getNodeListAll().filter(node -> node.get("name").toString().contains(key) || 70 | node.get("label").toString().contains(key) || 71 | node.get("value").toString().contains(key)); 72 | } 73 | } 74 | 75 | public List> getNodeListAll() { 76 | if (nodeListAll == null) { 77 | nodeListAll = XPathUtil.getNodeListByXPath("//*[not(*)]", currentPageDom); 78 | } 79 | return nodeListAll; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/ScreenShot.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | import com.ceshiren.appcrawler.utils.Log.log 4 | import com.ceshiren.appcrawler.utils.LogicUtils.tryAndCatch 5 | import org.apache.commons.io.FileUtils 6 | 7 | import java.awt.{BasicStroke, Color} 8 | import java.io.File 9 | import javax.imageio.ImageIO 10 | import scala.collection.mutable 11 | 12 | class ScreenShot { 13 | private var currentIMG: File = null; 14 | 15 | def this(file: File) { 16 | this 17 | currentIMG = file 18 | } 19 | 20 | def save(file: File): Unit = { 21 | currentIMG = file 22 | } 23 | 24 | def get(): File = { 25 | currentIMG 26 | } 27 | 28 | def saveToFile(path: String): Unit = { 29 | FileUtils.copyFile(currentIMG, new File(path)) 30 | } 31 | 32 | def clip(newImageName: String, x: Int, y: Int, w: Int, h: Int, screenWidth: Int, screenHeight: Int): Unit = { 33 | val img = ImageIO.read(ScreenShot.last().get()) 34 | val graph = img.createGraphics() 35 | 36 | if (img.getWidth > screenWidth) { 37 | log.info("scale the origin image") 38 | graph.drawImage(img, 0, 0, screenWidth, screenHeight, null) 39 | } 40 | graph.setStroke(new BasicStroke(5)) 41 | graph.setColor(Color.RED) 42 | graph.drawRect(x, y, w, h) 43 | graph.dispose() 44 | 45 | log.info(s"write png ${newImageName}") 46 | if (img.getWidth > screenWidth) { 47 | log.info("scale the origin image and save") 48 | //fixed: RasterFormatException: (y + height) is outside of Raster 横屏需要处理异常 49 | val subImg = tryAndCatch(img.getSubimage(0, 0, screenWidth, screenHeight)) match { 50 | case Some(value) => value 51 | case None => { 52 | img.getSubimage(0, 0, screenWidth, screenHeight) 53 | } 54 | } 55 | ImageIO.write(subImg, "png", new java.io.File(newImageName)) 56 | } else { 57 | log.info(s"ImageIO.write newImageName ${newImageName}") 58 | ImageIO.write(img, "png", new java.io.File(newImageName)) 59 | } 60 | } 61 | } 62 | 63 | object ScreenShot { 64 | val queue = mutable.Queue[ScreenShot]() 65 | 66 | def save(file: File): Unit = { 67 | val screen = new ScreenShot(file) 68 | queue.enqueue(screen) 69 | if (queue.size > 2) { 70 | queue.dequeue() 71 | } 72 | } 73 | 74 | def last(): ScreenShot = { 75 | if (queue.nonEmpty) { 76 | queue.last 77 | } else { 78 | null 79 | } 80 | } 81 | 82 | def pre(): ScreenShot = { 83 | if (queue.size >= 2) { 84 | queue(queue.size - 2) 85 | } else { 86 | null 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/Step.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | /** 4 | * 当given先决条件满足时,在when的find指定内容上执行action操作。并断言then里面的所有表达式 5 | * 6 | * @param given 7 | * @param when 8 | * @param then 9 | * @param xpath 10 | * @param action 11 | * @param actions 12 | * @param times 13 | */ 14 | case class Step( 15 | given: List[String] = List[String](), 16 | var when: When = null, 17 | //todo: testcase和trigger 遍历都支持断言和报告输出 18 | `then`: List[String] = List[String](), 19 | xpath: String = "//*", 20 | action: String = null, 21 | actions: List[String] = List[String](), 22 | var times: Int = -1 23 | ) { 24 | def use(): Int = { 25 | times -= 1 26 | times 27 | } 28 | 29 | //todo: selectedList也支持given结构 30 | def getGiven(): List[String] = { 31 | if (given == null) { 32 | List(getXPath()) 33 | } else { 34 | given 35 | } 36 | } 37 | 38 | def getXPath(): String = { 39 | if (when == null) { 40 | if (this.xpath == null) { 41 | "/*" 42 | } else { 43 | this.xpath 44 | } 45 | } else { 46 | if (when.xpath == null) { 47 | "/*" 48 | } else { 49 | when.xpath 50 | } 51 | } 52 | } 53 | 54 | def getAction(): String = { 55 | //todo: 支持actions 56 | val result = if (when == null) { 57 | action 58 | } else { 59 | when.action 60 | } 61 | if (result == null) { 62 | "" 63 | } else { 64 | result 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/TreeNode.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | import java.io.{BufferedWriter, FileWriter} 4 | import scala.collection.mutable.ListBuffer 5 | 6 | /** 7 | * Created by seveniruby on 15/12/18. 8 | */ 9 | case class TreeNode[T]( 10 | value: T, 11 | children: ListBuffer[TreeNode[T]] = ListBuffer[TreeNode[T]]() 12 | ) { 13 | 14 | def equals(node: TreeNode[T]): Boolean = { 15 | node.value == this.value 16 | } 17 | 18 | 19 | def find(tree: TreeNode[T], node: TreeNode[T]): Option[TreeNode[T]] = { 20 | if (tree.equals(node)) { 21 | return Some(tree) 22 | } 23 | tree.children.foreach(t => { 24 | find(t, node) match { 25 | case Some(v) => return Some(v) 26 | case None => {} 27 | } 28 | }) 29 | None 30 | } 31 | 32 | def appendNode(currenTree: TreeNode[T], node: TreeNode[T]): TreeNode[T] = { 33 | find(currenTree, node) match { 34 | case Some(v) => { 35 | v 36 | } 37 | case None => { 38 | this.children.append(node) 39 | node 40 | } 41 | } 42 | } 43 | 44 | 45 | def toXml(tree: TreeNode[T]): String = { 46 | val s = new StringBuffer() 47 | val before = (tree: TreeNode[T]) => { 48 | s.append(s"""""") 49 | //todo: 增加图片地址链接 LINK="file:///Users/seveniruby/projects/LBSRefresh/Android_20160216105737/946_StockDetail-Back--.png" 50 | } 51 | val after = (tree: TreeNode[T]) => { 52 | s.append("") 53 | s.append("\n") 54 | } 55 | 56 | s.append("""""") 57 | s.append("\n") 58 | traversal[T](tree, before, after) 59 | s.append("") 60 | s.toString 61 | } 62 | 63 | def traversal[T](tree: TreeNode[T], 64 | before: (TreeNode[T]) => Any = (x: TreeNode[T]) => (), 65 | after: (TreeNode[T]) => Any = (x: TreeNode[T]) => ()): Unit = { 66 | before(tree) 67 | tree.children.foreach(t => { 68 | traversal(t, before, after) 69 | }) 70 | after(tree) 71 | } 72 | 73 | def generateFreeMind(list: ListBuffer[T], path: String = null): String = { 74 | if (list.isEmpty) { 75 | return "" 76 | } 77 | val root = TreeNode(list.head) 78 | var currentNode = root 79 | list.slice(1, list.size).foreach(e => { 80 | currentNode = currentNode.appendNode(root, TreeNode(e)) 81 | }) 82 | val xml = toXml(root) 83 | if (path != null) { 84 | val file = new java.io.File(path) 85 | val bw = new BufferedWriter(new FileWriter(file)) 86 | bw.write(xml) 87 | bw.close() 88 | } 89 | xml 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/model/When.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | case class When(xpath: String = null, 4 | action: String = null, 5 | actions: List[String] = List[String]() 6 | ) 7 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/DemoPlugin.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin 2 | 3 | import com.ceshiren.appcrawler.model.URIElement 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | /** 6 | * Created by seveniruby on 16/1/21. 7 | */ 8 | class DemoPlugin extends Plugin{ 9 | override def beforeElementAction(element: URIElement): Unit ={ 10 | log.info("demo com.ceshiren.appcrawler.plugin before element action") 11 | log.info(element) 12 | log.info("demo com.ceshiren.appcrawler.plugin end") 13 | } 14 | override def afterUrlRefresh(url:String): Unit ={ 15 | getCrawler().currentUrl=url.split('|').last 16 | log.info(s"new url=${getCrawler().currentUrl}") 17 | if(getCrawler().currentUrl.contains("Browser")){ 18 | getCrawler().getBackButton() 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/FlowDiff.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin 2 | 3 | import com.ceshiren.appcrawler.model.URIElement 4 | 5 | /** 6 | * Created by seveniruby on 16/9/25. 7 | */ 8 | class FlowDiff extends Plugin{ 9 | override def start(): Unit ={ 10 | } 11 | 12 | override def afterElementAction(element: URIElement): Unit ={ 13 | //getCrawler().store.saveResDom(getCrawler().driver.currentPageSource) 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/FreeMind.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin 2 | 3 | import com.ceshiren.appcrawler.model.TreeNode 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | 6 | import scala.collection.mutable.ListBuffer 7 | import scala.reflect.io.File 8 | 9 | /** 10 | * Created by seveniruby on 16/9/19. 11 | */ 12 | class FreeMind extends Plugin{ 13 | 14 | private val elementTree = TreeNode[String]("AppCrawler") 15 | private val elementTreeList = ListBuffer[String]() 16 | 17 | override def stop(): Unit ={ 18 | log.info(s"genereate freemind file freemind.mm") 19 | report() 20 | } 21 | 22 | def report(): Unit ={ 23 | getCrawler().store.getClickedElementsList.foreach(element=>{ 24 | elementTreeList.append(element.getUrl) 25 | elementTreeList.append(element.getXpath) 26 | }) 27 | 28 | File(s"${getCrawler().conf.resultDir}/freemind.mm").writeAll( 29 | elementTree.generateFreeMind(elementTreeList) 30 | ) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/IDeviceScreenshot.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin 2 | 3 | import com.ceshiren.appcrawler.utils.Log.log 4 | 5 | import scala.sys.process._ 6 | 7 | /** 8 | * Created by seveniruby on 16/4/26. 9 | */ 10 | class IDeviceScreenshot extends Plugin { 11 | 12 | var use = false 13 | 14 | override def start(): Unit = { 15 | getCrawler().conf.capability("udid") match { 16 | case null => { 17 | use = false 18 | log.info("udid=null use simulator") 19 | } 20 | case "" => { 21 | use = false 22 | log.info("udid= use simulator") 23 | } 24 | case _ => { 25 | use = true 26 | log.info("use idevicescreenshot") 27 | } 28 | } 29 | } 30 | 31 | override def screenshot(path: String): Boolean = { 32 | //非真机不使用 33 | if (use == false) return false 34 | val cmd = s"idevicescreenshot ${path}" 35 | log.info(s"cmd=${cmd}") 36 | cmd.! 37 | true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/LogPlugin.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin 2 | 3 | import com.ceshiren.appcrawler.driver.AppiumClient 4 | import com.ceshiren.appcrawler.model.URIElement 5 | import com.ceshiren.appcrawler.utils.Log.log 6 | 7 | import java.util.logging.Level 8 | import scala.collection.mutable.ListBuffer 9 | import scala.reflect.io.File 10 | 11 | /** 12 | * Created by seveniruby on 16/1/21. 13 | * 14 | * 如果某种类型的控件点击次数太多, 就跳过. 设定一个阈值 15 | */ 16 | class LogPlugin extends Plugin { 17 | private var logs = ListBuffer[String]() 18 | val driver = getCrawler().driver.asInstanceOf[AppiumClient].driver 19 | 20 | override def afterElementAction(element: URIElement): Unit = { 21 | //第一次先试验可用的log 后续就可以跳过从而加速 22 | if (logs.isEmpty) { 23 | driver.manage().logs().getAvailableLogTypes.toArray().foreach(logName => { 24 | log.info(s"read log=${logName.toString}") 25 | try { 26 | saveLog(logName.toString) 27 | logs += logName.toString 28 | } catch { 29 | case ex: Exception => log.warn(s"log=${logName.toString} not exist") 30 | } 31 | }) 32 | } 33 | if(element.getAction!="skip") { 34 | logs.foreach(log => { 35 | saveLog(log) 36 | }) 37 | } 38 | } 39 | 40 | def saveLog(logName:String): Unit ={ 41 | log.info(s"read log=${logName.toString}") 42 | val logMessage = driver.manage().logs.get(logName.toString).filter(Level.ALL).toArray() 43 | log.info(s"log=${logName} size=${logMessage.size}") 44 | if (logMessage.size > 0) { 45 | val fileName = getCrawler().getBasePathName()+".log" 46 | log.info(s"save ${logName} to $fileName") 47 | File(fileName).writeAll(logMessage.mkString("\n")) 48 | log.info(s"save ${logName} end") 49 | } 50 | } 51 | 52 | 53 | override def afterUrlRefresh(url: String): Unit = { 54 | 55 | } 56 | override def stop(): Unit ={ 57 | logs.foreach(log => { 58 | saveLog(log) 59 | }) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/Plugin.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin 2 | 3 | import com.ceshiren.appcrawler.core.Crawler 4 | import com.ceshiren.appcrawler.model.URIElement 5 | import com.ceshiren.appcrawler.utils.Log.log 6 | 7 | /** 8 | * Created by seveniruby on 16/1/7. 9 | */ 10 | abstract class Plugin{ 11 | private var crawler: Crawler=_ 12 | 13 | def getCrawler(): Crawler ={ 14 | this.crawler 15 | } 16 | def setCrawer(crawler:Crawler): Unit ={ 17 | this.crawler=crawler 18 | } 19 | def init(crawler: Crawler): Unit ={ 20 | this.crawler=crawler 21 | 22 | log.info(this.getClass.getName+" init") 23 | } 24 | def start(): Unit ={ 25 | 26 | } 27 | def afterUrlRefresh(url:String): Unit ={ 28 | 29 | } 30 | 31 | def beforeBack(): Unit ={ 32 | 33 | } 34 | def fixElementAction(element: URIElement): Unit ={ 35 | 36 | } 37 | def beforeElementAction(element: URIElement): Unit ={ 38 | 39 | } 40 | def afterElementAction(element: URIElement): Unit ={ 41 | 42 | } 43 | 44 | /** 45 | * 如果实现了请设置返回值为true 46 | * @param path 47 | * @return 48 | */ 49 | def screenshot(path:String): Boolean ={ 50 | false 51 | } 52 | 53 | def stop(): Unit ={ 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/ReportPlugin.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin 2 | 3 | import com.ceshiren.appcrawler.model.URIElement 4 | import com.ceshiren.appcrawler.plugin.report.ReportFactory 5 | import com.ceshiren.appcrawler.utils.Log.log 6 | import com.ceshiren.appcrawler.utils.LogicUtils.asyncTask 7 | 8 | import java.nio.file.{Files, Paths} 9 | import scala.io.Source 10 | import scala.reflect.io.File 11 | 12 | /** 13 | * Created by seveniruby on 16/8/12. 14 | */ 15 | class ReportPlugin extends Plugin { 16 | 17 | var requestFile: String = _ 18 | 19 | override def start(): Unit = { 20 | ReportFactory.initReportPath(new java.io.File(getCrawler().conf.resultDir).getCanonicalPath) 21 | requestFile = ReportFactory.reportPath + File.separator + "request" 22 | } 23 | 24 | override def stop(): Unit = { 25 | this.getCrawler().saveLog() 26 | generateReport() 27 | } 28 | 29 | override def afterElementAction(element: URIElement): Unit = { 30 | //todo: 子线程处理,异步处理 31 | asyncTask(timeout = 120, name = "report", needThrow = true) { 32 | if (needReport()) { 33 | log.info("generate test report ") 34 | //getCrawler().saveLog() 35 | generateReport() 36 | } 37 | } 38 | } 39 | 40 | def needReport(): Boolean = { 41 | val curSize = getCrawler().store.getClickedElementsList.size 42 | if (curSize % 5 == 0) { 43 | if (curSize % 20 == 0) { 44 | true 45 | } else { 46 | log.info(s"read command from ${requestFile}") 47 | val command = if (Files.exists(Paths.get(requestFile))) { 48 | Source.fromFile(requestFile).mkString 49 | } else { 50 | "" 51 | } 52 | log.info(command) 53 | if (command.contains("stop")) { 54 | stop() 55 | true 56 | } else if (command.contains("save")) { 57 | true 58 | } else { 59 | false 60 | } 61 | } 62 | } else { 63 | false 64 | } 65 | } 66 | 67 | 68 | //todo: 使用独立工具出报告 69 | def generateReport(): Unit = { 70 | 71 | // if(false){ 72 | // // 生成allure报告 73 | // log.info("allure report generate") 74 | // MvnReplace.runTest() 75 | // if(MvnReplace.isExist) MvnReplace.executeCommand("cmd /c allure generate " + getCrawler().conf.resultDir + "/allure-results" +" -o " + getCrawler().conf.resultDir + "/report") 76 | // } 77 | 78 | log.info(s"reportPath=${ReportFactory.reportPath}") 79 | ReportFactory.initStore(getCrawler().store) 80 | ReportFactory.getReportEngine("scalatest") 81 | ReportFactory.getInstance().genTestCase(ReportFactory.reportPath) 82 | ReportFactory.getInstance().runTestCase() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/TagLimitPlugin.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin 2 | 3 | import com.ceshiren.appcrawler.model.URIElement 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | 6 | /** 7 | * Created by seveniruby on 16/1/21. 8 | * 9 | * 如果某种类型的控件点击次数太多, 就跳过. 设定一个阈值 10 | */ 11 | class TagLimitPlugin extends Plugin { 12 | private val tagLimit = scala.collection.mutable.Map[String, Int]() 13 | private var tagLimitMax = 3 14 | private var currentKey = "" 15 | 16 | override def start(): Unit = { 17 | //log.addAppender(getCrawler().fileAppender) 18 | tagLimitMax = getCrawler().conf.tagLimitMax 19 | } 20 | 21 | //fixed: conf.tagLimit未生效 22 | override def fixElementAction(element: URIElement): Unit = { 23 | if (element.getAction.startsWith("_")) { 24 | //非普通元素点击事件,不需要统计,比如back backApp 等 25 | return 26 | } 27 | 28 | if (getCrawler().conf.backButton.map(_.xpath).contains(element.getXpath)) { 29 | return 30 | } 31 | 32 | //todo: //*[@resource='xxxx'][1] genXPath=//sssss[@dddd=xxxx and ] 33 | // if (getCrawler().conf.backButton.map(_.xpath).contains(element.getXpath)) { 34 | // return 35 | // } 36 | currentKey = getAncestor(element) 37 | if (!tagLimit.contains(currentKey)) { 38 | //应用定制化的规则 39 | getTimesFromTagLimit(element) match { 40 | case Some(v) => { 41 | tagLimit(currentKey) = v 42 | log.info(s"tagLimit[${currentKey}]=${tagLimit(currentKey)} with conf.tagLimit") 43 | } 44 | case None => tagLimit(currentKey) = tagLimitMax 45 | } 46 | } 47 | 48 | log.info(s"tagLimit[${currentKey}]=${tagLimit(currentKey)}") 49 | //如果达到限制次数就退出,小于0表示无限制 50 | if (currentKey.nonEmpty && tagLimit(currentKey) == 0) { 51 | //todo: 重构action名字的定义 52 | element.setAction("_skip") 53 | log.info(s"$element need skip") 54 | } 55 | } 56 | 57 | override def afterElementAction(element: URIElement): Unit = { 58 | if (element.getAction.startsWith("_")) { 59 | //非普通元素点击事件,不需要统计,比如back backApp 等 60 | return 61 | } 62 | //todo: 因为afterElement不一定在doElement后执行,所以这个地方可能会漏掉统计,刷新报错时会发生 63 | if (tagLimit.contains(currentKey)) { 64 | tagLimit(currentKey) -= 1 65 | log.info(s"tagLimit[${currentKey}]=${tagLimit(currentKey)}") 66 | } else { 67 | log.trace(s"not contains ${currentKey}") 68 | } 69 | } 70 | 71 | def getAncestor(element: URIElement): String = { 72 | getCrawler().currentUrl + element.getAncestor() 73 | } 74 | 75 | def getTimesFromTagLimit(element: URIElement): Option[Int] = { 76 | this.getCrawler().conf.tagLimit.foreach(tag => { 77 | log.trace(s"find tag with ${tag}") 78 | val elementMatchList = getCrawler().driver.getNodeListByKey(tag.getXPath()).map(x => new URIElement(x, getCrawler().currentUrl)) 79 | log.trace(elementMatchList.length) 80 | log.trace(element) 81 | if (elementMatchList.contains(element)) { 82 | log.debug(s"${tag.getXPath()} hit") 83 | return Some(tag.times) 84 | } else { 85 | None 86 | } 87 | }) 88 | None 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/junit5/AllureTemplate.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.junit5; 2 | 3 | import com.ceshiren.appcrawler.plugin.report.ReportFactory; 4 | import io.qameta.allure.Allure; 5 | import org.junit.jupiter.api.DynamicTest; 6 | import org.junit.jupiter.api.TestFactory; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collection; 10 | 11 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 12 | 13 | public class AllureTemplate { 14 | //pageNum : activity的名字 15 | public String pageName=""; 16 | @TestFactory 17 | Collection AllElementInfo() { 18 | 19 | ArrayList arrayList = new ArrayList<>(); 20 | System.out.println(pageName + "getSelected: " + ReportFactory.getSelected(pageName).size()); 21 | ReportFactory.getSelected(pageName).forEach(value->{ 22 | arrayList.add(dynamicTest(String.format("index=%d action=%s, xpath=%s", 23 | value.getClickedIndex(), 24 | value.getAction(), 25 | value.getElement().getXpath() 26 | ), 27 | ()->{ 28 | System.out.println(String.format("req image: %s\nres image: %s\n", value.getReqImg(), value.getResImg())); 29 | Allure.addAttachment("req image", value.getReqImg()); 30 | Allure.addAttachment("res image", value.getResImg()); 31 | Allure.addAttachment("res dom", value.getResDom()); 32 | })); 33 | }); 34 | return arrayList; 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/junit5/FakeData.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.junit5; 2 | 3 | import scala.collection.mutable.HashMap; 4 | 5 | public class FakeData { 6 | HashMap map=new HashMap<>(); 7 | public FakeData(){ 8 | map.put("Page1", 1); 9 | map.put("Page2", 2); 10 | map.put("Page3", 3); 11 | map.put("Page4", 4); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/junit5/JUnit5Runtime.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.junit5 2 | 3 | import com.ceshiren.appcrawler.plugin.report.{Report, ReportFactory} 4 | import com.ceshiren.appcrawler.plugin.scalatest.SuiteToClass 5 | import com.ceshiren.appcrawler.utils.Log.log 6 | 7 | import scala.jdk.CollectionConverters._ 8 | /** 9 | * Created by seveniruby on 16/8/15. 10 | */ 11 | class JUnit5Runtime extends Report { 12 | 13 | override def genTestCase(resultDir: String): Unit = { 14 | //todo: 15 | 16 | log.info("save testcase") 17 | ReportFactory.initReportPath(resultDir) 18 | //为了保持独立使用 19 | 20 | val suites = ReportFactory.store.getElementStoreMap.asScala.map(x => x._2.getElement.getUrl.replaceAllLiterally("..", ".")).toList.distinct 21 | var index=0 22 | suites.foreach(suite => { 23 | log.info(s"gen testcase class ${suite}") 24 | //todo: 基于规则的多次点击事件只会被保存到一个状态中. 需要区分 25 | SuiteToClass.genTestCaseClass( 26 | suite, 27 | "com.ceshiren.appcrawler.plugin.junit5.AllureTemplate", 28 | Map("uri"->suite, "name"->suite), 29 | ReportFactory.testcaseDir 30 | ) 31 | }) 32 | 33 | //todo: 使用javaassit生成allure template的子类,并在初始化中修改page name 34 | } 35 | 36 | 37 | //todo: 用junit+allure代替 38 | override def runTestCase(namespace: String=""): Unit = { 39 | 40 | //seveniruby demo test 41 | //todo: execute junit5 https://junit.org/junit5/docs/current/user-guide/#launcher-api-execution 42 | 43 | 44 | //todo: delay allure javaagent 45 | //java -javaagent:xxxxxx.jar -jar appcrawler.jar 46 | //https://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html 47 | 48 | //todo: allure delay 49 | //从jar导出javaagent的jar,然后自己exec java -jaragent。。 50 | 51 | 52 | } 53 | 54 | override def changeTitle(title:String): Unit ={ 55 | //todo: 56 | //malu test demo 57 | //seveniruby need merge 58 | //naruto test 59 | //naruto 3 60 | //seveniruby2 61 | //sevenriby3 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/junit5/JUnit5RuntimeJava.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.junit5; 2 | 3 | import com.ceshiren.appcrawler.plugin.report.Report; 4 | import org.junit.platform.launcher.Launcher; 5 | import org.junit.platform.launcher.LauncherDiscoveryRequest; 6 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; 7 | import org.junit.platform.launcher.core.LauncherFactory; 8 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener; 9 | import org.junit.platform.launcher.listeners.TestExecutionSummary; 10 | 11 | import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns; 12 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectDirectory; 13 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage; 14 | 15 | public class JUnit5RuntimeJava extends Report { 16 | @Override 17 | public void genTestCase(String resultDir) { 18 | super.genTestCase(resultDir); 19 | } 20 | 21 | @Override 22 | public void runTestCase(String namespace) { 23 | 24 | //todo: https://junit.org/junit5/docs/current/user-guide/#running-tests-console-launcher 25 | //todo: junit的console运行方式 Runtime.getRuntime().exec("java -jar junit-platform-console-standalone-1.4.2.jar -d xxxx ") 26 | //todo: java -javaagent xxxx.jar -jar xxxx.jar 27 | //todo: api方式 28 | super.runTestCase(namespace); 29 | 30 | LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() 31 | .selectors( 32 | selectDirectory(""), 33 | selectPackage("com.example.mytests") 34 | ) 35 | .filters( 36 | includeClassNamePatterns(".*Tests") 37 | ) 38 | .build(); 39 | 40 | Launcher launcher = LauncherFactory.create(); 41 | 42 | // Register a listener of your choice 43 | SummaryGeneratingListener listener = new SummaryGeneratingListener(); 44 | launcher.registerTestExecutionListeners(listener); 45 | 46 | launcher.execute(request); 47 | 48 | TestExecutionSummary summary = listener.getSummary(); 49 | 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/report/Report.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.report 2 | 3 | import com.ceshiren.appcrawler.model.URIElementStore 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | import com.ceshiren.appcrawler.utils.TData 6 | 7 | import scala.io.Source 8 | /** 9 | * Created by seveniruby on 16/8/15. 10 | */ 11 | abstract class Report { 12 | 13 | 14 | def genTestCase(resultDir: String): Unit = { 15 | } 16 | 17 | 18 | //todo: 用junit+allure代替 19 | def runTestCase(namespace: String=""): Unit = { 20 | 21 | } 22 | 23 | def changeTitle(title:String): Unit ={ 24 | } 25 | 26 | def loadResult(elementsFile: String): URIElementStore ={ 27 | val content=Source.fromFile(elementsFile).mkString 28 | log.info(s"${elementsFile} size = ${content.size}") 29 | //todo: cannot deserialize from Object value (no delegate- or property-based Creator) 30 | TData.fromYaml[URIElementStore](content) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/report/ReportFactory.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.report 2 | 3 | import com.ceshiren.appcrawler.core.Status 4 | import com.ceshiren.appcrawler.model.{ElementInfo, URIElementStore} 5 | import com.ceshiren.appcrawler.plugin.junit5.JUnit5Runtime 6 | import com.ceshiren.appcrawler.plugin.scalatest.ScalaTestRuntime 7 | import com.ceshiren.appcrawler.utils.Log.log 8 | 9 | import java.{io, util} 10 | import scala.jdk.CollectionConverters.{IterableHasAsJava, MapHasAsScala} 11 | 12 | object ReportFactory { 13 | 14 | var showCancel = false 15 | var title = "AppCrawler" 16 | var master = "" 17 | var candidate = "" 18 | var reportDir = "" 19 | var store: URIElementStore = _ 20 | 21 | 22 | var reportPath = "" 23 | var testcaseDir = "" 24 | var report: Report = _ 25 | 26 | 27 | def initStore(store: URIElementStore): Unit = { 28 | this.store = store 29 | } 30 | 31 | def initReportPath(path: String): Unit = { 32 | reportPath = path 33 | log.info(s"reportPath=${ReportFactory.reportPath}") 34 | testcaseDir = reportPath + "/tmp/" 35 | log.info(s"testcaseDir=${ReportFactory.testcaseDir}") 36 | val tmpDir = new io.File(s"${reportPath}/tmp/") 37 | if (tmpDir.exists() == false) { 38 | log.info(s"create ${tmpDir.getPath} directory") 39 | tmpDir.mkdir() 40 | } 41 | } 42 | 43 | def getReportEngine(`type`: String = "scalatest"): Report = { 44 | report = `type` match { 45 | case "scalatest" => new ScalaTestRuntime(); 46 | case "junit5" => new JUnit5Runtime(); 47 | } 48 | return report 49 | } 50 | 51 | def getInstance(): Report = { 52 | if (report == null) { 53 | 54 | log.info("report not init") 55 | report = getReportEngine() 56 | } 57 | return report 58 | } 59 | 60 | def getSelected(uri: String): util.Collection[ElementInfo] = { 61 | log.trace(s"Report.store.elementStore size = ${ReportFactory.store.getElementStoreMap.size}") 62 | log.trace(s"uri=${uri}") 63 | val sortedElements = store.getElementStoreMap.asScala 64 | .filter(x => { 65 | val url = x._2.getElement.getUrl.replace("..", ".") 66 | url == uri 67 | }).values.toList 68 | .sortBy(_.getClickedIndex) 69 | 70 | log.trace(s"sortedElements=${sortedElements.size}") 71 | val selected = if (ReportFactory.showCancel) { 72 | log.info("show all elements") 73 | //把未遍历的放到后面 74 | sortedElements.filter(_.getAction == Status.CLICKED) ++ 75 | //sortedElements.filter(_.action == ElementStatus.Skipped) ++ 76 | sortedElements.filter(_.getAction == Status.READY) 77 | } else { 78 | log.info("only show clicked elements") 79 | sortedElements.filter(_.getAction == Status.CLICKED) 80 | } 81 | log.trace(s"selected elements size = ${selected.size}") 82 | return selected.asJavaCollection 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/scalatest/ScalaTestRuntime.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.scalatest 2 | 3 | import com.ceshiren.appcrawler.plugin.report.{Report, ReportFactory} 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | import org.scalatest.tools.Runner 6 | 7 | import scala.io.Source 8 | import scala.jdk.CollectionConverters._ 9 | /** 10 | * Created by seveniruby on 16/8/15. 11 | */ 12 | class ScalaTestRuntime extends Report { 13 | 14 | override def genTestCase(resultDir: String): Unit = { 15 | log.info("save testcase") 16 | ReportFactory.initReportPath(resultDir) 17 | //为了保持独立使用 18 | 19 | val suites = ReportFactory.store.getElementStoreMap.asScala.map(x => x._2.getElement.getUrl.replaceAllLiterally("..", ".")).toList.distinct 20 | var index=0 21 | suites.foreach(suite => { 22 | log.info(s"gen testcase class ${suite}") 23 | //todo: 基于规则的多次点击事件只会被保存到一个状态中. 需要区分 24 | SuiteToClass.genTestCaseClass( 25 | suite, 26 | "com.ceshiren.appcrawler.plugin.scalatest.ScalaTestTemplate", 27 | Map("uri"->suite, "name"->suite), 28 | ReportFactory.testcaseDir 29 | ) 30 | }) 31 | } 32 | 33 | 34 | //todo: 用junit+allure代替 35 | override def runTestCase(namespace: String=""): Unit = { 36 | var cmdArgs = Array("-R", ReportFactory.testcaseDir, 37 | "-oF", "-u", ReportFactory.reportPath, "-h", ReportFactory.reportPath) 38 | 39 | if(namespace.nonEmpty){ 40 | cmdArgs++=Array("-s", namespace) 41 | } 42 | log.debug(cmdArgs.mkString) 43 | 44 | /* 45 | val testcaseDirFile=new java.io.File(testcaseDir) 46 | FileUtils.listFiles(testcaseDirFile, Array(".class"), true).map(_.split(".class").head) 47 | val suites= testcaseDirFile.list().filter(_.endsWith(".class")).map(_.split(".class").head).toList 48 | suites.map(suite => Array("-s", s"${namespace}${suite}")).foreach(array => { 49 | cmdArgs = cmdArgs ++ array 50 | }) 51 | 52 | if (suites.size > 0) { 53 | log.info(s"run ${cmdArgs.toList}") 54 | Runner.run(cmdArgs) 55 | Runtimes.reset 56 | changeTitle 57 | } 58 | */ 59 | log.info(s"run ${cmdArgs.mkString(" ")}") 60 | Runner.run(cmdArgs) 61 | changeTitle(ReportFactory.title) 62 | } 63 | 64 | override def changeTitle(title:String): Unit ={ 65 | val originTitle="ScalaTest Results" 66 | val indexFile=ReportFactory.reportPath+"/index.html" 67 | val newContent=Source.fromFile(indexFile)(scala.io.Codec.UTF8).mkString.replace(originTitle, title) 68 | scala.reflect.io.File(indexFile).writeAll(newContent) 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/scalatest/ScalaTestTemplate.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.scalatest 2 | 3 | import com.ceshiren.appcrawler.AppCrawler 4 | import com.ceshiren.appcrawler.core.Status 5 | import com.ceshiren.appcrawler.plugin.report.ReportFactory 6 | import com.ceshiren.appcrawler.utils.Log.log 7 | import com.ceshiren.appcrawler.utils.XPathUtil 8 | import org.scalatest 9 | import org.scalatest._ 10 | 11 | import scala.jdk.CollectionConverters._ 12 | import scala.reflect.io.File 13 | 14 | /** 15 | * Created by seveniruby on 2017/3/25. 16 | */ 17 | class ScalaTestTemplate extends FunSuite with BeforeAndAfterAllConfigMap with Matchers { 18 | var name = "template" 19 | var uri = "" 20 | 21 | override def suiteName = name 22 | 23 | def addTestCase() { 24 | 25 | ReportFactory.getSelected(uri).asScala.foreach(ele => { 26 | val testcase = ele.getElement.getXpath.replace("\\", "\\\\") 27 | .replace("\"", "\\\"") 28 | .replace("\n", "") 29 | .replace("\r", "") 30 | 31 | log.debug(s"add testcase ${testcase}") 32 | //todo: 增加ignore和cancel的区分 33 | test(s"clickedIndex=${ele.getClickedIndex} action=${ele.getAction}\nxpath=${testcase}") { 34 | ele.getAction match { 35 | case Status.CLICKED => { 36 | markup( 37 | s""" 38 | | 39 | | 40 | |

41 | |

after clicked

42 | | 43 | """.stripMargin 44 | ) 45 | 46 | /* 47 | markup( 48 | s""" 49 | | 50 | |
 51 |               |
 52 |               |${ele.reqDom.replaceFirst("xml", "x_m_l")}
 53 |               |
 54 |               |
55 | """.stripMargin 56 | ) 57 | */ 58 | 59 | AppCrawler.crawler.conf.assertGlobal.foreach(step => { 60 | if (step.getGiven() == null || 61 | step.getGiven().forall(g => XPathUtil.getNodeListByKey(g, ele.getReqDom).nonEmpty) 62 | ) { 63 | log.info(s"match testcase ${ele.getElement.getXpath}") 64 | 65 | if (step.then != null) { 66 | log.info("assertion start") 67 | log.info(step.`then`) 68 | val cp = new scalatest.Checkpoints.Checkpoint 69 | step.then.foreach(existAssert => { 70 | log.debug(existAssert) 71 | cp { 72 | withClue(s"${existAssert} 不存在\n") { 73 | XPathUtil.getNodeListByXPath(existAssert, ele.getResDom).size should be > 0 74 | } 75 | } 76 | }) 77 | cp.reportAll() 78 | } 79 | } else { 80 | 81 | /* XPathUtil.getNodeListFromXPath(step.getXPath(), ele.reqDom) 82 | .map(_.getOrElse("xpath", "")).foreach(log.warn) 83 | log.warn(s"not match ${step.getXPath()} ${ele.element.xpath}")*/ 84 | } 85 | }) 86 | 87 | } 88 | case Status.READY => { 89 | cancel(s"${ele.getAction} not click") 90 | } 91 | case Status.SKIPPED => { 92 | cancel(s"${ele.getAction} skipped") 93 | } 94 | } 95 | 96 | } 97 | }) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/plugin/scalatest/SuiteToClass.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.plugin.scalatest 2 | 3 | import com.ceshiren.appcrawler.utils.Log.log 4 | import com.ceshiren.appcrawler.utils.LogicUtils.handleException 5 | import javassist.{ClassPool, CtConstructor} 6 | 7 | import scala.jdk.CollectionConverters._ 8 | import scala.util.{Failure, Success, Try} 9 | 10 | /** 11 | * Created by seveniruby on 2017/4/15. 12 | */ 13 | object SuiteToClass { 14 | 15 | var index=0 16 | 17 | //todo: 使用白名单 18 | def format(name:String): String ={ 19 | name 20 | .replaceAllLiterally("\\", "\\\\") 21 | .replaceAllLiterally("\"", "\\\"") 22 | .replaceAllLiterally("#", "") 23 | .replaceAllLiterally("&", "") 24 | .replaceAllLiterally("-", ".") 25 | .replaceAllLiterally(" ","") 26 | .replaceAllLiterally("*","") 27 | .replaceAllLiterally("?","") 28 | .replaceAllLiterally("|","") 29 | .replaceAllLiterally("<","") 30 | .replaceAllLiterally(">","") 31 | .replaceAllLiterally(":","") 32 | .replaceAll("[#;/\\:]", "") 33 | } 34 | /** 35 | * 生成用例对应的class文件,用于调用scalatest执行 36 | * 37 | */ 38 | def genTestCaseClass(className: String, superClassName: String, fields: Map[String, Any], directory: String): Unit = { 39 | val pool = ClassPool.getDefault 40 | val classNameFormat = format(className) 41 | log.trace(s"classNameFormat=${classNameFormat}") 42 | val clazz=pool.getOrNull(classNameFormat) 43 | if(clazz!=null){ 44 | if(clazz.isFrozen){ 45 | clazz.defrost() 46 | } 47 | } 48 | Try(pool.makeClass(classNameFormat)) match { 49 | case Success(classNew) => { 50 | classNew.setSuperclass(pool.get(superClassName)) 51 | val init = new CtConstructor(null, classNew) 52 | val body = fields.map(field => { 53 | //todo: 字段与类名分开 54 | //todo: 使用数据驱动解决 55 | s"${field._1}_$$eq(${'"' + format(field._2.toString) + '"'}); " 56 | }).mkString("\n") 57 | log.trace(body) 58 | init.setBody(s"{ ${body}\naddTestCase(); }") 59 | classNew.addConstructor(init) 60 | classNew.writeFile(directory) 61 | log.debug(s"write to ${directory}") 62 | 63 | } 64 | case Failure(e) => { 65 | handleException(e) 66 | } 67 | } 68 | } 69 | 70 | def genTestCaseClass2(className: String, superClassName: String, fields: java.util.HashMap[String, Any], directory: String): Unit = { 71 | genTestCaseClass(className, superClassName, scala.collection.immutable.Map()++fields.asScala, directory) 72 | } 73 | 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/utils/GA.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.utils 2 | 3 | import com.brsanthu.googleanalytics.GoogleAnalytics 4 | 5 | /** 6 | * Created by seveniruby on 16/2/26. 7 | */ 8 | 9 | 10 | object GA { 11 | val ga=GoogleAnalytics.builder().withTrackingId("UA-74406102-1").build() 12 | var appName = "default" 13 | 14 | def setAppName(app: String): Unit = { 15 | appName = app 16 | } 17 | 18 | def log(action: String): Unit = { 19 | ga.pageView(s"http://appcrawler.io/${appName}/${action}", "test").sendAsync() 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/utils/Log.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.utils; 2 | 3 | import org.apache.logging.log4j.Level; 4 | import org.apache.logging.log4j.LogManager; 5 | import org.apache.logging.log4j.Logger; 6 | import org.apache.logging.log4j.core.LoggerContext; 7 | import org.apache.logging.log4j.core.appender.FileAppender; 8 | import org.apache.logging.log4j.core.config.Configuration; 9 | import org.apache.logging.log4j.core.config.LoggerConfig; 10 | import org.apache.logging.log4j.core.layout.PatternLayout; 11 | 12 | public class Log { 13 | public static Logger log = LogManager.getLogger(Log.class); 14 | private static LoggerContext logContext = (LoggerContext) LogManager.getContext(false); 15 | private static Configuration configuration = logContext.getConfiguration(); 16 | private static LoggerConfig loggerConfig = configuration.getLoggerConfig(Log.class.getName()); 17 | private static FileAppender fileAppender; 18 | 19 | public static Logger setLogFilePath(String path) { 20 | fileAppender = FileAppender.newBuilder() 21 | .withFileName(path) 22 | .withAdvertise(false) 23 | .withAppend(false) 24 | .setName("CrawlerLog") 25 | .setLayout(PatternLayout.newBuilder().withPattern("%d{yyyy-MM-dd HH:mm:ss} %p [%C{1}.%L.%M] %m%n").build()) 26 | .build(); 27 | fileAppender.start(); 28 | loggerConfig.addAppender(fileAppender, Level.TRACE, null); 29 | logContext.updateLoggers(); 30 | return log; 31 | } 32 | 33 | public static void setLevel(Level level) { 34 | for (String name : loggerConfig.getAppenders().keySet()) { 35 | loggerConfig.removeAppender(name); 36 | } 37 | // loggerConfig.setLevel(level); 38 | loggerConfig.addAppender(configuration.getAppender("STDOUT"), level, null); 39 | if(fileAppender!=null) { 40 | loggerConfig.addAppender(fileAppender, Level.TRACE, null); 41 | } 42 | logContext.updateLoggers(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/com/ceshiren/appcrawler/utils/LogicUtils.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.utils 2 | 3 | import com.ceshiren.appcrawler.utils.Log.log 4 | 5 | import java.util.concurrent.{Callable, Executors, TimeUnit, TimeoutException} 6 | import scala.util.{Failure, Success, Try} 7 | 8 | object LogicUtils { 9 | 10 | def handleException(e: Throwable): Unit = { 11 | var exception = e 12 | do { 13 | log.error(exception.getLocalizedMessage) 14 | exception.getStackTrace.foreach(log.error) 15 | if (exception.getCause != null) { 16 | log.error("find more cause") 17 | } else { 18 | log.error("exception finish") 19 | } 20 | exception = exception.getCause 21 | } while (exception != null) 22 | } 23 | 24 | def tryAndCatch[T](r: => T): Option[T] = { 25 | Try(r) match { 26 | case Success(v) => { 27 | log.info("retry execute success") 28 | Some(v) 29 | } 30 | case Failure(e) => { 31 | handleException(e) 32 | None 33 | } 34 | } 35 | } 36 | 37 | def retryToSuccess(timeoutMS: Int, intervalMS: Int = 500, name: String = "")(callback: => Boolean): Try[Boolean] = { 38 | val start = System.currentTimeMillis() 39 | var r = false 40 | var error: Throwable = new Exception() 41 | do { 42 | Try(callback) match { 43 | case Success(value) => { 44 | r = value 45 | } 46 | case Failure(exception) => { 47 | error = exception 48 | } 49 | } 50 | Thread sleep (intervalMS) 51 | log.info(s"name=${name} wait") 52 | } while (System.currentTimeMillis() - start < timeoutMS && !r) 53 | log.info(s"name=${name} finish") 54 | if (r) { 55 | Success(r) 56 | } else { 57 | Failure(error) 58 | } 59 | 60 | 61 | } 62 | 63 | def asyncTask[T](timeout: Int = 30, name: String = "", needThrow: Boolean = false)(callback: => T): Either[T, Throwable] = { 64 | //todo: 异步线程消耗资源厉害,需要改进 65 | val start = System.currentTimeMillis() 66 | Try({ 67 | val task = Executors.newSingleThreadExecutor().submit(new Callable[T]() { 68 | def call(): T = { 69 | callback 70 | } 71 | }) 72 | if (timeout < 0) { 73 | task.get() 74 | } else { 75 | task.get(timeout, TimeUnit.SECONDS) 76 | } 77 | 78 | }) match { 79 | case Success(v) => { 80 | val end = System.currentTimeMillis() 81 | val use = (end - start) / 1000d 82 | if (use >= 0.5) { 83 | log.info(s"use time $use seconds name=${name} result=success") 84 | } 85 | Left(v) 86 | } 87 | case Failure(e) => { 88 | val end = System.currentTimeMillis() 89 | val use = (end - start) / 1000d 90 | if (use >= 1) { 91 | log.info(s"use time $use seconds name=${name} result=error") 92 | } 93 | if (needThrow) { 94 | throw e 95 | } 96 | e match { 97 | case e: TimeoutException => { 98 | log.error(s"${timeout} seconds timeout") 99 | } 100 | case _ => { 101 | handleException(e) 102 | } 103 | } 104 | Right(e) 105 | } 106 | } 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/CrawlerTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.junit.jupiter.api.Tag; 5 | import org.junit.jupiter.api.Test; 6 | 7 | class CrawlerTest { 8 | 9 | static String caps= "appPackage=com.example.android.apis," + 10 | "appActivity=.ApiDemos," + 11 | "noReset=false," + 12 | "automationName=uiautomator2," + 13 | "autoGrantPermissions=true," + 14 | "ignoreUnimportantViews=true," + 15 | "disableAndroidWatchers=true"; 16 | @BeforeAll 17 | static void beforeAll(){ 18 | 19 | } 20 | 21 | @Test 22 | @Tag("it") 23 | @Tag("feature") 24 | 25 | void runSteps() { 26 | AppCrawler.main(new String[]{ 27 | "--capability", caps, 28 | "-y", "{ maxDepth: 3, testcase: { name: runSteps test, steps: [ " + 29 | "{xpath: \"//*[@text='Views']\", then: [ \"//*[@text='Buttons']\" ]}, " + 30 | "{xpath: \"//*[@text='Buttons']\", then: [ \"//*[@text='SMALL']\", \"//*[@text='OFF']\" ]} " + 31 | "]}}", 32 | "-vv" 33 | }); 34 | } 35 | 36 | @Test 37 | @Tag("it") 38 | void runSteps2() { 39 | AppCrawler.main(new String[]{ 40 | "--capability", caps, 41 | "-y", "{ selectedList: [], testcase: { name: runSteps test, steps: [ " + 42 | "{xpath: \"//*[@text='Views']\", then: [ \"//*[@text='Buttons']\" ]}, " + 43 | "{xpath: \"//*[@text='Buttons']\", then: [ \"//*[@text='SMALL']\", \"//*[@text='OFF']\" ]} " + 44 | "]}}", 45 | "-vv" 46 | }); 47 | } 48 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/DemoTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler; 2 | 3 | import org.junit.jupiter.api.DynamicTest; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.TestFactory; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | import static org.junit.jupiter.api.DynamicTest.dynamicTest; 13 | 14 | public class DemoTest { 15 | 16 | public String dom; 17 | 18 | 19 | @Test 20 | public void hello() { 21 | System.out.println("hello"); 22 | 23 | } 24 | 25 | @Test 26 | public void report() { 27 | 28 | //AppCrawler.main(); 29 | } 30 | 31 | @TestFactory 32 | Collection dynamicTestsFromCollection() { 33 | return Arrays.asList( 34 | dynamicTest("1st dynamic test", () -> assertTrue(false)), 35 | dynamicTest("2nd dynamic test", () -> assertEquals(4, 3)) 36 | ); 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/SuiteToClassTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler; 2 | 3 | import com.ceshiren.appcrawler.plugin.scalatest.SuiteToClass$; 4 | import javassist.CannotCompileException; 5 | import javassist.ClassPool; 6 | import javassist.CtClass; 7 | import javassist.CtConstructor; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.io.IOException; 11 | import java.util.Arrays; 12 | import java.util.HashMap; 13 | 14 | 15 | class SuiteToClassTest { 16 | 17 | @Test 18 | void genTestCaseClass() { 19 | HashMap map=new HashMap<>(); 20 | map.put("uri", "xxxxxx"); 21 | map.put("name", "demo"); 22 | 23 | SuiteToClass$.MODULE$.genTestCaseClass2( 24 | "A", 25 | "com.ceshiren.appcrawler.plugin.scalatest.TemplateTestCase", 26 | map, 27 | "/tmp/suitetoclass"); 28 | 29 | SuiteToClass$.MODULE$.genTestCaseClass2( 30 | "", 31 | "com.ceshiren.appcrawler.plugin.scalatest.TemplateTestCase", 32 | map, 33 | "/tmp/suitetoclass"); 34 | } 35 | 36 | 37 | @Test 38 | void genClass() throws CannotCompileException, IOException { 39 | ClassPool pool=ClassPool.getDefault(); 40 | CtClass clazz=pool.getOrNull("com.ceshiren.appcrawler.plugin.junit5.AllureTemplate"); 41 | System.out.println(clazz); 42 | 43 | CtClass a=pool.makeClass("AA4Test"); 44 | 45 | CtConstructor init=new CtConstructor(null, a); 46 | init.setBody("System.out.println(\"AAAA\");"); 47 | 48 | a.setSuperclass(clazz); 49 | 50 | Arrays.stream(a.getMethods()).forEach(m->{ 51 | System.out.println(m.getName()); 52 | System.out.println(m.getMethodInfo().getDescriptor()); 53 | System.out.println(m.getMethodInfo().getName()); 54 | System.out.println(m.getMethodInfo().getAttributes()); 55 | try { 56 | System.out.println(m.getAnnotations()); 57 | } catch (ClassNotFoundException e) { 58 | e.printStackTrace(); 59 | } 60 | 61 | 62 | }); 63 | //a.addConstructor(init); 64 | a.writeFile("/tmp/aa"); 65 | 66 | 67 | 68 | } 69 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/driver/BaseDDTTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.IOException; 6 | 7 | import static com.ceshiren.appcrawler.utils.Log.log; 8 | 9 | class BaseDDTTest { 10 | 11 | @Test 12 | void shell() throws IOException, InterruptedException { 13 | BaseDDT ddt = new BaseDDT(); 14 | log.info(ddt.shell("ls")); 15 | } 16 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/it/TestBackDoor.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import io.appium.java_client.AppiumDriver; 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.openqa.selenium.remote.DesiredCapabilities; 9 | 10 | import java.io.IOException; 11 | import java.net.URL; 12 | import java.util.Arrays; 13 | 14 | public class TestBackDoor { 15 | 16 | private String APP = "/Users/seveniruby/temp/appcrawler/TheApp-v1.8.1.apk"; 17 | 18 | private AppiumDriver driver; 19 | 20 | @Before 21 | public void setUp() throws IOException { 22 | DesiredCapabilities caps = new DesiredCapabilities(); 23 | 24 | caps.setCapability("platformName", "Android"); 25 | caps.setCapability("deviceName", "Android Emulator"); 26 | caps.setCapability("automationName", "Espresso"); 27 | caps.setCapability("app", APP); 28 | driver = new AppiumDriver(new URL("http://localhost:4723/wd/hub"), caps); 29 | } 30 | 31 | @After 32 | public void tearDown() { 33 | try { 34 | driver.quit(); 35 | } catch (Exception ign) {} 36 | } 37 | 38 | @Test 39 | public void testBackdoor() throws InterruptedException { 40 | Thread.sleep(3000); 41 | ImmutableMap scriptArgs = ImmutableMap.of( 42 | "target", "application", 43 | "methods", Arrays.asList(ImmutableMap.of( 44 | "name", "raiseToast", 45 | "args", Arrays.asList(ImmutableMap.of( 46 | "value", "Hello from the test script by 咪咕阅读 & 霍格沃兹测试学院!", 47 | "type", "String" 48 | )) 49 | )) 50 | ); 51 | 52 | 53 | ImmutableMap scriptArgs2 = ImmutableMap.of( 54 | "target", "application", 55 | "methods", Arrays.asList(ImmutableMap.of( 56 | "name", "getJSMainModuleName" 57 | )) 58 | ); 59 | 60 | 61 | for(int i=0;i<100;i++) { 62 | 63 | System.out.println(driver.executeScript("mobile: backdoor", scriptArgs2)); 64 | System.out.println(driver.executeScript("mobile: backdoor", scriptArgs)); 65 | Thread.sleep(1000); 66 | } 67 | try { Thread.sleep(2000); } catch (Exception ign) {} // pause to allow visual verification 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/it/appium/SettingsTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it.appium; 2 | 3 | import io.appium.java_client.AppiumDriver; 4 | import io.appium.java_client.remote.MobileCapabilityType; 5 | import org.junit.jupiter.api.Test; 6 | import org.openqa.selenium.By; 7 | import org.openqa.selenium.remote.DesiredCapabilities; 8 | 9 | import java.net.MalformedURLException; 10 | import java.net.URL; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class SettingsTest { 14 | 15 | @Test 16 | void testSettings() throws MalformedURLException, InterruptedException { 17 | 18 | DesiredCapabilities capabilities = new DesiredCapabilities(); 19 | capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android"); 20 | 21 | AppiumDriver driver = new AppiumDriver(new URL("http://127.0.0.1:4723/wd/hub"), capabilities); 22 | driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS); 23 | // driver.setSetting(Setting.WAIT_FOR_IDLE_TIMEOUT, 5000); 24 | // driver.setSetting(Setting.IGNORE_UNIMPORTANT_VIEWS, true); 25 | System.out.println(capabilities.asMap()); 26 | driver.setSetting("enableMultiWindows", true); 27 | // driver.setSetting("shouldUseCompactResponses", false); 28 | 29 | // driver.setSetting("elementResponseAttributes", "name,text,attribute/resource-id,attribute/class"); 30 | 31 | System.out.println(driver.getPageSource()); 32 | driver.findElement(By.id("com.tencent.mm:id/he6")).click(); 33 | driver.findElement(By.xpath("//*[contains(@class, 'Edit')]")).sendKeys("10086"); 34 | driver.findElement(By.xpath("//*[contains(@text, '中国移动')]")).click(); 35 | // Thread.sleep(5000); 36 | 37 | for (int i = 0; i < 5; i++) { 38 | System.out.println(driver.getPageSource()); 39 | Thread.sleep(2000); 40 | } 41 | 42 | System.out.println(driver.getSettings()); 43 | 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/it/xueqiu_conf.yml: -------------------------------------------------------------------------------- 1 | --- 2 | logLevel: "TRACE" 3 | screenshot: true 4 | showCancel: true 5 | useNewData: true 6 | reportTitle: AppCrawler雪球内部版 7 | screenshotTimeout: 20 8 | currentDriver: "android" 9 | maxTime: 10800 10 | maxDepth: 8 11 | resultDir: "" 12 | beforeStartWait: 6000 13 | afterElementWait: 500 14 | capability: 15 | newCommandTimeout: 120 16 | launchTimeout: 120000 17 | platformVersion: "" 18 | platformName: "" 19 | autoWebview: "false" 20 | autoLaunch: "true" 21 | noReset: "false" 22 | androidCapability: 23 | deviceName: "demo" 24 | appPackage: "com.xueqiu.android" 25 | appActivity: ".view.WelcomeActivityAlias" 26 | app: "" 27 | appium: "http://127.0.0.1:4723/wd/hub" 28 | fullReset: false 29 | noReset: true 30 | automationName: uiautomator2 31 | findBy: "xpath" 32 | #suiteName: 33 | # - "//*[@selected='true']//android.widget.TextView/@text" 34 | baseUrl: 35 | - ".*MainActivity" 36 | appWhiteList: 37 | - "com.xueqiu.android" 38 | urlBlackList: 39 | - ".*Talk.*" 40 | - ".*Chat.*" 41 | - ".*股市直播.*" 42 | blackList: 43 | - xpath: "拉黑" 44 | - xpath: "举报" 45 | - xpath: ".*[0-9]{2}.*" 46 | #backButton: 47 | # - xpath: "//*[contains(@content-desc, 'Navigate up')]" 48 | beforeRestart: 49 | beforeElement: 50 | afterElement: 51 | afterAll: 52 | afterAllMax: 53 | selectedList: 54 | - xpath: "//*[@clickable='true']//android.widget.TextView[string-length(@text)>0 and string-length(@text)<20]" 55 | - xpath: "//android.widget.EditText" 56 | - xpath: "//android.widget.TextView[string-length(@text)>0 and string-length(@text)<20 and @clickable='true']" 57 | - xpath: "//*[contains(name(), 'Button')]" 58 | - xpath: "//*[contains(name(), 'Image')]" 59 | firstList: 60 | - xpath: "//*[contains(name(), 'Popover')]//*" 61 | - xpath: "//*[contains(name(), 'Window')][3]//*" 62 | - xpath: "//*[contains(name(), 'Window')][2]//*" 63 | lastList: 64 | - xpath: "//*[contains(@resource-id, 'header')]//*" 65 | - xpath: "//*[contains(@resource-id, 'indicator')]//*" 66 | - xpath: "//*[../*[@selected='true']]" 67 | - xpath: "//*[../../*/*[@selected='true'] and @resource-id='']" 68 | - xpath: "//*[../../*/*[@selected='true'] and contains(@resource-id, 'tab_')]" 69 | - xpath: "//*[contains(@resource-id,'tabs')]//*" 70 | #xpathAttributes: 71 | # - "resource-id" 72 | # - "content-desc" 73 | # - "text" 74 | #sortByAttribute: 75 | # - "depth" 76 | # - "list" 77 | # - "selected" 78 | triggerActions: 79 | - action: "click" 80 | xpath: "//*[contains(@resource-id, 'user_profile_icon')]" 81 | times: 1 82 | tagLimitMax: 2 83 | tagLimit: 84 | - xpath: //*[../*[@selected='true']] 85 | count: 12 86 | - xpath: //*[../../*/*[@selected='true']] 87 | count: 12 88 | assertGlobal: -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/model/NodeTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class NodeTest { 6 | 7 | @Test 8 | void append() { 9 | Node root=new Node(null); 10 | root.append(new Node("1")); 11 | } 12 | 13 | @Test 14 | void back() { 15 | } 16 | 17 | @Test 18 | void current() { 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/model/PageSourceTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model; 2 | 3 | import com.ceshiren.appcrawler.utils.Log; 4 | import com.ceshiren.appcrawler.utils.XPathUtil; 5 | import org.junit.jupiter.api.AfterEach; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.w3c.dom.Document; 9 | 10 | import java.io.IOException; 11 | import java.nio.charset.StandardCharsets; 12 | import java.nio.file.Files; 13 | import java.nio.file.Paths; 14 | 15 | import static com.ceshiren.appcrawler.utils.Log.log; 16 | 17 | class PageSourceTest { 18 | 19 | @BeforeEach 20 | void setUp() { 21 | } 22 | 23 | @AfterEach 24 | void tearDown() { 25 | } 26 | 27 | @Test 28 | void fromXML() throws IOException { 29 | PageSource source = new PageSource(); 30 | 31 | String path = "src/test/scala/com/ceshiren/appcrawler/ut/miniprogram.xml"; 32 | String content = new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8); 33 | Document doc = XPathUtil.toDocument(content); 34 | source.fromDocument(doc); 35 | log.info(source.getNodeListByKey("(//*[@package!=''])[1]").headOption().get().get("package")); 36 | } 37 | 38 | @Test 39 | void fromJSON() { 40 | } 41 | 42 | @Test 43 | void getNodeListByKey() { 44 | } 45 | 46 | @Test 47 | void demo() { 48 | PageSource page = new PageSource(); 49 | Log.setLogFilePath("/tmp/1.log"); 50 | log.trace("trace"); 51 | log.debug("debug"); 52 | log.info("info"); 53 | page.demo(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/Junit5ReportTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.BufferedInputStream; 6 | import java.io.BufferedReader; 7 | import java.io.InputStreamReader; 8 | 9 | class Junit5ReportTest { 10 | 11 | @Test 12 | void genTestCase() throws Exception { 13 | String path = "D:\\AppCrawler\\src\\test\\java\\com\\ceshiren\\appcrawler\\report\\generateClass"; 14 | Junit5Report junit5Report = new Junit5Report(); 15 | junit5Report.genTestCase(path); 16 | } 17 | 18 | //todo:ExecuteTest 19 | @Test 20 | void runTestCase() throws Exception { 21 | String namespace = "com.ceshiren.appcrawler.report.generateClass"; 22 | Junit5Report junit5Report = new Junit5Report(); 23 | junit5Report.runTestCase(namespace); 24 | } 25 | 26 | //todo:Console Launcher 27 | @Test 28 | void runTestCase1() throws Exception { 29 | //todo:带参 30 | String command = "java -jar junit-platform-console-standalone-1.4.0.jar"; 31 | Process process = Runtime.getRuntime().exec(command); 32 | BufferedInputStream bis = new BufferedInputStream( 33 | process.getInputStream()); 34 | BufferedReader br = new BufferedReader(new InputStreamReader(bis)); 35 | String line; 36 | while ((line = br.readLine()) != null) { 37 | System.out.println(line); 38 | } 39 | 40 | process.waitFor(); 41 | if (process.exitValue() != 0) { 42 | System.out.println("error!"); 43 | } 44 | 45 | bis.close(); 46 | br.close(); 47 | } 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/LoadYamlTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.FileNotFoundException; 6 | 7 | class LoadYamlTest { 8 | 9 | @Test 10 | void loadYaml() throws FileNotFoundException { 11 | LoadYaml loadYaml = new LoadYaml(); 12 | loadYaml.loadYaml(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/MvnReplaceTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.report; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class MvnReplaceTest { 6 | 7 | @Test 8 | void setPro() { 9 | } 10 | 11 | @Test 12 | void runTest() throws Exception { 13 | MvnReplace.runTest(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/CountryCodeSelectActivityTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/CountryCodeSelectActivityTests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/LoginActivityTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/LoginActivityTests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/LoginOptionActivityTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/LoginOptionActivityTests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/MyProfileActivityTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/MyProfileActivityTests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/id=register_phone_numberTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/id=register_phone_numberTests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/id=tv_loginTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/id=tv_loginTests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/id=tv_login_by_phone_or_othersTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/id=tv_login_by_phone_or_othersTests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/id=user_profile_iconTests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/id=user_profile_iconTests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/report/generateClass/雪球Tests.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seveniruby/AppCrawler/c5242631fa4e9958383ef3926db9ace0777d8fc1/src/test/java/com/ceshiren/appcrawler/report/generateClass/雪球Tests.class -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/AllureTemplateTestcase.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | import com.ceshiren.appcrawler.model.URIElementStore; 3 | import com.ceshiren.appcrawler.plugin.junit5.AllureTemplate; 4 | import com.ceshiren.appcrawler.plugin.report.Report; 5 | import com.ceshiren.appcrawler.plugin.report.ReportFactory; 6 | 7 | public class AllureTemplateTestcase extends AllureTemplate { 8 | public AllureTemplateTestcase(){ 9 | Report report= ReportFactory.getReportEngine("junit5"); 10 | URIElementStore store=report.loadResult("E://elements.yml"); 11 | ReportFactory.initStore(store); 12 | 13 | //todo: 老的数据不再兼容新的代码,需要重跑 14 | //不明白以上todo注释的理解 15 | this.pageName="com.xueqiu.android.LoginActivity"; 16 | ReportFactory.showCancel_$eq(true); 17 | } 18 | 19 | /*@Test 20 | public void run(){ 21 | 22 | }*/ 23 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/BackBtnXpathTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | import com.ceshiren.appcrawler.AppCrawler; 4 | import org.junit.Test; 5 | 6 | import java.util.Date; 7 | 8 | public class BackBtnXpathTest { 9 | @Test 10 | public void testCase2(){ 11 | AppCrawler.main(new String[]{ 12 | "--capability", 13 | "appPackage=com.xueqiu.android," + 14 | "appActivity=.view.WelcomeActivityAlias," + 15 | "noReset=false," + 16 | "automationName=uiautomator2," + 17 | "autoGrantPermissions=true," + 18 | "ignoreUnimportantViews=true," + 19 | "disableAndroidWatchers=true", 20 | "-o", 21 | "/temp/xueqiu/ut/backBtnXpath/" + new java.text.SimpleDateFormat("YYYYMMddHHmmss").format(new Date().getTime()), 22 | "-c", 23 | "src/test/java/com/ElementInfo/appcrawler/it/xueqiu_conf.yml" 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/Context.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | public class Context { 7 | List items() { 8 | return Arrays.asList( 9 | new Item("Item 1", "$19.99", Arrays.asList(new Feature("New!"), new Feature("Awesome!"))), 10 | new Item("Item 2", "$29.99", Arrays.asList(new Feature("Old."), new Feature("Ugly."))) 11 | ); 12 | } 13 | 14 | static class Item { 15 | Item(String name, String price, List features) { 16 | this.name = name; 17 | this.price = price; 18 | this.features = features; 19 | } 20 | String name, price; 21 | List features; 22 | } 23 | 24 | static class Feature { 25 | Feature(String description) { 26 | this.description = description; 27 | } 28 | String description; 29 | } 30 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/CountryCodeSelectActivityTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | import com.ceshiren.appcrawler.model.URIElementStore; 4 | import com.ceshiren.appcrawler.plugin.junit5.AllureTemplate; 5 | import com.ceshiren.appcrawler.plugin.report.Report; 6 | import com.ceshiren.appcrawler.plugin.report.ReportFactory; 7 | 8 | public class CountryCodeSelectActivityTest extends AllureTemplate { 9 | public CountryCodeSelectActivityTest() { 10 | Report var1 = ReportFactory.getReportEngine("junit5"); 11 | URIElementStore var2 = var1.loadResult("E://elements.yml"); 12 | ReportFactory.initStore(var2); 13 | super.pageName = "com.xueqiu.android.CountryCodeSelectActivity"; 14 | ReportFactory.showCancel_$eq(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/DiffTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | import com.ceshiren.appcrawler.AppCrawler; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class DiffTest { 7 | 8 | @Test 9 | public void testDiff(){ 10 | 11 | AppCrawler.main(new String[]{ 12 | "--report","/tmp/xueQiu400/reportDiff", 13 | "--master","/tmp/xueQiu400/20190315164643/elements.yml", 14 | "--candidate","/tmp/xueQiu400/20190315161950/elements.yml" 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/ElementUrlTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | import com.ceshiren.appcrawler.model.URIElement; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class ElementUrlTest { 7 | 8 | @Test 9 | public void test1(){ 10 | URIElement element = new URIElement( 11 | "com.xueqiu.android.MainActivity", 12 | "TextView", "TextView", 13 | "agree", 14 | "","好的","","7","","","","","", 15 | 0,0,0,0,""); 16 | 17 | System.out.println(element.elementUri()); 18 | } 19 | } -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/ExecutingTests.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | public class ExecutingTests { 4 | //todo:执行生成的activity类 5 | } 6 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/GenerateClassTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | import com.ceshiren.appcrawler.plugin.junit5.AllureTemplate; 4 | import javassist.ClassPool; 5 | import javassist.CtClass; 6 | import javassist.CtConstructor; 7 | 8 | 9 | public class GenerateClassTest { 10 | String ClassName; 11 | public GenerateClassTest(String ClassName) throws Exception{ 12 | this.ClassName = ClassName; 13 | init(); 14 | } 15 | public void init() throws Exception { 16 | 17 | String activity = "com.xueqiu.android." + ClassName; 18 | System.out.println(activity); 19 | //ClassPool:CtClass对象的容器 20 | ClassPool pool = ClassPool.getDefault(); 21 | 22 | //通过ClassPool生成一个public新类ClassName 23 | CtClass ctClass = pool.makeClass(ClassName + "Test"); 24 | //声明父类 25 | ctClass.setSuperclass(pool.get(AllureTemplate.class.getName())); 26 | 27 | //添加构造函数 28 | //CtConstructor ctConstructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, ctClass); 29 | CtConstructor ctConstructorNull = new CtConstructor(new CtClass[]{}, ctClass); 30 | StringBuffer buffer = new StringBuffer(); 31 | buffer.append("com.ceshiren.appcrawler.plugin.report.Report report=com.ceshiren.appcrawler.ReportFactory.genReport(\"junit5\");\n" + 32 | " com.ceshiren.appcrawler.data.AbstractElementStore store=report.loadResult(\"E://elements.yml\");\n" + 33 | " com.ceshiren.appcrawler.ReportFactory.initStore(store);\n" + 34 | " this.pageName=\""+ activity +"\";\n" + 35 | " com.ceshiren.appcrawler.ReportFactory.showCancel_$eq(true);"); 36 | System.out.println("{\n"+ buffer.toString() +"\n}"); 37 | //ctConstructor.setBody("{"+ buffer.toString() +"}"); 38 | ctConstructorNull.setBody("{"+ buffer.toString() +"}"); 39 | //把构造函数添加到新的类中 40 | ctClass.addConstructor(ctConstructorNull); 41 | //ctClass.addConstructor(ctConstructor); 42 | System.out.println("生成Activity类开始..."); 43 | //把生成的class文件写入文件夹 44 | ctClass.writeFile("E://"); 45 | System.out.println("生成Activity类完毕..."); 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/JUnit5ExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | import org.junit.jupiter.api.*; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | public class JUnit5ExceptionTest { 8 | 9 | static int index=0; 10 | @BeforeAll 11 | static void beforeAll(){ 12 | System.out.println("before all"); 13 | } 14 | @BeforeEach 15 | void beforeEach() throws Exception { 16 | System.out.println("before each"); 17 | System.out.println(this); 18 | System.out.println(index); 19 | if(index==2) { 20 | throw new Exception("after each exception"); 21 | } 22 | } 23 | @Test 24 | void case1() throws Exception { 25 | System.out.println("case1"); 26 | index+=1; 27 | System.out.println(index); 28 | assertTrue(true); 29 | } 30 | 31 | @Test 32 | void case2() throws Exception { 33 | System.out.println("case2"); 34 | index+=1; 35 | System.out.println(index); 36 | throw new Exception("ddd"); 37 | } 38 | 39 | @Test 40 | void case3() throws Exception { 41 | System.out.println("case3"); 42 | index+=1; 43 | System.out.println(index); 44 | assertTrue(true); 45 | } 46 | 47 | @AfterEach 48 | void afterEach() throws Exception { 49 | System.out.println("after each"); 50 | } 51 | 52 | @AfterAll 53 | static void afterAll(){ 54 | System.out.println("after all"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/Junit5TemplateTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | import com.ceshiren.appcrawler.report.ReadYaml; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.HashSet; 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | public class Junit5TemplateTest { 11 | Map map; 12 | Set set = new HashSet<>(); 13 | //1.读取.yml文件中的activity 14 | public Junit5TemplateTest() throws Exception { 15 | ReadYaml readYaml = new ReadYaml(); 16 | map = readYaml.convert2Map("E://elements.yml"); 17 | Map mapAct = (Map)map.get("elementStore"); 18 | for(Object activity : mapAct.keySet()){ 19 | //截至目前,set中存储的是activity的名字,接下来开始第二步 20 | set.add(activity.toString().split("\\.")[3]); 21 | } 22 | } 23 | 24 | //2.利用javassist动态生成extends AllureTemplate的子类,pageName作为参数传入 25 | @Test 26 | public void test() throws Exception { 27 | for(String activityName : set){ 28 | System.out.println(activityName); 29 | new GenerateClassTest(activityName); 30 | } 31 | System.out.println("AllureTemplate的子类生成完毕..."); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/ut/MustcheTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut; 2 | 3 | 4 | import com.github.mustachejava.DefaultMustacheFactory; 5 | import com.github.mustachejava.Mustache; 6 | import com.github.mustachejava.MustacheFactory; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.io.PrintWriter; 12 | 13 | public class MustcheTest { 14 | 15 | @Test 16 | void demo() throws IOException { 17 | MustacheFactory mf = new DefaultMustacheFactory(); 18 | Mustache mustache = mf.compile("template.mustache"); 19 | mustache.execute(new PrintWriter(System.out), new Context()).flush(); 20 | } 21 | 22 | @Test 23 | void path(){ 24 | System.out.println(System.getenv("PATH")); 25 | 26 | for(String path :System.getenv("PATH").split(File.pathSeparator)){ 27 | String allurePath=path+File.separator+"allure"; 28 | System.out.println(allurePath); 29 | System.out.println(new File(allurePath).exists()); 30 | } 31 | 32 | 33 | } 34 | 35 | @Test 36 | void exec() throws IOException { 37 | Runtime.getRuntime().exec("allure2"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/ceshiren/appcrawler/utils/LogTest.java: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.utils; 2 | 3 | import org.apache.logging.log4j.Level; 4 | import org.junit.jupiter.api.Test; 5 | 6 | class LogTest { 7 | 8 | @Test 9 | void setLogFilePath() { 10 | Log.log.trace("trace"); 11 | Log.log.info("demo"); 12 | Log.log.info(Log.log.getLevel()); 13 | Log.log.info(Log.log.getName()); 14 | Log.setLogFilePath("/tmp/1.log"); 15 | Log.log.trace("trace"); 16 | Log.log.info("info"); 17 | Log.log.trace("trace"); 18 | Log.log.info(Log.log.getLevel()); 19 | Log.log.info(Log.log.getName()); 20 | } 21 | 22 | @Test 23 | void setLevel() { 24 | Log.log.trace("trace"); 25 | Log.log.debug("debug"); 26 | Log.log.info("info"); 27 | Log.setLevel(Level.DEBUG); 28 | Log.setLogFilePath("/tmp/1.log"); 29 | Log.log.trace("trace2"); 30 | Log.log.debug("debug2"); 31 | Log.log.info("info2"); 32 | } 33 | } -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/driver/AdbDriverTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver 2 | 3 | import com.ceshiren.appcrawler.AppCrawler 4 | import org.scalatest.FunSuite 5 | 6 | class AdbDriverTest extends FunSuite { 7 | 8 | test("testGetPageSource") { 9 | println(AdbDriverTest.adbDriver.getPageSource()) 10 | } 11 | 12 | test("getAdb"){ 13 | println(AdbDriverTest.adbDriver.getAdb()) 14 | } 15 | 16 | test("screenshot"){ 17 | println(AdbDriverTest.adbDriver.screenshot().getAbsolutePath) 18 | } 19 | 20 | test("getUrl"){ 21 | println(AdbDriverTest.adbDriver.getAppName()) 22 | println(AdbDriverTest.adbDriver.getUrl()) 23 | 24 | } 25 | 26 | test("it"){ 27 | AppCrawler.main(Array( 28 | "-y", 29 | "blackList: [ {xpath: action_night}, {xpath: action_setting} ]", 30 | "--capability", 31 | "appPackage=com.xueqiu.android," + 32 | "appActivity=.view.WelcomeActivityAlias," + 33 | "automationName=adb,noReset=false," + 34 | "udid=adb.wetest.qq.com:41272," + 35 | "autoGrantPermissions=true," + 36 | "ignoreUnimportantViews=true," + 37 | "disableAndroidWatchers=true", 38 | "-o", 39 | s"/tmp/xueqiu/adb/${new java.text.SimpleDateFormat("YYYYMMddHHmmss").format(new java.util.Date().getTime)}", 40 | "-vv" 41 | ) 42 | ) 43 | } 44 | 45 | } 46 | 47 | object AdbDriverTest{ 48 | val adbDriver=new AdbDriver() 49 | } 50 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/driver/MockDriverTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver 2 | 3 | import com.ceshiren.appcrawler.model.URIElement 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | import org.junit.jupiter.api.Test 6 | 7 | class MockDriverTest { 8 | @Test 9 | def waitTest(): Unit ={ 10 | val mockDriver=new MockDriver() 11 | log.info("find 测吧") 12 | mockDriver.wait("测吧") 13 | log.info("find xxx") 14 | mockDriver.wait("xxx") 15 | log.info("finish") 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/driver/ReactWebDriverTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver 2 | 3 | import com.ceshiren.appcrawler.utils.LogicUtils.asyncTask 4 | import org.scalatest.FunSuite 5 | 6 | class ReactWebDriverTest extends FunSuite { 7 | 8 | test("testAsyncTask") { 9 | val client=new AppiumClient() 10 | asyncTask(10){ 11 | println("xxxx") 12 | println("dddddd") 13 | Thread.sleep(1000) 14 | } 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/driver/SeleniumDriverTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.driver 2 | 3 | import com.ceshiren.appcrawler.core.CrawlerConf 4 | import com.ceshiren.appcrawler.AppCrawler 5 | 6 | import java.net.URL 7 | import org.jsoup.nodes.Entities.EscapeMode 8 | import org.scalatest.{BeforeAndAfterEach, FunSuite} 9 | 10 | import scala.io.Source 11 | 12 | class SeleniumDriverTest extends FunSuite with BeforeAndAfterEach { 13 | 14 | override def beforeEach() { 15 | 16 | } 17 | 18 | test("testGetPageSource") { 19 | 20 | val selenium=new SeleniumDriver( configMap = Map( 21 | "browserName"-> "chrome", 22 | "url" -> "http://127.0.0.1:4444/wd/hub" 23 | )) 24 | 25 | selenium.conf=new CrawlerConf() 26 | selenium.driver.get("https://www.baidu.com") 27 | println(selenium.getPageSource()) 28 | 29 | 30 | } 31 | 32 | test("baidu"){ 33 | 34 | AppCrawler.main(Array( 35 | "--capability", "automationName=selenium,browserName=chrome,app=http://www.baidu.com", 36 | "-u", "http://127.0.0.1:4444/wd/hub", 37 | "-o", s"/Volumes/ram/baidu/${new java.text.SimpleDateFormat("YYYYMMddHHmmss").format(new java.util.Date().getTime)}", 38 | "--verbose" 39 | ) 40 | ) 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestAppium.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import com.ceshiren.appcrawler.driver.AppiumClient 4 | import io.appium.java_client.android.AndroidDriver 5 | import io.appium.java_client.remote.{AndroidMobileCapabilityType, MobileCapabilityType} 6 | import org.openqa.selenium.WebElement 7 | import org.openqa.selenium.remote.DesiredCapabilities 8 | import org.scalatest.FunSuite 9 | 10 | import java.net.URL 11 | import java.util.concurrent.TimeUnit 12 | 13 | /** 14 | * Created by seveniruby on 16/9/24. 15 | */ 16 | class TestAppium extends FunSuite{ 17 | val a=new AppiumClient() 18 | 19 | test("single session"){ 20 | val capa=new DesiredCapabilities() 21 | capa.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "com.xueqiu.android") 22 | capa.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".view.WelcomeActivityAlias") 23 | capa.setCapability(MobileCapabilityType.DEVICE_NAME, "demo") 24 | val driver=new AndroidDriver[WebElement](new URL("http://127.0.0.1:4723/wd/hub/"), capa) 25 | } 26 | 27 | test("test get window size"){ 28 | //System.setProperty("webdriver.http.factory", "apache") 29 | val capa=new DesiredCapabilities() 30 | capa.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "com.xueqiu.android") 31 | capa.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".view.WelcomeActivityAlias") 32 | capa.setCapability(MobileCapabilityType.DEVICE_NAME, "demo") 33 | val driver=new AndroidDriver[WebElement](new URL("http://127.0.0.1:5723/wd/hub/"), capa) 34 | Thread.sleep(10000) 35 | println(driver.manage().window().getSize) 36 | driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS) 37 | println(driver.getPageSource) 38 | } 39 | 40 | test("test xueqiu agreen"){ 41 | val capa=new DesiredCapabilities() 42 | capa.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "com.xueqiu.android") 43 | capa.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".view.WelcomeActivityAlias") 44 | capa.setCapability(MobileCapabilityType.DEVICE_NAME, "demo") 45 | capa.setCapability("uiautomationName", "uiautomator2") 46 | val driver=new AndroidDriver[WebElement](new URL("http://127.0.0.1:4723/wd/hub/"), capa) 47 | driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS) 48 | driver.findElementById("agree").click() 49 | println(driver.getPageSource) 50 | println(driver.getPageSource) 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestImage.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import org.scalatest.FunSuite 4 | 5 | //todo: 图像识别 6 | class TestImage extends FunSuite{ 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestJianShu.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import io.appium.java_client.android.AndroidDriver 4 | import org.openqa.selenium.WebElement 5 | import org.openqa.selenium.remote.DesiredCapabilities 6 | import org.scalatest._ 7 | 8 | import java.net.URL 9 | import scala.jdk.CollectionConverters._ 10 | 11 | /** 12 | * Created by seveniruby on 2017/6/6. 13 | */ 14 | class TestJianShu extends FunSuite with BeforeAndAfterAll with BeforeAndAfterEach with Matchers { 15 | 16 | val capabilities=new DesiredCapabilities() 17 | capabilities.setCapability("deviceName", "emulator-5554") 18 | capabilities.setCapability("appPackage", "com.jianshu.haruki") 19 | capabilities.setCapability("appActivity", "com.baiji.jianshu.account.SplashScreenActivity") 20 | capabilities.setCapability("unicodeKeyboard", "true") 21 | 22 | var driver=new AndroidDriver[WebElement](new URL("http://127.0.0.1:4723/wd/hub/"), capabilities) 23 | 24 | override def beforeAll(): Unit ={ 25 | capabilities.setCapability("app", "/Users/seveniruby/Downloads/Jianshu-2.3.1-17051515-1495076675.apk") 26 | driver=new AndroidDriver[WebElement](new URL("http://127.0.0.1:4723/wd/hub/"), capabilities) 27 | Thread.sleep(3000) 28 | verbose() 29 | } 30 | 31 | override def beforeEach(): Unit = { 32 | capabilities.setCapability("app", "") 33 | driver=new AndroidDriver[WebElement](new URL("http://127.0.0.1:4723/wd/hub/"), capabilities) 34 | Thread.sleep(3000) 35 | verbose() 36 | 37 | } 38 | 39 | def verbose(): Unit ={ 40 | println() 41 | println(driver.currentActivity()) 42 | println(driver.getPageSource) 43 | } 44 | 45 | test("绕过登陆"){ 46 | driver.findElementByXPath("//*[@text='跳过']").click() 47 | driver.findElementById("iv_close").click() 48 | driver.findElementsByXPath("//*[@text='登录']").size() should be >= 1 49 | } 50 | 51 | test("错误密码登录"){ 52 | driver.findElementByXPath("//*[@text='跳过']").click() 53 | driver.findElementByXPath("//*[@text='已有帐户登录']").click() 54 | driver.findElementByXPath("//*[@text='手机或邮箱']").sendKeys("seveniruby@gmail.com") 55 | driver.findElementByXPath("//*[@password='true']").sendKeys("wrong") 56 | driver.findElementByXPath("//*[@text='登录']").click() 57 | verbose() 58 | driver.findElementsByXPath("//*[contains(@text, '错误')]").size() should be >= 1 59 | } 60 | 61 | test("随便看看"){ 62 | driver.findElementByXPath("//*[@text='跳过']").click() 63 | driver.findElementByXPath("//*[@text='随便看看']").click() 64 | verbose() 65 | driver.findElementsByXPath("//*[contains(@resource-id, 'tag_flow_layout')]//*[contains(name(),'TextView')]").asScala.foreach(tag => { 66 | tag.click() 67 | Thread.sleep(1000) 68 | driver.findElementsByXPath("//*[@text='关注']").size() should be >=1 69 | driver.navigate().back() 70 | }) 71 | } 72 | 73 | override def afterEach(): Unit = { 74 | driver.quit() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestMacaca.scala.bak: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import org.scalatest.{BeforeAndAfterAll, FunSuite} 4 | import org.apache.log4j.Logger 5 | import com.alibaba.fastjson.JSONObject 6 | import macaca.client.MacacaClient 7 | 8 | 9 | /** 10 | * Created by seveniruby on 2017/4/17. 11 | */ 12 | class TestMacaca extends FunSuite with BeforeAndAfterAll{ 13 | 14 | val driver=new MacacaClient() 15 | override def beforeAll(): Unit = { 16 | 17 | val porps = new JSONObject() 18 | porps.put("autoAcceptAlerts", true) 19 | porps.put("browserName", "") 20 | porps.put("platformName", "android") 21 | porps.put("package", "com.gotokeep.keep") 22 | porps.put("activity", ".activity.SplashActivity") 23 | porps.put("reuse", 3) 24 | 25 | val desiredCapabilities = new JSONObject() 26 | desiredCapabilities.put("desiredCapabilities", porps) 27 | driver.initDriver(desiredCapabilities) 28 | 29 | } 30 | test("macaca android"){ 31 | println(driver.source()) 32 | } 33 | test("macaca chrome"){ 34 | val porps = new JSONObject() 35 | porps.put("autoAcceptAlerts", true) 36 | porps.put("browserName", "Chrome") 37 | porps.put("platformName", "desktop") // android or ios 38 | 39 | porps.put("javascriptEnabled", true) 40 | porps.put("platform", "ANY") 41 | 42 | val desiredCapabilities = new JSONObject() 43 | desiredCapabilities.put("desiredCapabilities", porps) 44 | driver.initDriver(desiredCapabilities) 45 | driver.get("http://www.baidu.com/") 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestNW.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import org.openqa.selenium.chrome.{ChromeDriver, ChromeOptions} 4 | import org.openqa.selenium.remote.{DesiredCapabilities, RemoteWebDriver} 5 | import org.scalatest.FunSuite 6 | 7 | import java.net.URL 8 | import scala.jdk.CollectionConverters._ 9 | 10 | /** 11 | * Created by seveniruby on 16/11/14. 12 | */ 13 | class TestNW extends FunSuite{ 14 | test("test nw"){ 15 | 16 | System.setProperty("webdriver.chrome.driver", 17 | "/Users/seveniruby/projects/nwjs/ics4_debug_nw0.14.7/chromedriver") 18 | val options=new ChromeOptions() 19 | options.addArguments("nwapp=/Users/seveniruby/projects/nwjs/ics4_debug_nw0.14.7/app") 20 | val driver=new ChromeDriver(options) 21 | println(driver.getPageSource) 22 | Thread.sleep(2000) 23 | driver.findElementsByXPath("//label").asScala.foreach(x=>{ 24 | println(x.getTagName) 25 | println(x.getLocation) 26 | println(x.getText) 27 | println("text()="+x.getAttribute("text()")) 28 | println("text="+x.getAttribute("text")) 29 | println("value="+x.getAttribute("value")) 30 | println("name="+x.getAttribute("name")) 31 | println("id="+x.getAttribute("id")) 32 | println("class="+x.getAttribute("class")) 33 | println("type="+x.getAttribute("type")) 34 | println("placeholder="+x.getAttribute("placeholder")) 35 | println("============") 36 | }) 37 | driver.findElementByXPath("//label[contains(., 'selectedRegion')]").click() 38 | 39 | //driver.quit() 40 | 41 | } 42 | 43 | test("test nw remote"){ 44 | val options=new ChromeOptions() 45 | options.addArguments("nwapp=/Users/seveniruby/projects/nwjs/ics4_debug_nw0.14.7/app") 46 | val url="http://10.3.2.65:4444/wd/hub" 47 | 48 | val dc = DesiredCapabilities.chrome() 49 | dc.setCapability(ChromeOptions.CAPABILITY, options) 50 | 51 | val driver=new RemoteWebDriver(new URL(url), dc) 52 | println(driver.getPageSource) 53 | Thread.sleep(2000) 54 | driver.findElementsByXPath("//label").asScala.foreach(x=>{ 55 | println(x.getTagName) 56 | println(x.getLocation) 57 | println(x.getText) 58 | println("text()="+x.getAttribute("text()")) 59 | println("text="+x.getAttribute("text")) 60 | println("value="+x.getAttribute("value")) 61 | println("name="+x.getAttribute("name")) 62 | println("id="+x.getAttribute("id")) 63 | println("class="+x.getAttribute("class")) 64 | println("type="+x.getAttribute("type")) 65 | println("placeholder="+x.getAttribute("placeholder")) 66 | println("============") 67 | }) 68 | driver.findElementByXPath("//label[contains(., 'selectedRegion')]").click() 69 | 70 | //driver.quit() 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestOCR.scala: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | package com.ceshiren.appcrawler.ut 4 | 5 | import java.awt.{BasicStroke, Color} 6 | import javax.imageio.ImageIO 7 | 8 | import net.sourceforge.tess4j.ITessAPI.TessPageIteratorLevel 9 | import org.scalatest.FunSuite 10 | import net.sourceforge.tess4j._ 11 | import scala.jdk.CollectionConverters._ 12 | 13 | 14 | /** 15 | * Created by seveniruby on 16/9/20. 16 | */ 17 | class TestOCR extends FunSuite{ 18 | 19 | test("test ocr"){ 20 | val api=new Tesseract() 21 | api.setTessVariable("TESSDATA_PREFIX", "/Users/seveniruby/temp/ocr/") 22 | api.setDatapath("/Users/seveniruby/temp/ocr/tessdata/") 23 | api.setLanguage("eng+chi_sim") 24 | 25 | //val path = "/Users/seveniruby/temp/ocr/ocr.png" 26 | //val path="/Users/seveniruby/temp/ocr/103_com.gotokeep.keep-PersonalPageActivity_android.widget.RelativeLayout-title_bar-android.widget.RelativeLayout-left_button.clicked.png" 27 | val path="/Users/seveniruby/temp/ocr/t1t.jpeg" 28 | val img=new java.io.File(path) 29 | val imgFile=ImageIO.read(img) 30 | val graph=imgFile.createGraphics() 31 | graph.setStroke(new BasicStroke(5)) 32 | graph.setColor(Color.RED) 33 | 34 | val result=api.doOCR(img) 35 | 36 | val words=api.getWords(imgFile, TessPageIteratorLevel.RIL_WORD).toList 37 | words.foreach(word=>{ 38 | 39 | val box=word.getBoundingBox 40 | val x=box.getX.toInt 41 | val y=box.getY.toInt 42 | val w=box.getWidth.toInt 43 | val h=box.getHeight.toInt 44 | 45 | graph.drawRect(x, y, w, h) 46 | graph.drawString(word.getText, x, y) 47 | 48 | println(word.getBoundingBox) 49 | println(word.getText) 50 | }) 51 | graph.dispose() 52 | ImageIO.write(imgFile, "png", new java.io.File(s"${img}.mark.png")) 53 | 54 | 55 | 56 | println(result) 57 | 58 | } 59 | 60 | } 61 | */ 62 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestQQ.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import com.ceshiren.appcrawler.AppCrawler 4 | import io.appium.java_client.android.AndroidDriver 5 | import org.openqa.selenium.WebElement 6 | import org.scalatest.{BeforeAndAfterAll, FunSuite} 7 | 8 | 9 | class SampleTest extends FunSuite with BeforeAndAfterAll{ 10 | private var driver:AndroidDriver[WebElement] = null 11 | 12 | test("test automation"){ 13 | AppCrawler.main(Array("-c", "src/test/scala/com/ceshiren/appcrawler/it/qq_automation.yml", 14 | "-o", s"/tmp/xueqiu/${System.currentTimeMillis()}", "--verbose" 15 | ) 16 | ) 17 | } 18 | 19 | 20 | test("ruqi automation"){ 21 | AppCrawler.main(Array("-c", "src/test/scala/com/ceshiren/appcrawler/it/ruqi_automation.yml", 22 | "-o", s"/tmp/ruqi/${System.currentTimeMillis()}", "--verbose" 23 | ) 24 | ) 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestSauceLabs.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import org.openqa.selenium.remote.{DesiredCapabilities, RemoteWebDriver} 4 | import org.scalatest.FunSuite 5 | 6 | import java.net.URL 7 | import scala.jdk.CollectionConverters._ 8 | 9 | class TestSauceLabs extends FunSuite{ 10 | 11 | val app = "/Users/seveniruby/Desktop/baiduyun/百度云同步盘/seven/Dropbox/sihanjishu/startup/测吧/业务/如期/如期-iOS安装包/PLM.ipa" 12 | //val app="/Users/seveniruby/Desktop/baiduyun/百度云同步盘/seven/Dropbox/sihanjishu/startup/测吧/业务/如期/PLM.zip" 13 | 14 | test("ios测试"){ 15 | val capability=new DesiredCapabilities() 16 | //capability.setCapability("app", app) 17 | //capability.setCapability("bundleId", "com.example.apple-samplecode.UICatalog") 18 | //capability.setCapability("appPackage", "com.example.apple-samplecode.UICatalog") 19 | //capability.setCapability("appActivity", "com.example.apple-samplecode.UICatalog") 20 | 21 | 22 | capability.setCapability("fullReset", "false") 23 | capability.setCapability("noReset", "false") 24 | //capability.setCapability("udid", "4F05E384-FE32-43DE-8539-4DC3E2EBC117") 25 | capability.setCapability("automationName", "XCUITest") 26 | capability.setCapability("platformName", "ios") 27 | capability.setCapability("platformVersion", "10.2") 28 | capability.setCapability("deviceName", "iPhone 7") 29 | capability.setCapability("autoAcceptAlerts", true) 30 | 31 | 32 | //val url="http://192.168.100.65:7771" 33 | //val url="http://127.0.0.1:8100" 34 | val url="http://127.0.0.1:4723/wd/hub" 35 | val driver=new RemoteWebDriver(new URL(url), capability) 36 | println(driver.getPageSource) 37 | driver.findElementsByXPath("//*[@label='OK']") match { 38 | case array if array.size()>0 => array.asScala.head.click() 39 | case _ => println("no OK alert") 40 | } 41 | driver.findElementsByXPath("//*").asScala.foreach(x=>{ 42 | println(x) 43 | println(x.getText) 44 | }) 45 | 46 | } 47 | 48 | test("ios测试 saucelabs"){ 49 | val capability=new DesiredCapabilities() 50 | capability.setCapability("app", app) 51 | 52 | capability.setCapability("bundleId", "www.ruqi.com") 53 | //capability.setCapability("bundleId", "com.example.apple-samplecode.UICatalog") 54 | //capability.setCapability("appPackage", "com.example.apple-samplecode.UICatalog") 55 | //capability.setCapability("appActivity", "com.example.apple-samplecode.UICatalog") 56 | capability.setCapability("fullReset", "false") 57 | capability.setCapability("noReset", "false") 58 | //capability.setCapability("udid", "4F05E384-FE32-43DE-8539-4DC3E2EBC117") 59 | capability.setCapability("automationName", "XCUITest") 60 | capability.setCapability("platformName", "ios") 61 | capability.setCapability("platformVersion", "10.2") 62 | capability.setCapability("deviceName", "iPhone 7") 63 | capability.setCapability("autoAcceptAlerts", true) 64 | 65 | capability.setCapability("testobject_api_key", "E571F6B0932E4DB1BD8E554A97904A0C") 66 | capability.setCapability("testobject_app_id", "ruqi") 67 | capability.setCapability("testobject_suite_name ", "My Suite 1!") 68 | capability.setCapability("testobject_test_name", "My Test 1!") 69 | //capability.setCapability("name", "My Test 1!") 70 | 71 | 72 | // Set Appium version 73 | capability.setCapability("appiumVersion", "1.7.1") 74 | val url = "https://us1.appium.testobject.com/wd/hub" 75 | 76 | 77 | //val url="http://192.168.100.65:7771" 78 | //val url="http://127.0.0.1:8100" 79 | //val url="http://127.0.0.1:4723/wd/hub" 80 | val driver=new RemoteWebDriver(new URL(url), capability) 81 | println(driver.getPageSource) 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestTesterHome.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import io.appium.java_client.android.AndroidDriver 4 | import org.openqa.selenium.WebElement 5 | import org.openqa.selenium.remote.DesiredCapabilities 6 | import org.scalatest._ 7 | 8 | import java.net.URL 9 | import scala.jdk.CollectionConverters._ 10 | 11 | /** 12 | * Created by seveniruby on 2017/6/6. 13 | */ 14 | class Testceshiren extends FunSuite with BeforeAndAfterAll with BeforeAndAfterEach with Matchers { 15 | 16 | val capabilities=new DesiredCapabilities() 17 | capabilities.setCapability("deviceName", "emulator-5554") 18 | capabilities.setCapability("app", "/Users/seveniruby/Downloads/app-release.apk_1.1.0.apk") 19 | capabilities.setCapability("appPackage", "com.ceshiren.nativeandroid") 20 | capabilities.setCapability("appActivity", ".views.MainActivity") 21 | capabilities.setCapability("unicodeKeyboard", "true") 22 | 23 | var driver=new AndroidDriver[WebElement](new URL("http://127.0.0.1:4723/wd/hub/"), capabilities) 24 | 25 | override def beforeEach(): Unit = { 26 | capabilities.setCapability("app", "") 27 | driver=new AndroidDriver[WebElement](new URL("http://127.0.0.1:4723/wd/hub/"), capabilities) 28 | Thread.sleep(3000) 29 | verbose() 30 | 31 | } 32 | 33 | def verbose(): Unit ={ 34 | println() 35 | println(driver.currentActivity()) 36 | println(driver.getPageSource) 37 | } 38 | 39 | test("招聘"){ 40 | driver.findElementByXPath("//*[@content-desc='Open navigation drawer']").click() 41 | driver.findElementByXPath("//*[@text='招聘']").click() 42 | driver.getContextHandles.asScala.foreach(println) 43 | verbose() 44 | driver.findElementsByXPath("//*[@text='欢迎报名第三届中国移动互联网测试开发大会']").size() should be >=1 45 | } 46 | test("精华帖"){ 47 | driver.findElementByXPath("//*[@content-desc='Open navigation drawer']").click() 48 | driver.findElementByXPath("//*[@text='社区']").click() 49 | //等待动画切换完成 50 | Thread.sleep(3000) 51 | driver.findElementByXPath("//*[@text='精华']").click() 52 | driver.findElementByXPath("//*[contains(@text, '王者荣耀')]").click() 53 | driver.findElementByXPath("//*[contains(@text, '评论')]").click() 54 | driver.findElementsByXPath("//*[@text='恒温']").size() should be >=1 55 | } 56 | 57 | override def afterEach(): Unit = { 58 | driver.quit() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestUiautomator2Server.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import io.appium.java_client.android.AndroidDriver 4 | import org.openqa.selenium.WebElement 5 | import org.openqa.selenium.remote.{DesiredCapabilities, RemoteWebDriver} 6 | import org.scalatest.FunSuite 7 | 8 | import java.net.URL 9 | 10 | 11 | class TestUiautomator2Server extends FunSuite{ 12 | 13 | 14 | test("selenium remote"){ 15 | val caps=new DesiredCapabilities() 16 | caps.setCapability("appPackage", "com.xueqiu.android") 17 | val driver=new RemoteWebDriver(new URL("http://127.0.0.1:8300/wd/hub"), caps) 18 | println(driver.getPageSource) 19 | } 20 | 21 | 22 | 23 | test("android driver"){ 24 | //System.setProperty("webdriver.http.factory", "okhttp") 25 | val caps=new DesiredCapabilities() 26 | caps.setCapability("appPackage", "com.xueqiu.android") 27 | val driver= { 28 | new AndroidDriver[WebElement](new URL("http://127.0.0.1:8200/wd/hub"), caps) 29 | } 30 | println(driver.getPageSource) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/TestWebDriverAgent.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.it 2 | 3 | import com.ceshiren.appcrawler.core.AppiumSuite 4 | import org.openqa.selenium.remote.{DesiredCapabilities, RemoteWebDriver} 5 | 6 | import java.net.URL 7 | import scala.jdk.CollectionConverters._ 8 | 9 | /** 10 | * Created by seveniruby on 16/6/3. 11 | */ 12 | class TestWebDriverAgent extends AppiumSuite{ 13 | test("use remote webdriver"){ 14 | val capability=new DesiredCapabilities() 15 | capability.setCapability("app", "/Users/seveniruby/projects/snowball-ios/DerivedData/Snowball/Build/Products/Debug-iphonesimulator/Snowball.app") 16 | capability.setCapability("bundleId", "com.xueqiu") 17 | capability.setCapability("fullReset", "true") 18 | capability.setCapability("noReset", "true") 19 | capability.setCapability("udid", "4F05E384-FE32-43DE-8539-4DC3E2EBC117") 20 | capability.setCapability("automationName", "XCUITest") 21 | capability.setCapability("platformName", "ios") 22 | capability.setCapability("deviceName", "iPhone Simulator") 23 | capability.setCapability("bundleId", "com.xueqiu") 24 | 25 | //val url="http://192.168.100.65:7771" 26 | val url="http://127.0.0.1:4723/wd/hub" 27 | val driver=new RemoteWebDriver(new URL(url), capability) 28 | println(driver.getPageSource) 29 | } 30 | 31 | 32 | test("use remote webdriver meituan"){ 33 | val capability=new DesiredCapabilities() 34 | capability.setCapability("app", "/Users/seveniruby/Downloads/app/waimai.app") 35 | capability.setCapability("bundleId", "com.meituan.iToGo.ep") 36 | //capability.setCapability("fullReset", false) 37 | //capability.setCapability("noReset", true) 38 | //capability.setCapability("udid", "4F05E384-FE32-43DE-8539-4DC3E2EBC117") 39 | capability.setCapability("automationName", "XCUITest") 40 | capability.setCapability("platformName", "ios") 41 | capability.setCapability("deviceName", "iPhone 6") 42 | capability.setCapability("platformVersion", "10.2") 43 | capability.setCapability("autoAcceptAlerts", true) 44 | //capability.setCapability("webDriverAgentUrl", "http://172.18.118.90:8100/") 45 | 46 | //val url="http://192.168.100.65:7771" 47 | //val url="http://127.0.0.1:8100" 48 | val url="http://127.0.0.1:4730/wd/hub" 49 | val driver=new RemoteWebDriver(new URL(url), capability) 50 | 51 | while(true){ 52 | Thread.sleep(2000) 53 | println(driver.getPageSource) 54 | } 55 | 56 | } 57 | 58 | test("use remote webdriver xueqiu"){ 59 | val capability=new DesiredCapabilities() 60 | capability.setCapability("app", "/Users/seveniruby/projects/snowball-ios/DerivedData/Snowball/Build/Products/Debug-iphonesimulator/Snowball.app") 61 | capability.setCapability("bundleId", "com.xueqiu") 62 | capability.setCapability("fullReset", "false") 63 | capability.setCapability("noReset", "true") 64 | //capability.setCapability("udid", "4F05E384-FE32-43DE-8539-4DC3E2EBC117") 65 | capability.setCapability("automationName", "XCUITest") 66 | capability.setCapability("platformName", "ios") 67 | capability.setCapability("deviceName", "iPhone Simulator") 68 | capability.setCapability("bundleId", "com.xueqiu") 69 | capability.setCapability("autoAcceptAlerts", true) 70 | 71 | 72 | //val url="http://192.168.100.65:7771" 73 | //val url="http://127.0.0.1:8100" 74 | val url="http://127.0.0.1:4730/wd/hub" 75 | val driver=new RemoteWebDriver(new URL(url), capability) 76 | 77 | while(true){ 78 | Thread.sleep(2000) 79 | driver.findElementsByXPath("//*").asScala.foreach(e=>{ 80 | println(s"tag=${e.getTagName} text=${e.getText}") 81 | }) 82 | println(driver.getPageSource) 83 | println("==============") 84 | } 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/keep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pluginList: 3 | - "com.ceshiren.appcrawler.plugin.TagLimitPlugin" 4 | - "com.ceshiren.appcrawler.plugin.ReportPlugin" 5 | logLevel: "TRACE" 6 | reportTitle: "Keep" 7 | saveScreen: true 8 | screenshotTimeout: 20 9 | currentDriver: "android" 10 | showCancel: true 11 | tagLimitMax: 3 12 | tagLimit: 13 | - xpath: //*[../*[@selected='true']] 14 | count: 12 15 | maxTime: 10800 16 | resultDir: "" 17 | capability: 18 | newCommandTimeout: 120 19 | launchTimeout: 120000 20 | platformVersion: "" 21 | platformName: "Android" 22 | autoWebview: "false" 23 | autoLaunch: "true" 24 | noReset: "true" 25 | androidInstallTimeout: 180000 26 | androidCapability: 27 | deviceName: "192.168.0.102:5555" 28 | appPackage: "com.gotokeep.keep" 29 | appActivity: ".activity.SplashActivity" 30 | app: "" 31 | appium: "http://127.0.0.1:4723/wd/hub" 32 | # automationName: uiautomator2 33 | automationName: macaca 34 | reuse: 3 35 | # nativeWebScreenshot: "true" 36 | defineUrl: 37 | - //*[@selected='true' and contains(name(), 'TextView')]/@text 38 | appWhiteList: 39 | - android 40 | - com.shafa.market 41 | baseUrl: 42 | - ".*MainActivity" 43 | - ".*SNBHomeView.*" 44 | maxDepth: 20 45 | headFirst: true 46 | enterWebView: true 47 | urlBlackList: 48 | - .*OutdoorSummaryMap.* 49 | - .*PersonalPage.* 50 | - .*Training.* 51 | - .*FriendRank.* 52 | - .*\\.base\\.Container.* 53 | - ".*球友.*" 54 | - ".*png.*" 55 | - ".*Talk.*" 56 | - ".*Chat.*" 57 | - ".*Safari.*" 58 | - "WriteStatus.*" 59 | - "Browser.*" 60 | - "MyselfUser" 61 | - ".*MyselfUser.*" 62 | - ".*股市直播.*" 63 | #urlWhiteList: 64 | #- ".*Main.*" 65 | backButton: 66 | - //*[contains(@resource-id, "left_button")] 67 | #defaultBackAction: 68 | #- import sys.process._; 69 | #- Thread.sleep(5000) 70 | #- val name=Seq("adb", "shell", "dumpsys window windows | grep mCurrentFocus").!!.split(" ")(4).split("/")(0) 71 | #- println(s"kill package ${name}") 72 | #- Seq("adb", "shell", s"am force-stop ${name}").!! 73 | #firstList: 74 | #- //*[contains(@resource-id, "layout_picker_view_container"] 75 | selectedList: 76 | #android非空标签 77 | - //*[@clickable='true'] 78 | - //*[@clickable='true']//*[contains(name(), 'Text') and string-length(@text)>0 and string-length(@text)<10 ] 79 | #通用的button和image 80 | - //*[@clickable='true']//*[contains(name(), 'Button')] 81 | - //*[@clickable='true']//*[contains(name(), 'Image')] 82 | #todo:如果多个规则都包含相同控件, 如何排序 83 | #处于选中状态的同级控件最后点击 84 | lastList: 85 | - //*[../*[@selected='true']] 86 | - //*[../../*/*[@selected='true']] 87 | - //*[../../*/*[@selected='true'] and contains(@resource-id, 'tab_')] 88 | - //*[contains(name(), "HorizontalScrollView")] 89 | - //*[@resource-id='com.gotokeep.keep:id/layout_bottom'] 90 | blackList: 91 | - ".*\\.[0-9].*" 92 | - ".*[0-9][0-9].*" 93 | - //*[contains(@resource-id, "wrapper_in_custom_title_bar")]//*[contains(@resource-id, "right_button")] 94 | - //*[contains(@resource-id, "share")] 95 | - //*[contains(@text, "开始第")] 96 | - //*[contains(@resource-id, "lock")] 97 | - //*[contains(@text, "举报")] 98 | triggerActions: 99 | - xpath: //*[contains(@resource-id, "layout_picker_view_container")]//*[@text="确定"] 100 | - xpath: //*[contains(@resource-id, "content-wrapper_dialog")]//*[@text="不发了"] 101 | - xpath: //*[@text="拒绝"] 102 | - xpath: //*[@text="结束训练"] 103 | - xpath: //*[contains(@resource-id, "quit_confirm_button")]//*[contains(@text, "确定")] 104 | - xpath: //*[contains(@resource-id, "layout_right_second_button")]//*[contains(@resource-id, "right_second_button")] 105 | action: yoga 106 | times: 1 107 | - xpath: //*[contains(@text, "微信朋友圈")] 108 | - xpath: //*[contains(@text, "发送")] 109 | - xpath: 110 | asserts: 111 | - given: 112 | - //*[@text="胸部"] 113 | when: [] 114 | then: 115 | - //*[contains(@text, "success")] 116 | - //*[@package="com.gotokeep.keep"] 117 | 118 | 119 | #所有view的叶子节点 一般表示游戏 120 | #- action: monkey 121 | # xpath: //android.view.View[not(*) and contains(@bounds, "[0,0]") ] 122 | # times: 20 123 | #startupActions: 124 | #- println(driver) 125 | #beforeElementAction: 126 | #- xpath: //*[@resource-id="com.shafa.market:id/nav"]//android.widget.TextView 127 | # action: MiniAppium.event(21) 128 | #- Thread.sleep(3000) 129 | #- println(driver.getPageSource()) 130 | #afterElementAction: 131 | #- println(driver) 132 | #afterUrlFinished: 133 | #- monkey() 134 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/maimai.yml: -------------------------------------------------------------------------------- 1 | --- 2 | logLevel: "TRACE" 3 | saveScreen: true 4 | reportTitle: "" 5 | screenshotTimeout: 20 6 | currentDriver: "Android" 7 | swipeRetryMax: 2 8 | waitLoading: 1000 9 | waitLaunch: 10000 10 | tagLimitMax: 300 11 | tagLimit: [] 12 | showCancel: false 13 | maxTime: 10800 14 | resultDir: "/tmp/maimai/" 15 | sikuliImages: "" 16 | devices: 17 | - platformName: "" 18 | platformVersion: "9.2" 19 | deviceName: "iPhone 6" 20 | capability: 21 | appActivity: "" 22 | appium: "http://127.0.0.1:4723/wd/hub" 23 | noReset: "true" 24 | app: "" 25 | appPackage: "" 26 | fullReset: "false" 27 | dontStopAppOnReset: true 28 | androidCapability: 29 | app: "" 30 | appPackage: "com.taou.maimai" 31 | # appActivity: ".SplashActivity" 32 | appActivity: .MainActivity 33 | iosCapability: 34 | app: "" 35 | bundleId: "" 36 | autoAcceptAlerts: "true" 37 | xpathAttributes: 38 | - "name" 39 | - "label" 40 | - "value" 41 | - "resource-id" 42 | - "content-desc" 43 | - "index" 44 | - "text" 45 | defineUrl: 46 | - //*[@text='职位详情' and @instance='0' ]/ancestor::*//*[@class='android.view.View' and (@instance='16' or @instance='17') ] 47 | - //*[@text='极速联系' and @instance='0' ]/ancestor::*//*[@class='android.view.View' and @instance='1' ] 48 | - //*[@text='求职' and @instance='0' ] 49 | baseUrl: [] 50 | appWhiteList: [] 51 | maxDepth: 6 52 | sortByAttribute: 53 | - "depth" 54 | - "selected" 55 | enterWebView: true 56 | urlBlackList: [] 57 | urlWhiteList: [] 58 | defaultBackAction: [] 59 | backButton: [] 60 | firstList: [] 61 | selectedList: 62 | - xpath: 回复过 63 | action: click 64 | - xpath: 极速联系 65 | action: click 66 | blackList: [] 67 | triggerActions: [] 68 | autoCrawl: true 69 | assert: 70 | name: "ceshiren AppCrawler" 71 | steps: [] 72 | testcase: 73 | name: "ceshiren AppCrawler" 74 | steps: 75 | - xpath: 人脉办事 76 | action: click 77 | - xpath: 求职招聘 78 | action: click 79 | beforeElementAction: [] 80 | afterElementAction: [] 81 | afterUrlFinished: 82 | - given: 83 | - 互联网测试 84 | when: 85 | action: "driver.swipe(0.5, 0.7, 0.5, 0.3)" 86 | beforeRestart: [] 87 | monkeyEvents: [] 88 | monkeyRunTimeSeconds: 30 89 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/meituanwaimai_private.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pluginList: 3 | - "com.ceshiren.appcrawler.plugin.FlowDiff" 4 | #- "com.ceshiren.appcrawler.plugin.ProxyPlugin" 5 | logLevel: "TRACE" 6 | saveScreen: true 7 | reportTitle: 美团app自动遍历报告 8 | screenshotTimeout: 20 9 | tagLimitMax: 2 10 | currentDriver: "android" 11 | maxTime: 10800 12 | resultDir: "" 13 | capability: 14 | newCommandTimeout: 120 15 | launchTimeout: 120000 16 | platformVersion: "" 17 | platformName: "" 18 | noReset: "true" 19 | androidCapability: 20 | deviceName: "demo" 21 | appPackage: "com.sankuai.meituan.takeoutnew" 22 | appActivity: ".ui.page.boot.WelcomeActivity" 23 | app: "" 24 | appium: "http://127.0.0.1:4723/wd/hub" 25 | iosCapability: 26 | deviceName: "iPhone 6 Plus" 27 | bundleId: "com.meituan.iToGo.ep" 28 | screenshotWaitTimeout: "10" 29 | platformVersion: "10.2" 30 | app: "/Users/seveniruby/Downloads/app/waimai.app" 31 | appium: "http://127.0.0.1:4730/wd/hub" 32 | automationName: "xcuitest" 33 | defineUrl: 34 | - "//*[@selected='true']/@text" 35 | - "//*[@selected='true']/@text" 36 | - "//*[contains(name(), 'NavigationBar')]/@label" 37 | baseUrl: 38 | - ".*MainActivity" 39 | - ".*SNBHomeView.*" 40 | maxDepth: 20 41 | headFirst: true 42 | enterWebView: true 43 | urlBlackList: [] 44 | backButton: [] 45 | firstList: 46 | - "//*[contains(name(), 'Popover')]//*" 47 | - "//*[contains(name(), 'Window')][3]//*" 48 | - "//*[contains(name(), 'Window')][2]//*" 49 | selectedList: 50 | #android非空标签 51 | - //*[clickable="true"] 52 | - //*[clickable="true"]//android.widget.TextView[string-length(@text)>0 and string-length(@text)<20] 53 | - //android.widget.EditText 54 | - //*[contains(name(), 'Text') and string-length(@text)>0 and string-length(@text)<20 ] 55 | #ios 56 | - //*[contains(name(), 'Text') and string-length(@value)>0 and string-length(@value)<20 ] 57 | #通用的button和image 58 | - //*[contains(name(), 'Button')] 59 | - //*[contains(name(), 'Image')] 60 | - //*[@resource-id="com.xueqiu.android:id/minute_period_container"] 61 | #todo:如果多个规则都包含相同控件, 如何排序 62 | #处于选中状态的同级控件最后点击 63 | lastList: [] 64 | blackList: 65 | - //*[contains(name(), 'TextField')] 66 | - //*[contains(name(), 'EditText')] 67 | #排除掉ios的状态栏 68 | - //*[contains(name(), 'StatusBar')]//* 69 | triggerActions: 70 | - xpath: 聚餐加个菜 71 | - xpath: 眉州 72 | tagLimit: 73 | - xpath: //*[../*[@selected='true']] 74 | count: 12 75 | - xpath: //*[../../*/*[@selected='true']] 76 | count: 12 77 | startupActions: 78 | - swipe("left") 79 | - swipe("right") 80 | - swipe("down") 81 | beforeElementAction: [] 82 | afterElementAction: 83 | - driver.executeScript("alert(777)") 84 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/qq_automation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pluginList: 3 | - "com.ceshiren.appcrawler.plugin.FlowDiff" 4 | #- "com.ceshiren.appcrawler.plugin.ProxyPlugin" 5 | logLevel: "TRACE" 6 | saveScreen: false 7 | reportTitle: AppCrawler雪球内部版 8 | screenshotTimeout: 20 9 | tagLimitMax: 20000 10 | swipeRetryMax: 300 11 | currentDriver: "android" 12 | maxTime: 10800 13 | resultDir: "" 14 | xpathAttributes: ["name", "label", "value", "resource-id", "content-desc", "class", "text"] 15 | androidCapability: 16 | platformName: "Android" 17 | #automationName: uiautomator2 18 | dontStopAppOnReset: "true" 19 | newCommandTimeout: 120 20 | #unicodeKeyboard: "true" 21 | #resetKeyboard: "true" 22 | #应用配置 23 | deviceName: "demo" 24 | appPackage: "com.tencent.mobileqq" 25 | appActivity: ".activity.SplashActivity" 26 | app: "" 27 | appium: "http://127.0.0.1:4723/wd/hub" 28 | defineUrl: 29 | - string(//*[@content-desc='群成员默认排序']/@content-desc) 30 | - string(//*[contains(@content-desc, '加好友') and @clickable='true']/@content-desc) 31 | - string(//*[@text=' 管理员 ']/@text) 32 | - "^.*\\([0-9]{5,}\\)" 33 | maxDepth: 10 34 | selectedList: 35 | - xpath: //*[contains(@text, 'LV')]/../*/* 36 | triggerActions: 37 | - xpath: "//*[@text='消息']/ancestor::*//*[contains(name(), 'EditText')]" 38 | - { xpath: "//*[contains(@text, '看点')]/ancestor::*//*[contains(name(), 'EditText')]", action: "Selenium" } 39 | - xpath: 来自.*群 40 | - xpath: 来自.*群聊 41 | - xpath: 群资料卡 42 | - xpath: ^共[0-9]*人 43 | - { xpath: "^加载中.*", action: "Thread.sleep(5000)" } 44 | beforeRestart: 45 | #- "adb devices" #| "grep BNU0217628002684" #|| "adb kill-server" #&& "adb devices"! 46 | - "adb devices" #| "grep BNU0217628002684" #|| "adb kill-server" #&& "adb devices"! 47 | afterUrlFinished: 48 | - given: 49 | - //*[@content-desc='群聊成员默认排序'] 50 | when: 51 | action: "driver.swipe(0.5, 0.7, 0.5, 0.3)" -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/ruqi_automation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | logLevel: "TRACE" 3 | reportTitle: 如期 4 | xpathAttributes: ["name", "label", "value", "resource-id", "content-desc", "class", "text"] 5 | androidCapability: 6 | platformName: "Android" 7 | noReset: "false" 8 | fullReset: "false" 9 | dontStopAppOnReset: "true" 10 | newCommandTimeout: 120 11 | unicodeKeyboard: "true" 12 | resetKeyboard: "true" 13 | #应用配置 14 | app: "" 15 | #app: "/Users/seveniruby/Desktop/baiduyun/百度云同步盘/seven/Dropbox/sihanjishu/startup/测吧/业务/如期/tfs_ruqi_云测_v2.1.0.apk" 16 | deviceName: "127.0.0.1:6886" 17 | appPackage: "com.tritonsfs.ruqi" 18 | appActivity: ".ui.activity.SplashActivity" 19 | appium: "http://127.0.0.1:4723/wd/hub" 20 | maxDepth: 10 21 | selectedList: [] 22 | backButton: 23 | - xpath: rl_left_first 24 | appWhiteList: ["com.android.packageinstaller"] 25 | waitLoading: 3000 26 | waitLaunch: 10000 27 | triggerActions: 28 | - { xpath: "//*[contains(@class, 'Button') and contains(@text, '安装') and not(contains(@package, 'com.tritonsfs.ruqi')) ]" } 29 | - { xpath: "//*[contains(@class, 'Button') and contains(@text, '允许') and not(contains(@package, 'com.tritonsfs.ruqi')) ]" } 30 | - { xpath: "//*[contains(@class, 'Button') and contains(@text, '信任') and not(contains(@package, 'com.tritonsfs.ruqi')) ]" } 31 | - { xpath: "//*[contains(@resource-id, 'permission_allow_button')]"} 32 | - { xpath: "//*[contains(@resource-id, 'iv_pager') and @clickable='false' ]", action: "driver.swipe(0.9, 0.5, 0.1, 0.5)" } 33 | - { xpath: "//*[contains(@resource-id, 'iv_pager') and @clickable='true' ]" } 34 | - { xpath: "//*[contains(@resource-id, 'et_login_phone') and not(contains(@text, '156 0053 4760'))]", action: 15600534760 } 35 | - { xpath: iv_roll_circle_loading, action: Thread.sleep(1000) } 36 | - { xpath: 获取验证码 } 37 | - { xpath: 短信验证码, action: "123456" } 38 | - { xpath: 登录, times: 1 } 39 | - { xpath: 以后再说 } 40 | - { xpath: 请输入图片验证码, action: "1234" } 41 | - { xpath: tv_sms_submit } 42 | - { xpath: 评估额度, times: 1 } 43 | - { xpath: 基本信息, times: 1 } 44 | - { xpath: "^人行征信$", times: 1 } 45 | - { xpath: 芝麻信用, times: 1 } 46 | - { xpath: 新手指引, times: 1 } 47 | - { xpath: 息费试算, times: 1 } 48 | - { xpath: 申请查询, times: 1 } 49 | - { xpath: 帮助中心, times: 1 } 50 | - { xpath: 信用生活, times: 1 } 51 | - { xpath: 学车分期, times: 1 } 52 | - { xpath: 更多精彩, times: 1 } 53 | - { xpath: 借款管家, times: 1 } 54 | - { xpath: 信用报告, times: 1 } 55 | - { xpath: 网信贷, times: 1 } 56 | - { xpath: 信用乐, times: 1 } 57 | - { xpath: ^我的$, times: 1 } 58 | - { xpath: 实名认证, times: 1 } 59 | - { xpath: 个人资料, times: 1 } 60 | - { xpath: "//*[contains(@text, '个人资料')]/ancestor::*//*[contains(@text, '人行征信') ]", times: 1 } 61 | - { xpath: 我的借款, times: 1 } 62 | - { xpath: 银行卡, times: 1 } 63 | - { xpath: "//*[contains(@text, '银行账户管理')]/ancestor::*//*[contains(@resource-id, 'ib_right_first') ]", times: 1 } 64 | - { xpath: iv_shooting_bank_card, times: 1 } -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/shafa_private.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pluginList: 3 | - "com.ceshiren.appcrawler.plugin.TagLimitPlugin" 4 | - "com.ceshiren.appcrawler.plugin.ReportPlugin" 5 | logLevel: "TRACE" 6 | reportTitle: "华为" 7 | saveScreen: true 8 | screenshotTimeout: 20 9 | currentDriver: "android" 10 | tagLimitMax: 3 11 | tagLimit: 12 | - xpath: //*[../*[@selected='true']] 13 | count: 12 14 | maxTime: 10800 15 | resultDir: "" 16 | capability: 17 | newCommandTimeout: 120 18 | launchTimeout: 120000 19 | platformVersion: "" 20 | platformName: "" 21 | autoWebview: "false" 22 | autoLaunch: "true" 23 | noReset: "false" 24 | androidInstallTimeout: 180000 25 | androidCapability: 26 | deviceName: "192.168.0.103:5555" 27 | appPackage: "com.shafa.market" 28 | appActivity: ".ShafaMainAct" 29 | app: "" 30 | appium: "http://127.0.0.1:4723/wd/hub" 31 | # nativeWebScreenshot: "true" 32 | defineUrl: 33 | - //*[@selected='true' and contains(name(), 'TextView')]/@text 34 | appWhiteList: 35 | - android 36 | - com.shafa.market 37 | baseUrl: 38 | - ".*MainActivity" 39 | - ".*SNBHomeView.*" 40 | maxDepth: 20 41 | headFirst: true 42 | enterWebView: true 43 | urlBlackList: 44 | - ".*球友.*" 45 | - ".*png.*" 46 | - ".*Talk.*" 47 | - ".*Chat.*" 48 | - ".*Safari.*" 49 | - "WriteStatus.*" 50 | - "Browser.*" 51 | - "MyselfUser" 52 | - ".*MyselfUser.*" 53 | - ".*股市直播.*" 54 | #urlWhiteList: 55 | #- ".*Main.*" 56 | backButton: 57 | - "//*[@resource-id='action_back']" 58 | - "//*[@resource-id='android:id/up']" 59 | - "//*[@resource-id='android:id/home']" 60 | - "//*[@resource-id='android:id/action_bar_title']" 61 | - "//*[@name='nav_icon_back']" 62 | - "//*[@name='Back']" 63 | - "//*[@name='返回']" 64 | - "//*[@name='确定']" 65 | defaultBackAction: 66 | - import sys.process._; 67 | - Thread.sleep(5000) 68 | - val name=Seq("adb", "shell", "dumpsys window windows | grep mCurrentFocus").!!.split(" ")(4).split("/")(0) 69 | - println(s"kill package ${name}") 70 | - Seq("adb", "shell", s"am force-stop ${name}").!! 71 | #firstList: 72 | selectedList: 73 | #android非空标签 74 | - //*[@clickable='true'] 75 | - //*[@clickable='true']//*[contains(name(), 'Text') and string-length(@text)>0 and string-length(@text)<10 ] 76 | #通用的button和image 77 | - //*[@clickable='true']//*[contains(name(), 'Button')] 78 | - //*[@clickable='true']//*[contains(name(), 'Image')] 79 | #todo:如果多个规则都包含相同控件, 如何排序 80 | #处于选中状态的同级控件最后点击 81 | lastList: 82 | - //*[../*[@selected='true']] 83 | - //*[../../*/*[@selected='true']] 84 | - //*[../../*/*[@selected='true'] and contains(@resource-id, 'tab_')] 85 | blackList: 86 | - //*[contains(name(), 'EditText')] 87 | - 运行 88 | - 提交评论 89 | - //*[@text='全部更新'] 90 | - //*[@text='全部更新']/..//* 91 | - //*[../*[@text='全部更新']] 92 | - "[0-9].*" 93 | triggerActions: 94 | #- action: "click" 95 | # xpath: "沙发桌面" 96 | # times: 1 97 | - action: "click" 98 | xpath: "退出" 99 | times: 0 100 | - action: "click" 101 | xpath: "不保存" 102 | times: 0 103 | - action: "click" 104 | xpath: "确定" 105 | times: 0 106 | - action: "click" 107 | xpath: "关闭" 108 | times: 0 109 | - action: "click" 110 | xpath: "取消" 111 | times: 0 112 | - action: "click" 113 | xpath: "我要退出" 114 | times: 0 115 | - action: "click" 116 | xpath: "知道了" 117 | times: 0 118 | #所有view的叶子节点 一般表示游戏 119 | - action: monkey 120 | xpath: //android.view.View[not(*) and contains(@bounds, "[0,0]") ] 121 | times: 20 122 | startupActions: 123 | - println(driver) 124 | beforeElementAction: 125 | - xpath: //*[@resource-id="com.shafa.market:id/nav"]//android.widget.TextView 126 | action: MiniAppium.event(21) 127 | #- Thread.sleep(3000) 128 | #- println(driver.getPageSource()) 129 | afterElementAction: 130 | - println(driver) 131 | #afterUrlFinished: 132 | #- monkey() 133 | monkeyEvents: 134 | - 19 135 | - 19 136 | - 19 137 | - 19 138 | - 20 139 | - 21 140 | - 22 141 | - 23 142 | - 23 143 | - 23 144 | - 23 145 | - 23 146 | - 66 -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/weixin.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pluginList: [] 3 | logLevel: "TRACE" 4 | saveScreen: false 5 | reportTitle: AppCrawler雪球内部版 6 | screenshotTimeout: 20 7 | tagLimitMax: 500 8 | currentDriver: "android" 9 | maxTime: 10800 10 | resultDir: "" 11 | capability: 12 | newCommandTimeout: 120 13 | launchTimeout: 120000 14 | platformVersion: "" 15 | platformName: "" 16 | autoWebview: "false" 17 | autoLaunch: "true" 18 | noReset: true 19 | fullReset: false 20 | androidCapability: 21 | deviceName: "demo" 22 | appPackage: "com.tencent.mm" 23 | appActivity: ".ui.LauncherUI" 24 | app: "" 25 | appium: "http://127.0.0.1:4723/wd/hub" 26 | maxDepth: 20 27 | selectedList: 28 | - //*[contains(@text, '第二届')] 29 | - //*[contains(@content-desc, '聊天信息')] 30 | - //*[@text='发消息' or @text='添加到通讯录'] 31 | - //*[@text='发送'] 32 | - //*[contains(@content-desc, '搜索')] 33 | - //android.widget.ListView//android.widget.ImageView 34 | triggerActions: 35 | - xpath: //*[@text='搜索'] 36 | action: ceshiren 37 | times: 1 38 | - xpath: //*[contains(@text, '第二届')] 39 | times: 1 40 | - xpath: //*[contains(@content-desc, '聊天信息')] 41 | times: 1 42 | - xpath: //*[@text='添加到通讯录'] 43 | - xpath: //*[@text='发送'] 44 | - xpath: //*[contains(@content-desc, '搜索')] 45 | times: 1 46 | backButton: 47 | - //*[@content-desc='返回'] 48 | afterElementAction: 49 | - Thread.sleep(1000) -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/xiaomi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | logLevel: "TRACE" 3 | saveScreen: true 4 | showCancel: true 5 | reportTitle: AppCrawler雪球内部版 6 | screenshotTimeout: 20 7 | tagLimitMax: 2 8 | currentDriver: "android" 9 | maxTime: 10800 10 | capability: 11 | newCommandTimeout: 120 12 | launchTimeout: 120000 13 | platformVersion: "" 14 | platformName: "" 15 | autoWebview: "false" 16 | autoLaunch: "true" 17 | noReset: "true" 18 | fullReset: "false" 19 | dontStopAppOnReset: "true" 20 | androidCapability: 21 | deviceName: "demo" 22 | appPackage: "com.xiaomi.vipaccount" 23 | appActivity: ".ui.VipAccountActivity" 24 | app: "" 25 | appium: "http://192.168.31.27:4723/wd/hub" 26 | automationName: appium 27 | iosCapability: 28 | deviceName: "iPhone 6 Plus" 29 | bundleId: "com.xueqiu" 30 | screenshotWaitTimeout: "10" 31 | platformVersion: "9.3" 32 | autoAcceptAlerts: "true" 33 | app: "/Users/seveniruby/Library/Developer/Xcode/DerivedData/Snowball-ckpjegabufjxgxfeqyxgkmjuwmct/Build/Products/Debug-iphonesimulator/Snowball.app" 34 | appium: "http://127.0.0.1:4730/wd/hub" 35 | maxDepth: 8 -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/xueqiu_automation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pluginList: 3 | - "com.ceshiren.appcrawler.plugin.FlowDiff" 4 | #- "com.ceshiren.appcrawler.plugin.ProxyPlugin" 5 | logLevel: "TRACE" 6 | saveScreen: false 7 | showCancel: true 8 | reportTitle: AppCrawler雪球内部版 9 | screenshotTimeout: 20 10 | tagLimitMax: 5 11 | currentDriver: "android" 12 | maxTime: 10800 13 | resultDir: "" 14 | capability: 15 | newCommandTimeout: 120 16 | launchTimeout: 120000 17 | platformVersion: "" 18 | platformName: "" 19 | autoWebview: "false" 20 | autoLaunch: "true" 21 | noReset: "true" 22 | fullReset: "false" 23 | dontStopAppOnReset: "true" 24 | androidCapability: 25 | deviceName: "demo" 26 | appPackage: "com.xueqiu.android" 27 | appActivity: ".view.WelcomeActivityAlias" 28 | app: "" 29 | appium: "http://127.0.0.1:4723/wd/hub" 30 | #automationName: uiautomator2 31 | iosCapability: 32 | deviceName: "iPhone 6 Plus" 33 | bundleId: "com.xueqiu" 34 | screenshotWaitTimeout: "10" 35 | platformVersion: "9.3" 36 | autoAcceptAlerts: "true" 37 | app: "/Users/seveniruby/Library/Developer/Xcode/DerivedData/Snowball-ckpjegabufjxgxfeqyxgkmjuwmct/Build/Products/Debug-iphonesimulator/Snowball.app" 38 | appium: "http://192.168.31.27:4723/wd/hub" 39 | defineUrl: 40 | - "//*[@selected='true']/@text" 41 | - "//*[@selected='true']/@text" 42 | - "//*[contains(name(), 'NavigationBar')]/@label" 43 | #baseUrl: 44 | #- ".*MainActivity" 45 | #- ".*SNBHomeView.*" 46 | maxDepth: 1 47 | headFirst: true 48 | enterWebView: true 49 | urlBlackList: 50 | - ".*球友.*" 51 | - ".*png.*" 52 | - ".*Talk.*" 53 | - ".*Chat.*" 54 | - ".*Safari.*" 55 | - "WriteStatus.*" 56 | - "Browser.*" 57 | - "MyselfUser" 58 | - ".*MyselfUser.*" 59 | - ".*股市直播.*" 60 | #urlWhiteList: 61 | #- ".*Main.*" 62 | backButton: 63 | - xpath: //*[@resource-id='action_back'] 64 | - xpath: //*[@resource-id='android:id/up'] 65 | - xpath: //*[@resource-id='android:id/home'] 66 | - xpath: //*[@resource-id='android:id/action_bar_title'] 67 | - xpath: //*[@name='nav_icon_back'] 68 | - xpath: //*[@name='Back'] 69 | - xpath: //*[@name='返回'] 70 | - xpath: "//*[contains(name(), 'Button') and @name='取消']" 71 | - xpath: "//*[contains(name(), 'Button') and @label='返回']" 72 | - xpath: "//*[contains(name(), 'Button') and @name='关闭']" 73 | - xpath: "//*[contains(name(), 'Button') and @name='首页']" 74 | triggerActions: 75 | - xpath: "//*[contains(@resource-id, 'iv_close')]" 76 | - xpath: "//*[@resource-id='com.xueqiu.android:id/button_login']" 77 | times: 1 78 | - action: "15600534760" 79 | xpath: "//*[@resource-id='com.xueqiu.android:id/login_account']" 80 | times: 1 81 | - xpath: "//*[@resource-id='com.xueqiu.android:id/login_account']" 82 | times: 1 83 | - action: "hys2xueqiu" 84 | xpath: "//*[@resource-id='com.xueqiu.android:id/login_password']" 85 | times: 1 86 | - xpath: "button_next" 87 | times: 1 88 | - action: "15600534760" 89 | xpath: "//*[contains(name(), 'StaticText') and contains(@name, '登录')]" 90 | times: 1 91 | - action: "15600534760" 92 | xpath: "//*[contains(name(), 'TextField') and contains(@value, '手机')]" 93 | times: 1 94 | - action: "hys2xueqiu" 95 | xpath: "//*[contains(name(), 'SecureTextField')]" 96 | times: 1 97 | - xpath: "//*[contains(name(), 'Button') and contains(@name, '登 录')]" 98 | times: 1 99 | - xpath: ".*立即登录" 100 | times: 2 101 | - xpath: "//*[@name='登 录']" 102 | times: 2 103 | - xpath: "//*[@name='登录']" 104 | times: 2 105 | - action: "scroll left" 106 | xpath: "专题" 107 | times: 1 108 | - xpath: "点此.*" 109 | times: 3 110 | - xpath: "放弃" 111 | - xpath: "不保存" 112 | - xpath: "^确定$" 113 | - xpath: "^关闭$" 114 | - xpath: "取消" 115 | - xpath: "稍后再说" 116 | - xpath: "Cancel" 117 | - xpath: "这里可以.*" 118 | - xpath: ".*搬到这里.*" 119 | - xpath: "我要退出" 120 | - xpath: "tip_click_position" 121 | - xpath: "common guide icon ok" 122 | - xpath: "icon quotationinformation day" 123 | times: 1 124 | - xpath: "icon stock close" 125 | - xpath: "隐藏键盘" 126 | #一个神奇的符号 127 | - xpath: //*[@label='✕' and visible='true'] 128 | times: 10 129 | - action: 123 130 | xpath: //*[contains(name(), "EditText")] 131 | times: 10 132 | pri: 0 133 | - xpath: 我知道了 134 | testcase: 135 | name: demo1 136 | steps: 137 | - when: 138 | xpath: //* 139 | action: driver.swipe(0.8, 0.8, 0.2, 0.2) 140 | then: ["//*[@resource-id!='']"] 141 | - when: { xpath: //*, action: driver.swipe(0.5, 0.2, 0.5, 0.8) } 142 | - { xpath: 自选, action: click, then: [ "//*[contains(@text, '港股')]" ] } 143 | - { xpath: 沪深, action: click, then: [ "//*[contains(@text, '中国平安')]" ] } -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/xueqiu_base.yml: -------------------------------------------------------------------------------- 1 | --- 2 | logLevel: "TRACE" 3 | saveScreen: true 4 | showCancel: true 5 | reportTitle: AppCrawler雪球内部版 6 | tagLimitMax: 1 7 | currentDriver: "android" 8 | maxTime: 10800 9 | 10 | sortByAttribute: depth 11 | capability: 12 | newCommandTimeout: 120 13 | launchTimeout: 120000 14 | platformVersion: "" 15 | platformName: "" 16 | autoWebview: "false" 17 | autoLaunch: "true" 18 | noReset: "false" 19 | androidCapability: 20 | deviceName: "demo" 21 | appPackage: "com.xueqiu.android" 22 | appActivity: ".view.WelcomeActivityAlias" 23 | app: "" 24 | appium: "http://127.0.0.1:4723/wd/hub" 25 | fullReset: false 26 | noReset: true 27 | automationName: uiautomator2 28 | iosCapability: 29 | deviceName: "iPhone 7 Plus" 30 | #bundleId: "com.xueqiu" 31 | bundleId: com.example.apple-samplecode.UICatalog 32 | screenshotWaitTimeout: "10" 33 | platformVersion: "10.2" 34 | autoAcceptAlerts: "true" 35 | automationName: xcuitest 36 | app: /Users/seveniruby/projects/ios-uicatalog/build/Debug-iphonesimulator/UICatalog.app 37 | #app: "/Users/seveniruby/Library/Developer/Xcode/DerivedData/Snowball-ckpjegabufjxgxfeqyxgkmjuwmct/Build/Products/Debug-iphonesimulator/Snowball.app" 38 | appium: "http://127.0.0.1:4723/wd/hub" 39 | maxDepth: 8 40 | selectedList: 41 | #android非空标签 42 | - //*[@clickable="true"]//android.widget.TextView[string-length(@text)>0 and string-length(@text)<20] 43 | - //android.widget.EditText 44 | #ios 45 | - //*[contains(name(), 'Text') and string-length(@value)>0 and string-length(@value)<20 ] 46 | #通用的button和image 47 | - //*[contains(name(), 'Button')] 48 | - //*[contains(name(), 'Image')] 49 | triggerActions: 50 | - action: 123 51 | xpath: //*[contains(name(), "EditText")] 52 | # times: 10 53 | pri: 0 54 | - xpath: "不保存" 55 | tagLimit: 56 | - xpath: //*[../*[@selected='true']] 57 | count: 12 58 | - xpath: //*[../../*/*[@selected='true']] 59 | count: 12 60 | urlBlackList: 61 | - ".*UserProfileActivity.*" 62 | - ".*LoginOption.*" 63 | - ".*球友.*" 64 | - ".*png.*" 65 | - ".*Talk.*" 66 | - ".*Chat.*" 67 | - ".*Safari.*" 68 | - "WriteStatus.*" 69 | - "Browser.*" 70 | - "MyselfUser" 71 | - ".*MyselfUser.*" 72 | - ".*股市直播.*" 73 | #urlWhiteList: 74 | #- ".*Main.*" 75 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/it/xueqiu_sikuli.yml: -------------------------------------------------------------------------------- 1 | --- 2 | pluginList: 3 | - "com.ceshiren.appcrawler.plugin.FlowDiff" 4 | #- "com.ceshiren.appcrawler.plugin.ProxyPlugin" 5 | logLevel: "TRACE" 6 | saveScreen: true 7 | showCancel: true 8 | reportTitle: AppCrawler雪球内部版 9 | screenshotTimeout: 20 10 | tagLimitMax: 2 11 | currentDriver: "android" 12 | maxTime: 10800 13 | resultDir: "" 14 | sikuliImages: "/Users/seveniruby/temp/ocr/images" 15 | capability: 16 | newCommandTimeout: 120 17 | launchTimeout: 120000 18 | platformVersion: "" 19 | platformName: "" 20 | autoWebview: "false" 21 | autoLaunch: "true" 22 | noReset: "true" 23 | fullReset: "false" 24 | dontStopAppOnReset: "true" 25 | androidCapability: 26 | deviceName: "demo" 27 | appPackage: "com.xueqiu.android" 28 | appActivity: ".view.WelcomeActivityAlias" 29 | app: "" 30 | appium: "http://127.0.0.1:4723/wd/hub" 31 | automationName: sikuli 32 | iosCapability: 33 | deviceName: "iPhone 6 Plus" 34 | bundleId: "com.xueqiu" 35 | screenshotWaitTimeout: "10" 36 | platformVersion: "9.3" 37 | autoAcceptAlerts: "true" 38 | app: "/Users/seveniruby/Library/Developer/Xcode/DerivedData/Snowball-ckpjegabufjxgxfeqyxgkmjuwmct/Build/Products/Debug-iphonesimulator/Snowball.app" 39 | appium: "http://127.0.0.1:4730/wd/hub" 40 | defineUrl: 41 | - "//*[@selected='true']/@text" 42 | - "//*[@selected='true']/@text" 43 | - "//*[contains(name(), 'NavigationBar')]/@label" 44 | #baseUrl: 45 | #- ".*MainActivity" 46 | #- ".*SNBHomeView.*" 47 | maxDepth: 2 48 | headFirst: true 49 | selectedList: 50 | - xpath: //* 51 | triggerActions: 52 | - { xpath: "//*[contains(@name, '行情灰')]", times: 1 } 53 | - { xpath: 沪深港通, times: 1} -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/model/BDDTestCaseTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | import com.ceshiren.appcrawler.utils.Log.log 4 | import com.ceshiren.appcrawler.utils.TData 5 | import org.junit.jupiter.api.Test 6 | 7 | import scala.collection.mutable 8 | 9 | 10 | class BDDTestCaseTest { 11 | 12 | @Test 13 | def runStep() { 14 | val step1 = mutable.HashMap[String, Any]() 15 | step1.put("find", null) 16 | 17 | val step2 = mutable.HashMap[String, Any]() 18 | step2.put("find", "11") 19 | val step3 = mutable.HashMap[String, Any]() 20 | step3.put("find", step2) 21 | 22 | 23 | val testcase1 = BDDTestCase(when = List(step1.toMap, step2.toMap, step3.toMap)) 24 | val str = TData.toYaml(testcase1) 25 | log.info(str) 26 | 27 | val testcase2 = TData.fromYaml[BDDTestCase](str) 28 | log.info(testcase2) 29 | 30 | } 31 | 32 | @Test 33 | def mockDriver(){ 34 | 35 | val yamlStr = 36 | s""" 37 | |when: 38 | |- driver: mock 39 | |- chrome: 40 | |- get: https://ceshiren.com 41 | |- click: { id: search-button } 42 | |- find: { id: search-term } 43 | |- sendKeys: appium demo 44 | |- find: { id: search-term } 45 | |- shell: 46 | | format: 47 | | - "echo {} {}" 48 | | - 49 | | - attribute: text 50 | | - end 51 | | 52 | |""".stripMargin 53 | 54 | val yamlObject = TData.fromYaml[BDDTestCase](yamlStr) 55 | log.info(yamlObject) 56 | yamlObject.run() 57 | 58 | } 59 | 60 | @Test 61 | def seleniumDriver(){ 62 | 63 | val yamlStr = 64 | s""" 65 | |when: 66 | |- driver: selenium 67 | |- chrome: 68 | |- get: https://ceshiren.com 69 | |- click: { id: search-button } 70 | |- find: { id: search-term } 71 | |- sendKeys: appium demo 72 | |- find: { id: search-term } 73 | |- shell: 74 | | concat: 75 | | - echo 76 | | - attribute: text 77 | | - end 78 | | 79 | |""".stripMargin 80 | 81 | val yamlObject = TData.fromYaml[BDDTestCase](yamlStr) 82 | log.info(yamlObject) 83 | yamlObject.run() 84 | 85 | } 86 | 87 | 88 | @Test 89 | def dynamic(): Unit = { 90 | log.info(this) 91 | log.info(this.getClass) 92 | this.getClass.getDeclaredMethods.foreach(method => { 93 | method.getParameterTypes.foreach(p => { 94 | log.info(p) 95 | }) 96 | }) 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/model/ElementInfoTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.model 2 | 3 | import com.ceshiren.appcrawler.core.Status 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | import com.ceshiren.appcrawler.utils.TData 6 | import org.junit.jupiter.api.Test 7 | 8 | 9 | class ElementInfoTest { 10 | 11 | @Test 12 | def yaml(): Unit ={ 13 | val info=new ElementInfo() 14 | info.action=Status.CLICKED 15 | val strYaml=TData.toYaml(info) 16 | log.info(strYaml) 17 | val strJson=TData.toJson(info) 18 | log.info(strJson) 19 | val info2=TData.fromYaml[ElementInfo](strYaml) 20 | log.info(info2) 21 | } 22 | 23 | @Test 24 | def json(): Unit ={ 25 | val info=new ElementInfo() 26 | info.action=Status.CLICKED 27 | val strJson=TData.toJson(info) 28 | log.info(strJson) 29 | val info2=TData.fromJson[ElementInfo](strJson) 30 | log.info(info2) 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/AppCrawlerTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.AppCrawler 4 | 5 | import java.io.File 6 | import org.scalatest.FunSuite 7 | 8 | /** 9 | * Created by seveniruby on 2017/5/25. 10 | */ 11 | class AppCrawlerTest extends FunSuite{ 12 | test("parse test"){ 13 | 14 | var uri="http://xxxx.com/aa.apk" 15 | var res=AppCrawler.parsePath(uri) 16 | println(res) 17 | 18 | uri="http:\\xxxx.com/aa.apk" 19 | res=AppCrawler.parsePath(uri) 20 | println(res) 21 | 22 | 23 | uri="/Users/seveniruby/Downloads/base.apk" 24 | res=AppCrawler.parsePath(uri) 25 | println(res) 26 | 27 | 28 | uri="./project/build.properties" 29 | res=AppCrawler.parsePath(uri) 30 | println(res) 31 | 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/Demo.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | case class Demo( 4 | val i: Int = 1, 5 | val str: String = "" 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/DemoCrawlerSuite.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import org.scalatest.FunSuite 4 | 5 | /** 6 | * Created by seveniruby on 16/8/12. 7 | */ 8 | class DemoCrawlerSuite extends FunSuite{ 9 | var name="自动遍历" 10 | override def suiteName=name 11 | 1 to 10 foreach(i=>{ 12 | test(s"xxx ${i}"){ 13 | markup("") 14 | assert(1==i) 15 | } 16 | }) 17 | 18 | 1 to 10 foreach(i=>{ 19 | test(s"xxx ignore ${i}"){ 20 | markup("") 21 | cancel("未遍历") 22 | } 23 | }) 24 | 25 | 1 to 10 foreach(i=>{ 26 | test(s"xxx ignore ${i}"){ 27 | markup("") 28 | } 29 | }) 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/PageObjectDemo.java.ssp: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by seveniruby on 2017/1/10. 3 | */ 4 | 5 | <%@ val elements: scala.collection.mutable.ListBuffer[Map[String, Any]] %> 6 | <%@ val file:String %> 7 | 8 | import org.openqa.selenium.remote.RemoteWebElement; 9 | import io.appium.java_client.pagefactory.*; 10 | import org.openqa.selenium.support.FindBy; 11 | import org.openqa.selenium.support.FindAll; 12 | 13 | import io.appium.java_client.android.AndroidElement; 14 | import org.openqa.selenium.remote.RemoteWebElement; 15 | import io.appium.java_client.pagefactory.*; 16 | 17 | 18 | import java.util.List; 19 | 20 | 21 | public class PageObjectDemo_${file} { 22 | <% elements.foreach(element => {%> 23 | @FindBy(xpath = "${unescape(element("xpath").toString.replace("\"", "\\\""))}") 24 | private RemoteWebElement ${List(element("name"), element("content-desc"), element("text")) 25 | .filter(_.toString.nonEmpty) 26 | .mkString("_").replaceAll("[^a-zA-Z0-9_\\u4e00-\\u9fa5]", "")}; 27 | 28 | <% }) %> 29 | } 30 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/PageObjectDemoID.java.ssp: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by seveniruby on 2017/1/10. 3 | */ 4 | 5 | <%@ val elements: scala.collection.mutable.ListBuffer[Map[String, Any]] %> 6 | <%@ val file:String %> 7 | 8 | import org.openqa.selenium.remote.RemoteWebElement; 9 | import io.appium.java_client.pagefactory.*; 10 | import org.openqa.selenium.support.FindBy; 11 | import org.openqa.selenium.support.FindAll; 12 | 13 | import io.appium.java_client.android.AndroidElement; 14 | import org.openqa.selenium.remote.RemoteWebElement; 15 | import io.appium.java_client.pagefactory.*; 16 | 17 | 18 | import java.util.List; 19 | 20 | 21 | public class PageObjectDemo_${file} { 22 | <% elements.filter(e=>e.getOrElse("visible", "true")=="true") 23 | .filter(e=>e.getOrElse("name", "").toString.nonEmpty) 24 | .filter(e=>e.getOrElse("xpath", "").toString.contains("StatusBar")==false) 25 | .foreach(element => { 26 | %> 27 | @FindBy(id = "${unescape(element("name").toString.replace("\"", "\\\""))}") 28 | private RemoteWebElement ${List(element("name"), element("label"), element("value")).distinct 29 | .filter(_.toString.nonEmpty) 30 | .mkString("_").replaceAll("[^a-zA-Z0-9_\\u4e00-\\u9fa5]", "")}; 31 | 32 | <% }) %> 33 | 34 | <% elements.filter(e=>e.getOrElse("visible", "true")=="true") 35 | .filter(e=>{e.getOrElse("name", "").toString.isEmpty}) 36 | .filter(e=>e.getOrElse("xpath", "").toString.contains("StatusBar")==false) 37 | .foreach(element => { 38 | %> 39 | @FindBy(xpath = "${unescape(element("xpath").toString.replace("\"", "\\\""))}") 40 | private RemoteWebElement ${List(element("name"), element("label"), element("value"), element("xpath")) 41 | .filter(_.toString.nonEmpty).map(_.toString.replace("XCUIElementType", "")) 42 | .mkString("_").replaceAll("[^a-zA-Z0-9_\\u4e00-\\u9fa5]", "")}; 43 | 44 | <% }) %> 45 | } 46 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/RequestsTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.utils.Log 4 | import com.ceshiren.appcrawler.utils.LogicUtils.retryToSuccess 5 | import org.junit.jupiter.api.Test 6 | 7 | import scala.util.{Failure, Success, Try} 8 | 9 | class RequestsTest { 10 | @Test 11 | def get(): Unit ={ 12 | val session=requests.Session() 13 | // while(Try(session.get("http://127.0.0.1:7778/ping").text()).isFailure){ 14 | // Thread.sleep(500) 15 | // Log.log.info("wait") 16 | // } 17 | // Log.log.info("success") 18 | 19 | Log.log.info(retryToSuccess(timeoutMS = 6000)(session.get("http://127.0.0.1:7778/ping").text()=="pong")) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/SuiteToClassTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.plugin.scalatest.SuiteToClass 4 | import org.scalatest.FunSuite 5 | 6 | class SuiteToClassTest extends FunSuite { 7 | test("class name"){ 8 | val name ="com.tencent.mobileqq-加好友-☞ Mr.never \"day \"心(571529295)" 9 | SuiteToClass.genTestCaseClass(name, "com.ceshiren.appcrawler.plugin.report.DiffSuite", Map("suite"->name, "name"->name), "/tmp/class") 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestConf.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.core.CrawlerConf 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | import org.scalatest.{FunSuite, Matchers} 6 | 7 | /** 8 | * Created by seveniruby on 16/8/11. 9 | */ 10 | class TestConf extends FunSuite with Matchers{ 11 | 12 | 13 | 14 | test("save config"){ 15 | val conf=new CrawlerConf 16 | conf.save("conf.json") 17 | } 18 | 19 | /* 20 | test("load config"){ 21 | var conf=new com.ceshiren.appcrawler.core.CrawlerConf 22 | conf.baseUrl="xxx" 23 | println(conf.baseUrl) 24 | conf=conf.loadByJson4s("conf.json").get 25 | println(conf.baseUrl) 26 | } 27 | */ 28 | 29 | test("load config by jackson"){ 30 | var conf=new CrawlerConf 31 | conf.baseUrl=List("xxx") 32 | println(conf.baseUrl) 33 | conf.save("conf.json") 34 | conf=conf.load("conf.json") 35 | println(conf.baseUrl) 36 | assert(conf.baseUrl==List("xxx")) 37 | } 38 | 39 | 40 | test("one line yaml"){ 41 | val conf=new CrawlerConf().loadYaml("blackList: [ {xpath: action_night}, {xpath: action_setting} ]") 42 | println(conf.selectedList) 43 | println(conf.blackList) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestDataRecord.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.model.DataRecord 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | import org.scalatest.FunSuite 6 | 7 | /** 8 | * Created by seveniruby on 16/8/25. 9 | */ 10 | class TestDataRecord extends FunSuite { 11 | test("diff int"){ 12 | val stringDiff=new DataRecord() 13 | stringDiff.append(22) 14 | Thread.sleep(1000) 15 | stringDiff.append(33333) 16 | log.info(stringDiff.isDiff()) 17 | log.info(stringDiff.intervalMS()) 18 | } 19 | 20 | test("test interval"){ 21 | val diff=new DataRecord 22 | assert(0==diff.intervalMS(), diff) 23 | diff.append("0") 24 | Thread.sleep(500) 25 | diff.append("500") 26 | assert(diff.intervalMS()>=500, diff) 27 | Thread.sleep(2000) 28 | diff.append("2000") 29 | assert(diff.intervalMS()>=2000, diff) 30 | assert(diff.intervalMS()<=2200, diff) 31 | 32 | 33 | 34 | } 35 | 36 | test("diff first"){ 37 | val stringDiff=new DataRecord 38 | assert(false==stringDiff.isDiff, stringDiff) 39 | stringDiff.append("xxxx") 40 | assert(false==stringDiff.isDiff, stringDiff) 41 | stringDiff.append("3333") 42 | assert(true==stringDiff.isDiff, stringDiff) 43 | stringDiff.append("3333") 44 | assert(false==stringDiff.isDiff, stringDiff) 45 | 46 | 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestDynamicEval.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.plugin.{DemoPlugin, Plugin} 4 | import com.ceshiren.appcrawler.utils.Log.log 5 | import com.ceshiren.appcrawler.utils.DynamicEval 6 | import org.scalatest.FunSuite 7 | 8 | import java.io.File 9 | import scala.reflect.internal.util.ScalaClassLoader.URLClassLoader 10 | import scala.tools.nsc.{Global, Settings} 11 | 12 | /** 13 | * Created by seveniruby on 16/8/10. 14 | */ 15 | class TestDynamicEval extends FunSuite { 16 | 17 | val fileName="/Users/seveniruby/projects/LBSRefresh/iOS_20160813203343/AppCrawler_8.scala" 18 | test("MiniAppium dsl"){ 19 | DynamicEval.dsl("hello(\"seveniruby\", 30000)") 20 | DynamicEval.dsl("hello(\"ruby\", 30000)") 21 | DynamicEval.dsl(" hello(\"seveniruby\", 30000)") 22 | DynamicEval.dsl("hello(\"seveniruby\", 30000 ) ") 23 | DynamicEval.dsl("sleep(3)") 24 | DynamicEval.dsl("hello(\"xxxxx\")") 25 | DynamicEval.dsl("hello(\"xxxxx\"); hello(\"double\")") 26 | DynamicEval.dsl("println(com.ceshiren.appcrawler.AppCrawler.crawler.driver)") 27 | 28 | } 29 | 30 | test("benchmark"){ 31 | DynamicEval.dsl("println('hello')") 32 | DynamicEval.dsl("Thread.sleep(1000)") 33 | } 34 | 35 | test("MiniAppium dsl re eval"){ 36 | DynamicEval.dsl("val a=new java.util.Date") 37 | DynamicEval.dsl("val b=a") 38 | DynamicEval.dsl("val a=new java.util.Date") 39 | DynamicEval.dsl("println(a)") 40 | DynamicEval.dsl("println(b)") 41 | } 42 | 43 | test("shell"){ 44 | DynamicEval.dsl("\"12345\"") 45 | DynamicEval.dsl(" \"sh /tmp/1.sh\"!") 46 | DynamicEval.shell("echo xxx") 47 | DynamicEval.shell("which adb") 48 | DynamicEval.shell("adb") 49 | DynamicEval.shell("adb devices") 50 | DynamicEval.shell("bash -c 'adb devices; echo xxx;'") 51 | DynamicEval.shell("bash -c 'adb devices > /tmp/tmp.log &'") 52 | DynamicEval.shell("bash -c 'jobs -l '") 53 | } 54 | 55 | 56 | test("compile by scala"){ 57 | DynamicEval.init(new File(fileName).getParent) 58 | DynamicEval.compile(List(fileName)) 59 | 60 | } 61 | 62 | test("native compile"){ 63 | 64 | 65 | val outputDir=new File(fileName).getParent 66 | 67 | val settings = new Settings() 68 | settings.deprecation.value = true // enable detailed deprecation warnings 69 | settings.unchecked.value = true // enable detailed unchecked warnings 70 | settings.outputDirs.setSingleOutput(outputDir) 71 | settings.usejavacp.value = true 72 | 73 | val global = new Global(settings) 74 | val run = new global.Run 75 | run.compile(List(fileName)) 76 | 77 | } 78 | 79 | 80 | test("compile plugin"){ 81 | DynamicEval.init() 82 | DynamicEval.compile(List("src/universal/plugins/DynamicPlugin.scala")) 83 | val p=Class.forName("com.ceshiren.appcrawler.plugin.DynamicPlugin").newInstance() 84 | log.info(p) 85 | 86 | 87 | } 88 | 89 | test("test classloader"){ 90 | val classPath="target/tmp/" 91 | DynamicEval.init(classPath) 92 | DynamicEval.compile(List("/Users/seveniruby/projects/LBSRefresh/src/universal/plugins/")) 93 | val urls=Seq(new java.io.File(classPath).toURI.toURL) 94 | val loader=new URLClassLoader(urls, ClassLoader.getSystemClassLoader) 95 | val x=loader.loadClass("AppCrawler_5").newInstance().asInstanceOf[FunSuite] 96 | log.info(x.testNames) 97 | log.info(getClass.getCanonicalName) 98 | 99 | log.info(getClass.getProtectionDomain.getCodeSource.getLocation.getPath) 100 | 101 | } 102 | 103 | test("load plugins"){ 104 | 105 | val a=new DemoPlugin() 106 | log.info(a.asInstanceOf[Plugin]) 107 | //getClass.getClassLoader.asInstanceOf[URLClassLoader].loadClass("DynamicPlugin") 108 | val plugins=DynamicEval.loadPlugins("/Users/seveniruby/projects/LBSRefresh/src/universal/plugins/") 109 | plugins.foreach(log.info) 110 | 111 | } 112 | 113 | test("crawl keyword"){ 114 | DynamicEval.dsl("def crawl(depth:Int)=com.ceshiren.appcrawler.AppCrawler.crawler.crawl(depth)") 115 | DynamicEval.dsl("crawl(1)") 116 | } 117 | 118 | 119 | } 120 | 121 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestElementStore.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.core.Status 4 | import com.ceshiren.appcrawler.model.{ElementInfo, URIElement, URIElementStore} 5 | import com.ceshiren.appcrawler.utils.Log.log 6 | import com.ceshiren.appcrawler.utils.TData 7 | import org.scalatest.{FunSuite, Matchers} 8 | /** 9 | * Created by seveniruby on 16/9/17. 10 | */ 11 | class TestElementStore extends FunSuite with Matchers { 12 | test("save to yaml"){ 13 | val store=new URIElementStore 14 | 15 | val element_1=URIElement("a", "b", "c", "d", "e") 16 | val info_1=new ElementInfo() 17 | info_1.element=element_1 18 | info_1.action=Status.SKIPPED 19 | 20 | 21 | val element_2=URIElement("aa", "bb", "cc", "dd", "ee") 22 | val info_2=new ElementInfo() 23 | info_2.element=element_2 24 | info_2.action=Status.CLICKED 25 | 26 | store.elementStoreMap ++= scala.collection.mutable.Map( 27 | element_1.toString->info_1, 28 | element_2.toString->info_2 29 | ) 30 | 31 | store.clickedElementsList.append(element_2) 32 | 33 | log.info(store) 34 | val str=TData.toYaml(store) 35 | log.info(str) 36 | val store2=TData.fromYaml[URIElementStore](str) 37 | log.info(store2) 38 | val str2=TData.toYaml(store2) 39 | 40 | 41 | str should be equals str2 42 | store should be equals store2 43 | 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestGA.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.brsanthu.googleanalytics.GoogleAnalytics 4 | import com.ceshiren.appcrawler.utils.GA 5 | import org.scalatest.FunSuite 6 | 7 | /** 8 | * Created by seveniruby on 16/2/27. 9 | */ 10 | class TestGA extends FunSuite{ 11 | test("google analyse"){ 12 | GA.log("unittest") 13 | 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestGetClassFile.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.plugin.FlowDiff 4 | import com.ceshiren.appcrawler.plugin.report.{DiffSuite, Report} 5 | import org.apache.commons.io.FileUtils 6 | import org.scalatest.Checkpoints.Checkpoint 7 | import org.scalatest.{FunSuite, Matchers} 8 | 9 | /** 10 | * Created by seveniruby on 16/9/27. 11 | */ 12 | class TestGetClassFile extends FunSuite with Matchers{ 13 | 14 | 15 | 16 | test("test checkpoints"){ 17 | markup { 18 | """ 19 | |dddddddd 20 | """.stripMargin 21 | } 22 | markup("xxxx") 23 | val cp = new Checkpoint() 24 | val (x, y) = (1, 2) 25 | cp { x should be < 0 } 26 | cp { y should be > 9 } 27 | cp.reportAll() 28 | } 29 | 30 | test("test markup"){ 31 | markup { 32 | """ 33 | |dddddddd 34 | """.stripMargin 35 | } 36 | markup("xxxx") 37 | 38 | } 39 | 40 | test("get class file"){ 41 | val location=classOf[DiffSuite].getProtectionDomain.getCodeSource.getLocation 42 | println(location) 43 | val f=getClass.getResource("/com/xueqiu/qa/appcrawler/ut/TestDiffReport.class").getFile 44 | println(f) 45 | FileUtils.copyFile(new java.io.File(f), new java.io.File("/tmp/1.class")) 46 | 47 | 48 | 49 | println(getClass.getClassLoader.getResources("com/xueqiu/qa/appcrawler/ut/TestDiffReport.class")) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestJUnit5.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.AppCrawler 4 | import com.ceshiren.appcrawler.utils.Log 5 | import org.junit.jupiter.api.{Test, TestFactory} 6 | import org.junit.jupiter.api.Assertions._ 7 | import org.junit.jupiter.api.DynamicTest.dynamicTest 8 | import org.junit.jupiter.api.DynamicTest 9 | 10 | import java.util 11 | import io.qameta.allure.Description 12 | 13 | import scala.io.Source 14 | import scala.jdk.CollectionConverters._ 15 | 16 | class TestJUnit5 { 17 | @Test 18 | @Description("Some detailed test description") 19 | def x(): Unit = { 20 | assertTrue(1 == 1) 21 | } 22 | 23 | @TestFactory 24 | def dynamicTestsFromCollection: util.Collection[DynamicTest] = { 25 | """|1 26 | |2 27 | |3 28 | |4""".stripMargin.split("\n").map(line => { 29 | Log.log.info(line) 30 | dynamicTest(line, () => { 31 | assertTrue(true) 32 | }) 33 | }).toList.asJava 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestJUnit5Launcher.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import org.junit.platform.engine.discovery.DiscoverySelectors._ 4 | import org.junit.platform.engine.discovery.ClassNameFilter._ 5 | import org.scalatest.FunSuite 6 | import org.junit.platform.launcher.Launcher 7 | import org.junit.platform.launcher.LauncherDiscoveryRequest 8 | import org.junit.platform.launcher.TestExecutionListener 9 | import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder 10 | import org.junit.platform.launcher.core.LauncherFactory 11 | import org.junit.platform.launcher.listeners.SummaryGeneratingListener 12 | 13 | 14 | class TestJUnit5Launcher extends FunSuite{ 15 | test("xxxx"){ 16 | val request = LauncherDiscoveryRequestBuilder.request.selectors( 17 | selectPackage("com.ceshiren.appcrawler.ut"), 18 | selectClass(classOf[TestJUnit5])).build() 19 | //.filters(includeClassNamePatterns("Test.*")).build 20 | 21 | val launcher = LauncherFactory.create 22 | 23 | // Register a listener of your choice 24 | val listener = new SummaryGeneratingListener 25 | launcher.registerTestExecutionListeners(listener) 26 | 27 | val roots=launcher.discover(request).getRoots() 28 | val iter=roots.iterator() 29 | while(iter.hasNext){ 30 | val root=iter.next() 31 | println(root) 32 | val iter2=launcher.discover(request).getDescendants(root).iterator() 33 | while(iter2.hasNext){ 34 | println(iter2.next()) 35 | } 36 | } 37 | println(launcher.execute(request)) 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestJava.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.Demo 4 | import org.scalatest.FunSuite 5 | 6 | class TestJava extends FunSuite{ 7 | test("test java code"){ 8 | val d=new Demo(); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestReportPlugin.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.plugin.ReportPlugin 4 | import com.ceshiren.appcrawler._ 5 | import com.ceshiren.appcrawler.core.{Crawler, Status} 6 | import com.ceshiren.appcrawler.model.{ElementInfo, URIElement, URIElementStore} 7 | import com.ceshiren.appcrawler.plugin.ReportPlugin 8 | import com.ceshiren.appcrawler.utils.Log.log 9 | import org.scalatest.FunSuite 10 | import org.scalatest.tools.Runner 11 | 12 | /** 13 | * Created by seveniruby on 16/8/12. 14 | */ 15 | class TestReportPlugin extends FunSuite { 16 | test("gen suite"){ 17 | val report=new ReportPlugin() 18 | val crawler=new Crawler() 19 | report.setCrawer(crawler) 20 | 21 | val element_1=URIElement("a", "b", "c", "d", "e") 22 | val info_1=new ElementInfo() 23 | info_1.element=element_1 24 | info_1.action=Status.SKIPPED 25 | 26 | 27 | val element_2=URIElement("aa", "bb", "cc", "dd", "ee") 28 | val info_2=new ElementInfo() 29 | info_2.element=element_2 30 | info_2.action=Status.CLICKED 31 | 32 | val elementsStore=scala.collection.mutable.Map( 33 | element_1.toString->info_1, 34 | element_2.toString->info_2 35 | ) 36 | val store=new URIElementStore 37 | store.elementStoreMap ++= elementsStore 38 | // 由于更换了store对象,暂时关闭该测试 39 | // report.saveTestCase(store, "/tmp/") 40 | 41 | } 42 | 43 | test("run"){ 44 | 45 | val report=new ReportPlugin() 46 | val crawler=new Crawler() 47 | report.setCrawer(crawler) 48 | 49 | //Runner.run(Array("-R", "target", "-w", "com.ceshiren.appcrawler.report", "-o", "-u", "target/test-reports", "-h", "target/test-reports")) 50 | Runner.run(Array( 51 | "-R", "/Users/seveniruby/projects/LBSRefresh/target", 52 | "-w", "com.ceshiren.appcrawler", 53 | "-o", "-u", "target/test-reports", "-h", "target/test-reports")) 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestSpec.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import org.scalatest.FunSpec 4 | 5 | /** 6 | * Created by seveniruby on 16/8/12. 7 | */ 8 | class TestSpec extends FunSpec{ 9 | describe("A Set") { 10 | describe("when empty") { 11 | it("should have size 0") { 12 | assert(Set.empty.size == 0) 13 | } 14 | 15 | it("should produce NoSuchElementException when head is invoked") { 16 | assert(1==2) 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestStringTemplate.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.utils.Log.log 4 | import org.scalatest.FunSuite 5 | 6 | /** 7 | * Created by seveniruby on 16/8/12. 8 | */ 9 | class TestStringTemplate extends FunSuite { 10 | 11 | def genNumber(): String ={ 12 | 1 to 5 map (_.toString) mkString ("\n"+" "*4) 13 | } 14 | test("string template"){ 15 | val s= 16 | s""" 17 | |class A extends B { 18 | | test("ddddd"){ 19 | | ${genNumber()} 20 | | } 21 | |} 22 | """.stripMargin 23 | log.info(s) 24 | } 25 | 26 | test("string template from file"){ 27 | //todo: 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestSuites.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import org.scalatest.{FunSuite, Sequential} 4 | 5 | /** 6 | * Created by seveniruby on 2017/4/17. 7 | */ 8 | class TestSuites extends Sequential( 9 | new Demo1Suite 10 | ) 11 | 12 | class Demo1Suite extends FunSuite { 13 | test("1"){ 14 | 15 | } 16 | test("2"){ 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestThread.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.driver.AppiumClient 4 | import com.ceshiren.appcrawler.utils.LogicUtils.asyncTask 5 | import org.scalatest.FunSuite 6 | 7 | import scala.sys.process.Process 8 | 9 | 10 | /** 11 | * Created by seveniruby on 16/3/30. 12 | */ 13 | class TestThread extends FunSuite{ 14 | test("test start new thread and kill"){ 15 | var a=1 16 | var hello = new Thread(new Runnable { 17 | def run() { 18 | println("hello world") 19 | for(i<- 1 to 5){ 20 | Thread.sleep(1000) 21 | a+=1 22 | println(a) 23 | } 24 | println("thread end") 25 | } 26 | }) 27 | 28 | hello.start() 29 | Thread.sleep(7000) 30 | println(s"a=$a") 31 | 32 | hello = new Thread(new Runnable { 33 | def run() { 34 | println("hello world") 35 | Thread.sleep(5000) 36 | println("thread end") 37 | } 38 | }) 39 | 40 | hello.start() 41 | Thread.sleep(3000) 42 | hello.stop() 43 | } 44 | 45 | 46 | test("test slf4j"){ 47 | 48 | import org.slf4j.LoggerFactory 49 | val log = LoggerFactory.getLogger(classOf[TestThread]) 50 | log.trace("trace") 51 | log.debug("debug") 52 | log.info("info") 53 | log.warn("warnning") 54 | log.error("error") 55 | } 56 | 57 | 58 | /* 59 | test("test console"){ 60 | import scala.tools.nsc.Settings 61 | import scala.tools.nsc.interpreter.ILoop 62 | 63 | val settings=new Settings() 64 | val loop = new ILoop 65 | settings.usejavacp.value=true 66 | loop.process(settings) 67 | } 68 | */ 69 | 70 | 71 | def callbyname(count:Int =3)(callback: =>Unit): Unit ={ 72 | 1 to count foreach(x=>callback) 73 | } 74 | 75 | 76 | def callbythread(count:Int =3)(callback: =>Unit): Unit ={ 77 | 1 to count foreach(x=>{ 78 | val thread = new Thread(new Runnable { 79 | override def run(): Unit = { 80 | callback 81 | } 82 | }) 83 | thread.start() 84 | thread.join(3000) 85 | thread.stop() 86 | }) 87 | } 88 | 89 | test("test by name callback"){ 90 | println("before") 91 | callbythread(3){ 92 | println("xx start") 93 | Thread.sleep(5000) 94 | println("xx stop") 95 | } 96 | println("after") 97 | 98 | } 99 | 100 | test("executor service default"){ 101 | 102 | val pre=System.currentTimeMillis() 103 | val r=asyncTask(5){ 104 | Thread.sleep(100000) 105 | "xxxx" 106 | } 107 | assert(r==None) 108 | val now=System.currentTimeMillis() 109 | println((now-pre)/1000) 110 | 111 | } 112 | 113 | 114 | test("executor service expect"){ 115 | 116 | val pre=System.currentTimeMillis() 117 | val r=asyncTask(5){ 118 | Thread.sleep(1000) 119 | "xxxx" 120 | } 121 | assert(r.left.get=="xxxx") 122 | val now=System.currentTimeMillis() 123 | println((now-pre)/1000) 124 | 125 | } 126 | 127 | test("executor service Int expect"){ 128 | 129 | val pre=System.currentTimeMillis() 130 | val r=asyncTask(5) { 131 | Thread.sleep(100000) 132 | 1 133 | } 134 | assert(r==None) 135 | val now=System.currentTimeMillis() 136 | println((now-pre)/1000) 137 | 138 | } 139 | 140 | test("executor service Int"){ 141 | 142 | val pre=System.currentTimeMillis() 143 | val r=asyncTask(5){ 144 | Thread.sleep(1000) 145 | 1 146 | } 147 | assert(r.left.get==1) 148 | val now=System.currentTimeMillis() 149 | println((now-pre)/1000) 150 | 151 | } 152 | 153 | test("-1 async"){ 154 | val x=asyncTask(-1){ 155 | println("start") 156 | Thread.sleep(6000) 157 | 3 158 | } 159 | 0 to 10 foreach{ i=> 160 | Thread.sleep(1000) 161 | println(x) 162 | } 163 | } 164 | 165 | test("appium start"){ 166 | val process=Process("appium -p 4445") 167 | val pb=process.run() 168 | val x=asyncTask(10){ 169 | pb.exitValue() 170 | } 171 | println(x) 172 | Thread.sleep(20000) 173 | pb.destroy() 174 | } 175 | 176 | 177 | 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestTreeNode.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.model.TreeNode 4 | import org.scalatest.FunSuite 5 | 6 | import scala.collection.mutable.ListBuffer 7 | 8 | /** 9 | * Created by seveniruby on 16/2/10. 10 | */ 11 | class TestTreeNode extends FunSuite{ 12 | test("generate tree"){ 13 | val root=TreeNode("root") 14 | root.appendNode(root, TreeNode("1")).appendNode(root, TreeNode("11")).appendNode(root, TreeNode("111")) 15 | root.appendNode(root, TreeNode("2")).appendNode(root, TreeNode("21")) 16 | root.appendNode(root, TreeNode("3")) 17 | root.toXml(root) 18 | 19 | } 20 | 21 | test("generate tree by list"){ 22 | val list=ListBuffer(1, 2, 3, 4, 1, 5, 6, 5, 7) 23 | TreeNode(0).generateFreeMind(list, "1.mm") 24 | } 25 | 26 | 27 | test("generate tree by list string"){ 28 | val list=ListBuffer("1", "2", "3", "4", "1", "5", "66\"66", "5", "7") 29 | TreeNode("demo").generateFreeMind(list, "2.mm") 30 | } 31 | 32 | test("append node single"){ 33 | val root=TreeNode(0) 34 | var current1=root.appendNode(root, TreeNode(1)) 35 | println(current1) 36 | var current2=current1.appendNode(root, TreeNode(2)) 37 | println(root) 38 | println(current1) 39 | println(current2) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/TestURIElement.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.ut 2 | 3 | import com.ceshiren.appcrawler.model.URIElement 4 | import org.scalatest.FunSuite 5 | 6 | /** 7 | * Created by seveniruby on 16/9/29. 8 | */ 9 | class TestURIElement extends FunSuite { 10 | test("windows file name"){ 11 | val element=URIElement("", "", "", "", "//xxfxx[@index=\"11\" and @text=\"fff>>dddff\"]") 12 | println(element.toString()) 13 | } 14 | 15 | test("tag path"){ 16 | 17 | val element=URIElement("", "", "", "", "//xxfxx[@index=\"11\" and @index=\"2\" and @text=\"fff>>dddff\"]") 18 | println(element.getAncestor()) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/ut/scalate.ssp: -------------------------------------------------------------------------------- 1 |
    2 | #for (i <- 1 to 5) 3 |
  • ${i}
  • 4 |
  • ${unescape("<'\"")}
  • 5 | #end 6 | 7 |
-------------------------------------------------------------------------------- /src/test/scala/com/ceshiren/appcrawler/utils/LogicUtilsTest.scala: -------------------------------------------------------------------------------- 1 | package com.ceshiren.appcrawler.utils 2 | 3 | import org.scalatest.FunSuite 4 | 5 | import scala.collection.mutable.ListBuffer 6 | 7 | class LogicUtilsTest extends FunSuite { 8 | 9 | test("testAsyncTask") { 10 | val s = ListBuffer[Integer](1, 2, 3) 11 | LogicUtils.asyncTask(timeout = 5) { 12 | do{ 13 | Thread.sleep(1000) 14 | Log.log.info("wait") 15 | s.append(1) 16 | }while(s.size<10) 17 | } 18 | 19 | } 20 | 21 | } 22 | --------------------------------------------------------------------------------