selenium-webdriver 4.1.0 → 4.23.0

Sign up to get free protection for your applications and to get access to all the features.
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)