selenium-webdriver 4.1.0 → 4.23.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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +405 -1
  3. data/Gemfile +3 -0
  4. data/LICENSE +1 -1
  5. data/NOTICE +1 -1
  6. data/README.md +3 -3
  7. data/bin/linux/selenium-manager +0 -0
  8. data/bin/macos/selenium-manager +0 -0
  9. data/bin/windows/selenium-manager.exe +0 -0
  10. data/lib/selenium/server.rb +38 -40
  11. data/lib/selenium/webdriver/atoms/findElements.js +27 -27
  12. data/lib/selenium/webdriver/atoms/getAttribute.js +6 -100
  13. data/lib/selenium/webdriver/atoms/isDisplayed.js +24 -96
  14. data/lib/selenium/webdriver/atoms/mutationListener.js +0 -0
  15. data/lib/selenium/webdriver/atoms.rb +5 -3
  16. data/lib/selenium/webdriver/bidi/browsing_context.rb +88 -0
  17. data/lib/selenium/webdriver/bidi/browsing_context_info.rb +35 -0
  18. data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +35 -0
  19. data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +35 -0
  20. data/lib/selenium/webdriver/{common/driver_extensions/has_location.rb → bidi/log/filter_by.rb} +14 -11
  21. data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +33 -0
  22. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +33 -0
  23. data/lib/selenium/webdriver/bidi/log_handler.rb +63 -0
  24. data/lib/selenium/webdriver/bidi/log_inspector.rb +147 -0
  25. data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
  26. data/lib/selenium/webdriver/bidi/session.rb +51 -0
  27. data/lib/selenium/webdriver/bidi/struct.rb +44 -0
  28. data/lib/selenium/webdriver/bidi.rb +66 -0
  29. data/lib/selenium/webdriver/chrome/driver.rb +9 -29
  30. data/lib/selenium/webdriver/chrome/features.rb +9 -67
  31. data/lib/selenium/webdriver/chrome/options.rb +3 -223
  32. data/lib/selenium/webdriver/chrome/profile.rb +3 -83
  33. data/lib/selenium/webdriver/chrome/service.rb +4 -19
  34. data/lib/selenium/webdriver/chrome.rb +0 -16
  35. data/lib/selenium/webdriver/chromium/driver.rb +61 -0
  36. data/lib/selenium/webdriver/chromium/features.rb +99 -0
  37. data/lib/selenium/webdriver/chromium/options.rb +243 -0
  38. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  39. data/lib/selenium/webdriver/chromium.rb +29 -0
  40. data/lib/selenium/webdriver/common/action_builder.rb +60 -22
  41. data/lib/selenium/webdriver/common/child_process.rb +130 -0
  42. data/lib/selenium/webdriver/common/driver.rb +46 -90
  43. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  44. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +0 -1
  45. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  46. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +0 -2
  47. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +0 -2
  48. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_bidi.rb} +10 -5
  49. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +10 -1
  50. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +0 -2
  51. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +1 -4
  52. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  53. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +0 -2
  54. data/lib/selenium/webdriver/common/driver_extensions/has_fedcm_dialog.rb +55 -0
  55. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
  56. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +0 -2
  57. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +2 -3
  58. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +0 -2
  59. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +2 -69
  60. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +0 -2
  61. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +1 -3
  62. data/lib/selenium/webdriver/common/driver_finder.rb +97 -0
  63. data/lib/selenium/webdriver/common/element.rb +8 -8
  64. data/lib/selenium/webdriver/common/error.rb +27 -4
  65. data/lib/selenium/webdriver/common/fedcm/account.rb +50 -0
  66. data/lib/selenium/webdriver/common/fedcm/dialog.rb +74 -0
  67. data/lib/selenium/webdriver/common/fedcm.rb +27 -0
  68. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  69. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  70. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -25
  71. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  72. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -1
  73. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  74. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  75. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  76. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +59 -71
  77. data/lib/selenium/webdriver/common/{driver_extensions/has_network_connection.rb → interactions/pointer_cancel.rb} +19 -11
  78. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  79. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  80. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  81. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  82. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  83. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  84. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  85. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +114 -0
  86. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +42 -0
  87. data/lib/selenium/webdriver/common/keys.rb +1 -0
  88. data/lib/selenium/webdriver/common/local_driver.rb +53 -0
  89. data/lib/selenium/webdriver/common/logger.rb +93 -28
  90. data/lib/selenium/webdriver/common/manager.rb +1 -28
  91. data/lib/selenium/webdriver/common/options.rb +19 -19
  92. data/lib/selenium/webdriver/common/platform.rb +12 -52
  93. data/lib/selenium/webdriver/common/port_prober.rb +1 -1
  94. data/lib/selenium/webdriver/common/profile_helper.rb +1 -1
  95. data/lib/selenium/webdriver/common/proxy.rb +4 -4
  96. data/lib/selenium/webdriver/common/script.rb +45 -0
  97. data/lib/selenium/webdriver/common/search_context.rb +10 -8
  98. data/lib/selenium/webdriver/common/selenium_manager.rb +104 -0
  99. data/lib/selenium/webdriver/common/service.rb +21 -30
  100. data/lib/selenium/webdriver/common/service_manager.rb +8 -15
  101. data/lib/selenium/webdriver/common/shadow_root.rb +2 -3
  102. data/lib/selenium/webdriver/common/socket_lock.rb +3 -3
  103. data/lib/selenium/webdriver/common/socket_poller.rb +3 -3
  104. data/lib/selenium/webdriver/common/takes_screenshot.rb +6 -5
  105. data/lib/selenium/webdriver/common/target_locator.rb +2 -3
  106. data/lib/selenium/webdriver/common/timeouts.rb +2 -2
  107. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  108. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  109. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  110. data/lib/selenium/webdriver/common/websocket_connection.rb +176 -0
  111. data/lib/selenium/webdriver/common/window.rb +6 -6
  112. data/lib/selenium/webdriver/common/zipper.rb +1 -1
  113. data/lib/selenium/webdriver/common.rb +26 -5
  114. data/lib/selenium/webdriver/devtools/console_event.rb +0 -2
  115. data/lib/selenium/webdriver/devtools/exception_event.rb +0 -2
  116. data/lib/selenium/webdriver/devtools/mutation_event.rb +0 -2
  117. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  118. data/lib/selenium/webdriver/devtools/pinned_script.rb +0 -2
  119. data/lib/selenium/webdriver/devtools/request.rb +1 -3
  120. data/lib/selenium/webdriver/devtools/response.rb +1 -3
  121. data/lib/selenium/webdriver/devtools.rb +17 -114
  122. data/lib/selenium/webdriver/edge/driver.rb +9 -3
  123. data/lib/selenium/webdriver/edge/features.rb +8 -4
  124. data/lib/selenium/webdriver/edge/options.rb +17 -5
  125. data/lib/selenium/webdriver/edge/profile.rb +2 -2
  126. data/lib/selenium/webdriver/edge/service.rb +8 -7
  127. data/lib/selenium/webdriver/edge.rb +0 -2
  128. data/lib/selenium/webdriver/firefox/driver.rb +9 -2
  129. data/lib/selenium/webdriver/firefox/features.rb +11 -7
  130. data/lib/selenium/webdriver/firefox/options.rb +10 -16
  131. data/lib/selenium/webdriver/firefox/profile.rb +21 -17
  132. data/lib/selenium/webdriver/firefox/profiles_ini.rb +1 -1
  133. data/lib/selenium/webdriver/firefox/service.rb +0 -18
  134. data/lib/selenium/webdriver/firefox/util.rb +46 -0
  135. data/lib/selenium/webdriver/firefox.rb +1 -14
  136. data/lib/selenium/webdriver/ie/driver.rb +7 -1
  137. data/lib/selenium/webdriver/ie/features.rb +34 -0
  138. data/lib/selenium/webdriver/ie/options.rb +6 -4
  139. data/lib/selenium/webdriver/ie/service.rb +0 -22
  140. data/lib/selenium/webdriver/ie.rb +4 -17
  141. data/lib/selenium/webdriver/remote/bidi_bridge.rb +44 -0
  142. data/lib/selenium/webdriver/remote/{commands.rb → bridge/commands.rb} +22 -9
  143. data/lib/selenium/webdriver/remote/bridge/locator_converter.rb +76 -0
  144. data/lib/selenium/webdriver/remote/bridge.rb +147 -99
  145. data/lib/selenium/webdriver/remote/capabilities.rb +5 -55
  146. data/lib/selenium/webdriver/remote/driver.rb +35 -14
  147. data/lib/selenium/webdriver/remote/features.rb +75 -0
  148. data/lib/selenium/webdriver/remote/http/common.rb +24 -6
  149. data/lib/selenium/webdriver/remote/http/curb.rb +1 -3
  150. data/lib/selenium/webdriver/remote/http/default.rb +8 -14
  151. data/lib/selenium/webdriver/remote/response.rb +8 -34
  152. data/lib/selenium/webdriver/remote/server_error.rb +2 -2
  153. data/lib/selenium/webdriver/remote.rb +2 -1
  154. data/lib/selenium/webdriver/safari/driver.rb +7 -1
  155. data/lib/selenium/webdriver/safari/features.rb +5 -3
  156. data/lib/selenium/webdriver/safari/options.rb +5 -1
  157. data/lib/selenium/webdriver/safari/service.rb +10 -4
  158. data/lib/selenium/webdriver/safari.rb +1 -15
  159. data/lib/selenium/webdriver/support/color.rb +22 -22
  160. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  161. data/lib/selenium/webdriver/support/guards/guard.rb +14 -14
  162. data/lib/selenium/webdriver/support/guards/guard_condition.rb +1 -3
  163. data/lib/selenium/webdriver/support/guards.rb +2 -2
  164. data/lib/selenium/webdriver/support/relative_locator.rb +0 -1
  165. data/lib/selenium/webdriver/support/select.rb +3 -1
  166. data/lib/selenium/webdriver/version.rb +1 -1
  167. data/lib/selenium/webdriver.rb +7 -5
  168. data/selenium-webdriver.gemspec +20 -16
  169. metadata +135 -47
  170. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
  171. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +0 -63
  172. data/lib/selenium/webdriver/support/cdp_client_generator.rb +0 -108
@@ -24,6 +24,8 @@ module Selenium
24
24
  set_window_rect timeouts unhandled_prompt_behavior strict_file_interactability
25
25
  web_socket_url].freeze
26
26
 
27
+ GRID_OPTIONS = %i[enable_downloads].freeze
28
+
27
29
  class << self
28
30
  attr_reader :driver_path
29
31
 
@@ -38,12 +40,12 @@ module Selenium
38
40
  def ie(**opts)
39
41
  IE::Options.new(**opts)
40
42
  end
41
- alias_method :internet_explorer, :ie
43
+ alias internet_explorer ie
42
44
 
43
45
  def edge(**opts)
44
46
  Edge::Options.new(**opts)
45
47
  end
46
- alias_method :microsoftedge, :edge
48
+ alias microsoftedge edge
47
49
 
48
50
  def safari(**opts)
49
51
  Safari::Options.new(**opts)
@@ -57,7 +59,7 @@ module Selenium
57
59
  @options[key]
58
60
  end
59
61
 
60
- define_method "#{key}=" do |value|
62
+ define_method :"#{key}=" do |value|
61
63
  @options[key] = value
62
64
  end
63
65
  end
@@ -66,17 +68,10 @@ module Selenium
66
68
 
67
69
  attr_accessor :options
68
70
 
69
- def initialize(options: nil, **opts)
71
+ def initialize(**opts)
70
72
  self.class.set_capabilities
71
73
 
72
- @options = if options
73
- WebDriver.logger.deprecate(":options as keyword for initializing #{self.class}",
74
- "custom values directly in #new constructor",
75
- id: :options_options)
76
- opts.merge(options)
77
- else
78
- opts
79
- end
74
+ @options = opts
80
75
  @options[:browser_name] = self.class::BROWSER
81
76
  end
82
77
 
@@ -92,7 +87,7 @@ module Selenium
92
87
  #
93
88
 
94
89
  def add_option(name, value = nil)
95
- @options[name.keys.first] = name.values.first if value.nil? && name.is_a?(Hash)
90
+ name, value = name.first if value.nil? && name.is_a?(Hash)
96
91
  @options[name] = value
97
92
  end
98
93
 
@@ -102,7 +97,7 @@ module Selenium
102
97
  as_json == other.as_json
103
98
  end
104
99
 
105
- alias_method :eql?, :==
100
+ alias eql? ==
106
101
 
107
102
  #
108
103
  # @api private
@@ -111,13 +106,18 @@ module Selenium
111
106
  def as_json(*)
112
107
  options = @options.dup
113
108
 
109
+ downloads = options.delete(:enable_downloads)
110
+ options['se:downloadsEnabled'] = downloads unless downloads.nil?
114
111
  w3c_options = process_w3c_options(options)
115
112
 
116
- self.class::CAPABILITIES.each do |capability_alias, capability_name|
113
+ browser_options = self.class::CAPABILITIES.each_with_object({}) do |(capability_alias, capability_name), hash|
117
114
  capability_value = options.delete(capability_alias)
118
- options[capability_name] = capability_value if !capability_value.nil? && !options.key?(capability_name)
115
+ hash[capability_name] = capability_value unless capability_value.nil?
119
116
  end
120
- browser_options = defined?(self.class::KEY) ? {self.class::KEY => options} : options
117
+
118
+ raise Error::WebDriverError, "These options are not w3c compliant: #{options}" unless options.empty?
119
+
120
+ browser_options = {self.class::KEY => browser_options} if defined?(self.class::KEY)
121
121
 
122
122
  process_browser_options(browser_options)
123
123
  generate_as_json(w3c_options.merge(browser_options))
@@ -130,7 +130,7 @@ module Selenium
130
130
  end
131
131
 
132
132
  def process_w3c_options(options)
133
- w3c_options = options.select { |key, _val| w3c?(key) }
133
+ w3c_options = options.select { |key, val| w3c?(key) && !val.nil? }
134
134
  w3c_options[:unhandled_prompt_behavior] &&= w3c_options[:unhandled_prompt_behavior]&.to_s&.tr('_', ' ')
135
135
  options.delete_if { |key, _val| w3c?(key) }
136
136
  w3c_options
@@ -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
@@ -62,22 +62,14 @@ module Selenium
62
62
  end
63
63
  end
64
64
 
65
- def bitsize
66
- @bitsize ||= if defined?(FFI::Platform::ADDRESS_SIZE)
67
- FFI::Platform::ADDRESS_SIZE
68
- elsif defined?(FFI)
69
- FFI.type_size(:pointer) == 4 ? 32 : 64
70
- elsif jruby?
71
- Integer(ENV_JAVA['sun.arch.data.model'])
72
- else
73
- 1.size == 4 ? 32 : 64
74
- end
75
- end
76
-
77
65
  def jruby?
78
66
  engine == :jruby
79
67
  end
80
68
 
69
+ def truffleruby?
70
+ engine == :truffleruby
71
+ end
72
+
81
73
  def ruby_version
82
74
  RUBY_VERSION
83
75
  end
@@ -94,6 +86,10 @@ module Selenium
94
86
  os == :linux
95
87
  end
96
88
 
89
+ def unix?
90
+ os == :unix
91
+ end
92
+
97
93
  def wsl?
98
94
  return false unless linux?
99
95
 
@@ -104,8 +100,7 @@ module Selenium
104
100
  end
105
101
 
106
102
  def cygwin?
107
- RUBY_PLATFORM =~ /cygwin/
108
- !Regexp.last_match.nil?
103
+ RUBY_PLATFORM.include?('cygwin')
109
104
  end
110
105
 
111
106
  def null_device
@@ -116,7 +111,9 @@ module Selenium
116
111
  windows? && !cygwin? ? %("#{str}") : str
117
112
  end
118
113
 
119
- def cygwin_path(path, **opts)
114
+ def cygwin_path(path, only_cygwin: false, **opts)
115
+ return path if only_cygwin && !cygwin?
116
+
120
117
  flags = []
121
118
  opts.each { |k, v| flags << "--#{k}" if v }
122
119
 
@@ -155,43 +152,6 @@ module Selenium
155
152
  at_exit { yield if Process.pid == pid }
156
153
  end
157
154
 
158
- def find_binary(*binary_names)
159
- paths = ENV['PATH'].split(File::PATH_SEPARATOR)
160
-
161
- if windows?
162
- binary_names.map! { |n| "#{n}.exe" }
163
- binary_names.dup.each { |n| binary_names << n.gsub('exe', 'bat') }
164
- end
165
-
166
- binary_names.each do |binary_name|
167
- paths.each do |path|
168
- full_path = File.join(path, binary_name)
169
- full_path = unix_path(full_path) if windows?
170
- exe = Dir.glob(full_path).find { |f| File.executable?(f) }
171
- return exe if exe
172
- end
173
- end
174
-
175
- nil
176
- end
177
-
178
- def find_in_program_files(*binary_names)
179
- paths = [
180
- ENV['PROGRAMFILES'] || '\\Program Files',
181
- ENV['ProgramFiles(x86)'] || '\\Program Files (x86)',
182
- ENV['ProgramW6432'] || '\\Program Files'
183
- ]
184
-
185
- paths.each do |root|
186
- binary_names.each do |name|
187
- exe = File.join(root, name)
188
- return exe if File.executable?(exe)
189
- end
190
- end
191
-
192
- nil
193
- end
194
-
195
155
  def localhost
196
156
  info = Socket.getaddrinfo 'localhost', 80, Socket::AF_INET, Socket::SOCK_STREAM
197
157
 
@@ -34,7 +34,7 @@ module Selenium
34
34
  Platform.interfaces.each do |host|
35
35
  TCPServer.new(host, port).close
36
36
  rescue *IGNORED_ERRORS => e
37
- WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{e.message})")
37
+ WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{e.message})", id: :driver_service)
38
38
  # ignored - some machines appear unable to bind to some of their interfaces
39
39
  end
40
40
 
@@ -40,7 +40,7 @@ module Selenium
40
40
  end
41
41
 
42
42
  def as_json(*)
43
- {"zip" => encoded}
43
+ {'zip' => encoded}
44
44
  end
45
45
 
46
46
  def to_json(*)
@@ -49,7 +49,7 @@ module Selenium
49
49
  proxy = new
50
50
 
51
51
  ALLOWED.each do |k, v|
52
- proxy.send("#{k}=", data[v]) if data.key?(v)
52
+ proxy.send(:"#{k}=", data[v]) if data.key?(v)
53
53
  end
54
54
 
55
55
  proxy
@@ -60,7 +60,7 @@ module Selenium
60
60
 
61
61
  opts.each do |k, v|
62
62
  if ALLOWED.key?(k)
63
- send("#{k}=", v)
63
+ send(:"#{k}=", v)
64
64
  else
65
65
  not_allowed << k
66
66
  end
@@ -74,7 +74,7 @@ module Selenium
74
74
  def ==(other)
75
75
  other.is_a?(self.class) && as_json == other.as_json
76
76
  end
77
- alias_method :eql?, :==
77
+ alias eql? ==
78
78
 
79
79
  def ftp=(value)
80
80
  self.type = :manual
@@ -152,7 +152,7 @@ module Selenium
152
152
  'socksUsername' => socks_username,
153
153
  'socksPassword' => socks_password,
154
154
  'socksVersion' => socks_version
155
- }.delete_if { |_k, v| v.nil? }
155
+ }.compact
156
156
 
157
157
  json_result if json_result.length > 1
158
158
  end
@@ -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,13 +65,10 @@ 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
64
- rescue Selenium::WebDriver::Error::TimeoutError
65
- # Implicit Wait times out in Edge
66
- raise Selenium::WebDriver::Error::NoSuchElementError
67
72
  end
68
73
 
69
74
  #
@@ -75,13 +80,10 @@ module Selenium
75
80
  def find_elements(*args)
76
81
  how, what = extract_args(args)
77
82
 
78
- by = FINDERS[how.to_sym]
83
+ by = SearchContext.finders[how.to_sym]
79
84
  raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
80
85
 
81
86
  bridge.find_elements_by by, what, ref
82
- rescue Selenium::WebDriver::Error::TimeoutError
83
- # Implicit Wait times out in Edge
84
- []
85
87
  end
86
88
 
87
89
  private
@@ -0,0 +1,104 @@
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 'open3'
21
+
22
+ module Selenium
23
+ module WebDriver
24
+ #
25
+ # Wrapper for getting information from the Selenium Manager binaries.
26
+ # This implementation is still in beta, and may change.
27
+ # @api private
28
+ #
29
+ class SeleniumManager
30
+ class << self
31
+ attr_writer :bin_path
32
+
33
+ def bin_path
34
+ @bin_path ||= '../../../../../bin'
35
+ end
36
+
37
+ # @param [Array] arguments what gets sent to to Selenium Manager binary.
38
+ # @return [Hash] paths to the requested assets.
39
+ def binary_paths(*arguments)
40
+ arguments += %w[--language-binding ruby]
41
+ arguments += %w[--output json]
42
+ arguments << '--debug' if WebDriver.logger.debug?
43
+
44
+ run(binary, *arguments)
45
+ end
46
+
47
+ private
48
+
49
+ # @return [String] the path to the correct selenium manager
50
+ def binary
51
+ @binary ||= begin
52
+ if (location = ENV.fetch('SE_MANAGER_PATH', nil))
53
+ WebDriver.logger.debug("Selenium Manager set by ENV['SE_MANAGER_PATH']: #{location}")
54
+ end
55
+ location ||= platform_location
56
+
57
+ Platform.assert_executable(location)
58
+ WebDriver.logger.debug("Selenium Manager binary found at #{location}", id: :selenium_manager)
59
+ location
60
+ end
61
+ end
62
+
63
+ def run(*command)
64
+ WebDriver.logger.debug("Executing Process #{command}", id: :selenium_manager)
65
+
66
+ begin
67
+ stdout, stderr, status = Open3.capture3(*command)
68
+ rescue StandardError => e
69
+ raise Error::WebDriverError, "Unsuccessful command executed: #{command}; #{e.message}"
70
+ end
71
+
72
+ json_output = stdout.empty? ? {'logs' => [], 'result' => {}} : JSON.parse(stdout)
73
+ json_output['logs'].each do |log|
74
+ level = log['level'].casecmp('info').zero? ? 'debug' : log['level'].downcase
75
+ WebDriver.logger.send(level, log['message'], id: :selenium_manager)
76
+ end
77
+
78
+ result = json_output['result']
79
+ return result unless status.exitstatus.positive? || result.nil?
80
+
81
+ raise Error::WebDriverError,
82
+ "Unsuccessful command executed: #{command} - Code #{status.exitstatus}\n#{result}\n#{stderr}"
83
+ end
84
+
85
+ def platform_location
86
+ directory = File.expand_path(bin_path, __FILE__)
87
+ if Platform.windows?
88
+ "#{directory}/windows/selenium-manager.exe"
89
+ elsif Platform.mac?
90
+ "#{directory}/macos/selenium-manager"
91
+ elsif Platform.linux?
92
+ "#{directory}/linux/selenium-manager"
93
+ elsif Platform.unix?
94
+ WebDriver.logger.warn('Selenium Manager binary may not be compatible with Unix',
95
+ id: %i[selenium_manager unix_binary])
96
+ "#{directory}/linux/selenium-manager"
97
+ else
98
+ raise Error::WebDriverError, "unsupported platform: #{Platform.os}"
99
+ end
100
+ end
101
+ end
102
+ end # SeleniumManager
103
+ end # WebDriver
104
+ end # Selenium
@@ -39,12 +39,13 @@ module Selenium
39
39
  def ie(**opts)
40
40
  IE::Service.new(**opts)
41
41
  end
42
- alias_method :internet_explorer, :ie
42
+ alias internet_explorer ie
43
43
 
44
44
  def edge(**opts)
45
45
  Edge::Service.new(**opts)
46
46
  end
47
- alias_method :microsoftedge, :edge
47
+ alias microsoftedge edge
48
+ alias msedge edge
48
49
 
49
50
  def safari(**opts)
50
51
  Safari::Service.new(**opts)
@@ -56,8 +57,8 @@ module Selenium
56
57
  end
57
58
  end
58
59
 
59
- attr_accessor :host
60
- attr_reader :executable_path, :port, :extra_args
60
+ attr_accessor :host, :executable_path, :port, :log, :args
61
+ alias extra_args args
61
62
 
62
63
  #
63
64
  # End users should use a class method for the desired driver, rather than using this directly.
@@ -65,47 +66,37 @@ module Selenium
65
66
  # @api private
66
67
  #
67
68
 
68
- def initialize(path: nil, port: nil, args: nil)
69
- path ||= self.class.driver_path
69
+ def initialize(path: nil, port: nil, log: nil, args: nil)
70
70
  port ||= self.class::DEFAULT_PORT
71
71
  args ||= []
72
72
 
73
- @executable_path = binary_path(path)
73
+ @executable_path = path
74
74
  @host = Platform.localhost
75
75
  @port = Integer(port)
76
-
77
- @extra_args = args.is_a?(Hash) ? extract_service_args(args) : args
76
+ @log = case log
77
+ when :stdout
78
+ $stdout
79
+ when :stderr
80
+ $stderr
81
+ else
82
+ log
83
+ end
84
+ @args = args
78
85
 
79
86
  raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
80
87
  end
81
88
 
82
89
  def launch
83
- sm = ServiceManager.new(self)
84
- sm.start
85
- sm
90
+ @executable_path ||= begin
91
+ default_options = WebDriver.const_get("#{self.class.name&.split('::')&.[](2)}::Options").new
92
+ DriverFinder.new(default_options, self).driver_path
93
+ end
94
+ ServiceManager.new(self).tap(&:start)
86
95
  end
87
96
 
88
97
  def shutdown_supported
89
98
  self.class::SHUTDOWN_SUPPORTED
90
99
  end
91
-
92
- protected
93
-
94
- def extract_service_args(driver_opts)
95
- driver_opts.key?(:args) ? driver_opts.delete(:args) : []
96
- end
97
-
98
- private
99
-
100
- def binary_path(path = nil)
101
- path = path.call if path.is_a?(Proc)
102
- path ||= Platform.find_binary(self.class::EXECUTABLE)
103
-
104
- raise Error::WebDriverError, self.class::MISSING_TEXT unless path
105
-
106
- Platform.assert_executable path
107
- path
108
- end
109
100
  end # Service
110
101
  end # WebDriver
111
102
  end # Selenium
@@ -40,7 +40,8 @@ module Selenium
40
40
  @executable_path = config.executable_path
41
41
  @host = Platform.localhost
42
42
  @port = config.port
43
- @extra_args = config.extra_args
43
+ @io = config.log
44
+ @extra_args = config.args
44
45
  @shutdown_supported = config.shutdown_supported
45
46
 
46
47
  raise Error::WebDriverError, "invalid port: #{@port}" if @port < 1
@@ -49,7 +50,7 @@ module Selenium
49
50
  def start
50
51
  raise "already started: #{uri.inspect} #{@executable_path.inspect}" if process_running?
51
52
 
52
- Platform.exit_hook(&method(:stop)) # make sure we don't leave the server running
53
+ Platform.exit_hook { stop } # make sure we don't leave the server running
53
54
 
54
55
  socket_lock.locked do
55
56
  find_free_port
@@ -60,10 +61,11 @@ module Selenium
60
61
 
61
62
  def stop
62
63
  return unless @shutdown_supported
64
+ return if process_exited?
63
65
 
64
66
  stop_server
65
67
  @process.poll_for_exit STOP_TIMEOUT
66
- rescue ChildProcess::TimeoutError
68
+ rescue ChildProcess::TimeoutError, Errno::ECONNREFUSED
67
69
  nil # noop
68
70
  ensure
69
71
  stop_process
@@ -76,14 +78,10 @@ module Selenium
76
78
  private
77
79
 
78
80
  def build_process(*command)
79
- WebDriver.logger.debug("Executing Process #{command}")
81
+ WebDriver.logger.debug("Executing Process #{command}", id: :driver_service)
80
82
  @process = ChildProcess.build(*command)
81
- if WebDriver.logger.debug?
82
- @process.io.stdout = @process.io.stderr = WebDriver.logger.io
83
- elsif Platform.jruby?
84
- # Apparently we need to read the output of drivers on JRuby.
85
- @process.io.stdout = @process.io.stderr = File.new(Platform.null_device, 'w')
86
- end
83
+ @io ||= WebDriver.logger.io if WebDriver.logger.debug?
84
+ @process.io = @io if @io
87
85
 
88
86
  @process
89
87
  end
@@ -103,8 +101,6 @@ module Selenium
103
101
 
104
102
  def start_process
105
103
  @process = build_process(@executable_path, "--port=#{@port}", *@extra_args)
106
- # NOTE: this is a bug only in Windows 7
107
- @process.leader = true unless Platform.windows?
108
104
  @process.start
109
105
  end
110
106
 
@@ -112,12 +108,9 @@ module Selenium
112
108
  return if process_exited?
113
109
 
114
110
  @process.stop STOP_TIMEOUT
115
- @process.io.stdout.close if Platform.jruby? && !WebDriver.logger.debug?
116
111
  end
117
112
 
118
113
  def stop_server
119
- return if process_exited?
120
-
121
114
  connect_to_server do |http|
122
115
  headers = WebDriver::Remote::Http::Common::DEFAULT_HEADERS.dup
123
116
  http.get('/shutdown', headers)
@@ -42,10 +42,10 @@ module Selenium
42
42
  def ==(other)
43
43
  other.is_a?(self.class) && ref == other.ref
44
44
  end
45
- alias_method :eql?, :==
45
+ alias eql? ==
46
46
 
47
47
  def hash
48
- @id.hash ^ @bridge.hash
48
+ [@id, @bridge].hash
49
49
  end
50
50
 
51
51
  #
@@ -81,7 +81,6 @@ module Selenium
81
81
  private
82
82
 
83
83
  attr_reader :bridge
84
-
85
84
  end # ShadowRoot
86
85
  end # WebDriver
87
86
  end # Selenium
@@ -26,6 +26,7 @@ module Selenium
26
26
  class SocketLock
27
27
  def initialize(port, timeout)
28
28
  @port = port
29
+ @server = nil
29
30
  @timeout = timeout
30
31
  end
31
32
 
@@ -66,11 +67,10 @@ module Selenium
66
67
 
67
68
  def can_lock?
68
69
  @server = TCPServer.new(Platform.localhost, @port)
69
- ChildProcess.close_on_exec @server
70
-
70
+ @server.close_on_exec = true
71
71
  true
72
72
  rescue SocketError, Errno::EADDRINUSE, Errno::EBADF => e
73
- WebDriver.logger.debug("#{self}: #{e.message}")
73
+ WebDriver.logger.debug("#{self}: #{e.message}", id: :driver_service)
74
74
  false
75
75
  end
76
76
 
@@ -78,7 +78,7 @@ module Selenium
78
78
  def listening?
79
79
  addr = Socket.getaddrinfo(@host, @port, Socket::AF_INET, Socket::SOCK_STREAM)
80
80
  sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
81
- sockaddr = Socket.pack_sockaddr_in(@port, addr[0][3])
81
+ sockaddr = Socket.pack_sockaddr_in(@port, addr[0][3].to_s)
82
82
 
83
83
  begin
84
84
  sock.connect_nonblock sockaddr
@@ -93,13 +93,13 @@ module Selenium
93
93
  true
94
94
  rescue *NOT_CONNECTED_ERRORS
95
95
  sock&.close
96
- WebDriver.logger.debug("polling for socket on #{[@host, @port].inspect}")
96
+ WebDriver.logger.debug("polling for socket on #{[@host, @port].inspect}", id: :driver_service)
97
97
  false
98
98
  end
99
99
  end
100
100
 
101
101
  def socket_writable?(sock)
102
- IO.select(nil, [sock], nil, CONNECT_TIMEOUT)
102
+ sock.wait_writable(CONNECT_TIMEOUT)
103
103
  end
104
104
 
105
105
  def conn_completed?(sock)