Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/rb/lib/selenium/server.rb
1864 views
1
# frozen_string_literal: true
2
3
# Licensed to the Software Freedom Conservancy (SFC) under one
4
# or more contributor license agreements. See the NOTICE file
5
# distributed with this work for additional information
6
# regarding copyright ownership. The SFC licenses this file
7
# to you under the Apache License, Version 2.0 (the
8
# "License"); you may not use this file except in compliance
9
# with the License. You may obtain a copy of the License at
10
#
11
# http://www.apache.org/licenses/LICENSE-2.0
12
#
13
# Unless required by applicable law or agreed to in writing,
14
# software distributed under the License is distributed on an
15
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
# KIND, either express or implied. See the License for the
17
# specific language governing permissions and limitations
18
# under the License.
19
20
require 'selenium/webdriver/common/child_process'
21
require 'selenium/webdriver/common/port_prober'
22
require 'selenium/webdriver/common/socket_poller'
23
require 'net/http'
24
25
module Selenium
26
#
27
# Wraps the remote server jar
28
#
29
# Usage:
30
#
31
# server = Selenium::Server.new('/path/to/selenium-server-standalone.jar')
32
# server.start
33
#
34
# Automatically download the given version:
35
#
36
# server = Selenium::Server.get '2.6.0'
37
# server.start
38
#
39
# or the latest version:
40
#
41
# server = Selenium::Server.get :latest
42
# server.start
43
#
44
# Run the server in the background:
45
#
46
# server = Selenium::Server.new(jar, :background => true)
47
# server.start
48
#
49
# Add additional arguments:
50
#
51
# server = Selenium::Server.new(jar)
52
# server << ["--additional", "args"]
53
# server.start
54
#
55
56
class Server
57
class Error < StandardError; end
58
59
CL_RESET = WebDriver::Platform.windows? ? '' : "\r\e[0K"
60
61
class << self
62
#
63
# Download the given version of the selenium-server jar and return instance
64
#
65
# @param [String, Symbol] required_version X.Y.Z defaults to ':latest'
66
# @param [Hash] opts
67
# @return [Selenium::Server]
68
#
69
70
def get(required_version = :latest, opts = {})
71
new(download(required_version), opts)
72
end
73
74
#
75
# Download the given version of the selenium-server jar and return location
76
#
77
# @param [String, Symbol] required_version X.Y.Z defaults to ':latest'
78
# @return [String] location of downloaded file
79
#
80
81
def download(required_version = :latest)
82
required_version = latest if required_version == :latest
83
download_file_name = "selenium-server-#{required_version}.jar"
84
85
return download_file_name if File.exist? download_file_name
86
87
begin
88
download_location = available_assets[download_file_name]['browser_download_url']
89
released = Net::HTTP.get_response(URI.parse(download_location))
90
redirected = URI.parse released.header['location']
91
92
File.open(download_file_name, 'wb') do |destination|
93
download_server(redirected, destination)
94
end
95
rescue StandardError
96
FileUtils.rm_rf download_file_name
97
raise
98
end
99
100
download_file_name
101
end
102
103
#
104
# Ask GitHub what the latest selenium-server version is.
105
#
106
107
def latest
108
@latest ||= begin
109
available = available_assets.keys.map { |key| key[/selenium-server-(\d+\.\d+\.\d+)\.jar/, 1] }
110
available.map { |asset| Gem::Version.new(asset) }.max.to_s
111
end
112
end
113
114
# @api private
115
116
def available_assets
117
@available_assets ||= net_http_start('api.github.com') do |http|
118
json = http.get('/repos/seleniumhq/selenium/releases').body
119
all_assets = JSON.parse(json).map { |release| release['assets'] }.flatten
120
server_assets = all_assets.select { |asset| asset['name'].match(/selenium-server-(\d+\.\d+\.\d+)\.jar/) }
121
server_assets.each_with_object({}) { |asset, hash| hash[asset.delete('name')] = asset }
122
end
123
end
124
125
def net_http_start(address, &block)
126
http_proxy = ENV.fetch('http_proxy', nil) || ENV.fetch('HTTP_PROXY', nil)
127
if http_proxy
128
http_proxy = "http://#{http_proxy}" unless http_proxy.start_with?('http://')
129
uri = URI.parse(http_proxy)
130
131
Net::HTTP.start(address, nil, uri.host, uri.port, &block)
132
else
133
Net::HTTP.start(address, use_ssl: true, &block)
134
end
135
end
136
137
def download_server(uri, destination)
138
net_http_start('github-releases.githubusercontent.com') do |http|
139
request = Net::HTTP::Get.new uri
140
resp = http.request(request) do |response|
141
total = response.content_length
142
progress = 0
143
segment_count = 0
144
145
response.read_body do |segment|
146
progress += segment.length
147
segment_count += 1
148
149
if (segment_count % 15).zero?
150
percent = progress.fdiv(total) * 100
151
print "#{CL_RESET}Downloading #{destination.path}: #{percent.to_i}% (#{progress} / #{total})"
152
segment_count = 0
153
end
154
155
destination.write(segment)
156
end
157
end
158
159
raise Error, "#{resp.code} for #{destination.path}" unless resp.is_a? Net::HTTPSuccess
160
end
161
end
162
end
163
164
#
165
# The Mode of the Server
166
# :standalone, #hub, #node
167
#
168
169
attr_accessor :role, :host, :port, :timeout, :background, :log
170
171
#
172
# @param [String] jar Path to the server jar.
173
# @param [Hash] opts the options to create the server process with
174
#
175
# @option opts [Integer] :port Port the server should listen on (default: 4444).
176
# @option opts [Integer] :timeout Seconds to wait for server launch/shutdown (default: 30)
177
# @option opts [true,false] :background Run the server in the background (default: false)
178
# @option opts [true,false,String] :log Either a path to a log file,
179
# or true to pass server log to stdout.
180
# @raise [Errno::ENOENT] if the jar file does not exist
181
#
182
183
def initialize(jar, opts = {})
184
raise Errno::ENOENT, jar unless File.exist?(jar)
185
186
@java = opts.fetch(:java, 'java') || 'java'
187
@jar = jar
188
@host = '127.0.0.1'
189
@role = opts.fetch(:role, 'standalone')
190
@port = opts.fetch(:port, WebDriver::PortProber.above(4444))
191
@timeout = opts.fetch(:timeout, 30)
192
@background = opts.fetch(:background, false)
193
@additional_args = opts.fetch(:args, [])
194
@log = opts[:log]
195
if opts[:log_level]
196
@log ||= true
197
@additional_args << '--log-level'
198
@additional_args << opts[:log_level].to_s
199
end
200
201
@log_file = nil
202
end
203
204
def start
205
process.start
206
poll_for_service
207
208
process.wait unless @background
209
end
210
211
def stop
212
stop_process if @process
213
poll_for_shutdown
214
215
@log_file&.close
216
end
217
218
def webdriver_url
219
"http://#{@host}:#{@port}/wd/hub"
220
end
221
222
def <<(arg)
223
if arg.is_a?(Array)
224
@additional_args += arg
225
else
226
@additional_args << arg.to_s
227
end
228
end
229
230
private
231
232
def stop_process
233
@process.stop
234
rescue Errno::ECHILD
235
# already dead
236
ensure
237
@process = nil
238
end
239
240
def process
241
@process ||= begin
242
# extract any additional_args that start with -D as options
243
properties = @additional_args.dup - @additional_args.delete_if { |arg| arg[/^-D/] }
244
args = ['-jar', @jar, @role, '--port', @port.to_s]
245
server_command = [@java] + properties + args + @additional_args
246
cp = WebDriver::ChildProcess.build(*server_command)
247
248
if @log.is_a?(String)
249
cp.io = @log
250
elsif @log
251
cp.io = :out
252
end
253
254
cp.detach = @background
255
256
cp
257
end
258
end
259
260
def poll_for_service
261
return if socket.connected?
262
263
raise Error, "remote server not launched in #{@timeout} seconds"
264
end
265
266
def poll_for_shutdown
267
return if socket.closed?
268
269
raise Error, "remote server not stopped in #{@timeout} seconds"
270
end
271
272
def socket
273
@socket ||= WebDriver::SocketPoller.new(@host, @port, @timeout)
274
end
275
end # Server
276
end # Selenium
277
278