selenium-webdriver 4.44.0 → 4.46.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +19 -0
  3. data/Gemfile +1 -4
  4. data/README.md +1 -1
  5. data/bin/linux/selenium-manager +0 -0
  6. data/bin/macos/selenium-manager +0 -0
  7. data/bin/windows/selenium-manager.exe +0 -0
  8. data/lib/selenium/webdriver/atoms/findElements.js +272 -68
  9. data/lib/selenium/webdriver/atoms/getAttribute.js +152 -17
  10. data/lib/selenium/webdriver/atoms/isDisplayed.js +385 -39
  11. data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +465 -0
  12. data/lib/selenium/webdriver/bidi/protocol/browser.rb +222 -0
  13. data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +694 -0
  14. data/lib/selenium/webdriver/bidi/protocol/domain.rb +40 -0
  15. data/lib/selenium/webdriver/bidi/protocol/emulation.rb +334 -0
  16. data/lib/selenium/webdriver/bidi/protocol/input.rb +281 -0
  17. data/lib/selenium/webdriver/bidi/protocol/log.rb +104 -0
  18. data/lib/selenium/webdriver/bidi/protocol/network.rb +637 -0
  19. data/lib/selenium/webdriver/bidi/protocol/permissions.rb +73 -0
  20. data/lib/selenium/webdriver/bidi/protocol/script.rb +874 -0
  21. data/lib/selenium/webdriver/bidi/protocol/session.rb +241 -0
  22. data/lib/selenium/webdriver/bidi/protocol/speculation.rb +56 -0
  23. data/lib/selenium/webdriver/bidi/protocol/storage.rb +157 -0
  24. data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +83 -0
  25. data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +93 -0
  26. data/lib/selenium/webdriver/bidi/protocol.rb +39 -0
  27. data/lib/selenium/webdriver/bidi/serialization/record.rb +226 -0
  28. data/lib/selenium/webdriver/bidi/serialization/union.rb +114 -0
  29. data/lib/selenium/webdriver/bidi/serialization.rb +78 -0
  30. data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +936 -0
  31. data/lib/selenium/webdriver/bidi/support/check_generated.rb +55 -0
  32. data/lib/selenium/webdriver/bidi/transport.rb +52 -0
  33. data/lib/selenium/webdriver/bidi.rb +3 -0
  34. data/lib/selenium/webdriver/chrome/driver.rb +3 -3
  35. data/lib/selenium/webdriver/chromium/options.rb +3 -1
  36. data/lib/selenium/webdriver/chromium/profile.rb +6 -0
  37. data/lib/selenium/webdriver/common/client_config.rb +97 -0
  38. data/lib/selenium/webdriver/common/driver.rb +2 -2
  39. data/lib/selenium/webdriver/common/driver_finder.rb +32 -23
  40. data/lib/selenium/webdriver/common/local_driver.rb +19 -12
  41. data/lib/selenium/webdriver/common/proxy.rb +1 -1
  42. data/lib/selenium/webdriver/common/service.rb +1 -11
  43. data/lib/selenium/webdriver/common.rb +1 -0
  44. data/lib/selenium/webdriver/edge/driver.rb +3 -3
  45. data/lib/selenium/webdriver/firefox/driver.rb +3 -3
  46. data/lib/selenium/webdriver/ie/driver.rb +3 -3
  47. data/lib/selenium/webdriver/remote/bidi_bridge.rb +6 -1
  48. data/lib/selenium/webdriver/remote/bridge.rb +8 -10
  49. data/lib/selenium/webdriver/remote/driver.rb +12 -4
  50. data/lib/selenium/webdriver/remote/http/common.rb +25 -12
  51. data/lib/selenium/webdriver/remote/http/curb.rb +6 -8
  52. data/lib/selenium/webdriver/remote/http/default.rb +56 -39
  53. data/lib/selenium/webdriver/safari/driver.rb +3 -3
  54. data/lib/selenium/webdriver/safari/options.rb +8 -4
  55. data/lib/selenium/webdriver/safari.rb +1 -6
  56. data/lib/selenium/webdriver/support/guards/guard.rb +10 -11
  57. data/lib/selenium/webdriver/support/guards.rb +2 -1
  58. data/lib/selenium/webdriver/version.rb +1 -1
  59. data/selenium-webdriver.gemspec +1 -1
  60. metadata +27 -4
@@ -0,0 +1,55 @@
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
+ modules = build_ir(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 = modules.reject do |mod|
33
+ path = File.join(protocol_dir, "#{mod.filename}.rb")
34
+ File.exist?(path) && File.read(path) == render(mod, template)
35
+ end
36
+ return if stale.empty?
37
+
38
+ warn "Generated BiDi protocol code is stale or hand-edited: #{stale.map { |m| "#{m.filename}.rb" }.sort.join(', ')}"
39
+ warn 'Regenerate with: bazel run //rb/lib/selenium/webdriver:bidi-generate'
40
+ exit 1
41
+ end
42
+
43
+ # $(rootpath) is relative to the runfiles root; __dir__ anchors us there so it resolves the
44
+ # same way locally and on RBE (an execpath would not). This file lives at
45
+ # rb/lib/selenium/webdriver/bidi/support, so expand six levels up to the root — File.expand_path
46
+ # is separator-agnostic, unlike stripping a "/"-spelled suffix (which would miss on Windows).
47
+ # The cwd-relative rootpath fallback matches how spec_support's `rlocation` resolves.
48
+ def self.schema_path(rootpath)
49
+ runfiles_root = File.expand_path('../../../../../..', __dir__)
50
+ [File.join(runfiles_root, rootpath), rootpath].find { |p| File.exist?(p) } ||
51
+ raise("BiDi schema not found (looked for #{rootpath})")
52
+ end
53
+ end
54
+
55
+ BiDiGenerate.check!(ARGV.fetch(0)) if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,52 @@
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
+ def initialize(connection)
29
+ @connection = connection
30
+ end
31
+
32
+ def execute(cmd:, params: nil, result: nil)
33
+ reply = @connection.send_cmd(method: cmd, params: serialize(params))
34
+ raise Error::WebDriverError, error_message(reply) if reply['error']
35
+
36
+ value = reply['result']
37
+ result ? result.from_json(value) : value
38
+ end
39
+
40
+ private
41
+
42
+ def serialize(params)
43
+ params&.as_json || {}
44
+ end
45
+
46
+ def error_message(reply)
47
+ "#{reply['error']}: #{reply['message']}\n#{reply['stacktrace']}"
48
+ end
49
+ end # Transport
50
+ end # BiDi
51
+ end # WebDriver
52
+ 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
 
@@ -49,7 +49,9 @@ module Selenium
49
49
  # options = Selenium::WebDriver::Chrome::Options.new(args: ['start-maximized', 'user-data-dir=/tmp/temp_profile'])
50
50
  # driver = Selenium::WebDriver.for(:chrome, options: options)
51
51
  #
52
- # @param [Profile] profile An instance of a Chrome::Profile Class
52
+ # @param [Profile] profile (Deprecated) An instance of a Chrome::Profile class.
53
+ # Use {#add_argument} with `--user-data-dir=...`, {#add_preference}, and
54
+ # {#add_extension} instead.
53
55
  # @param [Hash] opts the pre-defined options to create the Chrome::Options with
54
56
  # @option opts [Array] encoded_extensions List of extensions that do not need to be Base64 encoded
55
57
  # @option opts [Array<String>] args List of command-line arguments to use when starting Chrome
@@ -28,6 +28,12 @@ module Selenium
28
28
  include ProfileHelper
29
29
 
30
30
  def initialize(model = nil)
31
+ WebDriver.logger.deprecate(
32
+ 'Chromium::Profile (including Chrome::Profile and Edge::Profile)',
33
+ "Options#add_argument('--user-data-dir=...'), Options#add_preference, and Options#add_extension",
34
+ id: :chromium_profile
35
+ )
36
+
31
37
  @model = verify_model(model)
32
38
  @extensions = []
33
39
  @encoded_extensions = []
@@ -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
@@ -319,9 +319,9 @@ module Selenium
319
319
 
320
320
  attr_reader :bridge
321
321
 
322
- def create_bridge(caps:, url:, http_client: nil)
322
+ def create_bridge(caps:, http_client:)
323
323
  klass = caps['webSocketUrl'] ? Remote::BiDiBridge : Remote::Bridge
324
- klass.new(http_client: http_client, url: url).tap do |bridge|
324
+ klass.new(http_client: http_client).tap do |bridge|
325
325
  bridge.create_session(caps)
326
326
  end
327
327
  end
@@ -26,6 +26,8 @@ module Selenium
26
26
  new(options, service_class.new).driver_path
27
27
  end
28
28
 
29
+ # @param options [Options, nil] when nil driver parsed from Service::EXECUTABLE
30
+ # @param service [Service]
29
31
  def initialize(options, service)
30
32
  @options = options
31
33
  @service = service
@@ -47,36 +49,43 @@ module Selenium
47
49
 
48
50
  def paths
49
51
  @paths ||= begin
50
- path = @service.class.driver_path
51
- path = path.call if path.is_a?(Proc)
52
- exe = @service.class::EXECUTABLE
53
- if path
54
- WebDriver.logger.debug("Skipping Selenium Manager; path to #{exe} specified in service class: #{path}")
55
- Platform.assert_executable(path)
56
- {driver_path: path}
57
- else
58
- output = SeleniumManager.binary_paths(*to_args)
59
- formatted = {driver_path: Platform.cygwin_path(output['driver_path'], only_cygwin: true),
60
- browser_path: Platform.cygwin_path(output['browser_path'], only_cygwin: true)}
61
- Platform.assert_executable(formatted[:driver_path])
62
-
63
- browser_path = formatted[:browser_path]
64
- Platform.assert_executable(browser_path)
65
- if @options.respond_to?(:binary) && @options.binary.nil?
66
- @options.binary = browser_path
67
- @options.browser_version = nil
68
- end
69
-
70
- formatted
71
- end
52
+ path = @service.executable_path || env_path || class_path
53
+ path ? paths_from_service(path) : paths_from_manager
72
54
  rescue StandardError => e
73
55
  WebDriver.logger.error("Exception occurred: #{e.message}")
74
56
  WebDriver.logger.error("Backtrace:\n\t#{e.backtrace&.join("\n\t")}")
75
- raise Error::NoSuchDriverError, "Unable to obtain #{exe}"
57
+ raise Error::NoSuchDriverError, "Unable to obtain #{@service.class::EXECUTABLE}"
76
58
  end
77
59
  end
78
60
 
61
+ def env_path
62
+ ENV.fetch(@service.class::DRIVER_PATH_ENV_KEY, nil)
63
+ end
64
+
65
+ def class_path
66
+ path = @service.class.driver_path
67
+ path.is_a?(Proc) ? path.call : path
68
+ end
69
+
70
+ def paths_from_service(path)
71
+ exe = @service.class::EXECUTABLE
72
+ WebDriver.logger.debug("Skipping Selenium Manager; path to #{exe} specified in service class: #{path}")
73
+ Platform.assert_executable(path)
74
+ {driver_path: path}
75
+ end
76
+
77
+ def paths_from_manager
78
+ output = SeleniumManager.binary_paths(*to_args)
79
+ formatted = {driver_path: Platform.cygwin_path(output['driver_path'], only_cygwin: true),
80
+ browser_path: Platform.cygwin_path(output['browser_path'], only_cygwin: true)}
81
+ Platform.assert_executable(formatted[:driver_path])
82
+ Platform.assert_executable(formatted[:browser_path])
83
+ formatted
84
+ end
85
+
79
86
  def to_args
87
+ return ['--driver', @service.class::EXECUTABLE] unless @options
88
+
80
89
  args = ['--browser', @options.browser_name]
81
90
  if @options.browser_version
82
91
  args << '--browser-version'
@@ -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
@@ -48,16 +49,22 @@ module Selenium
48
49
  raise ArgumentError, ":options must be an instance of #{default_options.class}"
49
50
  end
50
51
 
51
- service.executable_path ||= begin
52
- finder = WebDriver::DriverFinder.new(options, service)
53
- if options.respond_to?(:binary) && finder.browser_path?
54
- options.binary = finder.browser_path
55
- options.browser_version = nil
56
- end
57
- finder.driver_path
58
- end
52
+ finder = WebDriver::DriverFinder.new(options, service)
53
+ options.binary = finder.browser_path if options.respond_to?(:binary) && finder.browser_path?
54
+ service.executable_path = finder.driver_path
55
+ options.browser_version = nil if options.respond_to?(:binary) && options.binary
59
56
  options.as_json
60
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
61
68
  end
62
69
  end
63
70
  end
@@ -145,7 +145,7 @@ module Selenium
145
145
  'proxyType' => TYPES[type].downcase,
146
146
  'ftpProxy' => ftp,
147
147
  'httpProxy' => http,
148
- 'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(', ') : no_proxy,
148
+ 'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(',').map(&:strip).reject(&:empty?) : no_proxy,
149
149
  'proxyAutoconfigUrl' => pac,
150
150
  'sslProxy' => ssl,
151
151
  'autodetect' => auto_detect,
@@ -69,7 +69,6 @@ module Selenium
69
69
  def initialize(path: nil, port: nil, log: nil, args: nil)
70
70
  port ||= self.class::DEFAULT_PORT
71
71
  args ||= []
72
- path ||= env_path
73
72
 
74
73
  @executable_path = path
75
74
  @host = Platform.localhost
@@ -88,7 +87,7 @@ module Selenium
88
87
  end
89
88
 
90
89
  def launch
91
- @executable_path ||= env_path || find_driver_path
90
+ @executable_path ||= DriverFinder.new(nil, self).driver_path
92
91
  ServiceManager.new(self).tap(&:start)
93
92
  end
94
93
 
@@ -96,15 +95,6 @@ module Selenium
96
95
  self.class::SHUTDOWN_SUPPORTED
97
96
  end
98
97
 
99
- def find_driver_path
100
- default_options = WebDriver.const_get("#{self.class.name&.split('::')&.[](2)}::Options").new
101
- DriverFinder.new(default_options, self).driver_path
102
- end
103
-
104
- def env_path
105
- ENV.fetch(self.class::DRIVER_PATH_ENV_KEY, nil)
106
- end
107
-
108
98
  private
109
99
 
110
100
  def warn_driver_log_override
@@ -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'
@@ -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
 
@@ -36,9 +36,9 @@ module Selenium
36
36
 
37
37
  include LocalDriver
38
38
 
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, **)
39
+ def initialize(options: nil, service: nil, url: nil, http_client: nil, client_config: nil, **)
40
+ initialize_local_driver(options, service, url, http_client, client_config) do |caps, client|
41
+ super(caps: caps, http_client: client, **)
42
42
  end
43
43
  end
44
44
 
@@ -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,16 +17,21 @@
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/transport'
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, :transport
25
28
 
26
29
  def create_session(capabilities)
27
30
  super
28
31
  socket_url = @capabilities[:web_socket_url]
29
32
  @bidi = Selenium::WebDriver::BiDi.new(url: socket_url)
33
+ # Share the BiDi object's socket until the bridge owns the connection directly.
34
+ @transport = BiDi::Transport.new(@bidi.ws)
30
35
  end
31
36
 
32
37
  def get(url)
@@ -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 transport
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,14 @@ 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
+ http_client.server_url = url || client_config&.server_url || "http://#{Platform.localhost}:4444/wd/hub"
41
+ super(caps: caps, http_client: http_client, **)
40
42
  @bridge.file_detector = ->((filename, *)) { File.exist?(filename) && filename.to_s }
41
43
  command_list = @bridge.command_list
42
44
  @bridge.extend(WebDriver::Remote::Features)
@@ -45,6 +47,12 @@ module Selenium
45
47
 
46
48
  private
47
49
 
50
+ def assert_arguments(service, url, http_client, client_config)
51
+ raise ArgumentError, "Can not set :service object on #{self.class}" if service
52
+ raise ArgumentError, 'Cannot use both :http_client and :client_config' if http_client && client_config
53
+ raise ArgumentError, 'Cannot use both :url and a ClientConfig#server_url' if url && client_config&.server_url
54
+ end
55
+
48
56
  def devtools_url
49
57
  capabilities['se:cdp']
50
58
  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(*)