├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── markdown-navigator.xml ├── markdown-navigator │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── adb ├── AdbWinApi.dll ├── AdbWinUsbApi.dll └── adb.exe ├── app ├── .gitignore ├── apk │ └── app-debug.apk ├── build.gradle └── src │ └── main │ └── java │ ├── base │ └── TestFastAuto.java │ └── testqq │ ├── TestContacts.java │ └── TestMessage.java ├── build.gradle ├── fastautotest ├── build.gradle ├── libs │ └── AXMLPrinter2.jar └── src │ └── main │ ├── java │ ├── org │ │ └── uncommons │ │ │ └── reportng │ │ │ ├── AbstractReporter.java │ │ │ ├── HTMLReporter.java │ │ │ ├── JUnitXMLReporter.java │ │ │ ├── ReportMetadata.java │ │ │ ├── ReportNGException.java │ │ │ ├── ReportNGUtils.java │ │ │ ├── TestClassComparator.java │ │ │ ├── TestMethodComparator.java │ │ │ └── TestResultComparator.java │ └── yph │ │ ├── annotation │ │ └── FastAuto.java │ │ ├── base │ │ ├── AppiumServer.java │ │ ├── BaseTest.java │ │ └── FastAuto.java │ │ ├── bean │ │ ├── Configure.java │ │ └── TestBean.java │ │ ├── constant │ │ └── Constant.java │ │ ├── filter │ │ ├── AdbFilter.java │ │ ├── AnrFilter.java │ │ ├── CpuFilter.java │ │ ├── CrashFilter.java │ │ └── Filter.java │ │ ├── helper │ │ ├── RestartTestHelper.java │ │ └── XmlSuiteBuilder.java │ │ ├── listener │ │ ├── AnnotationListener.java │ │ ├── TestResultListener.java │ │ └── TestRetryListener.java │ │ ├── performance │ │ ├── Device.java │ │ └── PerforMonitor.java │ │ └── utils │ │ ├── ApkUtil.java │ │ ├── CmdUtil.java │ │ ├── CpuSnapshot.java │ │ ├── Log.java │ │ ├── RuntimeUtil.java │ │ ├── SleepUtil.java │ │ ├── SystemEnvUtil.java │ │ └── TimeUtil.java │ └── resources │ └── org │ └── uncommons │ └── reportng │ ├── messages │ ├── reportng.properties │ ├── reportng_fr.properties │ ├── reportng_pt.properties │ └── reportng_zh_CN.properties │ └── templates │ ├── html │ ├── class-results.html.vm │ ├── groups.html.vm │ ├── index.html.vm │ ├── output.html.vm │ ├── overview.html.vm │ ├── performance.html.vm │ ├── reportng.css │ ├── reportng.js │ ├── results.html.vm │ └── suites.html.vm │ └── xml │ └── results.xml.vm ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 1.7 41 | 42 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FastAutoTest 2 | ============ 3 | 4 | FastAutoTest是一个基于Appium的快速自动化框架. 5 | 6 | * 自动采集设备信息,无需手动获取,当USB口接入一台新设备可直接开启自动化测试工作; 7 | * 自动配置大部分信息,无需手动配置,摒弃TestNG群控时需要手工配置多个suite.xml的方式; 8 | * 自动启动Appium服务,无需手动打开,由自动化工作开始的时候通过代码打开; 9 | * 自动安装新版本软件,无需手动安装,由自动化工作开始前通过对比版本进行软件更新; 10 | * 自动采集性能数据(CPU,内存,流量)和对应执行栈,并在报告中展示; 11 | * 自动进行卡顿监测,内存泄漏监测,crash监测,收集日志和对应执行栈,并在报告中展示; 12 | * 智能失败重跑策略,出现异常退出会重启并进行场景恢复,不影响后面case的执行; 13 | * 内部DefaultReport已替换为修改版的 [ReportNG](https://github.com/dwdyer/reportng),更清晰地展示测试结果。 14 | 15 | 使用 16 | -------- 17 | __1、Download__ 18 | 19 | _Gradle_ 20 | * Step 1. 在项目根build.gradle文件中增加maven仓库依赖 21 | ```groovy 22 | allprojects { 23 | repositories { 24 | ... 25 | maven { url "https://dl.bintray.com/yph/maven" } 26 | } 27 | } 28 | ``` 29 | * Step 2. 在项目module的build.gradle添加依赖 30 | ```groovy 31 | dependencies { 32 | compile 'yph:fastautotest:1.4.2' 33 | } 34 | ``` 35 | _Maven_ 36 | ```xml 37 | 38 | yph 39 | fastautotest 40 | 1.4.2 41 | pom 42 | 43 | ``` 44 | __2、初始化__ 45 | 46 | 以手Q为例子,创建一个启动类TestFastAuto,然后进行一些必要的配置 47 | ```java 48 | public class TestFastAuto { 49 | public static void main(String[] args) { 50 | FastAuto.run(Configure.get() 51 | .setNode("node")//node路径 52 | .setApkPath("C:/Users/dell1/android-studio/workspace/workspace-2018/AppiumAutoTest/app/apk/app-debug.apk") 53 | .setAppPackage("com.tencent.mobileqq") 54 | .setAppActivity("com.tencent.mobileqq.activity.SplashActivity") 55 | .setAppiumMainJs("C:/Users/dell1/AppData/Local/Programs/appium-desktop/resources/app/node_modules/appium/build/lib/main.js") 56 | .addTestBean(new TestBean().setName("testqq").setClasses(new Class[]{TestMessage.class, TestContacts.class}))); 57 | } 58 | } 59 | ``` 60 | _说明:当你有多个Test可以通过addTestBean方法添加,每个Test通过setName方法设置名字,通过setClasses方法设置Class, 61 | 每个Test原则上是执行前要重启应用,执行完成后关闭应用,但你可以通过调用TestBean的notRestart()方法来实现不重启. 62 | 如果你配置好了node环境,并且待测Apk已经安装,那么初始化可以省略这些步骤,如下_ 63 | 64 | ```java 65 | FastAuto.run(Configure.get() 66 | .setAppPackage("com.tencent.mobileqq") 67 | .setAppActivity("com.tencent.mobileqq.activity.SplashActivity") 68 | .setAppiumMainJs("C:/Users/dell1/AppData/Local/Programs/appium-desktop/resources/app/node_modules/appium/build/lib/main.js") 69 | .addTestBean(new TestBean().setName("testqq").setClasses(new Class[]{TestMessage.class, TestContacts.class}))); 70 | ``` 71 | __3、编写Test__ 72 | ```java 73 | public class TestMessage extends BaseTest { 74 | @FindBys({@FindBy(className = "android.widget.TabWidget"),@FindBy(className = "android.widget.FrameLayout")}) 75 | List list; 76 | @Override 77 | protected void addCap(DesiredCapabilities caps){//假如你想添加参数,可重写此方法添加 78 | } 79 | @Test 80 | public void operation() { 81 | list.get(0).click(); 82 | } 83 | } 84 | ``` 85 | _说明:编写Test类继承自BaseTest,然后写一个方法,添加@Test注解后就可以编写逻辑代码了. 86 | 假如你想添加参数,可重写addCap方法添加,但一般不需要_ 87 | 88 | __4、结果展示__ 89 | 待传图... 90 | 91 | Detail 92 | -------- 93 | 94 | [Appium自动化之框架搭建](https://blog.csdn.net/u012874222/article/details/79485222) 95 | 96 | License 97 | ------- 98 | Copyright [2018] [yph] 99 | 100 | Licensed under the Apache License, Version 2.0 (the "License"); 101 | you may not use this file except in compliance with the License. 102 | You may obtain a copy of the License at 103 | 104 | http://www.apache.org/licenses/LICENSE-2.0 105 | 106 | Unless required by applicable law or agreed to in writing, software 107 | distributed under the License is distributed on an "AS IS" BASIS, 108 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 109 | See the License for the specific language governing permissions and 110 | limitations under the License. 111 | 112 | -------------------------------------------------------------------------------- /adb/AdbWinApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/adb/AdbWinApi.dll -------------------------------------------------------------------------------- /adb/AdbWinUsbApi.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/adb/AdbWinUsbApi.dll -------------------------------------------------------------------------------- /adb/adb.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/adb/adb.exe -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/apk/app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/app/apk/app-debug.apk -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | dependencies { 4 | compile fileTree(include: ['*.jar'], dir: 'libs') 5 | compile project(':fastautotest') 6 | } -------------------------------------------------------------------------------- /app/src/main/java/base/TestFastAuto.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/app/src/main/java/base/TestFastAuto.java -------------------------------------------------------------------------------- /app/src/main/java/testqq/TestContacts.java: -------------------------------------------------------------------------------- 1 | package testqq; 2 | 3 | import org.openqa.selenium.WebElement; 4 | import org.openqa.selenium.support.FindBy; 5 | import org.openqa.selenium.support.FindBys; 6 | import org.testng.annotations.Test; 7 | 8 | import java.util.List; 9 | 10 | import yph.base.BaseTest; 11 | 12 | 13 | public class TestContacts extends BaseTest { 14 | @FindBys({@FindBy(className = "android.widget.TabWidget"), @FindBy(className = "android.widget.FrameLayout")}) 15 | List list; 16 | @FindBy(id = "com.tencent.mobileqq:id/ivTitleBtnRightText") 17 | WebElement element; 18 | 19 | @Test 20 | public void entry() { 21 | list.get(0).click(); 22 | } 23 | 24 | @Test 25 | public void test() { 26 | element.click(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/testqq/TestMessage.java: -------------------------------------------------------------------------------- 1 | package testqq; 2 | 3 | import org.openqa.selenium.WebElement; 4 | import org.openqa.selenium.remote.DesiredCapabilities; 5 | import org.openqa.selenium.support.FindBy; 6 | import org.testng.Assert; 7 | import org.testng.annotations.Test; 8 | 9 | import io.appium.java_client.android.nativekey.AndroidKey; 10 | import io.appium.java_client.android.nativekey.KeyEvent; 11 | import yph.annotation.FastAuto; 12 | import yph.base.BaseTest; 13 | 14 | 15 | public class TestMessage extends BaseTest{ 16 | @FindBy(id = "com.tencent.mobileqq:id/conversation_head") 17 | WebElement element; 18 | @Override 19 | protected void addCap(DesiredCapabilities caps) {//假如你想添加参数,可重写此方法添加 20 | } 21 | 22 | @Test(description = "测试消息") 23 | public void test() { 24 | element.click(); 25 | driver.pressKey(new KeyEvent(AndroidKey.BACK)); 26 | } 27 | 28 | @FastAuto(author = "张三") 29 | @Test(description = "测试Assert") 30 | public void test1() { 31 | Assert.assertTrue(false); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | google() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.1.2' 10 | classpath 'com.novoda:bintray-release:0.8.1' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | google() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /fastautotest/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'bintray-release' 3 | dependencies { 4 | compile fileTree(dir: 'libs', include: ['*.jar']) 5 | compile 'org.testng:testng:6.14.3' 6 | compile 'io.appium:java-client:7.0.0' 7 | compile 'com.google.inject:guice:4.2.0' 8 | compile 'org.apache.velocity:velocity:1.7' 9 | } 10 | publish { 11 | artifactId = 'fastautotest' 12 | userOrg = 'yph' 13 | groupId = 'yph' 14 | publishVersion = '1.4.2' 15 | desc = 'FastAutoTest' 16 | website = 'https://github.com/qq542391099/FastAutoTest' 17 | licences = ['Apache-2.0'] 18 | } 19 | -------------------------------------------------------------------------------- /fastautotest/libs/AXMLPrinter2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/fastautotest/libs/AXMLPrinter2.jar -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/AbstractReporter.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 | package org.uncommons.reportng; 17 | 18 | import org.apache.commons.io.FileUtils; 19 | import org.apache.velocity.VelocityContext; 20 | import org.apache.velocity.app.Velocity; 21 | import org.testng.IReporter; 22 | 23 | import java.io.BufferedReader; 24 | import java.io.BufferedWriter; 25 | import java.io.File; 26 | import java.io.FileFilter; 27 | import java.io.FileInputStream; 28 | import java.io.FileOutputStream; 29 | import java.io.IOException; 30 | import java.io.InputStream; 31 | import java.io.InputStreamReader; 32 | import java.io.OutputStream; 33 | import java.io.OutputStreamWriter; 34 | import java.io.Writer; 35 | import java.util.ResourceBundle; 36 | 37 | /** 38 | * Convenient base class for the ReportNG reporters. Provides common functionality. 39 | * 40 | * @author Daniel Dyer 41 | */ 42 | public abstract class AbstractReporter implements IReporter { 43 | private static final String ENCODING = "utf-8"; 44 | 45 | protected static final String TEMPLATE_EXTENSION = ".vm"; 46 | 47 | private static final String META_KEY = "meta"; 48 | protected static final ReportMetadata META = new ReportMetadata(); 49 | private static final String UTILS_KEY = "utils"; 50 | private final String classpathPrefix; 51 | private static final ReportNGUtils UTILS = new ReportNGUtils(); 52 | private static final String MESSAGES_KEY = "messages"; 53 | private static final ResourceBundle MESSAGES; 54 | 55 | static { 56 | ResourceBundle resourceBundle; 57 | try { 58 | resourceBundle = ResourceBundle.getBundle("org.uncommons.reportng.messages.reportng", 59 | META.getLocale()); 60 | } catch (Exception e) { 61 | if (AbstractReporter.class.getResource("org.uncommons.reportng.messages") == null 62 | || AbstractReporter.class.getResource("org.uncommons.reportng.templates") == null) { 63 | try { 64 | FileUtils.copyDirectory(new File("fastautotest/src/main/resources/org/uncommons/reportng/"), new File(AbstractReporter.class.getResource("").getPath())); 65 | } catch (IOException ex) { 66 | ex.printStackTrace(); 67 | } 68 | } 69 | resourceBundle = ResourceBundle.getBundle("org.uncommons.reportng.messages.reportng", 70 | META.getLocale()); 71 | } 72 | MESSAGES = resourceBundle; 73 | } 74 | 75 | 76 | /** 77 | * @param classpathPrefix Where in the classpath to load templates from. 78 | */ 79 | protected AbstractReporter(String classpathPrefix) { 80 | this.classpathPrefix = classpathPrefix; 81 | Velocity.setProperty("resource.loader", "classpath"); 82 | Velocity.setProperty("classpath.resource.loader.class", 83 | "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); 84 | if (!META.shouldGenerateVelocityLog()) { 85 | Velocity.setProperty("runtime.log.logsystem.class", 86 | "org.apache.velocity.runtime.log.NullLogSystem"); 87 | } 88 | 89 | try { 90 | Velocity.init(); 91 | } catch (Exception ex) { 92 | throw new ReportNGException("Failed to initialise Velocity.", ex); 93 | } 94 | } 95 | 96 | 97 | /** 98 | * Helper method that creates a Velocity context and initialises it 99 | * with a reference to the ReportNG utils, report metadata and localised messages. 100 | * 101 | * @return An initialised Velocity context. 102 | */ 103 | protected VelocityContext createContext() { 104 | VelocityContext context = new VelocityContext(); 105 | context.put(META_KEY, META); 106 | context.put(UTILS_KEY, UTILS); 107 | context.put(MESSAGES_KEY, MESSAGES); 108 | return context; 109 | } 110 | 111 | 112 | /** 113 | * Generate the specified output file by merging the specified 114 | * Velocity template with the supplied context. 115 | */ 116 | protected void generateFile(File file, 117 | String templateName, 118 | VelocityContext context) throws Exception { 119 | //Writer writer = new BufferedWriter(new FileWriter(file)); 120 | //encoding to utf-8���޸��������� 121 | OutputStream out = new FileOutputStream(file); 122 | Writer writer = new BufferedWriter(new OutputStreamWriter(out, ENCODING)); 123 | try { 124 | Velocity.mergeTemplate(classpathPrefix + templateName, 125 | ENCODING, 126 | context, 127 | writer); 128 | writer.flush(); 129 | } finally { 130 | writer.close(); 131 | } 132 | } 133 | 134 | 135 | /** 136 | * Copy a single named resource from the classpath to the output directory. 137 | * 138 | * @param outputDirectory The destination directory for the copied resource. 139 | * @param resourceName The filename of the resource. 140 | * @param targetFileName The name of the file created in {@literal outputDirectory}. 141 | * @throws IOException If the resource cannot be copied. 142 | */ 143 | protected void copyClasspathResource(File outputDirectory, 144 | String resourceName, 145 | String targetFileName) throws IOException { 146 | String resourcePath = classpathPrefix + resourceName; 147 | InputStream resourceStream = getClass().getClassLoader().getResourceAsStream(resourcePath); 148 | copyStream(outputDirectory, resourceStream, targetFileName); 149 | } 150 | 151 | 152 | /** 153 | * Copy a single named file to the output directory. 154 | * 155 | * @param outputDirectory The destination directory for the copied resource. 156 | * @param sourceFile The path of the file to copy. 157 | * @param targetFileName The name of the file created in {@literal outputDirectory}. 158 | * @throws IOException If the file cannot be copied. 159 | */ 160 | protected void copyFile(File outputDirectory, 161 | File sourceFile, 162 | String targetFileName) throws IOException { 163 | InputStream fileStream = new FileInputStream(sourceFile); 164 | try { 165 | copyStream(outputDirectory, fileStream, targetFileName); 166 | } finally { 167 | fileStream.close(); 168 | } 169 | } 170 | 171 | 172 | /** 173 | * Helper method to copy the contents of a stream to a file. 174 | * 175 | * @param outputDirectory The directory in which the new file is created. 176 | * @param stream The stream to copy. 177 | * @param targetFileName The file to write the stream contents to. 178 | * @throws IOException If the stream cannot be copied. 179 | */ 180 | protected void copyStream(File outputDirectory, 181 | InputStream stream, 182 | String targetFileName) throws IOException { 183 | File resourceFile = new File(outputDirectory, targetFileName); 184 | BufferedReader reader = null; 185 | Writer writer = null; 186 | try { 187 | reader = new BufferedReader(new InputStreamReader(stream, ENCODING)); 188 | writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(resourceFile), ENCODING)); 189 | 190 | String line = reader.readLine(); 191 | while (line != null) { 192 | writer.write(line); 193 | writer.write('\n'); 194 | line = reader.readLine(); 195 | } 196 | writer.flush(); 197 | } finally { 198 | if (reader != null) { 199 | reader.close(); 200 | } 201 | if (writer != null) { 202 | writer.close(); 203 | } 204 | } 205 | } 206 | 207 | 208 | /** 209 | * Deletes any empty directories under the output directory. These 210 | * directories are created by TestNG for its own reports regardless 211 | * of whether those reports are generated. If you are using the 212 | * default TestNG reports as well as ReportNG, these directories will 213 | * not be empty and will be retained. Otherwise they will be removed. 214 | * 215 | * @param outputDirectory The directory to search for empty directories. 216 | */ 217 | protected void removeEmptyDirectories(File outputDirectory) { 218 | if (outputDirectory.exists()) { 219 | for (File file : outputDirectory.listFiles(new EmptyDirectoryFilter())) { 220 | file.delete(); 221 | } 222 | } 223 | } 224 | 225 | 226 | private static final class EmptyDirectoryFilter implements FileFilter { 227 | public boolean accept(File file) { 228 | return file.isDirectory() && file.listFiles().length == 0; 229 | } 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/HTMLReporter.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 org.uncommons.reportng; 18 | 19 | import org.apache.velocity.VelocityContext; 20 | import org.testng.IClass; 21 | import org.testng.IResultMap; 22 | import org.testng.ISuite; 23 | import org.testng.ISuiteResult; 24 | import org.testng.ITestNGMethod; 25 | import org.testng.ITestResult; 26 | import org.testng.Reporter; 27 | import org.testng.xml.XmlSuite; 28 | 29 | import java.io.File; 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | import java.util.ArrayList; 33 | import java.util.Collection; 34 | import java.util.Collections; 35 | import java.util.Comparator; 36 | import java.util.List; 37 | import java.util.Map; 38 | import java.util.SortedMap; 39 | import java.util.SortedSet; 40 | import java.util.TreeMap; 41 | import java.util.TreeSet; 42 | 43 | import static yph.performance.Device.deviceList; 44 | 45 | 46 | /** 47 | * Enhanced HTML reporter for TestNG that uses Velocity templates to generate its 48 | * output. 49 | * @author Daniel Dyer 50 | */ 51 | public class HTMLReporter extends AbstractReporter 52 | { 53 | private static final String FRAMES_PROPERTY = "org.uncommons.reportng.frames"; 54 | private static final String ONLY_FAILURES_PROPERTY = "org.uncommons.reportng.failures-only"; 55 | 56 | private static final String TEMPLATES_PATH = "org/uncommons/reportng/templates/html/"; 57 | private static final String INDEX_FILE = "index.html"; 58 | private static final String SUITES_FILE = "suites.html"; 59 | private static final String OVERVIEW_FILE = "overview.html"; 60 | private static final String PERFORMANCE_FILE = "performance.html"; 61 | private static final String GROUPS_FILE = "groups.html"; 62 | private static final String RESULTS_FILE = "results.html"; 63 | private static final String OUTPUT_FILE = "output.html"; 64 | private static final String CUSTOM_STYLE_FILE = "custom.css"; 65 | 66 | private static final String SUITE_KEY = "suite"; 67 | private static final String SUITES_KEY = "suites"; 68 | private static final String DEVICE_KEY = "device"; 69 | private static final String DEVICES_KEY = "devices"; 70 | private static final String GROUPS_KEY = "groups"; 71 | private static final String RESULT_KEY = "result"; 72 | private static final String FAILED_CONFIG_KEY = "failedConfigurations"; 73 | private static final String SKIPPED_CONFIG_KEY = "skippedConfigurations"; 74 | private static final String FAILED_TESTS_KEY = "failedTests"; 75 | private static final String SKIPPED_TESTS_KEY = "skippedTests"; 76 | private static final String PASSED_TESTS_KEY = "passedTests"; 77 | private static final String ONLY_FAILURES_KEY = "onlyReportFailures"; 78 | 79 | private static final String REPORT_DIRECTORY = "html"; 80 | 81 | private static final Comparator METHOD_COMPARATOR = new TestMethodComparator(); 82 | private static final Comparator RESULT_COMPARATOR = new TestResultComparator(); 83 | private static final Comparator CLASS_COMPARATOR = new TestClassComparator(); 84 | 85 | public HTMLReporter() 86 | { 87 | super(TEMPLATES_PATH); 88 | } 89 | 90 | 91 | /** 92 | * Generates a set of HTML files that contain data about the outcome of 93 | * the specified test suites. 94 | * @param suites Data about the test runs. 95 | * @param outputDirectoryName The directory in which to create the report. 96 | */ 97 | public void generateReport(List xmlSuites, 98 | List suites, 99 | String outputDirectoryName) 100 | { 101 | removeEmptyDirectories(new File(outputDirectoryName)); 102 | 103 | boolean useFrames = System.getProperty(FRAMES_PROPERTY, "true").equals("true"); 104 | boolean onlyFailures = System.getProperty(ONLY_FAILURES_PROPERTY, "false").equals("true"); 105 | 106 | File outputDirectory = new File(outputDirectoryName, REPORT_DIRECTORY); 107 | outputDirectory.mkdirs(); 108 | 109 | try 110 | { 111 | if (useFrames) 112 | { 113 | createFrameset(outputDirectory); 114 | } 115 | createOverview(suites, outputDirectory, !useFrames, onlyFailures); 116 | createPerformance(suites, outputDirectory, !useFrames, onlyFailures); 117 | createSuiteList(suites, outputDirectory, onlyFailures); 118 | createGroups(suites, outputDirectory); 119 | createResults(suites, outputDirectory, onlyFailures); 120 | createLog(outputDirectory, onlyFailures); 121 | copyResources(outputDirectory); 122 | } 123 | catch (Exception ex) 124 | { 125 | throw new ReportNGException("Failed generating HTML report.", ex); 126 | } 127 | } 128 | 129 | 130 | /** 131 | * Create the index file that sets up the frameset. 132 | * @param outputDirectory The target directory for the generated file(s). 133 | */ 134 | private void createFrameset(File outputDirectory) throws Exception 135 | { 136 | VelocityContext context = createContext(); 137 | generateFile(new File(outputDirectory, INDEX_FILE), 138 | INDEX_FILE + TEMPLATE_EXTENSION, 139 | context); 140 | } 141 | 142 | 143 | private void createOverview(List suites, 144 | File outputDirectory, 145 | boolean isIndex, 146 | boolean onlyFailures) throws Exception 147 | { 148 | VelocityContext context = createContext(); 149 | context.put(SUITES_KEY, suites); 150 | context.put(ONLY_FAILURES_KEY, onlyFailures); 151 | generateFile(new File(outputDirectory, isIndex ? INDEX_FILE : OVERVIEW_FILE), 152 | OVERVIEW_FILE + TEMPLATE_EXTENSION, 153 | context); 154 | } 155 | 156 | private void createPerformance(List suites, 157 | File outputDirectory, 158 | boolean isIndex, 159 | boolean onlyFailures) throws Exception 160 | { 161 | 162 | VelocityContext context = createContext(); 163 | context.put(DEVICES_KEY, deviceList); 164 | context.put(SUITES_KEY, suites); 165 | context.put(ONLY_FAILURES_KEY, onlyFailures); 166 | generateFile(new File(outputDirectory, isIndex ? INDEX_FILE : PERFORMANCE_FILE), 167 | PERFORMANCE_FILE + TEMPLATE_EXTENSION, 168 | context); 169 | } 170 | 171 | /** 172 | * Create the navigation frame. 173 | * @param outputDirectory The target directory for the generated file(s). 174 | */ 175 | private void createSuiteList(List suites, 176 | File outputDirectory, 177 | boolean onlyFailures) throws Exception 178 | { 179 | VelocityContext context = createContext(); 180 | context.put(SUITES_KEY, suites); 181 | context.put(ONLY_FAILURES_KEY, onlyFailures); 182 | generateFile(new File(outputDirectory, SUITES_FILE), 183 | SUITES_FILE + TEMPLATE_EXTENSION, 184 | context); 185 | } 186 | 187 | 188 | /** 189 | * Generate a results file for each test in each suite. 190 | * @param outputDirectory The target directory for the generated file(s). 191 | */ 192 | private void createResults(List suites, 193 | File outputDirectory, 194 | boolean onlyShowFailures) throws Exception 195 | { 196 | int index = 1; 197 | for (ISuite suite : suites) 198 | { 199 | int index2 = 1; 200 | for (ISuiteResult result : suite.getResults().values()) 201 | { 202 | boolean failuresExist = result.getTestContext().getFailedTests().size() > 0 203 | || result.getTestContext().getFailedConfigurations().size() > 0; 204 | if (!onlyShowFailures || failuresExist) 205 | { 206 | VelocityContext context = createContext(); 207 | context.put(RESULT_KEY, result); 208 | context.put(FAILED_CONFIG_KEY, sortByTestClass(result.getTestContext().getFailedConfigurations())); 209 | context.put(SKIPPED_CONFIG_KEY, sortByTestClass(result.getTestContext().getSkippedConfigurations())); 210 | context.put(FAILED_TESTS_KEY, sortByTestClass(result.getTestContext().getFailedTests())); 211 | context.put(SKIPPED_TESTS_KEY, sortByTestClass(result.getTestContext().getSkippedTests())); 212 | context.put(PASSED_TESTS_KEY, sortByTestClass(result.getTestContext().getPassedTests())); 213 | String fileName = String.format("suite%d_test%d_%s", index, index2, RESULTS_FILE); 214 | generateFile(new File(outputDirectory, fileName), 215 | RESULTS_FILE + TEMPLATE_EXTENSION, 216 | context); 217 | } 218 | ++index2; 219 | } 220 | ++index; 221 | } 222 | } 223 | 224 | 225 | /** 226 | * Group test methods by class and sort alphabetically. 227 | */ 228 | private SortedMap> sortByTestClass(IResultMap results) 229 | { 230 | SortedMap> sortedResults = new TreeMap>(CLASS_COMPARATOR); 231 | for (ITestResult result : results.getAllResults()) 232 | { 233 | List resultsForClass = sortedResults.get(result.getTestClass()); 234 | if (resultsForClass == null) 235 | { 236 | resultsForClass = new ArrayList(); 237 | sortedResults.put(result.getTestClass(), resultsForClass); 238 | } 239 | int index = Collections.binarySearch(resultsForClass, result, RESULT_COMPARATOR); 240 | if (index < 0) 241 | { 242 | index = Math.abs(index + 1); 243 | } 244 | resultsForClass.add(index, result); 245 | } 246 | return sortedResults; 247 | } 248 | 249 | 250 | 251 | /** 252 | * Generate a groups list for each suite. 253 | * @param outputDirectory The target directory for the generated file(s). 254 | */ 255 | private void createGroups(List suites, 256 | File outputDirectory) throws Exception 257 | { 258 | int index = 1; 259 | for (ISuite suite : suites) 260 | { 261 | SortedMap> groups = sortGroups(suite.getMethodsByGroups()); 262 | if (!groups.isEmpty()) 263 | { 264 | VelocityContext context = createContext(); 265 | context.put(SUITE_KEY, suite); 266 | context.put(GROUPS_KEY, groups); 267 | String fileName = String.format("suite%d_%s", index, GROUPS_FILE); 268 | generateFile(new File(outputDirectory, fileName), 269 | GROUPS_FILE + TEMPLATE_EXTENSION, 270 | context); 271 | } 272 | ++index; 273 | } 274 | } 275 | 276 | 277 | /** 278 | * Generate a groups list for each suite. 279 | * @param outputDirectory The target directory for the generated file(s). 280 | */ 281 | private void createLog(File outputDirectory, boolean onlyFailures) throws Exception 282 | { 283 | if (!Reporter.getOutput().isEmpty()) 284 | { 285 | VelocityContext context = createContext(); 286 | context.put(ONLY_FAILURES_KEY, onlyFailures); 287 | generateFile(new File(outputDirectory, OUTPUT_FILE), 288 | OUTPUT_FILE + TEMPLATE_EXTENSION, 289 | context); 290 | } 291 | } 292 | 293 | 294 | /** 295 | * Sorts groups alphabetically and also sorts methods within groups alphabetically 296 | * (class name first, then method name). Also eliminates duplicate entries. 297 | */ 298 | private SortedMap> sortGroups(Map> groups) 299 | { 300 | SortedMap> sortedGroups = new TreeMap>(); 301 | for (Map.Entry> entry : groups.entrySet()) 302 | { 303 | SortedSet methods = new TreeSet(METHOD_COMPARATOR); 304 | methods.addAll(entry.getValue()); 305 | sortedGroups.put(entry.getKey(), methods); 306 | } 307 | return sortedGroups; 308 | } 309 | 310 | 311 | /** 312 | * Reads the CSS and JavaScript files from the JAR file and writes them to 313 | * the output directory. 314 | * @param outputDirectory Where to put the resources. 315 | * @throws IOException If the resources can't be read or written. 316 | */ 317 | private void copyResources(File outputDirectory) throws IOException 318 | { 319 | copyClasspathResource(outputDirectory, "reportng.css", "reportng.css"); 320 | copyClasspathResource(outputDirectory, "reportng.js", "reportng.js"); 321 | // If there is a custom stylesheet, copy that. 322 | File customStylesheet = META.getStylesheetPath(); 323 | 324 | if (customStylesheet != null) 325 | { 326 | if (customStylesheet.exists()) 327 | { 328 | copyFile(outputDirectory, customStylesheet, CUSTOM_STYLE_FILE); 329 | } 330 | else 331 | { 332 | // If not found, try to read the file as a resource on the classpath 333 | // useful when reportng is called by a jarred up library 334 | InputStream stream = ClassLoader.getSystemClassLoader().getResourceAsStream(customStylesheet.getPath()); 335 | if (stream != null) 336 | { 337 | copyStream(outputDirectory, stream, CUSTOM_STYLE_FILE); 338 | } 339 | } 340 | } 341 | } 342 | } 343 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/JUnitXMLReporter.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 | package org.uncommons.reportng; 17 | 18 | import org.apache.velocity.VelocityContext; 19 | import org.testng.IClass; 20 | import org.testng.ISuite; 21 | import org.testng.ISuiteResult; 22 | import org.testng.ITestResult; 23 | import org.testng.xml.XmlSuite; 24 | 25 | import java.io.File; 26 | import java.util.Collection; 27 | import java.util.HashMap; 28 | import java.util.LinkedList; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.Set; 32 | 33 | /** 34 | * JUnit XML reporter for TestNG that uses Velocity templates to generate its 35 | * output. 36 | * @author Daniel Dyer 37 | */ 38 | public class JUnitXMLReporter extends AbstractReporter 39 | { 40 | private static final String RESULTS_KEY = "results"; 41 | 42 | private static final String TEMPLATES_PATH = "org/uncommons/reportng/templates/xml/"; 43 | private static final String RESULTS_FILE = "results.xml"; 44 | 45 | private static final String REPORT_DIRECTORY = "xml"; 46 | 47 | 48 | public JUnitXMLReporter() 49 | { 50 | super(TEMPLATES_PATH); 51 | } 52 | 53 | 54 | /** 55 | * Generates a set of XML files (JUnit format) that contain data about the 56 | * outcome of the specified test suites. 57 | * @param suites Data about the test runs. 58 | * @param outputDirectoryName The directory in which to create the report. 59 | */ 60 | public void generateReport(List xmlSuites, 61 | List suites, 62 | String outputDirectoryName) 63 | { 64 | removeEmptyDirectories(new File(outputDirectoryName)); 65 | 66 | File outputDirectory = new File(outputDirectoryName, REPORT_DIRECTORY); 67 | outputDirectory.mkdirs(); 68 | 69 | Collection flattenedResults = flattenResults(suites); 70 | 71 | for (TestClassResults results : flattenedResults) 72 | { 73 | VelocityContext context = createContext(); 74 | context.put(RESULTS_KEY, results); 75 | 76 | try 77 | { 78 | generateFile(new File(outputDirectory, results.getTestClass().getName() + '_' + RESULTS_FILE), 79 | RESULTS_FILE + TEMPLATE_EXTENSION, 80 | context); 81 | } 82 | catch (Exception ex) 83 | { 84 | throw new ReportNGException("Failed generating JUnit XML report.", ex); 85 | } 86 | } 87 | } 88 | 89 | 90 | /** 91 | * Flatten a list of test suite results into a collection of results grouped by test class. 92 | * This method basically strips away the TestNG way of organising tests and arranges 93 | * the results by test class. 94 | */ 95 | private Collection flattenResults(List suites) 96 | { 97 | Map flattenedResults = new HashMap(); 98 | for (ISuite suite : suites) 99 | { 100 | for (ISuiteResult suiteResult : suite.getResults().values()) 101 | { 102 | // Failed and skipped configuration methods are treated as test failures. 103 | organiseByClass(suiteResult.getTestContext().getFailedConfigurations().getAllResults(), flattenedResults); 104 | organiseByClass(suiteResult.getTestContext().getSkippedConfigurations().getAllResults(), flattenedResults); 105 | // Successful configuration methods are not included. 106 | 107 | organiseByClass(suiteResult.getTestContext().getFailedTests().getAllResults(), flattenedResults); 108 | organiseByClass(suiteResult.getTestContext().getSkippedTests().getAllResults(), flattenedResults); 109 | organiseByClass(suiteResult.getTestContext().getPassedTests().getAllResults(), flattenedResults); 110 | } 111 | } 112 | return flattenedResults.values(); 113 | } 114 | 115 | 116 | private void organiseByClass(Set testResults, 117 | Map flattenedResults) 118 | { 119 | for (ITestResult testResult : testResults) 120 | { 121 | getResultsForClass(flattenedResults, testResult).addResult(testResult); 122 | } 123 | } 124 | 125 | 126 | /** 127 | * Look-up the results data for a particular test class. 128 | */ 129 | private TestClassResults getResultsForClass(Map flattenedResults, 130 | ITestResult testResult) 131 | { 132 | TestClassResults resultsForClass = flattenedResults.get(testResult.getTestClass()); 133 | if (resultsForClass == null) 134 | { 135 | resultsForClass = new TestClassResults(testResult.getTestClass()); 136 | flattenedResults.put(testResult.getTestClass(), resultsForClass); 137 | } 138 | return resultsForClass; 139 | } 140 | 141 | 142 | /** 143 | * Groups together all of the data about the tests results from the methods 144 | * of a single test class. 145 | */ 146 | public static final class TestClassResults 147 | { 148 | private final IClass testClass; 149 | private final Collection failedTests = new LinkedList(); 150 | private final Collection skippedTests = new LinkedList(); 151 | private final Collection passedTests = new LinkedList(); 152 | 153 | private long duration = 0; 154 | 155 | 156 | private TestClassResults(IClass testClass) 157 | { 158 | this.testClass = testClass; 159 | } 160 | 161 | 162 | public IClass getTestClass() 163 | { 164 | return testClass; 165 | } 166 | 167 | 168 | /** 169 | * Adds a test result for this class. Organises results by outcome. 170 | */ 171 | void addResult(ITestResult result) 172 | { 173 | switch (result.getStatus()) 174 | { 175 | case ITestResult.SKIP: 176 | { 177 | if (META.allowSkippedTestsInXML()) 178 | { 179 | skippedTests.add(result); 180 | break; 181 | } 182 | // Intentional fall-through (skipped tests marked as failed if XML doesn't support skips). 183 | } 184 | case ITestResult.FAILURE: 185 | case ITestResult.SUCCESS_PERCENTAGE_FAILURE: 186 | { 187 | failedTests.add(result); 188 | break; 189 | } 190 | case ITestResult.SUCCESS: 191 | { 192 | passedTests.add(result); 193 | break; 194 | } 195 | } 196 | duration += (result.getEndMillis() - result.getStartMillis()); 197 | } 198 | 199 | 200 | public Collection getFailedTests() 201 | { 202 | return failedTests; 203 | } 204 | 205 | 206 | public Collection getSkippedTests() 207 | { 208 | return skippedTests; 209 | } 210 | 211 | 212 | public Collection getPassedTests() 213 | { 214 | return passedTests; 215 | } 216 | 217 | 218 | public long getDuration() 219 | { 220 | return duration; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/ReportMetadata.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 | package org.uncommons.reportng; 17 | 18 | import java.io.File; 19 | import java.net.InetAddress; 20 | import java.net.UnknownHostException; 21 | import java.text.DateFormat; 22 | import java.text.SimpleDateFormat; 23 | import java.util.Date; 24 | import java.util.Locale; 25 | 26 | /** 27 | * Provides access to static information useful when generating a report. 28 | * @author Daniel Dyer 29 | */ 30 | public final class ReportMetadata 31 | { 32 | static final String PROPERTY_KEY_PREFIX = "org.uncommons.reportng."; 33 | static final String TITLE_KEY = PROPERTY_KEY_PREFIX + "title"; 34 | static final String DEFAULT_TITLE = "Test Results Report"; 35 | static final String COVERAGE_KEY = PROPERTY_KEY_PREFIX + "coverage-report"; 36 | static final String EXCEPTIONS_KEY = PROPERTY_KEY_PREFIX + "show-expected-exceptions"; 37 | static final String OUTPUT_KEY = PROPERTY_KEY_PREFIX + "escape-output"; 38 | static final String XML_DIALECT_KEY = PROPERTY_KEY_PREFIX + "xml-dialect"; 39 | static final String STYLESHEET_KEY = PROPERTY_KEY_PREFIX + "stylesheet"; 40 | static final String LOCALE_KEY = PROPERTY_KEY_PREFIX + "locale"; 41 | static final String VELOCITY_LOG_KEY = PROPERTY_KEY_PREFIX + "velocity-log"; 42 | 43 | private static final DateFormat DATE_FORMAT = new SimpleDateFormat("EEEE dd MMMM yyyy"); 44 | private static final DateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm z"); 45 | 46 | 47 | /** 48 | * The date/time at which this report is being generated. 49 | */ 50 | private final Date reportTime = new Date(); 51 | 52 | 53 | /** 54 | * @return A String representation of the report date. 55 | * @see #getReportTime() 56 | */ 57 | public String getReportDate() 58 | { 59 | return DATE_FORMAT.format(reportTime); 60 | } 61 | 62 | 63 | /** 64 | * @return A String representation of the report time. 65 | * @see #getReportDate() 66 | */ 67 | public String getReportTime() 68 | { 69 | return TIME_FORMAT.format(reportTime); 70 | } 71 | 72 | 73 | public String getReportTitle() 74 | { 75 | return System.getProperty(TITLE_KEY, DEFAULT_TITLE); 76 | } 77 | 78 | 79 | /** 80 | * @return The URL (absolute or relative) of an HTML coverage report associated 81 | * with the test run. Null if there is no coverage report. 82 | */ 83 | public String getCoverageLink() 84 | { 85 | return System.getProperty(COVERAGE_KEY); 86 | } 87 | 88 | 89 | /** 90 | * If a custom CSS file has been specified, returns the path. Otherwise 91 | * returns null. 92 | * @return A {@link File} pointing to the stylesheet, or null if no stylesheet 93 | * is specified. 94 | */ 95 | public File getStylesheetPath() 96 | { 97 | String path = System.getProperty(STYLESHEET_KEY); 98 | return path == null ? null : new File(path); 99 | } 100 | 101 | 102 | /** 103 | * Returns false (the default) if stack traces should not be shown for 104 | * expected exceptions. 105 | * @return True if stack traces should be shown even for expected exceptions, 106 | * false otherwise. 107 | */ 108 | public boolean shouldShowExpectedExceptions() 109 | { 110 | return System.getProperty(EXCEPTIONS_KEY, "false").equalsIgnoreCase("true"); 111 | } 112 | 113 | 114 | /** 115 | * Returns true (the default) if log text should be escaped when displayed in a 116 | * report. Turning off escaping allows you to do something link inserting 117 | * link tags into HTML reports, but it also means that other output could 118 | * accidentally corrupt the mark-up. 119 | * @return True if reporter log output should be escaped when displayed in a 120 | * report, false otherwise. 121 | */ 122 | public boolean shouldEscapeOutput() 123 | { 124 | return System.getProperty(OUTPUT_KEY, "true").equalsIgnoreCase("true"); 125 | } 126 | 127 | 128 | /** 129 | * If the XML dialect has been set to "junit", we will render all skipped tests 130 | * as failed tests in the XML. Otherwise we use TestNG's extended version of 131 | * the XML format that allows for "" elements. 132 | */ 133 | public boolean allowSkippedTestsInXML() 134 | { 135 | return !System.getProperty(XML_DIALECT_KEY, "testng").equalsIgnoreCase("junit"); 136 | } 137 | 138 | 139 | /** 140 | * @return True if Velocity should generate a log file, false otherwise. 141 | */ 142 | public boolean shouldGenerateVelocityLog() 143 | { 144 | return System.getProperty(VELOCITY_LOG_KEY, "false").equalsIgnoreCase("true"); 145 | } 146 | 147 | 148 | /** 149 | * @return The user account used to run the tests and the host name of the 150 | * test machine. 151 | * @throws UnknownHostException If there is a problem accessing the machine's host name. 152 | */ 153 | public String getUser() throws UnknownHostException 154 | { 155 | String user = System.getProperty("user.name"); 156 | String host = InetAddress.getLocalHost().getHostName(); 157 | return user + '@' + host; 158 | } 159 | 160 | 161 | public String getJavaInfo() 162 | { 163 | return String.format("Java %s (%s)", 164 | System.getProperty("java.version"), 165 | System.getProperty("java.vendor")); 166 | } 167 | 168 | 169 | public String getPlatform() 170 | { 171 | return String.format("%s %s (%s)", 172 | System.getProperty("os.name"), 173 | System.getProperty("os.version"), 174 | System.getProperty("os.arch")); 175 | } 176 | 177 | 178 | /** 179 | * @return The locale specified by the System properties, or the platform default locale 180 | * if none is specified. 181 | */ 182 | public Locale getLocale() 183 | { 184 | if (System.getProperties().containsKey(LOCALE_KEY)) 185 | { 186 | String locale = System.getProperty(LOCALE_KEY); 187 | String[] components = locale.split("_", 3); 188 | switch (components.length) 189 | { 190 | case 1: return new Locale(locale); 191 | case 2: return new Locale(components[0], components[1]); 192 | case 3: return new Locale(components[0], components[1], components[2]); 193 | default: System.err.println("Invalid locale specified: " + locale); 194 | } 195 | } 196 | return Locale.getDefault(); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/ReportNGException.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 | package org.uncommons.reportng; 17 | 18 | /** 19 | * Unchecked exception thrown when an unrecoverable error occurs during report 20 | * generation. 21 | * @author Daniel Dyer 22 | */ 23 | public class ReportNGException extends RuntimeException 24 | { 25 | public ReportNGException(String string) 26 | { 27 | super(string); 28 | } 29 | 30 | 31 | public ReportNGException(String string, Throwable throwable) 32 | { 33 | super(string, throwable); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/ReportNGUtils.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 | package org.uncommons.reportng; 17 | 18 | import org.testng.IInvokedMethod; 19 | import org.testng.ISuite; 20 | import org.testng.ISuiteResult; 21 | import org.testng.ITestContext; 22 | import org.testng.ITestNGMethod; 23 | import org.testng.ITestResult; 24 | import org.testng.Reporter; 25 | import org.testng.SkipException; 26 | 27 | import java.lang.reflect.Method; 28 | import java.text.DecimalFormat; 29 | import java.text.NumberFormat; 30 | import java.util.ArrayList; 31 | import java.util.Arrays; 32 | import java.util.Collection; 33 | import java.util.Iterator; 34 | import java.util.LinkedList; 35 | import java.util.List; 36 | import java.util.Map; 37 | import java.util.Set; 38 | import java.util.regex.Matcher; 39 | import java.util.regex.Pattern; 40 | 41 | import yph.annotation.FastAuto; 42 | 43 | import static org.testng.Reporter.getOutput; 44 | 45 | /** 46 | * Utility class that provides various helper methods that can be invoked 47 | * from a Velocity template. 48 | * 49 | * @author Daniel Dyer 50 | */ 51 | public class ReportNGUtils { 52 | private static final NumberFormat DURATION_FORMAT = new DecimalFormat("#0.000"); 53 | private static final NumberFormat PERCENTAGE_FORMAT = new DecimalFormat("#0.00%"); 54 | 55 | /** 56 | * Returns the aggregate of the elapsed times for each test result. 57 | * 58 | * @param context The test results. 59 | * @return The sum of the test durations. 60 | */ 61 | public long getDuration(ITestContext context) { 62 | long duration = getDuration(context.getPassedConfigurations().getAllResults()); 63 | duration += getDuration(context.getPassedTests().getAllResults()); 64 | // You would expect skipped tests to have durations of zero, but apparently not. 65 | duration += getDuration(context.getSkippedConfigurations().getAllResults()); 66 | duration += getDuration(context.getSkippedTests().getAllResults()); 67 | duration += getDuration(context.getFailedConfigurations().getAllResults()); 68 | duration += getDuration(context.getFailedTests().getAllResults()); 69 | return duration; 70 | } 71 | 72 | 73 | /** 74 | * Returns the aggregate of the elapsed times for each test result. 75 | * 76 | * @param results A set of test results. 77 | * @return The sum of the test durations. 78 | */ 79 | private long getDuration(Set results) { 80 | long duration = 0; 81 | for (ITestResult result : results) { 82 | duration += (result.getEndMillis() - result.getStartMillis()); 83 | } 84 | return duration; 85 | } 86 | 87 | 88 | public String formatDuration(long startMillis, long endMillis) { 89 | long elapsed = endMillis - startMillis; 90 | return formatDuration(elapsed); 91 | } 92 | 93 | 94 | public String formatDuration(long elapsed) { 95 | double seconds = (double) elapsed / 1000; 96 | return DURATION_FORMAT.format(seconds); 97 | } 98 | 99 | public String getAuthod(ITestResult result) { 100 | String authod = "--"; 101 | try { 102 | Class clazz = Class.forName(result.getTestClass().getName()); 103 | Method[] methods = clazz.getDeclaredMethods(); 104 | if (methods != null && methods.length > 0) { 105 | for (Method method : methods) { 106 | FastAuto authodAnnotation = method.getAnnotation(FastAuto.class); 107 | if (authodAnnotation != null && 108 | result.getMethod().getMethodName().equals(method.getName())) { 109 | authod = authodAnnotation.author(); 110 | } 111 | } 112 | } 113 | } catch (ClassNotFoundException e) { 114 | e.printStackTrace(); 115 | } 116 | return authod; 117 | } 118 | public String getRunCounts(ITestResult result) { 119 | String RunCounts = "1"; 120 | List list = getTestOutput(result); 121 | for(String s : list){ 122 | if(s.contains("RunCount=")){ 123 | RunCounts = s.replace("RunCount=",""); 124 | } 125 | } 126 | return RunCounts; 127 | } 128 | /** 129 | * Convert a Throwable into a list containing all of its causes. 130 | * 131 | * @param t The throwable for which the causes are to be returned. 132 | * @return A (possibly empty) list of {@link Throwable}s. 133 | */ 134 | public List getCauses(Throwable t) { 135 | List causes = new LinkedList(); 136 | Throwable next = t; 137 | while (next.getCause() != null) { 138 | next = next.getCause(); 139 | causes.add(next); 140 | } 141 | return causes; 142 | } 143 | 144 | 145 | /** 146 | * Retrieves all log messages associated with a particular test result. 147 | * 148 | * @param result Which test result to look-up. 149 | * @return A list of log messages. 150 | */ 151 | public List getTestOutput(ITestResult result) { 152 | return Reporter.getOutput(result); 153 | } 154 | public List getTestOutputWithoutRunCount(ITestResult result) { 155 | List newList = new ArrayList<>(); 156 | List list = getTestOutput(result); 157 | for(String s : list){ 158 | if(!s.contains("RunCount=")){ 159 | newList.add(s); 160 | } 161 | } 162 | return newList; 163 | } 164 | 165 | /** 166 | * Retieves the output from all calls to {@link Reporter#log(String)} 167 | * across all tests. 168 | * 169 | * @return A (possibly empty) list of log messages. 170 | */ 171 | public List getAllOutput() { 172 | return getOutput(); 173 | } 174 | 175 | 176 | public boolean hasArguments(ITestResult result) { 177 | return result.getParameters().length > 0; 178 | } 179 | 180 | 181 | public String getArguments(ITestResult result) { 182 | Object[] arguments = result.getParameters(); 183 | List argumentStrings = new ArrayList(arguments.length); 184 | for (Object argument : arguments) { 185 | argumentStrings.add(renderArgument(argument)); 186 | } 187 | return commaSeparate(argumentStrings); 188 | } 189 | 190 | 191 | /** 192 | * Decorate the string representation of an argument to give some 193 | * hint as to its type (e.g. render Strings in double quotes). 194 | * 195 | * @param argument The argument to render. 196 | * @return The string representation of the argument. 197 | */ 198 | private String renderArgument(Object argument) { 199 | if (argument == null) { 200 | return "null"; 201 | } else if (argument instanceof String) { 202 | return "\"" + argument + "\""; 203 | } else if (argument instanceof Character) { 204 | return "\'" + argument + "\'"; 205 | } else { 206 | return argument.toString(); 207 | } 208 | } 209 | 210 | 211 | /** 212 | * @param result The test result to be checked for dependent groups. 213 | * @return True if this test was dependent on any groups, false otherwise. 214 | */ 215 | public boolean hasDependentGroups(ITestResult result) { 216 | return result.getMethod().getGroupsDependedUpon().length > 0; 217 | } 218 | 219 | 220 | /** 221 | * @return A comma-separated string listing all dependent groups. Returns an 222 | * empty string it there are no dependent groups. 223 | */ 224 | public String getDependentGroups(ITestResult result) { 225 | String[] groups = result.getMethod().getGroupsDependedUpon(); 226 | return commaSeparate(Arrays.asList(groups)); 227 | } 228 | 229 | 230 | /** 231 | * @param result The test result to be checked for dependent methods. 232 | * @return True if this test was dependent on any methods, false otherwise. 233 | */ 234 | public boolean hasDependentMethods(ITestResult result) { 235 | return result.getMethod().getMethodsDependedUpon().length > 0; 236 | } 237 | 238 | 239 | /** 240 | * @return A comma-separated string listing all dependent methods. Returns an 241 | * empty string it there are no dependent methods. 242 | */ 243 | public String getDependentMethods(ITestResult result) { 244 | String[] methods = result.getMethod().getMethodsDependedUpon(); 245 | return commaSeparate(Arrays.asList(methods)); 246 | } 247 | 248 | 249 | public boolean hasSkipException(ITestResult result) { 250 | return result.getThrowable() instanceof SkipException; 251 | } 252 | 253 | 254 | public String getSkipExceptionMessage(ITestResult result) { 255 | return hasSkipException(result) ? result.getThrowable().getMessage() : ""; 256 | } 257 | 258 | 259 | public boolean hasGroups(ISuite suite) { 260 | return !suite.getMethodsByGroups().isEmpty(); 261 | } 262 | 263 | 264 | /** 265 | * Takes a list of Strings and combines them into a single comma-separated 266 | * String. 267 | * 268 | * @param strings The Strings to combine. 269 | * @return The combined, comma-separated, String. 270 | */ 271 | private String commaSeparate(Collection strings) { 272 | StringBuilder buffer = new StringBuilder(); 273 | Iterator iterator = strings.iterator(); 274 | while (iterator.hasNext()) { 275 | String string = iterator.next(); 276 | buffer.append(string); 277 | if (iterator.hasNext()) { 278 | buffer.append(", "); 279 | } 280 | } 281 | return buffer.toString(); 282 | } 283 | 284 | 285 | /** 286 | * Replace any angle brackets, quotes, apostrophes or ampersands with the 287 | * corresponding XML/HTML entities to avoid problems displaying the String in 288 | * an XML document. Assumes that the String does not already contain any 289 | * entities (otherwise the ampersands will be escaped again). 290 | * 291 | * @param s The String to escape. 292 | * @return The escaped String. 293 | */ 294 | public String escapeString(String s) { 295 | if (s == null) { 296 | return null; 297 | } 298 | 299 | StringBuilder buffer = new StringBuilder(); 300 | for (int i = 0; i < s.length(); i++) { 301 | buffer.append(escapeChar(s.charAt(i))); 302 | } 303 | return buffer.toString(); 304 | } 305 | 306 | 307 | /** 308 | * Converts a char into a String that can be inserted into an XML document, 309 | * replacing special characters with XML entities as required. 310 | * 311 | * @param character The character to convert. 312 | * @return An XML entity representing the character (or a String containing 313 | * just the character if it does not need to be escaped). 314 | */ 315 | private String escapeChar(char character) { 316 | switch (character) { 317 | case '<': 318 | return "<"; 319 | case '>': 320 | return ">"; 321 | case '"': 322 | return """; 323 | case '\'': 324 | return "'"; 325 | case '&': 326 | return "&"; 327 | default: 328 | return String.valueOf(character); 329 | } 330 | } 331 | 332 | 333 | /** 334 | * Works like {@link #escapeString(String)} but also replaces line breaks with 335 | * <br /> tags and preserves significant whitespace. 336 | * 337 | * @param s The String to escape. 338 | * @return The escaped String. 339 | */ 340 | public String escapeHTMLString(String s) { 341 | if (s == null) { 342 | return null; 343 | } 344 | 345 | StringBuilder buffer = new StringBuilder(); 346 | for (int i = 0; i < s.length(); i++) { 347 | char ch = s.charAt(i); 348 | switch (ch) { 349 | case ' ': 350 | // All spaces in a block of consecutive spaces are converted to 351 | // non-breaking space ( ) except for the last one. This allows 352 | // significant whitespace to be retained without prohibiting wrapping. 353 | char nextCh = i + 1 < s.length() ? s.charAt(i + 1) : 0; 354 | buffer.append(nextCh == ' ' ? " " : " "); 355 | break; 356 | case '\n': 357 | buffer.append("
\n"); 358 | break; 359 | default: 360 | buffer.append(escapeChar(ch)); 361 | } 362 | } 363 | return buffer.toString(); 364 | } 365 | 366 | 367 | /** 368 | * TestNG returns a compound thread ID that includes the thread name and its numeric ID, 369 | * separated by an 'at' sign. We only want to use the thread name as the ID is mostly 370 | * unimportant and it takes up too much space in the generated report. 371 | * 372 | * @param threadId The compound thread ID. 373 | * @return The thread name. 374 | */ 375 | public String stripThreadName(String threadId) { 376 | if (threadId == null) { 377 | return null; 378 | } else { 379 | int index = threadId.lastIndexOf('@'); 380 | return index >= 0 ? threadId.substring(0, index) : threadId; 381 | } 382 | } 383 | 384 | 385 | /** 386 | * Find the earliest start time of the specified methods. 387 | * 388 | * @param methods A list of test methods. 389 | * @return The earliest start time. 390 | */ 391 | public long getStartTime(List methods) { 392 | long startTime = System.currentTimeMillis(); 393 | for (IInvokedMethod method : methods) { 394 | startTime = Math.min(startTime, method.getDate()); 395 | } 396 | return startTime; 397 | } 398 | 399 | 400 | public long getEndTime(ISuite suite, IInvokedMethod method, List methods) { 401 | boolean found = false; 402 | for (IInvokedMethod m : methods) { 403 | if (m == method) { 404 | found = true; 405 | } 406 | // Once a method is found, find subsequent method on same thread. 407 | else if (found && m.getTestMethod().getId().equals(method.getTestMethod().getId())) { 408 | return m.getDate(); 409 | } 410 | } 411 | return getEndTime(suite, method); 412 | } 413 | 414 | 415 | /** 416 | * Returns the timestamp for the time at which the suite finished executing. 417 | * This is determined by finding the latest end time for each of the individual 418 | * tests in the suite. 419 | * 420 | * @param suite The suite to find the end time of. 421 | * @return The end time (as a number of milliseconds since 00:00 1st January 1970 UTC). 422 | */ 423 | private long getEndTime(ISuite suite, IInvokedMethod method) { 424 | // Find the latest end time for all tests in the suite. 425 | for (Map.Entry entry : suite.getResults().entrySet()) { 426 | ITestContext testContext = entry.getValue().getTestContext(); 427 | for (ITestNGMethod m : testContext.getAllTestMethods()) { 428 | if (method == m) { 429 | return testContext.getEndDate().getTime(); 430 | } 431 | } 432 | // If we can't find a matching test method it must be a configuration method. 433 | for (ITestNGMethod m : testContext.getPassedConfigurations().getAllMethods()) { 434 | if (method == m) { 435 | return testContext.getEndDate().getTime(); 436 | } 437 | } 438 | for (ITestNGMethod m : testContext.getFailedConfigurations().getAllMethods()) { 439 | if (method == m) { 440 | return testContext.getEndDate().getTime(); 441 | } 442 | } 443 | } 444 | throw new IllegalStateException("Could not find matching end time."); 445 | } 446 | 447 | 448 | public String formatPercentage(int numerator, int denominator) { 449 | return PERCENTAGE_FORMAT.format(numerator / (double) denominator); 450 | } 451 | 452 | public String getImageString(String s) { 453 | String regex = "()"; 454 | Pattern pattern = Pattern.compile(regex); 455 | Matcher matcher = pattern.matcher(s); 456 | while (matcher.find()) { 457 | String group = matcher.group(1); 458 | //�ɸ���ʵ��������ͼƬ ȫ��һ��return 459 | return group; 460 | } 461 | return ""; 462 | } 463 | 464 | 465 | public String removeImage(String s) { 466 | return s.replaceAll("", ""); 467 | } 468 | 469 | } 470 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/TestClassComparator.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 | package org.uncommons.reportng; 17 | 18 | import org.testng.IClass; 19 | 20 | import java.util.Comparator; 21 | 22 | /** 23 | * Comparator for sorting classes alphabetically by fully-qualified name. 24 | * @author Daniel Dyer 25 | */ 26 | class TestClassComparator implements Comparator 27 | { 28 | public int compare(IClass class1, IClass class2) 29 | { 30 | return class1.getName().compareTo(class2.getName()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/TestMethodComparator.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 | package org.uncommons.reportng; 17 | 18 | import org.testng.ITestNGMethod; 19 | 20 | import java.util.Comparator; 21 | 22 | /** 23 | * Comparator for sorting TestNG test methods. Sorts method alphabeticaly 24 | * (first by fully-qualified class name, then by method name). 25 | * @author Daniel Dyer 26 | */ 27 | class TestMethodComparator implements Comparator 28 | { 29 | public int compare(ITestNGMethod method1, 30 | ITestNGMethod method2) 31 | { 32 | int compare = method1.getRealClass().getName().compareTo(method2.getRealClass().getName()); 33 | if (compare == 0) 34 | { 35 | compare = method1.getMethodName().compareTo(method2.getMethodName()); 36 | } 37 | return compare; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/org/uncommons/reportng/TestResultComparator.java: -------------------------------------------------------------------------------- 1 | //============================================================================= 2 | // Copyright 2006-2013 Daniel W. Dyer 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 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 | package org.uncommons.reportng; 17 | 18 | import org.testng.ITestResult; 19 | 20 | import java.util.Comparator; 21 | 22 | /** 23 | * Comparator for sorting TestNG test results alphabetically by method name. 24 | * @author Daniel Dyer 25 | */ 26 | class TestResultComparator implements Comparator 27 | { 28 | public int compare(ITestResult result1, ITestResult result2) 29 | { 30 | //return result1.getName().compareTo(result2.getName()); 31 | //�޸İ�ִ��˳������ 32 | if (result1.getStartMillis() processMap = new HashMap<>(); 15 | 16 | static void start(final String nodePath, final String appiumPath, final String port, final String bootstrapPort, final String chromeDriverPort, final String udid) { 17 | if (appiumPath.equals("")){ 18 | if (!CmdUtil.get().isProcessRunning("0.0.0.0:" + port)) { 19 | Log.e("Not Set AppiumMainJs Path , Please Set AppiumMainJs Path or Start Appium Manual."); 20 | } 21 | return; 22 | } 23 | if(!new File(appiumPath).exists()){ 24 | Log.e("AppiumMainJs Path Error"); 25 | return; 26 | } 27 | CmdUtil.get().killProcessIfExist("0.0.0.0:" + port); 28 | new Thread(new Runnable() { 29 | @Override 30 | public void run() { 31 | String cmd = nodePath + " \"" + appiumPath + "\" " + "--session-override " + " -p " 32 | + port + " -bp " + bootstrapPort + " --chromedriver-port " + chromeDriverPort + " -U " + udid; 33 | try { 34 | Process process = Runtime.getRuntime().exec(cmd); 35 | processMap.put(port, process); 36 | RuntimeUtil.exec(process,"AppiumServer("+port+") Starting..."); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | }).start(); 42 | SleepUtil.s(10000); 43 | while (!CmdUtil.get().isProcessRunning("0.0.0.0:" + port)) { 44 | SleepUtil.s(4000); 45 | } 46 | Log.i("AppiumServer("+port+") Start Successful"); 47 | } 48 | 49 | static void stop(String port) { 50 | if(processMap.containsKey(port)) 51 | processMap.get(port).destroy(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/base/BaseTest.java: -------------------------------------------------------------------------------- 1 | package yph.base; 2 | 3 | import org.openqa.selenium.remote.DesiredCapabilities; 4 | import org.openqa.selenium.support.PageFactory; 5 | import org.testng.annotations.AfterSuite; 6 | import org.testng.annotations.AfterTest; 7 | import org.testng.annotations.BeforeClass; 8 | import org.testng.annotations.BeforeSuite; 9 | import org.testng.annotations.BeforeTest; 10 | import org.testng.annotations.Parameters; 11 | 12 | import java.net.URL; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import io.appium.java_client.android.AndroidDriver; 16 | import io.appium.java_client.remote.AutomationName; 17 | import yph.helper.RestartTestHelper; 18 | import yph.performance.PerforMonitor; 19 | import yph.utils.Log; 20 | 21 | import static io.appium.java_client.remote.AndroidMobileCapabilityType.APP_ACTIVITY; 22 | import static io.appium.java_client.remote.AndroidMobileCapabilityType.APP_PACKAGE; 23 | import static io.appium.java_client.remote.AndroidMobileCapabilityType.AUTO_GRANT_PERMISSIONS; 24 | import static io.appium.java_client.remote.AndroidMobileCapabilityType.DONT_STOP_APP_ON_RESET; 25 | import static io.appium.java_client.remote.AndroidMobileCapabilityType.NO_SIGN; 26 | import static io.appium.java_client.remote.AndroidMobileCapabilityType.RESET_KEYBOARD; 27 | import static io.appium.java_client.remote.AndroidMobileCapabilityType.UNICODE_KEYBOARD; 28 | import static io.appium.java_client.remote.MobileCapabilityType.APP; 29 | import static io.appium.java_client.remote.MobileCapabilityType.AUTOMATION_NAME; 30 | import static io.appium.java_client.remote.MobileCapabilityType.DEVICE_NAME; 31 | import static io.appium.java_client.remote.MobileCapabilityType.NO_RESET; 32 | import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_NAME; 33 | import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_VERSION; 34 | 35 | public class BaseTest { 36 | 37 | @Parameters({"node", "appiumMainJs", "port", "bootstrap_port", "chromedriver_port", "udid"}) 38 | @BeforeSuite 39 | public void startServer(String node, String appiumMainJs, String port, String bootstrapPort, String chromeDriverPort, String udid) { 40 | AppiumServer.start(node, appiumMainJs, port, bootstrapPort, chromeDriverPort, udid); 41 | } 42 | 43 | @Parameters({"port", "platformName", "platformVersion", "deviceName", "appPackage", "appActivity", "app", "udid"}) 44 | @BeforeTest 45 | public void setUp(String appiumPort, String platformName, String platformVersion, String deviceName, String appPackage, 46 | String appActivity, String app, String udid) { 47 | if (!RestartTestHelper.isCurTestRestart()) return; 48 | DesiredCapabilities caps = new DesiredCapabilities(); 49 | caps.setCapability(PLATFORM_NAME, platformName); 50 | caps.setCapability(PLATFORM_VERSION, platformVersion); 51 | caps.setCapability(DEVICE_NAME, deviceName); 52 | caps.setCapability(NO_RESET, true); 53 | caps.setCapability(APP, app); 54 | caps.setCapability(APP_PACKAGE, appPackage); 55 | caps.setCapability(APP_ACTIVITY, appActivity); 56 | caps.setCapability(UNICODE_KEYBOARD, true); 57 | caps.setCapability(RESET_KEYBOARD, true); 58 | caps.setCapability(AUTO_GRANT_PERMISSIONS, true); 59 | caps.setCapability(DONT_STOP_APP_ON_RESET, true); 60 | caps.setCapability(NO_SIGN, true);//表示不重签名app在设置为true的情况下 61 | if (isLargeThan4d4(platformVersion))//U1会造成cpu升高,U2不会 62 | caps.setCapability(AUTOMATION_NAME, AutomationName.ANDROID_UIAUTOMATOR2); 63 | addCap(caps); 64 | AndroidDriver driver = newAndroidDriver(appiumPort, caps); 65 | androidDriverTl.set(driver); 66 | PerforMonitor perforMonitor = new PerforMonitor(deviceName, udid, appPackage); 67 | perforMonitor.start(Thread.currentThread()); 68 | perforMonitorTl.set(perforMonitor); 69 | } 70 | 71 | private AndroidDriver newAndroidDriver(String appiumPort, DesiredCapabilities caps) { 72 | try { 73 | return new AndroidDriver(new URL("http://127.0.0.1:" + appiumPort + "/wd/hub"), caps); 74 | } catch (Exception e) { 75 | Log.e("AndroidDriver init fail."); 76 | stopServer(appiumPort); 77 | throw new IllegalStateException(e); 78 | } 79 | } 80 | 81 | private boolean isLargeThan4d4(String platformVersion) { 82 | platformVersion = platformVersion.replace(".", ""); 83 | int v = Integer.valueOf(platformVersion); 84 | String v4d4s = "44"; 85 | for (int i = 2; i < platformVersion.length(); i++) { 86 | v4d4s = v4d4s + "0"; 87 | } 88 | int v4d4 = Integer.valueOf(v4d4s); 89 | return v >= v4d4; 90 | } 91 | 92 | protected void addCap(DesiredCapabilities caps) { 93 | } 94 | 95 | @AfterTest 96 | public void tearDown() throws Exception { 97 | if (!RestartTestHelper.isNextTestRestart()) return; 98 | perforMonitorTl.get().stop(); 99 | androidDriverTl.get().quit(); 100 | } 101 | 102 | @Parameters("port") 103 | @AfterSuite 104 | public void stopServer(String port) { 105 | AppiumServer.stop(port); 106 | } 107 | 108 | public static ThreadLocal androidDriverTl = new ThreadLocal<>(); 109 | public static ThreadLocal perforMonitorTl = new ThreadLocal<>(); 110 | protected AndroidDriver driver; 111 | 112 | @BeforeClass 113 | public void findPage() { 114 | driver = androidDriverTl.get(); 115 | driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); 116 | PageFactory.initElements(driver, this); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/base/FastAuto.java: -------------------------------------------------------------------------------- 1 | package yph.base; 2 | 3 | import org.testng.TestNG; 4 | import org.testng.reporters.JUnitXMLReporter; 5 | import org.testng.xml.XmlSuite; 6 | import org.uncommons.reportng.HTMLReporter; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import yph.bean.Configure; 13 | import yph.helper.XmlSuiteBuilder; 14 | import yph.listener.AnnotationListener; 15 | import yph.listener.TestResultListener; 16 | import yph.utils.ApkUtil; 17 | import yph.utils.CmdUtil; 18 | import yph.utils.Log; 19 | 20 | public class FastAuto { 21 | public static void run(Configure configure) { 22 | CmdUtil.get().init(configure.getAppPackage()); 23 | List testList = getTestList(configure); 24 | System.setProperty("org.uncommons.reportng.escape-output", "false"); 25 | TestNG testng = new TestNG(); 26 | 27 | testng.setXmlSuites(testList); 28 | testng.setUseDefaultListeners(false); 29 | testng.addListener(new AnnotationListener(configure.getRetryCount())); 30 | testng.addListener(new TestResultListener()); 31 | testng.addListener(new HTMLReporter()); 32 | testng.addListener(new JUnitXMLReporter()); 33 | testng.setSuiteThreadPoolSize(testList.size()); 34 | 35 | testng.run(); 36 | } 37 | 38 | private static List getTestList(Configure configure) { 39 | List testList = new ArrayList<>(); 40 | List devices = CmdUtil.get().getDevices(); 41 | for (int i = 0; i < devices.size(); i++) { 42 | String deviceUdid = devices.get(i); 43 | // checkUpdate(deviceName,configure); 44 | testList.add(new XmlSuiteBuilder(i, 45 | deviceUdid, 46 | CmdUtil.get().getDeviceName(deviceUdid), 47 | CmdUtil.get().getPlatformVersion(deviceUdid), 48 | configure) 49 | .build()); 50 | CmdUtil.get().clearLog(deviceUdid); 51 | } 52 | return testList; 53 | } 54 | 55 | private static void checkUpdate(String deviceName,Configure configure){ 56 | String apkPath = configure.getApkPath(); 57 | if(!apkPath.equals("")) { 58 | Map map = ApkUtil.readApk(apkPath); 59 | int newV = (int) map.get("versionCode"); 60 | int curV = CmdUtil.get().getVersionCode(deviceName); 61 | Log.i("curV:"+curV + " newV:"+newV); 62 | if(newV > curV){ 63 | // RuntimeUtil.installApk(configure.getAdb(),device,configure.getApkPath()); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/bean/Configure.java: -------------------------------------------------------------------------------- 1 | package yph.bean; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by yph on 2018/3/21. 8 | */ 9 | 10 | public class Configure { 11 | private String node = "node"; 12 | private String appiumMainJs = ""; 13 | private String appPackage = ""; 14 | private String appActivity = ""; 15 | private String apkPath = ""; 16 | private int retryCount = 0; 17 | private List testBeans = new ArrayList(); 18 | private static Configure configure = new Configure(); 19 | 20 | public List getTestBeans() { 21 | return testBeans; 22 | } 23 | 24 | public Configure addTestBean(TestBean testBean) { 25 | testBeans.add(testBean); 26 | return this; 27 | } 28 | 29 | private Configure(){} 30 | 31 | public static Configure get(){ 32 | return configure; 33 | } 34 | 35 | public Configure setNode(String node) { 36 | this.node = node; 37 | return this; 38 | } 39 | 40 | public Configure setAppiumMainJs(String appiumMainJs) { 41 | this.appiumMainJs = appiumMainJs; 42 | return this; 43 | } 44 | 45 | public Configure setAppPackage(String appPackage) { 46 | this.appPackage = appPackage; 47 | return this; 48 | } 49 | 50 | public Configure setAppActivity(String appActivity) { 51 | this.appActivity = appActivity; 52 | return this; 53 | } 54 | 55 | public Configure setApkPath(String apkPath) { 56 | this.apkPath = apkPath; 57 | return this; 58 | } 59 | 60 | 61 | public String getNode() { 62 | return node; 63 | } 64 | 65 | public String getAppiumMainJs() { 66 | return appiumMainJs; 67 | } 68 | 69 | public String getAppPackage() { 70 | return appPackage; 71 | } 72 | 73 | public String getAppActivity() { 74 | return appActivity; 75 | } 76 | 77 | public String getApkPath() { 78 | return apkPath; 79 | } 80 | 81 | public int getRetryCount() { 82 | return retryCount; 83 | } 84 | 85 | public Configure setRetryCount(int retryCount) { 86 | this.retryCount = retryCount; 87 | return this; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/bean/TestBean.java: -------------------------------------------------------------------------------- 1 | package yph.bean; 2 | 3 | /** 4 | * Created by yph on 2018/3/23. 5 | */ 6 | 7 | public class TestBean { 8 | private String name; 9 | private Class[] classes; 10 | private boolean restart = true; 11 | 12 | public TestBean() { 13 | } 14 | 15 | public TestBean(String name, Class[] classes) { 16 | this.name = name; 17 | this.classes = classes; 18 | } 19 | 20 | public String getName() { 21 | return name; 22 | } 23 | 24 | public TestBean setName(String name) { 25 | this.name = name; 26 | return this; 27 | } 28 | 29 | public Class[] getClasses() { 30 | return classes; 31 | } 32 | 33 | public TestBean setClasses(Class[] classes) { 34 | this.classes = classes; 35 | return this; 36 | } 37 | 38 | public boolean isRestart() { 39 | return restart; 40 | } 41 | 42 | public TestBean notRestart() { 43 | restart = false; 44 | return this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/constant/Constant.java: -------------------------------------------------------------------------------- 1 | package yph.constant; 2 | 3 | /** 4 | * Created by _yph on 2018/4/12 0012. 5 | */ 6 | 7 | public class Constant { 8 | public static final String GET_DEVICES = "Get Devices"; 9 | public static final String CHECK_ANR = "Check ANR"; 10 | public static final String CHECK_CRASH = "Check Crash"; 11 | 12 | public static final String ENTRY = "entry"; 13 | public static final String APP_NOT_STARTING ="App Not Starting"; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/filter/AdbFilter.java: -------------------------------------------------------------------------------- 1 | package yph.filter; 2 | 3 | /** 4 | * Created by _yph on 2018/5/12 0012. 5 | */ 6 | 7 | public class AdbFilter implements Filter { 8 | 9 | private static String filter[] = new String[]{"List of devices attached", "offline", "unauthorized", "adb server version", 10 | "daemon not running", "adb server is out of date", "daemon started successfully", "not found", "Failed to" 11 | , "No such file or directory"}; 12 | 13 | private static AdbFilter adbFilter = new AdbFilter(); 14 | 15 | public static Filter get() { 16 | return adbFilter; 17 | } 18 | 19 | @Override 20 | public boolean filt(String line) { 21 | if (line.trim().equals("")) return false; 22 | boolean b = true; 23 | for (String filt : filter) { 24 | if (line.contains(filt)) { 25 | b = false; 26 | break; 27 | } 28 | } 29 | return b; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/filter/AnrFilter.java: -------------------------------------------------------------------------------- 1 | package yph.filter; 2 | 3 | import yph.utils.Log; 4 | 5 | /** 6 | * Created by _yph on 2018/5/12 0012. 7 | */ 8 | 9 | public class AnrFilter extends AdbFilter { 10 | boolean isFind = false; 11 | boolean neverFind = false; 12 | 13 | @Override 14 | public boolean filt(String line) { 15 | if (super.filt(line)) { 16 | if(neverFind)return false; 17 | if(isFind && line.contains("\"")){ 18 | neverFind = true; 19 | return false; 20 | } 21 | if (!line.contains("at ")) return false; 22 | if (line.contains("at android.") || line.contains("at java.") 23 | || line.contains("at com.android.") || line.contains("at 2")) { 24 | return false; 25 | } 26 | isFind = true; 27 | Log.e(line); 28 | return true; 29 | } 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/filter/CpuFilter.java: -------------------------------------------------------------------------------- 1 | package yph.filter; 2 | 3 | import yph.bean.Configure; 4 | 5 | /** 6 | * Created by _yph on 2018/5/12 0012. 7 | */ 8 | 9 | public class CpuFilter extends AdbFilter { 10 | private static CpuFilter cpuFilter = new CpuFilter(); 11 | 12 | public static Filter get() { 13 | return cpuFilter; 14 | } 15 | @Override 16 | public boolean filt(String line) { 17 | if (super.filt(line)) { 18 | if (!line.contains(Configure.get().getAppPackage() + ":")) { 19 | return true; 20 | } 21 | } 22 | return false; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/filter/CrashFilter.java: -------------------------------------------------------------------------------- 1 | package yph.filter; 2 | 3 | import static yph.base.BaseTest.perforMonitorTl; 4 | 5 | /** 6 | * Created by _yph on 2018/5/12 0012. 7 | */ 8 | 9 | public class CrashFilter extends AdbFilter { 10 | 11 | @Override 12 | public boolean filt(String line) { 13 | if (super.filt(line)) { 14 | int pid = perforMonitorTl.get().pid; 15 | if (line.contains("" + pid)) { 16 | if (line.contains("at ") || line.contains("Exception:")) { 17 | if (line.contains("at android.") || line.contains("at java.") || 18 | line.contains("at com.android.") || line.contains("at dalvik.")) 19 | return false; 20 | return true; 21 | } 22 | } 23 | } 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/filter/Filter.java: -------------------------------------------------------------------------------- 1 | package yph.filter; 2 | 3 | /** 4 | * Created by _yph on 2018/5/12 0012. 5 | */ 6 | 7 | public interface Filter { 8 | boolean filt(String line); 9 | } 10 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/helper/RestartTestHelper.java: -------------------------------------------------------------------------------- 1 | package yph.helper; 2 | 3 | import yph.bean.Configure; 4 | import yph.bean.TestBean; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by _yph on 2018/5/6 0006. 11 | */ 12 | 13 | public class RestartTestHelper { 14 | 15 | private static ThreadLocal> restartTestListTl = new ThreadLocal<>(); 16 | 17 | private static List getRestartTestList() { 18 | if (restartTestListTl.get() == null) { 19 | List restartTestList = new ArrayList<>(); 20 | List testBeans = Configure.get().getTestBeans(); 21 | for (int i = 0 ; i< testBeans.size();i++) { 22 | if(i == 0) 23 | restartTestList.add(true); 24 | else 25 | restartTestList.add(testBeans.get(i).isRestart()); 26 | } 27 | restartTestListTl.set(restartTestList); 28 | } 29 | return restartTestListTl.get(); 30 | } 31 | 32 | public static boolean isCurTestRestart() { 33 | List restartTestList = getRestartTestList(); 34 | if(restartTestList.isEmpty()) return true; 35 | boolean isCurTestRestart = restartTestList.get(0); 36 | restartTestList.remove(0); 37 | return isCurTestRestart; 38 | } 39 | 40 | public static boolean isNextTestRestart() { 41 | List restartTestList = getRestartTestList(); 42 | if(restartTestList.isEmpty()) return true; 43 | boolean isCurTestRestart = restartTestList.get(0); 44 | return isCurTestRestart; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/helper/XmlSuiteBuilder.java: -------------------------------------------------------------------------------- 1 | package yph.helper; 2 | 3 | import org.testng.xml.XmlClass; 4 | import org.testng.xml.XmlSuite; 5 | import org.testng.xml.XmlTest; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | import yph.base.BaseTest; 13 | import yph.bean.Configure; 14 | import yph.bean.TestBean; 15 | 16 | 17 | /** 18 | * Created by yph on 2018/3/21. 19 | */ 20 | 21 | public class XmlSuiteBuilder { 22 | 23 | List mTests = new ArrayList<>(); 24 | Map parameters = new HashMap<>(); 25 | XmlSuite xmlSuite = new XmlSuite(); 26 | 27 | public XmlSuiteBuilder(int index, String deviceUdid, String deviceName, String platformVersion, Configure configure) { 28 | parameters.put("port",(4723+2*index)+""); 29 | parameters.put("bootstrap_port",(4724+2*index)+""); 30 | parameters.put("chromedriver_port",(9515+index)+""); 31 | parameters.put("udid", deviceUdid); 32 | parameters.put("platformName","Android"); 33 | parameters.put("platformVersion",platformVersion); 34 | parameters.put("deviceName", deviceName); 35 | parameters.put("node", configure.getNode()); 36 | parameters.put("appiumMainJs", configure.getAppiumMainJs()); 37 | parameters.put("appPackage", configure.getAppPackage()); 38 | parameters.put("appActivity", configure.getAppActivity()); 39 | parameters.put("app",configure.getApkPath()); 40 | 41 | parseTestBeans(configure.getTestBeans()); 42 | 43 | xmlSuite.setName(deviceName); 44 | xmlSuite.setTests(mTests); 45 | } 46 | 47 | 48 | private void parseTestBeans(List testBeans){ 49 | for(TestBean testBean : testBeans){ 50 | String testName = testBean.getName(); 51 | Class[] testClasses = testBean.getClasses(); 52 | List xmlClassListt = new ArrayList<>(); 53 | for (Class testClass : testClasses) { 54 | if(!testClass.getSuperclass().equals(BaseTest.class)){ 55 | throw new IllegalArgumentException(testClass.getSimpleName() +" should extends BaseTest"); 56 | } 57 | xmlClassListt.add(new XmlClass(testClass)); 58 | } 59 | XmlTest xmlTest = new XmlTest(); 60 | xmlTest.setName(testName); 61 | xmlTest.setClasses(xmlClassListt); 62 | xmlTest.setXmlSuite(xmlSuite); 63 | mTests.add(xmlTest); 64 | } 65 | } 66 | 67 | public XmlSuite build() { 68 | xmlSuite.setParameters(parameters); 69 | return xmlSuite; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/listener/AnnotationListener.java: -------------------------------------------------------------------------------- 1 | package yph.listener; 2 | 3 | import org.testng.IAnnotationTransformer; 4 | import org.testng.IRetryAnalyzer; 5 | import org.testng.annotations.ITestAnnotation; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.lang.reflect.Method; 9 | 10 | /** 11 | * Created by _yph on 2018/4/15 0015. 12 | */ 13 | 14 | public class AnnotationListener implements IAnnotationTransformer { 15 | 16 | public AnnotationListener(int maxRetryCount) { 17 | TestRetryListener.maxRetryCount = maxRetryCount; 18 | } 19 | 20 | @Override 21 | public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { 22 | IRetryAnalyzer retry = annotation.getRetryAnalyzer(); 23 | if (retry == null) { 24 | annotation.setRetryAnalyzer(TestRetryListener.class); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/listener/TestResultListener.java: -------------------------------------------------------------------------------- 1 | package yph.listener; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.openqa.selenium.OutputType; 5 | import org.testng.ITestContext; 6 | import org.testng.ITestResult; 7 | import org.testng.Reporter; 8 | import org.testng.TestListenerAdapter; 9 | 10 | import java.io.File; 11 | import java.util.Arrays; 12 | import java.util.Iterator; 13 | import java.util.List; 14 | 15 | import yph.base.BaseTest; 16 | 17 | public class TestResultListener extends TestListenerAdapter { 18 | 19 | @Override 20 | public void onTestFailure(ITestResult tr) { 21 | saveScreenShot(tr); 22 | super.onTestFailure(tr); 23 | } 24 | 25 | @Override 26 | public void onTestSkipped(ITestResult tr) { 27 | super.onTestSkipped(tr); 28 | } 29 | 30 | @Override 31 | public void onTestSuccess(ITestResult tr) { 32 | super.onTestSuccess(tr); 33 | } 34 | 35 | @Override 36 | public void onTestStart(ITestResult tr) { 37 | super.onTestStart(tr); 38 | } 39 | 40 | @Override 41 | public void onFinish(ITestContext testContext) { 42 | super.onFinish(testContext); 43 | Iterator skippedTests = testContext.getSkippedTests().getAllResults().iterator(); 44 | while (skippedTests.hasNext()) { 45 | ITestResult skippedTest = skippedTests.next(); 46 | int skipRunCount = 1; 47 | List list = Reporter.getOutput(skippedTest); 48 | for (String s : list) { 49 | if (s.contains("RunCount=")) {//获取skipcount 50 | skipRunCount = Integer.valueOf(s.replace("RunCount=", "")); 51 | } 52 | } 53 | if (skipRunCount == TestRetryListener.maxRetryCount + 1) {//找到最后一条skip 54 | for (ITestResult passedTest : testContext.getPassedTests().getAllResults()) {//如果pass了 55 | if (getId(skippedTest) == getId(passedTest)) {//那就把skipcount赋值给passcount 56 | Reporter.setCurrentTestResult(passedTest); 57 | } 58 | } 59 | for (ITestResult failTest : testContext.getFailedTests().getAllResults()) {//如果fail了 60 | if (getId(skippedTest) == getId(failTest)) {//那就把skipcount赋值给failcount 61 | Reporter.setCurrentTestResult(failTest); 62 | } 63 | } 64 | Reporter.log("RunCount=" + skipRunCount); 65 | } 66 | skippedTests.remove(); 67 | } 68 | } 69 | 70 | private int getId(ITestResult result) { 71 | int id = result.getTestClass().getName().hashCode(); 72 | id = id + result.getMethod().getMethodName().hashCode(); 73 | id = id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0); 74 | return id; 75 | } 76 | 77 | private void saveScreenShot(ITestResult tr) { 78 | String filePath = new StringBuilder("test-output/html/screenshot/") 79 | .append(tr.getTestContext().getSuite().getParameter("deviceName")) 80 | .append("/") 81 | .append(tr.getTestClass().getName().replace(".", "_")) 82 | .append("_") 83 | .append(tr.getName()) 84 | .append(".png") 85 | .toString(); 86 | File destFile = new File(filePath); 87 | try { 88 | File screenshot = BaseTest.androidDriverTl.get().getScreenshotAs(OutputType.FILE); 89 | FileUtils.copyFile(screenshot, destFile); 90 | } catch (Exception e) { 91 | Reporter.log("截图失败:" + e.toString()); 92 | return; 93 | } 94 | if (destFile.exists()) { 95 | Reporter.setCurrentTestResult(tr); 96 | Reporter.log(""); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/listener/TestRetryListener.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/fastautotest/src/main/java/yph/listener/TestRetryListener.java -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/performance/Device.java: -------------------------------------------------------------------------------- 1 | package yph.performance; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Created by _yph on 2018/4/15 0015. 8 | */ 9 | 10 | public class Device { 11 | public static List deviceList = new ArrayList<>(); 12 | private String name; 13 | private List cpuList = new ArrayList<>(); 14 | private List memList = new ArrayList<>(); 15 | private List trafficList = new ArrayList<>(); 16 | private List curStackList = new ArrayList<>(); 17 | 18 | public Device() { 19 | } 20 | 21 | public Device(String name) { 22 | this.name = name; 23 | } 24 | 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | public Device setName(String name) { 30 | this.name = name; 31 | return this; 32 | } 33 | 34 | public List getCpuList() { 35 | return cpuList; 36 | } 37 | 38 | public Device setCpu(int cpu) { 39 | cpuList.add(cpu); 40 | return this; 41 | } 42 | 43 | public List getMemList() { 44 | return memList; 45 | } 46 | 47 | public Device setMem(int mem) { 48 | memList.add(mem); 49 | return this; 50 | } 51 | 52 | public List getTrafficList() { 53 | return trafficList; 54 | } 55 | 56 | public Device setTraffic(long traffic) { 57 | trafficList.add(traffic); 58 | return this; 59 | } 60 | 61 | public List getCurStackList() { 62 | return curStackList; 63 | } 64 | 65 | public Device setCurStack(String curStack) { 66 | curStackList.add("'"+curStack+"'"); 67 | return this; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/performance/PerforMonitor.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/fastautotest/src/main/java/yph/performance/PerforMonitor.java -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/utils/ApkUtil.java: -------------------------------------------------------------------------------- 1 | package yph.utils; 2 | 3 | import android.content.res.AXmlResourceParser; 4 | import android.util.TypedValue; 5 | 6 | import org.xmlpull.v1.XmlPullParser; 7 | 8 | import java.util.Enumeration; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | import java.util.zip.ZipEntry; 12 | import java.util.zip.ZipFile; 13 | 14 | /** 15 | * Created by yph on 2018/3/23. 16 | */ 17 | 18 | public class ApkUtil { 19 | 20 | public static Map readApk(String apkUrl) { 21 | ZipFile zipFile; 22 | Map map = new HashMap(); 23 | try { 24 | zipFile = new ZipFile(apkUrl); 25 | Enumeration enumeration = zipFile.entries(); 26 | ZipEntry zipEntry = null; 27 | while (enumeration.hasMoreElements()) { 28 | zipEntry = (ZipEntry) enumeration.nextElement(); 29 | if (zipEntry.isDirectory()) { 30 | 31 | } else { 32 | if ("androidmanifest.xml".equals(zipEntry.getName().toLowerCase())) { 33 | AXmlResourceParser parser = new AXmlResourceParser(); 34 | parser.open(zipFile.getInputStream(zipEntry)); 35 | while (true) { 36 | int type = parser.next(); 37 | if (type == XmlPullParser.END_DOCUMENT) { 38 | break; 39 | } 40 | String name = parser.getName(); 41 | if (null != name && name.toLowerCase().equals("manifest")) { 42 | for (int i = 0; i != parser.getAttributeCount(); i++) { 43 | if ("versionName".equals(parser.getAttributeName(i))) { 44 | String versionName = getAttributeValue(parser, i); 45 | if (null == versionName) { 46 | versionName = ""; 47 | } 48 | map.put("versionName", versionName); 49 | } else if ("package".equals(parser.getAttributeName(i))) { 50 | String packageName = getAttributeValue(parser, i); 51 | if (null == packageName) { 52 | packageName = ""; 53 | } 54 | map.put("package", packageName); 55 | } else if ("versionCode".equals(parser.getAttributeName(i))) { 56 | String versionCode = getAttributeValue(parser, i); 57 | if (null == versionCode) { 58 | versionCode = ""; 59 | map.put("versionCode", versionCode); 60 | }else { 61 | map.put("versionCode", Integer.valueOf(versionCode)); 62 | } 63 | 64 | } 65 | } 66 | break; 67 | } 68 | } 69 | } 70 | 71 | } 72 | } 73 | zipFile.close(); 74 | } catch (Exception e) { 75 | map.put("code", "fail"); 76 | map.put("error", "读取apk失败"); 77 | } 78 | return map; 79 | } 80 | 81 | private static String getAttributeValue(AXmlResourceParser parser, int index) { 82 | int type = parser.getAttributeValueType(index); 83 | int data = parser.getAttributeValueData(index); 84 | if (type == TypedValue.TYPE_STRING) { 85 | return parser.getAttributeValue(index); 86 | } 87 | if (type == TypedValue.TYPE_ATTRIBUTE) { 88 | return String.format("?%s%08X", getPackage(data), data); 89 | } 90 | if (type == TypedValue.TYPE_REFERENCE) { 91 | return String.format("@%s%08X", getPackage(data), data); 92 | } 93 | if (type == TypedValue.TYPE_FLOAT) { 94 | return String.valueOf(Float.intBitsToFloat(data)); 95 | } 96 | if (type == TypedValue.TYPE_INT_HEX) { 97 | return String.format("0x%08X", data); 98 | } 99 | if (type == TypedValue.TYPE_INT_BOOLEAN) { 100 | return data != 0 ? "true" : "false"; 101 | } 102 | if (type == TypedValue.TYPE_DIMENSION) { 103 | return Float.toString(complexToFloat(data)) + DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK]; 104 | } 105 | if (type == TypedValue.TYPE_FRACTION) { 106 | return Float.toString(complexToFloat(data)) + FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK]; 107 | } 108 | if (type >= TypedValue.TYPE_FIRST_COLOR_INT && type <= TypedValue.TYPE_LAST_COLOR_INT) { 109 | return String.format("#%08X", data); 110 | } 111 | if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) { 112 | return String.valueOf(data); 113 | } 114 | return String.format("<0x%X, type 0x%02X>", data, type); 115 | } 116 | 117 | private static String getPackage(int id) { 118 | if (id >>> 24 == 1) { 119 | return "android:"; 120 | } 121 | return ""; 122 | } 123 | 124 | // ///////////////////////////////// ILLEGAL STUFF, DONT LOOK :) 125 | public static float complexToFloat(int complex) { 126 | return (float) (complex & 0xFFFFFF00) * RADIX_MULTS[(complex >> 4) & 3]; 127 | } 128 | 129 | private static final float RADIX_MULTS[] = 130 | { 131 | 0.00390625F, 3.051758E-005F, 132 | 1.192093E-007F, 4.656613E-010F 133 | }; 134 | private static final String DIMENSION_UNITS[] = {"px", "dip", "sp", "pt", "in", "mm", "", ""}; 135 | private static final String FRACTION_UNITS[] = {"%", "%p", "", "", "", "", "", ""}; 136 | 137 | } 138 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/utils/CmdUtil.java: -------------------------------------------------------------------------------- 1 | package yph.utils; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.Timer; 7 | import java.util.TimerTask; 8 | 9 | import yph.constant.Constant; 10 | import yph.filter.AnrFilter; 11 | import yph.filter.CpuFilter; 12 | import yph.filter.CrashFilter; 13 | 14 | import static yph.utils.RuntimeUtil.execAsync; 15 | 16 | /** 17 | * Created by _yph on 2018/3/15 0015. 18 | */ 19 | 20 | public class CmdUtil { 21 | 22 | private CmdUtil() { 23 | } 24 | 25 | public static CmdUtil get() { 26 | return Singleton.instance; 27 | } 28 | 29 | private static class Singleton { 30 | static CmdUtil instance = new CmdUtil(); 31 | } 32 | 33 | private final String adb = SystemEnvUtil.getCopyAdb(); 34 | // private final String adb = SystemEnvUtil.getResCopyAdb(); 35 | private String pkgName; 36 | 37 | public void init(String pkgName) { 38 | this.pkgName = pkgName; 39 | } 40 | 41 | public List getDevices() { 42 | List devices = new ArrayList<>(); 43 | List results = RuntimeUtil.exec(adb + " devices", Constant.GET_DEVICES); 44 | Log.i(results); 45 | if (results.size() > 0) { 46 | for (int i = 0; i < results.size(); i++) { 47 | String deviceUdid = results.get(i); 48 | deviceUdid = deviceUdid.substring(0, deviceUdid.indexOf("\t")); 49 | devices.add(deviceUdid); 50 | } 51 | } else { 52 | throw new IllegalStateException("Can't find devices"); 53 | } 54 | return devices; 55 | } 56 | 57 | public String getPlatformVersion(String deviceUdid) { 58 | List results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell getprop ro.build.version.release", "Get " + deviceUdid + " Platform Version"); 59 | Log.i(results); 60 | return results.get(0); 61 | } 62 | 63 | public int getPlatformVersionSdk(String deviceUdid) { 64 | int sdkNum = Integer.parseInt(RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell getprop ro.build.version.sdk", "").get(0)); 65 | return sdkNum; 66 | } 67 | 68 | public void installApk(String deviceUdid, String apkPath) { 69 | RuntimeUtil.exec(adb + " -s " + deviceUdid + " install " + apkPath, "Install App"); 70 | } 71 | 72 | public int getVersionCode(String deviceUdid) { 73 | List results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell dumpsys package " + pkgName + " | findstr versionCode"); 74 | String versionCode = "0"; 75 | if (results.size() > 0) { 76 | versionCode = results.get(0); 77 | versionCode = versionCode.substring(versionCode.indexOf("versionCode="), versionCode.indexOf(" targetSdk")); 78 | } 79 | return Integer.valueOf(versionCode); 80 | } 81 | 82 | public String getDeviceName(String deviceUdid) { 83 | List results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell getprop ro.product.model", "Get " + deviceUdid + " Device`s Name"); 84 | Log.i(results); 85 | return results.get(0); 86 | } 87 | 88 | public Timer getCpu(String deviceUdid, RuntimeUtil.AsyncInvoke asyncInvoke) { 89 | return execAsync(adb + " -s " + deviceUdid + " shell top -n 1 -s cpu|grep " + pkgName, asyncInvoke, 1, CpuFilter.get()); 90 | } 91 | 92 | public CpuSnapshot getCpu(String deviceUdid , int pid) { 93 | try { 94 | String sysCpu = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell cat /proc/stat |head -n 1").get(0); 95 | String[] cpuInfoArray = sysCpu.split(" "); 96 | if (cpuInfoArray.length < 9) { 97 | throw new IllegalStateException("cpu info array size must great than 9"); 98 | } 99 | long user = Long.parseLong(cpuInfoArray[2]); 100 | long nice = Long.parseLong(cpuInfoArray[3]); 101 | long system = Long.parseLong(cpuInfoArray[4]); 102 | long idle = Long.parseLong(cpuInfoArray[5]); 103 | long ioWait = Long.parseLong(cpuInfoArray[6]); 104 | long total = user + nice + system + idle + ioWait 105 | + Long.parseLong(cpuInfoArray[7]) 106 | + Long.parseLong(cpuInfoArray[8]); 107 | String appCpu = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell cat /proc/" + pid + "/stat |head -n 1").get(0); 108 | String[] pidCpuInfoList = appCpu.split(" "); 109 | if (pidCpuInfoList.length < 17) { 110 | throw new IllegalStateException("pid cpu info array size must great than 17"); 111 | } 112 | long appCpuTime = Long.parseLong(pidCpuInfoList[13]) 113 | + Long.parseLong(pidCpuInfoList[14]) 114 | + Long.parseLong(pidCpuInfoList[15])//有些不要15,16 待考究 115 | + Long.parseLong(pidCpuInfoList[16]);//有些不要15,16 待考究 116 | return new CpuSnapshot(user, system, idle, ioWait, total, appCpuTime); 117 | }catch (Exception e){ 118 | return null; 119 | } 120 | } 121 | 122 | public long getTraffic(String deviceUdid, int uid) { 123 | long rcvTraffic = -1, sndTraffic = -1; 124 | List results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell cat /proc/uid_stat/" + uid + "/tcp_rcv"); 125 | if (!results.isEmpty()) { 126 | rcvTraffic = Long.parseLong(results.get(0)); 127 | } 128 | results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell cat /proc/uid_stat/" + uid + "/tcp_snd"); 129 | if (!results.isEmpty()) { 130 | sndTraffic = Long.parseLong(results.get(0)); 131 | } 132 | return rcvTraffic + sndTraffic < 0 ? -1 : rcvTraffic + sndTraffic; 133 | } 134 | 135 | public int getMem(String deviceUdid) { 136 | List results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell dumpsys meminfo " + pkgName + "|grep TOTAL "); 137 | if (results.size() > 0) { 138 | String mem = results.get(0).replace("TOTAL", ""); 139 | return Integer.valueOf(getWordBetweenBlank(mem)) / 1000; 140 | } 141 | return -1; 142 | } 143 | 144 | public List getCrashLog(String deviceUdid) { 145 | List results = new ArrayList<>(); 146 | try { 147 | final Process process = Runtime.getRuntime().exec(adb + " -s " + deviceUdid + " shell logcat -v process *:E"); 148 | final Timer timer = new Timer(); 149 | timer.schedule(new TimerTask() { 150 | @Override 151 | public void run() { 152 | process.destroy(); 153 | timer.cancel(); 154 | } 155 | }, 500); 156 | results.addAll(RuntimeUtil.exec(process, Constant.CHECK_CRASH, new CrashFilter())); 157 | CmdUtil.get().clearLog(deviceUdid); 158 | } catch (IOException e) { 159 | e.printStackTrace(); 160 | } 161 | return results; 162 | } 163 | 164 | public void clearLog(String deviceUdid) { 165 | RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell logcat -c"); 166 | } 167 | 168 | public String getAnrLog(String deviceUdid) { 169 | String AnrLog = ""; 170 | List results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell cat /data/anr/traces.txt | grep -B 1 'Cmd line: " + pkgName + "'", Constant.CHECK_ANR); 171 | if (results.size() > 0) { 172 | String time = results.get(0); 173 | time = time.substring(time.indexOf("at") + 3, time.lastIndexOf(":") + 3); 174 | long l = TimeUtil.timeSubtract(time); 175 | if (l != -1 && l < 2) {//2分钟之内的anr 176 | results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell cat /data/anr/traces.txt", "", new AnrFilter()); 177 | for (String s : results) { 178 | AnrLog = AnrLog + s + "\n"; 179 | } 180 | } 181 | } 182 | return AnrLog; 183 | } 184 | 185 | public String getWordBetweenBlank(String mem) { 186 | String menTem = ""; 187 | char[] chars = mem.toCharArray(); 188 | boolean b = false; 189 | for (char c : chars) { 190 | if (c == ' ') { 191 | if (b) { 192 | break; 193 | } 194 | } else { 195 | menTem = menTem + c; 196 | b = true; 197 | } 198 | } 199 | return menTem.trim(); 200 | } 201 | 202 | public String execPs(String deviceUdid) { 203 | try { 204 | List results = RuntimeUtil.exec(adb + " -s " + deviceUdid + " shell ps " + (getPlatformVersionSdk(deviceUdid) > 25 ? "-A " : "") + 205 | "|grep " + pkgName + " |grep -v :"); 206 | return results.get(0); 207 | } catch (Exception e) { 208 | return ""; 209 | } 210 | } 211 | 212 | public boolean isProcessRunning(String keyMsg) { 213 | boolean running; 214 | List results = RuntimeUtil.exec("cmd.exe /c netstat -ano|findstr " + keyMsg); 215 | if (results.isEmpty()) { 216 | running = false; 217 | } else { 218 | running = true; 219 | } 220 | return running; 221 | } 222 | 223 | public void killProcessIfExist(String keyMsg) { 224 | List results = RuntimeUtil.exec("cmd.exe /c netstat -ano|findstr " + keyMsg); 225 | if (!results.isEmpty()) { 226 | RuntimeUtil.exec("cmd.exe /c taskkill /f /pid " + results.get(0).substring(results.get(0).lastIndexOf(" "))); 227 | } 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/utils/CpuSnapshot.java: -------------------------------------------------------------------------------- 1 | package yph.utils; 2 | 3 | /** 4 | * Created by _yph on 2019/3/2 0002. 5 | */ 6 | 7 | public class CpuSnapshot { 8 | 9 | public long user = 0; 10 | public long system = 0; 11 | public long idle = 0; 12 | public long ioWait = 0; 13 | public long total = 0; 14 | public long app = 0; 15 | 16 | 17 | public CpuSnapshot(long user, long system, long idle, long ioWait, long total, long app) { 18 | this.user = user; 19 | this.system = system; 20 | this.idle = idle; 21 | this.ioWait = ioWait; 22 | this.total = total; 23 | this.app = app; 24 | } 25 | 26 | 27 | } -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/utils/Log.java: -------------------------------------------------------------------------------- 1 | package yph.utils; 2 | 3 | import java.io.File; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Date; 6 | 7 | public class Log { 8 | 9 | private final static boolean isDebug = new File("fastautotest").exists(); 10 | 11 | public static void i(Object o) { 12 | System.out.println(getCurTime() + o); 13 | } 14 | 15 | public static void d(Object o) { 16 | if(isDebug) 17 | System.err.println(getCurTime() + o); 18 | } 19 | 20 | public static void e(Object o) { 21 | System.err.println(getCurTime() + o); 22 | } 23 | 24 | private static String getCurTime() { 25 | return new SimpleDateFormat("MM/dd HH:mm:ss:SSS ").format(new Date(System.currentTimeMillis())); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/utils/RuntimeUtil.java: -------------------------------------------------------------------------------- 1 | package yph.utils; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Timer; 10 | import java.util.TimerTask; 11 | 12 | import yph.constant.Constant; 13 | import yph.filter.AdbFilter; 14 | import yph.filter.Filter; 15 | 16 | public class RuntimeUtil { 17 | 18 | public static List exec(String cmd) { 19 | return exec(cmd, "", AdbFilter.get()); 20 | } 21 | 22 | public static List exec(String cmd, String log) { 23 | return exec(cmd, log, AdbFilter.get()); 24 | } 25 | 26 | public static List exec(String cmd, String log, Filter filter) { 27 | Process process = null; 28 | try { 29 | process = Runtime.getRuntime().exec(cmd); 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | } 33 | List list = exec(process, log, filter); 34 | return list; 35 | } 36 | 37 | public static List exec(Process process, String log) { 38 | return exec(process, log, AdbFilter.get()); 39 | } 40 | 41 | public static List exec(Process process, String log, Filter filter) { 42 | if (log != null && !log.equals("")) 43 | Log.i(log); 44 | List list = new ArrayList<>(); 45 | try { 46 | InputStream inputStream = process.getInputStream(); 47 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 48 | String line; 49 | while ((line = reader.readLine()) != null) { 50 | if (filter.filt(line)) { 51 | list.add(line); 52 | } 53 | } 54 | process.waitFor(); 55 | inputStream.close(); 56 | reader.close(); 57 | process.destroy(); 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | Log.e("出错:" + e.getMessage()); 61 | } 62 | return list; 63 | } 64 | 65 | 66 | public static Timer execAsync(final String cmd, final AsyncInvoke syncInvoke, int interval, final Filter filter) { 67 | Timer timer = new Timer(); 68 | TimerTask timerTask = new TimerTask() { 69 | @Override 70 | public void run() { 71 | try { 72 | // Log.d("cpu start"); 73 | // Log.d("exec " + cmd); 74 | Process process = Runtime.getRuntime().exec(cmd); 75 | InputStream inputStream = process.getInputStream(); 76 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 77 | String line; 78 | boolean isGetCpu = false; 79 | while ((line = reader.readLine()) != null) { 80 | if (filter.filt(line)) { 81 | isGetCpu = true; 82 | if (syncInvoke != null) syncInvoke.invoke(line); 83 | } 84 | } 85 | if (!isGetCpu && syncInvoke != null) { 86 | syncInvoke.invoke(Constant.APP_NOT_STARTING); 87 | } 88 | process.waitFor(); 89 | inputStream.close(); 90 | reader.close(); 91 | process.destroy(); 92 | } catch (Exception e) { 93 | e.printStackTrace(); 94 | Log.e("出错:" + e.getMessage()); 95 | } 96 | } 97 | }; 98 | if (interval > 0) 99 | timer.schedule(timerTask, 0, interval); 100 | else 101 | timer.schedule(timerTask, 0); 102 | return timer; 103 | } 104 | 105 | public interface AsyncInvoke { 106 | void invoke(String line); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/utils/SleepUtil.java: -------------------------------------------------------------------------------- 1 | package yph.utils; 2 | 3 | /** 4 | * Created by _yph on 2018/3/25 0025. 5 | */ 6 | 7 | public class SleepUtil { 8 | public static void s(long ms){ 9 | try { 10 | Thread.sleep(ms); 11 | } catch (InterruptedException e) { 12 | e.printStackTrace(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/utils/SystemEnvUtil.java: -------------------------------------------------------------------------------- 1 | package yph.utils; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | 5 | import java.io.*; 6 | import java.util.Arrays; 7 | 8 | /** 9 | * Created by _yph on 2018/5/7 0007. 10 | */ 11 | public class SystemEnvUtil { 12 | 13 | private static String[] adbFiles = {"adb.exe", "AdbWinApi.dll", "AdbWinUsbApi.dll"}; 14 | 15 | public static String getResCopyAdb() { 16 | try { 17 | for (String adbFile : adbFiles) { 18 | fileCopy("/adb/"+adbFile, "/adb/"+adbFile); 19 | } 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | throw new IllegalStateException("copy adb失败,请手动copy " + 23 | Arrays.asList(adbFiles).toString() + " 至项目根目录adb文件夹下"); 24 | } 25 | return "/adb/" + adbFiles[0]; 26 | } 27 | 28 | public static String getCopyAdb() { 29 | String destAdb = "adb/"; 30 | for (int i = 0; i < adbFiles.length; i++) { 31 | if (!new File(destAdb + adbFiles[i]).exists()) 32 | break; 33 | else if (i == adbFiles.length - 1) 34 | return destAdb + adbFiles[0]; 35 | } 36 | 37 | String oriAdb = getSystemAdb(); 38 | if (oriAdb == null) { 39 | throw new IllegalStateException("not set adb environment"); 40 | } else { 41 | copyAdb(oriAdb + "/", destAdb); 42 | } 43 | return destAdb + adbFiles[0]; 44 | } 45 | 46 | private static String getSystemAdb() { 47 | String pathString = System.getenv("Path"); 48 | String[] arr = pathString.split(";"); 49 | for (String s : arr) { 50 | if (s.contains("\\platform-tools")) { 51 | return s; 52 | } 53 | } 54 | String androidHome = System.getenv("ANDROID_HOME"); 55 | if (androidHome != null) { 56 | return androidHome + "/platform-tools"; 57 | } 58 | return null; 59 | } 60 | 61 | private static void copyAdb(String oriAdb, String destAdb) { 62 | try { 63 | for (String adbFile : adbFiles) { 64 | FileUtils.copyFile(new File(oriAdb + adbFile), new File(destAdb + adbFile)); 65 | } 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | throw new IllegalStateException("copy adb fail,请手动copy " + 69 | Arrays.asList(adbFiles).toString() + " 至项目根目录adb文件夹下"); 70 | } 71 | } 72 | 73 | public static void fileCopy(String srcFilePath, String destFilePath) throws Exception { 74 | File destFile = new File(destFilePath); 75 | FileUtils.forceMkdirParent(destFile); 76 | BufferedInputStream fis = new BufferedInputStream(ClassLoader.getSystemResourceAsStream(srcFilePath)); 77 | FileOutputStream fos = new FileOutputStream(destFile); 78 | byte[] buf = new byte[1024]; 79 | int c; 80 | while ((c = fis.read(buf)) != -1) { 81 | fos.write(buf, 0, c); 82 | } 83 | fis.close(); 84 | fos.close(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /fastautotest/src/main/java/yph/utils/TimeUtil.java: -------------------------------------------------------------------------------- 1 | package yph.utils; 2 | 3 | 4 | import java.text.DateFormat; 5 | import java.text.ParseException; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Calendar; 8 | import java.util.Date; 9 | import java.util.Locale; 10 | 11 | /** 12 | * Created by _yph on 2016/2/11 0011. 13 | */ 14 | public class TimeUtil { 15 | 16 | //format : "yyyy-MM-dd HH:mm:ss" 17 | public static String getTime(String format) { 18 | Date curDate = new Date(System.currentTimeMillis());// 获取当前时间 19 | return new SimpleDateFormat(format).format(curDate); 20 | } 21 | 22 | public static String getTime() { 23 | Date curDate = new Date(System.currentTimeMillis());// 获取当前时间 24 | return new SimpleDateFormat("yyyyMMddHHmmss").format(curDate); 25 | } 26 | 27 | public static String getTimeAndAddOne(String format, int i) { 28 | return addOne(new Date(System.currentTimeMillis()), format, i); 29 | } 30 | 31 | public static String addOne(Date curDate, String format, int i) { 32 | Calendar cal = Calendar.getInstance(); 33 | cal.setTime(curDate); 34 | cal.add(i, 1); 35 | return new SimpleDateFormat(format).format(cal.getTime()); 36 | } 37 | 38 | public static String timeP(long oldtime) { 39 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); 40 | return sdf.format(new Date(oldtime)); 41 | } 42 | 43 | public static long timeSubtract(String time2) { 44 | return timeSubtract(getTime("yyyy-MM-dd HH:mm:ss"), time2); 45 | } 46 | 47 | public static long timeSubtract(String time1, String time2) { 48 | DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 49 | try { 50 | Date d1 = df.parse(time1); 51 | Date d2 = df.parse(time2); 52 | long diff = d1.getTime() - d2.getTime();//这样得到的差值是微秒级别 53 | return diff/(1000 * 60); 54 | // System.out.println("" +diff/(1000 * 60)); 55 | // long days = diff / (1000 * 60 * 60 * 24); 56 | // long hours = (diff - days * (1000 * 60 * 60 * 24)) / (1000 * 60 * 60); 57 | // long minutes = (diff - days * (1000 * 60 * 60 * 24) - hours * (1000 * 60 * 60)) / (1000 * 60); 58 | // System.out.println("" + days + "天" + hours + "小时" + minutes + "分"); 59 | } catch (Exception e) { 60 | return -1; 61 | } 62 | } 63 | 64 | public static String getWeek(String str) { 65 | SimpleDateFormat sdf = new SimpleDateFormat("EEEE"); 66 | String week = sdf.format(parseDate(str, "yyyy-MM-dd HH:mm")); 67 | return week; 68 | } 69 | 70 | //根据日期取得星期几 71 | public static String getWeek_(String str) { 72 | SimpleDateFormat sdf = new SimpleDateFormat("EEEE"); 73 | String week = sdf.format(parseDate(str, "yyyy-MM-dd")); 74 | return week; 75 | } 76 | 77 | public static int daysOfTwo(Date fDate, Date oDate) { 78 | 79 | Calendar aCalendar = Calendar.getInstance(); 80 | 81 | aCalendar.setTime(fDate); 82 | 83 | int day1 = aCalendar.get(Calendar.DAY_OF_YEAR); 84 | 85 | aCalendar.setTime(oDate); 86 | 87 | int day2 = aCalendar.get(Calendar.DAY_OF_YEAR); 88 | 89 | return day2 - day1; 90 | 91 | } 92 | 93 | /** 94 | * String转换为时间 95 | * 96 | * @param str 97 | * @return 98 | */ 99 | public static Date parseDate(String str, String format) { 100 | SimpleDateFormat dateFormat = new SimpleDateFormat(format); 101 | Date addTime = null; 102 | try { 103 | addTime = dateFormat.parse(str); 104 | } catch (ParseException e) { 105 | e.printStackTrace(); 106 | } 107 | return addTime; 108 | } 109 | 110 | /** 111 | * 传入的时间在现在时间之前,注:不能比較只有 時分 的情況 112 | */ 113 | public static boolean isTimeBeforeNow(String str, String format) { 114 | return parseDate(str, format).before(new Date(System.currentTimeMillis())); 115 | } 116 | 117 | /** 118 | * 传入的时间在现在时间之后,注:不能比較只有 時分 的情況 119 | */ 120 | public static boolean isTimeAfterNow(String str, String format) { 121 | return parseDate(str, format).after(new Date(System.currentTimeMillis())); 122 | } 123 | 124 | /** 125 | * 将日期转换为字符串 126 | * 127 | * @param date 128 | * @return 129 | */ 130 | public static String ParseDateToString(Date date) { 131 | return ParseDateToString(date, "yyyy-MM-dd HH:mm:ss"); 132 | } 133 | 134 | /** 135 | * 将日期转换为字符串(重载) 136 | * 137 | * @param date 138 | * @param format:时间格式,必须符合yyyy-MM-dd HH:mm:ss 139 | * @return 140 | */ 141 | public static String ParseDateToString(Date date, String format) { 142 | SimpleDateFormat dateFormat = new SimpleDateFormat(format); 143 | 144 | return dateFormat.format(date); 145 | } 146 | 147 | /** 148 | * 将UMT时间转换为本地时间 149 | * 150 | * @param str 151 | * @return 152 | * @throws ParseException 153 | */ 154 | public static Date ParseUTCDate(String str) { 155 | //格式化2012-03-04T23:42:00+08:00 156 | SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.CHINA); 157 | try { 158 | Date date = formatter.parse(str); 159 | return date; 160 | } catch (ParseException e) { 161 | //格式化Sat, 17 Mar 2012 11:37:13 +0000 162 | //Sat, 17 Mar 2012 22:13:41 +0800 163 | try { 164 | SimpleDateFormat formatter2 = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.CHINA); 165 | Date date2 = formatter2.parse(str); 166 | 167 | return date2; 168 | } catch (ParseException ex) { 169 | return null; 170 | } 171 | } 172 | } 173 | 174 | /** 175 | * 将时间转换为中文 176 | * 177 | * @param datetime 178 | * @return 179 | */ 180 | public static String DateToChineseString(Date datetime) { 181 | Date today = new Date(); 182 | long seconds = (today.getTime() - datetime.getTime()) / 1000; 183 | 184 | long year = seconds / (24 * 60 * 60 * 30 * 12);// 相差年数 185 | long month = seconds / (24 * 60 * 60 * 30);//相差月数 186 | long date = seconds / (24 * 60 * 60); //相差的天数 187 | long hour = (seconds - date * 24 * 60 * 60) / (60 * 60);//相差的小时数 188 | long minute = (seconds - date * 24 * 60 * 60 - hour * 60 * 60) / (60);//相差的分钟数 189 | long second = (seconds - date * 24 * 60 * 60 - hour * 60 * 60 - minute * 60);//相差的秒数 190 | 191 | if (year > 0) { 192 | return year + "年前"; 193 | } 194 | if (month > 0) { 195 | return month + "月前"; 196 | } 197 | if (date > 0) { 198 | return date + "天前"; 199 | } 200 | if (hour > 0) { 201 | return hour + "小时前"; 202 | } 203 | if (minute > 0) { 204 | return minute + "分钟前"; 205 | } 206 | if (second > 0) { 207 | return second + "秒前"; 208 | } 209 | return "未知时间"; 210 | } 211 | 212 | /** 213 | * 将时间转换为分钟 如02:00 :01转换为120 214 | */ 215 | public static String timeToMinute(String time) { 216 | String sArray[] = time.split(":"); 217 | return Integer.valueOf(sArray[0]) * 60 + Integer.valueOf(sArray[1]) + ""; 218 | } 219 | 220 | /** 221 | * 将时间转换为分钟 如02:00 :01转换为120 222 | */ 223 | public static String timeTosecond(String time) { 224 | String sArray[] = time.split(":"); 225 | return Integer.valueOf(sArray[0]) * 360 + Integer.valueOf(sArray[1]) * 60 + Integer.valueOf(sArray[2]) + ""; 226 | } 227 | 228 | /** 229 | * 根据年 月 获取对应的月份 天数 230 | */ 231 | public static int getDaysByYearMonth(String year, String month) { 232 | 233 | Calendar a = Calendar.getInstance(); 234 | a.set(Calendar.YEAR, Integer.valueOf(year)); 235 | a.set(Calendar.MONTH, Integer.valueOf(month) - 1); 236 | a.set(Calendar.DATE, 1); 237 | a.roll(Calendar.DATE, -1); 238 | int maxDate = a.get(Calendar.DATE); 239 | return maxDate; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/messages/reportng.properties: -------------------------------------------------------------------------------- 1 | atTime=at 2 | causedBy=Caused by 3 | chronology=Chronology 4 | clickToExpandCollapse=Click to expand/collapse 5 | coverageReport=Coverage Report 6 | dependsOnGroups=Depends on group(s) 7 | dependsOnMethods=Depends on method(s) 8 | duration=Duration 9 | failed=Failed 10 | failed.tooltip=Some tests failed. 11 | failedConfiguration=Failed Configuration 12 | failedTests=Failed Tests 13 | generatedBy=Generated by TestNG with ReportNG 14 | groups=Groups 15 | groupsFor=Groups for 16 | logOutput=Log Output 17 | logOutput.description=Combined output from all calls to the log methods of the TestNG Reporter. 18 | method=Method 19 | methodArguments=Method arguments 20 | notApplicable=N/A 21 | onDate=on 22 | overview=Overview 23 | passed=Passed 24 | passed.tooltip=All tests passed. 25 | passedTests=Passed Tests 26 | passRate=Pass Rate 27 | skipped=Skipped 28 | skipped.reason=Reason 29 | skipped.tooltip=All executed tests passed but some tests were skipped. 30 | skippedConfiguration=Skipped Configuration 31 | skippedTests=Skipped Tests 32 | startTime=Start Time 33 | suites=Suites 34 | testDuration=Test duration 35 | thread=Thread 36 | total=Total 37 | log=Log Info 38 | screenshot=Screen Shot 39 | authod=Authod 40 | runcounts=RunCounts 41 | scanPerformance=ScanPerformance 42 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/messages/reportng_fr.properties: -------------------------------------------------------------------------------- 1 | atTime=à 2 | causedBy=Causés par 3 | chronology=Chronologie 4 | clickToExpandCollapse=Cliquez pour afficher/cacher 5 | coverageReport=Couverture de Test 6 | dependsOnGroups=Dépend des groupes 7 | dependsOnMethods=Dépend des méthodes 8 | duration=Durée 9 | failed=Échoué 10 | failed.tooltip=Quelques tests ont échoué. 11 | failedConfiguration=Configuration Échouée 12 | failedTests=Tests Échoués 13 | generatedBy=Générés par TestNG avec ReportNG 14 | groups=Groupes 15 | groupsFor=Groupes pour 16 | logOutput=Sortie de Journal 17 | logOutput.description=Le sortie combinée de toutes invocations des méthodes de la TestNG Reporter. 18 | method=Méthode 19 | methodArguments=Arguments de la méthode 20 | notApplicable=Néant 21 | onDate=du 22 | overview=Aperçu 23 | passed=Réussi 24 | passed.tooltip=Tous les tests ont réussi. 25 | passedTests=Tests Réussis 26 | passRate=Taux de Réussite 27 | skipped=Ignoré 28 | skipped.reason=Raison 29 | skipped.tooltip=Quelques tests ont été ignorées. 30 | skippedConfiguration=Configuration Ignorée 31 | skippedTests=Tests Ignorés 32 | suites=Suites 33 | testDuration=Durée de test 34 | total=Total 35 | authod=Authod 36 | runcounts=RunCounts 37 | scanPerformance=ScanPerformance 38 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/messages/reportng_pt.properties: -------------------------------------------------------------------------------- 1 | atTime=às 2 | causedBy=Causado por 3 | chronology=Cronologia 4 | clickToExpandCollapse=Clique para expandir/encolher 5 | coverageReport=Relatòrio de cobertura 6 | dependsOnGroups=Depende dos grupo(s) 7 | dependsOnMethods=Depende dos método(s) 8 | duration=Duração 9 | failed=Falha 10 | failed.tooltip=Alguns testes falharam. 11 | failedConfiguration=Configuração dos testes com Falha 12 | failedTests=Testes com Falha 13 | generatedBy=Gerado pelo TestNG com o ReportNG 14 | groups=Grupos 15 | groupsFor=Grupos para 16 | logOutput=Saída do Log 17 | logOutput.description=Saída de todas as chamadas aos métodos de log do TestNG Reporter. 18 | method=Método 19 | methodArguments=Argumentos do método 20 | notApplicable=N/D 21 | onDate=em 22 | overview=Resumo 23 | passed=Sucesso 24 | passed.tooltip=Todos os testes passaram. 25 | passedTests=Testes com Sucesso 26 | passRate=Taxa de sucesso 27 | skipped=Não executado 28 | skipped.reason=Razão 29 | skipped.tooltip=Todos os testes executados passaram, mas alguns não foram executados. 30 | skippedConfiguration=Configuração dos testes Não Executados 31 | skippedTests=Testes não executados 32 | suites=Suítes 33 | testDuration=Duração do teste 34 | total=Total 35 | authod=Authod 36 | runcounts=RunCounts 37 | scanPerformance=ScanPerformance 38 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/messages/reportng_zh_CN.properties: -------------------------------------------------------------------------------- 1 | atTime=at 2 | causedBy=Caused by 3 | chronology=Chronology 4 | clickToExpandCollapse=Click to expand/collapse 5 | coverageReport=Coverage Report 6 | dependsOnGroups=Depends on group(s) 7 | dependsOnMethods=Depends on method(s) 8 | duration=\u8017\u65f6 9 | failed=\u5931\u8d25 10 | failed.tooltip=Some tests failed. 11 | failedConfiguration=Failed Configuration 12 | failedTests=\u5931\u8d25\u7684\u6d4b\u8bd5 13 | generatedBy=Generated by TestNG with ReportNG 14 | groups=Groups 15 | groupsFor=Groups for 16 | logOutput=Log Output 17 | logOutput.description=Combined output from all calls to the log methods of the TestNG Reporter. 18 | method=Method 19 | methodArguments=Method arguments 20 | notApplicable=N/A 21 | onDate=on 22 | overview=\u603b\u89c8 23 | passed=\u901a\u8fc7 24 | passed.tooltip=All tests passed. 25 | passedTests=\u901a\u8fc7\u7684\u6d4b\u8bd5 26 | passRate=\u901a\u8fc7\u7387 27 | skipped=\u8df3\u8fc7 28 | skipped.reason=Reason 29 | skipped.tooltip=All executed tests passed but some tests were skipped. 30 | skippedConfiguration=Skipped Configuration 31 | skippedTests=\u5df2\u8df3\u8fc7\u7684\u6d4b\u8bd5 32 | startTime=\u5f00\u59cb\u65f6\u95f4 33 | suites=Suites 34 | testDuration=Test duration 35 | thread=Thread 36 | total=\u5168\u90e8 37 | log=Log 38 | screenshot=\u622a\u56fe 39 | authod=\u4f5c\u8005 40 | runcounts=\u6267\u884c\u6b21\u6570 41 | scanPerformance=\u67e5\u770b\u6027\u80fd\u8d70\u52bf\u56fe -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/class-results.html.vm: -------------------------------------------------------------------------------- 1 | ## This macro formats the results (whether passed, skipped or failed) of the test 2 | ## methods in a single class for inclusion in the HTML report. It assumes that the 3 | ## the results for the class are in a variable called $classResults. $id is a page 4 | ## scope variable that is used to assign unique identifiers to divs. 5 | 6 | #foreach ($testResult in $classResults) 7 | 8 | 9 | #set ($testInstanceName = "") 10 | #if ($testResult.testName) 11 | #set ($testInstanceName = " ($testResult.testName)") 12 | #end 13 | #if ($testResult.method.description && $testResult.method.description.length() > 0) 14 | $testResult.name$testInstanceName ($testResult.method.description) 15 | #else 16 | $testResult.name$testInstanceName 17 | #end 18 | 19 | 20 | $utils.formatDuration($testResult.startMillis, $testResult.endMillis)s 21 | 22 | 23 | $utils.getAuthod($testResult) 24 | 25 | 26 | $utils.getRunCounts($testResult) 27 | 28 | 29 | ## Display the dependencies for skipped test methods. 30 | #if ($testResult.status == 3) ## 3 means skipped. 31 | #if( $utils.hasDependentGroups($testResult) ) 32 | $messages.getString("dependsOnGroups"): 33 | $utils.getDependentGroups($testResult) 34 |
35 | #end 36 | #if ($utils.hasDependentMethods($testResult)) 37 | $messages.getString("dependsOnMethods"): 38 | $utils.getDependentMethods($testResult) 39 | #end 40 | #if ($utils.hasSkipException($testResult)) 41 | $messages.getString("skipped.reason"): 42 | $utils.getSkipExceptionMessage($testResult) 43 | #end 44 | #end 45 | 46 | #if ($utils.hasArguments($testResult)) 47 | $messages.getString("methodArguments"): $utils.getArguments($testResult)
48 | #end 49 | 50 | ## Show logger output for the test. 51 | #set ($output = $utils.getTestOutputWithoutRunCount($testResult)) 52 | #if ($output.size() > 0) 53 |
54 | #foreach( $line in $output ) 55 | #if ($meta.shouldEscapeOutput()) 56 | $utils.escapeHTMLString($utils.removeImage($line))
57 | #else 58 | $utils.removeImage($line)
59 | #end 60 | #end 61 |
62 | #end 63 | 64 | 65 | #if ($testResult.throwable && ( $testResult.status == 2 || $meta.shouldShowExpectedExceptions())) 66 | $utils.escapeHTMLString( $testResult.throwable.toString() )
67 |
68 | #foreach ($element in $testResult.throwable.stackTrace) 69 | $utils.escapeHTMLString( $element.toString() )
70 | #end 71 | #set ($causes = $utils.getCauses($testResult.throwable)) 72 | #foreach ($throwable in $causes) 73 | #set ($id = $id + 1) 74 | $messages.getString("causedBy"): $utils.escapeHTMLString( $throwable.toString() )
75 |
76 | #foreach ($element in $throwable.stackTrace) 77 | $utils.escapeHTMLString($element.toString())
78 | #end 79 |
80 | #end 81 |
82 | #end 83 | #set ($id = $id + 1) 84 | 85 | 86 | #set ($output = $utils.getTestOutput($testResult)) 87 | #if ($output.size() > 0) 88 |
89 | #foreach( $line in $output ) 90 | #if ($meta.shouldEscapeOutput()) 91 | $utils.escapeHTMLString($utils.getImageString($line))
92 | #else 93 | $utils.getImageString($line)
94 | #end 95 | #end 96 |
97 | #end 98 | 99 | 100 | #end 101 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/groups.html.vm: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | $meta.reportTitle - $suite.name - $messages.getString("groups") 7 | 8 | 9 | 10 | #if ($meta.stylesheetPath) 11 | 12 | #end 13 | 14 | 15 |

$messages.getString("groupsFor") $suite.name

16 | 17 | 18 | #foreach ($group in $groups.keySet()) 19 | 20 | 21 | 22 | #foreach ($test in $groups.get($group)) 23 | 24 | 25 | 26 | #end 27 | #end 28 |
$group
${test.realClass.name}.${test.methodName}
29 | 30 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/index.html.vm: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | $meta.reportTitle 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/output.html.vm: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | $meta.reportTitle - $messages.getString("logOutput") 7 | 8 | 9 | 10 | #if ($meta.stylesheetPath) 11 | 12 | #end 13 | 14 | 15 |

$messages.getString("logOutput")

16 |

17 | $messages.getString("logOutput.description") 18 |

19 | 20 |
21 | #foreach ($line in $utils.allOutput) 22 | #if ($meta.shouldEscapeOutput()) 23 | $utils.escapeHTMLString($line)
24 | #else 25 | $line 26 | #end 27 | #end 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/overview.html.vm: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | $meta.reportTitle - $messages.getString("overview") 7 | 8 | 9 | 10 | #if ($meta.stylesheetPath) 11 | 12 | #end 13 | 14 | 15 | 16 | 17 |
18 | FastAutoTest(Power By yph) 19 | $messages.getString("generatedBy") 20 | $messages.getString("atTime") $meta.reportTime $messages.getString("onDate") $meta.reportDate 21 |
$meta.user / $meta.javaInfo / $meta.platform 22 |
23 | 24 |

$meta.reportTitle

25 | #if (!$utils.allOutput.empty) 26 |

27 | $messages.getString("logOutput") 28 | #if ($meta.coverageLink) 29 | · $messages.getString("coverageReport") 30 | #end 31 |

32 | #end 33 | 46 | 47 | #foreach ($suite in $suites) 48 | #set($tempName = $suite.name.replace(' ','')) 49 |
50 | #end 51 | 52 | #foreach ($suite in $suites) 53 | 54 | #set ($suiteId = $velocityCount) 55 | #set ($totalTests = 0) 56 | #set ($totalPassed = 0) 57 | #set ($totalSkipped = 0) 58 | #set ($totalFailed = 0) 59 | 60 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | #foreach ($result in $suite.results) 81 | #set ($notPassedTests = $result.testContext.skippedTests.size() + $result.testContext.failedTests.size()) 82 | #set ($total = $result.testContext.passedTests.size() + $notPassedTests) 83 | #set ($totalTests = $totalTests + $total) 84 | #set ($totalPassed = $totalPassed + $result.testContext.passedTests.size()) 85 | #set ($totalSkipped = $totalSkipped + $result.testContext.skippedTests.size()) 86 | #set ($totalFailed = $totalFailed + $result.testContext.failedTests.size()) 87 | #set ($failuresExist = $result.testContext.failedTests.size()>0 || $result.testContext.failedConfigurations.size()>0) 88 | 89 | #if (($onlyReportFailures && $failuresExist) || (!$onlyReportFailures)) 90 | 91 | 94 | 97 | 98 | #if ($result.testContext.passedTests.size() > 0) 99 | 100 | #else 101 | 102 | #end 103 | 104 | #if ($result.testContext.skippedTests.size() > 0) 105 | 106 | #else 107 | 108 | #end 109 | 110 | #if ($result.testContext.failedTests.size() > 0) 111 | 112 | #else 113 | 114 | #end 115 | 116 | 124 | 125 | #end 126 | #end 127 | 128 | 129 | 130 | 131 | #if ($totalPassed > 0) 132 | 133 | #else 134 | 135 | #end 136 | 137 | #if ($totalSkipped > 0) 138 | 139 | #else 140 | 141 | #end 142 | 143 | #if ($totalFailed > 0) 144 | 145 | #else 146 | 147 | #end 148 | 149 | 157 | 158 | 159 |
61 | 69 | ${suite.name} 70 |
 $messages.getString("duration")$messages.getString("passed")$messages.getString("skipped")$messages.getString("failed")$messages.getString("passRate")
92 | ${result.testContext.name} 93 | 95 | $utils.formatDuration($utils.getDuration($result.testContext))s 96 | $result.testContext.passedTests.size()0$result.testContext.skippedTests.size()0$result.testContext.failedTests.size()0 117 | #if ($total > 0) 118 | #set ($passes = $total - $notPassedTests) 119 | $utils.formatPercentage($passes, $total) 120 | #else 121 | $messages.getString("notApplicable") 122 | #end 123 |
$messages.getString("total")$messages.getString("scanPerformance") $totalPassed0$totalSkipped0$totalFailed0 150 | #if ($totalTests > 0) 151 | #set ($totalPasses = $totalTests - $totalSkipped - $totalFailed) 152 | $utils.formatPercentage($totalPasses, $totalTests) 153 | #else 154 | $messages.getString("notApplicable") 155 | #end 156 |
160 | #end 161 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/performance.html.vm: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | $meta.reportTitle - $messages.getString("scanPerformance") 8 | 9 | 10 | 11 | #if ($meta.stylesheetPath) 12 | 13 | #end 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/reportng.css: -------------------------------------------------------------------------------- 1 | * {padding: 0; margin: 0;} 2 | a {color: #006699;} 3 | a:visited {color: #003366;} 4 | body {font-family: Lucida Sans Unicode, Lucida Grande, sans-serif; line-height: 1.8em; font-size: 62.5%; margin: 1.8em 1em;} 5 | h1 {font-family: Arial, Helvetica, sans-serif; font-weight: bold; font-size: 2.7em; margin-bottom: 0.6667em;} 6 | h2 {font-family: Arial, Helvetica, sans-serif; font-weight: bold; font-size: 1.8em; margin-bottom: 0;} 7 | p {font-size: 1.3em;} 8 | td {font-size: 1.3em;} 9 | 10 | .header {font-size: 1.4em; font-weight: bold; text-align: left;} 11 | .passed {background-color: #44aa44;} 12 | .skipped {background-color: #ffaa00;} 13 | .failed {background-color: #ff4444;} 14 | .failedConfig {background-color: #800000; color: #ffffff} 15 | .skippedConfig {background-color: #cc6600; color: #ffffff} 16 | .totalLabel {font-weight: bold; background-color: #ffffff;} 17 | 18 | .suite {background-color: #999999; font-weight: bold;} 19 | .test {background-color: #eeeeee; padding-left: 2em;} 20 | .test .passed {background-color: #88ee88;} 21 | .test .skipped {background-color: #ffff77;} 22 | .test .failed {background-color: #ff8888;} 23 | .group {background-color: #cccccc; color: #000000; font-weight: bold;} 24 | .suiteLinks {float: right; font-weight: normal; vertical-align: middle;} 25 | .suiteLinks a {color: #ffffff; margin-left: .5em;} 26 | .passRate {font-weight: bold; text-align: right;} 27 | .duration {text-align: left;} 28 | .thread {white-space: nowrap;} 29 | 30 | .resultsTable {border: 0; width: 100%; margin-top: 1.8em; line-height: 1.7em; border-spacing: 0.1em;} 31 | .resultsTable .method {width: 10em;} 32 | .resultsTable .duration {width: 4em;} 33 | .resultsTable .authod {width: 3em;} 34 | .resultsTable .runcounts {width: 4em;} 35 | .resultsTable .screenshot {width: 4em;} 36 | .resultsTable td {vertical-align: top; padding: 0 1em;} 37 | .resultsTable th {padding: 0 .5em;} 38 | .number {text-align: right;} 39 | .zero {font-weight: normal;} 40 | .columnHeadings {font-size: 1em;} 41 | .columnHeadings th {font-weight: normal;} 42 | 43 | .configTable {border: 1px solid #800000; color: #800000; margin-bottom: 1.5em;} 44 | 45 | #sidebarHeader {padding: 1.8em 1em; margin: 0 -1em 1.8em -1em;} 46 | #suites {line-height: 1.7em; border-spacing: 0.1em; width: 100%;} 47 | .tests {display: table-row-group;} 48 | .header.suite {cursor: pointer; clear: right; height: 1.214em; margin-top: 1px;} 49 | div.test {margin-top: 0.1em; clear: right; font-size: 1.3em;} 50 | 51 | /* The +/- toggle used in the navigation frame. */ 52 | .toggle {font-family: monospace; font-weight: bold; padding-left: 2px; padding-right: 5px; color: #777777;} 53 | .successIndicator {float: right; font-family: monospace; font-weight: bold; padding-right: 2px; color: #44aa44;} 54 | .skipIndicator {float: right; font-family: monospace; font-weight: bold; padding-right: 2px; color: #ffaa00;} 55 | .failureIndicator {float: right; font-family: monospace; font-weight: bold; padding-right: 2px; color: #ff4444;} 56 | 57 | 58 | /* These classes are for information about an individual test result. */ 59 | .result {font-size: 1.1em; vertical-align: middle;} 60 | .dependency {font-family: Lucida Console, Monaco, Courier New, monospace; font-weight: bold;} 61 | .arguments {font-family: Lucida Console, Monaco, Courier New, monospace; font-weight: bold;} 62 | .testOutput {font-family: Lucida Console, Monaco, Courier New, monospace; color: #666666;} 63 | .stackTrace {font-size: 0.9em; line-height: 1.2em; margin-left: 2em; display: none;} 64 | .stackTrace .stackTrace {font-size: inherit;} 65 | 66 | .description {border-bottom: 1px dotted #006699;} 67 | 68 | #meta {font-size: 1em; text-align: right; float: right;} 69 | #systemInfo {color: #666666;} 70 | 71 | /* Reporter log output (individual test ouput is style by "testOutput" above). */ 72 | #log {font-family: Lucida Console, Monaco, Courier New, monospace; font-size: 1.3em; margin-top: 1.8em;} 73 | 74 | .overviewTable {width: 100%; margin-top: 1.8em; line-height: 1.7em; border-spacing: 0.1em;} 75 | .overviewTable td {padding: 0 1em;} 76 | .overviewTable th {padding: 0 .5em;} 77 | .overviewTable .duration {width: 6em;} 78 | .overviewTable .passRate {width: 6em;} 79 | .overviewTable .number {width: 5em;} 80 | .overviewTable tr {height: 1.6em;} 81 | 82 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/reportng.js: -------------------------------------------------------------------------------- 1 | function toggleElement(elementId, displayStyle) { 2 | var element = document.getElementById(elementId); 3 | var current = element.currentStyle ? 4 | element.currentStyle['display'] : 5 | document.defaultView.getComputedStyle(element, null).getPropertyValue('display'); 6 | element.style.display = (current == 'none' ? displayStyle : 'none'); 7 | } 8 | 9 | function toggle(toggleId) { 10 | var toggle = document.getElementById ? document.getElementById(toggleId) : document.all[toggleId]; 11 | toggle.textContent = toggle.innerHTML == '\u25b6' ? '\u25bc' : '\u25b6'; 12 | } 13 | 14 | function imgShow(outerdiv, innerdiv, bigimg, _this) { 15 | var src = _this.attr("src"); //获取当前点击的pimg元素中的src属性 16 | $(bigimg).attr("src", src); //设置#bigimg元素的src属性 17 | 18 | /*获取当前点击图片的真实大小,并显示弹出层及大图*/ 19 | $("").attr("src", src).load(function() { 20 | var windowW = $(window).width() > window.screen.availWidth ? document.body.clientWidth : $(window).width(); //获取当前窗口宽度 21 | var windowH = $(window).height() > window.screen.availHeight ? document.body.clientHeight : $(window).height(); //获取当前窗口高度 22 | var realWidth = this.width; //获取图片真实宽度 23 | var realHeight = this.height; //获取图片真实高度 24 | var imgWidth, imgHeight; 25 | var scale = 1; //缩放尺寸,当图片真实宽度和高度大于窗口宽度和高度时进行缩放 26 | 27 | if(realHeight > windowH * scale) { //判断图片高度 28 | imgHeight = windowH * scale; //如大于窗口高度,图片高度进行缩放 29 | imgWidth = imgHeight / realHeight * realWidth; //等比例缩放宽度 30 | if(imgWidth > windowW * scale) { //如宽度扔大于窗口宽度 31 | imgWidth = windowW * scale; //再对宽度进行缩放 32 | } 33 | } else if(realWidth > windowW * scale) { //如图片高度合适,判断图片宽度 34 | imgWidth = windowW * scale; //如大于窗口宽度,图片宽度进行缩放 35 | imgHeight = imgWidth / realWidth * realHeight; //等比例缩放高度 36 | } else { //如果图片真实高度和宽度都符合要求,高宽不变 37 | imgWidth = realWidth; 38 | imgHeight = realHeight; 39 | } 40 | $(bigimg).css("width", imgWidth); //以最终的宽度对图片缩放 41 | 42 | var w = (windowW - imgWidth) / 2; //计算图片与窗口左边距 43 | var h = (windowH - imgHeight) / 2; //计算图片与窗口上边距 44 | $(innerdiv).css({ 45 | "top": h, 46 | "left": w 47 | }); //设置#innerdiv的top和left属性 48 | $(outerdiv).fadeIn("fast"); //淡入显示#outerdiv及.pimg 49 | }); 50 | 51 | $(outerdiv).click(function() { //再次点击淡出消失弹出层 52 | $(this).fadeOut("fast"); 53 | }); 54 | } 55 | 56 | function showCircleChart(render, title) { 57 | pcount = document.getElementById(title + "tpn").innerHTML; 58 | fcount = document.getElementById(title + "tfn").innerHTML; 59 | scount = document.getElementById(title + "tsn").innerHTML; 60 | var chart = iChart.create({ 61 | render: render, 62 | width: 340, 63 | height: 260, 64 | background_color: "#fefefe", 65 | gradient: false, 66 | color_factor: 0.2, 67 | border: { 68 | color: "BCBCBC", 69 | width: 0 70 | }, 71 | align: "top|center", 72 | offsetx: 0, 73 | offsety: 0, 74 | sub_option: { 75 | border: { 76 | color: "#BCBCBC", 77 | width: 1 78 | }, 79 | label: { 80 | fontweight: 500, 81 | fontsize: 11, 82 | color: "#4572a7", 83 | sign: "square", 84 | sign_size: 12, 85 | border: { 86 | color: "#BCBCBC", 87 | width: 1 88 | } 89 | } 90 | }, 91 | shadow: true, 92 | shadow_color: "#666666", 93 | shadow_blur: 2, 94 | showpercent: false, 95 | column_width: "70%", 96 | bar_height: "70%", 97 | radius: "90%", 98 | title: { 99 | text: title, 100 | color: '#3e576f' 101 | }, 102 | subtitle: { 103 | text: "", 104 | color: "#111111", 105 | fontsize: 16, 106 | font: "微软雅黑", 107 | textAlign: "center", 108 | height: 20, 109 | offsetx: 0, 110 | offsety: 0 111 | }, 112 | footnote: { 113 | text: "", 114 | color: "#111111", 115 | fontsize: 12, 116 | font: "微软雅黑", 117 | textAlign: "right", 118 | height: 20, 119 | offsetx: 0, 120 | offsety: 0 121 | }, 122 | legend: { 123 | enable: false, 124 | background_color: "#fefefe", 125 | color: "#333333", 126 | fontsize: 12, 127 | border: { 128 | color: "#BCBCBC", 129 | width: 1 130 | }, 131 | column: 1, 132 | align: "right", 133 | valign: "center", 134 | offsetx: 0, 135 | offsety: 0 136 | }, 137 | coordinate: { 138 | width: "80%", 139 | height: "84%", 140 | background_color: "#ffffff", 141 | axis: { 142 | color: "#a5acb8", 143 | width: [1, "", 1, ""] 144 | }, 145 | grid_color: "#d9d9d9", 146 | label: { 147 | fontweight: 500, 148 | color: "#666666", 149 | fontsize: 11 150 | } 151 | }, 152 | label: { 153 | fontweight: 500, 154 | color: "#666666", 155 | fontsize: 11 156 | }, 157 | type: "pie2d", 158 | data: [{ 159 | name: "Passed", 160 | value: pcount, 161 | color: "#44aa44" 162 | }, { 163 | name: "Failed", 164 | value: fcount, 165 | color: "#ff4444" 166 | }, { 167 | name: "Skipped", 168 | value: scount, 169 | color: "#FFD700" 170 | }] 171 | }); 172 | chart.draw(); 173 | } 174 | 175 | function showLineChart(render, title, subtitle,unit, value ,stack) { 176 | var data = [{ 177 | name: 'PV', 178 | value: value, 179 | color: '#ec4646', 180 | line_width: 2 181 | }]; 182 | var chartWidth; 183 | if(value.length<100){ 184 | chartWidth = 1000 185 | }else{ 186 | chartWidth = value.length*10 187 | } 188 | var coordinateWidth = chartWidth-60; 189 | var chart = new iChart.LineBasic2D({ 190 | render: render, 191 | data: data, 192 | align: 'right', 193 | title: { 194 | text: title, 195 | font: '微软雅黑', 196 | fontsize: 24, 197 | color: '#b4b4b4' 198 | }, 199 | subtitle : { 200 | text:subtitle, 201 | font : '微软雅黑', 202 | fontsize: 12, 203 | color:'#b4b4b4' 204 | }, 205 | width: chartWidth, 206 | height: 330, 207 | shadow: true, 208 | shadow_color: '#202020', 209 | shadow_blur: 8, 210 | shadow_offsetx: 0, 211 | shadow_offsety: 0, 212 | background_color: '#2e2e2e', 213 | tip: { 214 | enable: true, 215 | shadow: true, 216 | listeners: { 217 | //tip:提示框对象、name:数据名称、value:数据值、text:当前文本、i:数据点的索引 218 | parseText: function(tip, name, value, text, i) { 219 | return ""+stack[i]+"
"; 220 | } 221 | } 222 | }, 223 | crosshair: { 224 | enable: true, 225 | line_color: '#ec4646' 226 | }, 227 | sub_option: { 228 | smooth: true, 229 | label: false, 230 | hollow: false, 231 | hollow_inside: false, 232 | point_size: 8 233 | }, 234 | coordinate: { 235 | width: coordinateWidth, 236 | height: 240, 237 | striped_factor: 0.18, 238 | grid_color: '#4e4e4e', 239 | axis: { 240 | color: '#252525', 241 | width: [0, 0, 4, 4] 242 | }, 243 | scale: [{ 244 | position: 'left', 245 | start_scale: 0, 246 | end_scale: 100, 247 | scale_space: 10, 248 | scale_size: 2, 249 | scale_enable: false, 250 | label: { 251 | color: '#9d987a', 252 | font: '微软雅黑', 253 | fontsize: 11, 254 | fontweight: 600 255 | }, 256 | scale_color: '#9f9f9f' 257 | }] 258 | } 259 | }); 260 | chart.plugin(new iChart.Custom({ 261 | drawFn: function() { 262 | //计算位置 263 | var coo = chart.getCoordinate(), 264 | x = coo.get('originx'), 265 | y = coo.get('originy'), 266 | w = coo.width, 267 | h = coo.height; 268 | //在左上侧的位置,渲染一个单位的文字 269 | chart.target.textAlign('start') 270 | .textFont('600 11px 微软雅黑') 271 | .fillText(unit, x - 40, y - 12, false, '#9d987a') 272 | .textBaseline('top'); 273 | 274 | } 275 | })); 276 | //开始画图 277 | chart.draw(); 278 | } 279 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/results.html.vm: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | $meta.reportTitle - $result.testContext.name 7 | 8 | 9 | 10 | #if ($meta.stylesheetPath) 11 | 12 | #end 13 | 14 | 15 | 16 | 17 |

$result.testContext.name

18 |

19 | $messages.getString("testDuration"): $utils.formatDuration($utils.getDuration($result.testContext))s 20 |

21 | 22 | #set ($id = 0) 23 | #if ($failedConfigurations.size() > 0) 24 | 25 | 26 | #set ($id = 0) 27 | #foreach ($testClass in $failedConfigurations.keySet()) 28 | 29 | 30 | 31 | #set ($classResults = $failedConfigurations.get($testClass)) 32 | #parse("org/uncommons/reportng/templates/html/class-results.html.vm") 33 | #end 34 | 35 | #if ($skippedConfigurations.size() > 0) 36 | 37 | 38 | #set ($id = 0) 39 | #foreach ($testClass in $skippedConfigurations.keySet()) 40 | 41 | 42 | 43 | #set ($classResults = $skippedConfigurations.get($testClass)) 44 | #parse ("org/uncommons/reportng/templates/html/class-results.html.vm") 45 | #end 46 | #end 47 |
$messages.getString("failedConfiguration")
$testClass.name
 
$messages.getString("skippedConfiguration")
$testClass.name
48 | #end 49 | 50 | 51 | #if ($failedTests.size() > 0) 52 | 53 | 54 | #foreach ($testClass in $failedTests.keySet()) 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | #set ($classResults = $failedTests.get($testClass)) 64 | #parse ("org/uncommons/reportng/templates/html/class-results.html.vm") 65 | #end 66 |
$messages.getString("failedTests")
$testClass.name$messages.getString("duration")$messages.getString("authod")$messages.getString("runcounts")$messages.getString("log")$messages.getString("screenshot")
67 | #end 68 | 69 | #if ($skippedTests.size() > 0) 70 | 71 | 72 | #foreach ($testClass in $skippedTests.keySet()) 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | #set ($classResults = $skippedTests.get($testClass)) 81 | #parse ("org/uncommons/reportng/templates/html/class-results.html.vm") 82 | #end 83 |
$messages.getString("skippedTests")
$testClass.name$messages.getString("duration")$messages.getString("authod")$messages.getString("runcounts")$messages.getString("log")
84 | #end 85 | 86 | #if ($passedTests.size() > 0) 87 | 88 | 89 | #foreach ($testClass in $passedTests.keySet()) 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | #set ($classResults = $passedTests.get($testClass)) 99 | #parse ("org/uncommons/reportng/templates/html/class-results.html.vm") 100 | #end 101 |
$messages.getString("passedTests")
$testClass.name$messages.getString("duration")$messages.getString("authod")$messages.getString("runcounts")$messages.getString("log")
102 | #end 103 | 104 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/html/suites.html.vm: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | $meta.reportTitle - $messages.getString("suites") 7 | 8 | 9 | 10 | #if ($meta.stylesheetPath) 11 | 12 | #end 13 | 14 | 15 | 16 | 17 |
18 |

$meta.reportTitle

19 |

20 | $messages.getString("overview") 21 | #if (!$utils.allOutput.empty) 22 | · $messages.getString("logOutput") 23 | #end 24 | #if ($meta.coverageLink) 25 | · $messages.getString("coverageReport") 26 | #end 27 |

28 |
29 | 30 | #foreach ($suite in $suites) 31 | 32 | 33 | 36 | 37 | 38 | 39 | #set ($suiteId = $velocityCount) 40 | #foreach ($result in $suite.results) 41 | #set ($failuresExist = $result.testContext.failedTests.size()>0 || $result.testContext.failedConfigurations.size()>0) 42 | 43 | #if (($onlyReportFailures && $failuresExist) || (!$onlyReportFailures)) 44 | 45 | 57 | 58 | #end 59 | #end 60 | 61 | #end 62 |
34 | ${suite.name} 35 |
46 | #if ($result.testContext.failedTests.size() > 0) 47 | 48 | #else 49 | #if ($result.testContext.skippedTests.size() > 0) 50 | 51 | #else 52 | 53 | #end 54 | #end 55 | $result.testContext.name 56 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /fastautotest/src/main/resources/org/uncommons/reportng/templates/xml/results.xml.vm: -------------------------------------------------------------------------------- 1 | 2 | #set ($totalTests = $results.passedTests.size() + $results.skippedTests.size() + $results.failedTests.size()) 3 | 9 | 10 | #foreach ($testResult in $results.failedTests) 11 | #if ($testResult.testName) 12 | 13 | #else 14 | 15 | #end 16 | #if ($testResult.throwable) 17 | 24 | 35 | 36 | 37 | #else 38 | 45 | #end 46 | 47 | #end 48 | 49 | #foreach ($testResult in $results.skippedTests) 50 | #if ($testResult.testName) 51 | 52 | #else 53 | 54 | #end 55 | 56 | 57 | #end 58 | 59 | #foreach ($testResult in $results.passedTests) 60 | #if ($testResult.testName) 61 | 62 | #else 63 | 64 | #end 65 | #end 66 | 67 | 68 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/y-grey/FastAutoTest/1750b0a316a739be823702cc9777d82fd3ea38dd/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Oct 07 19:17:31 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':fastautotest' --------------------------------------------------------------------------------