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