selenium-webdriver 3.142.7 → 4.16.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 (188) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +685 -5
  3. data/Gemfile +6 -1
  4. data/LICENSE +1 -1
  5. data/NOTICE +2 -0
  6. data/README.md +4 -5
  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 +93 -90
  11. data/lib/selenium/webdriver/atoms/findElements.js +122 -0
  12. data/lib/selenium/webdriver/atoms/getAttribute.js +100 -7
  13. data/lib/selenium/webdriver/atoms/isDisplayed.js +77 -78
  14. data/lib/selenium/webdriver/atoms/mutationListener.js +55 -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/bidi/log/filter_by.rb +40 -0
  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_inspector.rb +143 -0
  24. data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
  25. data/lib/selenium/webdriver/bidi/session.rb +51 -0
  26. data/lib/selenium/webdriver/{common/keyboard.rb → bidi.rb} +21 -35
  27. data/lib/selenium/webdriver/chrome/driver.rb +9 -86
  28. data/lib/selenium/webdriver/chrome/features.rb +48 -0
  29. data/lib/selenium/webdriver/chrome/options.rb +9 -158
  30. data/lib/selenium/webdriver/chrome/profile.rb +3 -80
  31. data/lib/selenium/webdriver/chrome/service.rb +7 -29
  32. data/lib/selenium/webdriver/chrome.rb +5 -20
  33. data/lib/selenium/webdriver/chromium/driver.rb +60 -0
  34. data/lib/selenium/webdriver/{chrome/bridge.rb → chromium/features.rb} +48 -17
  35. data/lib/selenium/webdriver/chromium/options.rb +243 -0
  36. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  37. data/lib/selenium/webdriver/chromium.rb +29 -0
  38. data/lib/selenium/webdriver/common/action_builder.rb +126 -238
  39. data/lib/selenium/webdriver/common/child_process.rb +124 -0
  40. data/lib/selenium/webdriver/common/driver.rb +82 -43
  41. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  42. data/lib/selenium/webdriver/common/driver_extensions/{has_location.rb → full_page_screenshot.rb} +13 -11
  43. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  44. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +49 -0
  45. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +87 -0
  46. data/lib/selenium/webdriver/common/driver_extensions/{has_touch_screen.rb → has_bidi.rb} +9 -9
  47. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +86 -0
  48. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +36 -0
  49. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +42 -0
  50. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  51. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +41 -0
  52. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
  53. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +36 -0
  54. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +143 -0
  55. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_logs.rb} +4 -4
  56. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +16 -1
  57. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +69 -0
  58. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -13
  59. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +75 -0
  60. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  61. data/lib/selenium/webdriver/common/driver_finder.rb +45 -0
  62. data/lib/selenium/webdriver/common/element.rb +89 -29
  63. data/lib/selenium/webdriver/common/error.rb +53 -194
  64. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  65. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  66. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -22
  67. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  68. data/lib/selenium/webdriver/common/interactions/key_actions.rb +10 -6
  69. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  70. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  71. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  72. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +71 -82
  73. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  74. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  75. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  76. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  77. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  78. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  79. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  80. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  81. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  82. data/lib/selenium/webdriver/common/{w3c_manager.rb → interactions/wheel_input.rb} +14 -17
  83. data/lib/selenium/webdriver/common/keys.rb +1 -0
  84. data/lib/selenium/webdriver/common/local_driver.rb +46 -0
  85. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  86. data/lib/selenium/webdriver/common/logger.rb +119 -19
  87. data/lib/selenium/webdriver/common/manager.rb +11 -38
  88. data/lib/selenium/webdriver/common/options.rb +154 -23
  89. data/lib/selenium/webdriver/common/platform.rb +15 -52
  90. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  91. data/lib/selenium/webdriver/common/profile_helper.rb +11 -9
  92. data/lib/selenium/webdriver/common/proxy.rb +8 -5
  93. data/lib/selenium/webdriver/common/search_context.rb +7 -9
  94. data/lib/selenium/webdriver/common/selenium_manager.rb +140 -0
  95. data/lib/selenium/webdriver/common/service.rb +26 -143
  96. data/lib/selenium/webdriver/common/service_manager.rb +144 -0
  97. data/lib/selenium/webdriver/common/shadow_root.rb +86 -0
  98. data/lib/selenium/webdriver/common/socket_lock.rb +4 -4
  99. data/lib/selenium/webdriver/common/socket_poller.rb +4 -4
  100. data/lib/selenium/webdriver/common/takes_screenshot.rb +65 -0
  101. data/lib/selenium/webdriver/common/target_locator.rb +31 -4
  102. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  103. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  104. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  105. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  106. data/lib/selenium/webdriver/common/wait.rb +1 -1
  107. data/lib/selenium/webdriver/common/websocket_connection.rb +164 -0
  108. data/lib/selenium/webdriver/common/window.rb +6 -10
  109. data/lib/selenium/webdriver/common/zipper.rb +4 -10
  110. data/lib/selenium/webdriver/common.rb +43 -20
  111. data/lib/selenium/webdriver/devtools/console_event.rb +36 -0
  112. data/lib/selenium/webdriver/devtools/exception_event.rb +34 -0
  113. data/lib/selenium/webdriver/devtools/mutation_event.rb +35 -0
  114. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  115. data/lib/selenium/webdriver/devtools/pinned_script.rb +57 -0
  116. data/lib/selenium/webdriver/devtools/request.rb +65 -0
  117. data/lib/selenium/webdriver/devtools/response.rb +64 -0
  118. data/lib/selenium/webdriver/devtools.rb +96 -0
  119. data/lib/selenium/webdriver/edge/driver.rb +11 -27
  120. data/lib/selenium/webdriver/edge/features.rb +48 -0
  121. data/lib/selenium/webdriver/edge/options.rb +18 -43
  122. data/lib/selenium/webdriver/edge/profile.rb +33 -0
  123. data/lib/selenium/webdriver/edge/service.rb +8 -23
  124. data/lib/selenium/webdriver/edge.rb +11 -16
  125. data/lib/selenium/webdriver/firefox/driver.rb +38 -19
  126. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  127. data/lib/selenium/webdriver/firefox/features.rb +70 -0
  128. data/lib/selenium/webdriver/firefox/options.rb +73 -61
  129. data/lib/selenium/webdriver/firefox/profile.rb +20 -72
  130. data/lib/selenium/webdriver/firefox/service.rb +3 -25
  131. data/lib/selenium/webdriver/firefox/util.rb +1 -1
  132. data/lib/selenium/webdriver/firefox.rb +17 -28
  133. data/lib/selenium/webdriver/ie/driver.rb +5 -45
  134. data/lib/selenium/webdriver/ie/features.rb +34 -0
  135. data/lib/selenium/webdriver/ie/options.rb +14 -44
  136. data/lib/selenium/webdriver/ie/service.rb +3 -27
  137. data/lib/selenium/webdriver/ie.rb +4 -16
  138. data/lib/selenium/webdriver/remote/bridge/commands.rb +164 -0
  139. data/lib/selenium/webdriver/remote/bridge.rb +574 -88
  140. data/lib/selenium/webdriver/remote/capabilities.rb +144 -158
  141. data/lib/selenium/webdriver/remote/driver.rb +45 -14
  142. data/lib/selenium/webdriver/remote/features.rb +75 -0
  143. data/lib/selenium/webdriver/remote/http/common.rb +3 -8
  144. data/lib/selenium/webdriver/remote/http/curb.rb +1 -3
  145. data/lib/selenium/webdriver/remote/http/default.rb +24 -33
  146. data/lib/selenium/webdriver/remote/response.rb +17 -49
  147. data/lib/selenium/webdriver/remote/server_error.rb +1 -1
  148. data/lib/selenium/webdriver/remote.rb +15 -12
  149. data/lib/selenium/webdriver/safari/driver.rb +7 -29
  150. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +7 -5
  151. data/lib/selenium/webdriver/safari/options.rb +12 -27
  152. data/lib/selenium/webdriver/safari/service.rb +13 -11
  153. data/lib/selenium/webdriver/safari.rb +14 -20
  154. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  155. data/lib/selenium/webdriver/support/color.rb +24 -24
  156. data/lib/selenium/webdriver/support/event_firing_bridge.rb +5 -5
  157. data/lib/selenium/webdriver/support/guards/guard.rb +90 -0
  158. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +21 -20
  159. data/lib/selenium/webdriver/support/guards.rb +95 -0
  160. data/lib/selenium/webdriver/support/relative_locator.rb +50 -0
  161. data/lib/selenium/webdriver/support/select.rb +6 -4
  162. data/lib/selenium/webdriver/support.rb +1 -0
  163. data/lib/selenium/webdriver/version.rb +1 -1
  164. data/lib/selenium/webdriver.rb +19 -17
  165. data/selenium-webdriver.gemspec +36 -18
  166. metadata +161 -91
  167. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  168. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +0 -58
  169. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  170. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  171. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  172. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  173. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  174. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  175. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  176. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  177. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  178. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  179. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  180. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  181. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  182. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  183. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -60
  184. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  185. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  186. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  187. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  188. data/lib/selenium/webdriver/remote/w3c/commands.rb +0 -157
@@ -38,15 +38,31 @@ module Selenium
38
38
 
39
39
  def_delegators :@logger,
40
40
  :close,
41
- :debug, :debug?,
42
- :info, :info?,
43
- :warn, :warn?,
44
- :error, :error?,
41
+ :debug?,
42
+ :info?,
43
+ :warn?,
44
+ :error?,
45
45
  :fatal, :fatal?,
46
- :level, :level=
46
+ :level
47
47
 
48
- def initialize
49
- @logger = create_logger($stdout)
48
+ #
49
+ # @param [String] progname Allow child projects to use Selenium's Logger pattern
50
+ #
51
+ def initialize(progname = 'Selenium', default_level: nil, ignored: nil, allowed: nil)
52
+ default_level ||= $DEBUG || ENV.key?('DEBUG') ? :debug : :warn
53
+
54
+ @logger = create_logger(progname, level: default_level)
55
+ @ignored = Array(ignored)
56
+ @allowed = Array(allowed)
57
+ @first_warning = false
58
+ end
59
+
60
+ def level=(level)
61
+ if level == :info && @logger.level == :info
62
+ info(':info is now the default log level, to see additional logging, set log level to :debug')
63
+ end
64
+
65
+ @logger.level = level
50
66
  end
51
67
 
52
68
  #
@@ -73,29 +89,101 @@ module Selenium
73
89
  @logger.instance_variable_get(:@logdev).dev
74
90
  end
75
91
 
92
+ #
93
+ # Will not log the provided ID.
94
+ #
95
+ # @param [Array, Symbol] ids
96
+ #
97
+ def ignore(*ids)
98
+ @ignored += Array(ids).flatten
99
+ end
100
+
101
+ #
102
+ # Will only log the provided ID.
103
+ #
104
+ # @param [Array, Symbol] ids
105
+ #
106
+ def allow(*ids)
107
+ @allowed += Array(ids).flatten
108
+ end
109
+
110
+ #
111
+ # Used to supply information of interest for debugging a problem
112
+ # Overrides default #debug to skip ignored messages by provided id
113
+ #
114
+ # @param [String] message
115
+ # @param [Symbol, Array<Symbol>] id
116
+ # @yield see #deprecate
117
+ #
118
+ def debug(message, id: [], &block)
119
+ discard_or_log(:debug, message, id, &block)
120
+ end
121
+
122
+ #
123
+ # Used to supply information of general interest
124
+ #
125
+ # @param [String] message
126
+ # @param [Symbol, Array<Symbol>] id
127
+ # @yield see #deprecate
128
+ #
129
+ def info(message, id: [], &block)
130
+ discard_or_log(:info, message, id, &block)
131
+ end
132
+
133
+ #
134
+ # Used to supply information that suggests an error occurred
135
+ #
136
+ # @param [String] message
137
+ # @param [Symbol, Array<Symbol>] id
138
+ # @yield see #deprecate
139
+ #
140
+ def error(message, id: [], &block)
141
+ discard_or_log(:error, message, id, &block)
142
+ end
143
+
144
+ #
145
+ # Used to supply information that suggests action be taken by user
146
+ #
147
+ # @param [String] message
148
+ # @param [Symbol, Array<Symbol>] id
149
+ # @yield see #deprecate
150
+ #
151
+ def warn(message, id: [], &block)
152
+ discard_or_log(:warn, message, id, &block)
153
+ end
154
+
76
155
  #
77
156
  # Marks code as deprecated with/without replacement.
78
157
  #
79
158
  # @param [String] old
80
159
  # @param [String, nil] new
160
+ # @param [Symbol, Array<Symbol>] id
161
+ # @param [String] reference
162
+ # @yield appends additional message to end of provided template
81
163
  #
82
- def deprecate(old, new = nil)
164
+ def deprecate(old, new = nil, id: [], reference: '', &block)
165
+ id = Array(id)
166
+ return if @ignored.include?(:deprecations)
167
+
168
+ id << :deprecations if @allowed.include?(:deprecations)
169
+
83
170
  message = +"[DEPRECATION] #{old} is deprecated"
84
171
  message << if new
85
172
  ". Use #{new} instead."
86
173
  else
87
- ' and will be removed in the next releases.'
174
+ ' and will be removed in a future release.'
88
175
  end
176
+ message << " See explanation for this deprecation: #{reference}." unless reference.empty?
89
177
 
90
- warn message
178
+ discard_or_log(:warn, message, id, &block)
91
179
  end
92
180
 
93
181
  private
94
182
 
95
- def create_logger(output)
96
- logger = ::Logger.new(output)
97
- logger.progname = 'Selenium'
98
- logger.level = default_level
183
+ def create_logger(name, level:)
184
+ logger = ::Logger.new($stdout)
185
+ logger.progname = name
186
+ logger.level = level
99
187
  logger.formatter = proc do |severity, time, progname, msg|
100
188
  "#{time.strftime('%F %T')} #{severity} #{progname} #{msg}\n"
101
189
  end
@@ -103,12 +191,24 @@ module Selenium
103
191
  logger
104
192
  end
105
193
 
106
- def default_level
107
- if $DEBUG || ENV.key?('DEBUG')
108
- :debug
109
- else
110
- :warn
194
+ def discard_or_log(level, message, id)
195
+ id = Array(id)
196
+ return if (@ignored & id).any?
197
+ return if @allowed.any? && (@allowed & id).none?
198
+
199
+ return if ::Logger::Severity.const_get(level.upcase) < @logger.level
200
+
201
+ unless @first_warning
202
+ @first_warning = true
203
+ info("Details on how to use and modify Selenium logger:\n", id: [:logger_info]) do
204
+ "https://selenium.dev/documentation/webdriver/troubleshooting/logging\n"
205
+ end
111
206
  end
207
+
208
+ msg = id.empty? ? message : "[#{id.map(&:inspect).join(', ')}] #{message} "
209
+ msg += " #{yield}" if block_given?
210
+
211
+ @logger.send(level) { msg }
112
212
  end
113
213
  end # Logger
114
214
  end # WebDriver
@@ -36,6 +36,7 @@ module Selenium
36
36
  # @option opts [String] :value A value
37
37
  # @option opts [String] :path ('/') A path
38
38
  # @option opts [String] :secure (false) A boolean
39
+ # @option opts [String] :same_site (Strict or Lax) currently supported only in chrome 80+ versions
39
40
  # @option opts [Time,DateTime,Numeric,nil] :expires (nil) Expiry date, either as a Time, DateTime, or seconds since epoch.
40
41
  #
41
42
  # @raise [ArgumentError] if :name or :value is not specified
@@ -45,9 +46,15 @@ module Selenium
45
46
  raise ArgumentError, 'name is required' unless opts[:name]
46
47
  raise ArgumentError, 'value is required' unless opts[:value]
47
48
 
48
- opts[:path] ||= '/'
49
+ # NOTE: This is required because of https://bugs.chromium.org/p/chromedriver/issues/detail?id=3732
49
50
  opts[:secure] ||= false
50
51
 
52
+ same_site = opts.delete(:same_site)
53
+ opts[:sameSite] = same_site if same_site
54
+
55
+ http_only = opts.delete(:http_only)
56
+ opts[:httpOnly] = http_only if http_only
57
+
51
58
  obj = opts.delete(:expires)
52
59
  opts[:expiry] = seconds_from(obj).to_i if obj
53
60
 
@@ -62,7 +69,7 @@ module Selenium
62
69
  #
63
70
 
64
71
  def cookie_named(name)
65
- all_cookies.find { |c| c[:name] == name }
72
+ convert_cookie(@bridge.cookie(name))
66
73
  end
67
74
 
68
75
  #
@@ -97,42 +104,6 @@ module Selenium
97
104
  @timeouts ||= Timeouts.new(@bridge)
98
105
  end
99
106
 
100
- #
101
- # @api beta This API may be changed or removed in a future release.
102
- #
103
-
104
- def logs
105
- @logs ||= Logs.new(@bridge)
106
- end
107
-
108
- #
109
- # Create a new top-level browsing context
110
- # https://w3c.github.io/webdriver/#new-window
111
- # @param type [Symbol] Supports two values: :tab and :window.
112
- # Use :tab if you'd like the new window to share an OS-level window
113
- # with the current browsing context.
114
- # Use :window otherwise
115
- # @return [String] The value of the window handle
116
- #
117
- def new_window(type = :tab)
118
- case type
119
- when :tab, :window
120
- result = @bridge.new_window(type)
121
- unless result.key?('handle')
122
- raise UnknownError, "the driver did not return a handle. " \
123
- "The returned result: #{result.inspect}"
124
- end
125
- result['handle']
126
- else
127
- raise ArgumentError, "invalid argument for type. Got: '#{type.inspect}'. " \
128
- "Try :tab or :window"
129
- end
130
- end
131
-
132
- #
133
- # @api beta This API may be changed or removed in a future release.
134
- #
135
-
136
107
  def window
137
108
  @window ||= Window.new(@bridge)
138
109
  end
@@ -169,6 +140,8 @@ module Selenium
169
140
  path: cookie['path'],
170
141
  domain: cookie['domain'] && strip_port(cookie['domain']),
171
142
  expires: cookie['expiry'] && datetime_at(cookie['expiry']),
143
+ same_site: cookie['sameSite'],
144
+ http_only: cookie['httpOnly'],
172
145
  secure: cookie['secure']
173
146
  }
174
147
  end
@@ -19,35 +19,166 @@
19
19
 
20
20
  module Selenium
21
21
  module WebDriver
22
- module Common
23
- class Options
24
- private
25
-
26
- def generate_as_json(value)
27
- if value.respond_to?(:as_json)
28
- value.as_json
29
- elsif value.is_a?(Hash)
30
- value.each_with_object({}) { |(key, val), hash| hash[convert_json_key(key)] = generate_as_json(val) }
31
- elsif value.is_a?(Array)
32
- value.map(&method(:generate_as_json))
33
- elsif value.is_a?(Symbol)
34
- value.to_s
35
- else
36
- value
22
+ class Options
23
+ W3C_OPTIONS = %i[browser_name browser_version platform_name accept_insecure_certs page_load_strategy proxy
24
+ set_window_rect timeouts unhandled_prompt_behavior strict_file_interactability
25
+ web_socket_url].freeze
26
+
27
+ GRID_OPTIONS = %i[enable_downloads].freeze
28
+
29
+ class << self
30
+ attr_reader :driver_path
31
+
32
+ def chrome(**opts)
33
+ Chrome::Options.new(**opts)
34
+ end
35
+
36
+ def firefox(**opts)
37
+ Firefox::Options.new(**opts)
38
+ end
39
+
40
+ def ie(**opts)
41
+ IE::Options.new(**opts)
42
+ end
43
+ alias internet_explorer ie
44
+
45
+ def edge(**opts)
46
+ Edge::Options.new(**opts)
47
+ end
48
+ alias microsoftedge edge
49
+
50
+ def safari(**opts)
51
+ Safari::Options.new(**opts)
52
+ end
53
+
54
+ def set_capabilities
55
+ (W3C_OPTIONS + self::CAPABILITIES.keys).each do |key|
56
+ next if method_defined? key
57
+
58
+ define_method key do
59
+ @options[key]
60
+ end
61
+
62
+ define_method "#{key}=" do |value|
63
+ @options[key] = value
64
+ end
37
65
  end
38
66
  end
67
+ end
68
+
69
+ attr_accessor :options
70
+
71
+ def initialize(**opts)
72
+ self.class.set_capabilities
73
+
74
+ @options = opts
75
+ @options[:browser_name] = self.class::BROWSER
76
+ end
77
+
78
+ #
79
+ # Add a new option not yet handled by bindings.
80
+ #
81
+ # @example Leave Chrome open when chromedriver is killed
82
+ # options = Selenium::WebDriver::Chrome::Options.new
83
+ # options.add_option(:detach, true)
84
+ #
85
+ # @param [String, Symbol] name Name of the option
86
+ # @param [Boolean, String, Integer] value Value of the option
87
+ #
88
+
89
+ def add_option(name, value = nil)
90
+ name, value = name.first if value.nil? && name.is_a?(Hash)
91
+ @options[name] = value
92
+ end
39
93
 
40
- def convert_json_key(key)
41
- key = camel_case(key) if key.is_a?(Symbol)
42
- return key if key.is_a?(String)
94
+ def ==(other)
95
+ return false unless other.is_a? self.class
43
96
 
44
- raise TypeError, "expected String or Symbol, got #{key.inspect}:#{key.class}"
97
+ as_json == other.as_json
98
+ end
99
+
100
+ alias eql? ==
101
+
102
+ #
103
+ # @api private
104
+ #
105
+
106
+ def as_json(*)
107
+ options = @options.dup
108
+
109
+ downloads = options.delete(:enable_downloads)
110
+ options['se:downloadsEnabled'] = downloads unless downloads.nil?
111
+ w3c_options = process_w3c_options(options)
112
+
113
+ browser_options = self.class::CAPABILITIES.each_with_object({}) do |(capability_alias, capability_name), hash|
114
+ capability_value = options.delete(capability_alias)
115
+ hash[capability_name] = capability_value unless capability_value.nil?
45
116
  end
46
117
 
47
- def camel_case(str)
48
- str.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
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
+
122
+ process_browser_options(browser_options)
123
+ generate_as_json(w3c_options.merge(browser_options))
124
+ end
125
+
126
+ private
127
+
128
+ def w3c?(key)
129
+ W3C_OPTIONS.include?(key) || key.to_s.include?(':')
130
+ end
131
+
132
+ def process_w3c_options(options)
133
+ w3c_options = options.select { |key, val| w3c?(key) && !val.nil? }
134
+ w3c_options[:unhandled_prompt_behavior] &&= w3c_options[:unhandled_prompt_behavior]&.to_s&.tr('_', ' ')
135
+ options.delete_if { |key, _val| w3c?(key) }
136
+ w3c_options
137
+ end
138
+
139
+ def process_browser_options(_browser_options)
140
+ nil
141
+ end
142
+
143
+ def camelize?(_key)
144
+ true
145
+ end
146
+
147
+ def generate_as_json(value, camelize_keys: true)
148
+ if value.is_a?(Hash)
149
+ process_json_hash(value, camelize_keys)
150
+ elsif value.respond_to?(:as_json)
151
+ value.as_json
152
+ elsif value.is_a?(Array)
153
+ value.map { |val| generate_as_json(val, camelize_keys: camelize_keys) }
154
+ elsif value.is_a?(Symbol)
155
+ value.to_s
156
+ else
157
+ value
49
158
  end
50
- end # Options
51
- end # Common
159
+ end
160
+
161
+ def process_json_hash(value, camelize_keys)
162
+ value.each_with_object({}) do |(key, val), hash|
163
+ next if val.respond_to?(:empty?) && val.empty?
164
+
165
+ camelize = camelize_keys ? camelize?(key) : false
166
+ key = convert_json_key(key, camelize: camelize)
167
+ hash[key] = generate_as_json(val, camelize_keys: camelize)
168
+ end
169
+ end
170
+
171
+ def convert_json_key(key, camelize: true)
172
+ key = key.to_s if key.is_a?(Symbol)
173
+ key = camel_case(key) if camelize
174
+ return key if key.is_a?(String)
175
+
176
+ raise TypeError, "expected String or Symbol, got #{key.inspect}:#{key.class}"
177
+ end
178
+
179
+ def camel_case(str)
180
+ str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
181
+ end
182
+ end # Options
52
183
  end # WebDriver
53
184
  end # Selenium
@@ -57,25 +57,19 @@ module Selenium
57
57
  :jenkins
58
58
  elsif ENV['APPVEYOR']
59
59
  :appveyor
60
+ elsif ENV['GITHUB_ACTIONS']
61
+ :github
60
62
  end
61
63
  end
62
64
 
63
- def bitsize
64
- @bitsize ||= if defined?(FFI::Platform::ADDRESS_SIZE)
65
- FFI::Platform::ADDRESS_SIZE
66
- elsif defined?(FFI)
67
- FFI.type_size(:pointer) == 4 ? 32 : 64
68
- elsif jruby?
69
- Integer(ENV_JAVA['sun.arch.data.model'])
70
- else
71
- 1.size == 4 ? 32 : 64
72
- end
73
- end
74
-
75
65
  def jruby?
76
66
  engine == :jruby
77
67
  end
78
68
 
69
+ def truffleruby?
70
+ engine == :truffleruby
71
+ end
72
+
79
73
  def ruby_version
80
74
  RUBY_VERSION
81
75
  end
@@ -92,15 +86,21 @@ module Selenium
92
86
  os == :linux
93
87
  end
94
88
 
89
+ def unix?
90
+ os == :unix
91
+ end
92
+
95
93
  def wsl?
96
94
  return false unless linux?
97
95
 
98
- File.read('/proc/version').include?('Microsoft')
96
+ File.read('/proc/version').downcase.include?('microsoft')
97
+ rescue Errno::EACCES
98
+ # the file cannot be accessed on Linux on DeX
99
+ false
99
100
  end
100
101
 
101
102
  def cygwin?
102
- RUBY_PLATFORM =~ /cygwin/
103
- !Regexp.last_match.nil?
103
+ RUBY_PLATFORM.include?('cygwin')
104
104
  end
105
105
 
106
106
  def null_device
@@ -150,43 +150,6 @@ module Selenium
150
150
  at_exit { yield if Process.pid == pid }
151
151
  end
152
152
 
153
- def find_binary(*binary_names)
154
- paths = ENV['PATH'].split(File::PATH_SEPARATOR)
155
-
156
- if windows?
157
- binary_names.map! { |n| "#{n}.exe" }
158
- binary_names.dup.each { |n| binary_names << n.gsub('exe', 'bat') }
159
- end
160
-
161
- binary_names.each do |binary_name|
162
- paths.each do |path|
163
- full_path = File.join(path, binary_name)
164
- full_path = unix_path(full_path) if windows?
165
- exe = Dir.glob(full_path).find { |f| File.executable?(f) }
166
- return exe if exe
167
- end
168
- end
169
-
170
- nil
171
- end
172
-
173
- def find_in_program_files(*binary_names)
174
- paths = [
175
- ENV['PROGRAMFILES'] || '\\Program Files',
176
- ENV['ProgramFiles(x86)'] || '\\Program Files (x86)',
177
- ENV['ProgramW6432'] || '\\Program Files'
178
- ]
179
-
180
- paths.each do |root|
181
- binary_names.each do |name|
182
- exe = File.join(root, name)
183
- return exe if File.executable?(exe)
184
- end
185
- end
186
-
187
- nil
188
- end
189
-
190
153
  def localhost
191
154
  info = Socket.getaddrinfo 'localhost', 80, Socket::AF_INET, Socket::SOCK_STREAM
192
155
 
@@ -32,12 +32,10 @@ module Selenium
32
32
 
33
33
  def self.free?(port)
34
34
  Platform.interfaces.each do |host|
35
- begin
36
- TCPServer.new(host, port).close
37
- rescue *IGNORED_ERRORS => ex
38
- WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{ex.message})")
39
- # ignored - some machines appear unable to bind to some of their interfaces
40
- end
35
+ TCPServer.new(host, port).close
36
+ rescue *IGNORED_ERRORS => e
37
+ WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{e.message})", id: :driver_service)
38
+ # ignored - some machines appear unable to bind to some of their interfaces
41
39
  end
42
40
 
43
41
  true
@@ -31,8 +31,16 @@ module Selenium
31
31
  base.extend ClassMethods
32
32
  end
33
33
 
34
+ def self.decoded(json)
35
+ JSON.parse(json).fetch('zip')
36
+ end
37
+
38
+ def encoded
39
+ Zipper.zip(layout_on_disk)
40
+ end
41
+
34
42
  def as_json(*)
35
- {"zip" => Zipper.zip(layout_on_disk)}
43
+ {'zip' => encoded}
36
44
  end
37
45
 
38
46
  def to_json(*)
@@ -63,18 +71,12 @@ module Selenium
63
71
 
64
72
  module ClassMethods
65
73
  def from_json(json)
66
- data = JSON.parse(json).fetch('zip')
74
+ data = decoded(json)
67
75
 
68
- # can't use Tempfile here since it doesn't support File::BINARY mode on 1.8
69
- # can't use Dir.mktmpdir(&blk) because of http://jira.codehaus.org/browse/JRUBY-4082
70
- tmp_dir = Dir.mktmpdir
71
- begin
72
- zip_path = File.join(tmp_dir, "webdriver-profile-duplicate-#{json.hash}.zip")
76
+ Tempfile.create do |zip_path|
73
77
  File.open(zip_path, 'wb') { |zip_file| zip_file << Base64.decode64(data) }
74
78
 
75
79
  new Zipper.unzip(zip_path)
76
- ensure
77
- FileUtils.rm_rf tmp_dir
78
80
  end
79
81
  end
80
82
  end # ClassMethods
@@ -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
@@ -127,7 +127,10 @@ module Selenium
127
127
  end
128
128
 
129
129
  def type=(type)
130
- raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}" unless TYPES.key? type
130
+ unless TYPES.key? type
131
+ raise ArgumentError,
132
+ "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}"
133
+ end
131
134
 
132
135
  if defined?(@type) && type != @type
133
136
  raise ArgumentError, "incompatible proxy type #{type.inspect} (already set to #{@type.inspect})"
@@ -138,10 +141,10 @@ module Selenium
138
141
 
139
142
  def as_json(*)
140
143
  json_result = {
141
- 'proxyType' => TYPES[type],
144
+ 'proxyType' => TYPES[type].downcase,
142
145
  'ftpProxy' => ftp,
143
146
  'httpProxy' => http,
144
- 'noProxy' => no_proxy,
147
+ 'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(', ') : no_proxy,
145
148
  'proxyAutoconfigUrl' => pac,
146
149
  'sslProxy' => ssl,
147
150
  'autodetect' => auto_detect,
@@ -149,7 +152,7 @@ module Selenium
149
152
  'socksUsername' => socks_username,
150
153
  'socksPassword' => socks_password,
151
154
  'socksVersion' => socks_version
152
- }.delete_if { |_k, v| v.nil? }
155
+ }.compact
153
156
 
154
157
  json_result if json_result.length > 1
155
158
  end
@@ -30,6 +30,7 @@ module Selenium
30
30
  link_text: 'link text',
31
31
  name: 'name',
32
32
  partial_link_text: 'partial link text',
33
+ relative: 'relative',
33
34
  tag_name: 'tag name',
34
35
  xpath: 'xpath'
35
36
  }.freeze
@@ -59,10 +60,7 @@ module Selenium
59
60
  by = FINDERS[how.to_sym]
60
61
  raise ArgumentError, "cannot find element by #{how.inspect}" unless by
61
62
 
62
- bridge.find_element_by by, what.to_s, ref
63
- rescue Selenium::WebDriver::Error::TimeoutError
64
- # Implicit Wait times out in Edge
65
- raise Selenium::WebDriver::Error::NoSuchElementError
63
+ bridge.find_element_by by, what, ref
66
64
  end
67
65
 
68
66
  #
@@ -77,10 +75,7 @@ module Selenium
77
75
  by = FINDERS[how.to_sym]
78
76
  raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
79
77
 
80
- bridge.find_elements_by by, what.to_s, ref
81
- rescue Selenium::WebDriver::Error::TimeoutError
82
- # Implicit Wait times out in Edge
83
- []
78
+ bridge.find_elements_by by, what, ref
84
79
  end
85
80
 
86
81
  private
@@ -92,7 +87,10 @@ module Selenium
92
87
  when 1
93
88
  arg = args.first
94
89
 
95
- raise ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift" unless arg.respond_to?(:shift)
90
+ unless arg.respond_to?(:shift)
91
+ raise ArgumentError,
92
+ "expected #{arg.inspect}:#{arg.class} to respond to #shift"
93
+ end
96
94
 
97
95
  # this will be a single-entry hash, so use #shift over #first or #[]
98
96
  arr = arg.dup.shift