Test Automation with Selenium WebDriver, Java, and JUnit

Kito D. Mann (@kito99) Virtua, Inc.

Kito D. Mann (@kito99)

  • Principal Consultant at Virtua (http://virtua.tech)

  • Training, consulting, architecture, mentoring

    • JSF, Java EE, Polymer/Web Components, Angular

  • Official US PrimeFaces and PrimeNG partner

  • Author, JavaServer Faces in Action

  • Founder, JSF Central (http://www.jsfcentral.com)

Kito D. Mann (@kito99)

  • Co-host, Enterprise Java Newscast (http://enterprisejavanews.com)

  • Java Champion

  • Google Developer Expert in Web Technologies

  • Internationally recognized speaker

    • JavaOne, JavaZone, Devoxx, Devnexus, NFJS, etc.

  • JCP Member

    • JSF, MVC, JSF Portlet Bridge, Portlets

History of Selenium

  • Originally developed by Jason Huggins at ThoughtWorks in 2004 and open sourced later that year

  • Name came from a joke Huggins made in e-mail about Mercury Interactive: "you can cure mercury poisoning by taking selenium supplements"

History of Selenium

  • Three parts:

    • Selenium Developer API

    • Selenium Remote Control (RC)

    • Selenium Grid

History of Selenium

  • Selenium IDE developed by Shinya Kasatani in Japan around 2006

  • In 2011, Selenium 2 was released

    • Old Selenium APIs and Selenium RC replaced by the WebDriver API and Server

  • WebDriver became a W3C Working Draft standard in 2012

What is Selenium?

  • Selenium RC (deprecated)

  • Selenium WebDriver

    • Developer (Client) API

    • Server (WebDriver)

  • Selenium IDE

Selenium IDE

  • Firefox and Chrome extension for recording and playback

  • Saves tests in JSON format

  • Supports many different plugins

  • Great for quick-and dirty tests and helping you build tests by hand

  • Previous versions supported exporting to different languages

Selenium IDE

DEMO: Selenium IDE

How WebDriver Works

how webdriver works

How Remote WebDriver Works

how remote webdriver works

WebDriver API

webdriver api

WebDriver API

Desktop Drivers

  • Internet Explorer

  • Edge

  • Chrome

  • Safari

  • Firefox

  • Opera

Mobile Drivers

  • iOS

  • Android

  • Windows Phone

  • Blackberry

Desktop Drivers

  • QT

  • Windows Desktop

Headless Drivers

  • HttpUnit

  • PhantomJS

  • jBrowserDriver

  • Chrome (headless mode)

  • Firefox (headless mode)

WebDriver API Features

  • Supports all of the same features as Selenium IDE

    • Assertion (through the test runner)

    • Verification (through the test runer)

    • Locating Elements

    • Interacting with Elements

    • Waiting

    • JavaScript integration

WebDriver API Features

  • Test suite functionality provided by the test runnner

DEMO: Using the WebDriver API

jQuery (Sizzle) Integration

  • Execute JavaScript

    • May need to inject jQuery into the page

  • Third-party libraries

    • seleniumQuery

    • geb

seleniumQuery

import static io.github.seleniumquery.SeleniumQuery.$; // this will allow the short syntax

public class SeleniumQueryExample {
  public static void main(String[] args) {
    // The WebDriver will be instantiated only when first used
    $.driver()
        .useChrome() // sets Chrome as the driver (this is optional, if omitted, will default to HtmlUnit)
        .headless() // configures chrome to be headless
        .autoDriverDownload() // automatically downloads and configures chromedriver.exe
        .autoQuitDriver(); // automatically quits the driver when the JVM shuts down

    // or, instead, use any previously existing driver
    // $.driver().use(myExistingInstanceOfWebDriver);

    // starts the driver (if not started already) and opens the URL
    $.url("http://www.google.com/?hl=en");

    // interact with the page
    $(":text[name='q']").val("seleniumQuery"); // the keys are actually typed!

    // Besides the short syntax and the jQuery behavior you already know,
    // other very useful function in seleniumQuery is .waitUntil(),
    // handy for dealing with user-waiting actions (specially in Ajax enabled pages):

    // the command below waits until the button is visible and then performs a real user click (not just the JS event)
    $(":button[value='Google Search']").waitUntil().isVisible().then().click();

    // this waits for the #resultStats to be visible using a selector and, when it is visible, returns its text content
    String resultsText = $("#resultStats").waitUntil().is(":visible").then().text();

    // .assertThat() functions: fluently asserts that the text contains the string "seconds", ignoring case
    $("#resultStats").assertThat().text().containsIgnoreCase("seconds");

    System.out.println(resultsText);
    // should print something like: About 4,100 results (0.42 seconds)

    // $.quit(); // would quit the driver, but it is not needed as .autoQuitDriver() was used
  }
}

LiFT Style API

public class GoogleTest extends HamcrestWebDriverTestCase {

  @Override
  protected WebDriver createDriver() {
    return new HtmlUnitDriver();
  }

  public void testHasAnImageSearchPage() throws Exception {

    goTo("http://www.google.com");

    assertPresenceOf(link("Images"));
    assertPresenceOf(atLeast(4), links().with(text(not(equalTo("Images")))));

    clickOn(link("Images"));

    assertPresenceOf(title().with(text(equalTo("Google Image Search"))));
  }

}

Testing Techniques

What Types of Tests?

  • Checking static content

  • Testing links

  • Functional tests

  • Unit tests

When?

  • Difficult, time-consuming tests

  • Smoke testing

  • Regression testing

Locating Elements

  • Searching for ids and links are fast

  • XPath is the most flexible, but can be slower

    • Useful especially when ids are dynamic

  • CSS locators may be easier for some

  • DOM locators more of a legacy

  • Come up with specific guidelines

Keeping Track of Elements

  • Element selectors should be specific, but not brittle

  • Organize code so that the same selectors can be reused

UI Map / Object Repository

  • Place UI objects in a centralized location

  • Maps human readable names (i.e. "loginForm") to specific locator expressions (i.e. "//body/form[2]")

  • Can either be in code or a properties file

UI Map / Object Repository

admin.username = loginForm:tbUsername
admin.loginbutton = loginForm:btnLogin
admin.events.createnewevent = adminHomeForm:_activitynew
admin.events.cancel = addEditEventForm:_IDcancel
admin.events.viewoldevents = adminHomeForm:_activityold

Organizing Code

  • Write utility methods to help with complex operations

    • "Safe" operations

    • Multi-step operations (drag-and-drop sequences, etc.)

Page Object Pattern

  • Encapsulates the UI objects and functionality for a page

  • Tests call the page object to perform operations

  • Clean seperation between tests and the structure of the page

  • Single location for a specific page’s functionality

Page Object Pattern

package org.openqa.selenium.example;

import org.openqa.selenium.By;
import org.openqa.selenium.support.CacheLookup;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import org.openqa.selenium.WebElement;

public class GoogleSearchPage {
    // The element is now looked up using the name attribute,
    // and we never look it up once it has been used the first time
    @FindBy(name = "q")
    @CacheLookup
    private WebElement searchBox;

    public void searchFor(String text) {
        // We continue using the element just as before
        searchBox.sendKeys(text);
        searchBox.submit();
    }
}

Page Object Pattern

package org.openqa.selenium.example;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.openqa.selenium.support.PageFactory;

public class UsingGoogleSearchPage {
    public static void main(String[] args) {
        // Create a new instance of a driver
        WebDriver driver = new HtmlUnitDriver();

        // Navigate to the right place
        driver.get("http://www.google.com/");

        // Create a new instance of the search page class
        // and initialise any WebElement fields in it.
        GoogleSearchPage page = PageFactory.initElements(driver, GoogleSearchPage.class);

        // And now do the search.
        page.searchFor("Cheese");
    }
}

Executing Tests Remotely

  • Any tests can be executed on a remote WebDriverServer instance

  • Executes tests in browsers on a remote machine

  • Stand-alone JAR or Windows executable

Testing in the Cloud

  • Unit and code-based integration tests can easily be performed in the cloud as part of the build

  • UI integration tests (with tools like Selenium) can also run in the cloud

    • Must support multiple browser/OS combinations (including mobile)

    • Can also perform load testing

Selenium Testing Cloud Providers

  • SauceLabs

  • Browserstack

  • TestingBot

  • Spoonium

Questions?