Skip to content

Commit 7ac05c5

Browse files
titusfortnerdiemol
andauthored
Ruby driver finder (#11523)
* [rb] move common code for local drivers into a module * [rb] implement driver finder * [rb] selenium manager fallback produces multiple lines of output * [rb] can not connect to a server refusing connection; http library does not rescue this one * [rb] wrong bazel target for selenium manager unit test --------- Co-authored-by: Diego Molina <[email protected]>
1 parent 6d94706 commit 7ac05c5

35 files changed

Lines changed: 670 additions & 346 deletions

rb/lib/selenium/webdriver/chrome/driver.rb

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@ module Chrome
2828
#
2929

3030
class Driver < Chromium::Driver
31-
def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
32-
raise ArgumentError, "Can't initialize #{self.class} with :url" if url
31+
include LocalDriver
3332

34-
caps = process_options(options, capabilities)
35-
url = service_url(service || Service.chrome)
33+
def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
34+
caps, url = initialize_local_driver(capabilities, options, service, url)
3635
super(caps: caps, url: url, **opts)
3736
end
3837

@@ -45,16 +44,6 @@ def browser
4544
def devtools_address
4645
"http://#{capabilities['goog:chromeOptions']['debuggerAddress']}"
4746
end
48-
49-
def process_options(options, capabilities)
50-
if options && !options.is_a?(Options)
51-
raise ArgumentError, ":options must be an instance of #{Options}"
52-
elsif options.nil? && capabilities.nil?
53-
options = Options.new
54-
end
55-
56-
super(options, capabilities)
57-
end
5847
end # Driver
5948
end # Chrome
6049
end # WebDriver

rb/lib/selenium/webdriver/chrome/service.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ module Chrome
2525
class Service < Chromium::Service
2626
DEFAULT_PORT = 9515
2727
EXECUTABLE = 'chromedriver'
28-
MISSING_TEXT = <<~ERROR
29-
Unable to find chromedriver. Please download the server from
30-
https://chromedriver.storage.googleapis.com/index.html and place it somewhere on your PATH.
31-
More info at https://www.selenium.dev/documentation/webdriver/getting_started/install_drivers/?language=ruby.
32-
ERROR
3328
SHUTDOWN_SUPPORTED = true
3429
end # Service
3530
end # Chrome

rb/lib/selenium/webdriver/common.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
# under the License.
1919

2020
require 'selenium/webdriver/common/error'
21+
require 'selenium/webdriver/common/local_driver'
22+
require 'selenium/webdriver/common/driver_finder'
2123
require 'selenium/webdriver/common/platform'
2224
require 'selenium/webdriver/common/proxy'
2325
require 'selenium/webdriver/common/log_entry'

rb/lib/selenium/webdriver/common/driver.rb

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -318,24 +318,6 @@ def create_bridge(caps:, url:, http_client: nil)
318318
end
319319
end
320320

321-
def process_options(options, capabilities)
322-
if options && capabilities
323-
msg = "Don't use both :options and :capabilities when initializing #{self.class}, prefer :options"
324-
raise ArgumentError, msg
325-
end
326-
327-
options ? options.as_json : deprecate_capabilities(capabilities)
328-
end
329-
330-
def deprecate_capabilities(capabilities)
331-
unless is_a?(Remote::Driver)
332-
WebDriver.logger.deprecate("The :capabilities parameter for #{self.class}",
333-
":options argument with an instance of #{self.class}",
334-
id: :capabilities)
335-
end
336-
generate_capabilities(capabilities)
337-
end
338-
339321
def generate_capabilities(capabilities)
340322
Array(capabilities).map { |cap|
341323
if cap.is_a? Symbol
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
class DriverFinder
23+
def self.path(options, klass)
24+
path = klass.driver_path
25+
path = path.call if path.is_a?(Proc)
26+
path ||= Platform.find_binary(klass::EXECUTABLE)
27+
28+
path ||= begin
29+
SeleniumManager.driver_path(options)
30+
rescue Error::WebDriverError => e
31+
WebDriver.logger.debug("Unable obtain driver using Selenium Manager\n #{e.message}")
32+
nil
33+
end
34+
msg = "Unable to locate the #{klass::EXECUTABLE} executable; for more information on how to install drivers, " \
35+
'see https://www.selenium.dev/documentation/webdriver/getting_started/install_drivers/'
36+
raise Error::WebDriverError, msg unless path
37+
38+
Platform.assert_executable path
39+
path
40+
end
41+
end
42+
end
43+
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
module LocalDriver
23+
def initialize_local_driver(capabilities, options, service, url)
24+
raise ArgumentError, "Can't initialize #{self.class} with :url" if url
25+
26+
service ||= Service.send(browser)
27+
caps = process_options(options, capabilities, service)
28+
url = service_url(service)
29+
30+
[caps, url]
31+
end
32+
33+
def process_options(options, capabilities, service)
34+
default_options = Options.send(browser)
35+
36+
if options && capabilities
37+
msg = "Don't use both :options and :capabilities when initializing #{self.class}, prefer :options"
38+
raise ArgumentError, msg
39+
elsif options && !options.is_a?(default_options.class)
40+
raise ArgumentError, ":options must be an instance of #{default_options.class}"
41+
elsif capabilities
42+
WebDriver.logger.deprecate("The :capabilities parameter for #{self.class}",
43+
":options argument with an instance of #{self.class}",
44+
id: :capabilities)
45+
generate_capabilities(capabilities)
46+
else
47+
options ||= default_options
48+
service.executable_path ||= WebDriver::DriverFinder.path(options, service.class)
49+
options.as_json
50+
end
51+
end
52+
end
53+
end
54+
end

rb/lib/selenium/webdriver/common/selenium_manager.rb

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,24 @@ class SeleniumManager
3030
BIN_PATH = '../../../../../bin'
3131

3232
class << self
33-
# @param [String] driver_name which driver to use.
33+
# @param [Options] options browser options.
3434
# @return [String] the path to the correct driver.
35-
def driver_path(driver_name)
36-
unless %w[chromedriver geckodriver msedgedriver IEDriverServer].include?(driver_name)
37-
msg = "Unable to locate driver with name: #{driver_name}"
38-
raise Error::WebDriverError, msg
35+
def driver_path(options)
36+
unless options.is_a?(Options)
37+
raise ArgumentError, "SeleniumManager requires a WebDriver::Options instance, not a #{options.inspect}"
3938
end
4039

41-
location = run("#{binary} --driver #{driver_name}")
40+
command = [binary, '--browser', options.browser_name]
41+
if options.browser_version
42+
command << '--browser-version'
43+
command << options.browser_version
44+
end
45+
if options.respond_to?(:binary) && !options.binary.nil?
46+
command << '--browser-path'
47+
command << "\"#{options.binary.gsub('\ ', ' ').gsub(' ', '\ ')}\""
48+
end
49+
50+
location = run(command.join(' '))
4251
WebDriver.logger.debug("Driver found at #{location}")
4352
Platform.assert_executable location
4453

@@ -81,7 +90,7 @@ def run(command)
8190
raise Error::WebDriverError, "Unsuccessful command executed: #{command}\n#{stdout}#{stderr}"
8291
end
8392

84-
stdout.gsub("INFO\t", '').strip
93+
stdout.split("\n").last.gsub("INFO\t", '')
8594
end
8695
end
8796
end # SeleniumManager

rb/lib/selenium/webdriver/common/service.rb

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,10 @@ def driver_path=(path)
6767
#
6868

6969
def initialize(path: nil, port: nil, args: nil)
70-
path ||= self.class.driver_path
7170
port ||= self.class::DEFAULT_PORT
7271
args ||= []
7372

74-
@executable_path = binary_path(path)
73+
@executable_path = path
7574
@host = Platform.localhost
7675
@port = Integer(port)
7776

@@ -96,24 +95,6 @@ def extract_service_args(driver_opts)
9695
id: :driver_opts)
9796
driver_opts.key?(:args) ? driver_opts.delete(:args) : []
9897
end
99-
100-
private
101-
102-
def binary_path(path = nil)
103-
path = path.call if path.is_a?(Proc)
104-
path ||= Platform.find_binary(self.class::EXECUTABLE)
105-
106-
begin
107-
path ||= SeleniumManager.driver_path(self.class::EXECUTABLE)
108-
rescue Error::WebDriverError => e
109-
WebDriver.logger.debug("Unable obtain driver using Selenium Manager\n #{e.message}")
110-
end
111-
112-
raise Error::WebDriverError, self.class::MISSING_TEXT unless path
113-
114-
Platform.assert_executable path
115-
path
116-
end
11798
end # Service
11899
end # WebDriver
119100
end # Selenium

rb/lib/selenium/webdriver/common/service_manager.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def stop
6464

6565
stop_server
6666
@process.poll_for_exit STOP_TIMEOUT
67-
rescue ChildProcess::TimeoutError
67+
rescue ChildProcess::TimeoutError, Errno::ECONNREFUSED
6868
nil # noop
6969
ensure
7070
stop_process

rb/lib/selenium/webdriver/edge/driver.rb

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,10 @@ module Edge
2828
#
2929

3030
class Driver < Chromium::Driver
31-
def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
32-
raise ArgumentError, "Can't initialize #{self.class} with :url" if url
31+
include LocalDriver
3332

34-
caps = process_options(options, capabilities)
35-
url = service_url(service || Service.edge)
33+
def initialize(capabilities: nil, options: nil, service: nil, url: nil, **opts)
34+
caps, url = initialize_local_driver(capabilities, options, service, url)
3635
super(caps: caps, url: url, **opts)
3736
end
3837

@@ -45,16 +44,6 @@ def browser
4544
def devtools_address
4645
"http://#{capabilities['ms:edgeOptions']['debuggerAddress']}"
4746
end
48-
49-
def process_options(options, capabilities)
50-
if options && !options.is_a?(Options)
51-
raise ArgumentError, ":options must be an instance of #{Options}"
52-
elsif options.nil? && capabilities.nil?
53-
options = Options.new
54-
end
55-
56-
super(options, capabilities)
57-
end
5847
end # Driver
5948
end # Edge
6049
end # WebDriver

0 commit comments

Comments
 (0)