November 17, 2016

Testing The-Internet with Geb + Groovy + Spock: How did Yeoman set up the tests to run in Gradle?

Part 3 of 5. Need to read from the beginning

For the past few weeks, we've been examining in this blog how to build an automation framework with Geb + the Groovy Language + the Spock Framework, something I have been experimenting with in my new job as a Software Engineer in Test.

One of the developers at my workplace putting together a Geb + Groovy project heard about Yeoman, an open-source tooling application sponsored by the Google Chrome Developer Relations team. This series of blog posts have been examining the software design tool.

Yeoman creates the basic scaffolding for your projects, right out of the box. Instead of re-inventing the wheel, a basic template for the project is produced, depending on what Yeoman generator we picked.

We are using Chris Hluchan's Geb Generator. We will be testing against Dave Haeffner's site, The-Internet, at https://the-internet.herokuapp.com/, taking a look at his Login Page.


Examining the Build.Gradle Configuration File 


If you don't wish to install and configure Node.js, update npm, install and configure Yeoman, and install and configure the Geb Generator, I took the source code that was generated, and placed it as-is on my GitHub site.

Here's an original name! "Geb Project Generated By Yo".

We will be kicking off a few of their built in tests, and examine the build.gradle file and surrounding files to see how they are kicked off.

The next blog article we will be starting to look at the tests themselves. For now, we are focusing on...

Setting Up The Build.Gradle File

When we explored setting up a WebDriver development environment a few months ago, we talked about how, instead of using Maven to handle third-party dependencies, such as ChromeDriver we were going to be looking at Gradle.



The build.gradle file that the Geb generator set up servers many purposes.

  • It imports any files that are needed, such as a library to determine what OS is running the test.
  • It declares the name of the web drivers: Firefox, Chrome, and PhantomJS, a headless browser.
  • It groups up the version numbers of Groovy, Geb, Selenium, ChromeDriver, and PhantomJS all in one section. 
  • It download the Groovy and Gradle plugins needed. 
  • It sets up the data repository as Maven Central. 
  • It compiles all the dependencies using the testCompile Gradle keyword. 
  • It sets up the Html reports for the tests, and the jUnit test-results files and the directories where they will be placed. 
  • It sets up tasks that can be run: "chromeTest", "phantomJsTest" to run tests on specific browsers, or "test" to run the entire browser suite.  

Let's look at how, specifically, this build.gradle file gets this done.


Import Files Needed

 import org.apache.tools.ant.taskdefs.condition.Os  

Imports from Apache tools, the Apache Ant library that finds out what operating system that you are using. Although Apache Ant has been replaced by Apache Maven and Gradle, it still has libraries which are quite useful, such as the Os class (see detailed documentation). This class can test which Operating system the class is running on.

  • Does the Os.isFamily property equal Os_FAMILY_WINDOWS? We need to download Windows Executable files, the *.exe files for Chromedriver and PhantomJs. 
Note: Firefox version 46 and before does not need it's own special webdriver, it used the one built in. Firefox 47 and above uses Geckodriver, which we will not get into here.

To run tests on Chrome, you need to download and install Chromedriver, and set up the system properties for the test to run it. Same with PhantomJs, a popular headless browser used when you want the test to run but not necessarily need an actual browser to open, and PhantomJsDriver. 

This Yeoman Geb Generator sets all that up for us in the Build.Gradle file! 

Declare Extra Properties

 ext {  
  // The drivers we want to use  
  drivers = ["firefox", "chrome", "phantomJs"]  
   
  ext {  
   groovyVersion = '2.4.1'  
   gebVersion = '0.12.2'  
   seleniumVersion = '2.46.0'  
   chromeDriverVersion = '2.19'  
   phantomJsVersion = '1.9.7'  
  }  
 }  
  • Using Groovy, a scripting language for Java, we are declaring an array of Strings: firefox, chrome, and phantomJs, placing them in the variable called drivers. Cycling through the drivers array will show which drivers we can run tests on. 
  • Within the extra property block, we are nesting another extra property block. Need to upgrade to another version of Groovy, Geb, Selenium, ChromeDriver or PhantomJs? You don't need to search the entire file... it is all right here. 


The Applies Block:


 apply plugin: "groovy"  
 apply from: "gradle/idea.gradle"  
 apply from: "gradle/osSpecificDownloads.gradle"  

  • If we were setting up a Java project, we would use the Java plugin for Gradle, as we went over this past Memorial Day Weekend. Since this is a Groovy project, we are using the Groovy Plugin. This will allow us to compileGroovy, compiling the files in src/main, and compileTestGroovy, which will compile files in src/test.
  • Apply Plugin: "Groovy" will call up the Gradle Plugin() interface and apply the Groovy plugin.
  • Apply From: The two Apply From script will be called directly, going into the directory called "gradle", and run the file, "idea.gradle". Next, it will go into "gradle" and run the file "osSpecificDownloads.gradle". 
  • Idea.gradle: Using IntelliJ IDEA? Gradle has a special "idea" plugin that has special parameters that can be set up. To neaten up the main build.gradle file, that idea configuration has been split up. If you take a look at the file, the Geb generator set up a nested idea -> project -> ipr block, which sets up provider.asNode(), then tries to find the first VcsDirectoryMappings for Git. 
  • OsSpecificDownloads.gradle handles all of the specifics needed if you are running a Mac, a PC, or the Unix environment. We will dissect this file later. It handles the Gradle tasks, downloadChromeDriver, unzipChromeDriver, downloadPhantomJs, and unzipPhantomJs. Remember that unzipping a file depends upon the task of the file being downloaded first. When the tests are run, this separate configuration file comes into play, grabbing Chomedriver from the designated http://chromedriver.storage.googleapis.com subfolder, and grabs the correct windows.zip, macosx.zip, linux-x86_64.tar.bz2 or linux-1686.tar.bz2. Likewise, it grabs PhantomJs from elsewhere, from https://bitbucket.org/ariya/phantomjs/downloads/. These files will be stored in the build directory, under build/webdriver.

... Imagine if all the code contained in idea.gradle and osSpecificDownloads.gradle were combined in this main build.gradle configuration file? It would be unreadable!

The Repositories Block:

 repositories {  
  mavenCentral()  
 } 

  • A repository is where you store a group of somethings. A code repository is where you can store code. The Central Repository at http://search.maven.org/ is where you can go to find open-source components such as Spock, Geb, and Groovy. 
  • From Central.Sonotype.org: "The Central Repository is the the default repository for Apache Maven, SBT and other build systems and can be easily used from Apache Ant/Ivy, Gradle and many other tools [...] Open source organizations such as the Apache Software Foundation, the Eclipse Foundation, JBoss and many individual open source projects publish their components to the Central Repository [...] When you develop software, you depend on open source components. And these external libraries are predictably available on the Central Repository".
  • Want to see something more user friendly? See another viewpoint at https://mvnrepository.com/


The Dependencies Block:
 dependencies {  
  // If using Spock, need to depend on geb-spock  
  testCompile "org.gebish:geb-spock:$gebVersion"  
  testCompile("org.spockframework:spock-core:1.0-groovy-2.4") {  
   exclude group: "org.codehaus.groovy"  
  }  

  • Any third party dependencies, such as, say, the Spock Framework, the Groovy Programming language, or Selenium WebDriver? Group up the files here! Less searching that way.
  • Gradle has two different keywords: compile, and testCompile. If Groovy is only used for test code, the Groovy dependency is added to the testCompile configuration. Note: Since everything is test code, in the src/test directory, we are just using testCompile. 
  • For compiling our tests, we will be downloading from then Maven repository, the file geb-spock, 0.12.2.  Note that Geb-Spock is now up to 1.0 as of October 2016. 
  • We are going to also download spock-core, focusing on version 1.0-groovy-2.4. Note that there is a new version.
  • We want to wait to update the groovy dependencies for the real thing, and not just the Spock dependencies. 

Why didn't we set up this build.gradle configuration file to always get the latest version? When assembling the jigsaw puzzle of open-source solutions, an update of one piece of the puzzle may break another piece of functionality elsewhere. When things start breaking, you want the reason to be because the application has a bug, not because of incompatibility issues in your testing framework.



 testCompile "org.codehaus.groovy:groovy-all:$groovyVersion"  
  // If using JUnit, need to depend on geb-junit (3 or 4)  
  testCompile "org.gebish:geb-junit4:$gebVersion"  
  testCompile "org.seleniumhq.selenium:selenium-support:$seleniumVersion"  



 // Drivers  
  testCompile "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"  
  testCompile "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion"  
  // using a custom version of phantomjs driver for now as the original one does not support WebDriver > 2.43.1  
  testCompile("com.codeborne:phantomjsdriver:1.2.1") {  
   // phantomjs driver pulls in a different selenium version  
   transitive = false  


... Imagine having to download and set up all of that stuff by hand! Imagine wanting to run a clean test, deleting everything, and building up your system from scratch!

Since everything is captured in this build.gradle file, you can run all the tests by:

  • Mac: ./gradle tests
  • PC: gradle.bat tests
... if there are any dependencies missing, they will be automatically fetched from the internet, and downloaded into the "build" folder of the project, in whatever subfolder you designate. 

Want to run the tests so that the previous build configuration is blown away? Use the Gradle Wrapper to kick off a clean build of the tests!

  • Mac: ./gradlew clean test
  • PC: gradlew.bat clean test
You do NOT want to install everything by hand. With Gradle, you don't have to!

Set Up Reports:


 drivers.each { driver ->  
  task "${driver}Test"(type: Test) {  
   reports {  
    html.destination = reporting.file("$name/tests")  
    junitXml.destination = file("$buildDir/test-results/$name")  
   }  

Remember how we set up using Groovy the array of Strings called "drivers", which had the values, drivers = ["firefox", "chrome", "phantomJs"]? One by one, it is going to substitute in ${driver} those words in the array of Strings, so it will have three tasks set up:

  • task firefoxTest(type: Test)
  • task chromeTest(type: Test)
  • task phantomJsTest(type: Test)

These three tasks are set up to be source sets of type Test. We are adding these three test suites to run these tests in these source sets.

From The Java Plugin, Gradle.org: "The test task is an instance of Test. It automatically detects and executes all unit tests in the test source set. It also generates a report once test execution is complete. JUnit and TestNG are both supported. Have a look at Test for the complete API".

Because of this setup, to run individual tests, we just need to go into the main directory where this build.gradle file is and run on Mac and Linux:
./gradlew chromeTest
./gradlew firefoxTest
./gradlew phantomJsTest

... or gradlew.bat for the PC.

What about reports?

  • Html reports will be be under firefox/tests, chrome/tests or phantomJs/tests. 
  • JUnit XML Reports will be in the build directory, in build/test-results/firefox, build/test-results/chrome, build/test-results/phantomJs, after the test is run. 


Want to run your tests in parallel? 

"Gradle provides parallel test execution by running multiple test processes concurrently. Each test process executes only a single test at a time, so you generally don't need to do anything special to your tests to take advantage of this. The maxParallelForks property specifies the maximum number of test processes to run at any given time. The default is 1, that is, do not execute the tests in parallel".


Set up System Properties for Geb:
 systemProperty "geb.build.reportsDir", reporting.file("$name/geb")  
 systemProperty "geb.env", driver  
 systemProperty "profile", System.getProperty("profile", "production")  

  • When Geb is looking for the report directory, it will see if there is a system property called geb.build.reportsDir. It will find that the report is in firefox/geb, etc.

Read more about Java and System Properties from Oracle Java Documentation

Set up Individual Tests:
 chromeTest {  
  dependsOn unzipChromeDriver  
  def chromedriverFilename = Os.isFamily(Os.FAMILY_WINDOWS) ? "chromedriver.exe" : "chromedriver"  
  systemProperty "webdriver.chrome.driver", new File(unzipChromeDriver.outputs.files.singleFile, chromedriverFilename).absolutePath  
 }  

  • Remember, unzipChromeDriver is set up in the other configuration file, osSpecificDownloads.gradle file. Running ./gradlew chromeTest will automatically check if Chromedriver is downloaded and unzipped. If not, it will do it for you. 
  • Should you be using the Chromedriver file? Or Chromedriver.exe file (Family_Windows)? This will investigate your system and decide for you. 
  • Last, but not least, this will set up the webdriver.chrome.driver system property, and the location of the file that needs to be run. 


 phantomJsTest {  
  dependsOn unzipPhantomJs  
  def phantomJsFilename = Os.isFamily(Os.FAMILY_WINDOWS) ? "phantomjs.exe" : "bin/phantomjs"  
  systemProperty "phantomjs.binary.path", new File(unzipPhantomJs.outputs.files.singleFile, phantomJsFilename).absolutePath  
 }  

  • As for ChromeDriver, so goes PhantomJs. 


 test {  
  dependsOn drivers.collect { tasks["${it}Test"] }  
  enabled = false  
 }  

  • Want to run all tests? Running ./gradlew test, and it will collect all tasks with the suffix of "test", collect them all, and run them, one by one. 
So, that was the build configuration file! 

Next, we will examine the out-of-the-box tests that are generated. 

Until then, Happy Testing!


Testing The-Internet with Geb + Groovy + Spock:



-T.J. Maher
Twitter | LinkedIn | GitHub

// Sr. QA Engineer, Software Engineer in Test, Software Tester since 1996.
// Contributing Writer for TechBeacon.
// "Looking to move away from manual QA? Follow Adventures in Automation on Facebook!"

No comments: