require 'selenium/webdriver/common/child_process'
require 'selenium/webdriver/common/port_prober'
require 'selenium/webdriver/common/socket_poller'
require 'net/http'
module Selenium
class Server
class Error < StandardError; end
CL_RESET = WebDriver::Platform.windows? ? '' : "\r\e[0K"
class << self
def get(required_version = :latest, opts = {})
new(download(required_version), opts)
end
def download(required_version = :latest)
required_version = latest if required_version == :latest
download_file_name = "selenium-server-#{required_version}.jar"
return download_file_name if File.exist? download_file_name
begin
download_location = available_assets[download_file_name]['browser_download_url']
released = Net::HTTP.get_response(URI.parse(download_location))
redirected = URI.parse released.header['location']
File.open(download_file_name, 'wb') do |destination|
download_server(redirected, destination)
end
rescue StandardError
FileUtils.rm_rf download_file_name
raise
end
download_file_name
end
def latest
@latest ||= begin
available = available_assets.keys.map { |key| key[/selenium-server-(\d+\.\d+\.\d+)\.jar/, 1] }
available.map { |asset| Gem::Version.new(asset) }.max.to_s
end
end
def available_assets
@available_assets ||= net_http_start('api.github.com') do |http|
json = http.get('/repos/seleniumhq/selenium/releases').body
all_assets = JSON.parse(json).map { |release| release['assets'] }.flatten
server_assets = all_assets.select { |asset| asset['name'].match(/selenium-server-(\d+\.\d+\.\d+)\.jar/) }
server_assets.each_with_object({}) { |asset, hash| hash[asset.delete('name')] = asset }
end
end
def net_http_start(address, &block)
http_proxy = ENV.fetch('http_proxy', nil) || ENV.fetch('HTTP_PROXY', nil)
if http_proxy
http_proxy = "http://#{http_proxy}" unless http_proxy.start_with?('http://')
uri = URI.parse(http_proxy)
Net::HTTP.start(address, nil, uri.host, uri.port, &block)
else
Net::HTTP.start(address, use_ssl: true, &block)
end
end
def download_server(uri, destination)
net_http_start('github-releases.githubusercontent.com') do |http|
request = Net::HTTP::Get.new uri
resp = http.request(request) do |response|
total = response.content_length
progress = 0
segment_count = 0
response.read_body do |segment|
progress += segment.length
segment_count += 1
if (segment_count % 15).zero?
percent = progress.fdiv(total) * 100
print "#{CL_RESET}Downloading #{destination.path}: #{percent.to_i}% (#{progress} / #{total})"
segment_count = 0
end
destination.write(segment)
end
end
raise Error, "#{resp.code} for #{destination.path}" unless resp.is_a? Net::HTTPSuccess
end
end
end
attr_accessor :role, :host, :port, :timeout, :background, :log
def initialize(jar, opts = {})
raise Errno::ENOENT, jar unless File.exist?(jar)
@java = opts.fetch(:java, 'java') || 'java'
@jar = jar
@host = '127.0.0.1'
@role = opts.fetch(:role, 'standalone')
@port = opts.fetch(:port, WebDriver::PortProber.above(4444))
@timeout = opts.fetch(:timeout, 30)
@background = opts.fetch(:background, false)
@additional_args = opts.fetch(:args, [])
@log = opts[:log]
if opts[:log_level]
@log ||= true
@additional_args << '--log-level'
@additional_args << opts[:log_level].to_s
end
@log_file = nil
end
def start
process.start
poll_for_service
process.wait unless @background
end
def stop
stop_process if @process
poll_for_shutdown
@log_file&.close
end
def webdriver_url
"http://#{@host}:#{@port}/wd/hub"
end
def <<(arg)
if arg.is_a?(Array)
@additional_args += arg
else
@additional_args << arg.to_s
end
end
private
def stop_process
@process.stop
rescue Errno::ECHILD
ensure
@process = nil
end
def process
@process ||= begin
properties = @additional_args.dup - @additional_args.delete_if { |arg| arg[/^-D/] }
args = ['-jar', @jar, @role, '--port', @port.to_s]
server_command = [@java] + properties + args + @additional_args
cp = WebDriver::ChildProcess.build(*server_command)
if @log.is_a?(String)
cp.io = @log
elsif @log
cp.io = :out
end
cp.detach = @background
cp
end
end
def poll_for_service
return if socket.connected?
raise Error, "remote server not launched in #{@timeout} seconds"
end
def poll_for_shutdown
return if socket.closed?
raise Error, "remote server not stopped in #{@timeout} seconds"
end
def socket
@socket ||= WebDriver::SocketPoller.new(@host, @port, @timeout)
end
end
end