class Capybara::Apparition::Browser::Launcher::Local

Constants

DEFAULT_BOOLEAN_OPTIONS

Chromium command line options peter.sh/experiments/chromium-command-line-switches/

DEFAULT_OPTIONS
DEFAULT_VALUE_OPTIONS

Note: –no-sandbox is not needed if you properly setup a user in the container. github.com/ebidel/lighthouse-ci/blob/master/builder/Dockerfile#L35-L40 no-sandbox disable-web-security

HEADLESS_OPTIONS
KILL_TIMEOUT

Public Class Methods

new(headless:, **options) click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 36
def initialize(headless:, **options)
  @path = ENV['BROWSER_PATH']
  @options = DEFAULT_OPTIONS.merge(options[:browser_options] || {})
  if headless
    @options.merge!(HEADLESS_OPTIONS)
    @options['disable-gpu'] = nil if Capybara::Apparition.windows?
  end
  @options['user-data-dir'] = Dir.mktmpdir
end
process_killer(pid) click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 15
def self.process_killer(pid)
  proc do
    sleep 1
    if Capybara::Apparition.windows?
      ::Process.kill('KILL', pid)
    else
      ::Process.kill('USR1', pid)
      timer = Capybara::Helpers.timer(expire_in: KILL_TIMEOUT)
      while ::Process.wait(pid, ::Process::WNOHANG).nil?
        sleep 0.05
        next unless timer.expired?

        ::Process.kill('KILL', pid)
        ::Process.wait(pid)
        break
      end
    end
  rescue Errno::ESRCH, Errno::ECHILD # rubocop:disable Lint/SuppressedException
  end
end
start(*args, **options) click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 11
def self.start(*args, **options)
  new(*args, **options).tap(&:start)
end

Public Instance Methods

host() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 79
def host
  @host ||= ws_url.host
end
port() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 83
def port
  @port ||= ws_url.port
end
restart() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 74
def restart
  stop
  start
end
start() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 46
def start
  @output = Queue.new

  process_options = {}
  process_options[:pgroup] = true unless Capybara::Apparition.windows?
  cmd = [path] + @options.map { |k, v| v.nil? ? "--#{k}" : "--#{k}=#{v}" }

  stdin, @stdout_stderr, wait_thr = Open3.popen2e(*cmd, process_options)
  stdin.close

  @pid = wait_thr.pid

  @out_thread = Thread.new do
    while !@stdout_stderr.eof? && (data = @stdout_stderr.readpartial(512))
      @output << data
    end
  end

  ObjectSpace.define_finalizer(self, self.class.process_killer(@pid))
end
stop() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 67
def stop
  return unless @pid

  kill
  ObjectSpace.undefine_finalizer(self)
end
ws_url() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 87
def ws_url
  @ws_url ||= begin
    regexp = %r{DevTools listening on (ws://.*)}
    url = nil

    sleep 3
    loop do
      break if (url = @output.pop.scan(regexp)[0])
    end
    @out_thread.kill
    @out_thread.join # wait for thread to end before closing io
    close_io
    Addressable::URI.parse(url[0])
  end
end

Private Instance Methods

close_io() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 110
def close_io
  @stdout_stderr.close unless @stdout_stderr.closed?
rescue IOError
  raise unless RUBY_ENGINE == 'jruby'
end
find_first_binary(*binaries) click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 159
def find_first_binary(*binaries)
  paths = ENV['PATH'].split(File::PATH_SEPARATOR)

  binaries.product(paths).lazy.map do |(binary, path)|
    Dir.glob(File.join(path, binary)).find { |f| File.executable?(f) }
  end.reject(&:nil?).first
end
kill() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 105
def kill
  self.class.process_killer(@pid).call
  @pid = nil
end
linux_path() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 151
def linux_path
  directories = %w[/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin /opt/google/chrome]
  files = %w[google-chrome chrome chromium chromium-browser]

  directories.product(files).map { |p| p.join('/') }.find { |f| File.exist?(f) } ||
    find_first_binary(*files)
end
macosx_path() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 143
def macosx_path
  directories = ['', File.expand_path('~')]
  files = ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
           '/Applications/Chromium.app/Contents/MacOS/Chromium']
  directories.product(files).map(&:join).find { |f| File.exist?(f) } ||
    find_first_binary('Google Chrome', 'Chromium')
end
path() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 116
def path
  host_os = RbConfig::CONFIG['host_os']
  @path ||= case RbConfig::CONFIG['host_os']
  when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
    windows_path
  when /darwin|mac os/
    macosx_path
  when /linux|solaris|bsd/
    linux_path
  else
    raise ArgumentError, "unknown os: #{host_os.inspect}"
  end

  raise ArgumentError, 'Unable to find Chrome executeable' unless File.file?(@path.to_s) && File.executable?(@path.to_s)

  @path
end
windows_path() click to toggle source
# File lib/capybara/apparition/browser/launcher/local.rb, line 134
def windows_path
  envs = %w[LOCALAPPDATA PROGRAMFILES PROGRAMFILES(X86)]
  directories = %w[\\Google\\Chrome\\Application \\Chromium\\Application]
  files = %w[chrome.exe]

  directories.product(envs, files).lazy.map { |(dir, env, file)| "#{ENV[env]}\\#{dir}\\#{file}" }
             .find { |f| File.exist?(f) } || find_first_binary(*files)
end