Selenium WebDriver is an open-source, cross-platform library designed to help automate browser testing. It is used by test engineers to perform automation testing by interacting with web browsers and different elements on the web page to simulate the user actions on websites.
The introduction of Selenium Manager in Selenium WebDriver version 4.6.0 has brought a big relief to the test automation engineers as it has the batteries included.
This way, Selenium Manager does not require setting the driver executable path or using third-party libraries like WebDriverManager to start the browsers.
With the release of Selenium WebDriver 4.12.0, more features have been included, like Automated Driver Management and Automated Browser Management for Chrome and Firefox browsers. That said, if you don’t have the browsers installed on the machine where tests are run, Selenium will install the browser, download the required browser drivers, and run the tests. Awesome, isn’t it?
Selenium WebDriver with Java has a huge community, so automation testers can make use of active community contributions and detailed documentation to write tests. It can also be useful in providing quick help in case the testers get stuck while automating the websites.
In this blog, we will learn how to set up a Selenium WebDriver with Java and run the web automation tests (in series and parallel) on the LambdaTest cloud grid.
So, let’s get started!
Setting up the framework
Before we look into the demonstration, let’s first organize a few things that we will need further to write the code. For instance, programming language, framework, and more. In this case, we will use Java as our programming language, TestNG as our testing framework, and any IDE of your choice.
Programming Language/ Tools/Framework | Version |
Java | 17 |
Selenium WebDriver | 4.12.1 |
TestNG | 7.8.0 |
Maven | 3.9.4 |
To start with, we need to create a new Maven project in the IDE. After successfully creating the project, let's add the Selenium WebDriver and TestNG dependencies in the pom.xml file.
File Name: pom.xml
="1.0"="UTF-8"
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.mfaisalkhatri</groupId>
<artifactId>selenium-lambdatest-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>selenium-lambdatest-demo</name>
<url>http://maven.apache.org</url>
<properties>
<selenium-java.version>4.12.1</selenium-java.version>
<testng.version>7.8.0</testng.version>
<maven.compiler.version>3.11.0</maven.compiler.version>
<surefire-version>3.1.2</surefire-version>
<java.release.version>17</java.release.version>
<maven.source.encoding>UTF-8</maven.source.encoding>
<suite-xml>testng.xml</suite-xml>
<argLine>-Dfile.encoding=UTF-8 -Xdebug -Xnoagent</argLine>
</properties>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium-java.version}</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven.compiler.version}</version>
<configuration>
<release>${java.release.version}</release>
<encoding>${maven.source.encoding}</encoding>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-version}</version>
<executions>
<execution>
<goals>
<goal>test</goal>
</goals>
</execution>
</executions>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<properties>
<property>
<name>usedefaultlisteners</name>
<value>false</value>
</property>
</properties>
<suiteXmlFiles>
<suiteXmlFile>${suite-xml}</suiteXmlFile>
</suiteXmlFiles>
<argLine>${argLine}</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
With the dependencies updated in the pom.xml, the project setup is complete. We are good to start writing the web automation tests using Selenium WebDriver with Java
Writing the first test case using Selenium WebDriver and Java
We will use the Input form on LambdaTest's Selenium Playground to demo the workings of Selenium WebDriver with Java. The following is the Test Scenario we will be working on:
- Open LambdaTest’s Selenium Playground
- Click on the Input Form Demo link.
- Locate and enter values in all the fields on the Input Form Demo page.
- Click on the Submit button.
- Assert the message "Thanks for contacting us, we will get back to you shortly." displayed after the form is submitted successfully.
LambdaTest’s Selenium Playground WebSite
Input Form Demo page
Success Message
Setting up the configuration
As discussed in the earlier section on setting up the framework, we have already created the Maven project. The following screenshot of the project structure:
We will run the tests on Chrome and Firefox browsers in the LambdaTest Cloud grid.
LambdaTest is an AI-powered test orchestration and execution platform that lets you perform test automation at scale on an online Selenium grid of 3000+ real desktop browsers, browser versions, and operating systems. It offers a robust, scalable, and highly secure cloud platform for test execution that empowers development and testing teams to expedite their release cycles. By enabling parallel testing, it significantly reduces test execution times by multiple folds.
Create a Java class and name the file as DriverManager
. This class consists of the code that will help initiate the browsers along with setting up LambdaTest capabilities to run our test across different browsers, browser versions, and operating systems over the cloud.
File Name: DriverManager.java
public class DriverManager {
private static final ThreadLocal<WebDriver> DRIVER = new ThreadLocal<>();
private static final String GRID_URL = "@hub.lambdatest.com/wd/hub";
private static final String LT_ACCESS_TOKEN = System.getProperty("LT_ACCESS_KEY");
private static final String LT_USERNAME = System.getProperty("LT_USERNAME");
public void createDriver(final Browsers browser) {
switch (browser) {
case FIREFOX -> setupFirefoxDriver();
case CHROME_CLOUD -> setupChromeInLambdaTest();
case FIREFOX_CLOUD -> setupFirefoxInLambdaTest();
default -> setupChromeDriver();
}
setupBrowserTimeouts();
}
public WebDriver getDriver() {
return DriverManager.DRIVER.get();
}
public void quitDriver() {
if (null != DRIVER.get()) {
getDriver().quit();
DRIVER.remove();
}
}
private HashMap<String, Object> ltOptions() {
final var ltOptions = new HashMap<String, Object>();
ltOptions.put("username", LT_USERNAME);
ltOptions.put("accessKey", LT_ACCESS_TOKEN);
ltOptions.put("resolution", "2560x1440");
ltOptions.put("selenium_version", "4.0.0");
ltOptions.put("build", "LambdaTest Scale Demo");
ltOptions.put("name", "LambdaTest tests at scale");
ltOptions.put("acceptInsecureCerts", true);
ltOptions.put("w3c", true);
ltOptions.put("plugin", "java-testNG");
return ltOptions;
}
private void setDriver(final WebDriver driver) {
DriverManager.DRIVER.set(driver);
}
private void setupBrowserTimeouts() {
getDriver().manage()
.timeouts()
.implicitlyWait(Duration.ofSeconds(20));
}
private void setupChromeDriver() {
setDriver(new ChromeDriver());
}
private void setupChromeInLambdaTest() {
final var browserOptions = new ChromeOptions();
browserOptions.setPlatformName("Windows 10");
browserOptions.setCapability("LT:Options", ltOptions());
try {
setDriver(
new RemoteWebDriver(new URL(format("https://{0}:{1}{2}", LT_USERNAME, LT_ACCESS_TOKEN, GRID_URL)),
browserOptions));
} catch (final MalformedURLException e) {
throw new Error("Error setting up cloud browser in LambdaTest", e);
}
}
private void setupFirefoxInLambdaTest() {
final var browserOptions = new FirefoxOptions();
browserOptions.setPlatformName("Windows 10");
browserOptions.setCapability("LT:Options", ltOptions());
try {
setDriver(
new RemoteWebDriver(new URL(format("https://{0}:{1}{2}", LT_USERNAME, LT_ACCESS_TOKEN, GRID_URL)),
browserOptions));
} catch (final MalformedURLException e) {
throw new Error("Error setting up cloud browser in LambdaTest", e);
}
}
private void setupFirefoxDriver() {
setDriver(new FirefoxDriver());
}
}
Code Walkthrough
Before we jump to the outcome of this code, let us know to understand the code in a step-by-step process.
Step 1: Use the ThreadLocal
class to instantiate the WebDriver
instance. This ensures that each thread has its own isolated instance of the WebDriver
.
Step 2: The primary advantage of ThreadLocal
is ensuring a safe driver setting, especially when running tests in parallel. It isolates different threads, providing thread safety even if they set different values on the same ThreadLocal
object.
Step 3: This isolation helps run threads independently and in isolation, preventing conflicts and issues.
When running tests on the LambdaTest platform, specific mandatory capabilities need to be set. You can use the LambdaTest Capabilities generator to configure these capabilities effectively. This ensures your tests are compatible and work seamlessly with the LambdaTest platform.
Capability Name | Description |
GRID_URL | Remote URL required to run tests on LambdaTest Cloud grid |
LT_USERNAME | LambdaTest Username |
LT_ACCESS_KEY | LambdaTest Access Key |
LambdaTest Capabilities Generator allows setting the capabilities by selecting the needed configurations easily from the UI. Accordingly, it generates code that can be copy pasted easily into the project to start the browser automation test hassle-free.
Setting the capabilities in DriverManager class
The capabilities generated using the LambdaTest Capabilities generator have been updated in the ltOptions()
method that will be used in the setupChromeInLambdaTest()
and setupFirefoxInLambdaTest()
methods for setting the capabilities on LambdaTest Cloud grid for running tests on Chrome and Firefox browsers respectively.
setupChromeInLambdaTest() method
The additional parameter, like the Operating System, will be set using the setupChromeInLambdaTest()
method. We will be using the "Windows 10" platform. The RemoteWebDriver()
method of Selenium will help us in instantiating a new session of WebDriver on the LambdaTest Cloud that will be created by passing the GRID_URL
, LT_USERNAME
and LT_ACCESS_KEY
.
Similarly, the setupFirefoxInLambdaTest()
method will help in creating a new session of Firefox browser on the "Windows 10" platform on the LambdaTest Cloud grid.
On the LambdaTest Cloud grid based on the values passed in the testng.xml file for the browsers.
The setupBrowserTimeouts()
method will help set the implicit wait timeout for 20 seconds after successfully creating the browser session.
The implicit wait applies to all the web elements on the page and is added, so the driver should poll the page until an element is found or the timeout expires.
Similarly, Explicit Wait can also be added for searching a web element where the driver is asked to wait for a certain condition invoked using the ExpectedConditions
class.
With this, all the configurations are now set, and we can move on to write the automated tests for the test scenario we discussed earlier in the blog.
Configuring the test
Before we begin writing the tests, let's first create a BaseTest
class that will hold the common configuration setup, like starting and quitting the browsers.
This BaseTest
class will be extended by the actual Test class.
File Name: BaseTest.java
public class BaseTest {
protected DriverManager driverManager;
@BeforeClass
@Parameters("browser")
public void setup(final String browser) {
this.driverManager = new DriverManager();
this.driverManager.createDriver(Browsers.valueOf(browser.toUpperCase()));
}
@AfterClass
public void tearDown() {
this.driverManager.quitDriver();
}
}
The @Parameters
annotation in TestNG has been used in this BaseTest
class as it helps in the configuration of multiple parameters through testng.xml, thus removing the difficulty of updating the code repeatedly as the tests can be run on different browsers without any code modification. The browser values can be updated in the testng.xml, and the tests can be executed accordingly.
Implementation of test scenario
Just for a quick recap, we will be writing the tests for the following test scenario:
The following test class, SeleniumDemoTests
, has been created that will implement all the steps that we discussed in the test scenario.
File Name: SeleniumDemoTests.java
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Parameters;
import java.sql.DriverManager;
public class SeleniumDemoTests extends BaseTest {
@Test
public void testInputForm() {
this.driverManager.getDriver().navigate().to("https://www.lambdatest.com/selenium-playground/");
final var mainPage = new MainPage(this.driverManager.getDriver());
mainPage.clickOnLink("Input Form Submit");
final var formDemoPage = new FormDemoPage(this.driverManager.getDriver());
formDemoPage.fillForm("Faisal", "faisal@gmail.com", "Pass@111", "LambdaTest",
"https://www.lambdatest.com", "India", "Mumbai",
"Sector 22, Lane 1", "Landmark zone",
"Maharashtra", "400001");
assertEquals(formDemoPage.successMessage(), "Thanks for contacting us, we will get back to you shortly.");
}
}
While writing tests, the Page Object Model is used, which involves creating two separate page object classes that require testing: Main Page and Input Form Demo page.
The MainPage
class has the locators method for the LambdaTest’s Selenium Playground Main page. On this page, we need to locate the "Input Form Submit" link and click on it to open the Input Form Demo page.
File Name: MainPage.java
public class MainPage {
private final WebDriver driver;
public MainPage(final WebDriver driver) {
this.driver = driver;
}
public void clickOnLink(final String linkName) {
this.driver.findElement(By.linkText(linkName)).click();
}
}
To locate the "Input Form Submit" link, we will be using the Developer Tools option in the Chrome browser.
As it is an href link
, we can make use of the Selenium Selector LinkText
to locate this element. To make the method more robust for locating all the links on this page, a generic method clickOnLink()
has been created that accepts the link name in the string format and locates the link using the LinkText
selector strategy and clicks on it. This way, we don't have to add duplicate lines of code to locate other links on this page.
Next, let’s move to the "Form Demo" page that will be opened after the "Input Form Submit" link is clicked.
The FormDemoPage
class has been created to house all the locators of the Form Demo page.
File Name: FormDemoPage.java
public class FormDemoPage {
private final WebDriver driver;
private final WebDriverWait wait;
public FormDemoPage(final WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, Duration.ofSeconds(20));
}
private WebElement nameField() {
return this.driver.findElement(By.id("name"));
}
private WebElement emailField() {
return this.driver.findElement(By.id("inputEmail4"));
}
private WebElement passwordField() {
return this.driver.findElement(By.id("inputPassword4"));
}
private WebElement companyField() {
return this.driver.findElement(By.id("company"));
}
private WebElement websiteField() {
return this.driver.findElement(By.id("websitename"));
}
private WebElement country() {
return this.driver.findElement(By.name("country"));
}
private void countryName(final String countryName) {
country().click();
new Select(country()).selectByVisibleText(countryName);
}
private WebElement cityField() {
return this.driver.findElement(By.id("inputCity"));
}
private WebElement addressLineOneField() {
return this.driver.findElement(By.name("address_line1"));
}
private WebElement addressLineTwoField() {
return this.driver.findElement(By.name("address_line2"));
}
private WebElement stateField() {
return this.driver.findElement(By.id("inputState"));
}
private WebElement zipCodeField() {
return this.driver.findElement(By.id("inputZip"));
}
private WebElement submitBtn() {
return this.driver.findElement(By.cssSelector("#seleniumform button[type=\"submit\"]"));
}
public String successMessage() {
return this.wait.until(ExpectedConditions.presenceOfElementLocated(By.cssSelector("p.success-msg"))).getText();
}
public void fillForm(final String name, final String email, final String password, final String company,
final String website, final String country, final String city, final String addressLineOne,
final String addressLineTwo, final String state, final String zipCode) {
this.nameField().sendKeys(name);
this.emailField().sendKeys(email);
this.passwordField().sendKeys(password);
this.companyField().sendKeys(company);
this.websiteField().sendKeys(website);
this.countryName(country);
this.cityField().sendKeys(city);
this.addressLineOneField().sendKeys(addressLineOne);
this.addressLineTwoField().sendKeys(addressLineTwo);
this.stateField().sendKeys(state);
this.zipCodeField().sendKeys(zipCode);
this.submitBtn().click();
}
}
There are multiple text input fields on the Input Form Demo page like Name
, Email
, Password
, Company
, Website
, City
, State
, etc. all these fields are located using the ID locator. For example, the below nameField()
method returns the WebElement
for the Name
field. Likewise, separate methods are created for locating the other fields on this page.
Country
dropdown is a single dropdown field with the list of countries populated.
If we check the DOM of the Form Demo page, we can see that it has the Select
tag on it.
This clarifies that we can use the Select
class of Selenium to interact with this field. The strategy to interact with this field is to first locate the Country
field and then select the visible text of the Country Name from this field.
The country()
method will locate and return the WebElement
for the Country field. Next, the countryName()
method will select the Country
name provided in its parameter.
The Submit button on the page needs to be located so we can submit the form.
Looking at the DOM of the page, it can be seen that there are no ID or name attributes for the Submit button. Here, we can make use of the CSS Selector locator strategy to locate the Submit button by using the selector - #seleniumform button[type = "submit"]
.
#seleniumform
is the ID attribute for the Form
on the Form Demo page, and within it, the button tag
with the type
attribute value "submit" is available for the Submit button.
The fillForm()
method will perform the interactions with all the fields on the Form Demo page and click on the Submit button. The test data will be provided as a parameter in this method in the actual test.
Next, we will locate the success message text that will be displayed after the form is submitted successfully. Now, the catch here is the element will be visible after a few seconds of form submission. Hence, we will have to wait a few seconds before Selenium starts locating the element. If we don’t wait, we will get a NoSuchElementException
for the success message text, as Selenium will directly start searching for the text after the form submission.
To handle this waiting, we will be calling the presenceOfElementLocatedBy()
method from the expected_conditions
class using Explicit Wait in Selenium.
The successMessage()
method will wait for the WebElement
to be present and, once it is available, will locate the WebElement
and return its text in String format.
The following is the test that we discussed in the earlier section of this blog that implements the test scenario.
The test will first navigate to the LamdaTest’s Selenium playground website. Next, it will click on the "Input Form Submit" link on the main page. Once the Form Demo page is loaded, it will fill in the form using the fillForm()
method from the FormDemoPage
class.
The last statement of the test is for performing the assertion. It will check that the success message text is equal to "Thanks for contacting us, we will get back to you shortly."
Running automation tests at scale using Selenium on a LambdaTest cloud grid
The following testng.xml file will be used to run the test.
="1.0"="UTF-8"
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Selenium WebDriver Demo Suite for LambdaTests" parallel="tests">
<test name="Selenium Playground - Input Form Demo Test on Chrome">
<parameter name="browser" value="chrome"/>
<classes>
<class name="io.github.mfaisalkhatri.SeleniumDemoTests">
<methods>
<include name="testInputForm"/>
</methods>
</class>
</classes>
</test>
<test name="Selenium Playground - Input Form Demo Test on Firefox">
<parameter name="browser" value="firefox"/>
<classes>
<class name="io.github.mfaisalkhatri.SeleniumDemoTests">
<methods>
<include name="testInputForm"/>
</methods>
</class>
</classes>
</test>
<test name="Selenium Playground - Input Form Demo Test on LambdaTest Chrome">
<parameter name="browser" value="chrome_cloud"/>
<classes>
<class name="io.github.mfaisalkhatri.SeleniumDemoTests">
<methods>
<include name="testInputForm"/>
</methods>
</class>
</classes>
</test>
<test name="Selenium Playground - Input Form Demo Test on LambdaTest Firefox">
<parameter name="browser" value="firefox_cloud"/>
<classes>
<class name="io.github.mfaisalkhatri.SeleniumDemoTests">
<methods>
<include name="testInputForm"/>
</methods>
</class>
</classes>
</test>
</suite>
GitHub
There are a total of 4 different test blocks in the testng.xml file. All the tests will be running in parallel. Parallel execution of the tests helps in saving the test execution time because all tests are performed concurrently.
We need to mention parallel="tests"
for performing parallel testing in TestNG, in testng.xml. Here, all the test blocks updated in testng.xml will be executed in parallel.
The first and second test blocks will execute the test scenario on the local machine on Chrome and Firefox browsers, respectively. The third and fourth test blocks will execute the test scenario on the LambdaTest Cloud grid on Chrome and Firefox browsers respectively.
Our test execution is thread-safe as we have incorporated the ThreadLocal
class into our code. Hence, there is no need to worry about the overlapping test session issues.
Screenshot of the test executed using IntelliJ IDE
Tests executed on the LambdaTest Cloud grid can be viewed on the LambdaTest Dashboard. It provides a fair visibility of the test execution by providing step-by-step granular details of the test run in LambdaTest Analytics. Details like video recordings, device logs, screenshots, etc can also be viewed on it.
Check out the screenshots below, which will give you a fair insight into the dashboard for web automation tests.
LambdaTest Dashboard
The details of the build and the tests that were conducted are visible in the following screenshots. Each test includes the test name, browser name, browser version, OS name, respective OS version, and screen resolution.
It also has the video of the test that was run, giving a better idea about how tests were run on the browsers.
Test run on Chrome browser on LambdaTest Cloud
Test run on Firefox browser on LambdaTest Cloud
Conclusion
Selenium WebDriver is a popular web automation framework that automates web browser-related tests. Test Automation helps in faster feedback and running all the regression and functional tests quickly.
By integrating Selenium with Cloud providers like LambdaTest, we can run the Web automation tests at scale on different browsers and versions without worrying about the Operating System and Browser installations, as all the infrastructure is provided on demand.
After the test execution is completed successfully, detailed insight into the tests can be viewed with all the granular details that can help report stuff to the software teams and the stakeholders.