selenium-webdriver 4.46.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 +29 -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 +53 -23
- data/lib/selenium/webdriver/bidi/protocol/browser.rb +43 -15
- data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +172 -68
- data/lib/selenium/webdriver/bidi/protocol/domain.rb +8 -2
- data/lib/selenium/webdriver/bidi/protocol/emulation.rb +123 -9
- data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
- data/lib/selenium/webdriver/bidi/protocol/input.rb +101 -28
- data/lib/selenium/webdriver/bidi/protocol/log.rb +17 -6
- data/lib/selenium/webdriver/bidi/protocol/network.rb +146 -63
- data/lib/selenium/webdriver/bidi/protocol/permissions.rb +4 -2
- data/lib/selenium/webdriver/bidi/protocol/script.rb +264 -86
- data/lib/selenium/webdriver/bidi/protocol/session.rb +48 -3
- data/lib/selenium/webdriver/bidi/protocol/speculation.rb +4 -2
- data/lib/selenium/webdriver/bidi/protocol/storage.rb +32 -6
- data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +7 -2
- data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +49 -10
- data/lib/selenium/webdriver/bidi/protocol.rb +3 -0
- data/lib/selenium/webdriver/bidi/serialization/record.rb +194 -18
- data/lib/selenium/webdriver/bidi/serialization/union.rb +45 -3
- data/lib/selenium/webdriver/bidi/serialization.rb +13 -1
- data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +529 -45
- data/lib/selenium/webdriver/bidi/support/check_generated.rb +12 -4
- data/lib/selenium/webdriver/bidi/transport.rb +8 -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/driver.rb +11 -0
- 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/options.rb +21 -1
- data/lib/selenium/webdriver/common/proxy.rb +0 -8
- 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 +0 -1
- data/lib/selenium/webdriver/edge/service.rb +4 -1
- data/lib/selenium/webdriver/firefox/driver.rb +0 -3
- data/lib/selenium/webdriver/firefox/options.rb +19 -0
- data/lib/selenium/webdriver/firefox/profile.rb +13 -6
- data/lib/selenium/webdriver/remote/bidi_bridge.rb +39 -12
- data/lib/selenium/webdriver/remote/bridge.rb +1 -1
- data/lib/selenium/webdriver/remote/driver.rb +3 -1
- data/lib/selenium/webdriver/remote/http/default.rb +1 -1
- 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 +6 -3
|
@@ -25,21 +25,29 @@ module BiDiGenerate
|
|
|
25
25
|
# current schema — catching a hand-edit or a forgotten regeneration. Re-renders each module
|
|
26
26
|
# in memory (no file writes) and compares. The .rbs are covered by Steep.
|
|
27
27
|
def self.check!(schema_rootpath)
|
|
28
|
-
|
|
28
|
+
schema = Schema.new(JSON.parse(File.read(schema_path(schema_rootpath))))
|
|
29
29
|
protocol_dir = File.expand_path('../protocol', __dir__)
|
|
30
30
|
template = File.join(__dir__, 'templates', 'module.rb.erb')
|
|
31
31
|
|
|
32
|
-
stale =
|
|
32
|
+
stale = build_ir(schema).filter_map do |mod|
|
|
33
33
|
path = File.join(protocol_dir, "#{mod.filename}.rb")
|
|
34
|
-
File.exist?(path) && File.read(path) == render(mod, template)
|
|
34
|
+
"#{mod.filename}.rb" unless File.exist?(path) && File.read(path) == render(mod, template)
|
|
35
35
|
end
|
|
36
|
+
stale << 'error_code.rb' unless error_module_current?(schema, protocol_dir)
|
|
36
37
|
return if stale.empty?
|
|
37
38
|
|
|
38
|
-
warn "Generated BiDi protocol code is stale or hand-edited: #{stale.
|
|
39
|
+
warn "Generated BiDi protocol code is stale or hand-edited: #{stale.sort.join(', ')}"
|
|
39
40
|
warn 'Regenerate with: bazel run //rb/lib/selenium/webdriver:bidi-generate'
|
|
40
41
|
exit 1
|
|
41
42
|
end
|
|
42
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
|
+
|
|
43
51
|
# $(rootpath) is relative to the runfiles root; __dir__ anchors us there so it resolves the
|
|
44
52
|
# same way locally and on RBE (an execpath would not). This file lives at
|
|
45
53
|
# rb/lib/selenium/webdriver/bidi/support, so expand six levels up to the root — File.expand_path
|
|
@@ -25,13 +25,18 @@ module Selenium
|
|
|
25
25
|
#
|
|
26
26
|
# @api private
|
|
27
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
|
+
|
|
28
33
|
def initialize(connection)
|
|
29
34
|
@connection = connection
|
|
30
35
|
end
|
|
31
36
|
|
|
32
37
|
def execute(cmd:, params: nil, result: nil)
|
|
33
38
|
reply = @connection.send_cmd(method: cmd, params: serialize(params))
|
|
34
|
-
raise
|
|
39
|
+
raise error_for(reply) if reply['error']
|
|
35
40
|
|
|
36
41
|
value = reply['result']
|
|
37
42
|
result ? result.from_json(value) : value
|
|
@@ -43,8 +48,8 @@ module Selenium
|
|
|
43
48
|
params&.as_json || {}
|
|
44
49
|
end
|
|
45
50
|
|
|
46
|
-
def
|
|
47
|
-
|
|
51
|
+
def error_for(reply)
|
|
52
|
+
Protocol::ErrorCode.for(reply['error']).new("#{reply['message']}\n#{reply['stacktrace']}")
|
|
48
53
|
end
|
|
49
54
|
end # Transport
|
|
50
55
|
end # BiDi
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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,7 +136,6 @@ 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
140
|
'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(',').map(&:strip).reject(&:empty?) : no_proxy,
|
|
149
141
|
'proxyAutoconfigUrl' => pac,
|
|
@@ -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
|
|
|
@@ -76,7 +76,6 @@ require 'selenium/webdriver/common/driver_extensions/prints_page'
|
|
|
76
76
|
require 'selenium/webdriver/common/driver_extensions/uploads_files'
|
|
77
77
|
require 'selenium/webdriver/common/driver_extensions/full_page_screenshot'
|
|
78
78
|
require 'selenium/webdriver/common/driver_extensions/has_addons'
|
|
79
|
-
require 'selenium/webdriver/common/driver_extensions/has_bidi'
|
|
80
79
|
require 'selenium/webdriver/common/driver_extensions/has_devtools'
|
|
81
80
|
require 'selenium/webdriver/common/driver_extensions/has_file_downloads'
|
|
82
81
|
require 'selenium/webdriver/common/driver_extensions/has_session_events'
|
|
@@ -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,9 +29,6 @@ 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
|
|
@@ -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
|
|
@@ -40,7 +40,6 @@ module Selenium
|
|
|
40
40
|
LOCK_FILES = %w[.parentlock parent.lock lock].freeze
|
|
41
41
|
|
|
42
42
|
attr_reader :name, :log_file
|
|
43
|
-
attr_writer :secure_ssl, :load_no_focus_lib
|
|
44
43
|
|
|
45
44
|
class << self
|
|
46
45
|
def ini
|
|
@@ -110,19 +109,28 @@ module Selenium
|
|
|
110
109
|
end
|
|
111
110
|
|
|
112
111
|
def port=(port)
|
|
112
|
+
WebDriver.logger.deprecate('Firefox::Profile#port=', 'the Service class', id: :firefox_profile)
|
|
113
113
|
self[WEBDRIVER_PREFS[:port]] = port
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
def secure_ssl=(value)
|
|
117
|
+
WebDriver.logger.deprecate('Firefox::Profile#secure_ssl=', id: :firefox_profile)
|
|
118
|
+
@secure_ssl = value
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def load_no_focus_lib=(value)
|
|
122
|
+
WebDriver.logger.deprecate('Firefox::Profile#load_no_focus_lib=', id: :firefox_profile)
|
|
123
|
+
@load_no_focus_lib = value
|
|
124
|
+
end
|
|
125
|
+
|
|
116
126
|
def log_file=(file)
|
|
117
127
|
@log_file = file
|
|
118
128
|
self[WEBDRIVER_PREFS[:log_file]] = file
|
|
119
129
|
end
|
|
120
130
|
|
|
121
|
-
#
|
|
122
|
-
# Add the extension (directory, .zip or .xpi) at the given path to the profile.
|
|
123
|
-
#
|
|
124
|
-
|
|
125
131
|
def add_extension(path, name = extension_name_for(path))
|
|
132
|
+
WebDriver.logger.deprecate('Firefox::Profile#add_extension', 'Driver#install_addon',
|
|
133
|
+
id: :firefox_profile)
|
|
126
134
|
@extensions[name] = Extension.new(path)
|
|
127
135
|
end
|
|
128
136
|
|
|
@@ -133,7 +141,6 @@ module Selenium
|
|
|
133
141
|
when :manual
|
|
134
142
|
self['network.proxy.type'] = 1
|
|
135
143
|
|
|
136
|
-
set_manual_proxy_preference 'ftp', proxy.ftp
|
|
137
144
|
set_manual_proxy_preference 'http', proxy.http
|
|
138
145
|
set_manual_proxy_preference 'ssl', proxy.ssl
|
|
139
146
|
set_manual_proxy_preference 'socks', proxy.socks
|
|
@@ -18,40 +18,55 @@
|
|
|
18
18
|
# under the License.
|
|
19
19
|
|
|
20
20
|
require 'selenium/webdriver/bidi'
|
|
21
|
-
require 'selenium/webdriver/bidi/
|
|
21
|
+
require 'selenium/webdriver/bidi/protocol'
|
|
22
22
|
|
|
23
23
|
module Selenium
|
|
24
24
|
module WebDriver
|
|
25
25
|
module Remote
|
|
26
26
|
class BiDiBridge < Bridge
|
|
27
|
-
attr_reader :bidi, :
|
|
27
|
+
attr_reader :bidi, :connection
|
|
28
|
+
|
|
29
|
+
READINESS_STATE = {
|
|
30
|
+
'none' => :none,
|
|
31
|
+
'eager' => :interactive,
|
|
32
|
+
'normal' => :complete
|
|
33
|
+
}.freeze
|
|
28
34
|
|
|
29
35
|
def create_session(capabilities)
|
|
30
36
|
super
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
|
|
38
|
+
begin
|
|
39
|
+
@bidi = Selenium::WebDriver::BiDi.new(url: validated_socket_url)
|
|
40
|
+
# Reuse the BiDi object's socket as the connection until the bridge owns it directly.
|
|
41
|
+
@connection = @bidi.ws
|
|
42
|
+
rescue StandardError
|
|
43
|
+
quit
|
|
44
|
+
raise
|
|
45
|
+
end
|
|
35
46
|
end
|
|
36
47
|
|
|
37
48
|
def get(url)
|
|
38
|
-
browsing_context.navigate(url)
|
|
49
|
+
browsing_context.navigate(context: window_handle, url: url, wait: readiness_state)
|
|
50
|
+
nil
|
|
39
51
|
end
|
|
40
52
|
|
|
41
53
|
def go_back
|
|
42
|
-
browsing_context.traverse_history(-1)
|
|
54
|
+
browsing_context.traverse_history(context: window_handle, delta: -1)
|
|
55
|
+
nil
|
|
43
56
|
end
|
|
44
57
|
|
|
45
58
|
def go_forward
|
|
46
|
-
browsing_context.traverse_history(1)
|
|
59
|
+
browsing_context.traverse_history(context: window_handle, delta: 1)
|
|
60
|
+
nil
|
|
47
61
|
end
|
|
48
62
|
|
|
49
63
|
def refresh
|
|
50
|
-
browsing_context.reload
|
|
64
|
+
browsing_context.reload(context: window_handle, wait: readiness_state)
|
|
65
|
+
nil
|
|
51
66
|
end
|
|
52
67
|
|
|
53
68
|
def quit
|
|
54
|
-
bidi
|
|
69
|
+
bidi&.close
|
|
55
70
|
rescue *QUIT_ERRORS
|
|
56
71
|
nil
|
|
57
72
|
ensure
|
|
@@ -64,8 +79,20 @@ module Selenium
|
|
|
64
79
|
|
|
65
80
|
private
|
|
66
81
|
|
|
82
|
+
def validated_socket_url
|
|
83
|
+
url = @capabilities[:web_socket_url]
|
|
84
|
+
return url if url.is_a?(String) && url.start_with?('ws://', 'wss://')
|
|
85
|
+
|
|
86
|
+
raise Error::WebDriverError,
|
|
87
|
+
"BiDi was enabled, but the remote end did not return a valid webSocketUrl: #{url.inspect}."
|
|
88
|
+
end
|
|
89
|
+
|
|
67
90
|
def browsing_context
|
|
68
|
-
@browsing_context ||=
|
|
91
|
+
@browsing_context ||= BiDi::Protocol::BrowsingContext.new(connection)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def readiness_state
|
|
95
|
+
READINESS_STATE.fetch(capabilities[:page_load_strategy] || 'normal')
|
|
69
96
|
end
|
|
70
97
|
end # BiDiBridge
|
|
71
98
|
end # Remote
|
|
@@ -598,7 +598,7 @@ module Selenium
|
|
|
598
598
|
raise(WebDriver::Error::WebDriverError, msg)
|
|
599
599
|
end
|
|
600
600
|
|
|
601
|
-
def
|
|
601
|
+
def connection
|
|
602
602
|
msg = 'BiDi must be enabled by setting #web_socket_url to true in options class'
|
|
603
603
|
raise(WebDriver::Error::WebDriverError, msg)
|
|
604
604
|
end
|
|
@@ -37,7 +37,9 @@ module Selenium
|
|
|
37
37
|
|
|
38
38
|
caps = process_options(options, capabilities)
|
|
39
39
|
http_client ||= Remote::Http::Default.new(client_config: client_config)
|
|
40
|
-
|
|
40
|
+
server_url = url || client_config&.server_url || "http://#{Platform.localhost}:4444/wd/hub"
|
|
41
|
+
http_client.server_url = server_url
|
|
42
|
+
caps['se:remoteUrl'] = server_url.to_s.chomp('/')
|
|
41
43
|
super(caps: caps, http_client: http_client, **)
|
|
42
44
|
@bridge.file_detector = ->((filename, *)) { File.exist?(filename) && filename.to_s }
|
|
43
45
|
command_list = @bridge.command_list
|
|
@@ -124,7 +124,7 @@ module Selenium
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
def follow_redirect(response, redirects)
|
|
127
|
-
WebDriver.logger.debug("Redirect to #{response['Location']}; times: #{redirects}")
|
|
127
|
+
WebDriver.logger.debug("Redirect to #{response['Location']}; times: #{redirects}", id: :redirect)
|
|
128
128
|
raise Error::WebDriverError, 'too many redirects' if redirects >= client_config.max_redirects
|
|
129
129
|
|
|
130
130
|
request(:get, URI.parse(response['Location']), DEFAULT_HEADERS.dup, nil, redirects + 1)
|
|
@@ -25,7 +25,8 @@ module Selenium
|
|
|
25
25
|
|
|
26
26
|
# @see https://developer.apple.com/documentation/webkit/about_webdriver_for_safari
|
|
27
27
|
CAPABILITIES = {automatic_inspection: 'safari:automaticInspection',
|
|
28
|
-
automatic_profiling: 'safari:automaticProfiling'
|
|
28
|
+
automatic_profiling: 'safari:automaticProfiling',
|
|
29
|
+
experimental_web_socket_url: 'safari:experimentalWebSocketUrl'}.freeze
|
|
29
30
|
BROWSER = 'safari'
|
|
30
31
|
TECHNOLOGY_PREVIEW = 'Safari Technology Preview'
|
|
31
32
|
|
|
@@ -43,6 +44,20 @@ module Selenium
|
|
|
43
44
|
def browser_name
|
|
44
45
|
@options[:browser_name] = Safari.technology_preview? ? TECHNOLOGY_PREVIEW : BROWSER
|
|
45
46
|
end
|
|
47
|
+
|
|
48
|
+
#
|
|
49
|
+
# Enables WebDriver BiDi for Safari, which also requires the experimental capability, and
|
|
50
|
+
# warns that Safari's BiDi support is experimental.
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
#
|
|
54
|
+
|
|
55
|
+
def enable_bidi!
|
|
56
|
+
super
|
|
57
|
+
WebDriver.logger.warn("Safari's WebDriver BiDi support is experimental and may be incomplete",
|
|
58
|
+
id: :safari_bidi)
|
|
59
|
+
@options[:experimental_web_socket_url] = true
|
|
60
|
+
end
|
|
46
61
|
end # Options
|
|
47
62
|
end # Safari
|
|
48
63
|
end # WebDriver
|
|
@@ -30,7 +30,7 @@ module Selenium
|
|
|
30
30
|
attr_reader :guarded, :type, :messages, :reason, :tracker
|
|
31
31
|
|
|
32
32
|
def initialize(guarded, type, guards = nil)
|
|
33
|
-
@guarded = guarded
|
|
33
|
+
@guarded = guarded.dup
|
|
34
34
|
@tracker = guards&.bug_tracker || ''
|
|
35
35
|
@messages = guards&.messages || {}
|
|
36
36
|
@messages[:unknown] = 'TODO: Investigate why this is failing and file a bug report'
|
|
@@ -47,7 +47,7 @@ module Selenium
|
|
|
47
47
|
when Symbol
|
|
48
48
|
messages[reason]
|
|
49
49
|
else
|
|
50
|
-
"
|
|
50
|
+
"#{type.to_s.tr('_', ' ')} #{guarded};"
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
case type
|
|
@@ -57,8 +57,10 @@ module Selenium
|
|
|
57
57
|
"Test skipped because it is unreliable in this configuration; #{details}"
|
|
58
58
|
when :skip_unless, :exclusive
|
|
59
59
|
"Test does not apply to this configuration; #{details}"
|
|
60
|
-
|
|
60
|
+
when :pending_if, :pending_unless, :except, :only
|
|
61
61
|
"Test guarded; #{details}"
|
|
62
|
+
else
|
|
63
|
+
raise ArgumentError, "unknown guard type: #{type}"
|
|
62
64
|
end
|
|
63
65
|
end
|
|
64
66
|
|
|
@@ -81,6 +83,21 @@ module Selenium
|
|
|
81
83
|
def exclusive?
|
|
82
84
|
@type == :skip_unless || @type == :exclusive
|
|
83
85
|
end
|
|
86
|
+
|
|
87
|
+
# A pending guard that only applies when the failure matches an expected exception.
|
|
88
|
+
def exception?
|
|
89
|
+
(except? || only?) && !@guarded[:exception].nil?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Whether the exception is the guard's `exception:` class and matches its optional `message:`
|
|
93
|
+
# (Regexp pattern or exact String), following RSpec's `raise_error` semantics.
|
|
94
|
+
def matches_exception?(exception)
|
|
95
|
+
spec = @guarded[:exception]
|
|
96
|
+
return false unless spec && exception.is_a?(spec[:class])
|
|
97
|
+
|
|
98
|
+
message = spec[:message]
|
|
99
|
+
message.nil? || (message.is_a?(Regexp) ? message.match?(exception.message) : message == exception.message)
|
|
100
|
+
end
|
|
84
101
|
end # Guard
|
|
85
102
|
end # Guards
|
|
86
103
|
end # Support
|
|
@@ -41,7 +41,7 @@ module Selenium
|
|
|
41
41
|
def add_condition(name, condition = false, &block)
|
|
42
42
|
condition = false if condition.nil?
|
|
43
43
|
@guard_conditions << GuardCondition.new(name, condition, &block)
|
|
44
|
-
WebDriver.logger.debug "Running with Guard '#{name}' set to: #{condition}"
|
|
44
|
+
WebDriver.logger.debug "Running with Guard '#{name}' set to: #{condition}", id: :guard
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def add_message(name, message)
|
|
@@ -52,12 +52,18 @@ module Selenium
|
|
|
52
52
|
if !skipping_guard.nil?
|
|
53
53
|
[:skip, skipping_guard.message]
|
|
54
54
|
elsif !pending_guard.nil? && ENV.fetch('SKIP_PENDING', nil)
|
|
55
|
-
[:skip, pending_guard.message]
|
|
56
|
-
elsif !pending_guard.nil?
|
|
55
|
+
[:skip, "(skipped by SKIP_PENDING) #{pending_guard.message}"]
|
|
56
|
+
elsif !pending_guard.nil? && !pending_guard.exception?
|
|
57
57
|
[:pending, pending_guard.message]
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# The deferred `exception:` pending guard, evaluated against the failure after the run, else nil.
|
|
62
|
+
def pending_exception_guard
|
|
63
|
+
guard = pending_guard
|
|
64
|
+
guard if disposition.nil? && guard&.exception?
|
|
65
|
+
end
|
|
66
|
+
|
|
61
67
|
def satisfied?(guard)
|
|
62
68
|
@guard_conditions.all? { |condition| condition.satisfied?(guard) }
|
|
63
69
|
end
|