Selenium modifies the first three methods of HTTP requests

One of the test automation challenges is to modify the request header in Selenium WebDriver. I'll share how to modify HTTP request headers using Selenium WebDriver.

What is an HTTP request header

HTTP request header is an important part of HTTP protocol. They define HTTP messages (requests or responses) and allow clients and servers to exchange optional metadata with messages. They consist of case insensitive header field names followed by a colon, followed by header field values. The title field can be extended to multiple rows by having at least one space or horizontal tab before each additional row.

Headings can be grouped according to their context:

  • Request header: the HTTP request header is used to provide additional information about the resource being acquired and the client making the request.
  • Response header: the HTTP response header provides information about the response.

The following is the main information contained in the HTTP request header:

  • IP address (source) and port number.
  • The URL of the requested web page.
  • Web server or target site (host).
  • The type of data that the browser will accept (text, html, xml, etc.).
  • The type of browser that sends compatible data (Mozilla, Chrome, IE).
  • As a response, the HTTP response header containing the request data is sent back by.

The HTTP request header needs to be changed

The following are some scenarios where the HTTP request header may need to be changed in the test work:

  • Test control and different versions by establishing appropriate HTTP request headers.
  • Situations where different aspects of a Web application or even server logic need to be thoroughly tested.
  • Since the HTTP request header is used to enable some specific parts of the Web application logic, which are usually disabled in normal mode, it may be necessary to modify the HTTP request header from time to time according to the test scenario.

Testing the guest mode on the tested Web application is a situation where the tester may need to modify the HTTP request header. However, the function of modifying HTTP request headers once supported by Selenium RC is not handled by Selenium Webdriver.

Selenium modify request header

There are many ways to modify the request header in Selenium Java. In general, there are several possibilities, and then you can modify the header request in the Java selenium project.

  • Use the Java HTTP request framework.
  • Use reverse proxy.
  • Use Firefox browser extensions.

Java HTTP request framework

Together with Selenium, we can use REST Assured, which is a great tool for using REST services in a simple way. The REST Assured tutorial for configuring a project is very simple and will not be introduced here.

Let's consider the following scenario:

  • We have a Java class called RequestHeaderChangeDemo in which we maintain the basic configuration
  • We have a test step file called TestSteps, where we will call the RequestHeaderChangeDemo Java class method, through which we will execute our test.

Observe the following Java class named RequestHeaderChangeDemo.

BASE_ URLs are websites that apply the following four methods:

  • Authenticated user
  • Get products
  • Add product
  • Remove product
    public class RequestHeaderChangeDemo {

        private static final String BASE_URL = "https://****";

        public static IRestResponse<Token> authenticateUser(AuthorizationRequest authRequest) {

            RestAssured.baseURI = BASE_URL;
            RequestSpecification request = RestAssured.given();
            request.header("Content-Type", "application/json");

            Response response = request.body(authRequest).post(Route.generateToken());
            return new RestResponse(Token.class, response);
        }

        Some duplicate codes are omitted here

    }

In the above Java class file, we repeatedly send base in each successive method_ URLs and headers. Examples are as follows:

RestAssured.baseURI = BASE_URL;
RequestSpecification request = RestAssured.given(); 
request.header("Content-Type", "application/json");
Response response = request.body(authRequest).post(Route.generateToken());

The request.header method requests the request header in JSON format. There is a lot of code duplication, which reduces the maintainability of the code. This can be avoided if we initialize the RequestSpecification Object in the constructor and make these methods non static (that is, create instance methods). Because the instance method in Java belongs to the Object of the class rather than the class itself, it can be called even after the Object of the class is created. At the same time, we will override the instance method.

Converting a method to an instance method has the following advantages:

  • Authentication occurs only once in a RequestSpecification object. You no longer need to create the same request for other requests.
  • Flexibly modify the request header in the project.

So let's look at the Java class RequestHeaderChangeDemo and the test step file TestSteps when we use the instance method.

Java class of RequestHeaderChangeDemo class with instance method

    public class RequestHeaderChangeDemo {

        private final RequestSpecification request;

        public RequestHeaderChangeDemo(String baseUrl) {
            RestAssured.baseURI = baseUrl;
            request = RestAssured.given();
            request.header("Content-Type", "application/json");
        }

        public void authenticateUser(AuthorizationRequest authRequest) {
            Response response = request.body(authRequest).post(Route.generateToken());
            if (response.statusCode() != HttpStatus.SC_OK)
                throw new RuntimeException("Authentication Failed. Content of failed Response: " + response.toString() + " , Status Code : " + response.statusCode());

            Token tokenResponse = response.body().jsonPath().getObject("$", Token.class);
            request.header("Authorization", "Bearer " + tokenResponse.token);
        }

        public IRestResponse<Products> getProducts() {
            Response response = request.get(Route.products());
            return new RestResponse(Products.class, response);
        }
        Some codes are omitted here
    }

Code practice

  • We created a constructor to initialize the RequestSpecification object containing the BaseURL and the request header.
  • Earlier, we had to pass tokens in each request header. Now, once we receive the token response in the method authenticateUser(), we put it in the same instance of the request. This enables the execution of the test step to move forward without adding a token for each request as before. This makes the request header available for subsequent calls to the server.
  • The RequestHeaderChangeDemo Java class will now be initialized in the TestSteps file.

We change the TestSteps file according to the changes in the RequestHeaderChangeDemo Java class.

public class TestSteps
{
    private final String USER_ID = "";    
    private Response response;
    private IRestResponse<UserAccount> userAccountResponse;
    private Product product;
    private final String BaseUrl = "https://******";
    private RequestHeaderChangeDemo endPoints;
    
    @Given("^User is authorized$")
    public void authorizedUser()
    {
        endPoints = new RequestHeaderChangeDemo (BaseUrl);
        AuthorizationRequest authRequest = new AuthorizationRequest("(Username)", "(Password)");
        endPoints.authenticateUser(authRequest);
    }
 
    @Given("^Available Product List$")
    public void availableProductLists() 
    {       
        IRestResponse<Products> productsResponse = endPoints.getProducts();
        Product = productsResponse.getBody().products.get(0);
    }
 
    @When("^Adding the Product in Wishlist$")

    {
        ADDPROD code = new ADDPROD(product.code);
        AddProductsRequest addProductsRequest = new AddProductsRequest(USER_ID, code);
        userAccountResponse = endPoints.addProduct(addProductsRequest);
    }
 

}

This is what we did in the modified implementation:

  • Initialize the RequestHeaderChangeDemo class object as the endpoint.
  • The BaseURL is passed in the first method, that is, authorized user.
  • In the method authorizedUser, we called the constructor authenticateUser of the RequestHeaderChangeDemo class.
  • Therefore, subsequent steps define the same endpoint object.

Use reverse proxy

As the name implies, we can choose to use a proxy when processing request header changes in the Java Selenium automation test suite. Since Selenium prohibits injecting information into browsers and servers, it can be processed using a proxy. This method is not preferred if the test is performed behind the corporate firewall.

As a component of the Web infrastructure, the proxy enables Web traffic to pass through it by positioning itself between the client and the server. The agent works in a similar way, allowing traffic to pass through it, allowing safe traffic to pass through and preventing potential threats. Agents have the ability to partially or completely modify requests and responses.

The core idea is to send the authorization request header to bypass the stage containing the credential dialog, also known as the basic authentication dialog. However, it turns out to be a tiring process, especially when test cases need to be reconfigured frequently.

This is where the browser mob proxy library comes into play. Let's see how to use the browser mob proxy with a sample web site protected with basic authentication. To solve this problem, we may narrow down two possible methods:

  • Add authorization request headers to all requests without conditions or exceptions.
  • Add request headers only to requests that meet certain criteria.

Although we will not solve the request header management problem, we will demonstrate how to solve the authorization problem with the help of the browser mob proxy authorization tool set. In this part of Selenium Java tutorial, we will only show the first method (adding authorization request headers to all requests).

First, we add the dependency of browsermob proxy in pom.xml

    <dependencies>
        <dependency>
            <groupId>net.lightbody.bmp</groupId>
            <artifactId>browsermob-core</artifactId>
            <version>2.1.5</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Then you need to make some changes in the code:

public class caseFirstTest
{
    WebDriver driver;
    BrowserMobProxy proxy;
 
    @BeforeAll
    public static void globalSetup()
    {
        System.setProperty("webdriver.gecko.driver", "(path of the driver)");
    }
 
    @BeforeEach
    public void setUp()
    {
        setUpProxy();
        FirefoxOptions Options = new FirefoxOptions();
        Options.setProxy(ClientUtil.createSeleniumProxy(proxy));
        driver = new FirefoxDriver(Options);
    }
 
    @Test
    public void testBasicAuth()
    {
        driver.get("https://webelement.click/stand/basic?lang=en");
        Wait<WebDriver> waiter = new FluentWait(driver).withTimeout(Duration.ofSeconds(50)).ignoring(NoSuchElementException.class);
        String greetings = waiter.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("(Mention the xpath)"))).getText();
        Assertions.assertEquals("(message");
    }
 
    @AfterEach
    public void tearDown()
    {
        if(driver != null)
        {
            driver.quit();
        }
        if(proxy != null)
        {
            proxy.stop();
        }
    }
    private void setUpProxy(
    {
    }
}

If you want to pass this method to all request header requests, that is, a specific proxy, in this case, you should call the forAllProxy method, as follows:

public void forAllProxy()
{
    proxy = new BrowserMobProxyServer();
    try {
        String authHeader = "Basic " + Base64.getEncoder().encodeToString("webelement:click".getBytes("utf-8"));
        proxy.addHeader("checkauth", authfirstHeader);
    }
    catch (UnsupportedEncodingException e)
    {
        System.err.println("the Authorization can not be passed");
        e.printStackTrace();
    }
    proxy.start(0);
}

In the above code, the line starting with authHeader indicates that we are creating a request header, which will be added to the request. After that, these requests will be passed through the proxy we created in proxy.addHeader("checkauth", authfirstHeader).

try {
        String authHeader = "Basic " + Base64.getEncoder().encodeToString("webelement:click".getBytes("utf-8"));
        proxy.addHeader("checkauth", authfirstHeader);
    }
    catch (UnsupportedEncodingException e)
    {
        ........................
    }
    proxy.start(0);
}

Finally, we start the agent, set 0 to mark the start parameter, and the agent starts on the port.

Using Firefox extensions

Here's how to modify the request header using the appropriate Firefox browser extension. The main disadvantage of this option is that it is only applicable to Firefox (not Chrome, Edge and other browsers). It is rarely tested with Firefox now. Learn it briefly.

Perform the following steps to modify the HTTP request header using the Firefox extension:

  • Download Firefox browser extensions
  • Load extension.
  • Set extension preferences.
  • Set the required functions.
  • Prepare test automation scripts.

Let's step by step:

Download Firefox browser extensions

Solve it yourself.

Load Firefox extensions

Refer to the following code to add a Firefox configuration file:

FirefoxProfile profile = new FirefoxProfile();
File modifyHeaders = new File(System.getProperty("user.dir") + "/resources/modify_headers.xpi");
profile.setEnableNativeEvents(false); 
 
try {
    profile.addExtension(modifyHeaders); 
}
catch (IOException e)
{
    e.printStackTrace();
}

Set extension preferences

Once we load the Firefox extension into the project, we set preferences (that is, the various inputs we need to set before triggering the extension). This is done using the profile.setPreference method.

This method sets preferences for any given profile through the keyset parameter mechanism. The first parameter here is the key to set the value, and the second parameter sets the corresponding integer value.

This is a reference implementation:

profile.setPreference("modifyheaders.headers.count", 1);
profile.setPreference("modifyheaders.headers.action0", "Add");
profile.setPreference("modifyheaders.headers.name0", "Value");
profile.setPreference("modifyheaders.headers.value0", "numeric value");
profile.setPreference("modifyheaders.headers.enabled0", true);
profile.setPreference("modifyheaders.config.active", true);
profile.setPreference("modifyheaders.config.alwaysOn", true);

In the above code, we list the number of times we want to set the header instance.

profile.setPreference("modifyheaders.headers.count", 1);

Next, we specify the operation, and the request header name and request header value contain the values dynamically received from the API call.

profile.setPreference("modifyheaders.headers.action0", "Add");

For the rest of the implementation, we enable all so that it allows the extension to be loaded when the Firefox browser is instantiated by the WebDriver and set the extension to active mode using the HTTP request header.

Set the required functions

Desired Capabilities in Selenium is used to set the browser, browser version and platform type that need to perform automated tests.

Here, how to set the required functions:

DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName("firefox");
capabilities.setPlatform(org.openqa.selenium.Platform.ANY);
capabilities.setCapability(FirefoxDriver.PROFILE, profile);
 
WebDriver driver = new FirefoxDriver(capabilities);
driver.get("url");

Complete automation use case

After completing all the above steps, we will continue to design the whole test automation script:

public void startwebsite()
{
    FirefoxProfile profile = new FirefoxProfile();
    File modifyHeaders = new File(System.getProperty("user.dir") + "/resources/modify_headers.xpi");
    profile.setEnableNativeEvents(false); 
    try
    {
        profile.addExtension(modifyHeaders); 
    }
    catch (IOException e)
    {
        e.printStackTrace(); 
    }
 
    profile.setPreference("modifyheaders.headers.count", 1);
    profile.setPreference("modifyheaders.headers.action0", "Add");
    profile.setPreference("modifyheaders.headers.name0", "Value");
    profile.setPreference("modifyheaders.headers.value0", "Numeric Value");
    profile.setPreference("modifyheaders.headers.enabled0", true);
    profile.setPreference("modifyheaders.config.active", true);
    profile.setPreference("modifyheaders.config.alwaysOn", true);
 
    DesiredCapabilities capabilities = new DesiredCapabilities();
    capabilities.setBrowserName("firefox");
    capabilities.setPlatform(org.openqa.selenium.Platform.ANY);
    capabilities.setCapability(FirefoxDriver.PROFILE, profile);
 
    WebDriver driver = new FirefoxDriver(capabilities);
    driver.get("url");
}

Posted by shoebappa on Fri, 19 Nov 2021 01:07:51 -0800