Boosted Testing Strategies for 2019: A Tutorial on Java Automation Testing

Test automation engineers globally strive to enhance their frameworks, seeking improved stability, speed, usability, and maintainability. This continuous evolution is essential to ensure widespread framework adoption within their organizations, as outdated frameworks quickly become obsolete.

This article highlights key areas for framework updates in 2019, preparing for advancements in 2020. The focus is on addressing common “pain points” - aspects that are complex or prone to failures. Three areas identified for simplification and improvement include:

  1. Selenium Grid
  2. Waits
  3. Chrome DevTools

Selenium Grid, known for its setup challenges and instability, requires attention to identify potential improvements. Exploring new waits in the Selenium API can enhance test stability. Additionally, integrating Chrome DevTools, an indispensable tool for testers, with Selenium presents further optimization opportunities.

Tip #1: Dockerize Your Selenium Grid

Setting up, maintaining, and deploying Selenium Grid, especially within a CI pipeline, can be cumbersome. Utilizing pre-built Selenium Docker images provides a significantly easier, more stable, and manageable solution.

Note: The primary limitation of this approach is the lack of Internet Explorer (IE) support, as containerizing the Windows operating system remains infeasible.

Getting Set Up

To begin, ensure you have Docker and Docker Compose installed on your system. For Windows 10 or Mac users, both are typically included within the Docker Desktop.

Starting Your Grid

The official Selenium repository on Docker Hub offers pre-built Docker images for the Selenium Hub, along with Firefox and Chrome Nodes.

A straightforward way to leverage these components locally is by creating a Docker Compose file (docker-compose.yml) in your project’s root directory.

Below is an example demonstrating the creation of a Grid comprising:

  • A single Selenium Hub
  • One Chrome node
  • One Firefox node
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#docker-compose.yml
version: "3"
services:
  selenium-hub:
	image: selenium/hub:3.141.59-neon
	container_name: selenium-hub
	ports:
  	- "4444:4444"
  chrome:
	image: selenium/node-chrome:3.141.59-neon
	volumes:
  	- /dev/shm:/dev/shm
	depends_on:
  	- selenium-hub
	environment:
  	- HUB_HOST=selenium-hub
  	- HUB_PORT=4444
  firefox:
	image: selenium/node-firefox:3.141.59-neon
	volumes:
  	- /dev/shm:/dev/shm
	depends_on:
  	- selenium-hub
	environment:
  	- HUB_HOST=selenium-hub
  	- HUB_PORT=4444

The Docker Compose file defines your Grid’s configuration. Refer to the official documentation for detailed information on creating such files.

To launch your Grid, execute the following command in a terminal (PowerShell or Command Prompt on Windows) from your project’s root directory:

1
docker-compose up

Connecting to the Grid

Connecting to your Selenium Grid follows the standard procedure, with the Hub listening on port 4444 of your local machine. The example below illustrates setting up a Driver to utilize the Chrome Node.

1
2
3
4
5
6
7
8
9
// Driver.java
protected static RemoteWebDriver browser;
DesiredCapabilities cap = new DesiredCapabilities();
ChromeOptions chromeOptions = new ChromeOptions();
           	 
cap.setCapability(ChromeOptions.CAPABILITY, chromeOptions);           	 
cap.setBrowserName("chrome");
           	 
driver = new RemoteWebDriver(cap);

Subsequently, utilize the TestNG library to execute tests on multiple nodes concurrently.

While running multiple browsers on a single node is technically possible, it’s not recommended. Best practice dictates using one browser per node for optimal performance.

Additional Tips and Tricks

For debugging purposes, consider a debug variant of your docker-compose.yml file that retrieves the debug browser nodes. These versions include a VNC server, enabling you to monitor browser activity during test execution.

Running browsers headlessly enhances speed, and Selenium provides base images for building custom images if additional software is required.

To ensure CI pipeline stability, deploy your Grid onto Kubernetes or Swarm. This facilitates rapid Docker restoration or replacement in case of failures.

Tip #2: Smart Waits

Waits are fundamental to test automation framework stability. They eliminate the need for sleeps or pauses, mitigating slow network and cross-browser issues while enhancing test speed.

Java Automation Testing Tutorial #2: Logical Operators in Waits: Be Specific with Your Waits

The comprehensive ExpectedConditions class offers a wide range of scenarios. While ExpectedConditions.presenceOfElementLocated(locator) often suffices, prioritize methods within this class to handle every user action by integrating them into your Actions.java class. This approach strengthens tests against most cross-browser and website performance issues.

For instance, when clicking a link opens a new tab, employ ExpectedConditions.numberOfWindowsToBe(2) to ensure the tab’s availability before switching.

Utilize a wait to guarantee capturing all elements on a page during findElements usage, particularly beneficial for search result pages. For example, this line:

1
List<WebElement> results = driver.findElements(locators.RESULTS);

might yield an empty List if search results are pending. Instead, leverage the numberOfElementsToBeMoreThan expected condition to wait for results exceeding zero. For example:

1
2
3
4
5
6
7
8
9
WebElement searchButton = driver.findElement(locators.SEARCH_BUTTON);
searchButton.click(); 

new WebDriverWait(driver, 30)	
	.until(ExpectedConditions
		.numberOfElementsToBeMoreThan(locators.RESULTS, 0)); 

List<WebElement> results = driver.findElements(locators.RESULTS);
results.get(0).click();

Now, findElements executes only after search results populate.

This wait is valuable when dealing with front-end frameworks that pose challenges to Selenium, such as Angular websites. A method like this enhances test stability:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
protected static WebElement waitForElement(By locator){    
	try {        
		new WebDriverWait(browser, 30)                
			.until(ExpectedConditions                
				.numberOfElementsToBeMoreThan(locator, 0));    
	} catch (TimeoutException e){        
		e.printStackTrace();            
		Assert.fail("Timeout: The element couldn't be found in " + WAIT + " seconds!");    
	} catch (Exception e){              
		e.printStackTrace();        
		Assert.fail("Something went wrong!");    
	}    
	return browser.findElement(locator);    
}

You can even wait for elements to disappear from visibility. This proves useful when awaiting a pop-up’s disappearance after clicking OK or Save before proceeding.

1
2
3
4
5
6
7
8
WebElement okButton = driver.findElement(locators.OK_BUTTON);
okButton.click();
 
new WebDriverWait(driver, 30)
	.until(
		ExpectedConditions
			.invisibilityOfElementLocated(locators.POPUP_TITLE)
);

The official documentation provides a comprehensive list of methods, including those discussed. Reviewing these possibilities can significantly enhance your framework’s stability.

Java Automation Testing Tutorial #2: Logical Operators in Waits

Enhance wait resilience using logical operators. For instance, to verify an element’s presence and clickability, use the following (these examples return a boolean):

1
2
3
4
5
wait.until(ExpectedConditions.and(               
	ExpectedConditions.presenceOfElementLocated(locator),                	
	ExpectedConditions.elementToBeClickable(locator)
	)
);

The OR operator is suitable when uncertain about page title changes. Include a URL check if the initial condition fails to confirm the correct page:

1
2
3
4
5
wait.until(ExpectedConditions.or(                
	ExpectedConditions.titleIs(expectedTitle),                 
	ExpectedConditions.urlToBe(expectedUrl)
	)
);

Use the NOT operator to ensure a checkbox is disabled after an action:

1
2
3
4
wait.until(ExpectedConditions.not(
	ExpectedConditions.elementToBeClickable(locator)
	)
);

Operators contribute to more resilient and less brittle waits.

Tip #3: Chrome DevTools: Simulating Network Conditions

Testing solely on localhost or local networks provides an inaccurate representation of real-world performance. Simulating varying upload and download speeds offers a realistic perspective on internet performance, where timeouts can lead to action failures. Chrome DevTools empowers such simulations.

The following code demonstrates accessing the Toptal homepage under different network conditions. First, define speeds using a TestNG data provider:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import org.testng.annotations.DataProvider;

public class ExcelDataProvider {

		@DataProvider(name = "networkConditions")
    public static Object[][] networkConditions() throws Exception {
        return new Object[][] {
						// Upload Speed, Dowload Speed in kb/s and latency in ms.
            { 5000 , 5000, 5 },
            { 10000, 7000, 5 },
            { 15000, 9000, 5 },
            { 20000, 10000, 5 },
            { 0, 0 },
        };
    }
}

Note: Upload and download throttling are in kb/s, while latency is in ms.

This data facilitates testing under diverse network conditions. Within the test, the CommandExecutor executes commands in the browser’s current session. This activates the necessary settings in Chrome’s Developer Tools functionality to simulate the slow network. Include the code within the if statement in a @BeforeClass method when running test suites.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.openqa.selenium.remote.Command;
import org.openqa.selenium.remote.CommandExecutor;
import org.openqa.selenium.remote.Response;

public class TestClass {

// load our data provider
@Test(dataProvider = "networkConditions")
public void test(int download, int upload, int latency)
throws IOException {

        // only run if the network is throttled
        if (download > 0 && upload > 0) {
                CommandExecutor executor = driver.getCommandExecutor();

                // create a hashmap of the required network conditions
                Map map = new HashMap();
                // you can even test 'offline' behaviour
                map.put("offline", false);
                map.put("latency", latency);

                map.put("download_throughput", downloadThroughput);
                map.put("upload_throughput", uploadThroughput);

                // execute our code
                Response response = executor.execute(
                        new Command(driver.getSessionId(),
                                    "setNetworkConditions",
                                    ImmutableMap.of("network_conditions", ImmutableMap.copyOf(map))));
        }

        // Open the website
        driver.get("https://www.toptal.com/");

        // You can then check that elements are loaded etc.
        // Don't forget to use waits!
}
}

Bonus Tip: How to Manage Your Cookies

Browser cookies can lead to behavioral inconsistencies in your application based on their presence from previous sessions, like automatic user logins. Clearing cookies before test runs mitigates potential issues.

The following snippet deletes all cookies:

1
driver.manage().deleteAllCookies();

You can delete a cookie by name:

1
driver.manage().deleteCookieNamed("CookieName");

Retrieve a cookie’s content:

1
String myCookie = driver.manage().getCookieNamed("CookieName").getValue(); 

Or get all cookies:

1
List<Cookie> cookies = driver.manage().getCookies();

Test Automation in 2020: Looking to the Future

Selenium 4, expected shortly, introduces significant improvements. While still under development, the availability of an alpha version warrants exploration.

Note: Track progress through the roadmap.

W3C WebDriver Standardization

Selenium 4 eliminates the JSON wire protocol for browser communication, enabling direct interaction between automated tests and browsers. This addresses Selenium’s flakiness, including browser upgrade issues, potentially boosting test speed.

A Simpler Selenium Grid

Selenium Grid becomes more stable, manageable, and easier to set up. The combined node and hub setup eliminates separate configurations. Enhanced Docker support, native parallel testing, an informative UI, and request tracing with Hooks enhance debugging capabilities.

Documentation

The Selenium documentation receives a much-needed update, addressing its stagnation since Selenium 2.0.

Changes to the API

Opera and PhantomJS browser support is discontinued. Headless execution is possible with Chrome or Firefox, with Opera’s Chromium base deemed sufficient for testing.

WebElement.getSize() and WebElement.getLocation() are consolidated into WebElement.getRect(). An API command for capturing element screenshots is also introduced.

For WebDriver Window, getPosition and getSize are replaced by getRect, while setPosition and setSize are replaced by setRect. fullscreen and minimize methods facilitate these actions within tests.

Other Notable Changes:

  • Browser-specific Options classes now extend the Capabilities class.
  • driver.switchTo().parentFrame() simplifies frame navigation.
  • nice locators, a subclass of By, offer higher-level operations.
  • The DevTools API implementation unlocks Chrome Debugging Protocol features (and equivalents in other browsers), including:
    • Complete page screenshots, including off-screen elements.
    • Log streaming.
    • Waiting for mutation events.
  • Deprecated methods and classes are removed.

Note: An Alpha version of Selenium 4 is available from the Maven repository. Testing this against your current framework (ideally in a sandbox) is recommended to prepare for the upgrade.

Conclusion

This article outlines several test automation framework improvements for enhanced stability and usability, positively impacting the software delivery lifecycle.

While these changes are beneficial, a comprehensive framework review for “pain points” is highly recommended. For instance, are you leveraging WebDriver Manager for driver management, or are manual updates still prevalent?

Regular framework reviews, at least annually or ideally biannually, are crucial. Familiarize yourself with the upcoming changes in Selenium 4.0 using the alpha releases. Be prepared for these impactful changes.

Share any novel methods or techniques discovered during your framework review in the comments section below to benefit fellow readers.

For insights into automated tests in Selenium and using Page Object models for maintainable and reusable test routines, explore “Automation in Selenium: Page Object Model and Page Factory.”

Licensed under CC BY-NC-SA 4.0