84

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?

Andrei Botalov
  • 20,686
  • 11
  • 89
  • 123
tamouse
  • 2,169
  • 1
  • 19
  • 26
  • Possible duplicates: [Headers and Selenium Webdriver 2](http://stackoverflow.com/q/13047195/841064), [How to send an http RequestHeader using Selenium 2?](http://stackoverflow.com/q/6478672/841064) – Andrei Botalov Mar 27 '13 at 08:17
  • Another possible duplicate: [How to add headers or parameters to an HTTP request handled with Selenium Webdriver?](http://stackoverflow.com/q/10426860/841064) – Andrei Botalov Mar 27 '13 at 08:27
  • 1
    Thanks, Andrey. I wonder why these weren't offered up when I was searching... – tamouse Mar 28 '13 at 04:53

12 Answers12

51

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.

Nek
  • 2,715
  • 1
  • 20
  • 34
Andrei Botalov
  • 20,686
  • 11
  • 89
  • 123
  • 1
    I have been using poltergeist to set the Referer, which does accomplish the goal for testing. I wanted to also use selenium with a head in order to display it working for those interested. – tamouse Mar 28 '13 at 04:56
  • If you create or use a browser specific plugin to add request headers, then you can automate the plugin testing as mentioned here - https://www.blazemeter.com/blog/6-easy-steps-testing-your-chrome-extension-selenium. That way you can automate setting up the plugin with headers before your test code begins. However, this approach does not seem to be easy. – MasterJoe Apr 07 '17 at 23:16
  • @FlorianWicher please check this: https://sqa.stackexchange.com/questions/37227/how-to-modify-http-request-header-in-selenium-webdriver-with-java – Pawel Nieradka Apr 09 '20 at 10:16
  • I posted a response below, but I've been able to do this in c# by intercepting network traffic with the web driver and modifying the headers. – Shawn Palmer May 01 '23 at 10:51
51

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
Will Keeling
  • 22,055
  • 4
  • 51
  • 61
  • 4
    I had to use `driver._client.set_header_overrides(headers=dict_headers)` to solve my problem. – Kartikey Singh Feb 01 '19 at 18:58
  • @KartikeySingh it is not good to use private members. They can change between bug fix versions so a requirement ~=1.1.1 will not work – GuiTaek Jul 01 '22 at 12:50
9

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)
4

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.

HargrimmTheBleak
  • 2,147
  • 1
  • 19
  • 19
  • Beware of `cattr_accessor :headers` — it will preserve headers between tests, so you might need to flush it in the middleware: `@helper.heades = nil`, otherwise be ready to have fun debugging flaky tests later on. – Yuriy Kharchenko Oct 15 '19 at 10:30
2

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}
Glenn
  • 57
  • 8
2

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

Nafees Nehar
  • 71
  • 1
  • 7
2

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.

Kewin Remy
  • 501
  • 4
  • 12
1

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.

John P
  • 11
  • 2
1

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);
  1. 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
  1. 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");

Praveen
  • 1,387
  • 1
  • 12
  • 22
  • 1
    Hi, it sound great but I get this error: `org.openqa.selenium.WebDriverException: : Failed to read the 'localStorage' property from 'Window': Access is denied for this document.` – Zoette Mar 26 '21 at 02:43
  • 1
    I also get the same Access Is Denied error. Has anyone figured this one out? – Ray Jan 27 '22 at 20:35
  • 1
    You may want to refer to https://docs.modheader.com/advanced/selenium-webdriver on how to download and use ModHeader in Selenium WebDriver. – Hao Nguyen Jan 10 '23 at 14:39
0

In c# selenium, an option is to use IWebDriver and add network interceptors that appropriately update the headers.

driver.Manage().Network.AddRequestHandler(new NetworkRequestHandler()
{
  RequestMatcher = (u) => true,
  RequestTransformer = (u) =>
  {
    u.Headers["my-header"] = "some-value";
    return u;
  }
});
Shawn Palmer
  • 321
  • 3
  • 5
-1

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.

-3

If you just need to set the User-Agent header, there is an option for Chrome:

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"')

Now the browser sends User-Agent.

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277
Jame
  • 63
  • 1
  • 5
  • 4
    this doesn't solve the problem or answer the question whatsoever. – Corey Goldberg May 31 '19 at 19:06
  • 2
    I dont know if it answered the asked questions or not. But it did solve my problem of passing user agent in header when using headless driver. This is need in some sites I guess that require such header to stop scrapping data – user3046442 Apr 11 '20 at 11:14
  • 2
    Thank you so much! This tricks the site into thinking that headless browser is infact a normal browser with a header. This solves my issue! – Mihir Verma Mar 04 '21 at 08:52
  • @MihirVerma actually, no, no it doesn't. It helps, but it's not bulletproof. Things like [Distil](https://stackoverflow.com/a/54984891/16051077) can still detect an automated browser. And with the new advent of the new `Sec-CH-UA-*` [headers](https://chromestatus.com/feature/5995832180473856), there will be a huge mismatch since both those headers and the user-agent is currently still being sent. All overriding the user-agent string does is remove the word `headless` from it. – DataMinion Mar 22 '22 at 21:34