├── .gitignore ├── .travis.yml ├── README.md ├── google-style.xml ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── ansonliao │ └── selenium │ ├── annotations │ ├── Author.java │ ├── Description.java │ ├── Headless.java │ ├── Incognito.java │ ├── PageName.java │ ├── Retry.java │ ├── URL.java │ └── browser │ │ ├── Chrome.java │ │ ├── Edge.java │ │ ├── Firefox.java │ │ ├── IgnoreChrome.java │ │ ├── IgnoreEdge.java │ │ ├── IgnoreFirefox.java │ │ ├── IgnoreInternetExplorer.java │ │ ├── IgnoreOpera.java │ │ ├── IgnorePhantomJs.java │ │ ├── InternetExplorer.java │ │ ├── Opera.java │ │ └── PhantomJs.java │ ├── factory │ ├── ChromeFactory.java │ ├── DriverManager.java │ ├── DriverManagerFactory.java │ ├── EdgeFactory.java │ ├── FirefoxFactory.java │ ├── InternetExplorerFactory.java │ ├── OperaFactory.java │ ├── PhantomJsFactory.java │ └── WDManager.java │ ├── internal │ ├── OptionalConsumer.java │ ├── OptionalEx.java │ └── ScreenshotManager.java │ ├── json │ ├── JsonLoader.java │ └── JsonParser.java │ ├── parallel │ ├── ClassFinder.java │ ├── MethodFinder.java │ ├── SeleniumParallel.java │ └── SeleniumParallelTestListener.java │ ├── report │ └── factory │ │ ├── ExtentManager.java │ │ └── ExtentTestManager.java │ ├── testng │ ├── RetryListener.java │ ├── TestNGFilter.java │ ├── TestNGRetry.java │ ├── TestNGRunner.java │ ├── TestResultListener.java │ ├── XmlClassBuilder.java │ ├── XmlSuiteBuilder.java │ └── XmlTestBuilder.java │ └── utils │ ├── AnnotationUtils.java │ ├── AuthorUtils.java │ ├── BrowserUtils.java │ ├── CapsUtils.java │ ├── MyFileUtils.java │ ├── PlatformUtils.java │ ├── SEConfig.java │ ├── StringUtils.java │ ├── TestGroupUtils.java │ ├── WDMHelper.java │ └── config │ └── SEConfigs.java └── test └── java └── testcases └── Test_Demo.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.o 7 | *.so 8 | 9 | # Logs and databases # 10 | ###################### 11 | *.log 12 | *.sqlite 13 | *.out 14 | 15 | # OS generated files # 16 | ###################### 17 | .DS_Store 18 | .DS_Store? 19 | *.DS_Store 20 | ._* 21 | .Spotlight-V100 22 | .Trashes 23 | Icon? 24 | ehthumbs.db 25 | Thumbs.db 26 | *~ 27 | 28 | # Maven # 29 | ######### 30 | .classpath 31 | .project 32 | .settings/ 33 | target/ 34 | test-output/ 35 | 36 | # NetBeans # 37 | ############ 38 | nbproject/private/ 39 | /nb-configuration.xml 40 | 41 | # IDEA 42 | ############ 43 | .idea 44 | *.iml 45 | 46 | # Web Driver 47 | drivers/ 48 | 49 | # Debug Files 50 | *.txt 51 | *.html 52 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | script: mvn clean site install -Dmaven.test.skip=true 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Selenium-Extensions 2 | [![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://github.com/saikrishna321/AppiumTestDistribution) 3 | [![badge-jdk](https://img.shields.io/badge/jdk-8-green.svg)](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 4 | [![Build Status](https://travis-ci.org/ansonliao/Selenium-Extensions.svg?branch=master)](https://travis-ci.org/ansonliao/Selenium-Extensions/builds/) 5 | 6 | This is a extension for Selenium in Java to extend Selenium for make Selenium test case can be run in parallel, and make it easy to support multiple browsers. 7 | Integrated beautiful and powerful HTML test report: [ExtentReports](http://extentreports.com/). 8 | 9 | ## Maven Dependency 10 | Add the below dependencies in your `pom.xml` (Master) 11 | 12 | ```xml 13 | 14 | 15 | jitpack.io 16 | https://jitpack.io 17 | 18 | 19 | ``` 20 | 21 | ```xml 22 | 23 | com.github.ansonliao 24 | Selenium-Extensions 25 | 2.3.7-SNAPSHOT 26 | 27 | ``` 28 | 29 | ## Parallel 30 | Introduce [TestNG](http://testng.org/doc/index.html) framework to support Selenium test case parallel run. 31 | Parallel mode is by Test, the tag `` of TestNG test suite xml. 32 | 33 | ## Visual TestNG test suite XML generation 34 | No need to provide TestNG test suite XMl file to start the test, TestNG test suite XML file will be generated programmatically. 35 | 36 | ## Report 37 | ### Testing Report 38 | 39 | After test completed, Extents test report can be found `target/ExtentReports.html`. 40 | 41 | ### Screenshot for test fail 42 | Screenshot can be found at `target/screenshots` for the test failed. 43 | 44 | ## Download WebDriver binary automatically 45 | `Selenium-Extensions` will download `WebDriver` binaries for Test automatically, 46 | no need download the binary manually before the test start. 47 | 48 | ### WebDriverManager 49 | 50 | For download the webdriver binary, introduced the dependency `webdrivermanager` of `io.github.bonigarcia`, for more detail the setting for the `Web Driver Manager`, please refer to the official document: [Read Me](https://github.com/bonigarcia/webdrivermanager/), and the configuration file: [Configuration](https://github.com/bonigarcia/webdrivermanager#configuration) . 51 | 52 | ## Multiple browsers support 53 | Browser support: 54 | 55 | - **Chrome**: Mac, Linux, Windows 56 | - **FireFox**: Mac, Linux, Windows 57 | - **PhantomJs**: Mac, Linux, Windows 58 | - **Opera**: Mac, Linux, Windows 59 | - **Edge**: Windows 60 | - **InternetExplorer**: Windows 61 | - **Safari**: DEPRECATED 62 | 63 | ## Multiple browsers in Runtime 64 | ```java 65 | public class TestBrowser { 66 | @Test 67 | @Chrome 68 | public void f1() { 69 | // your testing code here 70 | } 71 | 72 | @Test 73 | @Firefox 74 | public void f2() { 75 | // your testing code here 76 | } 77 | } 78 | ``` 79 | 80 | Even, you can annotated your test class: 81 | ```java 82 | @Chrome 83 | public class TestBrowser { 84 | @Test 85 | @Edge 86 | public void f1() { 87 | // your testing code here 88 | } 89 | 90 | @Test 91 | @Firefox 92 | public void f2() { 93 | // your testing code here 94 | } 95 | } 96 | ``` 97 | 98 | From the above code, test class `BrowserTest` was annotated by `@Chrome`, so all test method of TestNG of test class `BrowserTest` will be run at browser `Chrome`. 99 | So finally, test method: 100 | * `f1()`: will be run at browsers `Edge` and `Chrome` (if Windows OS platform); 101 | * `f2()`: will be run at browsers `Firefox` and `Chrome`. 102 | 103 | ## Dynamic URL support for test method 104 | ```java 105 | public class TestBrowser { 106 | 107 | @Test 108 | @Chrome 109 | @URL("https://www.google.com/") 110 | public void f1() { 111 | // your testing code here 112 | } 113 | 114 | @Test 115 | @Firefox 116 | @URL("https://www.wordpress.com") 117 | public void f2() { 118 | // your testing code here 119 | } 120 | } 121 | ``` 122 | 123 | When the test start (test cases `f1`, `f2`), the URL will be open automatically, URL `https://www.google.com/` will be 124 | launched for test case `f1`, URL `https://www.wordpress.com` will be launched for test case `f2`. 125 | 126 | also, you can annotate test class by `@URL`, such as: 127 | ```java 128 | @URL("http://www.google.com") 129 | public class TestBrowser { 130 | @Test 131 | @Chrome 132 | public void f1() { 133 | // your testing code here 134 | } 135 | 136 | @Test 137 | @Firefox 138 | public void f2() { 139 | // your testing code here 140 | } 141 | } 142 | ``` 143 | 144 | Above two test cases `f1`, `f2` will be lauch URL `http://www.google.com` automatically. 145 | 146 | ## Run test case 147 | 148 | You need to create the `runner` to trigger the testing run. 149 | 150 | Below is a sample, it is the simplest sample run all test cases, only run method `TestNGRunner.Run()`. 151 | 152 | ```java 153 | public class MyTestRunner { 154 | @Test 155 | public void run() { 156 | TestNGRunner.Run(); 157 | } 158 | } 159 | ``` 160 | 161 | When test runner created, we can trigger the test by `Maven` command, as below. 162 | 163 | ```bash 164 | mvn clean test -Dtest=MyTestRunner 165 | ``` 166 | 167 | ## Filter 168 | `Selenium-Extentsions` support run the test case by filters. 169 | The filter includes: 170 | 171 | - *All*: run test cases in test project 172 | - *Package*: run test cases by specify packages 173 | - *Test Group*: run test cases by specify test groups which in TestNG test annotation `@Test` 174 | - *Test class*: run test cases by specify test classes which are TestNG test class 175 | - *Browser*: run test cases by specify browser, such as `CHROME`, `FIREFOX` 176 | 177 | ### Full configuration list 178 | You can create your owned configuration and named to `seleniumextentsions.properties` or `se.properties`. 179 | Please note that make sure put your owned configuration file to the resources directory of under `src` that the main Java code (`src/main/resources`) or test Java code (`src/test/resources`). 180 | 181 | | **Key** | **Description** | **Value** | **Default Value**| 182 | | --- | --- | ---| --- | 183 | | add.browser.group.to.report | Whether add testing browser to ExtentReports as a group of ExtentReport | Boolean: `true` / `false` | `false` (Java keyword in boolean, case sensitive) | 184 | | run.by.browsers | Run all TestNG test by the specified browser(s). This setting will ignore the existing browser annotation of TestNG test class, and will resign all valid TestNG test class to this setting browser | `CHROME`, `FIREFOX`, `IE`, `OPERA`, `PHAMTOMJS`, `EDGE`, `INTEREXPLORER`| null | 185 | | default.browser | Setting the default browser for the valid TestNG test case which without any valid browser annotation| `CHROME`, `FIREFOX`, `IE`, `OPERA`, `PHAMTOMJS`, `EDGE`, `INTEREXPLORER` | `CHROME` (no case sensitive) | 186 | | browser.annotation.package | The package that places all browser annotations and all ignore browser annotations. | string | `com.github.ansonliao.selenium.annotations.browser` | 187 | | test.tag.class.size.of.testngxml | The setting for how many TestNG test class will be held for each `Test` tag of TestNG XML | positive integer | 10 | 188 | | testing.package.names | Specified what test script under the package(s) will be run | string or string of list | nulll | 189 | | testing.browser.names | Run the test cases which specified annotated by browser annotation match to this setting | string or string of list | null | 190 | | testing.test.groups | Run the test cases which are assigned to the testing group of TestNG of this setting | string or string of list | null | 191 | | testing.testng.classes | Specified what valid TestNG testing class will be executed | string or string of list | null | 192 | | testng.listeners | Provided your owned TestNG listener(s) | string or string of list | `com.github.ansonliao.selenium.testng.TestResultListener`, `com.github.ansonliao.selenium.parallel.SeleniumParallelTestListener` | 193 | | testng.class.prefix | Specified the specificed TestNG testing class that the leading, the TestNG testing class scanned will only filter the testing class which the class name starts with the setting | string | `test` (no case sensitive) | 194 | 195 | > Please note that, for the setting can be string of list, please use comma (`,`) as the separator 196 | 197 | ### Sample configuration: `src/test/resources/seleniumextensions.properties` 198 | 199 | ``` 200 | add.browser.group.to.report=true 201 | run.by.browsers=CHROME, FIREFOX 202 | default.browser=CHROME // can be removed 203 | browser.annotation.package=com.github.ansonliao.selenium.annotations.browser // can be removed 204 | testing.package.names=example.phase1, example.phase2 205 | test.tag.class.size.of.testngxml=20 206 | testing.test.groups=REGRESSION, SMOKE 207 | testng.listeners=example.listeners.mylistener1, example.listeners.mylistener2 208 | 209 | ``` 210 | 211 | ### Provide filters as Maven command line argument 212 | 213 | also you can provide the configuration by Maven's command line argument: 214 | 215 | Execute `Smoke` test case (TestNG test group includes `SMOKE`) only 216 | 217 | ```bash 218 | mvn clean test -Dtest=your_test_runner -Dtesting.test.groups=SMOKE 219 | ``` 220 | 221 | Or have a package that place all `Smoke` test case together and the package name is `example.smoke`: 222 | 223 | ```bash 224 | mvn clean test -Dtest=your_test_runner -Dtesting.package.names=example.smoke 225 | ``` 226 | 227 | ### Provide filters programmatically 228 | 229 | In your Test Runner, before calling `TestNGRunner.Run(...)`, you can set the configuration via set the values of `System.Properties` or `System.envs` of Java: 230 | 231 | ```java 232 | public class RegressionRunner { 233 | @Test 234 | public void runner() { 235 | System.setProperty("testing.test.groups", "REGRESSION"); 236 | TestNGRunner.run(); 237 | } 238 | } 239 | 240 | public class SmokeRunner { 241 | @Test 242 | public void runner() { 243 | System.setProperty("testing.test.groups", "SMOKE"); 244 | } 245 | } 246 | 247 | public class ChromeTestRunner { 248 | @Test 249 | public void runner() { 250 | System.setProperty("testing.browser.names", "CHROME"); 251 | TestNGRunner.run(); 252 | } 253 | } 254 | 255 | ``` 256 | 257 | 258 | 259 | #### Difference between `default.browser` and `testing.browser.names` 260 | 261 | - `testing.browser.names`: The program will look for the valid TestNG testing class and testing method's browser annotations and check the browser annotation of test case whether contained in the setting of `testing.browser.names`, and then execute the test case which annotated by the browser annotation contains in the setting. 262 | - `run.by.browsers`: The programm will look for the valid TestNG test case and then ignore the browser annotation(s), and then add browser(s) of `run.by.browsers` as the new browser annotation(s) for the valid TestNG test case. 263 | 264 | **Sample:** 265 | 266 | Let's say we have two test methods below: 267 | 268 | ```java 269 | public class TestCaseSample { 270 | 271 | @Test(groups={"Regresion", "SMOKE"}) 272 | @Chrome 273 | public void regression1() { 274 | // testing code here 275 | } 276 | 277 | @Test(groups={"Regression", "BVT"}) 278 | @Firefox 279 | public void regression2() { 280 | // testing code here 281 | } 282 | } 283 | ``` 284 | 285 | 1. Setting `run.by.browsers=EDGE, PHANTOMJS` applied, those two test cases will be executed against to browser `Edge`, `PhantomJS` 286 | 2. Setting `testing.browser.names=FIREFOX` applied, only `regression2()` of those two test cases will be executed, because only test case `regression2()` have browser `Firefox` annotation 287 | 3. Setting `testing.browser.names=PHANTOMJS` applied, no test case will be executed, because no test case annotated to browser `PHANTOMJS` 288 | 289 | ## License 290 | ![Apache License version 2.0](http://www.apache.org/img/asf_logo.png) 291 | 292 | Selenium-Extensions is released under [Apache License version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 293 | -------------------------------------------------------------------------------- /google-style.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 76 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 104 | 106 | 107 | 108 | 109 | 111 | 112 | 113 | 114 | 116 | 117 | 118 | 119 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 130 | 132 | 134 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 155 | 156 | 157 | 158 | 159 | 160 | 162 | 163 | 164 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 174 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.github.ansonliao 6 | Selenium-Extensions 7 | 2.3.7 8 | jar 9 | 10 | Selenium-Extensions 11 | A tool for run Web UI test in parallel across browsers 12 | https://github.com/ansonliao/Selenium-Extensions 13 | 14 | 15 | 16 | GNU Public License version 3.0 17 | http://www.gnu.org/licenses/gpl-3.0.txt 18 | 19 | 20 | 21 | 22 | 23 | Anson Liao 24 | ansonliao.xiao@gmail.com 25 | 26 | owner 27 | 28 | 29 | 30 | 31 | 32 | UTF-8 33 | 6.10 34 | 1.7.24 35 | 1.8 36 | 1.8 37 | 1.7.24 38 | 3.141.59 39 | 0.0.8 40 | 1.3.2 41 | 2.3 42 | 2.2.5 43 | 2.0-M7 44 | 8.18 45 | 3.6.0 46 | 2.19.1 47 | 2.7.5 48 | 2.17 49 | 3.1.3 50 | 1.0.10 51 | 2.8.5 52 | 2.4.0 53 | 1.5.2 54 | false 55 | 56 | 57 | 58 | 59 | org.seleniumhq.selenium 60 | selenium-java 61 | ${selenium.java.version} 62 | 63 | 64 | gson 65 | com.google.code.gson 66 | 67 | 68 | httpmime 69 | org.apache.httpcomponents 70 | 71 | 72 | 73 | 74 | org.testng 75 | testng 76 | ${testng.version} 77 | 78 | 79 | com.aventstack 80 | extentreports 81 | ${aventstack.extentreports.version} 82 | 83 | 84 | gson 85 | com.google.code.gson 86 | 87 | 88 | httpmime 89 | org.apache.httpcomponents 90 | 91 | 92 | 93 | 94 | ru.yandex.qatools.ashot 95 | ashot 96 | ${ashot.version} 97 | 98 | 99 | org.seleniumhq.selenium 100 | selenium-java 101 | 102 | 103 | org.hamcrest 104 | hamcrest-all 105 | 106 | 107 | 108 | 109 | org.slf4j 110 | slf4j-api 111 | ${slf4j.api.version} 112 | 113 | 114 | org.slf4j 115 | slf4j-simple 116 | ${slf4j.simple.version} 117 | 118 | 119 | io.github.bonigarcia 120 | webdrivermanager 121 | ${webdrivermanager.version} 122 | 123 | 124 | gson 125 | com.google.code.gson 126 | 127 | 128 | slf4j-api 129 | org.slf4j 130 | 131 | 132 | 133 | 134 | io.github.lukehutch 135 | fast-classpath-scanner 136 | ${fast.classpath.scanner.version} 137 | 138 | 139 | com.thoughtworks.qdox 140 | qdox 141 | ${thoughtworks.qdox.version} 142 | compile 143 | 144 | 145 | com.github.lalyos 146 | jfiglet 147 | ${lalyos.jfiglet.version} 148 | 149 | 150 | com.typesafe 151 | config 152 | ${typesafe.config.version} 153 | 154 | 155 | org.aeonbits.owner 156 | owner-java8 157 | ${aeonbits.owner.version} 158 | 159 | 160 | com.google.code.gson 161 | gson 162 | ${gson.version} 163 | 164 | 165 | com.jayway.jsonpath 166 | json-path 167 | ${jsonpath.version} 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-compiler-plugin 177 | ${maven.compiler.plugin.version} 178 | 179 | ${maven.compiler.target} 180 | ${maven.compiler.source} 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | release 189 | 190 | 191 | 192 | org.apache.maven.plugins 193 | maven-surefire-plugin 194 | ${maven.surefire.plugin.version} 195 | 196 | 197 | org.apache.maven.plugins 198 | maven-checkstyle-plugin 199 | ${maven.checkstyle.plugin.version} 200 | 201 | 202 | com.puppycrawl.tools 203 | checkstyle 204 | [${puppycrawl.checkstyle.version},) 205 | 206 | 207 | 208 | ${basedir}/google-style.xml 209 | ${project.build.sourceEncoding} 210 | true 211 | true 212 | true 213 | true 214 | 215 | 216 | 217 | validate 218 | validate 219 | 220 | check 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | org.apache.maven.plugins 234 | maven-checkstyle-plugin 235 | ${maven.checkstyle.plugin.version} 236 | 237 | ${basedir}/google-style.xml 238 | ${project.build.sourceEncoding} 239 | false 240 | true 241 | true 242 | true 243 | 244 | 245 | 246 | 247 | checkstyle 248 | 249 | 250 | 251 | 252 | 253 | org.apache.maven.plugins 254 | maven-jxr-plugin 255 | ${maven.jxr.plugin.version} 256 | 257 | true 258 | 259 | 260 | 261 | org.apache.maven.plugins 262 | maven-project-info-reports-plugin 263 | 2.9 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/Author.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Author { 11 | String[] value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/Description.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Description { 11 | String value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/Headless.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Headless { 11 | String value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/Incognito.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Incognito { 11 | String value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/PageName.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface PageName { 11 | String value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/Retry.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Retry { 11 | int maxRetry() default 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/URL.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) 10 | public @interface URL { 11 | String value() default ""; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/Chrome.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Chrome { 11 | String value() default "Chrome"; 12 | 13 | String type() default "BROWSER"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/Edge.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Edge { 11 | String value() default "Edge"; 12 | 13 | String type() default "BROWSER"; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/Firefox.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Firefox { 11 | String value() default "Firefox"; 12 | 13 | String type() default "BROWSER"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/IgnoreChrome.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface IgnoreChrome { 11 | String value() default ""; 12 | 13 | String type() default "BROWSER_IGNORE"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/IgnoreEdge.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface IgnoreEdge { 11 | String value() default ""; 12 | 13 | String type() default "BROWSER_IGNORE"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/IgnoreFirefox.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface IgnoreFirefox { 11 | String value() default ""; 12 | 13 | String type() default "BROWSER_IGNORE"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/IgnoreInternetExplorer.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface IgnoreInternetExplorer { 11 | String value() default ""; 12 | 13 | String type() default "BROWSER_IGNORE"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/IgnoreOpera.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface IgnoreOpera { 11 | String value() default ""; 12 | 13 | String type() default "BROWSER_IGNORE"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/IgnorePhantomJs.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Deprecated 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.TYPE, ElementType.METHOD}) 11 | public @interface IgnorePhantomJs { 12 | String value() default ""; 13 | 14 | String type() default "BROWSER_IGNORE"; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/InternetExplorer.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface InternetExplorer { 11 | String value() default "InternetExplorer"; 12 | 13 | String type() default "BROWSER"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/Opera.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.TYPE, ElementType.METHOD}) 10 | public @interface Opera { 11 | String value() default "Opera"; 12 | 13 | String type() default "BROWSER"; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/annotations/browser/PhantomJs.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.annotations.browser; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Deprecated 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target({ElementType.TYPE, ElementType.METHOD}) 11 | public @interface PhantomJs { 12 | String value() default "PhantomJs"; 13 | 14 | String type() default "BROWSER"; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/ChromeFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.openqa.selenium.chrome.ChromeDriver; 5 | import org.openqa.selenium.chrome.ChromeOptions; 6 | import org.openqa.selenium.remote.DesiredCapabilities; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.testng.util.Strings; 10 | 11 | import java.io.File; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | import static com.github.ansonliao.selenium.utils.CapsUtils.getCaps; 16 | import static com.github.ansonliao.selenium.utils.CapsUtils.getCliArgs; 17 | import static com.github.ansonliao.selenium.utils.CapsUtils.getExtensions; 18 | import static com.github.ansonliao.selenium.utils.PlatformUtils.getPlatform; 19 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 20 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 21 | import static java.util.stream.Collectors.toList; 22 | import static org.openqa.selenium.remote.BrowserType.CHROME; 23 | 24 | /** 25 | * 1. 26 | * Chromedriver Capabilities & ChromeOptions: 27 | * http://chromedriver.chromium.org/capabilities 28 | * https://stackoverflow.com/a/46786163 29 | * 2. 30 | * About set the mobile emulator, please refer to: 31 | * http://chromedriver.chromium.org/mobile-emulation 32 | * 3. 33 | * ChromeDriver options argument list can refer to: 34 | * http://peter.sh/experiments/chromium-command-line-switches/ 35 | * https://chromium.googlesource.com/chromium/src/+/master/chrome/common/chrome_switches.cc 36 | * https://chromium.googlesource.com/chromium/src/+/master/chrome/common/pref_names.cc 37 | */ 38 | public class ChromeFactory extends DriverManager { 39 | 40 | private static final Logger logger = LoggerFactory.getLogger(ChromeFactory.class); 41 | private static ChromeFactory instance = new ChromeFactory(); 42 | private ChromeOptions options = new ChromeOptions(); 43 | 44 | private ChromeFactory() { 45 | super(); 46 | } 47 | 48 | public synchronized static ChromeFactory getInstance() { 49 | return instance; 50 | } 51 | 52 | @Override 53 | public WebDriver getDriver() { 54 | List argList = getCliArgs(CHROME); 55 | List extensions = getExtensions(CHROME); 56 | Map caps = getCaps(CHROME); 57 | if (isHeadless) { 58 | if (!argList.parallelStream() 59 | .filter(arg -> String.valueOf(arg).toLowerCase().contains("headless")) 60 | .map(String::valueOf).anyMatch(s -> Strings.isNotNullAndNotEmpty(s))) { 61 | argList.add("headless"); 62 | } 63 | } 64 | if (isIncognito) { 65 | if (!argList.parallelStream() 66 | .filter(arg -> String.valueOf(arg).toLowerCase().contains("incognito")) 67 | .map(String::valueOf).anyMatch(s -> Strings.isNotNullAndNotEmpty(s))) { 68 | argList.add("incognito"); 69 | } 70 | } 71 | if (!caps.containsKey("platform")) { 72 | caps.put("platform", getPlatform().toString()); 73 | } 74 | options.addArguments(argList.parallelStream() 75 | .map(String::valueOf) 76 | .map(String::trim) 77 | .map(String::toLowerCase) 78 | .distinct().collect(toList())); 79 | extensions.parallelStream() 80 | .map(String::valueOf) 81 | .map(File::new) 82 | .forEach(options::addExtensions); 83 | // options.setExperimentalOption("prefs", caps); 84 | // caps.forEach(options::setCapability); 85 | caps.forEach((k, v) -> options.setCapability(k, v)); 86 | DesiredCapabilities capabilities = DesiredCapabilities.chrome(); 87 | capabilities.setCapability(ChromeOptions.CAPABILITY, options); 88 | driver = Strings.isNullOrEmpty(SELENIUM_HUB_URL) 89 | ? new ChromeDriver(options) 90 | : getDriver(capabilities, SELENIUM_HUB_URL); 91 | return driver; 92 | } 93 | 94 | @Override 95 | protected WebDriver buildRemoteWebDriver() { 96 | return null; 97 | } 98 | 99 | @Override 100 | public String getExportParameterKey() { 101 | return removeQuoteMark(getConfigInstance().chromeDriverProperty()); 102 | } 103 | 104 | @Override 105 | public Logger getLogger() { 106 | return logger; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/DriverManager.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.openqa.selenium.remote.DesiredCapabilities; 5 | import org.openqa.selenium.remote.RemoteWebDriver; 6 | import org.slf4j.Logger; 7 | import org.testng.util.Strings; 8 | 9 | import java.net.MalformedURLException; 10 | import java.net.URL; 11 | import java.text.DateFormat; 12 | import java.text.SimpleDateFormat; 13 | 14 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 15 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 16 | 17 | public abstract class DriverManager { 18 | 19 | protected static final String SELENIUM_HUB_URL = retrieveSeleniumHubUrl(); 20 | public WebDriver driver; 21 | public Logger logger = getLogger(); 22 | protected boolean isHeadless = false; 23 | protected boolean isIncognito = false; 24 | private String exportParameter = getExportParameterKey(); 25 | 26 | public static String retrieveSeleniumHubUrl() { 27 | String url = removeQuoteMark(getConfigInstance().seleniumHubUrl()); 28 | if (Strings.isNullOrEmpty(url)) { 29 | return ""; 30 | } 31 | 32 | if (!url.endsWith("wd/hub")) { 33 | return url.endsWith("/") ? url + "wd/hub" : url + "/wd/hub"; 34 | } 35 | return url; 36 | } 37 | 38 | public abstract WebDriver getDriver(); 39 | 40 | public WebDriver getDriver(DesiredCapabilities capabilities, String seleniumHubUrl) { 41 | RemoteWebDriver remoteWebDriver = null; 42 | try { 43 | logger.info("Create RemoteWebDriver instance with Selenium Hub URL: {}", 44 | seleniumHubUrl); 45 | remoteWebDriver = new RemoteWebDriver(new URL(seleniumHubUrl), capabilities); 46 | } catch (MalformedURLException e) { 47 | logger.error("Malformed URL found: {}", SELENIUM_HUB_URL); 48 | e.printStackTrace(); 49 | } 50 | return remoteWebDriver; 51 | } 52 | 53 | protected abstract WebDriver buildRemoteWebDriver(); 54 | 55 | public abstract String getExportParameterKey(); 56 | 57 | public abstract Logger getLogger(); 58 | 59 | public void quitDriver() { 60 | if (driver != null) { 61 | driver.quit(); 62 | } 63 | } 64 | 65 | public void setHeadless(boolean isHeadless) { 66 | this.isHeadless = isHeadless; 67 | } 68 | 69 | public void setIncognito(boolean isIncognito) { 70 | this.isIncognito = isIncognito; 71 | } 72 | 73 | public void exportDriver(String key, String value) { 74 | logger.info("Export {} as {}", key, value); 75 | System.setProperty(key, value); 76 | } 77 | 78 | protected String getTimezone() { 79 | DateFormat dbFormatter = new SimpleDateFormat("EEEE, MMMM dd, yyyy hh:mm aa zzzz"); 80 | return dbFormatter.getTimeZone().getID(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/DriverManagerFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import com.google.common.collect.Maps; 4 | 5 | import java.util.Map; 6 | import java.util.function.Supplier; 7 | 8 | public class DriverManagerFactory { 9 | private static Map> map = Maps.newHashMap(); 10 | 11 | static { 12 | map.put("CHROME", ChromeFactory::getInstance); 13 | map.put("FIREFOX", FirefoxFactory::getInstance); 14 | map.put("EDGE", EdgeFactory::getInstance); 15 | map.put("INTERNETEXPLOER", InternetExplorerFactory::getInstance); 16 | map.put("PHANTOMJS", PhantomJsFactory::getInstance); 17 | map.put("OPERA", OperaFactory::getInstance); 18 | } 19 | 20 | public static DriverManager getManager(String browserName) { 21 | Supplier driverManagerSupplier = map.get(browserName.toUpperCase()); 22 | if (driverManagerSupplier != null) { 23 | return driverManagerSupplier.get(); 24 | } 25 | throw new IllegalArgumentException("No such browser webdriver: " + browserName); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/EdgeFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.openqa.selenium.edge.EdgeDriver; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 9 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 10 | 11 | public class EdgeFactory extends DriverManager { 12 | 13 | private static final Logger logger = 14 | LoggerFactory.getLogger(EdgeFactory.class); 15 | private static EdgeFactory instance = new EdgeFactory(); 16 | 17 | private EdgeFactory() { 18 | super(); 19 | } 20 | 21 | public synchronized static EdgeFactory getInstance() { 22 | return instance; 23 | } 24 | 25 | @Override 26 | public WebDriver getDriver() { 27 | driver = new EdgeDriver(); 28 | return driver; 29 | } 30 | 31 | @Override 32 | protected WebDriver buildRemoteWebDriver() { 33 | return null; 34 | } 35 | 36 | @Override 37 | public String getExportParameterKey() { 38 | return removeQuoteMark(getConfigInstance().edgeDriverProperty()); 39 | } 40 | 41 | @Override 42 | public Logger getLogger() { 43 | return logger; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/FirefoxFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.openqa.selenium.firefox.FirefoxDriver; 5 | import org.openqa.selenium.firefox.FirefoxOptions; 6 | import org.openqa.selenium.remote.DesiredCapabilities; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.testng.util.Strings; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | import static com.github.ansonliao.selenium.utils.CapsUtils.getCaps; 15 | import static com.github.ansonliao.selenium.utils.CapsUtils.getCliArgs; 16 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 17 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 18 | import static java.util.stream.Collectors.toList; 19 | import static org.openqa.selenium.remote.BrowserType.FIREFOX; 20 | 21 | /** 22 | * Selenium WebDriver desired capabilities: 23 | * https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities 24 | * https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode 25 | */ 26 | public class FirefoxFactory extends DriverManager { 27 | 28 | private static final Logger logger = 29 | LoggerFactory.getLogger(FirefoxFactory.class); 30 | private static FirefoxFactory instance = new FirefoxFactory(); 31 | private FirefoxOptions options = new FirefoxOptions(); 32 | 33 | private FirefoxFactory() { 34 | super(); 35 | } 36 | 37 | public synchronized static FirefoxFactory getInstance() { 38 | return instance; 39 | } 40 | 41 | @Override 42 | public WebDriver getDriver() { 43 | List argList = getCliArgs(FIREFOX); 44 | Map caps = getCaps(FIREFOX); 45 | if (isHeadless) { 46 | if (argList.parallelStream().filter(arg -> String.valueOf(arg).toLowerCase().contains("headless")) 47 | .map(String::valueOf).anyMatch(s -> Strings.isNotNullAndNotEmpty(s))) { 48 | argList.add("--headless"); 49 | } 50 | } 51 | DesiredCapabilities capabilities = DesiredCapabilities.firefox(); 52 | caps.forEach((k, v) -> capabilities.setCapability(k, v)); 53 | caps.forEach((k, v) -> options.setCapability(k, v)); 54 | options.addArguments(argList.parallelStream() 55 | .map(String::valueOf) 56 | .map(String::trim) 57 | .map(String::toLowerCase) 58 | .distinct().collect(toList())); 59 | driver = Strings.isNullOrEmpty(SELENIUM_HUB_URL) 60 | ? new FirefoxDriver(options) 61 | : getDriver(capabilities, SELENIUM_HUB_URL); 62 | return driver; 63 | } 64 | 65 | @Override 66 | protected WebDriver buildRemoteWebDriver() { 67 | return null; 68 | } 69 | 70 | @Override 71 | public String getExportParameterKey() { 72 | return removeQuoteMark(getConfigInstance().firefoxDriverProperty()); 73 | } 74 | 75 | @Override 76 | public Logger getLogger() { 77 | return logger; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/InternetExplorerFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.openqa.selenium.ie.InternetExplorerDriver; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 9 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 10 | 11 | public class InternetExplorerFactory extends DriverManager { 12 | 13 | private static final Logger logger = 14 | LoggerFactory.getLogger(InternetExplorerFactory.class); 15 | private static InternetExplorerFactory instance = new InternetExplorerFactory(); 16 | 17 | private InternetExplorerFactory() { 18 | super(); 19 | } 20 | 21 | public synchronized static InternetExplorerFactory getInstance() { 22 | return instance; 23 | } 24 | 25 | @Override 26 | public WebDriver getDriver() { 27 | driver = new InternetExplorerDriver(); 28 | return driver; 29 | } 30 | 31 | @Override 32 | protected WebDriver buildRemoteWebDriver() { 33 | return null; 34 | } 35 | 36 | @Override 37 | public String getExportParameterKey() { 38 | return removeQuoteMark(getConfigInstance().ieDriverProperty()); 39 | } 40 | 41 | @Override 42 | public Logger getLogger() { 43 | return logger; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/OperaFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.openqa.selenium.opera.OperaDriver; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 9 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 10 | 11 | public class OperaFactory extends DriverManager { 12 | 13 | private static final Logger logger = 14 | LoggerFactory.getLogger(OperaFactory.class); 15 | private static OperaFactory instance = new OperaFactory(); 16 | 17 | private OperaFactory() { 18 | super(); 19 | } 20 | 21 | public synchronized static OperaFactory getInstance() { 22 | return instance; 23 | } 24 | 25 | @Override 26 | public WebDriver getDriver() { 27 | driver = new OperaDriver(); 28 | return driver; 29 | } 30 | 31 | @Override 32 | protected WebDriver buildRemoteWebDriver() { 33 | return null; 34 | } 35 | 36 | @Override 37 | public String getExportParameterKey() { 38 | return removeQuoteMark(getConfigInstance().operaDriverProperty()); 39 | } 40 | 41 | @Override 42 | public Logger getLogger() { 43 | return logger; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/PhantomJsFactory.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 8 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 9 | 10 | @Deprecated 11 | public class PhantomJsFactory extends DriverManager { 12 | 13 | private static final Logger logger = 14 | LoggerFactory.getLogger(PhantomJsFactory.class); 15 | private static PhantomJsFactory instance = new PhantomJsFactory(); 16 | 17 | private PhantomJsFactory() { 18 | super(); 19 | } 20 | 21 | public synchronized static PhantomJsFactory getInstance() { 22 | return instance; 23 | } 24 | 25 | @Override 26 | public WebDriver getDriver() { 27 | return null; 28 | } 29 | 30 | @Override 31 | protected WebDriver buildRemoteWebDriver() { 32 | return null; 33 | } 34 | 35 | @Override 36 | public String getExportParameterKey() { 37 | return removeQuoteMark(getConfigInstance().phantomjsDriverProperty()); 38 | } 39 | 40 | @Override 41 | public Logger getLogger() { 42 | return logger; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/factory/WDManager.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.factory; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | 5 | public class WDManager { 6 | 7 | private static ThreadLocal webDriverThreadLocal = new ThreadLocal<>(); 8 | 9 | public synchronized static WebDriver getDriver() { 10 | return webDriverThreadLocal.get(); 11 | } 12 | 13 | public synchronized static void setDriver(WebDriver driver) { 14 | webDriverThreadLocal.set(driver); 15 | } 16 | 17 | public synchronized static void quitDriver() { 18 | getDriver().quit(); 19 | } 20 | 21 | public synchronized static void removeDriver() { 22 | webDriverThreadLocal.remove(); 23 | } 24 | 25 | public synchronized static void quitAndClearDriver() { 26 | quitDriver(); 27 | removeDriver(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/internal/OptionalConsumer.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.internal; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Consumer; 5 | 6 | public class OptionalConsumer { 7 | private Optional optional; 8 | 9 | private OptionalConsumer(Optional optional) { 10 | this.optional = optional; 11 | } 12 | 13 | public static OptionalConsumer of(Optional optional) { 14 | return new OptionalConsumer<>(optional); 15 | } 16 | 17 | public OptionalConsumer ifPresent(Consumer c) { 18 | optional.ifPresent(c); 19 | return this; 20 | } 21 | 22 | public OptionalConsumer orElse(Runnable r) { 23 | if (!optional.isPresent()) { 24 | r.run(); 25 | } 26 | return this; 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/internal/OptionalEx.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.internal; 2 | 3 | import java.util.Optional; 4 | import java.util.function.Consumer; 5 | 6 | public class OptionalEx { 7 | private boolean isPresent; 8 | 9 | private OptionalEx(boolean isPresent) { 10 | this.isPresent = isPresent; 11 | } 12 | 13 | public static OptionalEx ifPresent(Optional opt, Consumer consumer) { 14 | if (opt.isPresent()) { 15 | consumer.accept(opt.get()); 16 | return new OptionalEx(true); 17 | } 18 | return new OptionalEx(false); 19 | } 20 | 21 | public void orElse(Runnable runner) { 22 | if (!isPresent) { 23 | runner.run(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/internal/ScreenshotManager.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.internal; 2 | 3 | import com.github.ansonliao.selenium.factory.WDManager; 4 | import com.github.ansonliao.selenium.utils.MyFileUtils; 5 | import org.openqa.selenium.OutputType; 6 | import org.openqa.selenium.TakesScreenshot; 7 | import ru.yandex.qatools.ashot.AShot; 8 | 9 | import javax.imageio.ImageIO; 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.sql.Timestamp; 13 | 14 | public class ScreenshotManager { 15 | private static final String FILE_SEPARATOR = File.separator; 16 | private static final String PROJECT_ROOT_DIR = System.getProperty("user.dir"); 17 | private static final String SCREENSHOT_DIR = PROJECT_ROOT_DIR 18 | .concat(FILE_SEPARATOR) 19 | .concat("target") 20 | .concat(FILE_SEPARATOR) 21 | .concat("screenshots"); 22 | 23 | public String capture(Class clazz, String imgPrefix, String browserName) { 24 | // File scrFile = ((TakesScreenshot) WDManager.getDriver()) 25 | // .getScreenshotAs(OutputType.FILE); 26 | 27 | String destDir = String.join( 28 | FILE_SEPARATOR, 29 | SCREENSHOT_DIR, 30 | clazz.getPackage().getName(), 31 | clazz.getSimpleName(), 32 | browserName, 33 | imgPrefix.concat("_").concat(String.valueOf( 34 | new Timestamp(System.currentTimeMillis()).getTime())) 35 | .concat(".jpeg")); 36 | try { 37 | // MyFileUtils.copyFile(scrFile, new File(destDir)); 38 | ImageIO.write( 39 | new AShot().takeScreenshot(WDManager.getDriver()).getImage(), 40 | "PNG", 41 | new File(destDir)); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | return destDir.replace( 46 | PROJECT_ROOT_DIR 47 | .concat(FILE_SEPARATOR) 48 | .concat("target") 49 | .concat(FILE_SEPARATOR), ""); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/json/JsonLoader.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.json; 2 | 3 | 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonElement; 6 | import org.aeonbits.owner.loaders.Loader; 7 | 8 | import java.io.IOException; 9 | import java.net.MalformedURLException; 10 | import java.net.URI; 11 | import java.net.URL; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Properties; 18 | 19 | public class JsonLoader implements Loader { 20 | 21 | public boolean accept(URI uri) { 22 | try { 23 | URL url = uri.toURL(); 24 | return url.getFile().toLowerCase().endsWith(".json"); 25 | } catch (MalformedURLException ex) { 26 | return false; 27 | } 28 | } 29 | 30 | public void load(Properties result, URI uri) throws IOException { 31 | Path path = Paths.get(uri); 32 | List list = Files.readAllLines(path); 33 | parseJsonString(String.join(System.lineSeparator(), list), result); 34 | } 35 | 36 | public String defaultSpecFor(String uriPrefix) { 37 | return uriPrefix + ".json"; 38 | } 39 | 40 | private void parseJsonString(String json, Properties result) { 41 | com.google.gson.JsonParser parser = new com.google.gson.JsonParser(); 42 | JsonElement element = parser.parse(json); 43 | parseJsonElement(element, result, ""); 44 | } 45 | 46 | private void parseJsonElement(JsonElement element, Properties result, String targetKey) { 47 | if (element.isJsonObject()) { 48 | for (String key : element.getAsJsonObject().keySet()) { 49 | parseJsonElement( 50 | element.getAsJsonObject().get(key), 51 | result, 52 | targetKey + (targetKey.isEmpty() ? "" : ".") + key); 53 | } 54 | } else if (element.isJsonArray()) { 55 | JsonArray jsonArray = element.getAsJsonArray(); 56 | List list = new ArrayList<>(); 57 | for (int i = 0; i < jsonArray.size(); i++) { 58 | JsonElement jsonElement = jsonArray.get(i); 59 | if (jsonElement.isJsonObject()) { 60 | parseJsonElement(jsonElement, result, targetKey + "[" + i + "]"); 61 | } else if (jsonElement.isJsonPrimitive()) { 62 | list.add(jsonElement.getAsString()); 63 | } 64 | } 65 | if (!list.isEmpty()) { 66 | result.put(targetKey, String.join(", ", list)); 67 | } 68 | } else { 69 | result.put(targetKey, element.getAsString()); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/json/JsonParser.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.json; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonArray; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonNull; 7 | import com.google.gson.JsonObject; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.FileNotFoundException; 11 | import java.io.FileReader; 12 | import java.util.List; 13 | 14 | public class JsonParser { 15 | 16 | private static final Gson GSON_INSTANCE = new Gson(); 17 | 18 | public static Gson getGsonInstance() { 19 | return GSON_INSTANCE; 20 | } 21 | 22 | public synchronized static JsonElement getJsonElement(JsonElement json, String path) { 23 | String[] parts = path.split("\\.|\\[|\\]"); 24 | JsonElement result = json; 25 | 26 | for (String key : parts) { 27 | key = key.trim(); 28 | if (key.isEmpty()) { 29 | continue; 30 | } 31 | if (result == null) { 32 | result = JsonNull.INSTANCE; 33 | break; 34 | } 35 | if (result.isJsonObject()) { 36 | result = ((JsonObject) result).get(key); 37 | } else if (result.isJsonArray()) { 38 | int ix = Integer.valueOf(key) - 1; 39 | result = ((JsonArray) result).get(ix); 40 | } else { 41 | break; 42 | } 43 | } 44 | return result; 45 | } 46 | 47 | // public synchronized static Map getMapNode(JsonElement json, String path) { 48 | // if (!isNodeExisted(json, path)) { 49 | // return Maps.newHashMap(); 50 | // } 51 | // JsonElement element = getJsonElement(json, path); 52 | // return getGsonInstance().fromJson(element.toString(), Map.class); 53 | // } 54 | // 55 | // public synchronized static List getArrayNodeAsList(JsonElement json, String path) { 56 | // if (!isNodeExisted(json, path)) { 57 | // return Lists.newArrayList(); 58 | // } 59 | // JsonElement element = getJsonElement(json, path); 60 | // return getGsonInstance().fromJson(element.toString(), List.class); 61 | // } 62 | 63 | public synchronized static boolean isNodeExisted(JsonElement json, String path) { 64 | return json == null ? false : !(getJsonElement(json, path) == null); 65 | } 66 | 67 | public static void main(String[] args) throws FileNotFoundException { 68 | String jsonFile = "caps/caps.json"; 69 | BufferedReader reader = new BufferedReader(new FileReader(jsonFile)); 70 | 71 | Gson gson = new Gson(); 72 | JsonObject jsonObject = gson.fromJson(reader, JsonObject.class); 73 | System.out.println(isNodeExisted(jsonObject, "chrome")); 74 | System.out.println(getGsonInstance().fromJson( 75 | getJsonElement(jsonObject, "chrome.cli_args").toString(), List.class)); 76 | // System.out.println(isNodeExisted(jsonObject, "chrome.caps")); 77 | // JsonElement element = getJsonElement(jsonObject, "chrome.caps"); 78 | // System.out.println(element.toString()); 79 | // Map map = gson.fromJson(element.toString(), Map.class); 80 | // map.forEach((k, v) -> System.out.println("Key: " + k + ", value: " + v)); 81 | 82 | // List arglist = gson.fromJson(getJsonElement(jsonObject, "chrome.test"), List.class); 83 | // System.out.println(isNodeExisted(jsonObject, "chrome.test")); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/parallel/ClassFinder.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.parallel; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Sets; 5 | import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner; 6 | import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.testng.annotations.Test; 10 | 11 | import java.io.File; 12 | import java.net.URL; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 19 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 20 | import static java.util.stream.Collectors.toList; 21 | 22 | public class ClassFinder { 23 | private static final String PACKAGE_SEPARATOR = "."; 24 | private static final String DIR_SEPARATOR = File.separator; 25 | private static final String CLASS_FILE_SUFFIX = ".class"; 26 | private static final String TESTNG_TEST_CLASS_PREFIX = 27 | removeQuoteMark(getConfigInstance().testngTestClassPrefix().toLowerCase().trim()); 28 | private static final String BAD_PACKAGE_ERROR = 29 | "Unable to get resources from path '%s'. Are you sure the package '%s' exists?"; 30 | private static Logger logger = LoggerFactory.getLogger(ClassFinder.class); 31 | private static FastClasspathScanner classpathScanner; 32 | private static ScanResult scanResult; 33 | 34 | static { 35 | classpathScanner = new FastClasspathScanner().enableMethodAnnotationIndexing(); 36 | scanResult = classpathScanner.scan(); 37 | } 38 | 39 | public static List> findAllTestClassesInPackage(String scannedPackage) { 40 | List> testClasses = new ArrayList<>(); 41 | List> classes = find(scannedPackage); 42 | for (Class clazz : classes) { 43 | if (clazz.getSimpleName().toLowerCase().contains("test")) { 44 | testClasses.add(clazz); 45 | } 46 | } 47 | return testClasses; 48 | } 49 | 50 | public static List> find(String scannedPackage) { 51 | String scannedPath = scannedPackage.replace(PACKAGE_SEPARATOR, DIR_SEPARATOR); 52 | URL scannedUrl = Thread.currentThread().getContextClassLoader().getResource(scannedPath); 53 | if (scannedUrl == null) { 54 | throw new IllegalArgumentException( 55 | String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage)); 56 | } 57 | File scannedDir = new File(scannedUrl.getFile()); 58 | List> classes = Lists.newArrayList(); 59 | for (File file : scannedDir.listFiles()) { 60 | classes.addAll(find(file, scannedPackage)); 61 | } 62 | return classes; 63 | } 64 | 65 | private static List> find(File file, String scannedPackage) { 66 | List> classes = Lists.newArrayList(); 67 | String resource = scannedPackage + PACKAGE_SEPARATOR + file.getName(); 68 | if (file.isDirectory()) { 69 | for (File child : file.listFiles()) { 70 | classes.addAll(find(child, resource)); 71 | } 72 | } else if (resource.endsWith(CLASS_FILE_SUFFIX)) { 73 | int endIndex = resource.length() - CLASS_FILE_SUFFIX.length(); 74 | String className = resource.substring(0, endIndex); 75 | try { 76 | classes.add(Class.forName(className)); 77 | } catch (ClassNotFoundException e) { 78 | e.printStackTrace(); 79 | } 80 | } 81 | return classes; 82 | } 83 | 84 | public static Set> findAnnotatedTestClasses(List> classes) { 85 | Set> annotatedTestClasses = Sets.newHashSet(); 86 | classes.forEach(clazz -> { 87 | if (clazz.getName().toLowerCase().startsWith(TESTNG_TEST_CLASS_PREFIX)) { 88 | annotatedTestClasses.add(clazz); 89 | } 90 | }); 91 | 92 | return annotatedTestClasses; 93 | } 94 | 95 | public static List> findAllTestNGTestClasses(String... packages) { 96 | Set> classes = Sets.newHashSet(); 97 | //ScanResult result = classpathScanner.scan(); 98 | 99 | // find all testng test classes with class annotated @Test 100 | scanResult.getNamesOfClassesWithAnnotation(Test.class) 101 | .parallelStream() 102 | .map(className -> createClass(className)) 103 | .filter(clazz -> 104 | clazz.getSimpleName().toLowerCase().startsWith(TESTNG_TEST_CLASS_PREFIX)) 105 | .filter(clazz -> clazz.getAnnotation(Test.class).enabled()) 106 | .forEach(clazz -> classes.add(clazz)); 107 | 108 | // find all testng test classes with method annotated @Test 109 | scanResult.getNamesOfClassesWithMethodAnnotation(Test.class) 110 | .parallelStream() 111 | .map(className -> createClass(className)) 112 | .filter(clazz -> 113 | clazz.getSimpleName().toLowerCase().startsWith(TESTNG_TEST_CLASS_PREFIX)) 114 | .filter(clazz -> !clazz.isAnnotationPresent(Test.class) 115 | || clazz.getAnnotation(Test.class).enabled()) 116 | .forEach(clazz -> classes.add(clazz)); 117 | 118 | if (packages == null || packages.length == 0) { 119 | logger.info("Find all TestNG test classes in current project."); 120 | return classes.stream().collect(toList()); 121 | } 122 | 123 | List packageNames = Arrays.asList(packages); 124 | logger.info("Find TestNG classes in package(s): " + packageNames); 125 | 126 | //TODO: replace forEach() with filter of stream 127 | classes.stream().collect(toList()).forEach(aClass -> { 128 | if (!packageNames.contains(aClass.getPackage().getName())) { 129 | classes.remove(aClass); 130 | } 131 | }); 132 | 133 | return classes.stream().collect(toList()); 134 | } 135 | 136 | public synchronized static Class createClass(String className) { 137 | Class clazz = null; 138 | try { 139 | clazz = Class.forName(className); 140 | } catch (ClassNotFoundException e) { 141 | e.printStackTrace(); 142 | } 143 | 144 | return clazz; 145 | } 146 | 147 | public static ScanResult getScanAllClassesResult() { 148 | return scanResult; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/parallel/MethodFinder.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.parallel; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.testng.annotations.Test; 7 | 8 | import java.lang.reflect.Method; 9 | import java.util.List; 10 | 11 | import static java.util.stream.Collectors.toList; 12 | 13 | public class MethodFinder { 14 | private static Logger logger = LoggerFactory.getLogger(MethodFinder.class); 15 | 16 | public static List findAllAnnotatedTestMethodInClass(Class clazz) { 17 | logger.info("Find all @Test method in class: " + clazz.getName()); 18 | return findMethodInClass(clazz).stream().distinct() 19 | .filter(method -> method.isAnnotationPresent(Test.class)) 20 | .collect(toList()); 21 | } 22 | 23 | public static List findMethodInClass(Class clazz) { 24 | logger.info("Find all declared methods in class: " + clazz.getName()); 25 | return Lists.newArrayList(clazz.getDeclaredMethods()).parallelStream() 26 | .distinct().collect(toList()); 27 | } 28 | 29 | public static List findTestNGMethodInClassByGroup(Class clazz, String groupName) { 30 | List methodList = Lists.newArrayList(); 31 | 32 | if (clazz.isAnnotationPresent(Test.class)) { 33 | Test t = (Test) clazz.getAnnotation(Test.class); 34 | methodList.addAll(Lists.newArrayList(clazz.getDeclaredMethods())); 35 | 36 | if (t.groups().length > 0) { 37 | List groups = Lists.newArrayList(t.groups()); 38 | if (groups.contains(groupName)) { 39 | return Lists.newArrayList(clazz.getDeclaredMethods()); 40 | } 41 | } 42 | } 43 | 44 | return Lists.newArrayList(clazz.getDeclaredMethods()) 45 | .parallelStream() 46 | .filter(m -> m.isAnnotationPresent(Test.class)) 47 | .filter(m -> Lists.newArrayList(m.getAnnotation(Test.class).groups()).contains(groupName)) 48 | .collect(toList()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/parallel/SeleniumParallel.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.parallel; 2 | 3 | import com.aventstack.extentreports.Status; 4 | import com.github.ansonliao.selenium.annotations.Description; 5 | import com.github.ansonliao.selenium.annotations.Headless; 6 | import com.github.ansonliao.selenium.annotations.Incognito; 7 | import com.github.ansonliao.selenium.annotations.URL; 8 | import com.github.ansonliao.selenium.factory.DriverManager; 9 | import com.github.ansonliao.selenium.report.factory.ExtentTestManager; 10 | import com.github.ansonliao.selenium.utils.MyFileUtils; 11 | import com.thoughtworks.qdox.JavaProjectBuilder; 12 | import com.thoughtworks.qdox.model.DocletTag; 13 | import com.thoughtworks.qdox.model.JavaClass; 14 | import com.thoughtworks.qdox.model.JavaMethod; 15 | import org.openqa.selenium.OutputType; 16 | import org.openqa.selenium.TakesScreenshot; 17 | import org.openqa.selenium.WebDriver; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.testng.annotations.Test; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.lang.reflect.Method; 25 | import java.sql.Timestamp; 26 | import java.util.Arrays; 27 | import java.util.HashSet; 28 | import java.util.List; 29 | import java.util.Set; 30 | import java.util.stream.Collectors; 31 | 32 | public class SeleniumParallel { 33 | private static final String FILE_SEPARATOR = File.separator; 34 | private static final String PROJECT_ROOT_DIR = System.getProperty("user.dir"); 35 | private static final String SCREENSHOT_DIR = PROJECT_ROOT_DIR 36 | .concat(FILE_SEPARATOR) 37 | .concat("target") 38 | .concat(FILE_SEPARATOR) 39 | .concat("screenshots"); 40 | protected static Logger logger = 41 | LoggerFactory.getLogger(SeleniumParallel.class); 42 | protected static JavaProjectBuilder javaProjectBuilder = 43 | new JavaProjectBuilder(); 44 | protected DriverManager driverManager; 45 | protected String browserName; 46 | protected String url; 47 | private WebDriver driver; 48 | 49 | public String findUrl(Method method) { 50 | if (method.isAnnotationPresent(URL.class)) { 51 | url = method.getAnnotation(URL.class).value(); 52 | } else if (method.getDeclaringClass().isAnnotationPresent(URL.class)) { 53 | url = method.getDeclaringClass().getAnnotation(URL.class).value(); 54 | } else { 55 | url = null; 56 | } 57 | 58 | return url; 59 | } 60 | 61 | public String getUrl() { 62 | return url; 63 | } 64 | 65 | public WebDriver startWebDriver(Method method) { 66 | driverManager.setIncognito(method.isAnnotationPresent(Incognito.class) ? true : false); 67 | driverManager.setHeadless(method.isAnnotationPresent(Headless.class) ? true : false); 68 | driver = driverManager.getDriver(); 69 | return driver; 70 | } 71 | 72 | public WebDriver getDriver() { 73 | return this.driver; 74 | } 75 | 76 | public void setDriver(WebDriver driver) { 77 | this.driver = driver; 78 | } 79 | 80 | public WebDriver openUrl(String url) { 81 | getDriver().get(url); 82 | ExtentTestManager.getExtentTest().log( 83 | Status.INFO, 84 | withBoldHTML("Test Start")); 85 | ExtentTestManager.getExtentTest().log(Status.INFO, 86 | withBoldHTML("Open URL: ") + url); 87 | return driver; 88 | } 89 | 90 | protected String takeScreenShot(String imgPrefix) throws IOException { 91 | File scrFile = ((TakesScreenshot) getDriver()).getScreenshotAs(OutputType.FILE); 92 | 93 | String destDir = SCREENSHOT_DIR 94 | .concat(FILE_SEPARATOR) 95 | .concat(this.getClass().getPackage().getName()) 96 | .concat(FILE_SEPARATOR) 97 | .concat(this.getClass().getSimpleName()) 98 | .concat(FILE_SEPARATOR) 99 | .concat(this.browserName) 100 | .concat(FILE_SEPARATOR) 101 | .concat(imgPrefix) 102 | .concat("_") 103 | .concat(String.valueOf(new Timestamp(System.currentTimeMillis()).getTime())) 104 | .concat(".jpeg"); 105 | MyFileUtils.copyFile(scrFile, new File(destDir)); 106 | return destDir.replace( 107 | PROJECT_ROOT_DIR 108 | .concat(FILE_SEPARATOR) 109 | .concat("target") 110 | .concat(FILE_SEPARATOR), ""); 111 | } 112 | 113 | public List getAuthors(String className, Method method) { 114 | logger.info("Get authors of Class = " + className); 115 | 116 | javaProjectBuilder.addSourceTree(new File( 117 | PROJECT_ROOT_DIR 118 | .concat(FILE_SEPARATOR) 119 | .concat("src"))); 120 | JavaClass cls = javaProjectBuilder.getClassByName(className); 121 | List authors = cls.getTags().stream() 122 | .filter(tag -> tag.getName().equals("author") || tag.getName().equals("author:")) 123 | .collect(Collectors.toList()); 124 | logger.info("Class: {}, authors: {} ", className, authors.toString()); 125 | 126 | // get class authors as default author name 127 | Set allAuthors = new HashSet<>(); 128 | if (authors.size() != 0) { 129 | for (DocletTag author : authors) { 130 | if (author.getValue().trim().length() > 0) { 131 | allAuthors.add(author.getValue().trim()); 132 | } 133 | } 134 | } 135 | 136 | // get method author 137 | List methods = cls.getMethods(); 138 | logger.info("Get authors of method = " + methods.toString()); 139 | JavaMethod mth = null; 140 | for (JavaMethod m : methods) { 141 | if (m.getName().equalsIgnoreCase(method.getName())) { 142 | mth = m; 143 | break; 144 | } 145 | } 146 | 147 | authors = mth.getTags().stream() 148 | .filter(tag -> tag.getName().equals("author") || tag.getName().equals("author:")) 149 | .collect(Collectors.toList()); 150 | if (authors.size() != 0) { 151 | allAuthors.clear(); 152 | for (DocletTag author : authors) { 153 | if (author.getValue().trim().length() > 0) { 154 | allAuthors.add(author.getValue().trim()); 155 | } 156 | } 157 | } 158 | 159 | return allAuthors.stream().collect(Collectors.toList()); 160 | } 161 | 162 | public List getTestGroups(Object object) { 163 | Set allGroups = new HashSet<>(); 164 | Test t; 165 | 166 | if (object instanceof Class) { 167 | Class clazz = (Class) object; 168 | t = (Test) clazz.getAnnotation(Test.class); 169 | if (t != null && t.groups().length > 0) { 170 | allGroups.addAll(Arrays.asList(t.groups())); 171 | } 172 | return allGroups.stream().collect(Collectors.toList()); 173 | } else { 174 | Class clazz = ((Method) object).getDeclaringClass(); 175 | t = (Test) clazz.getAnnotation(Test.class); 176 | if (t != null && t.groups().length > 0) { 177 | allGroups.addAll(Arrays.asList(t.groups())); 178 | } 179 | 180 | t = ((Method) object).getAnnotation(Test.class); 181 | if (t != null && t.groups().length > 0) { 182 | allGroups.addAll(Arrays.asList(t.groups())); 183 | } 184 | return allGroups.stream().collect(Collectors.toList()); 185 | } 186 | } 187 | 188 | public String getDescription(Object object) { 189 | String description = null; 190 | if (object instanceof Class) { 191 | Class clazz = (Class) object; 192 | if (clazz.isAnnotationPresent(Description.class)) { 193 | return ((Description) clazz.getAnnotation(Description.class)) 194 | .value().trim(); 195 | } 196 | } 197 | if (object instanceof Method) { 198 | Method method = (Method) object; 199 | if (method.isAnnotationPresent(Description.class)) { 200 | return method.getAnnotation(Description.class) 201 | .value().trim(); 202 | } 203 | } 204 | 205 | return description; 206 | } 207 | 208 | public String withBoldHTML(String s) { 209 | return !s.trim().isEmpty() 210 | ? "" + s + "" 211 | : ""; 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/parallel/SeleniumParallelTestListener.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.parallel; 2 | 3 | import com.aventstack.extentreports.Status; 4 | import com.github.ansonliao.selenium.annotations.Headless; 5 | import com.github.ansonliao.selenium.annotations.Incognito; 6 | import com.github.ansonliao.selenium.annotations.URL; 7 | import com.github.ansonliao.selenium.factory.DriverManager; 8 | import com.github.ansonliao.selenium.factory.DriverManagerFactory; 9 | import com.github.ansonliao.selenium.report.factory.ExtentTestManager; 10 | import com.github.ansonliao.selenium.utils.AuthorUtils; 11 | import com.github.ansonliao.selenium.utils.MyFileUtils; 12 | import com.github.ansonliao.selenium.utils.TestGroupUtils; 13 | import com.github.ansonliao.selenium.utils.WDMHelper; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.testng.IClassListener; 17 | import org.testng.IInvokedMethod; 18 | import org.testng.IInvokedMethodListener; 19 | import org.testng.ISuite; 20 | import org.testng.ISuiteListener; 21 | import org.testng.ITestClass; 22 | import org.testng.ITestResult; 23 | import org.testng.util.Strings; 24 | 25 | import java.lang.reflect.Method; 26 | import java.util.List; 27 | import java.util.Optional; 28 | 29 | import static com.github.ansonliao.selenium.factory.WDManager.getDriver; 30 | import static com.github.ansonliao.selenium.factory.WDManager.setDriver; 31 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 32 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 33 | import static java.util.stream.Collectors.toList; 34 | 35 | public class SeleniumParallelTestListener implements IClassListener, IInvokedMethodListener, ISuiteListener { 36 | private static final Logger logger = 37 | LoggerFactory.getLogger(SeleniumParallelTestListener.class); 38 | private static final String TESTNG_BROWSER_PARAM_KEY = 39 | removeQuoteMark(getConfigInstance().testngXmlBrowserParamKey()); 40 | 41 | @Override 42 | public void beforeInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) { 43 | String browserName = iInvokedMethod.getTestMethod().getXmlTest() 44 | .getParameter(TESTNG_BROWSER_PARAM_KEY); 45 | 46 | Method method = 47 | iInvokedMethod.getTestMethod().getConstructorOrMethod().getMethod(); 48 | DriverManager driverManager = 49 | DriverManagerFactory.getManager(browserName); 50 | driverManager.setHeadless(method.isAnnotationPresent(Headless.class)); 51 | driverManager.setIncognito(method.isAnnotationPresent(Incognito.class)); 52 | setDriver(driverManager.getDriver()); 53 | 54 | List groups = TestGroupUtils.getMethodTestGroups(method); 55 | if (getConfigInstance().addBrowserGroupToReport()) { 56 | groups.add(browserName); 57 | } 58 | 59 | ExtentTestManager.createTest( 60 | method, browserName, AuthorUtils.getMethodAuthors(method), 61 | groups, iTestResult.getParameters()); 62 | 63 | // open url if URL annotation had value 64 | Optional.ofNullable(method.getAnnotation(URL.class)) 65 | .ifPresent(url -> openRemoteURL(url.value().trim())); 66 | } 67 | 68 | @Override 69 | public void afterInvocation(IInvokedMethod iInvokedMethod, ITestResult iTestResult) { 70 | 71 | } 72 | 73 | @Override 74 | public void onStart(ISuite iSuite) { 75 | /** TODO: 76 | * until WDM 1.7.2, download WebDriver binary only works at single thread, 77 | * upgrade WebDriver binary to Async with multi-threads once WDM support 78 | * multi-thread download 79 | */ 80 | final String SELENIUM_HUB_URL = getConfigInstance().seleniumHubUrl(); 81 | if (Strings.isNullOrEmpty(SELENIUM_HUB_URL)) { 82 | List browserList = iSuite.getXmlSuite().getTests().stream() 83 | .map(xmlTest -> removeQuoteMark(xmlTest.getParameter(TESTNG_BROWSER_PARAM_KEY))) 84 | .distinct().collect(toList()); 85 | 86 | browserList.parallelStream().forEach(WDMHelper::downloadWebDriverBinary); 87 | logger.info("Completed WebDriver binary download: {}", browserList); 88 | return; 89 | } 90 | logger.info("Selenium Hub found: [{}], WebDriver binaries will not be downloaded.", SELENIUM_HUB_URL); 91 | } 92 | 93 | @Override 94 | public void onFinish(ISuite iSuite) { 95 | 96 | } 97 | 98 | @Override 99 | public void onBeforeClass(ITestClass iTestClass) { 100 | String browserName = 101 | iTestClass.getXmlTest().getParameter(TESTNG_BROWSER_PARAM_KEY); 102 | MyFileUtils.createScreenshotFolderForBrowser(iTestClass.getRealClass(), browserName); 103 | } 104 | 105 | @Override 106 | public void onAfterClass(ITestClass iTestClass) { 107 | 108 | } 109 | 110 | private void openRemoteURL(String url) { 111 | if (Strings.isNotNullAndNotEmpty(url)) { 112 | getDriver().get(url); 113 | ExtentTestManager.getExtentTest().log( 114 | Status.INFO, 115 | String.format("Open URL: %s", url)); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/report/factory/ExtentManager.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.report.factory; 2 | 3 | import com.aventstack.extentreports.ExtentReports; 4 | import com.aventstack.extentreports.reporter.ExtentHtmlReporter; 5 | import com.aventstack.extentreports.reporter.configuration.ChartLocation; 6 | import com.aventstack.extentreports.reporter.configuration.Theme; 7 | 8 | import java.io.File; 9 | import java.net.URL; 10 | 11 | public class ExtentManager { 12 | private static final String PROJECT_ROOT_DIR = System.getProperty("user.dir"); 13 | private static final String FILE_SEPARATOR = File.separator; 14 | private static ExtentReports extentReports; 15 | private static String filePath = PROJECT_ROOT_DIR 16 | .concat(FILE_SEPARATOR) 17 | .concat("target") 18 | .concat(FILE_SEPARATOR) 19 | .concat("ExtentReports.html"); 20 | 21 | public synchronized static ExtentReports getExtentReports() { 22 | if (extentReports == null) { 23 | extentReports = new ExtentReports(); 24 | extentReports.attachReporter(getHtmlReporter()); 25 | extentReports.setSystemInfo("Selenium Java Version", System.getProperty("java.version")); 26 | extentReports.setSystemInfo("Environment", "Prod"); 27 | extentReports.setSystemInfo("Selenium Java Client", "3"); 28 | } 29 | 30 | return extentReports; 31 | } 32 | 33 | private static ExtentHtmlReporter getHtmlReporter() { 34 | ExtentHtmlReporter htmlReporter = new ExtentHtmlReporter(filePath); 35 | URL inputUrl = null; 36 | try { 37 | inputUrl = Thread.currentThread().getContextClassLoader().getResource("extent.xml"); 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | File dest = new File( 42 | PROJECT_ROOT_DIR 43 | .concat(FILE_SEPARATOR) 44 | .concat("target") 45 | .concat(FILE_SEPARATOR) 46 | .concat("extent.xml")); 47 | 48 | htmlReporter.config().setChartVisibilityOnOpen(true); 49 | htmlReporter.config().setDocumentTitle("Selenium Web UI Test"); 50 | htmlReporter.config().setReportName("Selenium Web UI Test"); 51 | htmlReporter.config().setTestViewChartLocation(ChartLocation.TOP); 52 | htmlReporter.config().setTheme(Theme.STANDARD); 53 | return htmlReporter; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/report/factory/ExtentTestManager.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.report.factory; 2 | 3 | import com.aventstack.extentreports.ExtentReports; 4 | import com.aventstack.extentreports.ExtentTest; 5 | import com.aventstack.extentreports.Status; 6 | import com.github.ansonliao.selenium.annotations.Description; 7 | import com.google.common.collect.Lists; 8 | import com.google.common.collect.Maps; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.testng.Reporter; 12 | import org.testng.util.Strings; 13 | 14 | import java.lang.reflect.Method; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | import static java.util.stream.Collectors.joining; 19 | 20 | public class ExtentTestManager { 21 | private static final Logger sfl4jLogger = LoggerFactory.getLogger(ExtentTestManager.class); 22 | public static ThreadLocal extentTests = new ThreadLocal<>(); 23 | public static ExtentReports extentReport = ExtentManager.getExtentReports(); 24 | public static Map parentTests = Maps.newHashMap(); 25 | public static Map childTests = Maps.newHashMap(); 26 | public static ThreadLocal grandTests = new ThreadLocal<>(); 27 | private static ExtentTest extentTest; 28 | 29 | public synchronized static ExtentTest getExtentTest() { 30 | return grandTests.get(); 31 | } 32 | 33 | public synchronized static ExtentTest createTest( 34 | String name, String description, String browserType) { 35 | extentTest = extentReport.createTest(name, description) 36 | .assignCategory(browserType); 37 | extentTests.set(extentTest); 38 | return extentTests.get(); 39 | } 40 | 41 | public synchronized static ExtentTest createTest(String name, String description) { 42 | return createTest(name, description, String.valueOf(Thread.currentThread().getId())); 43 | } 44 | 45 | public synchronized static ExtentTest createTest(Method method, String browserName, 46 | List authors, List groups, Object... parameters) { 47 | ExtentTest test; 48 | String description; 49 | Class clazz = method.getDeclaringClass(); 50 | String className = clazz.getName(); 51 | 52 | if (!parentTests.containsKey(className)) { 53 | description = clazz.isAnnotationPresent(Description.class) 54 | ? ((Description) clazz.getAnnotation(Description.class)).value().trim() 55 | : ""; 56 | test = extentReport.createTest(className, description); 57 | parentTests.put(className, test); 58 | } 59 | 60 | String childNodeKey = String.join(".", clazz.getName(), method.getName()); 61 | if (!childTests.containsKey(childNodeKey)) { 62 | description = method.isAnnotationPresent(Description.class) 63 | ? (method.getAnnotation(Description.class)).value().trim() 64 | : ""; 65 | test = parentTests.get(className).createNode(method.getName(), description); 66 | if (authors != null && authors.size() > 0) { 67 | for (String author : authors) { 68 | test.assignAuthor(author); 69 | } 70 | } 71 | childTests.put(childNodeKey, test); 72 | } 73 | 74 | String extTestNodeName = ""; 75 | if (parameters != null && parameters.length != 0) { 76 | String paramStr = Lists.newArrayList(parameters).parallelStream() 77 | .map(param -> 78 | param instanceof String 79 | ? "\"".concat(param.toString()).concat("\"") 80 | : String.valueOf(param)) 81 | .collect(joining(", ")); 82 | extTestNodeName = extTestNodeName.concat("Parames: ").concat(paramStr); 83 | sfl4jLogger.info("Create ExtentReport test node: {}", extTestNodeName); 84 | } 85 | 86 | sfl4jLogger.info("Create ExtentReport test node: {}.{} - {}, description: {}", 87 | className, method.getName(), 88 | browserName, Strings.isNullOrEmpty(extTestNodeName) ? "N/A" : extTestNodeName); 89 | test = childTests.get(childNodeKey).createNode(browserName, extTestNodeName); 90 | if (groups != null && groups.size() > 0) { 91 | for (String group : groups) { 92 | test.assignCategory(group); 93 | } 94 | } 95 | grandTests.set(test); 96 | 97 | return test; 98 | } 99 | 100 | public synchronized static ExtentTest createTest(String name) { 101 | return createTest(name, "Sample Test"); 102 | } 103 | 104 | public synchronized static void logOutPut(String imgSrc, String headerName) { 105 | String imgPath = "
" 106 | + "

" 107 | + headerName + "

"; 109 | extentReport.setTestRunnerOutput(imgPath); 110 | } 111 | 112 | public synchronized static void logger(String message) { 113 | Reporter.log(message + "
", true); 114 | getExtentTest().log(Status.INFO, message + "
"); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/testng/RetryListener.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.testng; 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 | public class RetryListener implements IAnnotationTransformer { 11 | 12 | @Override 13 | public void transform(ITestAnnotation iTestAnnotation, Class aClass, 14 | Constructor constructor, Method method) { 15 | IRetryAnalyzer iRetryAnalyzer = iTestAnnotation.getRetryAnalyzer(); 16 | if (iRetryAnalyzer == null) { 17 | iTestAnnotation.setRetryAnalyzer(TestNGRetry.class); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/testng/TestNGFilter.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.testng; 2 | 3 | import com.github.ansonliao.selenium.parallel.ClassFinder; 4 | import com.github.ansonliao.selenium.parallel.MethodFinder; 5 | import com.github.ansonliao.selenium.utils.BrowserUtils; 6 | import com.github.ansonliao.selenium.utils.StringUtils; 7 | import com.google.common.collect.HashMultimap; 8 | import com.google.common.collect.Multimap; 9 | import com.google.common.collect.Sets; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.testng.annotations.Test; 13 | import org.testng.collections.Lists; 14 | 15 | import java.lang.reflect.Method; 16 | import java.util.ArrayList; 17 | import java.util.Collection; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Set; 22 | 23 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 24 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 25 | import static java.util.stream.Collectors.toList; 26 | import static java.util.stream.Collectors.toSet; 27 | 28 | public class TestNGFilter { 29 | private static final Logger logger = LoggerFactory.getLogger(TestNGFilter.class); 30 | private static List> testngClasses = Lists.newArrayList(); 31 | private static Multimap, Method> testNGClass2MethodMap = HashMultimap.create(); 32 | 33 | static { 34 | String[] testingPackageNames = getConfigInstance().testingPackageNames().parallelStream() 35 | .map(StringUtils::removeQuoteMark).collect(toList()) 36 | .toArray(new String[getConfigInstance().testingPackageNames().size()]); 37 | testngClasses = ClassFinder.findAllTestNGTestClasses(testingPackageNames); 38 | } 39 | 40 | public static Map, List>> run() { 41 | filterClassesByTestingClassList(); 42 | filterTestNGMethodByTestGroups(); 43 | filterTestNGMethodByBrowsers(); 44 | return getBrowser2TestNGClass2TestNGMethodMap(); 45 | } 46 | 47 | // filter testng classes by given testng class list 48 | public static List> filterClassesByTestingClassList() { 49 | if (!getConfigInstance().testingTestNGClasses().isEmpty()) { 50 | testngClasses = testngClasses.parallelStream() 51 | .filter(aClass -> 52 | getConfigInstance().testingTestNGClasses() 53 | .parallelStream() 54 | .map(StringUtils::removeQuoteMark) 55 | .map(String::toUpperCase) 56 | .collect(toList()) 57 | .contains(aClass.getCanonicalName().toUpperCase())) 58 | .distinct() 59 | .collect(toList()); 60 | return testngClasses; 61 | } 62 | return testngClasses; 63 | } 64 | 65 | /** 66 | * @return A map that key = testng class, value = list of testng method of the testng class 67 | */ 68 | public static Multimap, Method> filterTestNGMethodByTestGroups() { 69 | if (getConfigInstance().testingTestGroups().isEmpty()) { 70 | testngClasses.stream() 71 | .forEach(aClass -> 72 | MethodFinder.findAllAnnotatedTestMethodInClass(aClass).stream() 73 | .filter(method -> method.getAnnotation(Test.class).enabled()) 74 | .forEach(method -> testNGClass2MethodMap.put(aClass, method))); 75 | 76 | return testNGClass2MethodMap; 77 | } 78 | 79 | testngClasses.stream().forEach(aClass -> { 80 | List methodList = getConfigInstance().testingTestGroups() 81 | .parallelStream() 82 | .map(StringUtils::removeQuoteMark) 83 | .map(group -> MethodFinder.findTestNGMethodInClassByGroup(aClass, group)) 84 | .flatMap(Collection::stream) 85 | .distinct() 86 | .filter(method -> method.getAnnotation(Test.class).enabled()) 87 | .collect(toList()); 88 | if (methodList.size() > 0) { 89 | methodList.forEach(method -> testNGClass2MethodMap.put(aClass, method)); 90 | } 91 | }); 92 | 93 | return testNGClass2MethodMap; 94 | } 95 | 96 | // filter testng method by given test browser list 97 | public static Multimap, Method> filterTestNGMethodByBrowsers() { 98 | if (!getConfigInstance().testingBrowserNames().isEmpty()) { 99 | logger.info("Detected testing browser filter to: {}", getConfigInstance().testingBrowserNames()); 100 | Set browserFilters = getConfigInstance().testingBrowserNames() 101 | .parallelStream().map(String::toUpperCase).map(StringUtils::removeQuoteMark).collect(toSet()); 102 | Multimap, Method> resultMap = HashMultimap.create(testNGClass2MethodMap); 103 | testNGClass2MethodMap.keySet().stream().forEach(aClass -> 104 | testNGClass2MethodMap.get(aClass).stream().forEach(method -> { 105 | Sets.SetView result = 106 | Sets.intersection(BrowserUtils.getMethodSupportedBrowsers(method), browserFilters); 107 | if (result.size() == 0) { 108 | if (testNGClass2MethodMap.get(aClass).contains(method)) { 109 | resultMap.get(aClass).remove(method); 110 | } 111 | } 112 | })); 113 | testNGClass2MethodMap.clear(); 114 | testNGClass2MethodMap = HashMultimap.create(resultMap); 115 | return resultMap; 116 | } 117 | return testNGClass2MethodMap; 118 | } 119 | 120 | public static Map, List>> getBrowser2TestNGClass2TestNGMethodMap() { 121 | Map, List>> browserTestingMap = new HashMap<>(); 122 | Multimap browserTestngMethodMap = HashMultimap.create(); 123 | 124 | if (!getConfigInstance().runByBrowsers().isEmpty()) { 125 | // only fetch support browsers 126 | List browsers = 127 | Sets.intersection( 128 | getConfigInstance().runByBrowsers().parallelStream() 129 | .map(StringUtils::removeQuoteMark) 130 | .map(String::toUpperCase).collect(toSet()), 131 | Sets.newHashSet(BrowserUtils.getSupportedBrowsers())) 132 | .parallelStream().collect(toList()); 133 | 134 | logger.info("Run Tests by browsers: {}", browsers); 135 | 136 | if (browsers.isEmpty()) { 137 | return browserTestingMap; 138 | } 139 | 140 | browsers.parallelStream().map(String::trim).forEach(browserName -> 141 | testNGClass2MethodMap.keySet().forEach(aClass -> 142 | testNGClass2MethodMap.get(aClass).forEach(method -> { 143 | Set ignoreBrowsers = BrowserUtils.getMethodIgnoredBrowsers(method); 144 | if (ignoreBrowsers.isEmpty() || !ignoreBrowsers.contains(browserName)) { 145 | if (!browserTestingMap.containsKey(browserName)) { 146 | browserTestingMap.put(browserName, new HashMap<>()); 147 | } 148 | if (!browserTestingMap.get(browserName).containsKey(aClass)) { 149 | browserTestingMap.get(browserName).put(aClass, new ArrayList<>()); 150 | } 151 | if (!browserTestingMap.get(browserName).get(aClass).contains(method)) { 152 | browserTestingMap.get(browserName).get(aClass).add(method); 153 | } 154 | } else { 155 | String msg = "Class: [{}], Method: [{}] ignore browser [{}]" 156 | + " execute as @Ignore{} found"; 157 | logger.info(msg, 158 | aClass.getCanonicalName(), method.getName(), 159 | browserName, 160 | browserName.substring(0, 1).toUpperCase() 161 | + browserName.substring(1).toLowerCase()); 162 | } 163 | }))); 164 | return browserTestingMap; 165 | } 166 | 167 | testNGClass2MethodMap.keySet().forEach(aClass -> 168 | testNGClass2MethodMap.get(aClass).forEach(method -> { 169 | // add default browser if the testng method without any browser annotations 170 | Set methodSupportedBrowsers = addIncludedDefaultBrowser(method); 171 | methodSupportedBrowsers.forEach(browserName -> 172 | browserTestngMethodMap.put(browserName, method)); 173 | methodSupportedBrowsers.forEach(browserName -> { 174 | if (!browserTestingMap.containsKey(browserName)) { 175 | browserTestingMap.put(browserName, new HashMap<>()); 176 | } 177 | if (!browserTestingMap.get(browserName).containsKey(aClass)) { 178 | browserTestingMap.get(browserName).put(aClass, new ArrayList<>()); 179 | } 180 | if (!browserTestingMap.get(browserName).get(aClass).contains(method)) { 181 | browserTestingMap.get(browserName).get(aClass).add(method); 182 | } 183 | }); 184 | })); 185 | 186 | logger.info("Run Tests by browsers: {}", browserTestingMap.keySet()); 187 | return browserTestingMap; 188 | } 189 | 190 | private static Set addIncludedDefaultBrowser(Method method) { 191 | String DEFAULT_BROWSER_TYPE_NAME = removeQuoteMark(getConfigInstance().defaultBrowser().toUpperCase()); 192 | Set methodSupportedBrowsers = BrowserUtils.getMethodSupportedBrowsers(method); 193 | Set methodBrowsers = BrowserUtils.getMethodBrowsers(method); 194 | Set ignoreBrowsers = BrowserUtils.getMethodIgnoredBrowsers(method); 195 | if (methodBrowsers.isEmpty() && !ignoreBrowsers.contains(DEFAULT_BROWSER_TYPE_NAME)) { 196 | methodSupportedBrowsers.add(DEFAULT_BROWSER_TYPE_NAME); 197 | } 198 | return methodSupportedBrowsers; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/testng/TestNGRetry.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.testng; 2 | 3 | import com.github.ansonliao.selenium.annotations.Retry; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.testng.IRetryAnalyzer; 7 | import org.testng.ITestResult; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | public class TestNGRetry implements IRetryAnalyzer { 12 | private static final Logger logger = 13 | LoggerFactory.getLogger(TestNGRetry.class); 14 | private static int maxRetryCount; 15 | private int retryCount = 1; 16 | 17 | public static int getMaxRetryCount() { 18 | return maxRetryCount; 19 | } 20 | 21 | @Override 22 | public boolean retry(ITestResult iTestResult) { 23 | Class clazz = iTestResult.getMethod().getTestClass().getRealClass(); 24 | Method method = iTestResult.getMethod().getConstructorOrMethod() 25 | .getMethod(); 26 | 27 | //ifPresent(Optional.ofNullable(method.getAnnotation(Retry.class)), 28 | // retry -> { 29 | // maxRetryCount = retry.maxRetry(); 30 | // maxRetryCount = maxRetryCount >= 0 ? maxRetryCount : 0; }) 31 | // .ifPresent(Optional.ofNullable(clzz.getAnnotation(Retry.class)), 32 | // retry -> { 33 | // maxRetryCount = retry.maxRetry(); 34 | // maxRetryCount = maxRetryCount >= 0 ? maxRetryCount : 0; }) 35 | // .orElse(() -> maxRetryCount = 0); 36 | 37 | if (method.isAnnotationPresent(Retry.class)) { 38 | maxRetryCount = method.getAnnotation(Retry.class).maxRetry(); 39 | maxRetryCount = maxRetryCount >= 0 ? maxRetryCount : 0; 40 | } else if (clazz.isAnnotationPresent(Retry.class)) { 41 | maxRetryCount = clazz.getAnnotation(Retry.class).maxRetry(); 42 | maxRetryCount = maxRetryCount >= 0 ? maxRetryCount : 0; 43 | } else { 44 | maxRetryCount = 0; 45 | } 46 | 47 | if (retryCount <= maxRetryCount) { 48 | logger.info("Retry for Class: [{}], Method: [{}], RETRY TIME = {}", 49 | clazz.getName(), method.getName(), retryCount); 50 | retryCount++; 51 | return true; 52 | } 53 | 54 | // Support TestNG dataprovider retry 55 | if (maxRetryCount - retryCount == 1) { 56 | retryCount = 1; 57 | } 58 | 59 | return false; 60 | } 61 | 62 | public int getRetryCount() { 63 | return retryCount; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/testng/TestNGRunner.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.testng; 2 | 3 | import com.github.lalyos.jfiglet.FigletFont; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.testng.TestNG; 7 | import org.testng.xml.XmlSuite; 8 | 9 | import java.io.IOException; 10 | 11 | import static java.util.Arrays.asList; 12 | 13 | public class TestNGRunner { 14 | private static final Logger logger = LoggerFactory.getLogger(TestNGRunner.class); 15 | 16 | public static void Run() { 17 | XmlSuite xmlSuite = XmlSuiteBuilder.build(); 18 | TestNGRunner.Run(xmlSuite); 19 | } 20 | 21 | public static void Run(XmlSuite xmlSuite) { 22 | TestNG testNG = new TestNG(); 23 | testNG.setXmlSuites(asList(xmlSuite)); 24 | 25 | logFigletFont("Test Start"); 26 | testNG.run(); 27 | 28 | logFigletFont("Test Completed"); 29 | } 30 | 31 | private static void logFigletFont(String value) { 32 | try { 33 | logger.info("\n" + FigletFont.convertOneLine(value)); 34 | } catch (IOException e) { 35 | e.printStackTrace(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/testng/TestResultListener.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.testng; 2 | 3 | import com.aventstack.extentreports.MediaEntityBuilder; 4 | import com.aventstack.extentreports.Status; 5 | import com.github.ansonliao.selenium.factory.WDManager; 6 | import com.github.ansonliao.selenium.internal.ScreenshotManager; 7 | import com.github.ansonliao.selenium.report.factory.ExtentTestManager; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.testng.ITestContext; 11 | import org.testng.ITestNGMethod; 12 | import org.testng.ITestResult; 13 | import org.testng.TestListenerAdapter; 14 | 15 | import java.io.IOException; 16 | import java.util.Arrays; 17 | 18 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 19 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 20 | 21 | public class TestResultListener extends TestListenerAdapter { 22 | 23 | private static Logger logger = 24 | LoggerFactory.getLogger(TestListenerAdapter.class); 25 | 26 | @Override 27 | public void onTestFailure(ITestResult iTestResult) { 28 | super.onTestFailure(iTestResult); 29 | Class clazz = getTestRealClass(iTestResult); 30 | ITestNGMethod method = getTestMethod(iTestResult); 31 | String browserName = getTestBrowser(iTestResult); 32 | 33 | ScreenshotManager screenshotManager = new ScreenshotManager(); 34 | String imgPrefix = screenshotManager.capture(clazz, method.getMethodName(), browserName); 35 | ExtentTestManager.getExtentTest().fail(iTestResult.getThrowable()); 36 | 37 | try { 38 | ExtentTestManager.getExtentTest().fail( 39 | "Screenshot: ", 40 | MediaEntityBuilder.createScreenCaptureFromPath(imgPrefix).build()); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | } 44 | 45 | logger.info("Test fail: {} - {} - {}, screenshot - {}", 46 | clazz.getName(), method.getMethodName(), browserName, imgPrefix); 47 | 48 | ExtentTestManager.getExtentTest().log(Status.FAIL, "Test Failed"); 49 | WDManager.quitAndClearDriver(); 50 | ExtentTestManager.extentReport.flush(); 51 | } 52 | 53 | @Override 54 | public void onTestSkipped(ITestResult iTestResult) { 55 | super.onTestSkipped(iTestResult); 56 | logger.info("Test skipped: {} - {} - {}", 57 | getTestRealClass(iTestResult).getName(), 58 | getTestMethod(iTestResult).getMethodName(), 59 | getTestBrowser(iTestResult)); 60 | ExtentTestManager.getExtentTest().log(Status.SKIP, "Test Skipped"); 61 | WDManager.quitAndClearDriver(); 62 | ExtentTestManager.extentReport.flush(); 63 | } 64 | 65 | @Override 66 | public void onTestSuccess(ITestResult iTestResult) { 67 | super.onTestSuccess(iTestResult); 68 | logger.info("Test succeeded: {} - {} - {}", 69 | getTestRealClass(iTestResult).getName(), 70 | getTestMethod(iTestResult).getMethodName(), 71 | getTestBrowser(iTestResult)); 72 | ExtentTestManager.getExtentTest().log(Status.PASS, "Test Passed"); 73 | WDManager.quitAndClearDriver(); 74 | ExtentTestManager.extentReport.flush(); 75 | } 76 | 77 | @Override 78 | public void onFinish(ITestContext iTestContext) { 79 | /** 80 | super.onFinish(iTestContext); 81 | 82 | // List of test results which we will delete later 83 | ArrayList testsToBeRemoved = Lists.newArrayList(); 84 | 85 | // Collect all id's from passed test 86 | Set passedTestIds = Sets.newHashSet(); 87 | iTestContext.getPassedTests().getAllResults().forEach(iTestResult -> { 88 | logger.info("Passed test: " + iTestResult.getName()); 89 | passedTestIds.add(getTestId(iTestResult)); 90 | }); 91 | 92 | // Eliminate the repeat methods 93 | Set skipTestIds = Sets.newHashSet(); 94 | iTestContext.getSkippedTests().getAllResults().forEach(iTestResult -> { 95 | logger.info("Skip test: " + iTestResult.getName()); 96 | 97 | // id = class + method + dataprovider 98 | int skipTestId = getTestId(iTestResult); 99 | if (skipTestIds.contains(skipTestId) || passedTestIds.contains(skipTestId)) { 100 | testsToBeRemoved.add(iTestResult); 101 | } else { 102 | skipTestIds.add(skipTestId); 103 | } 104 | }); 105 | 106 | // Eliminate the repeat failed methods 107 | Set failedTestIds = Sets.newHashSet(); 108 | iTestContext.getFailedTests().getAllResults().forEach(iTestResult -> { 109 | logger.info("Failed test: " + iTestResult.getName()); 110 | 111 | // id = class + method + dataprovider 112 | int failedTestId = getTestId(iTestResult); 113 | 114 | // If we saw this test as a failed test before we mark as to be 115 | // deleted 116 | // or delete this failed test if there is at least one passed 117 | // version 118 | if (failedTestIds.contains(failedTestId) 119 | || passedTestIds.contains(failedTestId) 120 | || skipTestIds.contains(failedTestId)) { 121 | testsToBeRemoved.add(iTestResult); 122 | } else { 123 | failedTestIds.add(failedTestId); 124 | } 125 | }); 126 | 127 | // Finally delete all tests that are marked 128 | for (Iterator iterator = 129 | iTestContext.getFailedTests().getAllResults().iterator(); 130 | iterator.hasNext();) { 131 | ITestResult testResult = iterator.next(); 132 | if (testsToBeRemoved.contains(testResult)) { 133 | logger.info("Remove repeat failed test: " + testResult.getName()); 134 | iterator.remove(); 135 | } 136 | } 137 | 138 | iTestContext.getFailedTests().getAllResults().forEach(iTestResult -> { 139 | if (testsToBeRemoved.contains(iTestResult)) { 140 | iTestContext.getFailedTests().getAllResults().remove(iTestResult); 141 | } 142 | }); 143 | */ 144 | } 145 | 146 | private Class getTestRealClass(ITestResult iTestResult) { 147 | return iTestResult.getTestClass().getRealClass(); 148 | } 149 | 150 | private ITestNGMethod getTestMethod(ITestResult iTestResult) { 151 | return iTestResult.getMethod(); 152 | } 153 | 154 | private String getTestBrowser(ITestResult iTestResult) { 155 | return iTestResult.getTestContext().getCurrentXmlTest() 156 | .getAllParameters().get(removeQuoteMark(getConfigInstance().testngXmlBrowserParamKey())); 157 | } 158 | 159 | private int getTestId(ITestResult result) { 160 | int id = result.getTestClass().getName().hashCode(); 161 | id = id + result.getMethod().getMethodName().hashCode(); 162 | id = id + (result.getParameters() != null ? Arrays.hashCode(result.getParameters()) : 0); 163 | return id; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/testng/XmlClassBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.testng; 2 | 3 | import com.google.common.collect.HashMultimap; 4 | import com.google.common.collect.Multimap; 5 | import org.testng.xml.XmlClass; 6 | import org.testng.xml.XmlInclude; 7 | 8 | import java.lang.reflect.Method; 9 | 10 | import static java.util.stream.Collectors.toList; 11 | 12 | public class XmlClassBuilder { 13 | 14 | public static Multimap build() { 15 | Multimap browserXmlClassMap = HashMultimap.create(); 16 | TestNGFilter.run().forEach((browserName, testngClassMap) -> 17 | testngClassMap.forEach((aClass, methods) -> { 18 | XmlClass xmlClass = new XmlClass(aClass.getCanonicalName()); 19 | xmlClass.setIncludedMethods(methods.parallelStream() 20 | .map(Method::getName).map(XmlInclude::new) 21 | .distinct().collect(toList())); 22 | 23 | browserXmlClassMap.put(browserName, xmlClass); 24 | })); 25 | 26 | return browserXmlClassMap; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/testng/XmlSuiteBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.testng; 2 | 3 | import com.github.ansonliao.selenium.utils.StringUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.testng.xml.XmlSuite; 7 | 8 | import java.util.List; 9 | 10 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 11 | import static java.util.stream.Collectors.toList; 12 | 13 | public class XmlSuiteBuilder { 14 | private static final Logger logger = LoggerFactory.getLogger(XmlSuiteBuilder.class); 15 | 16 | public static XmlSuite build() { 17 | XmlSuite xmlSuite = new XmlSuite(); 18 | // set generate xml suite information 19 | xmlSuite = setXmlSuiteGenerateInfo(xmlSuite); 20 | // listeners 21 | xmlSuite = addXmlSuiteListeners(xmlSuite); 22 | // add xml tests 23 | xmlSuite = addXmlSuiteTests(xmlSuite); 24 | logger.info("\n" + xmlSuite.toXml()); 25 | 26 | return xmlSuite; 27 | } 28 | 29 | public static XmlSuite setXmlSuiteGenerateInfo(XmlSuite suite) { 30 | suite.setName("Selenium Web UI Test"); 31 | suite.setParallel(XmlSuite.ParallelMode.TESTS); 32 | suite.setPreserveOrder(false); 33 | suite.setThreadCount(Runtime.getRuntime().availableProcessors()); 34 | suite.setVerbose(2); 35 | return suite; 36 | } 37 | 38 | public static XmlSuite addXmlSuiteListeners(XmlSuite suite) { 39 | List listeners = getConfigInstance().testngListeners().parallelStream() 40 | .map(StringUtils::removeQuoteMark).distinct().collect(toList()); 41 | logger.info("TestNG Listeners found: {}", listeners); 42 | listeners.forEach(suite::addListener); 43 | return suite; 44 | } 45 | 46 | public static XmlSuite addXmlSuiteTests(XmlSuite suite) { 47 | XmlTestBuilder.setXmlSuite(suite); 48 | XmlTestBuilder.build(); 49 | return XmlTestBuilder.getXmlSuite(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/testng/XmlTestBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.testng; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Multimap; 5 | import com.google.common.collect.Sets; 6 | import org.testng.xml.XmlClass; 7 | import org.testng.xml.XmlSuite; 8 | import org.testng.xml.XmlTest; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 15 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 16 | import static java.util.stream.Collectors.toList; 17 | 18 | public class XmlTestBuilder { 19 | private static XmlSuite xmlSuite; 20 | 21 | public static List build() { 22 | Set xmlTestList = Sets.newHashSet(); 23 | Multimap browserXmlclassMap = XmlClassBuilder.build(); 24 | final int DEFAULT_TEST_CLASS_SIZE = getConfigInstance().testTagClassSizeOfTestNgXml(); 25 | boolean isPreserveOrder = getConfigInstance().testngPreserveOrder(); 26 | 27 | browserXmlclassMap.keySet().forEach(browserName -> { 28 | List xmlClassList = browserXmlclassMap.get(browserName) 29 | .stream().distinct().collect(toList()); 30 | ArrayList tempXmlClass = Lists.newArrayList(xmlClassList); 31 | int xmlClassGroupSize = xmlClassList.size() / DEFAULT_TEST_CLASS_SIZE + 1; 32 | String browserParamKey = removeQuoteMark(getConfigInstance().testngXmlBrowserParamKey()); 33 | int counter = xmlClassGroupSize; 34 | 35 | int startIndex = 0; 36 | int endIndex = DEFAULT_TEST_CLASS_SIZE; 37 | int browserIndex = 1; 38 | while (xmlClassGroupSize > 0) { 39 | if (tempXmlClass.size() == 0) { 40 | break; 41 | } 42 | 43 | XmlTest xmlTest = new XmlTest(xmlSuite); 44 | String xmlTestName = browserIndex == 1 45 | ? String.format("Selenium Test - %s", browserName) 46 | : String.format("Selenium Test - %s %d", browserName, browserIndex); 47 | xmlTest.setName(xmlTestName); 48 | xmlTest.addParameter(browserParamKey, browserName); 49 | xmlTest.setPreserveOrder(isPreserveOrder); 50 | 51 | if (tempXmlClass.size() < DEFAULT_TEST_CLASS_SIZE) { 52 | xmlTest.setXmlClasses(tempXmlClass.subList(startIndex, tempXmlClass.size())); 53 | } else { 54 | xmlTest.setXmlClasses(tempXmlClass.subList(startIndex, endIndex)); 55 | tempXmlClass = Lists.newArrayList(tempXmlClass.subList(endIndex, tempXmlClass.size())); 56 | } 57 | 58 | xmlTestList.add(xmlTest); 59 | --xmlClassGroupSize; 60 | ++browserIndex; 61 | } 62 | }); 63 | 64 | // add groups 65 | // if (!getConfigInstance().testingTestGroups().isEmpty()) { 66 | // xmlTestList.forEach(xmlTest -> 67 | // getConfigInstance().testingTestGroups() 68 | // .forEach(group -> xmlTest.addIncludedGroup(group))); 69 | // } 70 | 71 | return Lists.newArrayList(xmlTestList); 72 | } 73 | 74 | public static XmlSuite getXmlSuite() { 75 | return xmlSuite; 76 | } 77 | 78 | public static void setXmlSuite(XmlSuite suite) { 79 | xmlSuite = suite; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/AnnotationUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Method; 9 | import java.util.Set; 10 | 11 | public class AnnotationUtils { 12 | private static Logger logger = LoggerFactory.getLogger(AnnotationUtils.class); 13 | 14 | public static synchronized Set getClassAnnotations(Class clazz) { 15 | return Sets.newHashSet(clazz.getAnnotations()); 16 | } 17 | 18 | public static synchronized Set getMethodAnnotations(Method method) { 19 | return Sets.newHashSet(method.getAnnotations()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/AuthorUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import com.github.ansonliao.selenium.annotations.Author; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Sets; 6 | import com.thoughtworks.qdox.JavaProjectBuilder; 7 | import com.thoughtworks.qdox.model.DocletTag; 8 | import com.thoughtworks.qdox.model.JavaClass; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.File; 13 | import java.lang.reflect.Method; 14 | import java.util.List; 15 | import java.util.Optional; 16 | import java.util.Set; 17 | import java.util.function.Predicate; 18 | import java.util.stream.Collectors; 19 | 20 | public class AuthorUtils { 21 | private static final Logger logger = LoggerFactory.getLogger(AuthorUtils.class); 22 | private static final String PROJECT_ROOT_DIR = System.getProperty("user.dir"); 23 | private static final String FILE_SEPARATOR = File.separator; 24 | private static JavaProjectBuilder javaProjectBuilder = new JavaProjectBuilder(); 25 | private static Predicate filterAuthorTag = tag -> 26 | tag.getName().equals("author") || tag.getName().equals("author:"); 27 | 28 | static { 29 | javaProjectBuilder.addSourceTree(new File( 30 | PROJECT_ROOT_DIR.concat(FILE_SEPARATOR).concat("src"))); 31 | } 32 | 33 | public synchronized static List getMethodAuthors(Method method) { 34 | Class clazz = method.getDeclaringClass(); 35 | String className = clazz.getName(); 36 | 37 | Set classAuthors = Sets.newHashSet(); 38 | Set methodAuthors = Sets.newHashSet(); 39 | 40 | // get class authors 41 | getClassAuthors(clazz) 42 | .ifPresent(classAuthors::addAll); 43 | 44 | // get method authors 45 | javaProjectBuilder.getClassByName(className).getMethods().stream() 46 | .filter(m -> m.getName().equalsIgnoreCase(method.getName())).findAny() 47 | .ifPresent(m -> m.getTags().stream().filter(filterAuthorTag).findAny() 48 | .ifPresent(tag -> methodAuthors.add(tag.getValue().trim()))); 49 | 50 | getAuthorsFromAnnotation(method) 51 | .ifPresent(authorList -> authorList.forEach(methodAuthors::add)); 52 | 53 | return methodAuthors.size() > 0 54 | ? Lists.newArrayList(methodAuthors) 55 | : Lists.newArrayList(classAuthors); 56 | } 57 | 58 | public synchronized static Optional> getClassAuthors(Class clazz) { 59 | String className = clazz.getName(); 60 | JavaClass cls = javaProjectBuilder.getClassByName(className); 61 | List authors = Lists.newArrayList(); 62 | 63 | getAuthorsFromAnnotation(clazz) 64 | .ifPresent(authorList -> authorList.forEach(authors::add)); 65 | 66 | cls.getTags().stream() 67 | .filter(filterAuthorTag).distinct() 68 | .map(DocletTag::getValue).map(String::trim).map(authors::add) 69 | .distinct().collect(Collectors.toList()); 70 | 71 | return Optional.of(authors); 72 | } 73 | 74 | private synchronized static Optional> getAuthorsFromAnnotation(Object obj) { 75 | Author author = obj instanceof Class 76 | ? (Author) ((Class) obj).getAnnotation(Author.class) 77 | : (Author) ((Method) obj).getAnnotation(Author.class); 78 | 79 | if (author == null || author.value().length == 0) { 80 | return Optional.empty(); 81 | } 82 | 83 | return Optional.of(Lists.newArrayList(author.value()) 84 | .stream().distinct().collect(Collectors.toList())); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/BrowserUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import com.github.ansonliao.selenium.parallel.ClassFinder; 4 | import com.google.common.collect.Lists; 5 | import com.google.common.collect.Sets; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.lang.annotation.Annotation; 10 | import java.lang.reflect.Method; 11 | import java.util.List; 12 | import java.util.Set; 13 | import java.util.stream.Collectors; 14 | 15 | import static com.github.ansonliao.selenium.utils.StringUtils.removeQuoteMark; 16 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 17 | 18 | public class BrowserUtils { 19 | private static Logger logger = LoggerFactory.getLogger(BrowserUtils.class); 20 | 21 | public static List getSupportedBrowsers() { 22 | return ClassFinder.getScanAllClassesResult() 23 | .getNamesOfAllClasses() 24 | .parallelStream() 25 | .filter(className -> 26 | className.startsWith( 27 | removeQuoteMark(getConfigInstance().browserAnnotationPackage()))) 28 | .map(ClassFinder::createClass) 29 | //.filter(aClass -> aClass.isAnnotation()) 30 | .filter(Class::isAnnotation) 31 | .map(aClass -> aClass.getSimpleName().toUpperCase()) 32 | .filter(className -> 33 | !className.startsWith( 34 | removeQuoteMark(getConfigInstance().browserIgnoreAnnotationPrefix()))) 35 | .collect(Collectors.toList()); 36 | } 37 | 38 | public static List getSupportedIgnoreBrowsers() { 39 | return ClassFinder.getScanAllClassesResult() 40 | .getNamesOfAllClasses() 41 | .parallelStream() 42 | .filter(className -> 43 | className.startsWith( 44 | removeQuoteMark(getConfigInstance().browserAnnotationPackage()))) 45 | .map(ClassFinder::createClass) 46 | .filter(Class::isAnnotation) 47 | .map(aClass -> aClass.getSimpleName().toUpperCase()) 48 | .filter(className -> 49 | className.startsWith( 50 | removeQuoteMark(getConfigInstance().browserIgnoreAnnotationPrefix()))) 51 | .map(className -> 52 | className.substring( 53 | removeQuoteMark(getConfigInstance().browserIgnoreAnnotationPrefix()).length())) 54 | .collect(Collectors.toList()); 55 | } 56 | 57 | public static synchronized Set getClassSupportedBrowsers(Class clazz) { 58 | Set enabledBrowsers = getClassBrowsers(clazz); 59 | Set ignoredBrowsers = getClassIgnoredBrowsers(clazz); 60 | return Sets.difference( 61 | enabledBrowsers, 62 | Sets.intersection(enabledBrowsers, ignoredBrowsers)); 63 | } 64 | 65 | public static synchronized Set getClassBrowsers(Class clazz) { 66 | Set classBrowserList = filterBrowsersFromAnnotations( 67 | Lists.newArrayList(clazz.getAnnotations())); 68 | return Sets.intersection( 69 | classBrowserList, 70 | Sets.newHashSet(getSupportedBrowsers())); 71 | } 72 | 73 | public static Set getClassIgnoredBrowsers(Class clazz) { 74 | return filterBrowsersIgnoredFromAnnotations( 75 | Lists.newArrayList(clazz.getAnnotations())); 76 | 77 | } 78 | 79 | public static Set getMethodSupportedBrowsers(Method method) { 80 | Class clazz = method.getDeclaringClass(); 81 | return getMethodSupportedBrowsers(clazz, method); 82 | } 83 | 84 | public static Set getMethodSupportedBrowsers(Class clazz, Method method) { 85 | Set classSupportedBrowsers = getClassBrowsers(clazz); 86 | Set classIgnoredBrowsers = getClassIgnoredBrowsers(clazz); 87 | Set enabledBrowsers = getMethodBrowsers(method); 88 | Set ignoredBrowsers = getMethodIgnoredBrowsers(method); 89 | 90 | enabledBrowsers = Sets.union(classSupportedBrowsers, enabledBrowsers); 91 | ignoredBrowsers = Sets.union(classIgnoredBrowsers, ignoredBrowsers); 92 | 93 | return Sets.difference( 94 | enabledBrowsers, 95 | Sets.intersection(enabledBrowsers, ignoredBrowsers)) 96 | .parallelStream().collect(Collectors.toSet()); 97 | } 98 | 99 | public static Set getMethodBrowsers(Method method) { 100 | Set methodBrowserList = filterBrowsersFromAnnotations( 101 | Lists.newArrayList(method.getAnnotations())); 102 | return Sets.intersection( 103 | methodBrowserList, 104 | Sets.newHashSet(getSupportedBrowsers())) 105 | .parallelStream().collect(Collectors.toSet()); 106 | } 107 | 108 | public static Set getMethodIgnoredBrowsers(Method method) { 109 | return filterBrowsersIgnoredFromAnnotations( 110 | Lists.newArrayList(method.getAnnotations())); 111 | } 112 | 113 | public synchronized static Set filterBrowsersFromAnnotations(List annotations) { 114 | return annotations.parallelStream() 115 | .map(annotation -> 116 | annotation.annotationType().getSimpleName().toUpperCase()) 117 | .filter(className -> 118 | (!className.equals("TEST") 119 | && !className.equals("INCOGNITO") 120 | && !className.equals("HEADLESS"))) 121 | .collect(Collectors.toSet()); 122 | } 123 | 124 | public synchronized static Set filterBrowsersIgnoredFromAnnotations( 125 | List annotations) { 126 | return annotations.parallelStream() 127 | .map(annotation -> 128 | annotation.annotationType().getSimpleName().toUpperCase()) 129 | .filter(className -> 130 | className.startsWith( 131 | removeQuoteMark(getConfigInstance().browserIgnoreAnnotationPrefix()))) 132 | .map(className -> 133 | className.substring( 134 | removeQuoteMark(getConfigInstance().browserIgnoreAnnotationPrefix()).length())) 135 | .collect(Collectors.toSet()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/CapsUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Maps; 5 | import com.jayway.jsonpath.Configuration; 6 | import com.jayway.jsonpath.DocumentContext; 7 | import com.jayway.jsonpath.JsonPath; 8 | import com.jayway.jsonpath.PathNotFoundException; 9 | import com.jayway.jsonpath.spi.json.GsonJsonProvider; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.testng.util.Strings; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | import static com.github.ansonliao.selenium.utils.MyFileUtils.isFileExisted; 20 | import static com.github.ansonliao.selenium.utils.PlatformUtils.getPlatform; 21 | import static com.github.ansonliao.selenium.utils.PlatformUtils.isWindows; 22 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 23 | import static com.jayway.jsonpath.Option.DEFAULT_PATH_LEAF_TO_NULL; 24 | 25 | public class CapsUtils { 26 | 27 | public static final String CLI_ARGS_KEY = "cli_args"; 28 | public static final String DESIRED_CAPABILITIES_KEY = "caps"; 29 | public static final String EXTENSIONS_KEY = "extensions"; 30 | public static Configuration GSON_CONFIGURATION = Configuration.builder().jsonProvider(new GsonJsonProvider()) 31 | .options(DEFAULT_PATH_LEAF_TO_NULL).build(); 32 | 33 | private static Logger logger = LoggerFactory.getLogger(CapsUtils.class); 34 | private static String wdCapsFilePath; 35 | private static DocumentContext documentContext; 36 | 37 | static { 38 | wdCapsFilePath = isWindows(getPlatform().toString()) 39 | ? getConfigInstance().capsPath().replace("\\", "/") 40 | : getConfigInstance().capsPath(); 41 | try { 42 | documentContext = isFileExisted(wdCapsFilePath) 43 | ? JsonPath.parse(new File(wdCapsFilePath)) 44 | : null; 45 | } catch (IOException e) { 46 | e.printStackTrace(); 47 | } 48 | } 49 | 50 | public synchronized static boolean isJsonFileEmpty() { 51 | if (documentContext == null) { 52 | return true; 53 | } 54 | return Strings.isNullOrEmpty(documentContext.read("$").toString().trim()); 55 | } 56 | 57 | public synchronized static boolean isPathExists(String path) { 58 | if (isJsonFileEmpty()) { 59 | return false; 60 | } 61 | String fullPath = path.trim().startsWith("$.") ? path : "$.".concat(path); 62 | try { 63 | documentContext.read(fullPath); 64 | return true; 65 | } catch (PathNotFoundException e) { 66 | return false; 67 | } 68 | } 69 | 70 | public synchronized static Map getCaps(String browser) { 71 | if (isJsonFileEmpty()) { 72 | return Maps.newHashMap(); 73 | } 74 | String path = "$.".concat(browser).concat(".").concat(DESIRED_CAPABILITIES_KEY); 75 | 76 | return isPathExists(path) ? documentContext.read(path, Map.class) : Maps.newHashMap(); 77 | } 78 | 79 | public synchronized static List getCliArgs(String browser) { 80 | if (isJsonFileEmpty()) { 81 | return Lists.newArrayList(); 82 | } 83 | String path = "$.".concat(browser).concat(".").concat(CLI_ARGS_KEY); 84 | 85 | return isPathExists(path) ? documentContext.read(path, List.class) : Lists.newArrayList(); 86 | } 87 | 88 | public synchronized static List getExtensions(String browser) { 89 | if (isJsonFileEmpty()) { 90 | return Lists.newArrayList(); 91 | } 92 | String path = "$.".concat(browser).concat(".").concat(EXTENSIONS_KEY); 93 | return isPathExists(path) ? documentContext.read(path, List.class) : Lists.newArrayList(); 94 | } 95 | 96 | public synchronized static Map getEmulation(String browser) { 97 | if (isJsonFileEmpty()) { 98 | return Maps.newHashMap(); 99 | } 100 | String path = "$.".concat(browser).concat(".").concat("emulation"); 101 | 102 | return isPathExists(path) ? documentContext.read(path, Map.class) : Maps.newHashMap(); 103 | } 104 | 105 | // public synchronized static JsonElement getCaps() { 106 | // if (!isCapsExisted(wdCapsFilePath)) { 107 | // logger.info("No webdriver desired capabilities Json file found from path : " 108 | // + wdCapsFilePath); 109 | // return JsonNull.INSTANCE; 110 | // } 111 | // 112 | // capsJsonReader = getCapsJsonReader(wdCapsFilePath); 113 | // JsonObject object = getGsonInstance().fromJson(capsJsonReader, JsonObject.class); 114 | // JsonElement element = JsonParser.getJsonElement(object, ""); 115 | // if (element == null) { 116 | // logger.info( 117 | // "WebDriver desired capabilities Json file was found from path: {}, but no Json data retrieved.", 118 | // wdCapsFilePath); 119 | // return null; 120 | // } 121 | // return element; 122 | // } 123 | // 124 | // public synchronized static boolean isCapsExisted(String file) { 125 | // return Files.exists(Paths.get(file)); 126 | // } 127 | // 128 | // public synchronized static BufferedReader getCapsJsonReader(String file) { 129 | // if (isCapsExisted(file)) { 130 | // try { 131 | // return new BufferedReader(new FileReader(file)); 132 | // } catch (FileNotFoundException e) { 133 | // e.printStackTrace(); 134 | // return null; 135 | // } 136 | // } else { 137 | // return null; 138 | // } 139 | // } 140 | 141 | public static void main(String[] args) throws IOException { 142 | System.out.println(getEmulation("chrome")); 143 | 144 | // System.out.println(JsonPath.using(Configuration.builder().jsonProvider(new GsonJsonProvider()) 145 | // .options(DEFAULT_PATH_LEAF_TO_NULL).build()) 146 | // .parse(new File(wdCapsFilePath)).read("$.chrome.emulation").toString()); 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/MyFileUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.testng.util.Strings; 7 | 8 | import java.io.File; 9 | 10 | public class MyFileUtils extends FileUtils { 11 | private static final Logger logger = LoggerFactory.getLogger(MyFileUtils.class); 12 | private static final String FILE_SEPARATOR = File.separator; 13 | private static final String PROJECT_ROOT_DIR = System.getProperty("user.dir"); 14 | private static final String SCREENSHOT_DIR = PROJECT_ROOT_DIR 15 | .concat(FILE_SEPARATOR) 16 | .concat("target") 17 | .concat(FILE_SEPARATOR) 18 | .concat("screenshots"); 19 | 20 | public static synchronized File createScreenshotFolderForBrowser( 21 | Class clazz, String browserName) { 22 | String className = clazz.getSimpleName(); 23 | String packageName = clazz.getPackage().getName(); 24 | String destDir = String.join( 25 | FILE_SEPARATOR, SCREENSHOT_DIR, 26 | packageName, className, browserName); 27 | 28 | if ((new File(destDir)).exists()) { 29 | return new File(destDir); 30 | } 31 | 32 | logger.info("Create screenshot directory: {}", destDir); 33 | new File(destDir).mkdirs(); 34 | return new File(destDir); 35 | } 36 | 37 | public static synchronized boolean isFileExisted(String path) { 38 | return Strings.isNotNullAndNotEmpty(path) && new File(path).exists(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/PlatformUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import org.openqa.selenium.Platform; 4 | 5 | public class PlatformUtils { 6 | 7 | public static synchronized Platform getPlatform() { 8 | String osName = System.getProperty("os.name"); 9 | String osNameMatch = osName.toLowerCase(); 10 | if (isMac(osNameMatch)) { 11 | return Platform.MAC; 12 | } else if (isUnix(osNameMatch)) { 13 | return Platform.LINUX; 14 | } else if (isWindows(osNameMatch)) { 15 | return Platform.WINDOWS; 16 | } else { 17 | return Platform.LINUX; 18 | } 19 | } 20 | 21 | public static synchronized boolean isWindows(String osName) { 22 | return (osName.indexOf("win") >= 0); 23 | } 24 | 25 | public static synchronized boolean isMac(String osName) { 26 | return (osName.indexOf("mac") >= 0 || osName.indexOf("darwin") >= 0); 27 | } 28 | 29 | public static synchronized boolean isUnix(String osName) { 30 | return (osName.indexOf("nix") >= 0 31 | || osName.indexOf("nux") >= 0 32 | || osName.indexOf("aix") >= 0); 33 | } 34 | 35 | public static synchronized boolean isSolaris(String osName) { 36 | return (osName.indexOf("sunos") >= 0); 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/SEConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import com.typesafe.config.Config; 4 | import com.typesafe.config.ConfigException; 5 | import com.typesafe.config.ConfigFactory; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.testng.util.Strings; 9 | 10 | @Deprecated 11 | public class SEConfig { 12 | private static final Logger logger = 13 | LoggerFactory.getLogger(SEConfig.class); 14 | private static SEConfig instance = new SEConfig(); 15 | private Config config; 16 | 17 | protected SEConfig() { 18 | config = ConfigFactory.load(SEConfig.class.getClassLoader(), 19 | System.getProperty("se.properties", "seleniumextensions.properties")); 20 | } 21 | 22 | public synchronized static SEConfig getInstance() { 23 | return instance; 24 | } 25 | 26 | public static String getString(String key) { 27 | String value = ""; 28 | if (!key.equals("")) { 29 | // dots are not allowed in POSIX environmental variables 30 | value = System.getenv(key.replace(".", "_")); 31 | if (value == null) { 32 | value = SEConfig.getInstance().config.getString(key); 33 | } 34 | 35 | } 36 | return value.contains("\"") || value.contains("'") 37 | ? value.replace("\"", "").replace("'", "") 38 | : value; 39 | } 40 | 41 | public static int getInt(String key) { 42 | return SEConfig.getInstance().config.getInt(key); 43 | } 44 | 45 | public static boolean getBoolean(String key) { 46 | String value = SEConfig.getInstance().config.getString(key); 47 | return Strings.isNotNullAndNotEmpty(value) 48 | ? Boolean.valueOf(value) 49 | : false; 50 | } 51 | 52 | public static boolean isKeyExisted(String key) { 53 | try { 54 | logger.info("Checking property key existed or not: [{}].", key); 55 | String value = getString(key); 56 | logger.info("Property was found: [{}: {}].", key, value); 57 | } catch (ConfigException e) { 58 | logger.info("Property was not found, key = {}.", key); 59 | return false; 60 | } 61 | return true; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import com.google.common.base.Strings; 4 | 5 | public class StringUtils { 6 | 7 | public synchronized static String removeQuoteMark(String s) { 8 | return Strings.isNullOrEmpty(s) ? s : s.replaceAll("^[\"']|[\"']$", ""); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/TestGroupUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import com.google.common.collect.Lists; 4 | import com.google.common.collect.Sets; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.testng.annotations.Test; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 15 | import static java.util.Collections.emptyList; 16 | import static java.util.stream.Collectors.toList; 17 | import static java.util.stream.Collectors.toSet; 18 | 19 | public class TestGroupUtils { 20 | private static final Logger logger = 21 | LoggerFactory.getLogger(TestGroupUtils.class); 22 | 23 | public static List getClassTestGroups(Class clazz) { 24 | if (!clazz.isAnnotationPresent(Test.class)) { 25 | return emptyList(); 26 | } 27 | 28 | Test annotation = clazz.getAnnotation(Test.class); 29 | if (annotation.groups().length == 0) { 30 | return emptyList(); 31 | } 32 | 33 | return Arrays.stream(annotation.groups()).distinct().collect(toList()); 34 | } 35 | 36 | public static List getMethodTestGroups(Method method) { 37 | if (!getConfigInstance().testingTestGroups().isEmpty()) { 38 | return getConfigInstance().testingTestGroups().parallelStream() 39 | .map(StringUtils::removeQuoteMark) 40 | .distinct().collect(toList()); 41 | } 42 | 43 | Set classTestGroups = Sets.newHashSet(getClassTestGroups(method.getDeclaringClass())); 44 | 45 | if (!method.isAnnotationPresent(Test.class)) { 46 | return Lists.newArrayList(classTestGroups); 47 | } 48 | 49 | Test annotation = method.getAnnotation(Test.class); 50 | if (annotation.groups().length == 0) { 51 | return Lists.newArrayList(classTestGroups); 52 | } 53 | 54 | return Sets.union(classTestGroups, Arrays.stream(annotation.groups()).collect(toSet())) 55 | .parallelStream().collect(toList()); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/WDMHelper.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils; 2 | 3 | import com.google.common.collect.ImmutableMultiset; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import static com.github.ansonliao.selenium.utils.config.SEConfigs.getConfigInstance; 11 | import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; 12 | import static io.github.bonigarcia.wdm.WebDriverManager.edgedriver; 13 | import static io.github.bonigarcia.wdm.WebDriverManager.firefoxdriver; 14 | import static io.github.bonigarcia.wdm.WebDriverManager.iedriver; 15 | import static io.github.bonigarcia.wdm.WebDriverManager.operadriver; 16 | import static io.github.bonigarcia.wdm.WebDriverManager.phantomjs; 17 | 18 | public class WDMHelper { 19 | private static final Logger logger = LoggerFactory.getLogger(WDMHelper.class); 20 | private static final List BROWSER_LIST = 21 | ImmutableMultiset.of("CHROME", "FIREFOX", "EDGE", "INTERNETEXPLORER", "PHANTOMJS", "OPERA").asList(); 22 | private static boolean useTaobaoMirror = getConfigInstance().useTaobaoMirror(); 23 | 24 | public static void downloadWebDriverBinary(String browserName) { 25 | if (!BROWSER_LIST.parallelStream().anyMatch(s -> s.equalsIgnoreCase(browserName))) { 26 | throw new IllegalArgumentException("Illegal webdriver browser name was found: " + browserName 27 | + ", please provide the correct webdriver browser name: " + browserList()); 28 | } 29 | 30 | switch (browserName.toUpperCase()) { 31 | case "CHROME": 32 | downloadWebDriver(Platform.CHROME, useTaobaoMirror); 33 | break; 34 | case "FIREFOX": 35 | downloadWebDriver(Platform.FIREFOX, useTaobaoMirror); 36 | break; 37 | case "PHANTOMJS": 38 | downloadWebDriver(Platform.PHANTOMJS, useTaobaoMirror); 39 | break; 40 | case "EDGE": 41 | downloadWebDriver(Platform.EDGE, useTaobaoMirror); 42 | break; 43 | case "INTERNETEXPLORER": 44 | downloadWebDriver(Platform.IE, useTaobaoMirror); 45 | break; 46 | case "OPERA": 47 | downloadWebDriver(Platform.OPERA, useTaobaoMirror); 48 | break; 49 | default: 50 | downloadWebDriver(Platform.CHROME, useTaobaoMirror); 51 | } 52 | } 53 | 54 | public static void exportDriver(String key, String value) { 55 | logger.info("Export {} = {}", key, value); 56 | System.setProperty(key, value); 57 | } 58 | 59 | private static void downloadWebDriver(Platform platform, boolean taobaoMirror) { 60 | if (taobaoMirror) { 61 | logger.info("Use TaoBao Mirror to download WebDriver binary."); 62 | downloadWDBinaryFromMirror(platform); 63 | } else { 64 | downloadWDBinary(platform); 65 | } 66 | } 67 | 68 | private static void downloadWDBinary(Platform platform) { 69 | switch (platform) { 70 | case CHROME: 71 | chromedriver().setup(); 72 | logger.info("Downloaded ChromeDriver from official mirror"); 73 | break; 74 | case FIREFOX: 75 | firefoxdriver().setup(); 76 | logger.info("Downloaded FirefoxDriver from official mirror"); 77 | break; 78 | case EDGE: 79 | edgedriver().setup(); 80 | logger.info("Downloaded EdgeDriver from official mirror"); 81 | break; 82 | case IE: 83 | iedriver().setup(); 84 | logger.info("Downloaded IEDriver from official mirror"); 85 | break; 86 | case PHANTOMJS: 87 | phantomjs().setup(); 88 | logger.info("Downloaded PhantomJSDriver from official mirror"); 89 | break; 90 | case OPERA: 91 | operadriver().setup(); 92 | logger.info("Downloaded OperaDriver from official mirror"); 93 | break; 94 | default: 95 | chromedriver().setup(); 96 | logger.info("Downloaded ChromeDriver from official mirror"); 97 | break; 98 | } 99 | } 100 | 101 | private static void downloadWDBinaryFromMirror(Platform platform) { 102 | switch (platform) { 103 | case CHROME: 104 | chromedriver().useMirror().setup(); 105 | logger.info("Downloaded ChromeDriver from mirror (Taobao)"); 106 | break; 107 | case FIREFOX: 108 | firefoxdriver().useMirror().setup(); 109 | logger.info("Downloaded FirefoxDriver from mirror (Taobao)"); 110 | break; 111 | case PHANTOMJS: 112 | phantomjs().useMirror().setup(); 113 | logger.info("Downloaded PhantomJSDriver from mirror (Taobao)"); 114 | break; 115 | case EDGE: 116 | edgedriver().useMirror().setup(); 117 | logger.info("Downloaded EdgeDriver from mirror (Taobao)"); 118 | break; 119 | case IE: 120 | iedriver().useMirror().setup(); 121 | logger.info("Downloaded IEDriver from mirror (Taobao)"); 122 | break; 123 | case OPERA: 124 | operadriver().useMirror().setup(); 125 | logger.info("Downloaded OperaDriver from mirror (Taobao)"); 126 | break; 127 | default: 128 | chromedriver().useMirror().setup(); 129 | logger.info("Downloaded ChromeDriver from mirror (Taobao)"); 130 | break; 131 | } 132 | } 133 | 134 | private static List browserList() { 135 | return Arrays.asList("CHROME", "FIREFOX", "EDGE", "INTERNETEXPLORER", "PHANTOMJS", "OPERA"); 136 | } 137 | 138 | private enum Platform { 139 | CHROME, FIREFOX, PHANTOMJS, EDGE, IE, OPERA 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/github/ansonliao/selenium/utils/config/SEConfigs.java: -------------------------------------------------------------------------------- 1 | package com.github.ansonliao.selenium.utils.config; 2 | 3 | import org.aeonbits.owner.Config; 4 | import org.aeonbits.owner.ConfigFactory; 5 | 6 | import java.util.List; 7 | 8 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 9 | import static org.aeonbits.owner.Config.HotReloadType.ASYNC; 10 | import static org.aeonbits.owner.Config.LoadType.FIRST; 11 | 12 | public class SEConfigs { 13 | private static SEConfiguration config; 14 | 15 | public static synchronized SEConfiguration getConfigInstance() { 16 | if (config == null) { 17 | config = ConfigFactory.create(SEConfiguration.class, System.getProperties(), System.getenv()); 18 | } 19 | return config; 20 | } 21 | 22 | @Config.HotReload(value = 500, unit = MILLISECONDS, type = ASYNC) 23 | @Config.Sources({ 24 | "classpath:seleniumextensions.properties", 25 | "classpath:se.properties"}) 26 | public interface SEConfiguration extends Config { 27 | 28 | // SE utils identify keys 29 | @Key("add.browser.group.to.report") 30 | @DefaultValue("false") 31 | boolean addBrowserGroupToReport(); 32 | 33 | @Key("run.by.browsers") 34 | @DefaultValue("") 35 | @Separator(",") 36 | List runByBrowsers(); 37 | 38 | @Key("default.browser") 39 | @DefaultValue("Chrome") 40 | String defaultBrowser(); 41 | 42 | @Key("browser.annotation.package") 43 | @DefaultValue("com.github.ansonliao.selenium.annotations.browser") 44 | String browserAnnotationPackage(); 45 | 46 | @Key("test.tag.class.size.of.testngxml") 47 | @DefaultValue("10") 48 | int testTagClassSizeOfTestNgXml(); 49 | 50 | @Key("testing.package.names") 51 | @DefaultValue("") 52 | @Separator(",") 53 | List testingPackageNames(); 54 | 55 | @Key("testing.browser.names") 56 | @DefaultValue("") 57 | @Separator(",") 58 | List testingBrowserNames(); 59 | 60 | @Key("testing.test.groups") 61 | @DefaultValue("") 62 | @Separator(",") 63 | List testingTestGroups(); 64 | 65 | @Key("testing.testng.classes") 66 | @DefaultValue("") 67 | @Separator(",") 68 | List testingTestNGClasses(); 69 | 70 | @Key("testng.listeners") 71 | @DefaultValue( 72 | "com.github.ansonliao.selenium.testng.TestResultListener, " 73 | + "com.github.ansonliao.selenium.parallel.SeleniumParallelTestListener") 74 | @Separator(",") 75 | List testngListeners(); 76 | 77 | @Key("testng.class.prefix") 78 | @DefaultValue("test") 79 | String testngTestClassPrefix(); 80 | 81 | // key set to "browser.ignore.anntation.prefix" if needed 82 | @DefaultValue("IGNORE") 83 | String browserIgnoreAnnotationPrefix(); 84 | 85 | // key set to "testng.xml.browser.parameter.key" if needed 86 | @DefaultValue("browser") 87 | String testngXmlBrowserParamKey(); 88 | 89 | @Key("testng.test.preserve.order") 90 | @DefaultValue("false") 91 | boolean testngPreserveOrder(); 92 | 93 | // key set to "BROWSER_IGNORE_ANNOTATION_TYPE_PROPERTY" if needed 94 | @DefaultValue("BROWSER_IGNORE") 95 | String browserIgnoreAnnotationTypeProp(); 96 | 97 | // key set to "BROWSER_ANNOTATION_TYPE_PROPERTY" if needed 98 | @DefaultValue("BROWSER") 99 | String browserAnnotationTypeProp(); 100 | 101 | // webdriver export property key 102 | @Key("wd.parameter.key.chrome") 103 | @DefaultValue("webdriver.chrome.driver") 104 | String chromeDriverProperty(); 105 | 106 | @Key("wd.parameter.key.firefox") 107 | @DefaultValue("webdriver.gecko.driver") 108 | String firefoxDriverProperty(); 109 | 110 | @Key("wd.parameter.key.phantomjs") 111 | @DefaultValue("phantomjs.binary.path") 112 | String phantomjsDriverProperty(); 113 | 114 | @Key("wd.parameter.key.edge") 115 | @DefaultValue("webdriver.edge.driver") 116 | String edgeDriverProperty(); 117 | 118 | @Key("wd.parameter.key.ie") 119 | @DefaultValue("webdriver.ie.driver") 120 | String ieDriverProperty(); 121 | 122 | @Key("wd.parameter.key.opera") 123 | @DefaultValue("webdriver.opera.driver") 124 | String operaDriverProperty(); 125 | 126 | @Key("wd.use.mirror") 127 | @DefaultValue("false") 128 | boolean useTaobaoMirror(); 129 | 130 | // selenium grid, remote webdriver 131 | @Key("selenium.hub.url") 132 | @DefaultValue("") 133 | String seleniumHubUrl(); 134 | 135 | @Key("wd.caps.file") 136 | @DefaultValue("caps/caps.json") 137 | String capsPath(); 138 | 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/test/java/testcases/Test_Demo.java: -------------------------------------------------------------------------------- 1 | package testcases; 2 | 3 | import com.github.ansonliao.selenium.annotations.URL; 4 | import com.github.ansonliao.selenium.utils.WDMHelper; 5 | import com.google.common.collect.Lists; 6 | import org.testng.annotations.Test; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import static com.github.ansonliao.selenium.factory.WDManager.getDriver; 11 | 12 | public class Test_Demo { 13 | 14 | @Test 15 | @URL("http://the-internet.herokuapp.com/login") 16 | public void f1() throws InterruptedException { 17 | TimeUnit.SECONDS.sleep(5); 18 | System.out.println(getDriver().getCurrentUrl()); 19 | System.out.println(getDriver().getTitle()); 20 | } 21 | 22 | @Test 23 | public void f2() { 24 | Lists.newArrayList("chrome", "firefox") 25 | .parallelStream() 26 | .forEach(WDMHelper::downloadWebDriverBinary); 27 | System.out.println("completed"); 28 | } 29 | 30 | } 31 | --------------------------------------------------------------------------------