class Selenium::WebDriver::DevTools

Constants

RESPONSE_WAIT_INTERVAL
RESPONSE_WAIT_TIMEOUT

Public Class Methods

new(url:) click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 33
def initialize(url:)
  @callback_threads = ThreadGroup.new

  @messages = []
  @session_id = nil
  @url = url

  process_handshake
  @socket_thread = attach_socket_listener
  start_session
end

Public Instance Methods

callbacks() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 51
def callbacks
  @callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }
end
close() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 45
def close
  @callback_threads.list.each(&:exit)
  @socket_thread.exit
  socket.close
end
method_missing(method, *_args) click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 74
def method_missing(method, *_args)
  desired_class = "Selenium::DevTools::V#{Selenium::DevTools.version}::#{method.capitalize}"
  return unless Object.const_defined?(desired_class)

  self.class.class_eval do
    define_method(method) do
      Object.const_get(desired_class).new(self)
    end
  end

  send(method)
end
respond_to_missing?(method, *_args) click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 87
def respond_to_missing?(method, *_args)
  desired_class = "Selenium::DevTools::V#{Selenium::DevTools.version}::#{method.capitalize}"
  Object.const_defined?(desired_class)
end
send_cmd(method, **params) click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 55
def send_cmd(method, **params)
  id = next_id
  data = {id: id, method: method, params: params.reject { |_, v| v.nil? }}
  data[:sessionId] = @session_id if @session_id
  data = JSON.generate(data)
  WebDriver.logger.debug "DevTools -> #{data}"

  out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
  socket.write(out_frame.to_s)

  message = wait.until do
    @messages.find { |m| m['id'] == id }
  end

  raise Error::WebDriverError, error_message(message['error']) if message['error']

  message
end

Private Instance Methods

attach_socket_listener() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 99
def attach_socket_listener
  Thread.new do
    Thread.current.abort_on_exception = true
    Thread.current.report_on_exception = false

    until socket.eof?
      incoming_frame << socket.readpartial(1024)

      while (frame = incoming_frame.next)
        message = process_frame(frame)
        next unless message['method']

        params = message['params']
        callbacks[message['method']].each do |callback|
          @callback_threads.add(callback_thread(params, &callback))
        end
      end
    end
  end
end
callback_thread(params) { |params| ... } click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 144
def callback_thread(params)
  Thread.new do
    Thread.current.abort_on_exception = true

    # We might end up blocked forever when we have an error in event.
    # For example, if network interception event raises error,
    # the browser will keep waiting for the request to be proceeded
    # before returning back to the original thread. In this case,
    # we should at least print the error.
    Thread.current.report_on_exception = true

    yield params
  end
end
error_message(error) click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 187
def error_message(error)
  [error['code'], error['message'], error['data']].join(': ')
end
incoming_frame() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 127
def incoming_frame
  @incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
end
next_id() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 182
def next_id
  @id ||= 0
  @id += 1
end
process_frame(frame) click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 131
def process_frame(frame)
  message = frame.to_s

  # Firefox will periodically fail on unparsable empty frame
  return {} if message.empty?

  message = JSON.parse(message)
  @messages << message
  WebDriver.logger.debug "DevTools <- #{message}"

  message
end
process_handshake() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 94
def process_handshake
  socket.print(ws.to_s)
  ws << socket.readpartial(1024)
end
socket() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 163
def socket
  @socket ||= begin
    if URI(@url).scheme == 'wss'
      socket = TCPSocket.new(ws.host, ws.port)
      socket = OpenSSL::SSL::SSLSocket.new(socket, OpenSSL::SSL::SSLContext.new)
      socket.sync_close = true
      socket.connect

      socket
    else
      TCPSocket.new(ws.host, ws.port)
    end
  end
end
start_session() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 120
def start_session
  targets = target.get_targets.dig('result', 'targetInfos')
  page_target = targets.find { |target| target['type'] == 'page' }
  session = target.attach_to_target(target_id: page_target['targetId'], flatten: true)
  @session_id = session.dig('result', 'sessionId')
end
wait() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 159
def wait
  @wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
end
ws() click to toggle source
# File lib/selenium/webdriver/devtools.rb, line 178
def ws
  @ws ||= WebSocket::Handshake::Client.new(url: @url)
end