Home

Webdriver Functional Testing Plugin

Integrates Webdriver with grails to allow functional testing in both HtmlUnit and real browsers.

Overview

The Webdriver plugin allows you to perform functional testing on your app. This means that the app is really running and the tests interact with it through the UI of a real (or simulated) web browser, just as a real user would. There are a number of other grails plugins that give this functionality, notably webtest, functional-test, geb and Selenium-RC.

So what does Webdriver do differently? To my mind there are three things I really like:

  • Support for the Page Object pattern which lets you cleanly abstract away HTML details
  • The ability to run individual (or multiple) tests directly through my IDE like any other JUnit test
  • The ability to run in a simulated browser (HtmlUnit) for speed or actually drive real web browsers like Firefox, IE and Chrome.

Page Objects

Using Page Objects means that you create an API for your application. For each page that you want to interact with in a test, you create a Page class, extending from WebDriverPage. Page classes have properties that allow access to form fields and text on the page and methods that you call to jump to other pages. The tests then just interact with these page objects, making assertions and navigating around your application. If you change your HTML around (change the ID of an element, or add an extra div for example) you only have to modify a specific part of the page object and not every test that interacted with that element.

Here's an example from an app that requires authentication.

http://localhost:8080/app/login

<html> 
    <head> 
        <title>Login</title> 
        <link rel="stylesheet" type="text/css" href="/webdriver/css/main.css" /> 
    </head> 
    <body> 
        <div class="body"> 
            <h1>Login</h1> 
            <form action="/webdriver/login/login" method="post" > 
                Username: <input type="text" name="username" id="username" value="" /><br/> 
                Password: <input type="password" name="password" id="password" value="" /><br/> 
                <input type='submit' value='Login' id="loginButton" /> 
            </form> 
        </div>     
    </body> 
</html>

test/functional/pages/LoginPage.groovy

class LoginPage extends WebDriverPage {
    static expectedTitle = "Login"
    static expectedURL = ~"/login/.*"

    String username
    String password
    String message

    ButtonElement<HomePage> loginButton

    static elements = {
        message(By.xpath("//div[@class='messages']"))
    }
}

test/functional/tests/LoginTests.groovy

class LoginTests {
    @Rule
    public WebDriverHelper webdriver = new WebDriverHelper()

    private LoginPage loginPage

    @Before
    public void openLoginPage() {
        loginPage = webdriver.open('/', LoginPage.class)
    }

    @Test
    public void testEmptyFieldsLogin() {
        loginPage.loginButton.clickStay()
        assertEquals("Incorrect username or password", loginPage.error)
    }

    @Test
    public void testUnSuccessfulLogin() {
        loginPage.username = 'test'
        loginPage.password = 'wrong'
        loginPage.loginButton.clickStay()
        assertEquals("Incorrect username or password", loginPage.error)
    }

    @Test
    public void testSuccessfulLogin() {
        loginPage.username = 'test'
        loginPage.password = 'password'
        def homePage = loginPage.loginButton.click()
        assertEquals("Login Successful", homePage.message)
    }
}

Some points to note about the above example:

  1. The static elements field on the page defines the mapping between the properties of the class and the HTML UI elements. If a field does not have a mapping, it is assumed that the field name directly maps to the ID or name of the element, like username, password, rememberMe and loginButton above.
  2. See https://bitbucket.org/refactor/grails-webdriver/src/9fad4997c4cc/test/unit/pages/test/TestOnePage.groovy for examples of the supported features of a Page
  3. The loginButton field will return an automatically instantiated Page object when the .click() method is called. In the cases (usually errors) where you want to stay on the current page, the .clickStay() method is called.
  4. If a page defines expectedTitle and/or expectedURL fields, they will be validated on page transitions and before calls to verify you are on the page you are on. Both strings and regex patterns are supported
  5. Elements do not have to always exist on the page. If you don't try to access them, the test will not fail. See the message field in this case.
  6. Pages need to extend WebDriverPage, but you'd normally create a base page that maps to your main layout and extend other pages from it.

For more information on the Page Object pattern, see http://code.google.com/p/selenium/wiki/PageObjects

Running Tests

The Grails Way

You can run your webdriver tests just like all your other Grails tests, with grails test-app or grails test-app -functional This will start a server, run your tests, and give you a report in test/reports. If any failures occur, the current HTML is saved and linked to the failure. If you are running in Firefox (see below), it will also save a png snapshot of the page.

If you are running on a CI server like Jenkins, you can set the system property -Dwebdriver.output.url=http://ci.server/artifacts/app/target/test-reports/webdriver to change the links in the error output so they don't refer to the local filesystem of your CI server.

Through Your IDE

You can also run your tests directly through your IDE, bypassing the slow grails startup and providing quick turnaround. Just start your server with grails run-app or grails test run-app, then run your test like you would any other (Ctrl-Shift-F10 in Intellij - choose JUnit if it asks you to choose between Grails and JUnit). If you are running your code on a non standard host or port, you'll need to include the system property -Dgrails.testing.functional.baseUrl=http://localhost:8180/MyApp

Multiple Browsers

If you'd like to run your tests in Firefox, IE or Chrome, just add the system property (substitute ie or chrome as appropriate) -Dwebdriver.browser=firefox to your test-app call like grails -Dwebdriver.browser=firefox test-app or to your IDE test run command.

Demo

Please see the book project in test/functional/tests/BookTests.groovy of the plugin source and the tests in test/unit for examples of how to use the plugin.

Related plugins

Version History

  • 0.1.0 - Initial Version
  • 0.1.1 - Removed jars already supplied by Grails
  • 0.2.0 - Refactored to move Page magic into WebDriverPage base class rather than metaprogramming
  • 0.2.1 - Misc Fixes
  • 0.2.2 - Updated WebDriver to selenium-java-2.0a2.zip. Small tweaks. Fixed to work with Grails 1.2.0
  • 0.2.3 - Updated WebDriver to selenium-java-2.0a4.zip. Moved location of screenshots. Made work with no context path
  • 0.3.0 - Updated WebDriver to selenium-java-2.0a7. Moved to mvn dependencies. Moved to junit 4
  • 0.3.1 - Updated WebDriver to selenium-java.2.0b4
  • 0.3.2 - Updated WebDriver to selenium-java.2.0rc2. Added optional keyword. Updated to Grails 1.3.7, small fixes for Linux.
  • 0.3.3 - Updated WebDriver to selenium 2.8
  • 0.4.0 - Upgraded to Selenium 2.18 and Grails 2.0
  • 0.4.2 - Upgraded to Selenium 2.27 and Grails 2.2

Bug Tracker

Defects and enhancements can be posted in the Grails-Plugins JIRA

Source Code

The plugin source code can by found here at bitbucket. The project is released under Apache Software License 2.0.

References

Updated

Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.