├── .gitignore ├── README.md ├── binaries └── .gitignore ├── pom.xml └── src ├── main ├── java │ └── cucumber │ │ └── jvm │ │ └── parallel │ │ ├── JSONReportMerger.java │ │ ├── JSONWriter.java │ │ ├── JSReportMerger.java │ │ └── JUnitReportMerger.java └── resources │ ├── RepositoryMapForMavenWebDriverBinaryDownloaderPlugin.xml │ └── RepositoryMapForMavenWebDriverBinaryDownloaderPlugin.xsd └── test ├── java └── cucumber │ └── examples │ └── java │ └── testNG │ ├── DriverManager.java │ ├── LocalDriverFactory.java │ ├── LocalWebDriverListener.java │ ├── RemoteDriverFactory.java │ ├── RemoteWebDriverListener.java │ ├── page_objects │ └── GoogleCom.java │ ├── runners │ ├── RunCukesTestInChrome.java │ ├── RunCukesTestInFirefox.java │ └── RunSingleFeature.java │ └── stepDefinitions │ ├── BeforeAfterHooks.java │ ├── Feature1And2Stepdefs.java │ └── bugs │ └── Bug12345.java └── resources ├── TestNGRunTestsLocally.xml ├── TestNGRunTestsRemotely.xml ├── features ├── WIP │ └── WIP.feature ├── bugs │ └── bug_no_12345.feature ├── feature_no1 │ └── first.feature └── feature_no2 │ └── second.feature └── log4j.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # files 2 | *.tmp 3 | *.log 4 | *.log 5 | .DS_Store 6 | *.out 7 | *.swp 8 | *.zip 9 | 0 10 | 11 | #folders 12 | target 13 | test-output 14 | 15 | #intellij idea 16 | .idea 17 | *.iml 18 | *.ipr 19 | *.iws 20 | 21 | # eclipse 22 | .project 23 | .classpath 24 | .settings 25 | 26 | #gedit 27 | *.*~ 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This example project is based on few other projects: 2 | * [Cucumber-JVM-Parallel](https://github.com/tristanmccarthy/Cucumber-JVM-Parallel) 3 | * [java-parallel](https://github.com/cucumber/cucumber-jvm/tree/java-parallel-example/examples/java-parallel) 4 | * [java-webbit-websockets-selenium](https://github.com/cucumber/cucumber-jvm/tree/java-parallel-example/examples/java-webbit-websockets-selenium) 5 | 6 | It allows you to run Cucumber features (tests/scenarios) in multiple browsers simultaneously using Selenium (WebDriver) and TestNG. 7 | 8 | 9 | ## Running features in IDE 10 | Tested in IntelliJ Idea 13.1.1 11 | To run all stories from IDE only in Firefox, simply right click on one of the files: 12 | * cucumber.examples.java.testNG.runners.RunCukesTestInChrome 13 | * cucumber.examples.java.testNG.runners.RunCukesTestInFirefox 14 | 15 | And chose "Run ..." 16 | (Yes, choosing RunCukesTestInChrome will also run tests in FF!) 17 | 18 | 19 | To run all stories simultaneously in both browsers (Chrome and Firefox) right click on one of the files: 20 | * src/test/resources/TestNGRunTestsLocally.xml 21 | * src/test/resources/TestNGRunTestsRemotely.xml 22 | 23 | And chose "Run ..." 24 | 25 | To run just one selected feature, change the feature name in the class below: 26 | 27 | cucumber.examples.java.testNG.runners.RunSingleFeature 28 | 29 | And as in previous example, right click on this class and chose "Run ..." 30 | 31 | 32 | ## Running features from CLI 33 | Run tests using local browsers: 34 | 35 | mvn clean install 36 | 37 | Run tests using browsers running on remote nodes: 38 | 39 | mvn clean install -P runTestsRemotely 40 | 41 | 42 | ## Viewing the results 43 | All Cucumber reports [html, json, xml, js] are in: target/cucumber-report 44 | 45 | 46 | ## How to download WebDriver binaries automatically 47 | This project is using Mark Collin's "selenium-standalone-server-plugin" which is a Maven plugin that can download 48 | WebDriver binaries automatically. 49 | Once you configure the plugin to your liking, then: 50 | 51 | mvn clean install -P downloadDriverBinaries 52 | 53 | The pom.xml is currently configured to download only a Chrome driver binary for 64bit Linux OSes. 54 | If you can't download desired driver binary, then check if its URL and checksum specified in: 55 | 56 | src/main/resources/RepositoryMapForMavenWebDriverBinaryDownloaderPlugin.xml 57 | 58 | are correct. If not, then modify this file accordingly. 59 | 60 | 61 | ## Jenkins configuration 62 | I'll add a tutorial later 63 | 64 | ### tools that need to be installed on the Jenkins Host machine 65 | maven 2/3 66 | 67 | ### List of useful plugins 68 | AnsiColor 69 | Cucumber json test reporting. 70 | cucumber-perf 71 | cucumber-reports 72 | GIT client plugin 73 | GIT plugin 74 | Hudson Locks and Latches plugin 75 | Maven Integration plugin 76 | SSH Credentials Plugin 77 | TestNG Results Plugin 78 | Xvfb plugin -------------------------------------------------------------------------------- /binaries/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore all other files 2 | * 3 | 4 | # do not ignore itself :) 5 | !.gitignore -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | info.cukes 4 | cucumber-testng-parallel-selenium 5 | jar 6 | Example Java TestNG Cucumber parallel test executor 7 | 1.0 8 | 9 | 10 | UTF-8 11 | 2.9 12 | 1.7 13 | 2 14 | 15 | 1.0.0 16 | 1.1.7-SNAPSHOT 17 | 1.1.7-SNAPSHOT 18 | 1.0.3 19 | 1.2.17 20 | 1.3.2 21 | 3.1 22 | 2.16 23 | 1.2.1 24 | 1.3 25 | 2.40.0 26 | 6.8.7 27 | 28 | 29 | 30 | 35 | 36 | runTestsLocally 37 | 38 | true 39 | 40 | 41 | src/test/resources/TestNGRunTestsLocally.xml 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-compiler-plugin 48 | ${org.apache.maven.plugins.maven-compiler-plugin.version} 49 | 50 | UTF-8 51 | ${source.and.target.JVM.version} 52 | ${source.and.target.JVM.version} 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-surefire-plugin 58 | ${org.apache.maven.plugins.maven-surefire-plugin.version} 59 | 60 | 61 | ${testNG.suiteXmlFile} 62 | 63 | false 64 | -Duser.language=en 65 | -Xmx1024m 66 | -XX:MaxPermSize=256m 67 | -Dfile.encoding=UTF-8 68 | false 69 | 73 | true 74 | 75 | 76 | 77 | integration-test 78 | 79 | test 80 | 81 | 82 | 83 | 84 | 85 | org.codehaus.mojo 86 | exec-maven-plugin 87 | ${org.codehaus.mojo.exec-maven-plugin.version} 88 | 89 | 90 | merge-cucumber-js-reports 91 | post-integration-test 92 | 93 | java 94 | 95 | 96 | test 97 | cucumber.jvm.parallel.JSReportMerger 98 | 99 | target/cucumber-report/ 100 | 101 | 102 | 103 | 104 | merge-cucumber-json-reports 105 | post-integration-test 106 | 107 | java 108 | 109 | 110 | test 111 | cucumber.jvm.parallel.JSONReportMerger 112 | 113 | target/cucumber-report/ 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | runTestsRemotely 124 | 132 | 133 | src/test/resources/TestNGRunTestsRemotely.xml 134 | 135 | 136 | 137 | 138 | org.apache.maven.plugins 139 | maven-compiler-plugin 140 | ${org.apache.maven.plugins.maven-compiler-plugin.version} 141 | 142 | UTF-8 143 | ${source.and.target.JVM.version} 144 | ${source.and.target.JVM.version} 145 | 146 | 147 | 148 | org.apache.maven.plugins 149 | maven-surefire-plugin 150 | ${org.apache.maven.plugins.maven-surefire-plugin.version} 151 | 152 | 153 | ${testNG.suiteXmlFile} 154 | 155 | false 156 | -Duser.language=en 157 | -Xmx1024m 158 | -XX:MaxPermSize=256m 159 | -Dfile.encoding=UTF-8 160 | false 161 | 165 | true 166 | 167 | 168 | 169 | integration-test 170 | 171 | test 172 | 173 | 174 | 175 | 176 | 177 | org.codehaus.mojo 178 | exec-maven-plugin 179 | ${org.codehaus.mojo.exec-maven-plugin.version} 180 | 195 | 196 | merge-cucumber-json-reports 197 | post-integration-test 198 | 199 | java 200 | 201 | 202 | test 203 | cucumber.jvm.parallel.JSONReportMerger 204 | 205 | target/cucumber-report/ 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 219 | downloadDriverBinaries 220 | 221 | 222 | 223 | org.apache.maven.plugins 224 | maven-compiler-plugin 225 | ${org.apache.maven.plugins.maven-compiler-plugin.version} 226 | 227 | ${source.and.target.JVM.version} 228 | ${source.and.target.JVM.version} 229 | 230 | 231 | 232 | com.lazerycode.selenium 233 | driver-binary-downloader-maven-plugin 234 | ${com.lazerycode.selenium.version} 235 | 236 | 237 | binaries 238 | 239 | binaries/zips 240 | src/main/resources/RepositoryMapForMavenWebDriverBinaryDownloaderPlugin.xml 241 | 242 | false 243 | true 244 | false 245 | 246 | 247 | false 248 | 249 | true 250 | 251 | true 252 | true 253 | 254 | 255 | ${googlchromedriverbinary.version} 256 | 257 | 258 | 259 | 2 260 | 261 | 20000 262 | 263 | 10000 264 | 265 | false 266 | 267 | 268 | 269 | 270 | 271 | 272 | selenium 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | sonatype-snapshots 285 | https://oss.sonatype.org/content/repositories/snapshots 286 | 287 | true 288 | 289 | 290 | 291 | 292 | 293 | 294 | info.cukes 295 | cucumber-jvm-deps 296 | ${info.cukes.cucumber-jvm-deps.version} 297 | 298 | 299 | com.thoughtworks.xstream 300 | xstream 301 | 302 | 303 | com.googlecode.java-diff-utils 304 | diffutils 305 | 306 | 307 | 308 | 309 | info.cukes 310 | cucumber-java 311 | ${info.cukes.cucumber-java.version} 312 | test 313 | 314 | 315 | org.testng 316 | testng 317 | ${org.testng.testng.version} 318 | 319 | 320 | info.cukes 321 | cucumber-testng 322 | ${info.cukes.cucumber-testng.version} 323 | 324 | 325 | junit 326 | junit 327 | 328 | 329 | 330 | 331 | info.cukes 332 | cucumber-junit 333 | ${info.cukes.cucumber-java.version} 334 | test 335 | 336 | 337 | org.seleniumhq.selenium 338 | selenium-server 339 | ${org.seleniumhq.selenium.selenium-server.version} 340 | test 341 | 342 | 343 | log4j 344 | log4j 345 | ${log4j.log4j.version} 346 | 347 | 348 | org.apache.commons 349 | commons-io 350 | ${org.apache.commons.commons-io.version} 351 | 352 | 353 | 360 | org.hamcrest 361 | hamcrest-library 362 | ${org.hamcrest.hamcrest-library.version} 363 | 364 | 365 | 372 | com.lazerycode.selenium 373 | driver-binary-downloader-maven-plugin 374 | ${com.lazerycode.selenium.version} 375 | 376 | 377 | 380 | com.googlecode.json-simple 381 | json-simple 382 | 1.1 383 | 384 | 385 | -------------------------------------------------------------------------------- /src/main/java/cucumber/jvm/parallel/JSONReportMerger.java: -------------------------------------------------------------------------------- 1 | package cucumber.jvm.parallel; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.json.simple.JSONArray; 5 | import org.json.simple.parser.JSONParser; 6 | import org.json.simple.parser.ParseException; 7 | import org.json.simple.JSONObject; 8 | 9 | import org.apache.commons.io.FileUtils; 10 | 11 | 12 | import java.io.*; 13 | import java.nio.file.Files; 14 | import java.nio.file.LinkOption; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | import java.util.Collection; 18 | import java.util.UUID; 19 | 20 | /** 21 | * A Cucumber JSON report merger, based on Tristan McCarthy's Cucumber JS Report Merger 22 | * @author Janusz Kowalczyk 23 | * @see - http://www.opencredo.com/2013/07/02/running-cucumber-jvm-tests-in-parallel/ 24 | * @see - https://github.com/tristanmccarthy/Cucumber-JVM-Parallel 25 | */ 26 | public class JSONReportMerger { 27 | private static String reportFileName = "cucumber.json"; 28 | private static String reportImageExtension = "png"; 29 | static Logger log; 30 | 31 | static { 32 | log = Logger.getLogger(JSONReportMerger.class); 33 | } 34 | 35 | public static void main(String[] args) throws Throwable { 36 | File reportDirectory = new File(args[0]); 37 | if (reportDirectory.exists()) { 38 | JSONReportMerger munger = new JSONReportMerger(); 39 | munger.mergeReports(reportDirectory); 40 | } 41 | } 42 | 43 | 44 | /** 45 | * Merge all reports together into master report in given reportDirectory 46 | * @param reportDirectory 47 | * @throws Exception 48 | */ 49 | public void mergeReports(File reportDirectory) throws Throwable { 50 | 51 | // check if other reporter already copied json report to target directory 52 | // if so, then delete it, so that we can merge all the sub reports properly 53 | Path targetReportPath = Paths.get(reportDirectory.toString() + File.separator + reportFileName); 54 | if ( Files.exists(targetReportPath, LinkOption.NOFOLLOW_LINKS) ) { 55 | FileUtils.forceDelete(targetReportPath.toFile()); 56 | } 57 | 58 | File mergedReport = null; 59 | Collection existingReports = FileUtils.listFiles(reportDirectory, new String[]{"json"}, true); 60 | 61 | for (File report : existingReports) { 62 | //only address report files 63 | if (report.getName().equals(reportFileName)) { 64 | //rename all the image files (to give unique names) in report directory and update report 65 | renameEmbededImages(report); 66 | // prepend parent report directory name to all feature IDs and Names 67 | // this helps analyse report later on 68 | renameFeatureIDsAndNames(report); 69 | /* 70 | //if we are on the first pass, copy the directory of the file to use as basis for merge 71 | if (mergedReport == null) { 72 | // copy just the cucumber.json 73 | FileUtils.copyFileToDirectory(report, reportDirectory); 74 | // access this first copied report 75 | mergedReport = new File(reportDirectory, reportFileName); 76 | } else { 77 | //otherwise merge this report into existing master report 78 | mergeFiles(mergedReport, report); 79 | }*/ 80 | } 81 | } 82 | } 83 | 84 | /** 85 | * merge source file into target 86 | * 87 | * @param target 88 | * @param source 89 | */ 90 | public void mergeFiles(File target, File source) throws Throwable { 91 | 92 | String targetReport = FileUtils.readFileToString(target); 93 | String sourceReport = FileUtils.readFileToString(source); 94 | 95 | JSONParser jp = new JSONParser(); 96 | 97 | try { 98 | JSONArray parsedTargetJSON = (JSONArray)jp.parse(targetReport); 99 | JSONArray parsedSourceJSON = (JSONArray)jp.parse(sourceReport); 100 | // Merge two JSON reports 101 | parsedTargetJSON.addAll(parsedSourceJSON); 102 | // this is a new writer that adds JSON indentation. 103 | Writer writer = new JSONWriter(); 104 | // convert our parsedJSON to a pretty form 105 | parsedTargetJSON.writeJSONString(writer); 106 | // and save the pretty version to disk 107 | FileUtils.writeStringToFile(target, writer.toString()); 108 | } catch (ParseException pe) { 109 | pe.printStackTrace(); 110 | } catch (Exception e) { 111 | e.printStackTrace(); 112 | } 113 | } 114 | 115 | /** 116 | * Prepend parent directory name to all feature names for easier report analysis 117 | * 118 | * @param reportFile 119 | */ 120 | public void renameFeatureIDsAndNames(File reportFile) throws Throwable { 121 | String reportDirName = reportFile.getParentFile().getName(); 122 | String fileAsString = FileUtils.readFileToString(reportFile); 123 | JSONParser jp = new JSONParser(); 124 | 125 | try { 126 | JSONArray parsedJSON = (JSONArray)jp.parse(fileAsString); 127 | 128 | for (Object o : parsedJSON) { 129 | JSONObject jo = (JSONObject) o; 130 | String curFeatureID = jo.get("id").toString(); 131 | String curFeatureName = jo.get("name").toString(); 132 | 133 | String newFeatureID = String.format("%s - %s", reportDirName, curFeatureID); 134 | String newFeatureName = String.format("%s - %s", reportDirName, curFeatureName); 135 | 136 | log.info("Changing feature ID and Name from: " + curFeatureID + " / " + curFeatureName + " to: " + newFeatureID + " / " + newFeatureName); 137 | 138 | jo.put("id", newFeatureID); 139 | jo.put("name", newFeatureName); 140 | } 141 | // this is a new writer that adds JSON indentation. 142 | Writer writer = new JSONWriter(); 143 | // convert our parsedJSON to a pretty form 144 | parsedJSON.writeJSONString(writer); 145 | // and save the pretty version to disk 146 | FileUtils.writeStringToFile(reportFile, writer.toString()); 147 | } catch (ParseException pe) { 148 | pe.printStackTrace(); 149 | } catch (Exception e) { 150 | e.printStackTrace(); 151 | } 152 | } 153 | 154 | /** 155 | * Give unique names to embedded images to ensure they aren't lost during merge 156 | * Update report file to reflect new image names 157 | * 158 | * @param reportFile 159 | */ 160 | public void renameEmbededImages(File reportFile) throws Throwable { 161 | File reportDirectory = reportFile.getParentFile(); 162 | Collection embeddedImages = FileUtils.listFiles(reportDirectory, new String[]{reportImageExtension}, true); 163 | 164 | String fileAsString = FileUtils.readFileToString(reportFile); 165 | 166 | for (File image : embeddedImages) { 167 | String curImageName = image.getName(); 168 | String uniqueImageName = reportDirectory.getName() + "-" + UUID.randomUUID().toString() + "." + reportImageExtension; 169 | 170 | image.renameTo(new File(reportDirectory, uniqueImageName)); 171 | fileAsString = fileAsString.replace(curImageName, uniqueImageName); 172 | } 173 | 174 | FileUtils.writeStringToFile(reportFile, fileAsString); 175 | } 176 | } -------------------------------------------------------------------------------- /src/main/java/cucumber/jvm/parallel/JSONWriter.java: -------------------------------------------------------------------------------- 1 | package cucumber.jvm.parallel; 2 | 3 | import java.io.StringWriter; 4 | 5 | /** 6 | * 7 | * http://code.google.com/p/json-simple/issues/detail?id=22 8 | * http://code.google.com/p/json-simple/issues/attachmentText?id=22&aid=220009000&name=JSonWriter.java&token=JFPBdQPSUs1cIM6Bl0KdKxP5BUs%3A1397646495589 9 | * @author Elad Tabak 10 | * @since 28-Nov-2011 11 | * @version 0.1 12 | * 13 | */ 14 | public class JSONWriter extends StringWriter { 15 | 16 | private int indent = 0; 17 | 18 | @Override 19 | public void write(int c) { 20 | if (((char)c) == '[' || ((char)c) == '{') { 21 | super.write(c); 22 | super.write('\n'); 23 | indent++; 24 | writeIndentation(); 25 | } else if (((char)c) == ',') { 26 | super.write(c); 27 | super.write('\n'); 28 | writeIndentation(); 29 | } else if (((char)c) == ']' || ((char)c) == '}') { 30 | super.write('\n'); 31 | indent--; 32 | writeIndentation(); 33 | super.write(c); 34 | } else { 35 | super.write(c); 36 | } 37 | 38 | } 39 | 40 | private void writeIndentation() { 41 | for (int i = 0; i < indent; i++) { 42 | super.write(" "); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cucumber/jvm/parallel/JSReportMerger.java: -------------------------------------------------------------------------------- 1 | package cucumber.jvm.parallel; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | import java.io.*; 5 | import java.util.Collection; 6 | import java.util.UUID; 7 | 8 | /** 9 | * 10 | * @author Tristan McCarthy 11 | * @see - http://www.opencredo.com/2013/07/02/running-cucumber-jvm-tests-in-parallel/ 12 | * @see - https://github.com/tristanmccarthy/Cucumber-JVM-Parallel 13 | */ 14 | public class JSReportMerger { 15 | private static String reportFileName = "report.js"; 16 | private static String reportImageExtension = "png"; 17 | 18 | public static void main(String[] args) throws Throwable { 19 | File reportDirectory = new File(args[0]); 20 | if (reportDirectory.exists()) { 21 | JSReportMerger munger = new JSReportMerger(); 22 | munger.mergeReports(reportDirectory); 23 | } 24 | } 25 | 26 | /** 27 | * Merge all reports together into master report in given reportDirectory 28 | * @param reportDirectory 29 | * @throws Exception 30 | */ 31 | public void mergeReports(File reportDirectory) throws Throwable { 32 | Collection existingReports = FileUtils.listFiles(reportDirectory, new String[]{"js"}, true); 33 | 34 | File mergedReport = null; 35 | 36 | for (File report : existingReports) { 37 | //only address report files 38 | if (report.getName().equals(reportFileName)) { 39 | //rename all the image files (to give unique names) in report directory and update report 40 | renameEmbededImages(report); 41 | 42 | //if we are on the first pass, copy the directory of the file to use as basis for merge 43 | if (mergedReport == null) { 44 | FileUtils.copyDirectory(report.getParentFile(), reportDirectory); 45 | mergedReport = new File(reportDirectory, reportFileName); 46 | //otherwise merge this report into existing master report 47 | } else { 48 | mergeFiles(mergedReport, report); 49 | } 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * merge source file into target 56 | * 57 | * @param target 58 | * @param source 59 | */ 60 | public void mergeFiles(File target, File source) throws Throwable { 61 | //copy embeded images 62 | Collection embeddedImages = FileUtils.listFiles(source.getParentFile(), new String[]{reportImageExtension}, true); 63 | for (File image : embeddedImages) { 64 | FileUtils.copyFileToDirectory(image, target.getParentFile()); 65 | } 66 | 67 | //merge report files 68 | String targetReport = FileUtils.readFileToString(target); 69 | String sourceReport = FileUtils.readFileToString(source); 70 | 71 | FileUtils.writeStringToFile(target, targetReport + sourceReport); 72 | } 73 | 74 | /** 75 | * Give unique names to embedded images to ensure they aren't lost during merge 76 | * Update report file to reflect new image names 77 | * 78 | * @param reportFile 79 | */ 80 | public void renameEmbededImages(File reportFile) throws Throwable { 81 | File reportDirectory = reportFile.getParentFile(); 82 | Collection embeddedImages = FileUtils.listFiles(reportDirectory, new String[]{reportImageExtension}, true); 83 | 84 | String fileAsString = FileUtils.readFileToString(reportFile); 85 | 86 | for (File image : embeddedImages) { 87 | String curImageName = image.getName(); 88 | String uniqueImageName = UUID.randomUUID().toString() + "." + reportImageExtension; 89 | 90 | image.renameTo(new File(reportDirectory, uniqueImageName)); 91 | fileAsString = fileAsString.replace(curImageName, uniqueImageName); 92 | } 93 | 94 | FileUtils.writeStringToFile(reportFile, fileAsString); 95 | } 96 | } -------------------------------------------------------------------------------- /src/main/java/cucumber/jvm/parallel/JUnitReportMerger.java: -------------------------------------------------------------------------------- 1 | package cucumber.jvm.parallel; 2 | 3 | import org.apache.commons.io.FileUtils; 4 | 5 | import java.io.File; 6 | import java.util.Collection; 7 | import java.util.UUID; 8 | 9 | /** 10 | * 11 | * @author Tristan McCarthy 12 | * @see - http://www.opencredo.com/2013/07/02/running-cucumber-jvm-tests-in-parallel/ 13 | * @see - https://github.com/tristanmccarthy/Cucumber-JVM-Parallel 14 | */ 15 | public class JUnitReportMerger { 16 | private static String reportFileName = "report.js"; 17 | private static String reportImageExtension = "png"; 18 | 19 | public static void main(String[] args) throws Throwable { 20 | File reportDirectory = new File(args[0]); 21 | if (reportDirectory.exists()) { 22 | JUnitReportMerger munger = new JUnitReportMerger(); 23 | munger.mergeReports(reportDirectory); 24 | } 25 | } 26 | 27 | /** 28 | * Merge all reports together into master report in given reportDirectory 29 | * @param reportDirectory 30 | * @throws Exception 31 | */ 32 | public void mergeReports(File reportDirectory) throws Throwable { 33 | Collection existingReports = FileUtils.listFiles(reportDirectory, new String[]{"js"}, true); 34 | 35 | File mergedReport = null; 36 | 37 | for (File report : existingReports) { 38 | //only address report files 39 | if (report.getName().equals(reportFileName)) { 40 | //rename all the image files (to give unique names) in report directory and update report 41 | renameEmbededImages(report); 42 | 43 | //if we are on the first pass, copy the directory of the file to use as basis for merge 44 | if (mergedReport == null) { 45 | FileUtils.copyDirectory(report.getParentFile(), reportDirectory); 46 | mergedReport = new File(reportDirectory, reportFileName); 47 | //otherwise merge this report into existing master report 48 | } else { 49 | mergeFiles(mergedReport, report); 50 | } 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * merge source file into target 57 | * 58 | * @param target 59 | * @param source 60 | */ 61 | public void mergeFiles(File target, File source) throws Throwable { 62 | //copy embeded images 63 | Collection embeddedImages = FileUtils.listFiles(source.getParentFile(), new String[]{reportImageExtension}, true); 64 | for (File image : embeddedImages) { 65 | FileUtils.copyFileToDirectory(image, target.getParentFile()); 66 | } 67 | 68 | //merge report files 69 | String targetReport = FileUtils.readFileToString(target); 70 | String sourceReport = FileUtils.readFileToString(source); 71 | 72 | FileUtils.writeStringToFile(target, targetReport + sourceReport); 73 | } 74 | 75 | /** 76 | * Give unique names to embedded images to ensure they aren't lost during merge 77 | * Update report file to reflect new image names 78 | * 79 | * @param reportFile 80 | */ 81 | public void renameEmbededImages(File reportFile) throws Throwable { 82 | File reportDirectory = reportFile.getParentFile(); 83 | Collection embeddedImages = FileUtils.listFiles(reportDirectory, new String[]{reportImageExtension}, true); 84 | 85 | String fileAsString = FileUtils.readFileToString(reportFile); 86 | 87 | for (File image : embeddedImages) { 88 | String curImageName = image.getName(); 89 | String uniqueImageName = UUID.randomUUID().toString() + "." + reportImageExtension; 90 | 91 | image.renameTo(new File(reportDirectory, uniqueImageName)); 92 | fileAsString = fileAsString.replace(curImageName, uniqueImageName); 93 | } 94 | 95 | FileUtils.writeStringToFile(reportFile, fileAsString); 96 | } 97 | } -------------------------------------------------------------------------------- /src/main/resources/RepositoryMapForMavenWebDriverBinaryDownloaderPlugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | https://selenium.googlecode.com/files/IEDriverServer_Win32_2.37.0.zip 8 | d23aa898f50946f6b1ae5fa933116cff1b83f150 9 | sha1 10 | 11 | 12 | https://selenium.googlecode.com/files/IEDriverServer_x64_2.37.0.zip 13 | 38acef909ef660953aa189558cf5d7bff2f6d801 14 | sha1 15 | 16 | 17 | 18 | 19 | http://selenium-release.storage.googleapis.com/2.39/IEDriverServer_Win32_2.39.0.zip 20 | bd4bc2b77a04999148e7fab974336e99 21 | md5 22 | 23 | 24 | http://selenium-release.storage.googleapis.com/2.39/IEDriverServer_x64_2.39.0.zip 25 | 7d19f3d7ffb9cb40fc26cc38885b9160 26 | md5 27 | 28 | 29 | 30 | 31 | http://selenium-release.storage.googleapis.com/2.40/IEDriverServer_Win32_2.40.0.zip 32 | 90c495b4bcd5e86c0a2e5c5c4ac16369 33 | md5 34 | 35 | 36 | http://selenium-release.storage.googleapis.com/2.40/IEDriverServer_x64_2.40.0.zip 37 | 7769f840dc566657efe0a1268ec24904 38 | md5 39 | 40 | 41 | 42 | 43 | 44 | 45 | http://chromedriver.storage.googleapis.com/2.6/chromedriver_win32.zip 46 | 48f8d2462e44b8d06f88ff56857620c0 47 | md5 48 | 49 | 50 | 51 | 52 | http://chromedriver.storage.googleapis.com/2.7/chromedriver_win32.zip 53 | f9b86861545951bd24da0235f9793f67 54 | md5 55 | 56 | 57 | 58 | 59 | http://chromedriver.storage.googleapis.com/2.8/chromedriver_win32.zip 60 | 209236a3fc361f06c0bf8554010fc231 61 | md5 62 | 63 | 64 | 65 | 66 | http://chromedriver.storage.googleapis.com/2.9/chromedriver_win32.zip 67 | ef6a4819563ef993c3aac8f229c0ca91 68 | md5 69 | 70 | 71 | 72 | 73 | 74 | 75 | https://phantomjs.googlecode.com/files/phantomjs-1.9.2-windows.zip 76 | 5fcfb32d9df9e603a3980139026bc33d516dae01 77 | sha1 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | http://chromedriver.storage.googleapis.com/2.0/chromedriver_linux32.zip 87 | c0d96102715c4916b872f91f5bf9b12c 88 | md5 89 | 90 | 91 | http://chromedriver.storage.googleapis.com/2.0/chromedriver_linux64.zip 92 | 858ebaf47e13dce7600191ed59974c09 93 | md5 94 | 95 | 96 | 97 | 98 | http://chromedriver.storage.googleapis.com/2.1/chromedriver_linux32.zip 99 | 1d7e908253f7240d1596332082cc5742 100 | md5 101 | 102 | 103 | http://chromedriver.storage.googleapis.com/2.1/chromedriver_linux64.zip 104 | de406b5a1aac2bfb2f419ac01d7231e2 105 | md5 106 | 107 | 108 | 109 | 110 | http://chromedriver.storage.googleapis.com/2.2/chromedriver_linux32.zip 111 | 801b9f6c28a32575d8eae2abb1cdecd5 112 | md5 113 | 114 | 115 | http://chromedriver.storage.googleapis.com/2.2/chromedriver_linux64.zip 116 | d5b73ee424717e45601553e91e204ad6 117 | md5 118 | 119 | 120 | 121 | 122 | http://chromedriver.storage.googleapis.com/2.3/chromedriver_linux32.zip 123 | f3af4d92060e6d61c2d2ed86ad584246 124 | md5 125 | 126 | 127 | http://chromedriver.storage.googleapis.com/2.3/chromedriver_linux64.zip 128 | 1a816cc185a15af4d450805629790b0a 129 | md5 130 | 131 | 132 | 133 | 134 | http://chromedriver.storage.googleapis.com/2.6/chromedriver_linux32.zip 135 | 3f8ef2f01a7fb5805bed2d644569acba 136 | md5 137 | 138 | 139 | http://chromedriver.storage.googleapis.com/2.6/chromedriver_linux64.zip 140 | 5e70555cbbf75e3480dd1629a35bc7e3 141 | md5 142 | 143 | 144 | 145 | 146 | http://chromedriver.storage.googleapis.com/2.5/chromedriver_linux32.zip 147 | aa61a13c88349be12750d339c7798dc7 148 | md5 149 | 150 | 151 | http://chromedriver.storage.googleapis.com/2.5/chromedriver_linux64.zip 152 | 2a45ca3b32bd1685bb796eeb564e9fc8 153 | md5 154 | 155 | 156 | 157 | 158 | http://chromedriver.storage.googleapis.com/2.6/chromedriver_linux32.zip 159 | 007bbff965cb4be6fe7734236b5afd52 160 | md5 161 | 162 | 163 | http://chromedriver.storage.googleapis.com/2.6/chromedriver_linux64.zip 164 | 0dbc5606c8481f8ae9775d1a27c3a8a5 165 | md5 166 | 167 | 168 | 169 | 170 | http://chromedriver.storage.googleapis.com/2.7/chromedriver_linux32.zip 171 | f054ab7718126c55daa73dd8b13d10bf 172 | md5 173 | 174 | 175 | http://chromedriver.storage.googleapis.com/2.7/chromedriver_linux64.zip 176 | 07e80e731ee84bc16834c36142d03b8e 177 | md5 178 | 179 | 180 | 181 | 182 | http://chromedriver.storage.googleapis.com/2.8/chromedriver_linux32.zip 183 | 8db373d7cd45a4a188519041ccf271db 184 | md5 185 | 186 | 187 | http://chromedriver.storage.googleapis.com/2.8/chromedriver_linux64.zip 188 | 72a3eacd74b6e3d67d4871a747ecbeb2 189 | md5 190 | 191 | 192 | 193 | 194 | http://chromedriver.storage.googleapis.com/2.9/chromedriver_linux32.zip 195 | 780c12bf34d4f4029b7586b927b1fa69 196 | md5 197 | 198 | 199 | http://chromedriver.storage.googleapis.com/2.9/chromedriver_linux64.zip 200 | e2e44f064ecb69ec5b6f1b80f3d13c93 201 | md5 202 | 203 | 204 | 205 | 206 | 207 | 208 | https://phantomjs.googlecode.com/files/phantomjs-1.9.2-linux-x86_64.tar.bz2 209 | c78c4037d98fa893e66fc516214499c58228d2f9 210 | sha1 211 | 212 | 213 | https://phantomjs.googlecode.com/files/phantomjs-1.9.2-linux-i686.tar.bz2 214 | 9ead5dd275f79eaced61ce63dbeca58be4d7f090 215 | sha1 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | http://chromedriver.storage.googleapis.com/2.6/chromedriver_mac32.zip 225 | 4643652d403961dd9a9a1980eb1a06bf8b6e9bad 226 | sha1 227 | 228 | 229 | 230 | 231 | 232 | 233 | https://phantomjs.googlecode.com/files/phantomjs-1.9.2-macosx.zip 234 | 36357dc95c0676fb4972420ad25455f49a8f3331 235 | sha1 236 | 237 | 238 | 239 | 240 | -------------------------------------------------------------------------------- /src/main/resources/RepositoryMapForMavenWebDriverBinaryDownloaderPlugin.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/DriverManager.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.openqa.selenium.*; 5 | import org.openqa.selenium.remote.RemoteWebDriver; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * A generic WebDriver manager, it works with local and remote instances of WebDriver. 11 | * 12 | * @author: Confusions Personified 13 | * @src: http://rationaleemotions.wordpress.com/2013/07/31/parallel-webdriver-executions-using-testng/ 14 | */ 15 | public class DriverManager { 16 | 17 | /* 18 | This simple line does all the mutlithread magic. 19 | For more details please refer to the src link above :) 20 | */ 21 | private static ThreadLocal driver = new ThreadLocal(); 22 | static Logger log; 23 | 24 | static { 25 | log = Logger.getLogger(DriverManager.class); 26 | } 27 | 28 | public static WebDriver getDriver() { 29 | if (driver.get() == null) { 30 | // this is need when running tests from IDE 31 | log.info("Thread has no WedDriver, creating new one"); 32 | setWebDriver(LocalDriverFactory.createInstance(null)); 33 | } 34 | log.debug("Getting instance of remote driver" + driver.get().getClass()); 35 | return driver.get(); 36 | } 37 | 38 | public static void setWebDriver(WebDriver driver) { 39 | driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); 40 | DriverManager.driver.set(driver); 41 | } 42 | 43 | /** 44 | * Returns a string containing current browser name, its version and OS name. 45 | * This method is used in the the *WebDriverListeners to change the test name. 46 | * */ 47 | public static String getBrowserInfo(){ 48 | log.debug("Getting browser info"); 49 | // we have to cast WebDriver object to RemoteWebDriver here, because the first one does not have a method 50 | // that would tell you which browser it is driving. (sick!) 51 | Capabilities cap = ((RemoteWebDriver) DriverManager.getDriver()).getCapabilities(); 52 | String b = cap.getBrowserName(); 53 | String os = cap.getPlatform().toString(); 54 | String v = cap.getVersion(); 55 | return String.format("%s v:%s %s", b, v, os); 56 | } 57 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/LocalDriverFactory.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG; 2 | 3 | import org.openqa.selenium.WebDriver; 4 | import org.openqa.selenium.chrome.ChromeDriver; 5 | import org.openqa.selenium.firefox.FirefoxDriver; 6 | import org.openqa.selenium.htmlunit.HtmlUnitDriver; 7 | import org.openqa.selenium.ie.InternetExplorerDriver; 8 | 9 | /** 10 | * Based on the LocalDriverFactory found at: onrationaleemotions.wordpress.com 11 | * @author: Confusions Personified 12 | * @src: http://rationaleemotions.wordpress.com/2013/07/31/parallel-webdriver-executions-using-testng/ 13 | */ 14 | public class LocalDriverFactory { 15 | public static WebDriver createInstance(String browserName) { 16 | WebDriver driver; 17 | browserName = (browserName != null) ? browserName : "firefox"; 18 | 19 | switch (Browser.valueOf(browserName.toUpperCase())) { 20 | case FIREFOX: 21 | driver = new FirefoxDriver(); 22 | break; 23 | case IE: 24 | driver = new InternetExplorerDriver(); 25 | break; 26 | case CHROME: 27 | System.setProperty("webdriver.chrome.driver", "binaries/linux/googlechrome/64bit/2.9/chromedriver"); 28 | driver = new ChromeDriver(); 29 | break; 30 | case HTMLUNIT: 31 | driver = new HtmlUnitDriver(); 32 | break; 33 | case HTMLUNITJS: 34 | driver = new HtmlUnitDriver(true); 35 | break; 36 | default: 37 | driver = new FirefoxDriver(); 38 | break; 39 | } 40 | // maximize browser's window on start 41 | driver.manage().window().maximize(); 42 | return driver; 43 | } 44 | 45 | private static enum Browser { 46 | FIREFOX, 47 | CHROME, 48 | IE, 49 | HTMLUNIT, 50 | HTMLUNITJS; 51 | } 52 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/LocalWebDriverListener.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.openqa.selenium.WebDriver; 5 | import org.testng.IInvokedMethod; 6 | import org.testng.IInvokedMethodListener; 7 | import org.testng.ITestResult; 8 | import org.testng.internal.BaseTestMethod; 9 | 10 | import java.lang.reflect.Field; 11 | 12 | 13 | /** 14 | * Based on the LocalDriverFactory found at: onrationaleemotions.wordpress.com 15 | * @author: Confusions Personified 16 | * @src: http://rationaleemotions.wordpress.com/2013/07/31/parallel-webdriver-executions-using-testng/ 17 | */ 18 | public class LocalWebDriverListener implements IInvokedMethodListener { 19 | 20 | static Logger log = Logger.getLogger(LocalWebDriverListener.class); 21 | 22 | @Override 23 | public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { 24 | log.debug("BEGINNING: org.stng.jbehave.LocalWebDriverListener.beforeInvocation"); 25 | if (method.isTestMethod()) { 26 | // get browser name specified in the TestNG XML test suite file 27 | String browserName = method.getTestMethod().getXmlTest().getLocalParameters().get("browserName"); 28 | // get and set new instance of local WebDriver 29 | log.info("getting driver for: " + browserName); 30 | WebDriver driver = LocalDriverFactory.createInstance(browserName); 31 | DriverManager.setWebDriver(driver); 32 | log.info("Done! Created "+ browserName + " driver!" ); 33 | } else { 34 | log.warn("Provided method is NOT a TestNG testMethod!!!"); 35 | } 36 | log.debug("END: org.stng.jbehave.LocalWebDriverListener.beforeInvocation"); 37 | } 38 | 39 | @Override 40 | public void afterInvocation(IInvokedMethod method, ITestResult testResult) { 41 | log.debug("BEGINNING: org.stng.jbehave.LocalWebDriverListener.afterInvocation"); 42 | if (method.isTestMethod()) { 43 | try { 44 | String browser = DriverManager.getBrowserInfo(); 45 | // change the name of the test method that will appear in the report to one that will contain 46 | // also browser name, version and OS. 47 | // very handy when analysing results. 48 | BaseTestMethod bm = (BaseTestMethod)testResult.getMethod(); 49 | Field f = bm.getClass().getSuperclass().getDeclaredField("m_methodName"); 50 | f.setAccessible(true); 51 | String newTestName = testResult.getTestContext().getCurrentXmlTest().getName() + " - " + bm.getMethodName() + " - " + browser; 52 | log.info("Renaming test method name from: '" + bm.getMethodName() + "' to: '" + newTestName + "'"); 53 | f.set(bm, newTestName); 54 | } catch (Exception ex) { 55 | System.out.println("afterInvocation exception:\n" + ex.getMessage()); 56 | ex.printStackTrace(); 57 | } finally { 58 | // close the browser 59 | WebDriver driver = DriverManager.getDriver(); 60 | if (driver != null) { 61 | driver.quit(); 62 | } 63 | } 64 | } 65 | log.debug("END: org.stng.jbehave.LocalWebDriverListener.afterInvocation"); 66 | } 67 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/RemoteDriverFactory.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.openqa.selenium.remote.DesiredCapabilities; 5 | import org.openqa.selenium.remote.RemoteWebDriver; 6 | 7 | import java.net.MalformedURLException; 8 | import java.net.URL; 9 | 10 | /** 11 | * A factory of remote WebDrivers. Based on an example taken from: 12 | * @src: http://rationaleemotions.wordpress.com/2013/07/31/parallel-webdriver-executions-using-testng/ 13 | */ 14 | public class RemoteDriverFactory { 15 | 16 | static Logger log = Logger.getLogger(RemoteDriverFactory.class); 17 | 18 | static RemoteWebDriver createInstance(String browserName) { 19 | URL hubUrl = null; 20 | try { 21 | hubUrl = new URL("http://localhost:4444/wd/hub"); 22 | } catch (MalformedURLException e) { 23 | e.printStackTrace(); 24 | } 25 | return RemoteDriverFactory.createInstance(hubUrl,browserName); 26 | } 27 | 28 | static RemoteWebDriver createInstance(URL hubUrl, String browserName) { 29 | RemoteWebDriver driver = null; 30 | if (browserName.equalsIgnoreCase("firefox")) { 31 | DesiredCapabilities capability = DesiredCapabilities.firefox(); 32 | driver = new RemoteWebDriver(hubUrl, capability); 33 | return driver; 34 | } 35 | if (browserName.equalsIgnoreCase("chrome")) { 36 | DesiredCapabilities capability = DesiredCapabilities.chrome(); 37 | driver = new RemoteWebDriver(hubUrl, capability); 38 | return driver; 39 | } 40 | if (browserName.equalsIgnoreCase("ie")) { 41 | DesiredCapabilities capability = DesiredCapabilities.internetExplorer(); 42 | driver = new RemoteWebDriver(hubUrl, capability); 43 | return driver; 44 | } 45 | if (browserName.equalsIgnoreCase("safari")) { 46 | DesiredCapabilities capability = DesiredCapabilities.safari(); 47 | driver = new RemoteWebDriver(hubUrl, capability); 48 | return driver; 49 | } 50 | if (browserName.equalsIgnoreCase("opera")) { 51 | DesiredCapabilities capability = DesiredCapabilities.opera(); 52 | driver = new RemoteWebDriver(hubUrl, capability); 53 | return driver; 54 | } 55 | if (browserName.equalsIgnoreCase("phantomjs")) { 56 | DesiredCapabilities capability = DesiredCapabilities.phantomjs(); 57 | driver = new RemoteWebDriver(hubUrl, capability); 58 | return driver; 59 | } 60 | if (browserName.equalsIgnoreCase("android")) { 61 | DesiredCapabilities capability = DesiredCapabilities.android(); 62 | driver = new RemoteWebDriver(hubUrl, capability); 63 | return driver; 64 | } 65 | if (browserName.equalsIgnoreCase("htmlUnit")) { 66 | DesiredCapabilities capability = DesiredCapabilities.htmlUnit(); 67 | driver = new RemoteWebDriver(hubUrl, capability); 68 | return driver; 69 | } 70 | if (browserName.equalsIgnoreCase("htmlUnitWithJs")) { 71 | DesiredCapabilities capability = DesiredCapabilities.htmlUnitWithJs(); 72 | driver = new RemoteWebDriver(hubUrl, capability); 73 | return driver; 74 | } 75 | if (browserName.equalsIgnoreCase("ipad")) { 76 | DesiredCapabilities capability = DesiredCapabilities.ipad(); 77 | driver = new RemoteWebDriver(hubUrl, capability); 78 | return driver; 79 | } 80 | if (browserName.equalsIgnoreCase("iphone")) { 81 | DesiredCapabilities capability = DesiredCapabilities.iphone(); 82 | driver = new RemoteWebDriver(hubUrl, capability); 83 | return driver; 84 | } 85 | log.info("RemoteDriverFactory created an instance of RemoteWebDriver for: " + browserName); 86 | return driver; 87 | } 88 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/RemoteWebDriverListener.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG; 2 | 3 | import org.apache.log4j.Logger; 4 | import org.openqa.selenium.WebDriver; 5 | import org.testng.IInvokedMethod; 6 | import org.testng.IInvokedMethodListener; 7 | import org.testng.ITestResult; 8 | import org.testng.internal.BaseTestMethod; 9 | 10 | import java.lang.reflect.Field; 11 | import java.net.MalformedURLException; 12 | import java.net.URL; 13 | 14 | 15 | /** 16 | * A factory of remote WebDrivers. 17 | * Based on the LocalDriverFactory found at: onrationaleemotions.wordpress.com 18 | * @author: Confusions Personified 19 | * @src: http://rationaleemotions.wordpress.com/2013/07/31/parallel-webdriver-executions-using-testng/ 20 | */ 21 | public class RemoteWebDriverListener implements IInvokedMethodListener { 22 | 23 | static Logger log = Logger.getLogger(RemoteWebDriverListener.class); 24 | 25 | @Override 26 | public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { 27 | log.debug("BEGINNING: org.stng.jbehave.RemoteWebDriverListener.beforeInvocation"); 28 | if (method.isTestMethod()) { 29 | // get browser name specified in the TestNG XML test suite file 30 | String browserName = method.getTestMethod().getXmlTest().getLocalParameters().get("browserName"); 31 | URL hubURL = null; 32 | try { 33 | // get hub URL specified in the TestNG XML test suite file 34 | // if not provided then use default http://localhost:4444/wd/hub 35 | hubURL = new URL( 36 | (method.getTestMethod().getXmlTest().getSuite().getParameter("hubURL") != "") 37 | ? method.getTestMethod().getXmlTest().getSuite().getParameter("hubURL") 38 | : "http://localhost:4444/wd/hub"); 39 | } catch (MalformedURLException e) { 40 | System.out.println("ex:\n" + e.getMessage() + ""); 41 | e.printStackTrace(); 42 | } 43 | log.info("HUB URL: " + hubURL); 44 | // get and set new instance of remote WebDriver 45 | WebDriver driver = RemoteDriverFactory.createInstance(hubURL, browserName); 46 | DriverManager.setWebDriver(driver); 47 | log.info("Done! Created "+ browserName + " driver!" ); 48 | } else { 49 | log.warn("Provided method is NOT a TestNG testMethod!!!"); 50 | } 51 | log.debug("END: org.stng.jbehave.RemoteWebDriverListener.beforeInvocation"); 52 | } 53 | 54 | @Override 55 | public void afterInvocation(IInvokedMethod method, ITestResult testResult) { 56 | log.debug("BEGINNING: org.stng.jbehave.RemoteWebDriverListener.afterInvocation"); 57 | if (method.isTestMethod()) { 58 | try { 59 | String browser = DriverManager.getBrowserInfo(); 60 | // change the name of the test method that will appear in the report to one that will contain 61 | // also browser name, version and OS. 62 | // very handy when analysing results. 63 | BaseTestMethod bm = (BaseTestMethod)testResult.getMethod(); 64 | Field f = bm.getClass().getSuperclass().getDeclaredField("m_methodName"); 65 | f.setAccessible(true); 66 | String newTestName = testResult.getTestContext().getCurrentXmlTest().getName() + " - " + bm.getMethodName() + " - " + browser; 67 | log.info("Renaming test method name from: '" + bm.getMethodName() + "' to: '" + newTestName + "'"); 68 | f.set(bm, newTestName); 69 | } catch (Exception ex) { 70 | System.out.println("afterInvocation exception:\n" + ex.getMessage()); 71 | ex.printStackTrace(); 72 | } finally { 73 | // close the browser 74 | WebDriver driver = DriverManager.getDriver(); 75 | if (driver != null) { 76 | driver.quit(); 77 | } 78 | } 79 | } 80 | log.debug("END: org.stng.jbehave.RemoteWebDriverListener.afterInvocation"); 81 | } 82 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/page_objects/GoogleCom.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG.page_objects; 2 | 3 | import org.openqa.selenium.By; 4 | import org.openqa.selenium.WebDriver; 5 | import org.openqa.selenium.WebElement; 6 | import org.openqa.selenium.support.ui.ExpectedConditions; 7 | import org.openqa.selenium.support.ui.WebDriverWait; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Created by jay on 10/04/14. 13 | */ 14 | public class GoogleCom { 15 | 16 | private By search_box = By.id("gbqfq"); 17 | private By search_button = By.id("gbqfb"); 18 | private By search_results = By.cssSelector("li.g"); 19 | private By search_results_list = By.cssSelector("div.srg"); 20 | private WebDriver driver; 21 | private WebDriverWait wait; 22 | private List theSearchResults; 23 | 24 | public GoogleCom(WebDriver driver) { 25 | this.driver = driver; 26 | this.wait = new WebDriverWait(this.driver, 10); 27 | } 28 | 29 | public void go(){ 30 | this.driver.get("https://www.google.co.uk/"); 31 | this.wait.until(ExpectedConditions.visibilityOfElementLocated(search_box)); 32 | } 33 | 34 | 35 | public void searchFor(String keywords){ 36 | this.driver.findElement(search_box).clear(); 37 | this.driver.findElement(search_box).sendKeys(keywords); 38 | this.driver.findElement(search_button).click(); 39 | this.wait.until(ExpectedConditions.visibilityOfElementLocated(search_results_list)); 40 | } 41 | 42 | public List getTheSearchResults() { 43 | return this.driver.findElements(search_results); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/runners/RunCukesTestInChrome.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG.runners; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.testng.AbstractTestNGCucumberTests; 5 | 6 | /** 7 | * Please notice that cucumber.examples.java.testNG.stepDefinitions.BeforeAfterHooks class 8 | * is in the same package as the steps definitions. 9 | * It has two methods that are executed before or after scenario. 10 | * I'm using it to delete cookies and take a screenshot if scenario fails. 11 | */ 12 | @CucumberOptions( 13 | features = "target/test-classes/features", 14 | glue = {"cucumber.examples.java.testNG.stepDefinitions"}, 15 | format = {"pretty", 16 | "html:target/cucumber-report/chrome", 17 | "json:target/cucumber-report/chrome/cucumber.json", 18 | "junit:target/cucumber-report/chrome/cucumber.xml"}) 19 | public class RunCukesTestInChrome extends AbstractTestNGCucumberTests { 20 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/runners/RunCukesTestInFirefox.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG.runners; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.testng.AbstractTestNGCucumberTests; 5 | 6 | /** 7 | * Please notice that cucumber.examples.java.testNG.stepDefinitions.BeforeAfterHooks class 8 | * is in the same package as the steps definitions. 9 | * It has two methods that are executed before or after scenario. 10 | * I'm using it to delete cookies and take a screenshot if scenario fails. 11 | */ 12 | @CucumberOptions( 13 | features = "target/test-classes/features", 14 | glue = {"cucumber.examples.java.testNG.stepDefinitions"}, 15 | format = {"pretty", 16 | "html:target/cucumber-report/firefox", 17 | "json:target/cucumber-report/firefox/cucumber.json", 18 | "junit:target/cucumber-report/firefox/cucumber.xml"}) 19 | public class RunCukesTestInFirefox extends AbstractTestNGCucumberTests { 20 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/runners/RunSingleFeature.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG.runners; 2 | 3 | import cucumber.api.CucumberOptions; 4 | import cucumber.api.junit.Cucumber; 5 | import cucumber.api.testng.AbstractTestNGCucumberTests; 6 | import org.junit.runner.RunWith; 7 | 8 | @RunWith(Cucumber.class) 9 | @CucumberOptions( 10 | features = "target/test-classes/features/bugs/bug_no_12345.feature", 11 | glue = {"cucumber.examples.java.testNG.stepDefinitions"}, 12 | format = {"pretty", 13 | "html:target/cucumber-report/singleFeature", 14 | "json:target/cucumber-report/singleFeature/cucumber.json", 15 | "junit:target/cucumber-report/singleFeature/cucumber.xml"}) 16 | public class RunSingleFeature extends AbstractTestNGCucumberTests { 17 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/stepDefinitions/BeforeAfterHooks.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG.stepDefinitions; 2 | 3 | import cucumber.api.Scenario; 4 | import cucumber.api.java.After; 5 | import cucumber.api.java.Before; 6 | import cucumber.examples.java.testNG.DriverManager; 7 | import org.apache.log4j.Logger; 8 | import org.openqa.selenium.OutputType; 9 | import org.openqa.selenium.TakesScreenshot; 10 | import org.openqa.selenium.WebDriverException; 11 | 12 | /** 13 | * Based on SharedDriver.java taken from cucumber-jvm/examples/java-webbit-websockets-selenium 14 | * 15 | * @src: https://github.com/cucumber/cucumber-jvm/blob/master/examples/java-webbit-websockets-selenium/src/test/java/cucumber/examples/java/websockets/SharedDriver.java 16 | * @author: https://github.com/aslakhellesoy 17 | */ 18 | public class BeforeAfterHooks { 19 | 20 | static Logger log; 21 | 22 | static { 23 | log = Logger.getLogger(BeforeAfterHooks.class); 24 | } 25 | 26 | /** 27 | * Delete all cookies at the start of each scenario to avoid 28 | * shared state between tests 29 | */ 30 | @Before 31 | public void deleteAllCookies() { 32 | log.info("Deleting all cookies..."); 33 | DriverManager.getDriver().manage().deleteAllCookies(); 34 | } 35 | 36 | /** 37 | * Embed a screenshot in test report if test is marked as failed 38 | */ 39 | @After 40 | public static void embedScreenshot(Scenario scenario) { 41 | if ( scenario.isFailed() ) { 42 | log.error("Scenario failed! Browser: " + DriverManager.getBrowserInfo() + " Taking screenshot..."); 43 | scenario.write("Current Page URL is: " + DriverManager.getDriver().getCurrentUrl()); 44 | scenario.write("Scenario Failed in: " + DriverManager.getBrowserInfo()); 45 | try { 46 | byte[] screenshot = ((TakesScreenshot) DriverManager.getDriver()).getScreenshotAs(OutputType.BYTES); 47 | scenario.embed(screenshot, "image/png"); 48 | } catch (WebDriverException somePlatformsDontSupportScreenshots) { 49 | log.error(somePlatformsDontSupportScreenshots.getMessage()); 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/stepDefinitions/Feature1And2Stepdefs.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG.stepDefinitions; 2 | 3 | import cucumber.api.java.en.Given; 4 | import cucumber.api.java.en.Then; 5 | import cucumber.api.java.en.When; 6 | import cucumber.examples.java.testNG.DriverManager; 7 | import org.apache.log4j.Logger; 8 | import org.openqa.selenium.By; 9 | import org.openqa.selenium.WebDriver; 10 | import org.openqa.selenium.WebElement; 11 | import org.openqa.selenium.support.ui.ExpectedConditions; 12 | import org.openqa.selenium.support.ui.WebDriverWait; 13 | 14 | import static org.hamcrest.Matchers.is; 15 | import static org.junit.Assert.assertThat; 16 | 17 | /** 18 | * Contains step definitions for two feature files: first.feature & second.feature 19 | * @author jk 20 | */ 21 | public class Feature1And2Stepdefs { 22 | 23 | static Logger log = Logger.getLogger(Feature1And2Stepdefs.class); 24 | WebDriver driver = DriverManager.getDriver(); 25 | WebElement webElement; 26 | WebDriverWait wait = new WebDriverWait(driver, 10); 27 | 28 | @Given("^I am on (.+)$") 29 | public void givenIAmOn(String URL) { 30 | log.info("Given I'm on "+URL+"
"); 31 | driver.get(URL); 32 | } 33 | 34 | @When("^I search for element (.+)$") 35 | public void whenISearchForElement(String element_id) { 36 | log.info("When I search for element " + element_id); 37 | webElement = driver.findElement(By.id(element_id)); 38 | wait.until(ExpectedConditions.visibilityOfElementLocated(By.id(element_id))); 39 | } 40 | 41 | @Then("^I should see this element$") 42 | public void thenIShouldSeeThisElement() { 43 | log.info("Then I should see this element"); 44 | assertThat("Element is not visible!!!", webElement.isDisplayed(), is(true)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/cucumber/examples/java/testNG/stepDefinitions/bugs/Bug12345.java: -------------------------------------------------------------------------------- 1 | package cucumber.examples.java.testNG.stepDefinitions.bugs; 2 | 3 | import cucumber.api.java.en.Given; 4 | import cucumber.api.java.en.Then; 5 | import cucumber.api.java.en.When; 6 | import cucumber.examples.java.testNG.DriverManager; 7 | import cucumber.examples.java.testNG.page_objects.GoogleCom; 8 | import org.apache.log4j.Logger; 9 | import org.openqa.selenium.*; 10 | import java.util.List; 11 | 12 | import static org.hamcrest.Matchers.is; 13 | import static org.junit.Assert.assertThat; 14 | 15 | /** 16 | * Simple steps mapping class that always fails. 17 | * I've added it to show how failed story will look like in the final Cucumber HTML report. 18 | * @author jk 19 | */ 20 | public class Bug12345 { 21 | 22 | static Logger log; 23 | WebDriver driver = DriverManager.getDriver(); 24 | private GoogleCom googleCom; 25 | private List searchResults; 26 | 27 | static { 28 | log = Logger.getLogger(Bug12345.class); 29 | } 30 | 31 | @Given("^I search on google.com for (.+)") 32 | public void givenISearchFor(String keywords) { 33 | log.info("Given I search google.com for " + keywords); 34 | googleCom = new GoogleCom(this.driver); 35 | googleCom.go(); 36 | googleCom.searchFor(keywords); 37 | } 38 | 39 | @When("^I get the search results$") 40 | public void whenIGetTheSearchResults() { 41 | log.info("When I get the search results"); 42 | this.searchResults = googleCom.getTheSearchResults(); 43 | } 44 | 45 | @Then("^I should find the answer \"(.+)\" among the search result$") 46 | public void thenIShouldSeeThisElement(String answer) { 47 | log.info("Then I should find the answer '"+answer+"' among the search result"); 48 | boolean didIFindTheAnswerToMyQuery = false; 49 | for ( WebElement we : this.searchResults){ 50 | // change the state of didIFindTheAnswerToMyQuery only if the answer was found 51 | didIFindTheAnswerToMyQuery = (we.getText().toLowerCase().contains(answer.toLowerCase())) ? true : didIFindTheAnswerToMyQuery; 52 | } 53 | assertThat("Couldn't find the answer for my question!!!", didIFindTheAnswerToMyQuery, is(true)); 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/resources/TestNGRunTestsLocally.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/test/resources/TestNGRunTestsRemotely.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/features/WIP/WIP.feature: -------------------------------------------------------------------------------- 1 | @WIP 2 | Feature: this is not implemented 3 | 4 | Scenario: Download Internet on your pendrive 5 | Given I have a nice and shiny pendrive 6 | When I plug it in to my laptop 7 | Then I should download whole Internet on to it -------------------------------------------------------------------------------- /src/test/resources/features/bugs/bug_no_12345.feature: -------------------------------------------------------------------------------- 1 | @bug_check 2 | @regression 3 | Feature: this feature does the regression check for bug no 12345 4 | 5 | Scenario Outline: Check if bug no 12345 haven't reoccurred 6 | Given I search on google.com for 7 | When I get the search results 8 | Then I should find the answer "" among the search result 9 | 10 | Examples: 11 | | keywords | answer | 12 | | can google search for itself? | no, it'd blow up the whole Internet | 13 | | where does Elvis live? | next door | -------------------------------------------------------------------------------- /src/test/resources/features/feature_no1/first.feature: -------------------------------------------------------------------------------- 1 | @functional_test 2 | @other_meta_tag 3 | Feature: this is the first feature name 4 | this is an example additional feature description 5 | 6 | 7 | Scenario: Go to cukes.info 8 | Given I am on http://cukes.info/ 9 | When I search for element header 10 | Then I should see this element 11 | 12 | Scenario Outline: Open various websites 13 | Given I am on 14 | When I search for element 15 | Then I should see this element 16 | 17 | Examples: 18 | | URL | element_id | 19 | | https://www.google.co.uk/ | hplogo | 20 | | https://www.facebook.com/ | blueBarHolder | -------------------------------------------------------------------------------- /src/test/resources/features/feature_no2/second.feature: -------------------------------------------------------------------------------- 1 | @functional_test 2 | @other_meta_tag 3 | Feature: this is the second feature name 4 | you can write anything you want here 5 | 6 | 7 | Scenario: Go to amazon.co.uk 8 | Given I am on http://www.amazon.co.uk/ 9 | When I search for element nav-logo 10 | Then I should see this element 11 | 12 | Scenario Outline: Open various websites 13 | Given I am on 14 | When I search for element 15 | Then I should see this element 16 | 17 | Examples: 18 | | URL | element_id | 19 | | https://github.com/cucumber/cucumber/wiki/Gherkin | wiki-wrapper | 20 | | http://testng.org/doc/index.html | topmenu | -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | --------------------------------------------------------------------------------