selenium-webdriver 4.45.0 → 4.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES +36 -0
- data/bin/linux/selenium-manager +0 -0
- data/bin/macos/selenium-manager +0 -0
- data/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +5971 -0
- data/bin/selenium-manager.cdx.json +11629 -0
- data/bin/windows/selenium-manager.exe +0 -0
- data/lib/selenium/webdriver/{common/driver_extensions/has_bidi.rb → bidi/error.rb} +21 -13
- data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +495 -0
- data/lib/selenium/webdriver/bidi/protocol/browser.rb +250 -0
- data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +798 -0
- data/lib/selenium/webdriver/bidi/protocol/domain.rb +46 -0
- data/lib/selenium/webdriver/bidi/protocol/emulation.rb +448 -0
- data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
- data/lib/selenium/webdriver/bidi/protocol/input.rb +354 -0
- data/lib/selenium/webdriver/bidi/protocol/log.rb +115 -0
- data/lib/selenium/webdriver/bidi/protocol/network.rb +720 -0
- data/lib/selenium/webdriver/bidi/protocol/permissions.rb +75 -0
- data/lib/selenium/webdriver/bidi/protocol/script.rb +1052 -0
- data/lib/selenium/webdriver/bidi/protocol/session.rb +286 -0
- data/lib/selenium/webdriver/bidi/protocol/speculation.rb +58 -0
- data/lib/selenium/webdriver/bidi/protocol/storage.rb +183 -0
- data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +88 -0
- data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +132 -0
- data/lib/selenium/webdriver/bidi/protocol.rb +42 -0
- data/lib/selenium/webdriver/bidi/serialization/record.rb +402 -0
- data/lib/selenium/webdriver/bidi/serialization/union.rb +156 -0
- data/lib/selenium/webdriver/bidi/serialization.rb +90 -0
- data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +1420 -0
- data/lib/selenium/webdriver/bidi/support/check_generated.rb +63 -0
- data/lib/selenium/webdriver/bidi/transport.rb +57 -0
- data/lib/selenium/webdriver/bidi.rb +3 -0
- data/lib/selenium/webdriver/chrome/driver.rb +3 -3
- data/lib/selenium/webdriver/chrome/service.rb +4 -1
- data/lib/selenium/webdriver/chromium/driver.rb +0 -1
- data/lib/selenium/webdriver/chromium/options.rb +20 -0
- data/lib/selenium/webdriver/common/client_config.rb +97 -0
- data/lib/selenium/webdriver/common/driver.rb +13 -2
- data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -7
- data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +0 -7
- data/lib/selenium/webdriver/common/driver_finder.rb +4 -3
- data/lib/selenium/webdriver/common/local_driver.rb +15 -4
- data/lib/selenium/webdriver/common/options.rb +21 -1
- data/lib/selenium/webdriver/common/proxy.rb +1 -9
- data/lib/selenium/webdriver/common/selenium_manager.rb +2 -1
- data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
- data/lib/selenium/webdriver/common.rb +1 -1
- data/lib/selenium/webdriver/edge/driver.rb +3 -3
- data/lib/selenium/webdriver/edge/service.rb +4 -1
- data/lib/selenium/webdriver/firefox/driver.rb +3 -6
- data/lib/selenium/webdriver/firefox/options.rb +19 -0
- data/lib/selenium/webdriver/firefox/profile.rb +13 -6
- data/lib/selenium/webdriver/ie/driver.rb +3 -3
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +41 -9
- data/lib/selenium/webdriver/remote/bridge.rb +8 -10
- data/lib/selenium/webdriver/remote/driver.rb +14 -4
- data/lib/selenium/webdriver/remote/http/common.rb +25 -12
- data/lib/selenium/webdriver/remote/http/default.rb +56 -39
- data/lib/selenium/webdriver/safari/driver.rb +3 -3
- data/lib/selenium/webdriver/safari/options.rb +16 -1
- data/lib/selenium/webdriver/support/guards/guard.rb +20 -3
- data/lib/selenium/webdriver/support/guards.rb +9 -3
- data/lib/selenium/webdriver/version.rb +1 -1
- metadata +29 -3
|
@@ -0,0 +1,63 @@
|
|
|
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 'json'
|
|
21
|
+
require_relative 'bidi_generate'
|
|
22
|
+
|
|
23
|
+
module BiDiGenerate
|
|
24
|
+
# Verifies the checked-in protocol .rb match what the generator would produce from the
|
|
25
|
+
# current schema — catching a hand-edit or a forgotten regeneration. Re-renders each module
|
|
26
|
+
# in memory (no file writes) and compares. The .rbs are covered by Steep.
|
|
27
|
+
def self.check!(schema_rootpath)
|
|
28
|
+
schema = Schema.new(JSON.parse(File.read(schema_path(schema_rootpath))))
|
|
29
|
+
protocol_dir = File.expand_path('../protocol', __dir__)
|
|
30
|
+
template = File.join(__dir__, 'templates', 'module.rb.erb')
|
|
31
|
+
|
|
32
|
+
stale = build_ir(schema).filter_map do |mod|
|
|
33
|
+
path = File.join(protocol_dir, "#{mod.filename}.rb")
|
|
34
|
+
"#{mod.filename}.rb" unless File.exist?(path) && File.read(path) == render(mod, template)
|
|
35
|
+
end
|
|
36
|
+
stale << 'error_code.rb' unless error_module_current?(schema, protocol_dir)
|
|
37
|
+
return if stale.empty?
|
|
38
|
+
|
|
39
|
+
warn "Generated BiDi protocol code is stale or hand-edited: #{stale.sort.join(', ')}"
|
|
40
|
+
warn 'Regenerate with: bazel run //rb/lib/selenium/webdriver:bidi-generate'
|
|
41
|
+
exit 1
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Whether the checked-in protocol/error_code.rb matches what the generator would render now.
|
|
45
|
+
def self.error_module_current?(schema, protocol_dir)
|
|
46
|
+
mod = ErrorModule.new(filename: 'error_code', codes: error_code_map(schema))
|
|
47
|
+
path = File.join(protocol_dir, 'error_code.rb')
|
|
48
|
+
File.exist?(path) && File.read(path) == render(mod, File.join(__dir__, 'templates', 'error_code.rb.erb'))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# $(rootpath) is relative to the runfiles root; __dir__ anchors us there so it resolves the
|
|
52
|
+
# same way locally and on RBE (an execpath would not). This file lives at
|
|
53
|
+
# rb/lib/selenium/webdriver/bidi/support, so expand six levels up to the root — File.expand_path
|
|
54
|
+
# is separator-agnostic, unlike stripping a "/"-spelled suffix (which would miss on Windows).
|
|
55
|
+
# The cwd-relative rootpath fallback matches how spec_support's `rlocation` resolves.
|
|
56
|
+
def self.schema_path(rootpath)
|
|
57
|
+
runfiles_root = File.expand_path('../../../../../..', __dir__)
|
|
58
|
+
[File.join(runfiles_root, rootpath), rootpath].find { |p| File.exist?(p) } ||
|
|
59
|
+
raise("BiDi schema not found (looked for #{rootpath})")
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
BiDiGenerate.check!(ARGV.fetch(0)) if $PROGRAM_NAME == __FILE__
|
|
@@ -0,0 +1,57 @@
|
|
|
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 BiDi
|
|
23
|
+
# The seam between the generated Protocol layer and the websocket: serializes a
|
|
24
|
+
# command's params, sends it, and parses the reply into its declared type.
|
|
25
|
+
#
|
|
26
|
+
# @api private
|
|
27
|
+
class Transport
|
|
28
|
+
# The websocket the transport sends over. Exposed so a domain can build a sibling
|
|
29
|
+
# domain (e.g. a vendor variant) over the same connection without Transport ever
|
|
30
|
+
# becoming a public constructor argument.
|
|
31
|
+
attr_reader :connection
|
|
32
|
+
|
|
33
|
+
def initialize(connection)
|
|
34
|
+
@connection = connection
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def execute(cmd:, params: nil, result: nil)
|
|
38
|
+
reply = @connection.send_cmd(method: cmd, params: serialize(params))
|
|
39
|
+
raise error_for(reply) if reply['error']
|
|
40
|
+
|
|
41
|
+
value = reply['result']
|
|
42
|
+
result ? result.from_json(value) : value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def serialize(params)
|
|
48
|
+
params&.as_json || {}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def error_for(reply)
|
|
52
|
+
Protocol::ErrorCode.for(reply['error']).new("#{reply['message']}\n#{reply['stacktrace']}")
|
|
53
|
+
end
|
|
54
|
+
end # Transport
|
|
55
|
+
end # BiDi
|
|
56
|
+
end # WebDriver
|
|
57
|
+
end # Selenium
|
|
@@ -30,9 +30,9 @@ module Selenium
|
|
|
30
30
|
class Driver < Chromium::Driver
|
|
31
31
|
include LocalDriver
|
|
32
32
|
|
|
33
|
-
def initialize(options: nil, service: nil, url: nil, **)
|
|
34
|
-
initialize_local_driver(options, service, url) do |caps,
|
|
35
|
-
super(caps: caps,
|
|
33
|
+
def initialize(options: nil, service: nil, url: nil, http_client: nil, client_config: nil, **)
|
|
34
|
+
initialize_local_driver(options, service, url, http_client, client_config) do |caps, client|
|
|
35
|
+
super(caps: caps, http_client: client, **)
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -27,8 +27,11 @@ module Selenium
|
|
|
27
27
|
DRIVER_PATH_ENV_KEY = 'SE_CHROMEDRIVER'
|
|
28
28
|
|
|
29
29
|
def initialize(args: nil, **)
|
|
30
|
+
args = Array(args.dup)
|
|
31
|
+
# skip when CHROME_LOG_FILE is set; --enable-chrome-logs would override the user's log file
|
|
32
|
+
args << '--enable-chrome-logs' unless ENV.key?('CHROME_LOG_FILE') || args.include?('--enable-chrome-logs')
|
|
33
|
+
|
|
30
34
|
if ENV.key?('SE_DEBUG')
|
|
31
|
-
args = Array(args.dup)
|
|
32
35
|
warn_driver_log_override if args.reject! { |arg| arg.include?('log-level') || arg.include?('silent') }
|
|
33
36
|
args << '--verbose'
|
|
34
37
|
end
|
|
@@ -86,6 +86,7 @@ module Selenium
|
|
|
86
86
|
@logging_prefs = options.delete(:logging_prefs) || {}
|
|
87
87
|
@encoded_extensions = @options.delete(:encoded_extensions) || []
|
|
88
88
|
@extensions = []
|
|
89
|
+
@vendor_options = {}
|
|
89
90
|
@options.delete(:extensions).each { |ext| validate_extension(ext) }
|
|
90
91
|
end
|
|
91
92
|
|
|
@@ -184,6 +185,24 @@ module Selenium
|
|
|
184
185
|
@options[:emulation] = opts
|
|
185
186
|
end
|
|
186
187
|
|
|
188
|
+
#
|
|
189
|
+
# Add a Chromium-specific capability nested in the browser options object
|
|
190
|
+
# (e.g. `goog:chromeOptions` / `ms:edgeOptions`) that is not yet exposed
|
|
191
|
+
# through a dedicated method. This is the escape hatch for legitimate
|
|
192
|
+
# vendor capabilities the bindings do not model.
|
|
193
|
+
#
|
|
194
|
+
# @example Set an option not handled by other methods
|
|
195
|
+
# options = Selenium::WebDriver::Chrome::Options.new
|
|
196
|
+
# options.add_chromium_option('unhandledCapability', 'value')
|
|
197
|
+
#
|
|
198
|
+
# @param [String, Symbol] name Name of the capability, as expected by the driver
|
|
199
|
+
# @param [Object] value Value of the capability
|
|
200
|
+
#
|
|
201
|
+
|
|
202
|
+
def add_chromium_option(name, value)
|
|
203
|
+
@vendor_options[name.to_s] = value
|
|
204
|
+
end
|
|
205
|
+
|
|
187
206
|
#
|
|
188
207
|
# Enables mobile browser use on Android.
|
|
189
208
|
#
|
|
@@ -209,6 +228,7 @@ module Selenium
|
|
|
209
228
|
enable_logging(browser_options) unless @logging_prefs.empty?
|
|
210
229
|
|
|
211
230
|
options = browser_options[self.class::KEY]
|
|
231
|
+
options.merge!(@vendor_options)
|
|
212
232
|
options['binary'] ||= binary_path if binary_path
|
|
213
233
|
|
|
214
234
|
if @profile
|
|
@@ -0,0 +1,97 @@
|
|
|
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 'uri'
|
|
21
|
+
|
|
22
|
+
module Selenium
|
|
23
|
+
module WebDriver
|
|
24
|
+
#
|
|
25
|
+
# Configuration for HTTP clients.
|
|
26
|
+
#
|
|
27
|
+
|
|
28
|
+
class ClientConfig
|
|
29
|
+
class << self
|
|
30
|
+
attr_accessor :default_extra_headers
|
|
31
|
+
attr_writer :default_user_agent
|
|
32
|
+
|
|
33
|
+
def default_user_agent
|
|
34
|
+
@default_user_agent ||= "selenium/#{WebDriver::VERSION} (ruby #{Platform.os})"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
attr_accessor :open_timeout, :read_timeout, :max_redirects, :proxy
|
|
39
|
+
attr_writer :extra_headers, :user_agent
|
|
40
|
+
attr_reader :server_url
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# @param [Numeric] open_timeout Seconds to wait for the connection to open.
|
|
44
|
+
# @param [Numeric] read_timeout Seconds to wait for a response.
|
|
45
|
+
# @param [Integer] max_redirects Maximum number of redirects to follow.
|
|
46
|
+
# @param [Proxy] proxy Proxy to use for the connection.
|
|
47
|
+
# @param [Hash] extra_headers Additional headers to send with each request.
|
|
48
|
+
# @param [String] user_agent Value to send as the User-Agent header.
|
|
49
|
+
# @param [String, URI] server_url URL of the server to connect to.
|
|
50
|
+
#
|
|
51
|
+
def initialize(open_timeout: 60,
|
|
52
|
+
read_timeout: 120,
|
|
53
|
+
max_redirects: 20,
|
|
54
|
+
proxy: nil,
|
|
55
|
+
extra_headers: nil,
|
|
56
|
+
user_agent: nil,
|
|
57
|
+
server_url: nil)
|
|
58
|
+
@open_timeout = open_timeout
|
|
59
|
+
@read_timeout = read_timeout
|
|
60
|
+
@max_redirects = max_redirects
|
|
61
|
+
@proxy = proxy || proxy_from_environment
|
|
62
|
+
@extra_headers = extra_headers
|
|
63
|
+
@user_agent = user_agent
|
|
64
|
+
self.server_url = server_url
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def user_agent
|
|
68
|
+
@user_agent || self.class.default_user_agent
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def extra_headers
|
|
72
|
+
@extra_headers || self.class.default_extra_headers
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def server_url=(url)
|
|
76
|
+
if url.nil?
|
|
77
|
+
@server_url = nil
|
|
78
|
+
else
|
|
79
|
+
url = url.to_s
|
|
80
|
+
url += '/' unless url.end_with?('/')
|
|
81
|
+
@server_url = URI.parse(url)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def proxy_from_environment
|
|
88
|
+
proxy = ENV.fetch('http_proxy', nil) || ENV.fetch('HTTP_PROXY', nil)
|
|
89
|
+
return unless proxy
|
|
90
|
+
|
|
91
|
+
no_proxy = ENV.fetch('no_proxy', nil) || ENV.fetch('NO_PROXY', nil)
|
|
92
|
+
proxy = "http://#{proxy}" unless proxy.match?(%r{\Ahttps?://})
|
|
93
|
+
Proxy.new(http: proxy, no_proxy: no_proxy)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -90,6 +90,17 @@ module Selenium
|
|
|
90
90
|
@bridge.status
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
#
|
|
94
|
+
# Returns the WebDriver BiDi connection for this session.
|
|
95
|
+
#
|
|
96
|
+
# @return [BiDi]
|
|
97
|
+
# @raise [Error::WebDriverError] if BiDi was not enabled for this session
|
|
98
|
+
#
|
|
99
|
+
|
|
100
|
+
def bidi
|
|
101
|
+
@bridge.bidi
|
|
102
|
+
end
|
|
103
|
+
|
|
93
104
|
#
|
|
94
105
|
# @return [Navigation]
|
|
95
106
|
# @see Navigation
|
|
@@ -319,9 +330,9 @@ module Selenium
|
|
|
319
330
|
|
|
320
331
|
attr_reader :bridge
|
|
321
332
|
|
|
322
|
-
def create_bridge(caps:,
|
|
333
|
+
def create_bridge(caps:, http_client:)
|
|
323
334
|
klass = caps['webSocketUrl'] ? Remote::BiDiBridge : Remote::Bridge
|
|
324
|
-
klass.new(http_client: http_client
|
|
335
|
+
klass.new(http_client: http_client).tap do |bridge|
|
|
325
336
|
bridge.create_session(caps)
|
|
326
337
|
end
|
|
327
338
|
end
|
|
@@ -57,13 +57,6 @@ module Selenium
|
|
|
57
57
|
#
|
|
58
58
|
|
|
59
59
|
def on_log_event(kind, &block)
|
|
60
|
-
if browser == :firefox
|
|
61
|
-
WebDriver.logger.deprecate(
|
|
62
|
-
'Driver#on_log_event on Firefox',
|
|
63
|
-
'the script.add_console_message_handler or the script.add_javascript_error_handler methods',
|
|
64
|
-
id: :on_log_event
|
|
65
|
-
)
|
|
66
|
-
end
|
|
67
60
|
raise Error::WebDriverError, "Don't know how to handle #{kind} events" unless KINDS.include?(kind)
|
|
68
61
|
|
|
69
62
|
enabled = log_listeners[kind].any?
|
|
@@ -60,13 +60,6 @@ module Selenium
|
|
|
60
60
|
#
|
|
61
61
|
|
|
62
62
|
def intercept(&block)
|
|
63
|
-
if browser == :firefox
|
|
64
|
-
WebDriver.logger.deprecate(
|
|
65
|
-
'Driver#intercept on Firefox',
|
|
66
|
-
'the new bidi.network.add_intercept method',
|
|
67
|
-
id: :intercept
|
|
68
|
-
)
|
|
69
|
-
end
|
|
70
63
|
@interceptor ||= DevTools::NetworkInterceptor.new(devtools)
|
|
71
64
|
@interceptor.intercept(&block)
|
|
72
65
|
end
|
|
@@ -52,8 +52,8 @@ module Selenium
|
|
|
52
52
|
path = @service.executable_path || env_path || class_path
|
|
53
53
|
path ? paths_from_service(path) : paths_from_manager
|
|
54
54
|
rescue StandardError => e
|
|
55
|
-
WebDriver.logger.error("Exception occurred: #{e.message}")
|
|
56
|
-
WebDriver.logger.error("Backtrace:\n\t#{e.backtrace&.join("\n\t")}")
|
|
55
|
+
WebDriver.logger.error("Exception occurred: #{e.message}", id: :driver_finder)
|
|
56
|
+
WebDriver.logger.error("Backtrace:\n\t#{e.backtrace&.join("\n\t")}", id: :driver_finder)
|
|
57
57
|
raise Error::NoSuchDriverError, "Unable to obtain #{@service.class::EXECUTABLE}"
|
|
58
58
|
end
|
|
59
59
|
end
|
|
@@ -69,7 +69,8 @@ module Selenium
|
|
|
69
69
|
|
|
70
70
|
def paths_from_service(path)
|
|
71
71
|
exe = @service.class::EXECUTABLE
|
|
72
|
-
WebDriver.logger.debug("Skipping Selenium Manager; path to #{exe} specified in service class: #{path}"
|
|
72
|
+
WebDriver.logger.debug("Skipping Selenium Manager; path to #{exe} specified in service class: #{path}",
|
|
73
|
+
id: :driver_finder)
|
|
73
74
|
Platform.assert_executable(path)
|
|
74
75
|
{driver_path: path}
|
|
75
76
|
end
|
|
@@ -20,15 +20,16 @@
|
|
|
20
20
|
module Selenium
|
|
21
21
|
module WebDriver
|
|
22
22
|
module LocalDriver
|
|
23
|
-
def initialize_local_driver(options, service, url)
|
|
24
|
-
|
|
23
|
+
def initialize_local_driver(options, service, url, http_client, client_config)
|
|
24
|
+
assert_local_arguments(url, http_client, client_config)
|
|
25
25
|
|
|
26
26
|
service ||= Service.send(browser)
|
|
27
27
|
caps = process_options(options, service)
|
|
28
|
-
|
|
28
|
+
http_client ||= Remote::Http::Default.new(client_config: client_config)
|
|
29
|
+
http_client.server_url = service_url(service)
|
|
29
30
|
|
|
30
31
|
begin
|
|
31
|
-
yield(caps,
|
|
32
|
+
yield(caps, http_client) if block_given?
|
|
32
33
|
rescue Selenium::WebDriver::Error::WebDriverError
|
|
33
34
|
@service_manager&.stop
|
|
34
35
|
raise
|
|
@@ -54,6 +55,16 @@ module Selenium
|
|
|
54
55
|
options.browser_version = nil if options.respond_to?(:binary) && options.binary
|
|
55
56
|
options.as_json
|
|
56
57
|
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def assert_local_arguments(url, http_client, client_config)
|
|
62
|
+
if url || client_config&.server_url
|
|
63
|
+
raise ArgumentError, "Can't set the server URL for #{self.class}; the service provides it"
|
|
64
|
+
elsif http_client && client_config
|
|
65
|
+
raise ArgumentError, 'Cannot use both :http_client and :client_config'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
57
68
|
end
|
|
58
69
|
end
|
|
59
70
|
end
|
|
@@ -75,6 +75,8 @@ module Selenium
|
|
|
75
75
|
|
|
76
76
|
@options = opts
|
|
77
77
|
@options[:browser_name] = self.class::BROWSER
|
|
78
|
+
|
|
79
|
+
enable_bidi! if @options[:web_socket_url]
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
#
|
|
@@ -93,6 +95,12 @@ module Selenium
|
|
|
93
95
|
@options[name] = value
|
|
94
96
|
end
|
|
95
97
|
|
|
98
|
+
#
|
|
99
|
+
# Enables WebDriver BiDi by requesting the W3C webSocketUrl capability.
|
|
100
|
+
#
|
|
101
|
+
# @return [Boolean]
|
|
102
|
+
#
|
|
103
|
+
|
|
96
104
|
def enable_bidi!
|
|
97
105
|
@options[:web_socket_url] = true
|
|
98
106
|
end
|
|
@@ -130,11 +138,23 @@ module Selenium
|
|
|
130
138
|
browser_options = {self.class::KEY => browser_options} if defined?(self.class::KEY)
|
|
131
139
|
|
|
132
140
|
process_browser_options(browser_options)
|
|
133
|
-
generate_as_json(w3c_options
|
|
141
|
+
generate_as_json(merge_browser_options(w3c_options, browser_options))
|
|
134
142
|
end
|
|
135
143
|
|
|
136
144
|
private
|
|
137
145
|
|
|
146
|
+
# Preserve a hand-built vendor options hash (e.g. passed through #add_option) by merging it
|
|
147
|
+
# with the binding's own, rather than letting one silently overwrite the other.
|
|
148
|
+
def merge_browser_options(w3c_options, browser_options)
|
|
149
|
+
w3c_options.merge(browser_options) do |_key, w3c_value, browser_value|
|
|
150
|
+
if w3c_value.is_a?(Hash) && browser_value.is_a?(Hash)
|
|
151
|
+
browser_value.merge(w3c_value)
|
|
152
|
+
else
|
|
153
|
+
browser_value
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
138
158
|
def w3c?(key)
|
|
139
159
|
W3C_OPTIONS.include?(key) || key.to_s.include?(':')
|
|
140
160
|
end
|
|
@@ -29,7 +29,6 @@ module Selenium
|
|
|
29
29
|
}.freeze
|
|
30
30
|
|
|
31
31
|
ALLOWED = {type: 'proxyType',
|
|
32
|
-
ftp: 'ftpProxy',
|
|
33
32
|
http: 'httpProxy',
|
|
34
33
|
no_proxy: 'noProxy',
|
|
35
34
|
pac: 'proxyAutoconfigUrl',
|
|
@@ -76,12 +75,6 @@ module Selenium
|
|
|
76
75
|
end
|
|
77
76
|
alias eql? ==
|
|
78
77
|
|
|
79
|
-
def ftp=(value)
|
|
80
|
-
WebDriver.logger.deprecate('FTP proxy support', nil, id: :ftp_proxy)
|
|
81
|
-
self.type = :manual
|
|
82
|
-
@ftp = value
|
|
83
|
-
end
|
|
84
|
-
|
|
85
78
|
def http=(value)
|
|
86
79
|
self.type = :manual
|
|
87
80
|
@http = value
|
|
@@ -143,9 +136,8 @@ module Selenium
|
|
|
143
136
|
def as_json(*)
|
|
144
137
|
json_result = {
|
|
145
138
|
'proxyType' => TYPES[type].downcase,
|
|
146
|
-
'ftpProxy' => ftp,
|
|
147
139
|
'httpProxy' => http,
|
|
148
|
-
'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(',
|
|
140
|
+
'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(',').map(&:strip).reject(&:empty?) : no_proxy,
|
|
149
141
|
'proxyAutoconfigUrl' => pac,
|
|
150
142
|
'sslProxy' => ssl,
|
|
151
143
|
'autodetect' => auto_detect,
|
|
@@ -50,7 +50,8 @@ module Selenium
|
|
|
50
50
|
def binary
|
|
51
51
|
@binary ||= begin
|
|
52
52
|
if (location = ENV.fetch('SE_MANAGER_PATH', nil))
|
|
53
|
-
WebDriver.logger.debug("Selenium Manager set by ENV['SE_MANAGER_PATH']: #{location}"
|
|
53
|
+
WebDriver.logger.debug("Selenium Manager set by ENV['SE_MANAGER_PATH']: #{location}",
|
|
54
|
+
id: :selenium_manager)
|
|
54
55
|
end
|
|
55
56
|
location ||= platform_location
|
|
56
57
|
|
|
@@ -36,7 +36,7 @@ module Selenium
|
|
|
36
36
|
'It should end with .png extension',
|
|
37
37
|
id: :screenshot
|
|
38
38
|
end
|
|
39
|
-
WebDriver.logger.debug("Saving screenshot to #{Dir.pwd}/#{png_path}")
|
|
39
|
+
WebDriver.logger.debug("Saving screenshot to #{Dir.pwd}/#{png_path}", id: :screenshot)
|
|
40
40
|
File.open(png_path, 'wb') { |f| f << screenshot_as(:png, full_page: full_page) }
|
|
41
41
|
end
|
|
42
42
|
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
20
|
require 'selenium/webdriver/common/error'
|
|
21
|
+
require 'selenium/webdriver/common/client_config'
|
|
21
22
|
require 'selenium/webdriver/common/local_driver'
|
|
22
23
|
require 'selenium/webdriver/common/driver_finder'
|
|
23
24
|
require 'selenium/webdriver/common/platform'
|
|
@@ -75,7 +76,6 @@ require 'selenium/webdriver/common/driver_extensions/prints_page'
|
|
|
75
76
|
require 'selenium/webdriver/common/driver_extensions/uploads_files'
|
|
76
77
|
require 'selenium/webdriver/common/driver_extensions/full_page_screenshot'
|
|
77
78
|
require 'selenium/webdriver/common/driver_extensions/has_addons'
|
|
78
|
-
require 'selenium/webdriver/common/driver_extensions/has_bidi'
|
|
79
79
|
require 'selenium/webdriver/common/driver_extensions/has_devtools'
|
|
80
80
|
require 'selenium/webdriver/common/driver_extensions/has_file_downloads'
|
|
81
81
|
require 'selenium/webdriver/common/driver_extensions/has_session_events'
|
|
@@ -30,9 +30,9 @@ module Selenium
|
|
|
30
30
|
class Driver < Chromium::Driver
|
|
31
31
|
include LocalDriver
|
|
32
32
|
|
|
33
|
-
def initialize(options: nil, service: nil, url: nil, **)
|
|
34
|
-
initialize_local_driver(options, service, url) do |caps,
|
|
35
|
-
super(caps: caps,
|
|
33
|
+
def initialize(options: nil, service: nil, url: nil, http_client: nil, client_config: nil, **)
|
|
34
|
+
initialize_local_driver(options, service, url, http_client, client_config) do |caps, client|
|
|
35
|
+
super(caps: caps, http_client: client, **)
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -27,8 +27,11 @@ module Selenium
|
|
|
27
27
|
DRIVER_PATH_ENV_KEY = 'SE_EDGEDRIVER'
|
|
28
28
|
|
|
29
29
|
def initialize(args: nil, **)
|
|
30
|
+
args = Array(args.dup)
|
|
31
|
+
# yes, it is --enable-chrome-logs, even on msedgedriver; skip when CHROME_LOG_FILE is set
|
|
32
|
+
args << '--enable-chrome-logs' unless ENV.key?('CHROME_LOG_FILE') || args.include?('--enable-chrome-logs')
|
|
33
|
+
|
|
30
34
|
if ENV.key?('SE_DEBUG')
|
|
31
|
-
args = Array(args.dup)
|
|
32
35
|
warn_driver_log_override if args.reject! { |arg| arg.include?('log-level') || arg.include?('silent') }
|
|
33
36
|
args << '--verbose'
|
|
34
37
|
end
|
|
@@ -29,16 +29,13 @@ module Selenium
|
|
|
29
29
|
EXTENSIONS = [DriverExtensions::HasAddons,
|
|
30
30
|
DriverExtensions::FullPageScreenshot,
|
|
31
31
|
DriverExtensions::HasContext,
|
|
32
|
-
DriverExtensions::HasBiDi,
|
|
33
|
-
DriverExtensions::HasLogEvents,
|
|
34
|
-
DriverExtensions::HasNetworkInterception,
|
|
35
32
|
DriverExtensions::PrintsPage].freeze
|
|
36
33
|
|
|
37
34
|
include LocalDriver
|
|
38
35
|
|
|
39
|
-
def initialize(options: nil, service: nil, url: nil, **)
|
|
40
|
-
initialize_local_driver(options, service, url) do |caps,
|
|
41
|
-
super(caps: caps,
|
|
36
|
+
def initialize(options: nil, service: nil, url: nil, http_client: nil, client_config: nil, **)
|
|
37
|
+
initialize_local_driver(options, service, url, http_client, client_config) do |caps, client|
|
|
38
|
+
super(caps: caps, http_client: client, **)
|
|
42
39
|
end
|
|
43
40
|
end
|
|
44
41
|
|
|
@@ -69,6 +69,7 @@ module Selenium
|
|
|
69
69
|
@options[:prefs]['remote.active-protocols'] = 1
|
|
70
70
|
@options[:env] ||= {}
|
|
71
71
|
@options[:log] ||= {level: log_level} if log_level
|
|
72
|
+
@vendor_options = {}
|
|
72
73
|
|
|
73
74
|
process_profile(@options.delete(:profile))
|
|
74
75
|
end
|
|
@@ -102,6 +103,23 @@ module Selenium
|
|
|
102
103
|
@options[:prefs][name] = value
|
|
103
104
|
end
|
|
104
105
|
|
|
106
|
+
#
|
|
107
|
+
# Add a Firefox-specific capability nested in the `moz:firefoxOptions`
|
|
108
|
+
# object that is not yet exposed through a dedicated method. This is the
|
|
109
|
+
# escape hatch for legitimate vendor capabilities the bindings do not model.
|
|
110
|
+
#
|
|
111
|
+
# @example Set an option not handled by other methods
|
|
112
|
+
# options = Selenium::WebDriver::Firefox::Options.new
|
|
113
|
+
# options.add_firefox_option('unhandledCapability', 'value')
|
|
114
|
+
#
|
|
115
|
+
# @param [String, Symbol] name Name of the capability, as expected by geckodriver
|
|
116
|
+
# @param [Object] value Value of the capability
|
|
117
|
+
#
|
|
118
|
+
|
|
119
|
+
def add_firefox_option(name, value)
|
|
120
|
+
@vendor_options[name.to_s] = value
|
|
121
|
+
end
|
|
122
|
+
|
|
105
123
|
#
|
|
106
124
|
# Sets Firefox profile.
|
|
107
125
|
#
|
|
@@ -153,6 +171,7 @@ module Selenium
|
|
|
153
171
|
def process_browser_options(browser_options)
|
|
154
172
|
browser_options['moz:debuggerAddress'] = true if @debugger_address
|
|
155
173
|
options = browser_options[KEY]
|
|
174
|
+
options.merge!(@vendor_options)
|
|
156
175
|
options['binary'] ||= Firefox.path if Firefox.path
|
|
157
176
|
options['profile'] = @profile if @profile
|
|
158
177
|
end
|