setting request headers in selenium

WebdriverCapybaraSelenium Webdriver

Webdriver Problem Overview


I'm attempting to set the request header 'Referer' to spoof a request coming from another site. We need the ability test that a specific referrer is used, which returns a specific form to the user, otherwise an alternative form is given.

I can do this within poltergeist by:

page.driver.headers = {"Referer" => referer_string}

but I can't find the equivalent functionality for the selemium driver.

How can I set request headers in the capybara selenium driver?

Webdriver Solutions


Solution 1 - Webdriver

Webdriver doesn't contain an API to do it. See issue 141 from Selenium tracker for more info. The title of the issue says that it's about response headers but it was decided that Selenium won't contain API for request headers in scope of this issue. Several issues about adding API to set request headers have been marked as duplicates: first, second, third.

Here are a couple of possibilities that I can propose:

  1. Use another driver/library instead of selenium
  2. Write a browser-specific plugin (or find an existing one) that allows you to add header for request.
  3. Use browsermob-proxy or some other proxy.

I'd go with option 3 in most of cases. It's not hard.

Note that Ghostdriver has an API for it but it's not supported by other drivers.

Solution 2 - Webdriver

For those people using Python, you may consider using Selenium Wire which can set request headers as well as provide you with the ability to inspect requests and responses.

from seleniumwire import webdriver  # Import from seleniumwire

# Create a new instance of the Chrome driver (or Firefox)
driver = webdriver.Chrome()

# Create a request interceptor
def interceptor(request):
    del request.headers['Referer']  # Delete the header first
    request.headers['Referer'] = 'some_referer'

# Set the interceptor on the driver
driver.request_interceptor = interceptor

# All requests will now use 'some_referer' for the referer
driver.get('https://mysite')

Install with:

pip install selenium-wire

Solution 3 - Webdriver

I had the same issue. I solved it downloading modify-headers firefox add-on and activate it with selenium.

The code in python is the following

fp = webdriver.FirefoxProfile()
path_modify_header = 'C:/xxxxxxx/modify_headers-0.7.1.1-fx.xpi'
fp.add_extension(path_modify_header)

fp.set_preference("modifyheaders.headers.count", 1)
fp.set_preference("modifyheaders.headers.action0", "Add")
fp.set_preference("modifyheaders.headers.name0", "Name_of_header") # Set here the name of the header
fp.set_preference("modifyheaders.headers.value0", "value_of_header") # Set here the value of the header
fp.set_preference("modifyheaders.headers.enabled0", True)
fp.set_preference("modifyheaders.config.active", True)
fp.set_preference("modifyheaders.config.alwaysOn", True)

driver = webdriver.Firefox(firefox_profile=fp)

Solution 4 - Webdriver

Had the same issue today, except that I needed to set different referer per test. I ended up using a middleware and a class to pass headers to it. Thought I'd share (or maybe there's a cleaner solution?):

lib/request_headers.rb:

class CustomHeadersHelper
  cattr_accessor :headers
end

class RequestHeaders
  def initialize(app, helper = nil)
    @app, @helper = app, helper
  end

  def call(env)
    if @helper
      headers = @helper.headers

      if headers.is_a?(Hash)
        headers.each do |k,v|
          env["HTTP_#{k.upcase.gsub("-", "_")}"] = v
        end
      end
    end

    @app.call(env)
  end
end

config/initializers/middleware.rb

require 'request_headers'

if %w(test cucumber).include?(Rails.env)
  Rails.application.config.middleware.insert_before Rack::Lock, "RequestHeaders", CustomHeadersHelper
end

spec/support/capybara_headers.rb

require 'request_headers'

module CapybaraHeaderHelpers
  shared_context "navigating within the site" do
    before(:each) { add_headers("Referer" => Capybara.app_host + "/") }
  end

  def add_headers(custom_headers)
    if Capybara.current_driver == :rack_test
      custom_headers.each do |name, value|
        page.driver.browser.header(name, value)
      end
    else
      CustomHeadersHelper.headers = custom_headers
    end
  end
end

spec/spec_helper.rb

...
config.include CapybaraHeaderHelpers

Then I can include the shared context wherever I need, or pass different headers in another before block. I haven't tested it with anything other than Selenium and RackTest, but it should be transparent, as header injection is done before the request actually hits the application.

Solution 5 - Webdriver

I wanted something a bit slimmer for RSpec/Ruby so that the custom code only had to live in one place. Here's my solution:

/spec/support/selenium.rb
...
RSpec.configure do |config|
  config.after(:suite) do
    $custom_headers = nil
  end
end

module RequestWithExtraHeaders
  def headers
    $custom_headers.each do |key, value|
      self.set_header "HTTP_#{key}", value
    end if $custom_headers

    super
  end
end
class ActionDispatch::Request
  prepend RequestWithExtraHeaders
end

Then in my specs:

/specs/features/something_spec.rb
...
$custom_headers = {"Referer" => referer_string}

Solution 6 - Webdriver

If you are using javacsript and only want to implement on chrome, Puppeteer is the best option as it has native support to modify headers. Check this out: https://pptr.dev/#?product=Puppeteer&version=v10.1.0&show=api-pagesetextrahttpheadersheaders

Although for cross-browser usage you might check out @requestly/selenium npm package. It is a wrapper around requestly extension to enable easy integration in selenium-webdriver.The extension can modify headers. Check out: https://www.npmjs.com/package/@requestly/selenium

Solution 7 - Webdriver

Setting request headers in the web driver directly does not work. This is true.

However, you can work around this problem by using the browser devtools (I tested with edge & chrome) and this works perfectly.

According to the documentation, you have the possibility to add custom headers: https://chromedevtools.github.io/devtools-protocol/tot/Network/

Please find below an example.

    [Test]
    public async Task AuthenticatedRequest()
    {
        await LogMessage("=== starting the test ===");

        EdgeOptions options = new EdgeOptions {UseChromium = true};
        options.AddArgument("no-sandbox");
        var driver = new RemoteWebDriver(new Uri(_testsSettings.GridUrl), options.ToCapabilities(), TimeSpan.FromMinutes(3));

        //Get DevTools
        IDevTools devTools = driver;

        //DevTools Session
        var session = devTools.GetDevToolsSession();

        var devToolsSession = session.GetVersionSpecificDomains<DevToolsSessionDomains>();
        await devToolsSession.Network.Enable(new Network.EnableCommandSettings());
        
        var extraHeader = new Network.Headers();
        var data = await Base64KerberosTicket();
        var headerValue = $"Negotiate {data}";
        
        await LogMessage($"header values is {headerValue}");

        extraHeader.Add("Authorization", headerValue);
        await devToolsSession.Network.SetExtraHTTPHeaders(new Network.SetExtraHTTPHeadersCommandSettings
        {
            Headers = extraHeader
        });

        driver.Url = _testsSettings.TestUrl;

        driver.Navigate();
        driver.Quit();

        await LogMessage("=== ending the test ===");
    }

This is an example written in C# but the same shall probably work with java, python as well as the major platforms.

Hope it helps the community.

Solution 8 - Webdriver

If you use the HtmlUnitDriver, you can set request headers by modifying the WebClient, like so:

final case class Header(name: String, value: String)

final class HtmlUnitDriverWithHeaders(headers: Seq[Header]) extends HtmlUnitDriver {
  super.modifyWebClient {
    val client = super.getWebClient
    headers.foreach(h => client.addRequestHeader(h.name, h.value))
    client
  }
}

The headers will then be on all requests made by the web browser.

Solution 9 - Webdriver

With the solutions already discussed above the most reliable one is using Browsermob-Proxy

But while working with the remote grid machine, Browsermob-proxy isn't really helpful.

This is how I fixed the problem in my case. Hopefully, might be helpful for anyone with a similar setup.

  1. Add the ModHeader extension to the chrome browser

How to download the Modheader? Link

ChromeOptions options = new ChromeOptions();
options.addExtensions(new File(C://Downloads//modheader//modheader.crx));

// Set the Desired capabilities 
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(ChromeOptions.CAPABILITY, options);

// Instantiate the chrome driver with capabilities
WebDriver driver = new RemoteWebDriver(new URL(YOUR_HUB_URL), options);

2. Go to the browser extensions and capture the Local Storage context ID of the ModHeader

Capture ID from ModHeader

  1. Navigate to the URL of the ModHeader to set the Local Storage Context

.

// set the context on the extension so the localStorage can be accessed
driver.get("chrome-extension://idgpnmonknjnojddfkpgkljpfnnfcklj/_generated_background_page.html");

Where `idgpnmonknjnojddfkpgkljpfnnfcklj` is the value captured from the Step# 2

4. Now add the headers to the request using Javascript

.

   ((Javascript)driver).executeScript(
         "localStorage.setItem('profiles', JSON.stringify([{  title: 'Selenium', hideComment: true, appendMode: '', 
             headers: [                        
               {enabled: true, name: 'token-1', value: 'value-1', comment: ''},
               {enabled: true, name: 'token-2', value: 'value-2', comment: ''}
             ],                          
             respHeaders: [],
             filters: []
          }]));");

Where token-1, value-1, token-2, value-2 are the request headers and values that are to be added.

  1. Now navigate to the required web-application.

    driver.get("your-desired-website");

Solution 10 - Webdriver

You can do it with PhantomJSDriver.

PhantomJSDriver pd = ((PhantomJSDriver) ((WebDriverFacade) getDriver()).getProxiedDriver());
pd.executePhantomJS(
            "this.onResourceRequested = function(request, net) {" +
            "   net.setHeader('header-name', 'header-value')" +
            "};");

Using the request object, you can filter also so the header won't be set for every request.

Solution 11 - Webdriver

check this out: chrome_options = Options()

chrome_options.add_argument('--headless')
chrome_options.add_argument('user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36"')

Problem solved!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontamouseView Question on Stackoverflow
Solution 1 - WebdriverAndrei BotalovView Answer on Stackoverflow
Solution 2 - WebdriverWill KeelingView Answer on Stackoverflow
Solution 3 - WebdriverFernando MarengoView Answer on Stackoverflow
Solution 4 - WebdriverHargrimmTheBleakView Answer on Stackoverflow
Solution 5 - WebdriverGlennView Answer on Stackoverflow
Solution 6 - WebdriverNafees NeharView Answer on Stackoverflow
Solution 7 - WebdriverKewin RemyView Answer on Stackoverflow
Solution 8 - WebdriverJohn PView Answer on Stackoverflow
Solution 9 - WebdriverPraveenView Answer on Stackoverflow
Solution 10 - Webdriverstijn van crombruggeView Answer on Stackoverflow
Solution 11 - WebdriverJameView Answer on Stackoverflow