selenium-webdriver 3.142.7 → 4.10.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 (186) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +611 -5
  3. data/Gemfile +5 -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 +94 -79
  11. data/lib/selenium/webdriver/atoms/findElements.js +121 -0
  12. data/lib/selenium/webdriver/atoms/getAttribute.js +100 -7
  13. data/lib/selenium/webdriver/atoms/isDisplayed.js +76 -78
  14. data/lib/selenium/webdriver/atoms/mutationListener.js +55 -0
  15. data/lib/selenium/webdriver/atoms.rb +2 -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 +44 -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 +6 -33
  32. data/lib/selenium/webdriver/chrome.rb +5 -18
  33. data/lib/selenium/webdriver/chromium/driver.rb +61 -0
  34. data/lib/selenium/webdriver/{chrome/bridge.rb → chromium/features.rb} +51 -16
  35. data/lib/selenium/webdriver/chromium/options.rb +261 -0
  36. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  37. data/lib/selenium/webdriver/chromium/service.rb +42 -0
  38. data/lib/selenium/webdriver/chromium.rb +32 -0
  39. data/lib/selenium/webdriver/common/action_builder.rb +128 -238
  40. data/lib/selenium/webdriver/common/child_process.rb +124 -0
  41. data/lib/selenium/webdriver/common/driver.rb +94 -43
  42. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  43. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +42 -0
  44. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  45. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +49 -0
  46. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +87 -0
  47. data/lib/selenium/webdriver/common/driver_extensions/{has_touch_screen.rb → has_bidi.rb} +9 -9
  48. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +86 -0
  49. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +36 -0
  50. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +42 -0
  51. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  52. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +41 -0
  53. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +36 -0
  54. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +5 -9
  55. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +143 -0
  56. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_logs.rb} +4 -4
  57. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +16 -1
  58. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +6 -27
  59. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +69 -0
  60. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -13
  61. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +75 -0
  62. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  63. data/lib/selenium/webdriver/common/driver_finder.rb +47 -0
  64. data/lib/selenium/webdriver/common/element.rb +89 -29
  65. data/lib/selenium/webdriver/common/error.rb +53 -194
  66. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  67. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  68. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -22
  69. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  70. data/lib/selenium/webdriver/common/interactions/key_actions.rb +10 -6
  71. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  72. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  73. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  74. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +71 -82
  75. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  76. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  77. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  78. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  79. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  80. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  81. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  82. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  83. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  84. data/lib/selenium/webdriver/common/{w3c_manager.rb → interactions/wheel_input.rb} +14 -17
  85. data/lib/selenium/webdriver/common/keys.rb +1 -0
  86. data/lib/selenium/webdriver/common/local_driver.rb +55 -0
  87. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  88. data/lib/selenium/webdriver/common/logger.rb +119 -19
  89. data/lib/selenium/webdriver/common/manager.rb +11 -38
  90. data/lib/selenium/webdriver/common/options.rb +169 -23
  91. data/lib/selenium/webdriver/common/platform.rb +14 -6
  92. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  93. data/lib/selenium/webdriver/common/profile_helper.rb +11 -9
  94. data/lib/selenium/webdriver/common/proxy.rb +8 -5
  95. data/lib/selenium/webdriver/common/search_context.rb +7 -9
  96. data/lib/selenium/webdriver/common/selenium_manager.rb +125 -0
  97. data/lib/selenium/webdriver/common/service.rb +26 -137
  98. data/lib/selenium/webdriver/common/service_manager.rb +144 -0
  99. data/lib/selenium/webdriver/common/shadow_root.rb +86 -0
  100. data/lib/selenium/webdriver/common/socket_lock.rb +4 -4
  101. data/lib/selenium/webdriver/common/socket_poller.rb +4 -4
  102. data/lib/selenium/webdriver/common/takes_screenshot.rb +65 -0
  103. data/lib/selenium/webdriver/common/target_locator.rb +31 -4
  104. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  105. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  106. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  107. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  108. data/lib/selenium/webdriver/common/wait.rb +1 -1
  109. data/lib/selenium/webdriver/common/websocket_connection.rb +164 -0
  110. data/lib/selenium/webdriver/common/window.rb +6 -10
  111. data/lib/selenium/webdriver/common/zipper.rb +4 -10
  112. data/lib/selenium/webdriver/common.rb +42 -18
  113. data/lib/selenium/webdriver/devtools/console_event.rb +36 -0
  114. data/lib/selenium/webdriver/devtools/exception_event.rb +34 -0
  115. data/lib/selenium/webdriver/devtools/mutation_event.rb +35 -0
  116. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  117. data/lib/selenium/webdriver/devtools/pinned_script.rb +57 -0
  118. data/lib/selenium/webdriver/devtools/request.rb +65 -0
  119. data/lib/selenium/webdriver/devtools/response.rb +64 -0
  120. data/lib/selenium/webdriver/devtools.rb +96 -0
  121. data/lib/selenium/webdriver/edge/driver.rb +11 -27
  122. data/lib/selenium/webdriver/edge/features.rb +44 -0
  123. data/lib/selenium/webdriver/edge/options.rb +18 -43
  124. data/lib/selenium/webdriver/edge/profile.rb +33 -0
  125. data/lib/selenium/webdriver/edge/service.rb +7 -27
  126. data/lib/selenium/webdriver/edge.rb +11 -14
  127. data/lib/selenium/webdriver/firefox/driver.rb +38 -19
  128. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  129. data/lib/selenium/webdriver/firefox/features.rb +66 -0
  130. data/lib/selenium/webdriver/firefox/options.rb +77 -50
  131. data/lib/selenium/webdriver/firefox/profile.rb +17 -71
  132. data/lib/selenium/webdriver/firefox/service.rb +3 -13
  133. data/lib/selenium/webdriver/firefox/util.rb +1 -1
  134. data/lib/selenium/webdriver/firefox.rb +17 -28
  135. data/lib/selenium/webdriver/ie/driver.rb +5 -45
  136. data/lib/selenium/webdriver/ie/options.rb +15 -46
  137. data/lib/selenium/webdriver/ie/service.rb +11 -19
  138. data/lib/selenium/webdriver/ie.rb +3 -16
  139. data/lib/selenium/webdriver/remote/bridge/commands.rb +170 -0
  140. data/lib/selenium/webdriver/remote/bridge.rb +592 -87
  141. data/lib/selenium/webdriver/remote/capabilities.rb +182 -124
  142. data/lib/selenium/webdriver/remote/driver.rb +30 -15
  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 +23 -31
  146. data/lib/selenium/webdriver/remote/response.rb +17 -49
  147. data/lib/selenium/webdriver/remote.rb +14 -12
  148. data/lib/selenium/webdriver/safari/driver.rb +7 -29
  149. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +3 -5
  150. data/lib/selenium/webdriver/safari/options.rb +12 -27
  151. data/lib/selenium/webdriver/safari/service.rb +13 -11
  152. data/lib/selenium/webdriver/safari.rb +14 -20
  153. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  154. data/lib/selenium/webdriver/support/color.rb +24 -24
  155. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  156. data/lib/selenium/webdriver/support/guards/guard.rb +87 -0
  157. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +21 -20
  158. data/lib/selenium/webdriver/support/guards.rb +95 -0
  159. data/lib/selenium/webdriver/support/relative_locator.rb +50 -0
  160. data/lib/selenium/webdriver/support/select.rb +6 -4
  161. data/lib/selenium/webdriver/support.rb +1 -0
  162. data/lib/selenium/webdriver/version.rb +1 -1
  163. data/lib/selenium/webdriver.rb +18 -17
  164. data/selenium-webdriver.gemspec +36 -18
  165. metadata +159 -89
  166. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  167. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  168. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  169. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  170. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  171. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  172. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  173. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  174. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  175. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  176. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  177. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  178. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  179. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  180. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  181. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -60
  182. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  183. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  184. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  185. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  186. data/lib/selenium/webdriver/remote/w3c/commands.rb +0 -157
@@ -21,126 +21,579 @@ module Selenium
21
21
  module WebDriver
22
22
  module Remote
23
23
  class Bridge
24
+ autoload :COMMANDS, 'selenium/webdriver/remote/bridge/commands'
24
25
  include Atoms
25
- include BridgeHelper
26
26
 
27
27
  PORT = 4444
28
- COMMANDS = {
29
- new_session: [:post, 'session']
30
- }.freeze
31
28
 
32
- attr_accessor :context, :http, :file_detector
33
- attr_reader :capabilities, :dialect
29
+ attr_accessor :http, :file_detector
30
+ attr_reader :capabilities
34
31
 
35
32
  #
36
- # Implements protocol handshake which:
33
+ # Initializes the bridge with the given server URL
34
+ # @param [String, URI] url url for the remote server
35
+ # @param [Object] http_client an HTTP client instance that implements the same protocol as Http::Default
36
+ # @api private
37
37
  #
38
- # 1. Creates session with driver.
39
- # 2. Sniffs response.
40
- # 3. Based on the response, understands which dialect we should use.
38
+
39
+ def initialize(url:, http_client: nil)
40
+ uri = url.is_a?(URI) ? url : URI.parse(url)
41
+ uri.path += '/' unless uri.path.end_with?('/')
42
+
43
+ @http = http_client || Http::Default.new
44
+ @http.server_url = uri
45
+ @file_detector = nil
46
+ end
47
+
41
48
  #
42
- # @return [OSS:Bridge, W3C::Bridge]
49
+ # Creates session.
43
50
  #
44
- def self.handshake(**opts)
45
- desired_capabilities = opts.delete(:desired_capabilities) { Capabilities.new }
46
51
 
47
- if desired_capabilities.is_a?(Symbol)
48
- unless Capabilities.respond_to?(desired_capabilities)
49
- raise Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
50
- end
52
+ def create_session(capabilities)
53
+ response = execute(:new_session, {}, prepare_capabilities_payload(capabilities))
51
54
 
52
- desired_capabilities = Capabilities.__send__(desired_capabilities)
55
+ @session_id = response['sessionId']
56
+ capabilities = response['capabilities']
57
+
58
+ raise Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
59
+
60
+ @capabilities = Capabilities.json_create(capabilities)
61
+
62
+ case @capabilities[:browser_name]
63
+ when 'chrome'
64
+ extend(WebDriver::Chrome::Features)
65
+ when 'firefox'
66
+ extend(WebDriver::Firefox::Features)
67
+ when 'msedge'
68
+ extend(WebDriver::Edge::Features)
69
+ when 'Safari', 'Safari Technology Preview'
70
+ extend(WebDriver::Safari::Features)
53
71
  end
72
+ end
54
73
 
55
- bridge = new(opts)
56
- capabilities = bridge.create_session(desired_capabilities, opts.delete(:options))
74
+ #
75
+ # Returns the current session ID.
76
+ #
57
77
 
58
- case bridge.dialect
59
- when :oss
60
- Remote::OSS::Bridge.new(capabilities, bridge.session_id, **opts)
61
- when :w3c
62
- Remote::W3C::Bridge.new(capabilities, bridge.session_id, **opts)
63
- else
64
- raise WebDriverError, 'cannot understand dialect'
78
+ def session_id
79
+ @session_id || raise(Error::WebDriverError, 'no current session exists')
80
+ end
81
+
82
+ def browser
83
+ @browser ||= begin
84
+ name = @capabilities.browser_name
85
+ name ? name.tr(' ', '_').downcase.to_sym : 'unknown'
65
86
  end
66
87
  end
67
88
 
89
+ def status
90
+ execute :status
91
+ end
92
+
93
+ def get(url)
94
+ execute :get, {}, {url: url}
95
+ end
96
+
68
97
  #
69
- # Initializes the bridge with the given server URL
70
- # @param [Hash] opts options for the driver
71
- # @option opts [String] :url url for the remote server
72
- # @option opts [Object] :http_client an HTTP client instance that implements the same protocol as Http::Default
73
- # @option opts [Capabilities] :desired_capabilities an instance of Remote::Capabilities describing the capabilities you want
74
- # @api private
98
+ # timeouts
99
+ #
100
+
101
+ def timeouts
102
+ execute :get_timeouts, {}
103
+ end
104
+
105
+ def timeouts=(timeouts)
106
+ execute :set_timeout, {}, timeouts
107
+ end
108
+
109
+ #
110
+ # alerts
75
111
  #
76
112
 
77
- def initialize(opts = {})
78
- opts = opts.dup
113
+ def accept_alert
114
+ execute :accept_alert
115
+ end
79
116
 
80
- http_client = opts.delete(:http_client) { Http::Default.new }
81
- url = opts.delete(:url) { "http://#{Platform.localhost}:#{PORT}/wd/hub" }
82
- opts.delete(:options)
117
+ def dismiss_alert
118
+ execute :dismiss_alert
119
+ end
83
120
 
84
- unless opts.empty?
85
- raise ArgumentError, "unknown option#{'s' if opts.size != 1}: #{opts.inspect}"
86
- end
121
+ def alert=(keys)
122
+ execute :send_alert_text, {}, {value: keys.chars, text: keys}
123
+ end
87
124
 
88
- uri = url.is_a?(URI) ? url : URI.parse(url)
89
- uri.path += '/' unless uri.path =~ %r{\/$}
125
+ def alert_text
126
+ execute :get_alert_text
127
+ end
128
+
129
+ #
130
+ # navigation
131
+ #
90
132
 
91
- http_client.server_url = uri
133
+ def go_back
134
+ execute :back
135
+ end
92
136
 
93
- @http = http_client
94
- @file_detector = nil
137
+ def go_forward
138
+ execute :forward
139
+ end
140
+
141
+ def url
142
+ execute :get_current_url
143
+ end
144
+
145
+ def title
146
+ execute :get_title
147
+ end
148
+
149
+ def page_source
150
+ execute :get_page_source
95
151
  end
96
152
 
97
153
  #
98
- # Creates session handling both OSS and W3C dialects.
154
+ # Create a new top-level browsing context
155
+ # https://w3c.github.io/webdriver/#new-window
156
+ # @param type [String] Supports two values: 'tab' and 'window'.
157
+ # Use 'tab' if you'd like the new window to share an OS-level window
158
+ # with the current browsing context.
159
+ # Use 'window' otherwise
160
+ # @return [Hash] Containing 'handle' with the value of the window handle
161
+ # and 'type' with the value of the created window type
99
162
  #
163
+ def new_window(type)
164
+ execute :new_window, {}, {type: type}
165
+ end
100
166
 
101
- def create_session(desired_capabilities, options = nil)
102
- response = execute(:new_session, {}, merged_capabilities(desired_capabilities, options))
167
+ def switch_to_window(name)
168
+ execute :switch_to_window, {}, {handle: name}
169
+ end
103
170
 
104
- @session_id = response['sessionId']
105
- oss_status = response['status']
106
- value = response['value']
171
+ def switch_to_frame(id)
172
+ id = find_element_by('id', id) if id.is_a? String
173
+ execute :switch_to_frame, {}, {id: id}
174
+ end
107
175
 
108
- if value.is_a?(Hash)
109
- @session_id = value['sessionId'] if value.key?('sessionId')
176
+ def switch_to_parent_frame
177
+ execute :switch_to_parent_frame
178
+ end
110
179
 
111
- if value.key?('capabilities')
112
- value = value['capabilities']
113
- elsif value.key?('value')
114
- value = value['value']
115
- end
180
+ def switch_to_default_content
181
+ switch_to_frame nil
182
+ end
183
+
184
+ QUIT_ERRORS = [IOError].freeze
185
+
186
+ def quit
187
+ execute :delete_session
188
+ http.close
189
+ rescue *QUIT_ERRORS
190
+ nil
191
+ end
192
+
193
+ def close
194
+ execute :close_window
195
+ end
196
+
197
+ def refresh
198
+ execute :refresh
199
+ end
200
+
201
+ #
202
+ # window handling
203
+ #
204
+
205
+ def window_handles
206
+ execute :get_window_handles
207
+ end
208
+
209
+ def window_handle
210
+ execute :get_window_handle
211
+ end
212
+
213
+ def resize_window(width, height, handle = :current)
214
+ raise Error::WebDriverError, 'Switch to desired window before changing its size' unless handle == :current
215
+
216
+ set_window_rect(width: width, height: height)
217
+ end
218
+
219
+ def window_size(handle = :current)
220
+ unless handle == :current
221
+ raise Error::UnsupportedOperationError,
222
+ 'Switch to desired window before getting its size'
116
223
  end
117
224
 
118
- raise Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
225
+ data = execute :get_window_rect
226
+ Dimension.new data['width'], data['height']
227
+ end
228
+
229
+ def minimize_window
230
+ execute :minimize_window
231
+ end
232
+
233
+ def maximize_window(handle = :current)
234
+ unless handle == :current
235
+ raise Error::UnsupportedOperationError,
236
+ 'Switch to desired window before changing its size'
237
+ end
238
+
239
+ execute :maximize_window
240
+ end
241
+
242
+ def full_screen_window
243
+ execute :fullscreen_window
244
+ end
245
+
246
+ def reposition_window(x, y)
247
+ set_window_rect(x: x, y: y)
248
+ end
249
+
250
+ def window_position
251
+ data = execute :get_window_rect
252
+ Point.new data['x'], data['y']
253
+ end
254
+
255
+ def set_window_rect(x: nil, y: nil, width: nil, height: nil)
256
+ params = {x: x, y: y, width: width, height: height}
257
+ params.update(params) { |_k, v| Integer(v) unless v.nil? }
258
+ execute :set_window_rect, {}, params
259
+ end
260
+
261
+ def window_rect
262
+ data = execute :get_window_rect
263
+ Rectangle.new data['x'], data['y'], data['width'], data['height']
264
+ end
265
+
266
+ def screenshot
267
+ execute :take_screenshot
268
+ end
269
+
270
+ def element_screenshot(element)
271
+ execute :take_element_screenshot, id: element
272
+ end
119
273
 
120
- if oss_status
121
- WebDriver.logger.info 'Detected OSS dialect.'
122
- @dialect = :oss
123
- Capabilities.json_create(value)
274
+ #
275
+ # HTML 5
276
+ #
277
+
278
+ def local_storage_item(key, value = nil)
279
+ if value
280
+ execute_script("localStorage.setItem('#{key}', '#{value}')")
124
281
  else
125
- WebDriver.logger.info 'Detected W3C dialect.'
126
- @dialect = :w3c
127
- W3C::Capabilities.json_create(value)
282
+ execute_script("return localStorage.getItem('#{key}')")
128
283
  end
129
284
  end
130
285
 
286
+ def remove_local_storage_item(key)
287
+ execute_script("localStorage.removeItem('#{key}')")
288
+ end
289
+
290
+ def local_storage_keys
291
+ execute_script('return Object.keys(localStorage)')
292
+ end
293
+
294
+ def clear_local_storage
295
+ execute_script('localStorage.clear()')
296
+ end
297
+
298
+ def local_storage_size
299
+ execute_script('return localStorage.length')
300
+ end
301
+
302
+ def session_storage_item(key, value = nil)
303
+ if value
304
+ execute_script("sessionStorage.setItem('#{key}', '#{value}')")
305
+ else
306
+ execute_script("return sessionStorage.getItem('#{key}')")
307
+ end
308
+ end
309
+
310
+ def remove_session_storage_item(key)
311
+ execute_script("sessionStorage.removeItem('#{key}')")
312
+ end
313
+
314
+ def session_storage_keys
315
+ execute_script('return Object.keys(sessionStorage)')
316
+ end
317
+
318
+ def clear_session_storage
319
+ execute_script('sessionStorage.clear()')
320
+ end
321
+
322
+ def session_storage_size
323
+ execute_script('return sessionStorage.length')
324
+ end
325
+
131
326
  #
132
- # Returns the current session ID.
327
+ # javascript execution
133
328
  #
134
329
 
135
- def session_id
136
- @session_id || raise(Error::WebDriverError, 'no current session exists')
330
+ def execute_script(script, *args)
331
+ result = execute :execute_script, {}, {script: script, args: args}
332
+ unwrap_script_result result
137
333
  end
138
334
 
139
- def browser
140
- @browser ||= begin
141
- name = @capabilities.browser_name
142
- name ? name.tr(' ', '_').to_sym : 'unknown'
335
+ def execute_async_script(script, *args)
336
+ result = execute :execute_async_script, {}, {script: script, args: args}
337
+ unwrap_script_result result
338
+ end
339
+
340
+ #
341
+ # cookies
342
+ #
343
+
344
+ def manage
345
+ @manage ||= WebDriver::Manager.new(self)
346
+ end
347
+
348
+ def add_cookie(cookie)
349
+ execute :add_cookie, {}, {cookie: cookie}
350
+ end
351
+
352
+ def delete_cookie(name)
353
+ execute :delete_cookie, name: name
354
+ end
355
+
356
+ def cookie(name)
357
+ execute :get_cookie, name: name
358
+ end
359
+
360
+ def cookies
361
+ execute :get_all_cookies
362
+ end
363
+
364
+ def delete_all_cookies
365
+ execute :delete_all_cookies
366
+ end
367
+
368
+ #
369
+ # actions
370
+ #
371
+
372
+ def action(async: false, devices: [], duration: 250)
373
+ ActionBuilder.new self, async: async, devices: devices, duration: duration
374
+ end
375
+ alias actions action
376
+
377
+ def send_actions(data)
378
+ execute :actions, {}, {actions: data}
379
+ end
380
+
381
+ def release_actions
382
+ execute :release_actions
383
+ end
384
+
385
+ def print_page(options = {})
386
+ execute :print_page, {}, {options: options}
387
+ end
388
+
389
+ def click_element(element)
390
+ execute :element_click, id: element
391
+ end
392
+
393
+ def send_keys_to_element(element, keys)
394
+ # TODO: rework file detectors before Selenium 4.0
395
+ if @file_detector
396
+ local_files = keys.first&.split("\n")&.map { |key| @file_detector.call(Array(key)) }&.compact
397
+ if local_files&.any?
398
+ keys = local_files.map { |local_file| upload(local_file) }
399
+ keys = Array(keys.join("\n"))
400
+ end
401
+ end
402
+
403
+ # Keep .split(//) for backward compatibility for now
404
+ text = keys.join
405
+ execute :element_send_keys, {id: element}, {value: text.chars, text: text}
406
+ end
407
+
408
+ def upload(local_file)
409
+ unless File.file?(local_file)
410
+ WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!",
411
+ id: :file_detector)
412
+ raise Error::WebDriverError, "You are trying to work with something that isn't a file."
143
413
  end
414
+
415
+ execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
416
+ end
417
+
418
+ def clear_element(element)
419
+ execute :element_clear, id: element
420
+ end
421
+
422
+ def submit_element(element)
423
+ script = "/* submitForm */ var form = arguments[0];\n" \
424
+ "while (form.nodeName != \"FORM\" && form.parentNode) {\n " \
425
+ "form = form.parentNode;\n" \
426
+ "}\n" \
427
+ "if (!form) { throw Error('Unable to find containing form element'); }\n" \
428
+ "if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" \
429
+ "var e = form.ownerDocument.createEvent('Event');\n" \
430
+ "e.initEvent('submit', true, true);\n" \
431
+ "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
432
+
433
+ execute_script(script, Element::ELEMENT_KEY => element)
434
+ rescue Error::JavascriptError
435
+ raise Error::UnsupportedOperationError, 'To submit an element, it must be nested inside a form element'
436
+ end
437
+
438
+ #
439
+ # element properties
440
+ #
441
+
442
+ def element_tag_name(element)
443
+ execute :get_element_tag_name, id: element
444
+ end
445
+
446
+ def element_attribute(element, name)
447
+ WebDriver.logger.debug "Using script for :getAttribute of #{name}", id: :script
448
+ execute_atom :getAttribute, element, name
449
+ end
450
+
451
+ def element_dom_attribute(element, name)
452
+ execute :get_element_attribute, id: element, name: name
453
+ end
454
+
455
+ def element_property(element, name)
456
+ execute :get_element_property, id: element, name: name
457
+ end
458
+
459
+ def element_aria_role(element)
460
+ execute :get_element_aria_role, id: element
461
+ end
462
+
463
+ def element_aria_label(element)
464
+ execute :get_element_aria_label, id: element
465
+ end
466
+
467
+ def element_value(element)
468
+ element_property element, 'value'
469
+ end
470
+
471
+ def element_text(element)
472
+ execute :get_element_text, id: element
473
+ end
474
+
475
+ def element_location(element)
476
+ data = execute :get_element_rect, id: element
477
+
478
+ Point.new data['x'], data['y']
479
+ end
480
+
481
+ def element_rect(element)
482
+ data = execute :get_element_rect, id: element
483
+
484
+ Rectangle.new data['x'], data['y'], data['width'], data['height']
485
+ end
486
+
487
+ def element_location_once_scrolled_into_view(element)
488
+ send_keys_to_element(element, [''])
489
+ element_location(element)
490
+ end
491
+
492
+ def element_size(element)
493
+ data = execute :get_element_rect, id: element
494
+
495
+ Dimension.new data['width'], data['height']
496
+ end
497
+
498
+ def element_enabled?(element)
499
+ execute :is_element_enabled, id: element
500
+ end
501
+
502
+ def element_selected?(element)
503
+ execute :is_element_selected, id: element
504
+ end
505
+
506
+ def element_displayed?(element)
507
+ WebDriver.logger.debug 'Using script for :isDisplayed', id: :script
508
+ execute_atom :isDisplayed, element
509
+ end
510
+
511
+ def element_value_of_css_property(element, prop)
512
+ execute :get_element_css_value, id: element, property_name: prop
513
+ end
514
+
515
+ #
516
+ # finding elements
517
+ #
518
+
519
+ def active_element
520
+ Element.new self, element_id_from(execute(:get_active_element))
521
+ end
522
+
523
+ alias switch_to_active_element active_element
524
+
525
+ def find_element_by(how, what, parent_ref = [])
526
+ how, what = convert_locator(how, what)
527
+
528
+ return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
529
+
530
+ parent_type, parent_id = parent_ref
531
+ id = case parent_type
532
+ when :element
533
+ execute :find_child_element, {id: parent_id}, {using: how, value: what.to_s}
534
+ when :shadow_root
535
+ execute :find_shadow_child_element, {id: parent_id}, {using: how, value: what.to_s}
536
+ else
537
+ execute :find_element, {}, {using: how, value: what.to_s}
538
+ end
539
+
540
+ Element.new self, element_id_from(id)
541
+ end
542
+
543
+ def find_elements_by(how, what, parent_ref = [])
544
+ how, what = convert_locator(how, what)
545
+
546
+ return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
547
+
548
+ parent_type, parent_id = parent_ref
549
+ ids = case parent_type
550
+ when :element
551
+ execute :find_child_elements, {id: parent_id}, {using: how, value: what.to_s}
552
+ when :shadow_root
553
+ execute :find_shadow_child_elements, {id: parent_id}, {using: how, value: what.to_s}
554
+ else
555
+ execute :find_elements, {}, {using: how, value: what.to_s}
556
+ end
557
+
558
+ ids.map { |id| Element.new self, element_id_from(id) }
559
+ end
560
+
561
+ def shadow_root(element)
562
+ id = execute :get_element_shadow_root, id: element
563
+ ShadowRoot.new self, shadow_root_id_from(id)
564
+ end
565
+
566
+ #
567
+ # virtual-authenticator
568
+ #
569
+
570
+ def add_virtual_authenticator(options)
571
+ authenticator_id = execute :add_virtual_authenticator, {}, options.as_json
572
+ VirtualAuthenticator.new(self, authenticator_id, options)
573
+ end
574
+
575
+ def remove_virtual_authenticator(id)
576
+ execute :remove_virtual_authenticator, {authenticatorId: id}
577
+ end
578
+
579
+ def add_credential(credential, id)
580
+ execute :add_credential, {authenticatorId: id}, credential
581
+ end
582
+
583
+ def credentials(authenticator_id)
584
+ execute :get_credentials, {authenticatorId: authenticator_id}
585
+ end
586
+
587
+ def remove_credential(credential_id, authenticator_id)
588
+ execute :remove_credential, {credentialId: credential_id, authenticatorId: authenticator_id}
589
+ end
590
+
591
+ def remove_all_credentials(authenticator_id)
592
+ execute :remove_all_credentials, {authenticatorId: authenticator_id}
593
+ end
594
+
595
+ def user_verified(verified, authenticator_id)
596
+ execute :set_user_verified, {authenticatorId: authenticator_id}, {isUserVerified: verified}
144
597
  end
145
598
 
146
599
  private
@@ -163,8 +616,8 @@ module Selenium
163
616
  raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
164
617
  end
165
618
 
166
- WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
167
- http.call(verb, path, command_hash)
619
+ WebDriver.logger.debug("-> #{verb.to_s.upcase} #{path}", id: :command)
620
+ http.call(verb, path, command_hash)['value']
168
621
  end
169
622
 
170
623
  def escaper
@@ -172,23 +625,75 @@ module Selenium
172
625
  end
173
626
 
174
627
  def commands(command)
175
- raise NotImplementedError unless command == :new_session
176
-
177
628
  COMMANDS[command]
178
629
  end
179
630
 
180
- def merged_capabilities(oss_capabilities, options = nil)
181
- w3c_capabilities = W3C::Capabilities.from_oss(oss_capabilities)
182
- w3c_capabilities.merge!(options.as_json) if options
631
+ def unwrap_script_result(arg)
632
+ case arg
633
+ when Array
634
+ arg.map { |e| unwrap_script_result(e) }
635
+ when Hash
636
+ element_id = element_id_from(arg)
637
+ return Element.new(self, element_id) if element_id
638
+
639
+ shadow_root_id = shadow_root_id_from(arg)
640
+ return ShadowRoot.new self, shadow_root_id if shadow_root_id
183
641
 
184
- {
185
- desiredCapabilities: oss_capabilities,
186
- capabilities: {
187
- firstMatch: [w3c_capabilities]
188
- }
189
- }
642
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
643
+ else
644
+ arg
645
+ end
190
646
  end
191
647
 
648
+ def element_id_from(id)
649
+ id['ELEMENT'] || id[Element::ELEMENT_KEY]
650
+ end
651
+
652
+ def shadow_root_id_from(id)
653
+ id[ShadowRoot::ROOT_KEY]
654
+ end
655
+
656
+ def prepare_capabilities_payload(capabilities)
657
+ capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
658
+ {capabilities: capabilities}
659
+ end
660
+
661
+ def convert_locator(how, what)
662
+ how = SearchContext::FINDERS[how.to_sym] || how
663
+
664
+ case how
665
+ when 'class name'
666
+ how = 'css selector'
667
+ what = ".#{escape_css(what.to_s)}"
668
+ when 'id'
669
+ how = 'css selector'
670
+ what = "##{escape_css(what.to_s)}"
671
+ when 'name'
672
+ how = 'css selector'
673
+ what = "*[name='#{escape_css(what.to_s)}']"
674
+ end
675
+
676
+ if what.is_a?(Hash)
677
+ what = what.each_with_object({}) do |(h, w), hash|
678
+ h, w = convert_locator(h.to_s, w)
679
+ hash[h] = w
680
+ end
681
+ end
682
+
683
+ [how, what]
684
+ end
685
+
686
+ ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
687
+ UNICODE_CODE_POINT = 30
688
+
689
+ # Escapes invalid characters in CSS selector.
690
+ # @see https://mathiasbynens.be/notes/css-escapes
691
+ def escape_css(string)
692
+ string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
693
+ string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
694
+
695
+ string
696
+ end
192
697
  end # Bridge
193
698
  end # Remote
194
699
  end # WebDriver