module Selenium::WebDriver::DriverExtensions::HasNetworkInterception

Public Instance Methods

intercept(&block) click to toggle source

Intercepts requests coming from browser allowing to either pass them through like proxy or provide a stubbed response instead.

@example Log requests and pass through

driver.intercept do |request, &continue|
  puts "#{request.method} #{request.url}"
  continue.call(request)
end

@example Stub requests for images

driver.intercept do |request, &continue|
  if request.url.match?(/\.png$/)
    request.url = 'https://upload.wikimedia.org/wikipedia/commons/d/d5/Selenium_Logo.png'
  end
  continue.call(request)
end

@example Log responses and pass through

driver.intercept do |request, &continue|
  continue.call(request) do |response|
    puts "#{response.code} #{response.body}"
  end
end

@example Mutate specific response

driver.intercept do |request, &continue|
  continue.call(request) do |response|
    response.body << 'Added by Selenium!' if request.url.include?('/myurl')
  end
end

@param [Proc] block which is called when request is intercepted @yieldparam [DevTools::Request] request @yieldparam [Proc] continue block which proceeds with the request and optionally yields response

# File lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb, line 63
def intercept(&block)
  devtools.network.set_cache_disabled(cache_disabled: true)
  devtools.fetch.on(:request_paused) do |params|
    id = params['requestId']
    if params.key?('responseStatusCode') || params.key?('responseErrorReason')
      intercept_response(id, params, &pending_response_requests.delete(id))
    else
      intercept_request(id, params, &block)
    end
  end
  devtools.fetch.enable(patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}])
end

Private Instance Methods

fetch_response_body(id) click to toggle source
# File lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb, line 127
def fetch_response_body(id)
  devtools.fetch.get_response_body(request_id: id).dig('result', 'body')
rescue Error::WebDriverError
  # CDP fails to get body on certain responses (301) and raises:
  # Can only get response body on requests captured after headers received.
end
intercept_request(id, params, &block) click to toggle source
# File lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb, line 82
def intercept_request(id, params, &block)
  original = DevTools::Request.from(id, params)
  mutable = DevTools::Request.from(id, params)

  block.call(mutable) do |&continue| # rubocop:disable Performance/RedundantBlockCall
    pending_response_requests[id] = continue

    if original == mutable
      devtools.fetch.continue_request(request_id: id)
    else
      devtools.fetch.continue_request(
        request_id: id,
        url: mutable.url,
        method: mutable.method,
        post_data: mutable.post_data,
        headers: mutable.headers.map do |k, v|
          {name: k, value: v}
        end
      )
    end
  end
end
intercept_response(id, params) { |mutable| ... } click to toggle source
# File lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb, line 105
def intercept_response(id, params)
  return devtools.fetch.continue_request(request_id: id) unless block_given?

  body = fetch_response_body(id)
  original = DevTools::Response.from(id, body, params)
  mutable = DevTools::Response.from(id, body, params)
  yield mutable

  if original == mutable
    devtools.fetch.continue_request(request_id: id)
  else
    devtools.fetch.fulfill_request(
      request_id: id,
      body: (Base64.strict_encode64(mutable.body) if mutable.body),
      response_code: mutable.code,
      response_headers: mutable.headers.map do |k, v|
        {name: k, value: v}
      end
    )
  end
end
pending_response_requests() click to toggle source
# File lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb, line 78
def pending_response_requests
  @pending_response_requests ||= {}
end