Sometimes in our automated tests in Selenium we need to test a page not just for some functionality (how proper the response for the user action is) but also for whether the resources of a page have been loaded properly. For example we might need to make sure the server returns proper response for the following resources:
-
images
-
video files
-
CSS files
-
JavaScript files
-
etc
The approach we are going to review here can also be useful when we run automated test of some AJAX page that tries to perform asynchronous requests but receives error from the server. In such case the result of a failure won’t necessarily be visible on UI so having the knowledge you will obtain after reading this article will allow you to combine the methods in order to increase the efficiency of your tests.
There are few points we are going to cover below:
Let’s now take a closer look at them.
Example description: make Selenium automated test fail if an image failed to load
In our example we’re prepare two html pages: one that will be having a valid reference to a picture and another one that will not. Two pages are to be used in order to demonstrate that the test that will be testing a valid page will pass successfully. The second test will obviously have to fail.
Below you can see the code of both the pages.
I suggest you to create a dedicated folder for keeping the files from this tutorial.
Page that has valid picture
<html> <head/> <body> <image src="https://webelement.click/assets/images/ru.png"/> <image src="https://webelement.click/assets/images/en.png"/> </body> </html>
Page that has broken picture
<html> <head/> <body> <image src="https://webelement.click/assets/images/ru.png"/> <image src="https://webelement.click/assets/images/eng.png"/> </body> </html>
Save the first page as good.html
and the second one as bad.html
(later in the article I assume that we put our pages to /tmp
folder - consider this when you adopt the example to your environment).
General description of the test method
Unfortunately Selenium by itself cannot (in most of the cases) detect if anything goes wrong on http(s) request level. Thus you will only know that some resource has not been properly loaded if you intentionally look up the element containing a resource and check certain properties of it. However the latter might still not work in all the cases.
The more robust and convenient method would be to intercept the responses which are sent by the browser at the moment of processing your page resources. This can be easily done with using a controllable proxy like BrowserMob-Proxy. The easiest way of making your code access BrowserMob-Proxy functionality is adding a dependency to your pom.xml
file (you have to use Maven of course). Below is the dependency that I use in my example:
<dependency> <groupId>net.lightbody.bmp</groupId> <artifactId>browsermob-core</artifactId> <version>2.1.5</version> <scope>test</scope> </dependency>
Having your WebDriver configured to use such proxy you will be able to monitor the responses that the server sends for the requests coming from the browser under your automated test control.
Another example of your Selenium automated tests (written in Java) integration with BrowserMob-Proxy lib you can find in my article were I show how to modify HTTP headers of the requests sent by browsers under Selenium control.
Implementing the test
Below you can find the complete example of the test that we’ll discuss in details right after the code snippet finishes. The boilerplate part assumes that we are using JUnit5 testing framework however the same can be implemented with TestNG or with pure main()
approach.
package click.webelement.monitorrequests; import net.lightbody.bmp.BrowserMobProxy; import net.lightbody.bmp.BrowserMobProxyServer; import net.lightbody.bmp.client.ClientUtil; import org.junit.jupiter.api.*; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxOptions; import java.util.ArrayList; import java.util.List; public class MonitorRequestsTest { WebDriver driver; static BrowserMobProxy proxy; static List<String> failedURIs = new ArrayList<>(); @BeforeAll public static void globalSetup(){ System.setProperty("webdriver.gecko.driver" , "/home/alexey/Desktop/Dev/webdrivers/geckodriver"); proxy = new BrowserMobProxyServer(); proxy.start(0); proxy.addResponseFilter((httpResponse, httpMessageContents, httpMessageInfo) -> { if(httpMessageInfo.getOriginalRequest().headers().get("Accept").contains("image/")){ int responseCode = httpResponse.getStatus().code(); if(responseCode >= 400 && responseCode < 600){ failedURIs.add(httpMessageInfo.getOriginalRequest().getUri()); } } }); } @BeforeEach public void setUp(){ FirefoxOptions ffOptions = new FirefoxOptions(); ffOptions.setProxy(ClientUtil.createSeleniumProxy(proxy)); driver = new FirefoxDriver(ffOptions); failedURIs.clear(); } @Test public void testGoodPage(){ driver.get("file:///tmp/good.html"); } @Test public void testBadPage(){ driver.get("file:///tmp/bad.html"); } @AfterEach public void tearDown(){ if(driver != null){ driver.quit(); } if(!failedURIs.isEmpty()){ Assertions.fail("There were resource loading issues " + "during the test " + "for the following resources: " + failedURIs.toString()); } } @AfterAll public static void globalTearDown(){ if(proxy != null){ proxy.stop(); } } }
Let’s now break this code down into pieces to get the method idea clearly. As you could already notice, except of the test methods which are really straightforward, we have four blocks here. They are @BeforeAll
that is running once before all the tests, @BeforeEach
that is running before each test, @AfterEach
that is running after each test and @AfterAll
that is executed once all the tests have completed.
Configure global set-up in @BeforeAll
This method brings the core idea to our tests. Here we start up the proxy that does not make sense to recreate for each new test (that is why we set it up in @BeforeAll
section) and add a listener that would be listening the responses. Let’s look at this part:
proxy.addResponseFilter((httpResponse, httpMessageContents, httpMessageInfo) -> { if(httpMessageInfo.getOriginalRequest().headers().get("Accept").contains("image/")){ int responseCode = httpResponse.getStatus().code(); if(responseCode >= 400 && responseCode < 600){ failedURIs.add(httpMessageInfo.getOriginalRequest().getUri()); } } });
Using addResponseFilter
method you can process all the responses in order to (for example) modify them in any aspect. In our particular case we are going to use it to look for a specific response codes return in reply to requests from our automated tests. But first of all we need to limit (actually I need - you are free to implement your own logic) the checks with image resources. There are few ways of how we can decide if the request asked a server for an image. I prefer to parse Accept
header of the request. Thus the browser informs a server that it expects to see the image in response.
After we have made sure we are processing a response returning an image we obtain the response code and check if it falls in the range from 400
(included) to 600
(excluded). This is how we detect if the request was not successful (codes like 4**
mean that the error seems to originate on client side, codes like 5**
point to some server-side issue).
The last thing we do in this block is adding the requested URI to the List field that is assumed to contain all the problematic requests. We’ll see how to deal with this list later.
Configure per-test set-up in @BeforeEach
Typical thing that is done in "before each" block is recreating a WebDriver. I adhere that practice in my example. However there is one more thing that we should stop at to look closer. I’m talking about this line:
failedURIs.clear();
Before each test we clean up the list that is used for holding problematic URIs so that the errors captured on previous tests would not impact the next tests.
Configure post-test polishing in @AfterEach
In this design the tests themselves are written in a regular Selenium manner like if we would not have the requirement to test particular HTTP requests. They might fail due to typical Selenium reasons like unavailability of the element or other UI issues.
After the test has finished JUnit5 executes @AfterEach
block that checks if there were problematic resources detected during the test run:
if(!failedURIs.isEmpty()){ Assertions.fail("There were resource loading issues " + "during the test " + "for the following resources: " + failedURIs.toString()); }
If the list is not empty by the end of the test run then the test is set forcibly failed. You should also notice that this section:
if(driver != null){ driver.quit(); }
goes first. It is important because otherwise the failed test would cause code interruption and WebDriver won’t be ever disposed.
Configure global tear down with @AfterAll and run the tests
The last thing we need to do is to release our proxy so that it is stopped and it is not holding the port any more. After these last lines we can execute our tests. Below is the output that the code should produce:
org.opentest4j.AssertionFailedError: There were resource loading issues during the test for the following resources: [/assets/images/eng.png] at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:39) at org.junit.jupiter.api.Assertions.fail(Assertions.java:109) at click.webelement.monitorrequests.MonitorRequestsTest.tearDown(MonitorRequestsTest.java:60) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Using this approach you’ll be able to add any sort of HTTP-response validation to your Selenium automated tests. If you still have the questions please send them to me using this form. I will amend the article according to your feedback.