Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/rb/lib/selenium/webdriver/common/service_manager.rb
4062 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
module Selenium
21
module WebDriver
22
#
23
# Base class implementing default behavior of service_manager object,
24
# responsible for starting and stopping driver implementations.
25
#
26
# @api private
27
#
28
class ServiceManager
29
START_TIMEOUT = 20
30
SOCKET_LOCK_TIMEOUT = 45
31
STOP_TIMEOUT = 20
32
33
#
34
# End users should use a class method for the desired driver, rather than using this directly.
35
#
36
# @api private
37
#
38
39
def initialize(config)
40
@executable_path = config.executable_path
41
@host = Platform.localhost
42
@port = config.port
43
@io = config.log
44
@extra_args = config.args
45
@shutdown_supported = config.shutdown_supported
46
47
raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
48
end
49
50
def start
51
raise "already started: #{uri.inspect} #{@executable_path.inspect}" if process_running?
52
53
Platform.exit_hook { stop } # make sure we don't leave the server running
54
55
socket_lock.locked do
56
find_free_port
57
start_process
58
connect_until_stable
59
end
60
end
61
62
def stop
63
return unless @shutdown_supported
64
return if process_exited?
65
66
stop_server
67
@process.poll_for_exit STOP_TIMEOUT
68
rescue ChildProcess::TimeoutError, Errno::ECONNREFUSED
69
nil # noop
70
ensure
71
stop_process
72
end
73
74
def uri
75
@uri ||= URI.parse("http://#{@host}:#{@port}")
76
end
77
78
private
79
80
def build_process(*command)
81
WebDriver.logger.debug("Executing Process #{command}", id: :driver_service)
82
@process = ChildProcess.build(*command)
83
if ENV.key?('SE_DEBUG')
84
if @io && @io != WebDriver.logger.io
85
WebDriver.logger.warn('SE_DEBUG is set; overriding user-specified driver log output to use stderr',
86
id: :se_debug)
87
end
88
@io = WebDriver.logger.io
89
end
90
@process.io = @io if @io
91
92
@process
93
end
94
95
def connect_to_server
96
Net::HTTP.start(@host, @port) do |http|
97
http.open_timeout = STOP_TIMEOUT / 2
98
http.read_timeout = STOP_TIMEOUT / 2
99
100
yield http
101
end
102
end
103
104
def find_free_port
105
@port = PortProber.above(@port)
106
end
107
108
def start_process
109
@process = build_process(@executable_path, "--port=#{@port}", *@extra_args)
110
@process.start
111
end
112
113
def stop_process
114
return if process_exited?
115
116
@process.stop STOP_TIMEOUT
117
end
118
119
def stop_server
120
connect_to_server do |http|
121
headers = WebDriver::Remote::Http::Common::DEFAULT_HEADERS.dup
122
WebDriver.logger.debug('Sending shutdown request to server', id: :driver_service)
123
http.get('/shutdown', headers)
124
end
125
end
126
127
def process_running?
128
defined?(@process) && @process&.alive?
129
end
130
131
def process_exited?
132
@process.nil? || @process.exited?
133
end
134
135
def connect_until_stable
136
deadline = current_time + START_TIMEOUT
137
138
loop do
139
error = check_connection_error
140
return unless error
141
142
raise Error::WebDriverError, "#{cannot_connect_error_text}: #{error}" if current_time > deadline
143
144
sleep 0.1
145
end
146
end
147
148
def check_connection_error
149
response = Net::HTTP.start(@host, @port, open_timeout: 0.5, read_timeout: 1) do |http|
150
http.get('/status', {'Connection' => 'close'})
151
end
152
153
return "status returned #{response.code}\n#{response.body}" unless response.is_a?(Net::HTTPSuccess)
154
155
status = JSON.parse(response.body)
156
ready = status['ready'] || status.dig('value', 'ready')
157
"driver not ready: #{response.body}" unless ready
158
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE, Errno::ETIMEDOUT,
159
Errno::EADDRNOTAVAIL, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout,
160
EOFError, SocketError, Net::HTTPBadResponse, JSON::ParserError => e
161
"#{e.class}: #{e.message}"
162
end
163
164
def current_time
165
Process.clock_gettime(Process::CLOCK_MONOTONIC)
166
end
167
168
def cannot_connect_error_text
169
"unable to connect to #{@executable_path} #{@host}:#{@port}"
170
end
171
172
def socket_lock
173
@socket_lock ||= SocketLock.new(@port - 1, SOCKET_LOCK_TIMEOUT)
174
end
175
end # Service
176
end # WebDriver
177
end # Selenium
178
179