class Rubium::Browser

Constants

MAX_CONNECT_WAIT_TIME
MAX_DEFAULT_TIMEOUT

Attributes

client[R]
devtools_url[R]
logger[R]
options[R]
pid[R]
port[R]
processed_requests_count[R]

Public Class Methods

new(options = {}) click to toggle source
# File lib/rubium/browser.rb, line 32
def initialize(options = {})
  @options = options

  if @options[:enable_logger]
    @logger = Logger.new(STDOUT)
    @logger.progname = self.class.to_s
  end

  create_browser
end
ports_pool() click to toggle source
# File lib/rubium/browser.rb, line 21
def ports_pool
  @pool ||= RandomPort::Pool.new
end
running_pids() click to toggle source
# File lib/rubium/browser.rb, line 25
def running_pids
  @running_pids ||= []
end

Public Instance Methods

body() click to toggle source
# File lib/rubium/browser.rb, line 97
def body
  response = @client.send_cmd "Runtime.evaluate", expression: 'document.documentElement.outerHTML'
  response.dig("result", "value")
end
click(selector) click to toggle source
# File lib/rubium/browser.rb, line 136
    def click(selector)
      @client.send_cmd "Runtime.evaluate", expression: <<~js
        document.querySelector("#{selector}").click();
      js
    end
close() click to toggle source
# File lib/rubium/browser.rb, line 50
def close
  if closed?
    logger.info "Browser already has been closed" if options[:enable_logger]
  else
    Process.kill("HUP", @pid)
    self.class.running_pids.delete(@pid)
    self.class.ports_pool.release(@port)

    FileUtils.rm_rf(@data_dir) if Dir.exist?(@data_dir)
    @closed = true

    logger.info "Closed browser" if options[:enable_logger]
  end
end
Also aliased as: destroy_driver!
closed?() click to toggle source
# File lib/rubium/browser.rb, line 67
def closed?
  @closed
end
cookies() click to toggle source
# File lib/rubium/browser.rb, line 163
def cookies
  response = @client.send_cmd "Network.getCookies"
  response["cookies"]
end
current_response() click to toggle source
# File lib/rubium/browser.rb, line 102
def current_response
  Nokogiri::HTML(body)
end
destroy_driver!()
Alias for: close
evaluate_on_new_document(script) click to toggle source
execute_script(script) click to toggle source
# File lib/rubium/browser.rb, line 181
def execute_script(script)
  @client.send_cmd "Runtime.evaluate", expression: script
end
fill_in(selector, text) click to toggle source
# File lib/rubium/browser.rb, line 175
    def fill_in(selector, text)
      execute_script <<~js
        document.querySelector("#{selector}").value = "#{text}"
      js
    end
goto(url, wait: options[:max_timeout] || MAX_DEFAULT_TIMEOUT) click to toggle source
# File lib/rubium/browser.rb, line 71
def goto(url, wait: options[:max_timeout] || MAX_DEFAULT_TIMEOUT)
  logger.info "Started request: #{url}" if options[:enable_logger]
  if options[:restart_after] && processed_requests_count >= options[:restart_after]
    restart!
  end

  response = @client.send_cmd "Page.navigate", url: url

  # By default, after Page.navigate we should wait till page will load completely
  # using Page.loadEventFired. But on some websites with Ajax navigation, Page.loadEventFired
  # will stuck forever. In this case you can provide `wait: false` option to skip waiting.
  if wait != false
    # https://chromedevtools.github.io/devtools-protocol/tot/Page#event-frameStoppedLoading
    Timeout.timeout(wait) do
      @client.wait_for do |event_name, event_params|
        event_name == "Page.frameStoppedLoading" && event_params["frameId"] == response["frameId"]
      end
    end
  end

  @processed_requests_count += 1
  logger.info "Finished request: #{url}" if options[:enable_logger]
end
Also aliased as: visit
has_css?(selector, wait: 0) click to toggle source
# File lib/rubium/browser.rb, line 116
def has_css?(selector, wait: 0)
  timer = 0
  until current_response.at_css(selector)
    return false if timer >= wait
    timer += 0.2 and sleep 0.2
  end

  true
end
has_text?(text, wait: 0) click to toggle source
# File lib/rubium/browser.rb, line 126
def has_text?(text, wait: 0)
  timer = 0
  until body&.include?(text)
    return false if timer >= wait
    timer += 0.2 and sleep 0.2
  end

  true
end
has_xpath?(path, wait: 0) click to toggle source
# File lib/rubium/browser.rb, line 106
def has_xpath?(path, wait: 0)
  timer = 0
  until current_response.at_xpath(path)
    return false if timer >= wait
    timer += 0.2 and sleep 0.2
  end

  true
end
restart!() click to toggle source
# File lib/rubium/browser.rb, line 43
def restart!
  logger.info "Restarting..." if options[:enable_logger]

  close
  create_browser
end
send_key_on(selector, key) click to toggle source

github.com/cyrus-and/chrome-remote-interface/issues/226#issuecomment-320247756 stackoverflow.com/a/18937620

# File lib/rubium/browser.rb, line 144
    def send_key_on(selector, key)
      @client.send_cmd "Runtime.evaluate", expression: <<~js
        document.querySelector("#{selector}").dispatchEvent(
          new KeyboardEvent("keydown", {
            bubbles: true, cancelable: true, keyCode: #{key}
          })
        );
      js
    end
set_cookies(cookies) click to toggle source

chromedevtools.github.io/devtools-protocol/tot/Network#method-setCookies

# File lib/rubium/browser.rb, line 169
def set_cookies(cookies)
  @client.send_cmd "Network.setCookies", cookies: cookies
end
visit(url, wait: options[:max_timeout] || MAX_DEFAULT_TIMEOUT)
Alias for: goto

Private Instance Methods

convert_proxy(proxy_string) click to toggle source
# File lib/rubium/browser.rb, line 260
def convert_proxy(proxy_string)
  ip, port, type, user, password = proxy_string.split(":")
  "#{type}://#{ip}:#{port}"
end
create_browser() click to toggle source
# File lib/rubium/browser.rb, line 187
def create_browser
  @processed_requests_count = 0

  @port = options[:debugging_port] || self.class.ports_pool.acquire
  @data_dir = "/tmp/rubium_profile_#{SecureRandom.hex}"

  chrome_path = Rubium.configuration.chrome_path ||
    Cliver.detect("chromium-browser") ||
    Cliver.detect("google-chrome")
  raise ConfigurationError, "Can't find chrome executable" unless chrome_path

  command = %W(
    #{chrome_path} about:blank
    --remote-debugging-port=#{@port}
    --user-data-dir=#{@data_dir}
  ) + DEFAULT_PUPPETEER_ARGS

  command << "--headless" if ENV["HEADLESS"] != "false" && options[:headless] != false
  command << "--window-size=#{options[:window_size].join(',')}" if options[:window_size]

  if options[:user_agent]
    user_agent = options[:user_agent].respond_to?(:call) ? options[:user_agent].call : options[:user_agent]
    command << "--user-agent=#{user_agent}"
  end

  if options[:proxy_server]
    proxy_server = options[:proxy_server].respond_to?(:call) ? options[:proxy_server].call : options[:proxy_server]
    proxy_server = convert_proxy(proxy_server) unless proxy_server.include?("://")
    command << "--proxy-server=#{proxy_server}"
  end

  @pid = spawn(*command, [:out, :err] => "/dev/null")
  self.class.running_pids << @pid
  @closed = false

  counter = 0
  begin
    counter += 0.2 and sleep 0.2
    @client = ChromeRemote.client(port: @port)
  rescue Errno::ECONNREFUSED => e
    counter < MAX_CONNECT_WAIT_TIME ? retry : raise(e)
  end

  @devtools_url = "http://localhost:#{@port}/"

  # https://github.com/GoogleChrome/puppeteer/blob/master/lib/Page.js
  @client.send_cmd "Target.setAutoAttach", autoAttach: true, waitForDebuggerOnStart: false
  @client.send_cmd "Network.enable"
  @client.send_cmd "Page.enable"

  evaluate_on_new_document(options[:extension_code]) if options[:extension_code]

  set_cookies(options[:cookies]) if options[:cookies]

  if options[:urls_blacklist] || options[:disable_images]
    urls = []

    if options[:urls_blacklist]
      urls += options[:urls_blacklist]
    end

    if options[:disable_images]
      urls += %w(jpg jpeg png gif swf svg tif).map { |ext| ["*.#{ext}", "*.#{ext}?*"] }.flatten
      urls << "data:image*"
    end

    @client.send_cmd "Network.setBlockedURLs", urls: urls
  end


  logger.info "Opened browser" if options[:enable_logger]
end