Simulate network connection problem for AJAX controls in Selenium WebDriver tests with BrowserMob-Proxy in Java

Sometimes, especially when you test AJAX applications, you have to test how your application (or particular controls on the page) would handle network connection issues. There are several ways how one can simulate such issue in their Selenium WebDriver automated tests. As far as Selenium itself does not have utility to impact the traffic from web browser I am going to show you how to apply BrowserMob-Proxy tool in order to address such the need. Below are the links to article anchor points so that you can skip what you already know.

Let’s now look at everything in more details.

Example description: asynchronous call from a page when the network connection is broken

Assume that you have a page that has an asynchronous control that is expected to behave in a certain way if the network connection has been broken. And we need to test it of course. Here I have prepared a very simplified example of such UI. There is a button that invokes a call to this site and tests if there are any problems with the response.

We’re going to implement Java test using WebDriver (in my case it is GeckoDriver but it does not really matter), Selenium and BrowserMob-Proxy libraries. That test would visit the page, check that the page detects connection online, then simulate breaking the network connection and tests that the page properly handles this (it detects connection offline). The last thing the test will do is bringing everything back and checking if everything works as in the beginning.

I won’t be using any test execution framework like JUnit or TestNG. To simplify the example I will start everything in main method.

How asynchronous UI is implemented in this example

There is a button on the page. That button has assigned handler that processes click events. The code of the handler is shown below.

function test() {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4) {
      if(this.status == 200){
        document.getElementById("result").innerHTML = 'Connection OK!'
      }else{
        document.getElementById("result").innerHTML = 'Connection NOT OK!'
      }
    }
  };
  xhttp.open("GET", "/en/welcome");
  xhttp.send();
}

Basically it waits for the request status becomes COMPLETED and checks for the proper status. Hence once the request has completed and status is anything but 200 the connection is considered broken.

Maven dependencies

Here is how your dependencies section should look like in your pom.xml file.

<dependencies>
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>3.141.59</version>
    </dependency>
    <dependency>
        <groupId>net.lightbody.bmp</groupId>
        <artifactId>browsermob-core</artifactId>
        <version>2.1.5</version>
    </dependency>
</dependencies>

Since we’re not going to explicitly involve any other libraries, this list will be enough for our tutorial.

Test fish-bone

Below you can see 80% of the test that I have prepared:

package click.webelement.connection;

import net.lightbody.bmp.BrowserMobProxy;
import net.lightbody.bmp.BrowserMobProxyServer;
import net.lightbody.bmp.client.ClientUtil;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import java.time.Duration;
import java.util.concurrent.TimeUnit;

public class TestBrokenConnection {

    WebDriver driver;
    BrowserMobProxy proxy;
    // PLACEHOLDER 1

    public TestBrokenConnection(){
        System.setProperty("webdriver.gecko.driver"
                , "/PATH_TO_WEBDRIVER/geckodriver");
        proxy = new BrowserMobProxyServer();
        proxy.start(0);
        // PLACEHOLDER 2
        FirefoxOptions ffOptions = new FirefoxOptions();
        ffOptions.setProxy(ClientUtil.createSeleniumProxy(proxy));
        driver = new FirefoxDriver(ffOptions);
    }

    private void test(){
        By CON_OK = By.xpath("//div[@id='result'][text()='Connection OK!']");
        By CON_NOK = By.xpath("//div[@id='result'][text()='Connection NOT OK!']");
        Wait<WebDriver> wait = new FluentWait<>(driver)
                .ignoring(NoSuchElementException.class)
                .withTimeout(Duration.ofSeconds(5));
        try{
            driver.get("https://webelement.click/stand/connection?lang=en");
            driver.findElement(By.tagName("button")).click();
            wait.until(ExpectedConditions.visibilityOfElementLocated(CON_OK));
            enableConnectivityIssue();
            driver.findElement(By.tagName("button")).click();
            wait.until(ExpectedConditions.visibilityOfElementLocated(CON_NOK));
            disableConnectivityIssue();
            driver.findElement(By.tagName("button")).click();
            wait.until(ExpectedConditions.visibilityOfElementLocated(CON_OK));
        }catch (Throwable e){
            System.err.println("Something wrong has happened");
            e.printStackTrace();
        }finally {
            driver.quit();
            proxy.stop();
        }
    }

    private void enableConnectivityIssue(){
        // PLACEHOLDER 3
    }

    private void disableConnectivityIssue(){
        // PLACEHOLDER 4
    }

    public static void main(String[] args){
        new TestBrokenConnection().test();
        System.out.println("End of test");
    }

}

Let’s briefly look at what is going on there.. So the core of the test is private void test() method. The logic is straightforward. It just checks that the connection is okay, then calls the method that is to spoil the connection, then tests if connection is not okay, then calls the method that restores the connection and again test that the connection is okay. Methods enableConnectivityIssue() and disableConnectivityIssue() are the points of our interest.

Another important place is the constructor of the class. We use it in order to initialize the state of our test. There we configure the driver and start BrowserMob-Proxy server.

You can also notice that there are several placeholders there in the code. Later in the post we’ll look at several ways of how we can address our objective. To save the space I’ll be just showing which code is to replace which placeholder.

Why this task is not really trivial

Before we start looking at possible solutions it is worth getting aware of what could be the pitfalls of such test and why this solution is not really trivial. First of all Selenium cannot block or modify any traffic coming from or to the browser which is acting under Selenium control. So the solution would be to use Java proxy that would be passing all the traffic through itself and then try to impact that proxy in different ways to spoil browser-to-server connectivity.

So there are three possible ways we’re going to try:

  1. Easy but not really reliable way - modify status code of server response

  2. Moderate and reliable way 1 - spoil domain resolving mechanism

  3. Moderate and reliable way 2 - configure SecurityManager in order to prohibit using sockets to connect to certain hosts

Approach #1: Modify response status code using ResponseFilter

First approach is really simple and less tricky. You should use it if you are sure it is relevant to your case. If your control implementation is based on response status check this might work well for you. In this example we’re going to add a ResponseFilter to our proxy that will be modifying the status of server response before it arrives to the web browser.

Pros

  • This approach is less tricky

Cons

  • This approach simulates unsuccessful server response. It is far not the same thing as the network problem between a client and a server. However for some controls you can come up with this approach.

  • You have to be aware of how your control is implemented.

Implementation

1) Add boolean badConnection; instead of // PLACEHOLDER 1. We’ll be using this flag within a filter to understand when we do need to modify response status and when we do not.

2) Replace // PLACEHOLDER 2 with the following code:

proxy.addResponseFilter((response, contents, messageInfo) -> {
    if(messageInfo.getOriginalUrl().contains("en/welcome") && badConnection){
        response.setStatus(HttpResponseStatus.BAD_GATEWAY);
    }
});

Here we introduce a filter that checks if the request is sent to a specific URI and if the connection has to be spoiled. If both are true, then it modifies the response with new status.

3) Replace // PLACEHOLDER 3 with badConnection = true;

4) Replace // PLACEHOLDER 4 with badConnection = false;

So this is it.

Approach #2: Corrupt host name resolving mechanism

In the second approach we’re going to corrupt and then restore back the mechanism of how BrowserMob-Proxy resolves the host names to ip addresses. This approach is more tricky since here we involve TCP specifics. The thing is that the connections which your web-browser opens are re-used for the sake of efficiency. So once been established, they are no more sensitive to host name resolving issues or any other issues which might take the place on connection establishing phase. This is why we need to apply some workarounds. Here and in the third approach.

Pros

  • More reliable since it simulates the failure of intermediate network infrastructure

Cons

  • This might not work if you do not use host names but the IP addresses in your URLs.

Implementation

1) Replace // PLACEHOLDER 1 with AdvancedHostResolver defaultResolver;. We’re going to use this field to preserve the default resolver so that we’ll be able to restore it later on.

2) Replace // PLACEHOLDER 2 with:

proxy.setIdleConnectionTimeout(1, TimeUnit.SECONDS);
defaultResolver = proxy.getHostNameResolver();

where proxy.setIdleConnectionTimeout(1, TimeUnit.SECONDS); is a sort of workaround/hack which makes the connections (remember that browser re-uses the connections) get closed by proxy after 1 second of inactivity (this makes new connection attempt to resolve the host name and get into our trap). Here defaultResolver = proxy.getHostNameResolver(); we also preserve the default resolver so that we will be able to restore it later.

3) Replace // PLACEHOLDER 3 with the following code:

proxy.setHostNameResolver(new BasicHostResolver() {
    @Override
    public Collection<InetAddress> resolve(String host) {
        return null;
    }
});
try {
    Thread.sleep(1500);
} catch (InterruptedException e) {
    e.printStackTrace();
}

Here we are doing two things. First we set new resolver for our proxy. That resolver has overridden resolve(String host) method. It just returns null so any attempt to resolve the host would obviously fail. Another thing is that we make our thread sleep for a bit more than a second. We do this in order to wait for idle connections get killed so that new connections will have to attempt to resolve the host names.

4) Replace // PLACEHOLDER 4 with:

proxy.setIdleConnectionTimeout(60, TimeUnit.SECONDS);
proxy.setHostNameResolver(defaultResolver);

so that the default host name resolver and idle connection timeout are restored.

Approach #3: Configure SecurityManager in order to block the sockets

The last approach we’re going to discuss in this post implies using Java security mechanism - a SecurityManager. Code of Java standard libraries widely uses this mechanism to authorize the actions. Hence we can prohibit the socket connections to certain hosts. As far as BrowserMob-Proxy is running in the same JVM as the test code and uses Java networking harness, by prohibiting the socket connection we can block the traffic from the proxy to the target web site.

Here we face the same pitfall as in previous approach - re-using opened connections so that we will have to apply the same workaround.

Pros

  • Reliable method that acts on very basic level and simulates network infrastructure problem.

Cons

  • Despite your project does not likely use SecurityManager (as by default) there is still the probability that it does. In such the way you will have to integrate your permission logic into existing one.

  • This approach is not really flexible. You have to consider even more pitfalls than in previous one.

Implementation

1) Placeholder // PLACEHOLDER 1 won’t be used in this example. So start from replacing // PLACEHOLDER 2 with:

proxy.setIdleConnectionTimeout(1, TimeUnit.SECONDS);

This will address the same problem we were addressing in previous example (workaround for killing the connections in order to force your page to create new ones).

2) Replace // PLACEHOLDER 2 with the following code:

SecurityManager securityManager = new SecurityManager(){
    @Override
    public void checkConnect(String host, int port) {
        if(!"localhost".equals(host)){
            throw new AccessControlException("Blocked for test purpose.");
        }
    }

    @Override
    public void checkPermission(Permission perm) {

    }
};
System.setSecurityManager(securityManager);
try {
    Thread.sleep(1500);
} catch (InterruptedException e) {
    e.printStackTrace();
}

In this listing we set up new security manager. We override the method that is used for checking if the connection to a certain place is permitted or not. We exclude localhost from that check since localhost is used by your Selenium library in order to connect to your WebDriver. We also override the method that is used to check the arbitrary permission so that it has no logic and all the actions which are being authorized through that method will be considered permitted.

Another thing is pausing your code for a moment sufficient for making idle connections established from your browser get killed (much like in previous approach).

3) Replace // PLACEHOLDER 3 with the following code:

proxy.setIdleConnectionTimeout(60, TimeUnit.SECONDS);
System.setSecurityManager(null);

Here we restore default security manager (that did not exist so that it would be null) and bring back default setting for idle connection timeout.

So, these are the approaches I’d like to tell you about. You can choose the one fitting your particular case or combine them in order to get more flexibility. If you still have the questions please send them to me using this form. I will amend the article according to your feedback.