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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +36 -0
  3. data/bin/linux/selenium-manager +0 -0
  4. data/bin/macos/selenium-manager +0 -0
  5. data/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +5971 -0
  6. data/bin/selenium-manager.cdx.json +11629 -0
  7. data/bin/windows/selenium-manager.exe +0 -0
  8. data/lib/selenium/webdriver/{common/driver_extensions/has_bidi.rb → bidi/error.rb} +21 -13
  9. data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +495 -0
  10. data/lib/selenium/webdriver/bidi/protocol/browser.rb +250 -0
  11. data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +798 -0
  12. data/lib/selenium/webdriver/bidi/protocol/domain.rb +46 -0
  13. data/lib/selenium/webdriver/bidi/protocol/emulation.rb +448 -0
  14. data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
  15. data/lib/selenium/webdriver/bidi/protocol/input.rb +354 -0
  16. data/lib/selenium/webdriver/bidi/protocol/log.rb +115 -0
  17. data/lib/selenium/webdriver/bidi/protocol/network.rb +720 -0
  18. data/lib/selenium/webdriver/bidi/protocol/permissions.rb +75 -0
  19. data/lib/selenium/webdriver/bidi/protocol/script.rb +1052 -0
  20. data/lib/selenium/webdriver/bidi/protocol/session.rb +286 -0
  21. data/lib/selenium/webdriver/bidi/protocol/speculation.rb +58 -0
  22. data/lib/selenium/webdriver/bidi/protocol/storage.rb +183 -0
  23. data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +88 -0
  24. data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +132 -0
  25. data/lib/selenium/webdriver/bidi/protocol.rb +42 -0
  26. data/lib/selenium/webdriver/bidi/serialization/record.rb +402 -0
  27. data/lib/selenium/webdriver/bidi/serialization/union.rb +156 -0
  28. data/lib/selenium/webdriver/bidi/serialization.rb +90 -0
  29. data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +1420 -0
  30. data/lib/selenium/webdriver/bidi/support/check_generated.rb +63 -0
  31. data/lib/selenium/webdriver/bidi/transport.rb +57 -0
  32. data/lib/selenium/webdriver/bidi.rb +3 -0
  33. data/lib/selenium/webdriver/chrome/driver.rb +3 -3
  34. data/lib/selenium/webdriver/chrome/service.rb +4 -1
  35. data/lib/selenium/webdriver/chromium/driver.rb +0 -1
  36. data/lib/selenium/webdriver/chromium/options.rb +20 -0
  37. data/lib/selenium/webdriver/common/client_config.rb +97 -0
  38. data/lib/selenium/webdriver/common/driver.rb +13 -2
  39. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -7
  40. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +0 -7
  41. data/lib/selenium/webdriver/common/driver_finder.rb +4 -3
  42. data/lib/selenium/webdriver/common/local_driver.rb +15 -4
  43. data/lib/selenium/webdriver/common/options.rb +21 -1
  44. data/lib/selenium/webdriver/common/proxy.rb +1 -9
  45. data/lib/selenium/webdriver/common/selenium_manager.rb +2 -1
  46. data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
  47. data/lib/selenium/webdriver/common.rb +1 -1
  48. data/lib/selenium/webdriver/edge/driver.rb +3 -3
  49. data/lib/selenium/webdriver/edge/service.rb +4 -1
  50. data/lib/selenium/webdriver/firefox/driver.rb +3 -6
  51. data/lib/selenium/webdriver/firefox/options.rb +19 -0
  52. data/lib/selenium/webdriver/firefox/profile.rb +13 -6
  53. data/lib/selenium/webdriver/ie/driver.rb +3 -3
  54. data/lib/selenium/webdriver/remote/bidi_bridge.rb +41 -9
  55. data/lib/selenium/webdriver/remote/bridge.rb +8 -10
  56. data/lib/selenium/webdriver/remote/driver.rb +14 -4
  57. data/lib/selenium/webdriver/remote/http/common.rb +25 -12
  58. data/lib/selenium/webdriver/remote/http/default.rb +56 -39
  59. data/lib/selenium/webdriver/safari/driver.rb +3 -3
  60. data/lib/selenium/webdriver/safari/options.rb +16 -1
  61. data/lib/selenium/webdriver/support/guards/guard.rb +20 -3
  62. data/lib/selenium/webdriver/support/guards.rb +9 -3
  63. data/lib/selenium/webdriver/version.rb +1 -1
  64. metadata +29 -3
@@ -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
@@ -31,9 +31,9 @@ module Selenium
31
31
 
32
32
  include LocalDriver
33
33
 
34
- def initialize(options: nil, service: nil, url: nil, **)
35
- initialize_local_driver(options, service, url) do |caps, driver_url|
36
- super(caps: caps, url: driver_url, **)
34
+ def initialize(options: nil, service: nil, url: nil, http_client: nil, client_config: nil, **)
35
+ initialize_local_driver(options, service, url, http_client, client_config) do |caps, client|
36
+ super(caps: caps, http_client: client, **)
37
37
  end
38
38
  end
39
39
 
@@ -17,36 +17,56 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
+ require 'selenium/webdriver/bidi'
21
+ require 'selenium/webdriver/bidi/protocol'
22
+
20
23
  module Selenium
21
24
  module WebDriver
22
25
  module Remote
23
26
  class BiDiBridge < Bridge
24
- attr_reader :bidi
27
+ attr_reader :bidi, :connection
28
+
29
+ READINESS_STATE = {
30
+ 'none' => :none,
31
+ 'eager' => :interactive,
32
+ 'normal' => :complete
33
+ }.freeze
25
34
 
26
35
  def create_session(capabilities)
27
36
  super
28
- socket_url = @capabilities[:web_socket_url]
29
- @bidi = Selenium::WebDriver::BiDi.new(url: socket_url)
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
30
46
  end
31
47
 
32
48
  def get(url)
33
- browsing_context.navigate(url)
49
+ browsing_context.navigate(context: window_handle, url: url, wait: readiness_state)
50
+ nil
34
51
  end
35
52
 
36
53
  def go_back
37
- browsing_context.traverse_history(-1)
54
+ browsing_context.traverse_history(context: window_handle, delta: -1)
55
+ nil
38
56
  end
39
57
 
40
58
  def go_forward
41
- browsing_context.traverse_history(1)
59
+ browsing_context.traverse_history(context: window_handle, delta: 1)
60
+ nil
42
61
  end
43
62
 
44
63
  def refresh
45
- browsing_context.reload
64
+ browsing_context.reload(context: window_handle, wait: readiness_state)
65
+ nil
46
66
  end
47
67
 
48
68
  def quit
49
- bidi.close
69
+ bidi&.close
50
70
  rescue *QUIT_ERRORS
51
71
  nil
52
72
  ensure
@@ -59,8 +79,20 @@ module Selenium
59
79
 
60
80
  private
61
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
+
62
90
  def browsing_context
63
- @browsing_context ||= WebDriver::BiDi::BrowsingContext.new(self)
91
+ @browsing_context ||= BiDi::Protocol::BrowsingContext.new(connection)
92
+ end
93
+
94
+ def readiness_state
95
+ READINESS_STATE.fetch(capabilities[:page_load_strategy] || 'normal')
64
96
  end
65
97
  end # BiDiBridge
66
98
  end # Remote
@@ -51,20 +51,13 @@ module Selenium
51
51
  end
52
52
 
53
53
  #
54
- # Initializes the bridge with the given server URL
55
- # @param [String, URI] url url for the remote server
56
- # @param [Object] http_client an HTTP client instance that implements the same protocol as Http::Default
54
+ # @param [Object] http_client a configured HTTP client implementing the same protocol as Http::Default
57
55
  # @api private
58
56
  #
59
57
 
60
- def initialize(url:, http_client: nil)
61
- uri = url.is_a?(URI) ? url : URI.parse(url)
62
- uri.path += '/' unless uri.path.end_with?('/')
63
-
64
- @http = http_client || Http::Default.new
65
- @http.server_url = uri
58
+ def initialize(http_client:)
59
+ @http = http_client
66
60
  @file_detector = nil
67
-
68
61
  @locator_converter = self.class.locator_converter
69
62
  end
70
63
 
@@ -605,6 +598,11 @@ module Selenium
605
598
  raise(WebDriver::Error::WebDriverError, msg)
606
599
  end
607
600
 
601
+ def connection
602
+ msg = 'BiDi must be enabled by setting #web_socket_url to true in options class'
603
+ raise(WebDriver::Error::WebDriverError, msg)
604
+ end
605
+
608
606
  def command_list
609
607
  COMMANDS
610
608
  end
@@ -31,12 +31,16 @@ module Selenium
31
31
  include DriverExtensions::HasFileDownloads
32
32
  include DriverExtensions::HasSessionEvents
33
33
 
34
- def initialize(capabilities: nil, options: nil, service: nil, url: nil, **)
35
- raise ArgumentError, "Can not set :service object on #{self.class}" if service
34
+ def initialize(capabilities: nil, options: nil, service: nil, url: nil, http_client: nil, client_config: nil,
35
+ **)
36
+ assert_arguments(service, url, http_client, client_config)
36
37
 
37
- url ||= "http://#{Platform.localhost}:4444/wd/hub"
38
38
  caps = process_options(options, capabilities)
39
- super(caps: caps, url: url, **)
39
+ http_client ||= Remote::Http::Default.new(client_config: client_config)
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('/')
43
+ super(caps: caps, http_client: http_client, **)
40
44
  @bridge.file_detector = ->((filename, *)) { File.exist?(filename) && filename.to_s }
41
45
  command_list = @bridge.command_list
42
46
  @bridge.extend(WebDriver::Remote::Features)
@@ -45,6 +49,12 @@ module Selenium
45
49
 
46
50
  private
47
51
 
52
+ def assert_arguments(service, url, http_client, client_config)
53
+ raise ArgumentError, "Can not set :service object on #{self.class}" if service
54
+ raise ArgumentError, 'Cannot use both :http_client and :client_config' if http_client && client_config
55
+ raise ArgumentError, 'Cannot use both :url and a ClientConfig#server_url' if url && client_config&.server_url
56
+ end
57
+
48
58
  def devtools_url
49
59
  capabilities['se:cdp']
50
60
  end
@@ -31,15 +31,32 @@ module Selenium
31
31
  BINARY_ENCODINGS = [Encoding::BINARY, Encoding::ASCII_8BIT].freeze
32
32
 
33
33
  class << self
34
- attr_accessor :extra_headers
35
- attr_writer :user_agent
36
-
37
34
  def user_agent
38
- @user_agent ||= "selenium/#{WebDriver::VERSION} (ruby #{Platform.os})"
35
+ ClientConfig.default_user_agent
36
+ end
37
+
38
+ def user_agent=(value)
39
+ ClientConfig.default_user_agent = value
40
+ end
41
+
42
+ def extra_headers
43
+ ClientConfig.default_extra_headers
44
+ end
45
+
46
+ def extra_headers=(value)
47
+ ClientConfig.default_extra_headers = value
39
48
  end
40
49
  end
41
50
 
42
- attr_writer :server_url
51
+ attr_reader :client_config
52
+
53
+ def initialize(client_config: nil)
54
+ @client_config = client_config || ClientConfig.new
55
+ end
56
+
57
+ def server_url=(url)
58
+ client_config.server_url = url
59
+ end
43
60
 
44
61
  def quit_errors
45
62
  [IOError]
@@ -76,17 +93,13 @@ module Selenium
76
93
  def common_headers
77
94
  @common_headers ||= begin
78
95
  headers = DEFAULT_HEADERS.dup
79
- headers['User-Agent'] = Common.user_agent
80
- headers = headers.merge(Common.extra_headers || {})
81
-
82
- headers
96
+ headers['User-Agent'] = client_config.user_agent
97
+ headers.merge(client_config.extra_headers || {})
83
98
  end
84
99
  end
85
100
 
86
101
  def server_url
87
- return @server_url if @server_url
88
-
89
- raise Error::WebDriverError, 'server_url not set'
102
+ client_config.server_url || raise(Error::WebDriverError, 'server_url not set')
90
103
  end
91
104
 
92
105
  def request(*)
@@ -24,19 +24,41 @@ module Selenium
24
24
  module Http
25
25
  # @api private
26
26
  class Default < Common
27
- attr_writer :proxy
28
-
29
- attr_accessor :open_timeout, :read_timeout
30
-
31
27
  # Initializes object.
32
28
  # Warning: Setting {#open_timeout} to non-nil values will cause a separate thread to spawn.
33
29
  # Debuggers that freeze the process will not be able to evaluate any operations if that happens.
30
+ # @param [ClientConfig] client_config - Configuration used to build the HTTP client.
34
31
  # @param [Numeric] open_timeout - Open timeout to apply to HTTP client.
35
32
  # @param [Numeric] read_timeout - Read timeout (seconds) to apply to HTTP client.
36
- def initialize(open_timeout: nil, read_timeout: nil)
37
- @open_timeout = open_timeout
38
- @read_timeout = read_timeout
39
- super()
33
+ def initialize(client_config: nil, open_timeout: nil, read_timeout: nil)
34
+ if client_config && (open_timeout || read_timeout)
35
+ raise ArgumentError, 'Cannot use both :client_config and :open_timeout/:read_timeout'
36
+ end
37
+
38
+ client_config ||= ClientConfig.new
39
+ client_config.open_timeout = open_timeout if open_timeout
40
+ client_config.read_timeout = read_timeout if read_timeout
41
+ super(client_config: client_config)
42
+ end
43
+
44
+ def open_timeout
45
+ client_config.open_timeout
46
+ end
47
+
48
+ def read_timeout
49
+ client_config.read_timeout
50
+ end
51
+
52
+ def open_timeout=(value)
53
+ client_config.open_timeout = value
54
+ end
55
+
56
+ def read_timeout=(value)
57
+ client_config.read_timeout = value
58
+ end
59
+
60
+ def proxy=(value)
61
+ client_config.proxy = value
40
62
  end
41
63
 
42
64
  def close
@@ -94,16 +116,20 @@ module Selenium
94
116
  end
95
117
 
96
118
  if response.is_a? Net::HTTPRedirection
97
- WebDriver.logger.debug("Redirect to #{response['Location']}; times: #{redirects}")
98
- raise Error::WebDriverError, 'too many redirects' if redirects >= MAX_REDIRECTS
99
-
100
- request(:get, URI.parse(response['Location']), DEFAULT_HEADERS.dup, nil, redirects + 1)
119
+ follow_redirect(response, redirects)
101
120
  else
102
121
  WebDriver.logger.debug(" <<< #{response.instance_variable_get(:@header).inspect}", id: :header)
103
122
  create_response response.code, response.body, response.content_type
104
123
  end
105
124
  end
106
125
 
126
+ def follow_redirect(response, redirects)
127
+ WebDriver.logger.debug("Redirect to #{response['Location']}; times: #{redirects}", id: :redirect)
128
+ raise Error::WebDriverError, 'too many redirects' if redirects >= client_config.max_redirects
129
+
130
+ request(:get, URI.parse(response['Location']), DEFAULT_HEADERS.dup, nil, redirects + 1)
131
+ end
132
+
107
133
  def new_request_for(verb, url, headers, payload)
108
134
  req = Net::HTTP.const_get(verb.to_s.capitalize).new(url.path, headers)
109
135
 
@@ -120,52 +146,43 @@ module Selenium
120
146
 
121
147
  def new_http_client
122
148
  if use_proxy?
123
- url = @proxy.http
149
+ url = proxy.http
124
150
  unless proxy.respond_to?(:http) && url
125
151
  raise Error::WebDriverError,
126
- "expected HTTP proxy, got #{@proxy.inspect}"
152
+ "expected HTTP proxy, got #{proxy.inspect}"
127
153
  end
128
154
 
129
- proxy = URI.parse(url)
155
+ proxy_uri = URI.parse(url)
130
156
 
131
- Net::HTTP.new(server_url.host, server_url.port, proxy.host, proxy.port, proxy.user, proxy.password)
157
+ Net::HTTP.new(server_url.host, server_url.port,
158
+ proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
132
159
  else
133
160
  Net::HTTP.new server_url.host, server_url.port
134
161
  end
135
162
  end
136
163
 
137
164
  def proxy
138
- @proxy ||= begin
139
- proxy = ENV.fetch('http_proxy', nil) || ENV.fetch('HTTP_PROXY', nil)
140
- no_proxy = ENV.fetch('no_proxy', nil) || ENV.fetch('NO_PROXY', nil)
141
-
142
- if proxy
143
- proxy = "http://#{proxy}" unless proxy.start_with?('http://')
144
- Proxy.new(http: proxy, no_proxy: no_proxy)
145
- end
146
- end
165
+ client_config.proxy
147
166
  end
148
167
 
149
168
  def use_proxy?
150
169
  return false if proxy.nil?
170
+ return true unless proxy.no_proxy
151
171
 
152
- if proxy.no_proxy
153
- ignored = proxy.no_proxy.split(',').any? do |host|
154
- host == '*' ||
155
- host == server_url.host || (
156
- begin
157
- IPAddr.new(host).include?(server_url.host)
158
- rescue ArgumentError
159
- false
160
- end
161
- )
162
- end
172
+ !proxy_ignored?
173
+ end
163
174
 
164
- !ignored
165
- else
166
- true
175
+ def proxy_ignored?
176
+ proxy.no_proxy.split(',').map(&:strip).reject(&:empty?).any? do |host|
177
+ host == '*' || host == server_url.host || ip_match?(host)
167
178
  end
168
179
  end
180
+
181
+ def ip_match?(host)
182
+ IPAddr.new(host).include?(server_url.host)
183
+ rescue ArgumentError
184
+ false
185
+ end
169
186
  end # Default
170
187
  end # Http
171
188
  end # Remote
@@ -31,9 +31,9 @@ module Selenium
31
31
 
32
32
  include LocalDriver
33
33
 
34
- def initialize(options: nil, service: nil, url: nil, **)
35
- initialize_local_driver(options, service, url) do |caps, driver_url|
36
- super(caps: caps, url: driver_url, **)
34
+ def initialize(options: nil, service: nil, url: nil, http_client: nil, client_config: nil, **)
35
+ initialize_local_driver(options, service, url, http_client, client_config) do |caps, client|
36
+ super(caps: caps, http_client: client, **)
37
37
  end
38
38
  end
39
39
 
@@ -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'}.freeze
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
- "Guarded by #{guarded};"
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
- else
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
@@ -19,6 +19,6 @@
19
19
 
20
20
  module Selenium
21
21
  module WebDriver
22
- VERSION = '4.45.0'
22
+ VERSION = '4.47.0'
23
23
  end # WebDriver
24
24
  end # Selenium
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selenium-webdriver
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.45.0
4
+ version: 4.47.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Rodionov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-06-16 00:00:00.000000000 Z
13
+ date: 2026-08-10 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: base64
@@ -273,6 +273,8 @@ files:
273
273
  - README.md
274
274
  - bin/linux/selenium-manager
275
275
  - bin/macos/selenium-manager
276
+ - bin/selenium-manager-THIRD-PARTY-NOTICES.txt
277
+ - bin/selenium-manager.cdx.json
276
278
  - bin/windows/selenium-manager.exe
277
279
  - lib/selenium-webdriver.rb
278
280
  - lib/selenium/server.rb
@@ -285,6 +287,7 @@ files:
285
287
  - lib/selenium/webdriver/bidi.rb
286
288
  - lib/selenium/webdriver/bidi/browser.rb
287
289
  - lib/selenium/webdriver/bidi/browsing_context.rb
290
+ - lib/selenium/webdriver/bidi/error.rb
288
291
  - lib/selenium/webdriver/bidi/log_handler.rb
289
292
  - lib/selenium/webdriver/bidi/network.rb
290
293
  - lib/selenium/webdriver/bidi/network/cookies.rb
@@ -295,8 +298,31 @@ files:
295
298
  - lib/selenium/webdriver/bidi/network/intercepted_request.rb
296
299
  - lib/selenium/webdriver/bidi/network/intercepted_response.rb
297
300
  - lib/selenium/webdriver/bidi/network/url_pattern.rb
301
+ - lib/selenium/webdriver/bidi/protocol.rb
302
+ - lib/selenium/webdriver/bidi/protocol/bluetooth.rb
303
+ - lib/selenium/webdriver/bidi/protocol/browser.rb
304
+ - lib/selenium/webdriver/bidi/protocol/browsing_context.rb
305
+ - lib/selenium/webdriver/bidi/protocol/domain.rb
306
+ - lib/selenium/webdriver/bidi/protocol/emulation.rb
307
+ - lib/selenium/webdriver/bidi/protocol/error_code.rb
308
+ - lib/selenium/webdriver/bidi/protocol/input.rb
309
+ - lib/selenium/webdriver/bidi/protocol/log.rb
310
+ - lib/selenium/webdriver/bidi/protocol/network.rb
311
+ - lib/selenium/webdriver/bidi/protocol/permissions.rb
312
+ - lib/selenium/webdriver/bidi/protocol/script.rb
313
+ - lib/selenium/webdriver/bidi/protocol/session.rb
314
+ - lib/selenium/webdriver/bidi/protocol/speculation.rb
315
+ - lib/selenium/webdriver/bidi/protocol/storage.rb
316
+ - lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb
317
+ - lib/selenium/webdriver/bidi/protocol/web_extension.rb
318
+ - lib/selenium/webdriver/bidi/serialization.rb
319
+ - lib/selenium/webdriver/bidi/serialization/record.rb
320
+ - lib/selenium/webdriver/bidi/serialization/union.rb
298
321
  - lib/selenium/webdriver/bidi/session.rb
299
322
  - lib/selenium/webdriver/bidi/struct.rb
323
+ - lib/selenium/webdriver/bidi/support/bidi_generate.rb
324
+ - lib/selenium/webdriver/bidi/support/check_generated.rb
325
+ - lib/selenium/webdriver/bidi/transport.rb
300
326
  - lib/selenium/webdriver/chrome.rb
301
327
  - lib/selenium/webdriver/chrome/driver.rb
302
328
  - lib/selenium/webdriver/chrome/features.rb
@@ -312,13 +338,13 @@ files:
312
338
  - lib/selenium/webdriver/common/action_builder.rb
313
339
  - lib/selenium/webdriver/common/alert.rb
314
340
  - lib/selenium/webdriver/common/child_process.rb
341
+ - lib/selenium/webdriver/common/client_config.rb
315
342
  - lib/selenium/webdriver/common/driver.rb
316
343
  - lib/selenium/webdriver/common/driver_extensions/downloads_files.rb
317
344
  - lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb
318
345
  - lib/selenium/webdriver/common/driver_extensions/has_addons.rb
319
346
  - lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb
320
347
  - lib/selenium/webdriver/common/driver_extensions/has_authentication.rb
321
- - lib/selenium/webdriver/common/driver_extensions/has_bidi.rb
322
348
  - lib/selenium/webdriver/common/driver_extensions/has_casting.rb
323
349
  - lib/selenium/webdriver/common/driver_extensions/has_cdp.rb
324
350
  - lib/selenium/webdriver/common/driver_extensions/has_context.rb