Sustainable software development requires reliable quality metrics based on automated tests. As you can see in the testing pyramid image this happens on different levels of the software. For automated GUI testing Intershop utilizes the specification framework Spock in combination with the web library Geb.
Term | Description |
---|---|
Geb | Geb is a browser automation solution. It can be used for scripting, scraping and general automation — or equally as a functional/web/acceptance testing solution via integration with testing frameworks such as Spock or JUnit. |
Spock | Spock is a testing and specification framework for Java and Groovy applications. |
General public documentation is available under http://www.gebish.org/. Information on Spock you can find underhttp://spockframework.github.io/spock/docs.
You may also have a look at the according Cookbook - Geb and Spock Tests.
Spock is a test and specification framework. Therefore, you can describe the requirements of the expected behavior with this framework. After implementation you can run the requirements against your code, to check automatically whether your requirements are fulfilled or not.
Spock specifications have the following form:
def "This is an example specification" () { given: //here you can define preconditions when: "This is the example condition" then: "This is the conclusion" when: "You can repeat the when then clause" then: "as often as you need" where: //Here you can give some test values. When you run the specification it is executed with all lines which you give here. variableName1 | variableName2 "variable1.1" | "variable1.2" "variable2.1" | "variable2.2" }
Further information of the behavior you can describe in groovy after the description of the when and then clauses.
Pure Spock specification can be exxecuted with standard JUnit-Runner.
Geb is a jQuery-like browser automation solution. It brings some Web drivers along, so you can run the test automatically against different web browsers as Chrome, Firefox, IE, PhantomJs. Geb can be integrated in different test frameworks like Spock, JUnit or TestNG.
Here is an example for a Geb test:
import geb.Browser Browser.drive { go "http://myapp.com/login" assert $("h1").text() == "Please Login" $("form.login").with { username = "admin" password = "password" login().click() } assert $("h1").text() == "Admin Section" }
You can create page objects to easily check that you are on the right page and reuse functionality of the page. A page object is a class, which extends the class "Page" or another page object.
To use Geb in Spock you need to extend your Spock-specification with GebReportingSpec.
You can use page objects like in Geb. Here is an example:
class MyPage extends Page { static at = { //Here you can check some content, which has to be available e.g. $('div', id: 'myId').size() > 0 } static content = { //Here you can save some page elements in variables, which you can use in your specification after the check "at MyPage" variableName { $('div', id:'myId') } } def methodName(variable1, variable2) { //With def you define a method, which you can use on the page. //When you use def in the method, you can define a local variable. def int myInt = variable1 variableName.$('input', id: 'thisId').value variable2+myInt } } class MyPage2 extends MyPage { static at = { //here you define additional elements to the elements of the static at of "MyPage" $('div', id: 'myId2').size() > 0 } //The rest is same like above. }
This is an example for a specification, which uses the pages from above:
import MyPage import MyPage2 class MySpec extends GebReportingSpec { def "This is an example specification" () { given: //here you can define preconditions when: "This is the example condition" at MyPage //check that you are on the defined page "MyPage" methodName 1, 'testVariable' //use the methode from the page object "MyPage" then: "This is the conclusion" //define some conclusion at MyPage2 when: "do something more" methodName variableName1, variableName2 //Use the method from the parent page "MyPage" on "MyPage2". As varaibles you can use the defined variables from the where clause. then: "something happens" //check that something happens where: //Here you can give some test values. When you run the specification it is executed with all lines which you give here. variableName1 | variableName2 1 | "variable1.2" 2 | "variable2.2" } }
Use modules to manage generic content at pages:
import geb.Module import geb.Page class MyModule extends Module{ //Parameter to specify the instance of MyModule def param1; static content = { //structure of all MyModules field {$("button",text:param1)} } } class MyPage extends Page{ static content = { //structure of MyPage based on modules modules {term -> module(new MyModule(param1:term)} } } class MySpec extends GebReportingSpec { def "This is an example specification" () { when: "I click at 'Next' button" at MyPage modules("Next").field.click() } }
Specifications, pages and modules for an assembly are stored within the assembly itself. The ish-assembly
plugin provides a SourceSet "remoteTest" for implementing remote tests. Geb Specifications has to be stored within the "geb" package of the RemoteTest SourceSet as shown in the code block below. There are no further restrictions for the package structure. The code block below shows a feasible package structure, which can be used.
The GebConfig.groovy has to be located within the default package of the remoteTest SourceSet. It is recommendet to place it into the resources folder.
src |-remoteTest |-groovy |-geb |-com.intershop.inspired |-b2c |-pages // place for pages and modules |-specs // place for all specs |-resources |-GebConfig.groovy
Geb attempts to load a ConfigSlurper script named GebConfig.groovy from the default package. The script allows you to control various aspects of Geb, i.e., timeouts or dealing with error (unexpected) pages. Please read the Configuration section of the Geb Manual in order to learn more about its capabilities. The most important part of the GebConfig Script is the the definition of the WebDriver environments. An example for a GebConfig.groovy script is displayed below.
When loading the GebConfig script, the ish-assembly
plugin provides system properties with information about the execution context of the geb Tests-, i.e., the path to the WebDriver executable. The properties listed below are always provided. Additional properties can be configured via the build.gradle of the assembly as described in the following sections.
Property | Description |
---|---|
geb.build.reportsDir | Root dir, i.e., for page dumps and othrgeb reports |
geb.build.baseUrl | URL of the started application server to be tested |
geb.env | Name of the requested webdriver environment |
webDriverDir | Root directory of the installed web driver to be used |
webDriverExec | Relative path to the web driver executable |
import org.openqa.selenium.Dimension import org.openqa.selenium.chrome.ChromeDriver import org.openqa.selenium.phantomjs.* import org.openqa.selenium.remote.* def gebEnv = System.getProperty("geb.env"); def webDriverDir = System.getProperty("webDriverDir") def webDriverExec = new File(webDriverDir, System.getProperty("webDriverExec")).absolutePath waiting { timeout = 10 } environments { phantomJsPC { driver = { def driver = createPhantomJsDriverInstance(webDriverExec) driver.manage().window().setSize(new Dimension(1920, 1200)) page.settings.resourceTimeout = 10000; driver } } chromeTablet { driver = { def driver = createChromeDriverInstance(webDriverExec) driver.manage().window().setSize(new Dimension(1024, 768)) driver } } chromePC { driver = { def driver = createChromeDriverInstance(webDriverExec) driver.manage().window().setSize(new Dimension(1920, 1200)) driver } } } private def createChromeDriverInstance(String webDriverExec) { System.setProperty("webdriver.chrome.driver", webDriverExec) driverInstance = new ChromeDriver() driverInstance } private def createPhantomJsDriverInstance(String webDriverExec) { System.setProperty("phantomjs.binary.path", webDriverExec) ArrayList cliArgsCap = new ArrayList(); cliArgsCap.add("--web-security=false"); cliArgsCap.add("--ssl-protocol=any"); cliArgsCap.add("--ignore-ssl-errors=true"); DesiredCapabilities desiredCapabilities = new DesiredCapabilities() desiredCapabilities.setCapability(PhantomJSDriverService.PHANTOMJS_CLI_ARGS, cliArgsCap); new PhantomJSDriver(desiredCapabilities) }
Depending on the set of Selenium web drivers, you want use for your asembly, you need to add related dependencies to toe remoteTest configuration.
dependencies { remoteTestCompile "org.seleniumhq.selenium:selenium-support:2.47.+" remoteTestCompile "org.seleniumhq.selenium:selenium-chrome-driver:2.47.+" remoteTestCompile ("com.codeborne:phantomjsdriver:1.2.+") { transitive = false } }
For executing geb tests from the assembly gradle project, you need to add gebConfiguration section as shown below into your build.gradle. The section is responsible for preparing web drivers and to provide the environment for executing the Geb tests.
There are two sub sections: systemProperties and webDrivers.
You can use the optional systemProperties section for declaring additional systemProperties, which will taken over to your GebConfig.groovy script and the Geb Specifications.
The webDrivers section defines all web drivers, to be supported from your Geb tests:
Structure | Description | ||
---|---|---|---|
<webDriverName> | Identifier for a Selenium web driver to be supported | ||
environments | List of Geb environments, related tot he web driver. Defined environments determines the value of the geb.env parameter, taken over to the GebConfig.groovy script. So the has to match with the environments, defined within the GebConfig.groovy file. | ||
download/<os> | Optional declarations, how the web drivers can be downloaded. If a download section for a web driver and the detected operating system exists, the driver will be downloaded into the gradle cach directory and installed into "project.buildDir/webdriver/<webDriverName>. If no related downloades section exists, you need to ensure, that the related web driver is installed before the Geb tests are executed. | ||
url | Download URL for the web driver | ||
archiveType | 'zip' and 'tar' are supported | ||
webDriverDir | (optional) Path to the extracted web driver. Default is $buildDir/webdriver/<webDriverName>. But depending on the downloades archive structure, it also may be be a subfolder of them. | ||
webDriverExec | Relative path to the driver binary |
Depending on your gebConfiguration the following tasks for executing gebTests are are available:
Example for a gibConfiguration section:
gebConfiguration { systemProperties { locale='en_US' } webDrivers { phantomJsDriver { environments { phantomJsPC {check=true} } download { linux { url = 'https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.8-linux-x86_64.tar.bz2' archiveType = 'tar' webDriverDir = 'phantomjs-1.9.7-linux-x86_64' webDriverExec = 'bin/phantomjs' } windows { url = 'https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.7-windows.zip' archiveType = 'zip' webDriverDir = 'phantomjs-1.9.7-windows' webDriverExec = 'phantomjs.exe' } } } chromeDriver { environments { chromePC chromeTablet } download { linux { url = 'http://chromedriver.storage.googleapis.com/2.20/chromedriver_linux64.zip' archiveType = 'zip' webDriverExec = 'chromedriver' } windows { url = 'http://chromedriver.storage.googleapis.com/2.20/chromedriver_win32.zip' archiveType = 'zip' webDriverExec = 'chromedriver.exe' } } } } }
The ish-assembly
plugin provides a Task remoteTest" for executing tests against a started application environment. A map "remoteTest.env" is defined, which is responsible for storing information, which are required for accessing the environment to be tested. By default, remoteTest.env is preloaded with all properties of your environment.properties. For accessing another environment, i.e., a demoSever enviornment. You can create another environment properties file (i.e., demoServer.properties) and load it with using the buld property "remoteTest.env":
gradlew -PremoteTest.env=demoServer.properties
There are default values for the following environment properties. Hence an value for at lease these properties are always ensured:
Property | Default value |
---|---|
hostName | localhost |
webserverPort | 80 |
webserverHttpsPort | 443 |
By default, geb Tests are not automatically triggered, when calling the remoteTest tasks. The assignment can be done within the assembly configuration with using remoteTest.dependsOn()
.
Depending on your gebConfiguration the following tasks for executing gebTests are are available:
Some examples for executing tests from the command line:
gradlew gebChromePCEnvironment
gradlew gebChromePCEnvironment --tests *Order*
gradlew gebChromePCEnvironment -PremoteTest.env=demoServer.properties
After the execution of the tests, the following reports/results are available:
See also: http://www.gebish.org/manual/current/
E.g., to find a div with class "test" you can use:
$('div', class: 'test')
E.g., Find all link, which href starts with "test"
$('a[href^="test"]')
E.g., Find all link, which href ends with "test"
$('a[href$="test"]')
You can find specific elements inside a given element by add the next find function with "."
E.g. find all elements "td" in the element "table" with id "tableId" :
$('table', id: "tableId").$('td')
You can execute a function for each found element, when you mark the web object with "*"
E.g., check that table cells from above contains a specific string "Test String".
$('table', id: "tableId").$('td')*.text().contains("Test String")
E.g., make all input element with class "hidden" visible.
js.exec 'jQuery("input[class=hide]").attr("style", "display: block !important")'
E.g., wait for element "div" with class "test" is available:
waitFor { $('div', class: "test").size() > 0 }
E.g., select a dropdown field by value or name.
<form> <select name="artist"> <option value="1">Ima Robot </option> <option value="2">Edward Sharpe and the Magnetic Zeros</option> <option value="3">Alexander</option> </select> </form> $("form").artist = "1" // selects the first option $("form").artist = 2 // selects the second option $("form").artist = "Alexander" // selects the third option $("[name='artist']").find("option").find{ it.text().trim() == "Ima Robot" } // selects the first option even if there are multiple spaces attached
Some Tests need a fully shown browser. Define your Browser, (e.g., Chrome) like this in GebConfig.groovy:
chrome { driver = { ... def d = new ChromeDriver() ... d.manage().window().maximize() d } }
The phantomJS-Browser cannot use the maximize()
function. To avoid conflicts, use code like this for specific size:
phantomJs { driver = { ... def d= new PhantomJSDriver() ... d.manage().window().setSize(new Dimension(1028, 768)) d } }
Although the specific waiting is not in the sense of Spock tests, there may be moments that requires a certain time to wait.
def sleepForNSeconds(int n){ def originalMilliseconds = System.currentTimeMillis() waitFor(n + 1){ (System.currentTimeMillis() - originalMilliseconds) > (n * 1000) } }
To simulate a hover effect and click at objects, which spawn after hovering use something like this.
def hover(hoverObject){ interact{ moveToElement( hoverObject) hoverObject.jquery.show() } }