Permalink
Fetching contributors…
Cannot retrieve contributors at this time
299 lines (231 sloc) 8.16 KB

Selenium Utils

Takes the misery out of selenium !

Build Status

Findr

Findr is a simple yet very powerful utility class that helps to write tests in a "wait-style", without accessing WebDriverWait directly.

The API is slick, easy to use and helps to be DRY and concise. It's based on chained methods in order to expose a clear API, and uses function composition in order to create chains of conditions. This chain is then evaluated atomically inside a WebDriverWait, under the hood.

Evaluation fails if the chain doesn't completely completes within a given timeout, and an exception is thrown.

Simple example over Google search :

// get google
driver.get("http://www.google.com");

final Findr findr = new Findr(driver);

// perform the search
findr			
    .$("#gbqfq")            
    .sendKeys("pojos on the web", Keys.ENTER); 

// check the results
findr			
    .$("#ires")
    .$$("h3.r")
    .at(0)		
    .$("a")
    .where(textEquals("POJOs on the Web!: Woko"))
    .eval();	

Findr instances are immutable and can be safely shared and reused :

Findr container = new Findr(driver).$("#container");
container.$("#username").sendKeys("john.doe");
container.$("#the-button").click();

Built-in predicates

The Findrs class exposes a set of static factory methods that create Predicate<WebElement>s for the recurrent stuff, for example :

  • attrEquals(String attrName, String expectedValue)
  • hasClass(String className)
  • textEquals(final String expected)

Those can be used directly in your findrs :

new Findr(driver)
	.$("div.my-class"))
	.where(attrEquals("my-attr", "my-value"))
	.where(textEquals("This is some content"))
	.eval();

Batch / Retry

The library also provides support for composing several "steps" into a single, retry-all operation. This allows to group a set of interactions and make sure that all of them are actually performed.

This can be used as a relacement for "nested" findrs, which are not easy to manage :

// a "nested" findr version that clicks/asserts 
// as an atomic op
void openDropDown() {
    f.$("#my-button").eval( e -> {
        e.click();
        // make sure that dropdown is shown
        // if not then the top-level eval()
        // will be called again
        // we use a "nested" Findr
        f.$("#my-dropdown")                
            .where(isDisplayed())
            .setTimeout(5) // must be < to the parent's timeout...
            .eval();
    });
}

Can be replaced with Retry :

import static com.pojosontheweb.selenium.Retry.retry;

void openDropDown() {
    retry()
        .add(() -> f.$("#my-button").click())
        .add(f.$("#my-dropdown").where(isDisplayed())
        .eval();
}

The add() method accepts several types of arguments (Findr, Runnable, mapping functions etc.), allowing to create a chain of steps. This chain is then evaluated calling the eval() method on it.

Error reporting

Findr tries to report failures in condition chains by including a String-ified version of the path. Of course, the stack trace of the Timeout exception will tell where the evaluation failed.

There are also variants to eval() that accept a failureMessage argument.

Verbose logging

Findr executes the various functions you compose as a "back box", and it's sometimes hard to understand where it went wrong in the conditions chain. In order to get insights about what's going on, you can set the sys prop webtests.findr.verbose, so that it outputs the logs (to stdout) when asserting the condition chain.

WebDriver init

Use DriverBuilder in order to create instances of WebDriver. The API can be used statically :

// create a simple Chrome Driver
WebDriver driver = DriverBuildr
	.chrome()
	.setDriverPath(new File("/path/to/chromedriver"))
	.build();

Or by defining system properties :

WebDriver = DriverBuilder.fromSysProps().build();

The latter approach allows for more flexible builds.

System Properties

Here is a list of all supported System Properties :

property allowed values default comment
General props
webtests.browser firefox,chrome firefox
webtests.locales en, fr, ... Comma-separated list of locale(s) for the tests (browser language)
webtests.hub.url valid remote driver url Connect to a Selenium Grid (RemoteWebDriver). Video recording is not available when using remote drivers.
webtests.findr.timeout Any (reasonable) positive integer 10 The Findr timeout in seconds
webtests.findr.sleep Any (reasonable) positive long 500 The Findr sleep interval in milliseconds. Allows to control polling frequency.
webtests.findr.verbose true,fase false log some infos about findr evaluation chains (helps debugging)
webtests.video.enabled true,false false enables video recording of failed tests
webtests.video.dir path to folder tmp dir
webtests.video.failures.only true,false true keep videos for failures only, or for all tests
Chrome only
webdriver.chrome.driver path to driver exe mandatory for Chrome

TestCase plumbing

Base classes are included that manage the driver init/close and video stuff. If you use JUnit for example, you simply have to extend a base class :

public class MyTest extends ManagedDriverJunit4TestBase {

    @Test
    public void testMe()  {
    	WebDriver d = getWebDriver();
    	...
    }

}

Doing so will allow you to run your test directly, and parameterize it using sys props.

There's also a TestUtil class that implements the lifecycle of a typical test. You can delegate to that one if you already extend a base class in your test.

Video recording

We have a very basic ScreenRecordr class that performs video capture on the host that runs the webdriver. It's activated by the TestCase plumbing, via sys props.

It's built on Monte Media Library, and is pure Java. It's been tested on a different platforms (mac, windows, linux), and even seems to work in headless/xvfb environments.

Using with Maven

Add the dependency to your pom :

<dependency>
    <groupId>com.pojosontheweb</groupId>
    <artifactId>selenium-utils-core</artifactId>
    <version>LATEST-SNAPSHOT</version>
    <scope>test</scope>
</dependency>

Configure surefire :

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
    	<systemPropertyVariables>
            <webtests.browser>${webtests.browser}</webtests.browser>
            <webtests.video.enabled>${webtests.video.enabled}</webtests.video.enabled>
            <webtests.video.dir>${project.build.directory}/webtests-videos</webtests.video.dir>
            <webdriver.chrome.driver>${webdriver.chrome.driver}</webdriver.chrome.driver>
        </systemPropertyVariables>
    </configuration>
</plugin>

Invoke maven :

$> mvn test

With sys props :

$> mvn test -Dwebtests.browser=chrome -Dwebdriver.chrome.driver=/opt/chromedriver -Dwebtests.video.enabled=true

Page Objects

Included is a simple yet useful AbstractPageObject class that you can use to create your own page helper libraries.

Groovy

For Groovy users, a set of extensions and additional stuff is available as a separate module. Have a look at Taste for more infos.