Skip to content

Commit 9f51236

Browse files
titusfortnerdiemol
andauthored
[rb] update SOC for driver finder and selenium manager classes (#13386)
[rb] move driver finding logic out of selenium manager class Co-authored-by: Diego Molina <[email protected]>
1 parent 4ecc103 commit 9f51236

File tree

20 files changed

+248
-451
lines changed

20 files changed

+248
-451
lines changed

rb/lib/selenium/webdriver/common/driver_finder.rb

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,64 @@
2020
module Selenium
2121
module WebDriver
2222
class DriverFinder
23-
def self.path(options, klass)
24-
path = klass.driver_path
25-
path = path.call if path.is_a?(Proc)
23+
def initialize(options, service)
24+
@options = options
25+
@service = service
26+
end
27+
28+
def browser_path
29+
paths[:browser_path]
30+
end
31+
32+
def driver_path
33+
paths[:driver_path]
34+
end
35+
36+
def browser_path?
37+
!browser_path.nil? && !browser_path.empty?
38+
end
39+
40+
private
2641

27-
path ||= begin
28-
SeleniumManager.driver_path(options) unless options.is_a?(Remote::Capabilities)
42+
def paths
43+
@paths ||= begin
44+
path = @service.class.driver_path
45+
path = path.call if path.is_a?(Proc)
46+
exe = @service.class::EXECUTABLE
47+
if path
48+
WebDriver.logger.debug("Skipping Selenium Manager; path to #{exe} specified in service class: #{path}")
49+
Platform.assert_executable(path)
50+
{driver_path: path}
51+
else
52+
output = SeleniumManager.binary_paths(*to_args)
53+
formatted = {driver_path: Platform.cygwin_path(output['driver_path'], only_cygwin: true),
54+
browser_path: Platform.cygwin_path(output['browser_path'], only_cygwin: true)}
55+
Platform.assert_executable(formatted[:driver_path])
56+
Platform.assert_executable(formatted[:browser_path])
57+
formatted
58+
end
2959
rescue StandardError => e
30-
raise Error::NoSuchDriverError, "Unable to obtain #{klass::EXECUTABLE} using Selenium Manager; #{e.message}"
60+
WebDriver.logger.error("Exception occurred: #{e.message}")
61+
WebDriver.logger.error("Backtrace:\n\t#{e.backtrace&.join("\n\t")}")
62+
raise Error::NoSuchDriverError, "Unable to obtain #{exe}"
3163
end
64+
end
3265

33-
begin
34-
Platform.assert_executable(path)
35-
rescue TypeError
36-
raise Error::NoSuchDriverError, "Unable to locate or obtain #{klass::EXECUTABLE}"
37-
rescue Error::WebDriverError => e
38-
raise Error::NoSuchDriverError, "#{klass::EXECUTABLE} located, but: #{e.message}"
66+
def to_args
67+
args = ['--browser', @options.browser_name]
68+
if @options.browser_version
69+
args << '--browser-version'
70+
args << @options.browser_version
3971
end
40-
41-
path
72+
if @options.respond_to?(:binary) && !@options.binary.nil?
73+
args << '--browser-path'
74+
args << @options.binary.gsub('\\', '\\\\\\')
75+
end
76+
if @options.proxy
77+
args << '--proxy'
78+
args << (@options.proxy.ssl || @options.proxy.http)
79+
end
80+
args
4281
end
4382
end
4483
end

rb/lib/selenium/webdriver/common/local_driver.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,14 @@ def process_options(options, service)
3838
raise ArgumentError, ":options must be an instance of #{default_options.class}"
3939
end
4040

41-
service.executable_path ||= WebDriver::DriverFinder.path(options, service.class)
41+
service.executable_path ||= begin
42+
finder = WebDriver::DriverFinder.new(options, service)
43+
if options.respond_to?(:binary) && finder.browser_path?
44+
options.binary = finder.browser_path
45+
options.browser_version = nil
46+
end
47+
finder.driver_path
48+
end
4249
options.as_json
4350
end
4451
end

rb/lib/selenium/webdriver/common/platform.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ def wrap_in_quotes_if_necessary(str)
111111
windows? && !cygwin? ? %("#{str}") : str
112112
end
113113

114-
def cygwin_path(path, **opts)
114+
def cygwin_path(path, only_cygwin: false, **opts)
115+
return path if only_cygwin && !cygwin?
116+
115117
flags = []
116118
opts.each { |k, v| flags << "--#{k}" if v }
117119

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

Lines changed: 36 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -34,88 +34,33 @@ def bin_path
3434
@bin_path ||= '../../../../../bin'
3535
end
3636

37-
# @param [Options] options browser options.
38-
# @return [String] the path to the correct driver.
39-
def driver_path(options)
40-
command = generate_command(binary, options)
41-
42-
output = run(*command)
43-
44-
browser_path = Platform.cygwin? ? Platform.cygwin_path(output['browser_path']) : output['browser_path']
45-
driver_path = Platform.cygwin? ? Platform.cygwin_path(output['driver_path']) : output['driver_path']
46-
Platform.assert_executable driver_path
47-
48-
if options.respond_to?(:binary) && browser_path && !browser_path.empty?
49-
options.binary = browser_path
50-
options.browser_version = nil
51-
end
52-
53-
driver_path
37+
# @param [Array] arguments what gets sent to to Selenium Manager binary.
38+
# @return [Hash] paths to the requested assets.
39+
def binary_paths(*arguments)
40+
arguments += %w[--language-binding ruby]
41+
arguments += %w[--output json]
42+
arguments << '--debug' if WebDriver.logger.debug?
43+
44+
run(binary, *arguments)
5445
end
5546

5647
private
5748

58-
def generate_command(binary, options)
59-
command = [binary, '--browser', options.browser_name]
60-
if options.browser_version
61-
command << '--browser-version'
62-
command << options.browser_version
63-
end
64-
if options.respond_to?(:binary) && !options.binary.nil?
65-
command << '--browser-path'
66-
command << options.binary.squeeze('\\').gsub('\\', '\\\\\\')
67-
end
68-
if options.proxy
69-
command << '--proxy'
70-
command << (options.proxy.ssl || options.proxy.http)
71-
end
72-
command
73-
end
74-
7549
# @return [String] the path to the correct selenium manager
7650
def binary
7751
@binary ||= begin
78-
location = ENV.fetch('SE_MANAGER_PATH', begin
79-
directory = File.expand_path(bin_path, __FILE__)
80-
if Platform.windows?
81-
"#{directory}/windows/selenium-manager.exe"
82-
elsif Platform.mac?
83-
"#{directory}/macos/selenium-manager"
84-
elsif Platform.linux?
85-
"#{directory}/linux/selenium-manager"
86-
elsif Platform.unix?
87-
WebDriver.logger.warn('Selenium Manager binary may not be compatible with Unix; verify settings',
88-
id: %i[selenium_manager unix_binary])
89-
"#{directory}/linux/selenium-manager"
90-
end
91-
rescue Error::WebDriverError => e
92-
raise Error::WebDriverError, "Unable to obtain Selenium Manager binary for #{e.message}"
93-
end)
52+
if (location = ENV.fetch('SE_MANAGER_PATH', nil))
53+
WebDriver.logger.debug("Selenium Manager set by ENV['SE_MANAGER_PATH']: #{location}")
54+
end
55+
location ||= platform_location
9456

95-
validate_location(location)
96-
location
97-
end
98-
end
99-
100-
def validate_location(location)
101-
begin
102-
Platform.assert_file(location)
10357
Platform.assert_executable(location)
104-
rescue TypeError
105-
raise Error::WebDriverError,
106-
"Unable to locate or obtain Selenium Manager binary; #{location} is not a valid file object"
107-
rescue Error::WebDriverError => e
108-
raise Error::WebDriverError, "Selenium Manager binary located, but #{e.message}"
58+
WebDriver.logger.debug("Selenium Manager binary found at #{location}", id: :selenium_manager)
59+
location
10960
end
110-
111-
WebDriver.logger.debug("Selenium Manager binary found at #{location}", id: :selenium_manager)
11261
end
11362

11463
def run(*command)
115-
command += %w[--language-binding ruby]
116-
command += %w[--output json]
117-
command << '--debug' if WebDriver.logger.debug?
118-
11964
WebDriver.logger.debug("Executing Process #{command}", id: :selenium_manager)
12065

12166
begin
@@ -124,16 +69,34 @@ def run(*command)
12469
raise Error::WebDriverError, "Unsuccessful command executed: #{command}; #{e.message}"
12570
end
12671

127-
json_output = stdout.empty? ? {} : JSON.parse(stdout)
128-
(json_output['logs'] || []).each do |log|
72+
json_output = stdout.empty? ? {'logs' => [], 'result' => {}} : JSON.parse(stdout)
73+
json_output['logs'].each do |log|
12974
level = log['level'].casecmp('info').zero? ? 'debug' : log['level'].downcase
13075
WebDriver.logger.send(level, log['message'], id: :selenium_manager)
13176
end
13277

13378
result = json_output['result']
134-
return result unless status.exitstatus.positive?
79+
return result unless status.exitstatus.positive? || result.nil?
13580

136-
raise Error::WebDriverError, "Unsuccessful command executed: #{command}\n#{result}#{stderr}"
81+
raise Error::WebDriverError,
82+
"Unsuccessful command executed: #{command} - Code #{status.exitstatus}\n#{result}\n#{stderr}"
83+
end
84+
85+
def platform_location
86+
directory = File.expand_path(bin_path, __FILE__)
87+
if Platform.windows?
88+
"#{directory}/windows/selenium-manager.exe"
89+
elsif Platform.mac?
90+
"#{directory}/macos/selenium-manager"
91+
elsif Platform.linux?
92+
"#{directory}/linux/selenium-manager"
93+
elsif Platform.unix?
94+
WebDriver.logger.warn('Selenium Manager binary may not be compatible with Unix',
95+
id: %i[selenium_manager unix_binary])
96+
"#{directory}/linux/selenium-manager"
97+
else
98+
raise Error::WebDriverError, "unsupported platform: #{Platform.os}"
99+
end
137100
end
138101
end
139102
end # SeleniumManager

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def initialize(path: nil, port: nil, log: nil, args: nil)
8989
def launch
9090
@executable_path ||= begin
9191
default_options = WebDriver.const_get("#{self.class.name&.split('::')&.[](2)}::Options").new
92-
DriverFinder.path(default_options, self.class)
92+
DriverFinder.new(default_options, self).driver_path
9393
end
9494
ServiceManager.new(self).tap(&:start)
9595
end

rb/spec/integration/selenium/webdriver/chrome/service_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module Chrome
2929
after { service_manager.stop }
3030

3131
it 'auto uses chromedriver' do
32-
service.executable_path = DriverFinder.path(Options.new, described_class)
32+
service.executable_path = DriverFinder.new(Options.new, described_class.new).driver_path
3333

3434
expect(service_manager.uri).to be_a(URI)
3535
end

rb/spec/integration/selenium/webdriver/edge/service_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module Edge
2929
after { service_manager.stop }
3030

3131
it 'auto uses edgedriver' do
32-
service.executable_path = DriverFinder.path(Options.new, described_class)
32+
service.executable_path = DriverFinder.new(Options.new, described_class.new).driver_path
3333

3434
expect(service_manager.uri).to be_a(URI)
3535
end

rb/spec/integration/selenium/webdriver/firefox/service_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ module Firefox
2929
after { service_manager.stop }
3030

3131
it 'auto uses geckodriver' do
32-
service.executable_path = DriverFinder.path(Options.new, described_class)
32+
service.executable_path = DriverFinder.new(Options.new, described_class.new).driver_path
3333

3434
expect(service_manager.uri).to be_a(URI)
3535
end

rb/spec/unit/selenium/webdriver/chrome/driver_spec.rb

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ module Chrome
3333
body: {value: {sessionId: 0, capabilities: {browserName: 'chrome'}}}.to_json,
3434
headers: {content_type: 'application/json'}}
3535
end
36+
let(:finder) { instance_double(DriverFinder, browser_path?: false, driver_path: '/path/to/driver') }
3637

3738
def expect_request(body: nil, endpoint: nil)
3839
body = (body || {capabilities: {alwaysMatch: {browserName: 'chrome', 'goog:chromeOptions': {}}}}).to_json
@@ -45,44 +46,46 @@ def expect_request(body: nil, endpoint: nil)
4546
end
4647

4748
it 'uses DriverFinder when provided Service without path' do
48-
allow(DriverFinder).to receive(:path)
49+
allow(DriverFinder).to receive(:new).and_return(finder)
4950
expect_request
5051
options = Options.new
5152

5253
described_class.new(service: service, options: options)
53-
expect(DriverFinder).to have_received(:path).with(options, service.class)
54+
expect(finder).to have_received(:driver_path)
5455
end
5556

5657
it 'does not use DriverFinder when provided Service with path' do
5758
expect_request
5859
allow(service).to receive(:executable_path).and_return('path')
59-
allow(DriverFinder).to receive(:path)
60+
allow(DriverFinder).to receive(:new).and_return(finder)
6061

6162
described_class.new(service: service)
62-
expect(DriverFinder).not_to have_received(:path)
63+
expect(finder).not_to have_received(:driver_path)
6364
end
6465

6566
it 'does not require any parameters' do
66-
allow(DriverFinder).to receive(:path).and_return('path')
67-
allow(Platform).to receive(:assert_file)
68-
allow(Platform).to receive(:assert_executable)
69-
67+
allow(DriverFinder).to receive(:new).and_return(finder)
7068
expect_request
7169

7270
expect { described_class.new }.not_to raise_exception
7371
end
7472

7573
it 'accepts provided Options as sole parameter' do
76-
allow(DriverFinder).to receive(:path).and_return('path')
77-
allow(Platform).to receive(:assert_file)
78-
allow(Platform).to receive(:assert_executable)
74+
allow(DriverFinder).to receive(:new).and_return(finder)
7975

8076
opts = {args: ['-f']}
8177
expect_request(body: {capabilities: {alwaysMatch: {browserName: 'chrome', 'goog:chromeOptions': opts}}})
8278

8379
expect { described_class.new(options: Options.new(**opts)) }.not_to raise_exception
8480
end
8581

82+
it 'raises an ArgumentError if parameter is not recognized' do
83+
allow(DriverFinder).to receive(:new).and_return(finder)
84+
85+
msg = 'unknown keyword: :invalid'
86+
expect { described_class.new(invalid: 'foo') }.to raise_error(ArgumentError, msg)
87+
end
88+
8689
it 'does not accept Options of the wrong class' do
8790
expect {
8891
described_class.new(options: Options.firefox)

rb/spec/unit/selenium/webdriver/chrome/service_spec.rb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ module Chrome
9191
end
9292
let(:service_manager) { instance_double(ServiceManager, uri: 'http://example.com') }
9393
let(:bridge) { instance_double(Remote::Bridge, quit: nil, create_session: {}) }
94+
let(:finder) { instance_double(DriverFinder, browser_path?: false, driver_path: '/path/to/driver') }
9495

9596
before do
9697
allow(Remote::Bridge).to receive(:new).and_return(bridge)
@@ -104,19 +105,15 @@ module Chrome
104105
end
105106

106107
it 'is created when :url is not provided' do
107-
allow(DriverFinder).to receive(:path).and_return('path')
108-
allow(Platform).to receive(:assert_file)
109-
allow(Platform).to receive(:assert_executable)
108+
allow(DriverFinder).to receive(:new).and_return(finder)
110109
allow(described_class).to receive(:new).and_return(service)
111110

112111
driver.new
113112
expect(described_class).to have_received(:new).with(no_args)
114113
end
115114

116115
it 'accepts :service without creating a new instance' do
117-
allow(DriverFinder).to receive(:path).and_return('path')
118-
allow(Platform).to receive(:assert_file)
119-
allow(Platform).to receive(:assert_executable)
116+
allow(DriverFinder).to receive(:new).and_return(finder)
120117
allow(described_class).to receive(:new)
121118

122119
driver.new(service: service)

0 commit comments

Comments
 (0)