90

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?

3

12 Answers 12

54

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 
4
  • 5
    I had to use driver._client.set_header_overrides(headers=dict_headers) to solve my problem.CommentedFeb 1, 2019 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
    CommentedJul 1, 2022 at 12:50
  • selenium-wire is now archived and refuse to install on python3.12. I used this to check browser security on our apps, I look into forking it.
    – MortenB
    CommentedJan 15, 2024 at 12:11
  • 3
    The project is no longer maintained, and has 150+ open issues.
    – Yash Nag
    CommentedMay 18, 2024 at 6:09
53

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.

4
  • 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
    CommentedMar 28, 2013 at 4:56
  • If you create or use a browser specific plugin to add request headers, then you can automate the plugin testing as mentioned here - blazemeter.com/blog/…. 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
    CommentedApr 7, 2017 at 23:16
  • @FlorianWicher please check this: sqa.stackexchange.com/questions/37227/…CommentedApr 9, 2020 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.CommentedMay 1, 2023 at 10:51
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) 
2
  • 2
    However, this does not work with Firefox Quantum (57.+).
    – Dio
    CommentedAug 2, 2018 at 9:57
  • this extension should be updated according to new manifest to work with 57+ fox
    – d-d
    CommentedApr 5, 2019 at 11:31
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.

1
  • 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.CommentedOct 15, 2019 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} 
    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

      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.

        2

        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; } }); 
          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.

            0

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

            3
            • 1
              Hi, it sound great but I get this error: org.openqa.selenium.WebDriverException: <unknown>: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
              – Zoette
              CommentedMar 26, 2021 at 2:43
            • 1
              I also get the same Access Is Denied error. Has anyone figured this one out?
              – Ray
              CommentedJan 27, 2022 at 20:35
            • 1
              You may want to refer to docs.modheader.com/advanced/selenium-webdriver on how to download and use ModHeader in Selenium WebDriver.CommentedJan 10, 2023 at 14:39
            -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.

              4
              • 4
                this doesn't solve the problem or answer the question whatsoever.CommentedMay 31, 2019 at 19:06
              • 3
                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 dataCommentedApr 11, 2020 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!CommentedMar 4, 2021 at 8:52
              • @MihirVerma actually, no, no it doesn't. It helps, but it's not bulletproof. Things like Distil can still detect an automated browser. And with the new advent of the new Sec-CH-UA-*headers, 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.CommentedMar 22, 2022 at 21:34

              Start asking to get answers

              Find the answer to your question by asking.

              Ask question

              Explore related questions

              See similar questions with these tags.