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
@@ -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
@@ -35,6 +35,9 @@ module Selenium
35
35
  @ws = WebSocketConnection.new(url: url)
36
36
  end
37
37
 
38
+ # @api private
39
+ attr_reader :ws
40
+
38
41
  def close
39
42
  @ws.close
40
43
  end
@@ -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, driver_url|
35
- super(caps: caps, url: driver_url, **)
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
@@ -27,7 +27,6 @@ module Selenium
27
27
 
28
28
  class Driver < WebDriver::Driver
29
29
  EXTENSIONS = [DriverExtensions::HasCDP,
30
- DriverExtensions::HasBiDi,
31
30
  DriverExtensions::HasCasting,
32
31
  DriverExtensions::HasFedCmDialog,
33
32
  DriverExtensions::HasNetworkConditions,
@@ -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:, url:, http_client: nil)
333
+ def create_bridge(caps:, http_client:)
323
334
  klass = caps['webSocketUrl'] ? Remote::BiDiBridge : Remote::Bridge
324
- klass.new(http_client: http_client, url: url).tap do |bridge|
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
- raise ArgumentError, "Can't initialize #{self.class} with :url" if url
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
- url = service_url(service)
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, url) if block_given?
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.merge(browser_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(', ') : no_proxy,
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, driver_url|
35
- super(caps: caps, url: driver_url, **)
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, driver_url|
41
- super(caps: caps, url: driver_url, **)
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