selenium-webdriver 4.35.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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +111 -0
  3. data/Gemfile +1 -4
  4. data/LICENSE +1 -1
  5. data/NOTICE +1 -1
  6. data/README.md +43 -23
  7. data/bin/linux/selenium-manager +0 -0
  8. data/bin/macos/selenium-manager +0 -0
  9. data/bin/selenium-manager-THIRD-PARTY-NOTICES.txt +5971 -0
  10. data/bin/selenium-manager.cdx.json +11629 -0
  11. data/bin/windows/selenium-manager.exe +0 -0
  12. data/lib/selenium/server.rb +30 -5
  13. data/lib/selenium/webdriver/atoms/findElements.js +272 -54
  14. data/lib/selenium/webdriver/atoms/getAttribute.js +152 -6
  15. data/lib/selenium/webdriver/atoms/isDisplayed.js +385 -28
  16. data/lib/selenium/webdriver/bidi/browser.rb +71 -0
  17. data/lib/selenium/webdriver/bidi/browsing_context.rb +3 -2
  18. data/lib/selenium/webdriver/bidi/{log/filter_by.rb → error.rb} +20 -16
  19. data/lib/selenium/webdriver/bidi/log_handler.rb +5 -0
  20. data/lib/selenium/webdriver/bidi/network/cookies.rb +13 -9
  21. data/lib/selenium/webdriver/bidi/network/credentials.rb +4 -0
  22. data/lib/selenium/webdriver/bidi/network/headers.rb +4 -0
  23. data/lib/selenium/webdriver/bidi/network/intercepted_auth.rb +4 -0
  24. data/lib/selenium/webdriver/bidi/network/intercepted_item.rb +4 -0
  25. data/lib/selenium/webdriver/bidi/network/intercepted_request.rb +20 -4
  26. data/lib/selenium/webdriver/bidi/network/intercepted_response.rb +24 -6
  27. data/lib/selenium/webdriver/bidi/network/url_pattern.rb +4 -0
  28. data/lib/selenium/webdriver/bidi/network.rb +18 -9
  29. data/lib/selenium/webdriver/bidi/protocol/bluetooth.rb +495 -0
  30. data/lib/selenium/webdriver/bidi/protocol/browser.rb +250 -0
  31. data/lib/selenium/webdriver/bidi/protocol/browsing_context.rb +798 -0
  32. data/lib/selenium/webdriver/bidi/{log/base_log_entry.rb → protocol/domain.rb} +22 -11
  33. data/lib/selenium/webdriver/bidi/protocol/emulation.rb +448 -0
  34. data/lib/selenium/webdriver/bidi/protocol/error_code.rb +66 -0
  35. data/lib/selenium/webdriver/bidi/protocol/input.rb +354 -0
  36. data/lib/selenium/webdriver/bidi/protocol/log.rb +115 -0
  37. data/lib/selenium/webdriver/bidi/protocol/network.rb +720 -0
  38. data/lib/selenium/webdriver/bidi/protocol/permissions.rb +75 -0
  39. data/lib/selenium/webdriver/bidi/protocol/script.rb +1052 -0
  40. data/lib/selenium/webdriver/bidi/protocol/session.rb +286 -0
  41. data/lib/selenium/webdriver/bidi/protocol/speculation.rb +58 -0
  42. data/lib/selenium/webdriver/bidi/protocol/storage.rb +183 -0
  43. data/lib/selenium/webdriver/bidi/protocol/user_agent_client_hints.rb +88 -0
  44. data/lib/selenium/webdriver/bidi/protocol/web_extension.rb +132 -0
  45. data/lib/selenium/webdriver/bidi/protocol.rb +42 -0
  46. data/lib/selenium/webdriver/bidi/serialization/record.rb +402 -0
  47. data/lib/selenium/webdriver/bidi/serialization/union.rb +156 -0
  48. data/lib/selenium/webdriver/bidi/serialization.rb +90 -0
  49. data/lib/selenium/webdriver/bidi/session.rb +4 -0
  50. data/lib/selenium/webdriver/bidi/support/bidi_generate.rb +1420 -0
  51. data/lib/selenium/webdriver/bidi/support/check_generated.rb +63 -0
  52. data/lib/selenium/webdriver/bidi/transport.rb +57 -0
  53. data/lib/selenium/webdriver/bidi.rb +4 -1
  54. data/lib/selenium/webdriver/chrome/driver.rb +4 -3
  55. data/lib/selenium/webdriver/chrome/service.rb +13 -0
  56. data/lib/selenium/webdriver/chromium/driver.rb +0 -1
  57. data/lib/selenium/webdriver/chromium/options.rb +23 -1
  58. data/lib/selenium/webdriver/chromium/profile.rb +6 -0
  59. data/lib/selenium/webdriver/common/child_process.rb +2 -1
  60. data/lib/selenium/webdriver/common/client_config.rb +97 -0
  61. data/lib/selenium/webdriver/common/driver.rb +13 -7
  62. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +0 -7
  63. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +0 -7
  64. data/lib/selenium/webdriver/common/driver_extensions/{has_bidi.rb → has_session_events.rb} +19 -7
  65. data/lib/selenium/webdriver/common/driver_finder.rb +35 -25
  66. data/lib/selenium/webdriver/common/error.rb +10 -3
  67. data/lib/selenium/webdriver/common/local_driver.rb +29 -12
  68. data/lib/selenium/webdriver/common/logger.rb +28 -0
  69. data/lib/selenium/webdriver/common/manager.rb +2 -0
  70. data/lib/selenium/webdriver/common/options.rb +41 -2
  71. data/lib/selenium/webdriver/common/platform.rb +1 -3
  72. data/lib/selenium/webdriver/common/proxy.rb +1 -9
  73. data/lib/selenium/webdriver/common/selenium_manager.rb +2 -1
  74. data/lib/selenium/webdriver/common/service.rb +4 -8
  75. data/lib/selenium/webdriver/common/service_manager.rb +36 -4
  76. data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
  77. data/lib/selenium/webdriver/common/takes_screenshot.rb +1 -1
  78. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +1 -1
  79. data/lib/selenium/webdriver/common/wait.rb +4 -1
  80. data/lib/selenium/webdriver/common/websocket_connection.rb +73 -37
  81. data/lib/selenium/webdriver/common.rb +2 -1
  82. data/lib/selenium/webdriver/devtools/response.rb +3 -3
  83. data/lib/selenium/webdriver/devtools.rb +1 -1
  84. data/lib/selenium/webdriver/edge/driver.rb +4 -3
  85. data/lib/selenium/webdriver/edge/service.rb +14 -0
  86. data/lib/selenium/webdriver/firefox/driver.rb +4 -6
  87. data/lib/selenium/webdriver/firefox/options.rb +19 -0
  88. data/lib/selenium/webdriver/firefox/profile.rb +13 -6
  89. data/lib/selenium/webdriver/firefox/service.rb +31 -2
  90. data/lib/selenium/webdriver/ie/driver.rb +4 -3
  91. data/lib/selenium/webdriver/ie/service.rb +10 -0
  92. data/lib/selenium/webdriver/remote/bidi_bridge.rb +44 -10
  93. data/lib/selenium/webdriver/remote/bridge.rb +20 -14
  94. data/lib/selenium/webdriver/remote/driver.rb +15 -4
  95. data/lib/selenium/webdriver/remote/features.rb +26 -1
  96. data/lib/selenium/webdriver/remote/http/common.rb +57 -12
  97. data/lib/selenium/webdriver/remote/http/curb.rb +6 -8
  98. data/lib/selenium/webdriver/remote/http/default.rb +56 -39
  99. data/lib/selenium/webdriver/safari/driver.rb +4 -3
  100. data/lib/selenium/webdriver/safari/options.rb +23 -4
  101. data/lib/selenium/webdriver/safari.rb +1 -6
  102. data/lib/selenium/webdriver/support/block_event_listener.rb +5 -1
  103. data/lib/selenium/webdriver/support/color.rb +14 -14
  104. data/lib/selenium/webdriver/support/event_firing_bridge.rb +5 -1
  105. data/lib/selenium/webdriver/support/guards/guard.rb +30 -14
  106. data/lib/selenium/webdriver/support/guards.rb +11 -4
  107. data/lib/selenium/webdriver/version.rb +1 -1
  108. data/lib/selenium/webdriver.rb +7 -2
  109. data/selenium-webdriver.gemspec +1 -1
  110. metadata +33 -11
  111. data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +0 -35
  112. data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +0 -33
  113. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +0 -33
  114. data/lib/selenium/webdriver/bidi/log_inspector.rb +0 -147
@@ -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
@@ -21,8 +21,8 @@ module Selenium
21
21
  module WebDriver
22
22
  class BiDi
23
23
  autoload :Session, 'selenium/webdriver/bidi/session'
24
- autoload :LogInspector, 'selenium/webdriver/bidi/log_inspector'
25
24
  autoload :LogHandler, 'selenium/webdriver/bidi/log_handler'
25
+ autoload :Browser, 'selenium/webdriver/bidi/browser'
26
26
  autoload :BrowsingContext, 'selenium/webdriver/bidi/browsing_context'
27
27
  autoload :Struct, 'selenium/webdriver/bidi/struct'
28
28
  autoload :Network, 'selenium/webdriver/bidi/network'
@@ -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,10 @@ module Selenium
30
30
  class Driver < Chromium::Driver
31
31
  include LocalDriver
32
32
 
33
- def initialize(options: nil, service: nil, url: nil, **)
34
- caps, url = initialize_local_driver(options, service, url)
35
- super(caps: caps, url: 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
+ end
36
37
  end
37
38
 
38
39
  def browser
@@ -26,6 +26,19 @@ module Selenium
26
26
  SHUTDOWN_SUPPORTED = true
27
27
  DRIVER_PATH_ENV_KEY = 'SE_CHROMEDRIVER'
28
28
 
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
+
34
+ if ENV.key?('SE_DEBUG')
35
+ warn_driver_log_override if args.reject! { |arg| arg.include?('log-level') || arg.include?('silent') }
36
+ args << '--verbose'
37
+ end
38
+
39
+ super
40
+ end
41
+
29
42
  def log
30
43
  return @log unless @log.is_a? String
31
44
 
@@ -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,
@@ -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
@@ -84,6 +86,7 @@ module Selenium
84
86
  @logging_prefs = options.delete(:logging_prefs) || {}
85
87
  @encoded_extensions = @options.delete(:encoded_extensions) || []
86
88
  @extensions = []
89
+ @vendor_options = {}
87
90
  @options.delete(:extensions).each { |ext| validate_extension(ext) }
88
91
  end
89
92
 
@@ -182,6 +185,24 @@ module Selenium
182
185
  @options[:emulation] = opts
183
186
  end
184
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
+
185
206
  #
186
207
  # Enables mobile browser use on Android.
187
208
  #
@@ -207,6 +228,7 @@ module Selenium
207
228
  enable_logging(browser_options) unless @logging_prefs.empty?
208
229
 
209
230
  options = browser_options[self.class::KEY]
231
+ options.merge!(@vendor_options)
210
232
  options['binary'] ||= binary_path if binary_path
211
233
 
212
234
  if @profile
@@ -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 = []
@@ -24,7 +24,8 @@ module Selenium
24
24
  #
25
25
 
26
26
  class ChildProcess
27
- TimeoutError = Class.new(StandardError)
27
+ class TimeoutError < StandardError
28
+ end
28
29
 
29
30
  SIGTERM = 'TERM'
30
31
  SIGKILL = 'KILL'
@@ -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,18 +330,13 @@ 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
328
339
 
329
- def service_url(service)
330
- @service_manager = service.launch
331
- @service_manager.uri
332
- end
333
-
334
340
  def screenshot
335
341
  bridge.screenshot
336
342
  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
@@ -20,17 +20,29 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module DriverExtensions
23
- module HasBiDi
23
+ module HasSessionEvents
24
24
  #
25
- # Retrieves WebDriver BiDi connection.
25
+ # Fires a custom session event to the remote server event bus.
26
+ # This allows test code to trigger server-side utilities that subscribe to
27
+ # the event bus.
26
28
  #
27
- # @return [BiDi]
29
+ # @param [String] event_type The type of event (e.g., "test:failed", "log:collect")
30
+ # @param [Hash] payload Optional data to include with the event
31
+ # @return [Hash] Response data including success status, event type, and timestamp
28
32
  #
29
-
30
- def bidi
31
- @bridge.bidi
33
+ # @example Fire a simple event
34
+ # driver.fire_session_event("test:started")
35
+ #
36
+ # @example Fire an event with payload
37
+ # driver.fire_session_event("test:failed", {
38
+ # testName: "LoginTest",
39
+ # error: "Element not found"
40
+ # })
41
+ #
42
+ def fire_session_event(event_type, payload = nil)
43
+ @bridge.fire_session_event(event_type, payload)
32
44
  end
33
- end # HasBiDi
45
+ end # HasSessionEvents
34
46
  end # DriverExtensions
35
47
  end # WebDriver
36
48
  end # Selenium
@@ -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,44 @@ 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
- WebDriver.logger.error("Exception occurred: #{e.message}")
74
- WebDriver.logger.error("Backtrace:\n\t#{e.backtrace&.join("\n\t")}")
75
- raise Error::NoSuchDriverError, "Unable to obtain #{exe}"
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
+ 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
+ id: :driver_finder)
74
+ Platform.assert_executable(path)
75
+ {driver_path: path}
76
+ end
77
+
78
+ def paths_from_manager
79
+ output = SeleniumManager.binary_paths(*to_args)
80
+ formatted = {driver_path: Platform.cygwin_path(output['driver_path'], only_cygwin: true),
81
+ browser_path: Platform.cygwin_path(output['browser_path'], only_cygwin: true)}
82
+ Platform.assert_executable(formatted[:driver_path])
83
+ Platform.assert_executable(formatted[:browser_path])
84
+ formatted
85
+ end
86
+
79
87
  def to_args
88
+ return ['--driver', @service.class::EXECUTABLE] unless @options
89
+
80
90
  args = ['--browser', @options.browser_name]
81
91
  if @options.browser_version
82
92
  args << '--browser-version'
@@ -38,9 +38,9 @@ module Selenium
38
38
  ERROR_URL = 'https://www.selenium.dev/documentation/webdriver/troubleshooting/errors'
39
39
 
40
40
  URLS = {
41
- NoSuchElementError: "#{ERROR_URL}#no-such-element-exception",
42
- StaleElementReferenceError: "#{ERROR_URL}#stale-element-reference-exception",
43
- InvalidSelectorError: "#{ERROR_URL}#invalid-selector-exception",
41
+ NoSuchElementError: "#{ERROR_URL}#nosuchelementexception",
42
+ StaleElementReferenceError: "#{ERROR_URL}#staleelementreferenceexception",
43
+ InvalidSelectorError: "#{ERROR_URL}#invalidselectorexception",
44
44
  NoSuchDriverError: "#{ERROR_URL}/driver_location"
45
45
  }.freeze
46
46
 
@@ -119,6 +119,13 @@ module Selenium
119
119
 
120
120
  class NoSuchWindowError < WebDriverError; end
121
121
 
122
+ #
123
+ # A command to find a devtools target could not be satisfied because
124
+ # the target could not be found.
125
+ #
126
+
127
+ class NoSuchTargetError < WebDriverError; end
128
+
122
129
  #
123
130
  # The element does not have a shadow root.
124
131
  #
@@ -20,14 +20,25 @@
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
- [caps, url]
31
+ begin
32
+ yield(caps, http_client) if block_given?
33
+ rescue Selenium::WebDriver::Error::WebDriverError
34
+ @service_manager&.stop
35
+ raise
36
+ end
37
+ end
38
+
39
+ def service_url(service)
40
+ @service_manager = service.launch
41
+ @service_manager.uri
31
42
  end
32
43
 
33
44
  def process_options(options, service)
@@ -38,16 +49,22 @@ module Selenium
38
49
  raise ArgumentError, ":options must be an instance of #{default_options.class}"
39
50
  end
40
51
 
41
- service.executable_path ||= begin
42
- finder = WebDriver::DriverFinder.new(options, service)
43
- if options.respond_to?(:binary) && finder.browser_path?
44
- options.binary = finder.browser_path
45
- options.browser_version = nil
46
- end
47
- finder.driver_path
48
- 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
49
56
  options.as_json
50
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
51
68
  end
52
69
  end
53
70
  end