selenium-webdriver 4.17.0 → 4.26.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +92 -0
  3. data/Gemfile +1 -0
  4. data/README.md +2 -2
  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/server.rb +2 -1
  9. data/lib/selenium/webdriver/atoms/findElements.js +26 -26
  10. data/lib/selenium/webdriver/atoms/getAttribute.js +2 -2
  11. data/lib/selenium/webdriver/atoms/isDisplayed.js +24 -97
  12. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +1 -1
  13. data/lib/selenium/webdriver/bidi/log_handler.rb +63 -0
  14. data/lib/selenium/webdriver/bidi/log_inspector.rb +5 -1
  15. data/lib/selenium/webdriver/bidi/session.rb +7 -7
  16. data/lib/selenium/webdriver/bidi/struct.rb +44 -0
  17. data/lib/selenium/webdriver/bidi.rb +10 -0
  18. data/lib/selenium/webdriver/chrome/service.rb +1 -0
  19. data/lib/selenium/webdriver/chromium/driver.rb +1 -0
  20. data/lib/selenium/webdriver/common/child_process.rb +8 -2
  21. data/lib/selenium/webdriver/common/driver.rb +21 -15
  22. data/lib/selenium/webdriver/common/driver_extensions/has_bidi.rb +1 -1
  23. data/lib/selenium/webdriver/common/driver_extensions/has_fedcm_dialog.rb +55 -0
  24. data/lib/selenium/webdriver/common/driver_finder.rb +66 -14
  25. data/lib/selenium/webdriver/common/error.rb +21 -21
  26. data/lib/selenium/webdriver/common/fedcm/account.rb +50 -0
  27. data/lib/selenium/webdriver/common/fedcm/dialog.rb +74 -0
  28. data/lib/selenium/webdriver/common/fedcm.rb +27 -0
  29. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +1 -1
  30. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +1 -1
  31. data/lib/selenium/webdriver/common/local_driver.rb +8 -1
  32. data/lib/selenium/webdriver/common/logger.rb +2 -2
  33. data/lib/selenium/webdriver/common/manager.rb +1 -1
  34. data/lib/selenium/webdriver/common/options.rb +1 -1
  35. data/lib/selenium/webdriver/common/platform.rb +3 -1
  36. data/lib/selenium/webdriver/common/script.rb +45 -0
  37. data/lib/selenium/webdriver/common/search_context.rb +10 -2
  38. data/lib/selenium/webdriver/common/selenium_manager.rb +36 -73
  39. data/lib/selenium/webdriver/common/service.rb +11 -4
  40. data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
  41. data/lib/selenium/webdriver/common/target_locator.rb +1 -2
  42. data/lib/selenium/webdriver/common/wait.rb +1 -1
  43. data/lib/selenium/webdriver/common/websocket_connection.rb +12 -0
  44. data/lib/selenium/webdriver/common.rb +4 -0
  45. data/lib/selenium/webdriver/devtools/network_interceptor.rb +1 -1
  46. data/lib/selenium/webdriver/edge/service.rb +1 -1
  47. data/lib/selenium/webdriver/firefox/options.rb +3 -0
  48. data/lib/selenium/webdriver/firefox/profile.rb +11 -5
  49. data/lib/selenium/webdriver/firefox/profiles_ini.rb +1 -1
  50. data/lib/selenium/webdriver/firefox/service.rb +1 -0
  51. data/lib/selenium/webdriver/ie/options.rb +3 -2
  52. data/lib/selenium/webdriver/ie/service.rb +1 -0
  53. data/lib/selenium/webdriver/remote/bidi_bridge.rb +44 -0
  54. data/lib/selenium/webdriver/remote/bridge/commands.rb +13 -1
  55. data/lib/selenium/webdriver/remote/bridge/locator_converter.rb +76 -0
  56. data/lib/selenium/webdriver/remote/bridge.rb +87 -46
  57. data/lib/selenium/webdriver/remote/capabilities.rb +1 -1
  58. data/lib/selenium/webdriver/remote/http/common.rb +21 -3
  59. data/lib/selenium/webdriver/remote/http/curb.rb +11 -5
  60. data/lib/selenium/webdriver/remote/response.rb +12 -19
  61. data/lib/selenium/webdriver/remote/server_error.rb +1 -1
  62. data/lib/selenium/webdriver/remote.rb +2 -1
  63. data/lib/selenium/webdriver/safari/service.rb +1 -1
  64. data/lib/selenium/webdriver/support/guards/guard.rb +8 -9
  65. data/lib/selenium/webdriver/version.rb +1 -1
  66. data/lib/selenium/webdriver.rb +1 -1
  67. data/selenium-webdriver.gemspec +9 -6
  68. metadata +70 -7
@@ -28,7 +28,7 @@ module Selenium
28
28
  #
29
29
 
30
30
  def bidi
31
- @bidi ||= Selenium::WebDriver::BiDi.new(url: capabilities[:web_socket_url])
31
+ @bridge.bidi
32
32
  end
33
33
  end # HasBiDi
34
34
  end # DriverExtensions
@@ -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
+ module Selenium
21
+ module WebDriver
22
+ module DriverExtensions
23
+ module HasFedCmDialog
24
+ # Disables the promise rejection delay for FedCm.
25
+ #
26
+ # FedCm by default delays promise resolution in failure cases for privacy reasons.
27
+ # This method allows turning it off to let tests run faster where this is not relevant.
28
+ def enable_fedcm_delay=(enable)
29
+ @bridge.fedcm_delay(enable)
30
+ end
31
+
32
+ # Resets the FedCm dialog cooldown.
33
+ #
34
+ # If a user agent triggers a cooldown when the account chooser is dismissed,
35
+ # this method resets that cooldown so that the dialog can be triggered again immediately.
36
+ def reset_fedcm_cooldown
37
+ @bridge.reset_fedcm_cooldown
38
+ end
39
+
40
+ def fedcm_dialog
41
+ @fedcm_dialog ||= FedCM::Dialog.new(@bridge)
42
+ end
43
+
44
+ def wait_for_fedcm_dialog(timeout: 5, interval: 0.2, message: nil, ignore: nil)
45
+ wait = Wait.new(timeout: timeout, interval: interval, message: message, ignore: ignore)
46
+ wait.until do
47
+ fedcm_dialog if fedcm_dialog.type
48
+ rescue Error::NoSuchAlertError
49
+ nil
50
+ end
51
+ end
52
+ end # HasFedCmDialog
53
+ end # DriverExtensions
54
+ end # WebDriver
55
+ end # Selenium
@@ -20,25 +20,77 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  class DriverFinder
23
- def self.path(options, klass)
24
- path = klass.driver_path
25
- path = path.call if path.is_a?(Proc)
23
+ def self.path(options, service_class)
24
+ WebDriver.logger.deprecate('DriverFinder.path(options, service_class)',
25
+ 'DriverFinder.new(options, service).driver_path')
26
+ new(options, service_class.new).driver_path
27
+ end
28
+
29
+ def initialize(options, service)
30
+ @options = options
31
+ @service = service
32
+ end
33
+
34
+ def browser_path
35
+ paths[:browser_path]
36
+ end
37
+
38
+ def driver_path
39
+ paths[:driver_path]
40
+ end
41
+
42
+ def browser_path?
43
+ !browser_path.nil? && !browser_path.empty?
44
+ end
45
+
46
+ private
47
+
48
+ def paths
49
+ @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
26
69
 
27
- path ||= begin
28
- SeleniumManager.driver_path(options) unless options.is_a?(Remote::Capabilities)
70
+ formatted
71
+ end
29
72
  rescue StandardError => e
30
- raise Error::NoSuchDriverError, "Unable to obtain #{klass::EXECUTABLE} using Selenium Manager; #{e.message}"
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}"
31
76
  end
77
+ end
32
78
 
33
- begin
34
- Platform.assert_executable(path)
35
- rescue TypeError
36
- raise Error::NoSuchDriverError, "Unable to locate or obtain #{klass::EXECUTABLE}"
37
- rescue Error::WebDriverError => e
38
- raise Error::NoSuchDriverError, "#{klass::EXECUTABLE} located, but: #{e.message}"
79
+ def to_args
80
+ args = ['--browser', @options.browser_name]
81
+ if @options.browser_version
82
+ args << '--browser-version'
83
+ args << @options.browser_version
39
84
  end
40
-
41
- path
85
+ if @options.respond_to?(:binary) && !@options.binary.nil?
86
+ args << '--browser-path'
87
+ args << @options.binary.gsub('\\', '\\\\\\')
88
+ end
89
+ if @options.proxy
90
+ args << '--proxy'
91
+ args << (@options.proxy.ssl || @options.proxy.http)
92
+ end
93
+ args
42
94
  end
43
95
  end
44
96
  end
@@ -37,17 +37,29 @@ module Selenium
37
37
  SUPPORT_MSG = 'For documentation on this error, please visit:'
38
38
  ERROR_URL = 'https://www.selenium.dev/documentation/webdriver/troubleshooting/errors'
39
39
 
40
- class WebDriverError < StandardError; end
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",
44
+ NoSuchDriverError: "#{ERROR_URL}/driver_location"
45
+ }.freeze
46
+
47
+ class WebDriverError < StandardError
48
+ def initialize(msg = '')
49
+ # Remove this conditional when all the error pages have been documented
50
+ super(URLS[class_name] ? "#{msg}; #{SUPPORT_MSG} #{URLS[class_name]}" : msg)
51
+ end
52
+
53
+ def class_name
54
+ self.class.name&.split('::')&.last&.to_sym
55
+ end
56
+ end
41
57
 
42
58
  #
43
59
  # An element could not be located on the page using the given search parameters.
44
60
  #
45
61
 
46
- class NoSuchElementError < WebDriverError
47
- def initialize(msg = '')
48
- super("#{msg}; #{SUPPORT_MSG} #{ERROR_URL}#no-such-element-exception")
49
- end
50
- end
62
+ class NoSuchElementError < WebDriverError; end
51
63
 
52
64
  #
53
65
  # A command to switch to a frame could not be satisfied because the frame could not be found.
@@ -65,11 +77,7 @@ module Selenium
65
77
  # A command failed because the referenced element is no longer attached to the DOM.
66
78
  #
67
79
 
68
- class StaleElementReferenceError < WebDriverError
69
- def initialize(msg = '')
70
- super("#{msg}; #{SUPPORT_MSG} #{ERROR_URL}#stale-element-reference-exception")
71
- end
72
- end
80
+ class StaleElementReferenceError < WebDriverError; end
73
81
 
74
82
  #
75
83
  # A command failed because the referenced shadow root is no longer attached to the DOM.
@@ -143,11 +151,7 @@ module Selenium
143
151
  # Argument was an invalid selector.
144
152
  #
145
153
 
146
- class InvalidSelectorError < WebDriverError
147
- def initialize(msg = '')
148
- super("#{msg}; #{SUPPORT_MSG} #{ERROR_URL}#invalid-selector-exception")
149
- end
150
- end
154
+ class InvalidSelectorError < WebDriverError; end
151
155
 
152
156
  #
153
157
  # A new session could not be created.
@@ -232,11 +236,7 @@ module Selenium
232
236
  # Indicates that driver was not specified and could not be located.
233
237
  #
234
238
 
235
- class NoSuchDriverError < WebDriverError
236
- def initialize(msg = '')
237
- super("#{msg}; #{SUPPORT_MSG} #{ERROR_URL}/driver_location")
238
- end
239
- end
239
+ class NoSuchDriverError < WebDriverError; end
240
240
  end # Error
241
241
  end # WebDriver
242
242
  end # Selenium
@@ -0,0 +1,50 @@
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
+ module FedCM
23
+ # Represents an account displayed in a FedCm account list.
24
+ # See: https://w3c-fedid.github.io/FedCM/#dictdef-identityprovideraccount
25
+ # https://w3c-fedid.github.io/FedCM/#webdriver-accountlist
26
+ class Account
27
+ LOGIN_STATE_SIGNIN = 'SignIn'
28
+ LOGIN_STATE_SIGNUP = 'SignUp'
29
+
30
+ attr_reader :account_id, :email, :name, :given_name, :picture_url,
31
+ :idp_config_url, :login_state, :terms_of_service_url, :privacy_policy_url
32
+
33
+ # Initializes a new account with the provided attributes.
34
+ #
35
+ # @param [Hash]
36
+ def initialize(**args)
37
+ @account_id = args['accountId']
38
+ @email = args['email']
39
+ @name = args['name']
40
+ @given_name = args['givenName']
41
+ @picture_url = args['pictureUrl']
42
+ @idp_config_url = args['idpConfigUrl']
43
+ @login_state = args['loginState']
44
+ @terms_of_service_url = args['termsOfServiceUrl']
45
+ @privacy_policy_url = args['privacyPolicyUrl']
46
+ end
47
+ end # Account
48
+ end # FedCM
49
+ end # WebDriver
50
+ end # Selenium
@@ -0,0 +1,74 @@
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
+ module FedCM
23
+ class Dialog
24
+ def initialize(bridge)
25
+ @bridge = bridge
26
+ end
27
+
28
+ DIALOG_TYPE_ACCOUNT_LIST = 'AccountChooser'
29
+ DIALOG_TYPE_AUTO_REAUTH = 'AutoReauthn'
30
+
31
+ # Closes the dialog as if the user had clicked X.
32
+ def click
33
+ @bridge.click_fedcm_dialog_button
34
+ end
35
+
36
+ # Closes the dialog as if the user had clicked X.
37
+ def cancel
38
+ @bridge.cancel_fedcm_dialog
39
+ end
40
+
41
+ # Selects an account as if the user had clicked on it.
42
+ #
43
+ # @param [Integer] index The index of the account to select from the list returned by get_accounts.
44
+ def select_account(index)
45
+ @bridge.select_fedcm_account index
46
+ end
47
+
48
+ # Returns the type of the open dialog.
49
+ #
50
+ # One of DIALOG_TYPE_ACCOUNT_LIST and DIALOG_TYPE_AUTO_REAUTH.
51
+ def type
52
+ @bridge.fedcm_dialog_type
53
+ end
54
+
55
+ # Returns the title of the dialog.
56
+ def title
57
+ @bridge.fedcm_title
58
+ end
59
+
60
+ # Returns the subtitle of the dialog or nil if none.
61
+ def subtitle
62
+ @bridge.fedcm_subtitle
63
+ end
64
+
65
+ # Returns the accounts shown in the account chooser.
66
+ #
67
+ # If this is an auto reauth dialog, returns the single account that is being signed in.
68
+ def accounts
69
+ @bridge.fedcm_account_list.map { |account| Account.new(**account) }
70
+ end
71
+ end # Dialog
72
+ end # FedCM
73
+ end # WebDriver
74
+ end # Selenium
@@ -0,0 +1,27 @@
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
+ module FedCM
23
+ autoload :Account, 'fedcm/account'
24
+ autoload :Dialog, 'fedcm/dialog'
25
+ end # FedCM
26
+ end # WebDriver
27
+ end # Selenium
@@ -28,7 +28,7 @@ module Selenium
28
28
 
29
29
  class PointerCancel < Interaction
30
30
  def initialize(source)
31
- super(source)
31
+ super
32
32
  @type = :pointerCancel
33
33
  end
34
34
 
@@ -28,7 +28,7 @@ module Selenium
28
28
 
29
29
  class WheelInput < InputDevice
30
30
  def initialize(name = nil)
31
- super(name)
31
+ super
32
32
  @type = Interactions::WHEEL
33
33
  end
34
34
 
@@ -38,7 +38,14 @@ module Selenium
38
38
  raise ArgumentError, ":options must be an instance of #{default_options.class}"
39
39
  end
40
40
 
41
- service.executable_path ||= WebDriver::DriverFinder.path(options, service.class)
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
42
49
  options.as_json
43
50
  end
44
51
  end
@@ -167,7 +167,7 @@ module Selenium
167
167
 
168
168
  id << :deprecations if @allowed.include?(:deprecations)
169
169
 
170
- message = +"[DEPRECATION] #{old} is deprecated"
170
+ message = "[DEPRECATION] #{old} is deprecated"
171
171
  message << if new
172
172
  ". Use #{new} instead."
173
173
  else
@@ -185,7 +185,7 @@ module Selenium
185
185
  logger.progname = name
186
186
  logger.level = level
187
187
  logger.formatter = proc do |severity, time, progname, msg|
188
- "#{time.strftime('%F %T')} #{severity} #{progname} #{msg}\n"
188
+ "#{time.strftime('%F %T')} #{severity} #{progname} #{msg}\n".force_encoding('UTF-8')
189
189
  end
190
190
 
191
191
  logger
@@ -56,7 +56,7 @@ module Selenium
56
56
  opts[:httpOnly] = http_only if http_only
57
57
 
58
58
  obj = opts.delete(:expires)
59
- opts[:expiry] = seconds_from(obj).to_i if obj
59
+ opts[:expiry] = seconds_from(obj).to_int if obj
60
60
 
61
61
  @bridge.add_cookie opts
62
62
  end
@@ -177,7 +177,7 @@ module Selenium
177
177
  end
178
178
 
179
179
  def camel_case(str)
180
- str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
180
+ str.gsub(/_([a-z])/) { Regexp.last_match(1)&.upcase }
181
181
  end
182
182
  end # Options
183
183
  end # WebDriver
@@ -111,7 +111,9 @@ module Selenium
111
111
  windows? && !cygwin? ? %("#{str}") : str
112
112
  end
113
113
 
114
- def cygwin_path(path, **opts)
114
+ def cygwin_path(path, only_cygwin: false, **opts)
115
+ return path if only_cygwin && !cygwin?
116
+
115
117
  flags = []
116
118
  opts.each { |k, v| flags << "--#{k}" if v }
117
119
 
@@ -0,0 +1,45 @@
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 Script
23
+ def initialize(bridge)
24
+ @log_handler = BiDi::LogHandler.new(bridge.bidi)
25
+ end
26
+
27
+ # @return [int] id of the handler
28
+ def add_console_message_handler(&block)
29
+ @log_handler.add_message_handler('console', &block)
30
+ end
31
+
32
+ # @return [int] id of the handler
33
+ def add_javascript_error_handler(&block)
34
+ @log_handler.add_message_handler('javascript', &block)
35
+ end
36
+
37
+ # @param [int] id of the handler previously added
38
+ def remove_console_message_handler(id)
39
+ @log_handler.remove_message_handler(id)
40
+ end
41
+
42
+ alias remove_javascript_error_handler remove_console_message_handler
43
+ end # Script
44
+ end # WebDriver
45
+ end # Selenium
@@ -35,6 +35,14 @@ module Selenium
35
35
  xpath: 'xpath'
36
36
  }.freeze
37
37
 
38
+ class << self
39
+ attr_accessor :extra_finders
40
+
41
+ def finders
42
+ FINDERS.merge(extra_finders || {})
43
+ end
44
+ end
45
+
38
46
  #
39
47
  # Find the first element matching the given arguments
40
48
  #
@@ -57,7 +65,7 @@ module Selenium
57
65
  def find_element(*args)
58
66
  how, what = extract_args(args)
59
67
 
60
- by = FINDERS[how.to_sym]
68
+ by = SearchContext.finders[how.to_sym]
61
69
  raise ArgumentError, "cannot find element by #{how.inspect}" unless by
62
70
 
63
71
  bridge.find_element_by by, what, ref
@@ -72,7 +80,7 @@ module Selenium
72
80
  def find_elements(*args)
73
81
  how, what = extract_args(args)
74
82
 
75
- by = FINDERS[how.to_sym]
83
+ by = SearchContext.finders[how.to_sym]
76
84
  raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
77
85
 
78
86
  bridge.find_elements_by by, what, ref