Selenium WebDriver – How To Improve Test Performance Using Java8 Stream

Overview:

What do we normally do to reduce the overall test execution time once you have hundreds of automated tests in our regression suite? Mostly we all go for spinning up multiple VMs to run our automated tests in parallel or we would increase thread count for parallel execution in a single VM. Don’t we? Have we ever tried to reduce the test execution time of a single test? [Assuming you do not have any explicit hard coded wait statements. If you have any, check this]

Lets see how it can be done using Java8 Stream!

Sample Application:

As usual, to explain things better, I am considering this static page. When we click on these buttons, we see a notification on the right top corner of the application where each notification message is displayed for approximately 4 seconds!

stream-prob-statement

Lets assume that our aim is to verify this functionality of the application.

Sample Page Object:

I create a simple page object considering only those 4 buttons and the corresponding 4 notification alerts. Our validation will be success only if the notification message/alert is displayed and it also hides within 5 seconds.

public class SamplePage {

    private final WebDriver driver;

    @FindBy(css="div.button-box button.btn-info")
    private WebElement btnInfo;

    @FindBy(css="div.button-box button.btn-warning")
    private WebElement btnWarning;

    @FindBy(css="div.button-box button.btn-success")
    private WebElement btnSuccess;

    @FindBy(css="div.button-box button.btn-danger")
    private WebElement btnDanger;

    @FindBy(css="div.jq-icon-info")
    private WebElement infoAlert;

    @FindBy(css="div.jq-icon-warning")
    private WebElement warningAlert;

    @FindBy(css="div.jq-icon-success")
    private WebElement successAlert;

    @FindBy(css="div.jq-icon-error")
    private WebElement dangerAlert;

    public SamplePage(final WebDriver driver){
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public void goTo(){
        this.driver.get("https://wrappixel.com/demos/admin-templates/admin-pro/main/ui-notification.html");
    }

    public boolean validateInfoAlert(){
        return this.validate(btnInfo,infoAlert);
    }

    public boolean validateWarningAlert(){
        return this.validate(btnWarning,warningAlert);
    }

    public boolean validateSuccessAlert(){
        return this.validate(btnSuccess, successAlert);
    }

    public boolean validateDangerAlert(){
        return this.validate(btnDanger, dangerAlert);
    }

    private boolean validate(WebElement button, WebElement notification) {
        button.click();
        boolean result = notification.isDisplayed();

        try{
            Awaitility.await()
                    .atMost(5, TimeUnit.SECONDS)
                    .until(() -> !notification.isDisplayed());
        }catch(Exception e){
            result = false;
        }
        return result;
    }

}

Now It is time for us to create a Test class to test this feature.

Sample Test:

public class NotificationTest {

    private WebDriver driver;
    private SamplePage samplePage;


    @BeforeTest
    public void init(){
        this.driver = DriverManager.getDriver();
        this.samplePage = new SamplePage(driver);
        this.samplePage.goTo();
    }

    @Test
    public void test(){
        Assert.assertTrue(this.samplePage.validateInfoAlert());
        Assert.assertTrue(this.samplePage.validateWarningAlert());
        Assert.assertTrue(this.samplePage.validateSuccessAlert());
        Assert.assertTrue(this.samplePage.validateDangerAlert());
    }

    @AfterTest
    public void tearDown(){
        this.driver.quit();
    }

}

Test Execution:

Lets execute the test. As you see in this video, the test runs just fine as we expected. However as part of the notification validation, we spend around 16 seconds to validate these alerts.

Our script does not have any hard coded wait statement. Can we still the improve the performance here?

Of course, we can. That is what we are going to see in this article.

Abstract Element Validator:

First we need to wrap any type of validations object to a specific abstract type. So, I create a simple abstract class as shown here.

public abstract class ElementValidator {
    public abstract boolean validate();
}

Since we are validating the notifications, I create below class by extending the above abstract class.

Notification Validator:

public class NotificationValidator extends ElementValidator {

    private final WebElement button;
    private final WebElement notification;

    public NotificationValidator(final WebElement button, final WebElement notification){
        this.button = button;
        this.notification = notification;
    }

    @Override
    public boolean validate() {
        this.button.click();
        boolean result = this.notification.isDisplayed();

        Awaitility.await()
                .atMost(5, TimeUnit.SECONDS)
                .until(() -> !this.notification.isDisplayed());
        
        return result && (!this.notification.isDisplayed());
    }
    
}

Page Object:

Our SamplePage class is modified to return the list of validation objects.

public class SamplePage {

    private final WebDriver driver;

    //all findby elements are here
 
    public SamplePage(final WebDriver driver){
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public void goTo(){
        this.driver.get("https://wrappixel.com/demos/admin-templates/admin-pro/main/ui-notification.html");
    }

    public List<ElementValidator> getElementValidators(){
        List<ElementValidator> elements = new ArrayList();
        elements.add(new NotificationValidator(btnInfo, infoAlert));
        elements.add(new NotificationValidator(btnWarning,warningAlert));
        elements.add(new NotificationValidator(btnSuccess, successAlert));
        elements.add(new NotificationValidator(btnDanger, dangerAlert));
        return elements;
    }

}

Sample Test:

Note the test method in the below class. I get the list of ElementValidator & then iterate using parallel stream and call the ‘validate’ method.

public class NotificationParallelTest {

    private WebDriver driver;
    private SamplePage samplePage;


    @BeforeTest
    public void init(){
        this.driver = DriverManager.getDriver();
        this.samplePage = new SamplePage(driver);
        this.samplePage.goTo();
    }

    @Test
    public void test(){
        this.samplePage.getElementValidators()  //list of element validators
                .stream()                       //iterate
                .parallel()                     //
                .map(ElementValidator::validate)//call the validate method
                .forEach(Assert::assertTrue);   //result should be true
    }

    @AfterTest
    public void tearDown(){
        this.driver.quit();
    }

}

Now, we spend only 4 seconds to validate these notification messages.

Why Abstract Class?

You might wonder why we need an abstract class here. Why can we not simply send a List<NotificationValidator> Instead of List<ElementValidator>? The answer is simple. We do NOT want to make the validation specific to Notification. Instead, it is good if it is generic. So that any future validation requirement can be easily accommodated with this approach.

Lets assume that above validation was included in our test automation framework in Sprint 1. Now in Sprint 2, they added below additional component on the same screen. As an automated engineer, We need to include this feature in our framework.

The requirement is – after clicking on the ‘X’ on the below alerts messages, they should disappear!

stream-prob-statement-1

Now you could understand that we might not be able to reuse NotificationValidator class. But we could create a new class, lets call it – DissmissalAlertValidator which extends ElementValidator class.

public class DissmissalAlertValidatior extends ElementValidator {

    private final WebElement dissmissalSection;

    public DissmissalAlertValidatior(final WebElement component){
        this.dissmissalSection = component;
    }

    @Override
    public boolean validate() {

        //all messages should be displayed first; the below result is true
        boolean result = this.dissmissalSection.findElements(By.cssSelector("button.close"))
                .stream()
                .allMatch(WebElement::isDisplayed);

        //close all the messages
        this.dissmissalSection.findElements(By.cssSelector("button.close"))
                .stream()
                .forEach(WebElement::click);

        //none of the messages should be displayed now
        result = result && this.dissmissalSection.findElements(By.cssSelector("button.close"))
                .stream()
                .noneMatch(WebElement::isDisplayed);

        return result;
    }
}

Page Object:

To support the additional requirement, my page has to return the additional validators in the list which is not a big change.

@FindBy(css="div.col-lg-4:nth-child(2)")
private WebElement dissmissalSection;

@FindBy(css="div.col-lg-4:nth-child(3)")
private WebElement alertWithImageIcon;


public List<ElementValidator> getElementValidators(){
    List<ElementValidator> elements = new ArrayList();
    elements.add(new NotificationValidator(btnInfo, infoAlert));
    elements.add(new NotificationValidator(btnWarning,warningAlert));
    elements.add(new NotificationValidator(btnSuccess, successAlert));
    elements.add(new NotificationValidator(btnDanger, dangerAlert));

    //2 additonal validators
    elements.add(new DissmissalAlertValidatior(dissmissalSection));
    elements.add(new DissmissalAlertValidatior(alertWithImageIcon));
    return elements;
}

Since test class expects the list of ElementValidator, it does not require really any change. So, the test class remains unchanged.  Lest run the test.

 

As you could see, we validated everything in the page without affecting the performance of our test!

Summary:

By carefully analyzing our test requirements and designing page objects appropriately with proper design principles, we could create highly reusable components and minimize the overall automation maintenance effort & test execution time. The above approach might work if we spend more time on a single page. But you might not be able to use this approach for 2 buttons when the onclick events of those buttons lead to different pages. So, a proper analysis is required. Main idea of the article is to show you that increasing the number of threads for your test classes / spinning up VMs are not the only options to reduce the test execution time.

Happy Testing & Subscribe 🙂

 

Share This:

5 thoughts on “Selenium WebDriver – How To Improve Test Performance Using Java8 Stream

  1. Amazing article! Great work on putting up all the valuable information and articles in your blog, thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.