Make your WebDriver based Selenium tests more readable, reusability and maintainable by using WebDriver Extensions!
WebDriver Extensions is designed to simplify Java based Selenium/WebDriver tests. It's built on top of Selenium/WebDriver to make your tests more readable, reusabable and maintainable by combining the Page Object Pattern and Bot Pattern.
Available through the Maven Central Repository! Latest release is version 3.11.1 which includes selenium-java 3.141.59 as a transitive dependency.
If you find a bug or have a feature request please create a new GitHub issue or even better clone this repository, commit your changes and make a Pull Request.
If you have question you can ask them in a GitHub issue.
Here is an example of how a cross browser test looks like with and without the WebDriver Extensions Framework. The test will run on Firefox, Chrome and Internet Explorer. It will google for "Hello World" and assert that the search result contains the searched text "Hello World".
@RunWith(WebDriverRunner.class)
@Firefox
@Chrome
@InternetExplorer
public class WebDriverExtensionsExampleTest {
// Model
@FindBy(name = "q")
WebElement queryInput;
@FindBy(name = "btnG")
WebElement searchButton;
@FindBy(id = "search")
WebElement searchResult;
@Test
public void searchGoogleForHelloWorldTest() {
open("http://www.google.com");
assertCurrentUrlContains("google");
type("Hello World", queryInput);
click(searchButton);
waitFor(3, SECONDS);
assertTextContains("Hello World", searchResult);
}
}
Imports are hidden for the sake of simplicity, for imports and instructions on how to run this example see this gist
@RunWith(Parameterized.class)
public class WebDriverExampleTest {
WebDriver driver;
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{"Firefox"}, {"Chrome"}, {"InternetExplorer"}
});
}
public WebDriverTest(String browserName) {
if (browserName.equals("Firefox")) {
driver = new FirefoxDriver();
} else if (browserName.equals("Chrome")) {
driver = new ChromeDriver();
} else if (browserName.equals("InternetExplorer")) {
driver = new InternetExplorerDriver();
}
PageFactory.initElements(driver, this);
}
@After
public void tearDown() {
driver.quit();
}
// Model
@FindBy(name = "q")
WebElement queryInput;
@FindBy(name = "btnG")
WebElement searchButton;
@FindBy(id = "search")
WebElement searchResult;
@Test
public void searchGoogleForHelloWorldTest() throws InterruptedException {
driver.get("http://www.google.com");
assert driver.getCurrentUrl().contains("google");
queryInput.sendKeys("Hello World");
searchButton.click();
SECONDS.sleep(3);
assert searchResult.getText().contains("Hello World");
}
}
Imports are hidden for the sake of simplicity, for imports and instructions on how to run this example see this gist
As you can see WebDriver Extensions Framework made the test almost readable as instructions you would give to someone who needs to manually perform this test. This is one of the main points of this framework. It also removed a lot of verbose boilerplate configuration code.
For the sake of simplicity this example does not demonstrate the Page Object Pattern. Please keep on reading the Getting Started section to read more about how to create and use Page Objects.
If wanted one could further increase readability by using the Groovy language instead of Java. Then the Hello World example would look like this
@Grab(group='com.github.webdriverextensions', module='webdriverextensions', version='3.11.1')
@RunWith(WebDriverRunner)
@Firefox
@Chrome
@InternetExplorer
class WebDriverExtensionsGroovyExampleTest {
// Model
@FindBy(name = "q")
WebElement queryInput;
@FindBy(name = "btnG")
WebElement searchButton;
@FindBy(id = "search")
WebElement searchResult;
@Test
void searchGoogleForHelloWorldTest() {
open "http://www.google.com"
assertCurrentUrlContains "google"
type "Hello World", queryInput
click searchButton
waitFor 3, SECONDS
assertTextContains "Hello World", searchResult
}
}
Imports are hidden for the sake of simplicity, for imports and instructions on how to run this example see this gist
Note that Groovy examples will not be covered by this document.
The Selenium project is compiled with Java 8 since version 3.0.0. Therefore WebDriver Extensions also requires you to use Java 3 in version 3.0.0 and above. Maven is not a requirement but is preferred and referred to in this document.
Add
<dependency>
<groupId>com.github.webdriverextensions</groupId>
<artifactId>webdriverextensions</artifactId>
<version>3.11.1</version>
</dependency>
...as a dependency in your pom.xml file.
There is no need to download any drivers manually. Instead use the WebDriver Extensions Maven Plugin GitHub to download and manage your drivers by adding
<plugin>
<groupId>com.github.webdriverextensions</groupId>
<artifactId>webdriverextensions-maven-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<goals>
<goal>install-drivers</goal>
</goals>
</execution>
</executions>
<configuration>
<drivers>
<driver>
<name>edgedriver</name>
<version>6.17134</version>
</driver>
<driver>
<name>internetexplorerdriver</name>
<version>3.9.0</version>
</driver>
<driver>
<name>chromedriver</name>
<version>74.0.3729.6</version>
</driver>
<driver>
<name>geckodriver</name>
<version>0.24.0</version>
</driver>
<driver>
<name>phantomjs</name>
<version>2.1.1</version>
</driver>
</drivers>
</configuration>
</plugin>
...as a plugin in your pom.xml file. Then simply just update the version tag of the driver when a new driver is available and re-run your tests with the mvn test
command or your preferred IDE.
The plugin will download the most suitable driver for your OS. The bit of the driver will be 32bit with the exception of running the tests from a linux 64bit OS. If you would like to specify the OS and bit of the drivers to download you can provide them with a <platform>
and <bit>
-tag inside each <driver>
-tag. Platform can be set to windows
, mac
or linux
while the bit can be set to 32
or 64
.
The drivers will placed in a folder called drivers
in the project root. If you will use the provided WebDriverRunner there is no need for passing driver paths as System Properties since the framework will take care of the for you. If you won't be using it make sure to point the drivers out manually.
If you have configured a proxy in the settings.xml file the first encountered active proxy will be used. To specify a specific proxy to use you can provide the proxy id in the configuration.
If you run your tests from eclipse make sure you've allowed the webdriverextensions-maven-plugin to run the install-drivers goal. You can do this by adding the following to your pom.xml
<pluginManagement>
<plugins>
<!--Eclipse m2e settings needed to install drivers with the webdriverextensions-maven-plugin -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>com.github.webdriverextensions</groupId>
<artifactId>webdriverextensions-maven-plugin</artifactId>
<versionRange>[1.0,)</versionRange>
<goals>
<goal>install-drivers</goal>
</goals>
</pluginExecutionFilter>
<action>
<execute>
<runOnIncremental>true</runOnIncremental>
</execute>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
For more information on configuring the driver please visit the WebDriver Extensions Maven Plugin GitHub page. If the latest drivers are not available yet please create an issue here.
Run your tests in parallel by adding
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<parallel>all</parallel>
<threadCount>10</threadCount>
<perCoreThreadCount>false</perCoreThreadCount>
</configuration>
</plugin>
...to your pom.xml file.
This configuration will run maximum 10 tests in parallel. For more information about the configuration please see section Fork Options and Parallel Test Execution in the documentation of the Maven Surefire Plugin.
Try not to use non final static variables within your tests if you run your tests in parallel. If you really have to use static variables that are not defined as final make sure to wrap them in InheritableThreadLocal objects. In this way they will be static within the current thread and child threads (i.e. the current test).
Also before configuring to run your tests in parallel check that your website allows it. For example problems could occur when logging in with the same user at the same time (if your website supports a login functionality). There could also be other reasons not to run tests in parallel.
Run your tests locally by using the WebDriverRunner
import com.github.webdriverextensions.junitrunner.WebDriverRunner;
import com.github.webdriverextensions.junitrunner.annotations.*;
@RunWith(WebDriverRunner.class)
@Firefox
@Chrome
@InternetExplorer
@Edge
@PhantomJS
public class CrossBrowserTest {
// Add WebElements, WebPages and other supported web models to use in tests
@Test
public void test1() {
// Configure browsers to test by annotating the class
}
@Test
@Safari
public void test2() {
// ...or by annotating methods
}
@Test
@IgnoreInternetExplorer
public void test3() {
// ...and use the ignore annotations to ignore specific browsers
}
...
}
...or remotely by adding the @RemoteAddress annotaion
@RunWith(WebDriverRunner.class)
@RemoteAddress("http://your-remote-url")
@Firefox
@Chrome
@InternetExplorer
@Edge
@PhantomJS
public class CrossBrowserTest {
...
}
To run your test headless without starting a browser, use the @HtmlUnit annotation. If wanted you can also run your tests against the Safari browser with the @Safari annotation (just make sure the chromedriver is installed). Note that there is currently a WebDriver issue with running the SafariDriver on some OSX/Safari versions.
Browser version
and platform
settings can be passed as annotation parameters e.g. @Firefox(version = "35.0", platform = Platform.MAC)
.
The desired capabilities can either be provided in JSON format as a string e.g. @Chrome(desiredCapabilities = "{ chromeOptions: { args: [''--start-maximized'] }")
or by creating a new class that extends the WebDriver's DesiredCapabilities
class
public class StartMaximized extends DesiredCapabilities {
public StartMaximized() {
ChromeOptions options = new ChromeOptions();
options.addArguments("--start-maximized");
setCapability(ChromeOptions.CAPABILITY, options);
}
}
...and passing that to the annotation e.g. @Chrome(desiredCapabilitiesClass = StartMaximized.class)
.
If you want set a custom browser name this can be done by using the @Browser annotation e.g. Browser(browserName = "foo")
.
For larger and more complex test grids the @Browsers annotation can be used. For example to test the Firefox browser on Windows, Mac and Linux
@Browsers(firefox = {
@Firefox(platform = Platform.WINDOWS),
@Firefox(platform = Platform.MAC),
@Firefox(platform = Platform.LINUX)
})
If you would like to use a custom driver path annotate the test with the @DriverPaths annotation, e.g.
@DriverPaths(chrome="path/to/chromedriver", internetExplorer ="path/to/internetexplorerdriver")
If you want to run your test against 64bit Internet Explorer versions you can specify the path to the 64 bit driver with the @DriverPaths annotation like this
@DriverPaths(internetExplorer ="drivers/internetexplorerdriver-windows-64bit.exe")
another way to do it is to set the webdriverextensions.ie.driver.use64Bit
to true
, e.g. when running the tests with maven: mvn test -Dwebdriverextensions.ie.driver.use64Bit=true
.
To take screenshots on test failure annotate the test class with the @TakeScreenshotOnFailure. The screenshots will be saved into a directory named screenshots
located in the project root. The path to the screenshots directory can be configured either by annotating the test class with the @ScreenshotsPath annotation or by setting the webdriverextensions.screenshotspath
property. E.g.
@RunWith(WebDriverRunner.class)
@Firefox
@TakeScreenshotOnFailure
@ScreenshotsPath("path/to/screenshots")
public class SomeTest {
...
}
The implicitly wait for tests can be set by annotating test classes or methods with the @ImplicitlyWait annotation. E.g.
@RunWith(WebDriverRunner.class)
@Firefox
@ImplicitlyWait(1)
public class SomeTest {
@Test
public void somethingToTest() {
// Implicittly wait is set to one second
}
@Test
@ImplicitlyWait(value = 1, unit = MINUTES)
public void somethingElseToTest() {
// Implicittly wait is set to one minute
}
}
To set other driver specific setting use the JUnit @Before annotation. The driver can be retreived by using the driver() method in the Bot class. E.g.
@RunWith(WebDriverRunner.class)
@Firefox
public class SomeTest {
@Before
public void configure() {
driver().manage().timeouts().pageLoadTimeout(10, SECONDS);
}
...
}
Model your website pages, e.g. a login page
<html>
<head>
<title>Login Page</title>
</head>
<body>
<form>
<label>Username</label> <input name="username">
<label>Password</label> <input name="password">
<input type="checkbox" name="remember-me"> Remember me
<button id="login-button">Login</button>
</form>
</body>
</html>
...by extending the WebPage class
import com.github.webdriverextensions.WebPage;
public class LoginPage extends WebPage {
@FindBy(name = "username")
public WebElement usernameInput;
@FindBy(name = "password")
public WebElement passwordInput;
@FindBy(name = "remember-me")
public WebElement rememberMeCheckbox;
@FindBy(id = "login-buttom")
public WebElement loginButton;
@Override
public void open(Object... arguments) {
// Define how to open this page, e.g.
open("https://www.your-website-url.com/login");
assertIsOpen();
}
@Override
public void assertIsOpen(Object... arguments) {
// Define how to assert that this page is open, e.g.
assertTitleEquals("Login Page");
assertIsDisplayed(usernameInput);
assertIsDisplayed(passwordInput);
assertIsDisplayed(rememberMeCheckbox);
assertIsDisplayed(loginButton);
}
}
...and then add and use it in your tests
@RunWith(WebDriverRunner.class)
@Firefox
public class LoginPageTest {
// Add models to inject into test
LoginPage loginPage;
@Test
public void loginTest() {
open(loginPage); // Calls the open method defined in LoginPage
type("foo", loginPage.username);
type("bar", loginPage.password);
click(loginButtom);
assertIsNotOpen(loginPage); // Calls the assertIsNotOpen method in the abstract WebPage class which inverts the assertIsOpen method defined in LoginPage
}
...
}
Since the WebPage class only implements a part of the the Openable interface you have to implement the open(Object... arguments) and assertIsOpen(Object... arguments) methods yourself. As soon as this is done you can also call the isOpen(Object... arguments), isNotOpen(Object... arguments) and the assertIsNotOpen(Object... arguments) methods inherited from the WebPage class.
The open(Object... arguments) and assertIsOpen(Object... arguments) methods can take any number of arguments and therefore it is possible to pass entity ids or other required data needed to load the page. E.g. a page showing a specific order
public class OrderPage {
@FindBy(id = "order-number")
public WebElement orderNumber;
...
@Override
public void open(Object... arguments) {
int orderNumberToOpen = (int) arguments[0];
System.err.println("https://www.your-website-url.com/order?orderid=" + orderNumberToOpen);
assertIsOpen(orderNumberToOpen);
}
@Override
public void assertIsOpen(Object... arguments) {
int orderNumberToAssert = (int) arguments[0];
assertTextEquals(orderNumberToAssert, orderNumber);
...
}
}
...and then use it in your test
open(orderPage, 134523); // Calls the open method defined in OrderPage with the order number 134523 as an argument
There is also a WebSite class which can be used if you would want to create a Site Object i.e. a model of the complete website. It is actually no difference between the WebPage and the WebSite class except the name.
An alternative to using the WebPage class is using the WebRepository class. The only difference is that it does not implement the Openable interface and therefore there is no need to override and implement the open(Object... arguments)
and assertIsOpen(Object... arguments)
methods.
Note that any class extending the WebPage, WebSite or WebRepository class that are added as fields in the test will automatically be injected/instantiated if the WebDriverRunner is used. If you won't run your tests with the WebDriverRunner you can call the Selenium WebDriver PageFactory.initElements
method and pass the WebDriverExtensionFieldDecorator before running the test, e.g.
PageFactory.initElements(new WebDriverExtensionFieldDecorator(yourDriver), this);
Model repeating html content, e.g. table rows
<table id="playlist">
<tr>
<td class="track">Hey Joe</td>
<td class="artist">Jimi Hendrix</td>
<td class="time">3:30</td>
<td class="album">Are You Experienced</td>
</tr>
<tr>
<td class="track">Play with Fire</td>
<td class="artist">The Rolling Stones</td>
<td class="time">2:14</td>
<td class="album">The Last time</td>
</tr>
...
</table>
...by extending the WebComponent
import com.github.webdriverextensions.WebComponent;
public class PlaylistRow extends WebComponent {
@FindBy(className = "track")
public WebElement track;
@FindBy(className = "artist")
public WebElement artist;
@FindBy(className = "time")
public WebElement time;
@FindBy(className = "album")
public WebElement album;
}
...and then include it as you include a WebElement
@FindBy(css = "#playlist tr")
public List<PlaylistRow> playlist;
...and then start using it
assertTextEquals("Hey Joe", playlist.get(0).track); // Use WebElements in WebComponents
click(playlist.get(0)); // Use WebComponents as WebElements
Note that @FindBy
annotation locators used inside a WebComponent have the WebComponent's html content as the search context. To locate html tags outside the WebComponent you could reset the search context by adding the @ResetSearchContext annotation.
If you wish to delegate the method calls of a WebComponent to an underlying WebElement you can do so by annotating a WebElement inside the WebComponent with the @Delegate annotation.
If you won't run your tests with the WebDriverRunner you must call the Selenium WebDriver PageFactory.initElements
method and pass the WebDriverExtensionFieldDecorator before running the test, e.g.
PageFactory.initElements(new WebDriverExtensionFieldDecorator(yourDriver), this);
Simply import the static Bot where you want to use it
import static com.github.webdriverextensions.Bot.*;
...and start interacting with your web models
open("https://www.your-website-url.com"); // Open urls
type("testuser", usernameInput); // Type into WebElements referencing text input tags
type("ai78cGsT", passwordInput);
uncheck(rememberMeCheckbox); // Check and uncheck WebElements referencing checkbox input tags
click(loginButton); // Click at WebElements
open(settingsPage) // Open WebPages
selectOption("Swedish", languageSelectBox); // Select options in WebElements referencing select tags
...and write your asserts
assertIsOpen(homePage); // Assert WebPages are open
assertTextEquals("testuser", currentUser); // Assert text in WebElements equals
assertTitleStartsWith("Wikipedia - "); // Assert title starts with
assertCurrentUrlMatches("http://[a-z]{2,3}.wikipedia.org/.*"); // Assert current url matches regex
assertHasClass("selected", homeTab); // Assert WebElement tags has class
// ...type assert then bring up the list of all supported asserts with your IDE's autocompletion
...and conditional statements
if (hasClass("selected", homeTab)) { // Check if WebElement tags has class
// ...do something
}
if (browserIsInternetExplorer()) { // Check if browser is Internet Explorer
// ...handle cross browser difference
}
...and wait for specific time and conditions
waitFor(3, MINUTES); // Wait for specific time
waitForElementToDisplay(downloadCompletePopup, 30, SECONDS); // Wait for WebElements to display within specific time
...and use the driver
System.out.println(driver().getPageSource());
...and take screenshots
takeScreenshots("screenshotfilename") // Save a screenshot to the screenshots directory in the project root
For a list of provided Bot methods take a look at the javadoc for the Bot class or use the autocompletion tool of your IDE (usally with Ctrl + Space and then start typing).
If you feel that some Bot methods are missing please describe them in a new GitHub issue or even better clone this repository, commit the new methods and create a Pull Request.
If you won't run your tests with the WebDriverRunner make sure you set the driver in the WebDriverExtensionsContext before using the Bot
WebDriverExtensionsContext.setDriver(yourDriver);
There is now also a VaadinBot that can be used if testing an application using the Vaadin Framework
Open your terminal and run
mvn archetype:generate -DarchetypeGroupId=com.github.webdriverextensions -DarchetypeArtifactId=webdriverextensions-archetype-quickstart
...and answer the questions to generate
projectname
├── drivers
├── pom.xml
└── src
├── main
│ └── java
│ └── com
│ └── companyname
│ ├── SiteNameSite.java
│ ├── SiteNameSiteTest.java
│ ├── component
│ │ └── ExampleWebComponent.java
│ └── page
│ └── MainPage.java
└── test
├── java
│ └── com
│ └── companyname
│ └── MainPageTest.java
└── resources
└── logback-test.xml
No need to add any drivers since the webdriverextensions-maven-plugin is configured to download them for you!
Simply just run the generated template test by executing
cd projectname
mvn test
The Javadoc of this project is available online hosted by javadoc.io. You can find the latest documentation over here. Please note that at the moment the documentation of the java classes and methods are limited (except for this documentation).
void takeScreenshotOf(WebElement element, String fileName)
void takeScreenshotWithHighlight(WebElement element, String fileName)
void takeScreenshotWithHighlight(WebElement element, int borderWidth, String fileName)
void takeScreenshotWithHighlight(WebElement element, Color highlightColor, String fileName)
void takeScreenshotWithHighlight(WebElement element, String fileName, int borderOffset)
void takeScreenshotWithHighlight(WebElement element, int borderWidth, Color highlightColor, String fileName)
void takeScreenshotWithHighlight(WebElement element, Color highlightColor, int borderOffset, String fileName)
void takeScreenshotWithHighlight(WebElement element, int borderWidth, int borderOffset, String fileName)
void takeScreenshotWithHighlight(WebElement element, int borderWidth, Color highlightColor, int borderOffset, String fileName)
void openInNewTab(WebElement element)
String openInNewTabAndFocus(WebElement element)
Set<String> availableWindowHandles()
String currentWindowHandle()
void switchToWindow(String handle)
void waitForNewTabToOpen(Set<String> oldWindowHandles)
void waitForNewTabToOpen(Set<String> oldWindowHandles, int seconds)
void waitForPageToLoad()
void waitForPageToLoad(int seconds)
void executeForLink(WebElement link, Runnable function)
void executeForLinks(Collection<WebElement> links, Runnable function)
mvn install -Dwebdriverextensions.disabledbrowsers=firefox,chrome,safari
public class TableComponent<T extends WebComponent> extends WebComponent {
@FindBy(...)
public List<T> rowList;
}
public class ASearchResultType extends WebComponent {
// the model for the search result row
}
@FindBy(...)
TableComponent<ASearchResultType> resultTable;
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.