selenium-webdriver 3.142.7 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +324 -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 +21 -29
  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/chrome/driver.rb +26 -83
  13. data/lib/selenium/webdriver/chrome/{bridge.rb → features.rb} +50 -12
  14. data/lib/selenium/webdriver/chrome/options.rb +129 -58
  15. data/lib/selenium/webdriver/chrome/profile.rb +6 -3
  16. data/lib/selenium/webdriver/chrome/service.rb +8 -15
  17. data/lib/selenium/webdriver/chrome.rb +10 -9
  18. data/lib/selenium/webdriver/common/action_builder.rb +97 -249
  19. data/lib/selenium/webdriver/common/driver.rb +112 -23
  20. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +43 -0
  21. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +51 -0
  22. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +89 -0
  23. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +77 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/{has_touch_screen.rb → has_cdp.rb} +10 -8
  25. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +45 -0
  26. data/lib/selenium/webdriver/{firefox/util.rb → common/driver_extensions/has_devtools.rb} +16 -19
  27. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +38 -0
  28. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +5 -8
  29. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +144 -0
  30. data/lib/selenium/webdriver/common/driver_extensions/has_logs.rb +30 -0
  31. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +17 -0
  32. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +6 -27
  33. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +136 -0
  34. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -11
  35. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +77 -0
  36. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +1 -0
  37. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  38. data/lib/selenium/webdriver/common/element.rb +82 -22
  39. data/lib/selenium/webdriver/common/error.rb +32 -196
  40. data/lib/selenium/webdriver/common/interactions/interaction.rb +4 -1
  41. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -5
  42. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +13 -13
  43. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  44. data/lib/selenium/webdriver/common/logger.rb +50 -15
  45. data/lib/selenium/webdriver/common/manager.rb +15 -15
  46. data/lib/selenium/webdriver/common/options.rb +156 -23
  47. data/lib/selenium/webdriver/common/platform.rb +6 -1
  48. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  49. data/lib/selenium/webdriver/common/profile_helper.rb +10 -2
  50. data/lib/selenium/webdriver/common/proxy.rb +6 -3
  51. data/lib/selenium/webdriver/common/search_context.rb +7 -3
  52. data/lib/selenium/webdriver/common/service.rb +17 -125
  53. data/lib/selenium/webdriver/common/service_manager.rb +151 -0
  54. data/lib/selenium/webdriver/common/shadow_root.rb +87 -0
  55. data/lib/selenium/webdriver/common/socket_lock.rb +2 -2
  56. data/lib/selenium/webdriver/common/socket_poller.rb +2 -2
  57. data/lib/selenium/webdriver/common/takes_screenshot.rb +66 -0
  58. data/lib/selenium/webdriver/common/target_locator.rb +32 -4
  59. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  60. data/lib/selenium/webdriver/common/wait.rb +1 -1
  61. data/lib/selenium/webdriver/common/window.rb +0 -4
  62. data/lib/selenium/webdriver/common.rb +23 -17
  63. data/lib/selenium/webdriver/devtools/console_event.rb +38 -0
  64. data/lib/selenium/webdriver/devtools/exception_event.rb +36 -0
  65. data/lib/selenium/webdriver/devtools/mutation_event.rb +37 -0
  66. data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
  67. data/lib/selenium/webdriver/devtools/request.rb +67 -0
  68. data/lib/selenium/webdriver/devtools/response.rb +66 -0
  69. data/lib/selenium/webdriver/devtools.rb +182 -0
  70. data/lib/selenium/webdriver/edge/driver.rb +7 -29
  71. data/lib/selenium/webdriver/edge/features.rb +44 -0
  72. data/lib/selenium/webdriver/edge/options.rb +11 -48
  73. data/lib/selenium/webdriver/{common/w3c_manager.rb → edge/profile.rb} +7 -19
  74. data/lib/selenium/webdriver/edge/service.rb +10 -26
  75. data/lib/selenium/webdriver/edge.rb +11 -14
  76. data/lib/selenium/webdriver/firefox/driver.rb +31 -19
  77. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  78. data/lib/selenium/webdriver/firefox/features.rb +66 -0
  79. data/lib/selenium/webdriver/firefox/options.rb +70 -49
  80. data/lib/selenium/webdriver/firefox/profile.rb +21 -71
  81. data/lib/selenium/webdriver/firefox/service.rb +5 -9
  82. data/lib/selenium/webdriver/firefox.rb +22 -20
  83. data/lib/selenium/webdriver/ie/driver.rb +1 -47
  84. data/lib/selenium/webdriver/ie/options.rb +13 -44
  85. data/lib/selenium/webdriver/ie/service.rb +13 -15
  86. data/lib/selenium/webdriver/ie.rb +8 -7
  87. data/lib/selenium/webdriver/remote/bridge.rb +558 -86
  88. data/lib/selenium/webdriver/remote/capabilities.rb +159 -123
  89. data/lib/selenium/webdriver/remote/commands.rb +163 -0
  90. data/lib/selenium/webdriver/remote/driver.rb +22 -12
  91. data/lib/selenium/webdriver/remote/http/common.rb +0 -5
  92. data/lib/selenium/webdriver/remote/http/default.rb +17 -20
  93. data/lib/selenium/webdriver/remote/http/persistent.rb +11 -6
  94. data/lib/selenium/webdriver/remote/response.rb +16 -47
  95. data/lib/selenium/webdriver/remote.rb +15 -13
  96. data/lib/selenium/webdriver/safari/driver.rb +3 -31
  97. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +3 -3
  98. data/lib/selenium/webdriver/safari/options.rb +10 -29
  99. data/lib/selenium/webdriver/safari/service.rb +4 -8
  100. data/lib/selenium/webdriver/safari.rb +17 -9
  101. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  102. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +63 -0
  103. data/lib/selenium/webdriver/support/cdp_client_generator.rb +108 -0
  104. data/lib/selenium/webdriver/support/color.rb +2 -2
  105. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  106. data/lib/selenium/webdriver/support/guards/guard.rb +89 -0
  107. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +22 -19
  108. data/lib/selenium/webdriver/support/guards.rb +95 -0
  109. data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
  110. data/lib/selenium/webdriver/support/select.rb +3 -3
  111. data/lib/selenium/webdriver/support.rb +1 -0
  112. data/lib/selenium/webdriver/version.rb +1 -1
  113. data/lib/selenium/webdriver.rb +12 -12
  114. data/selenium-webdriver.gemspec +28 -12
  115. metadata +128 -69
  116. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  117. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  118. data/lib/selenium/webdriver/common/keyboard.rb +0 -70
  119. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  120. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  121. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  122. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  123. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  124. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  125. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  126. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  127. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  128. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  129. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  130. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  131. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  132. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  133. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  134. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  135. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  136. data/lib/selenium/webdriver/remote/w3c/commands.rb +0 -157
@@ -22,125 +22,546 @@ 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.split(//), 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_script('var source = document.documentElement.outerHTML;' \
150
+ 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
151
+ 'return source;')
152
+ end
153
+
154
+ #
155
+ # Create a new top-level browsing context
156
+ # https://w3c.github.io/webdriver/#new-window
157
+ # @param type [String] Supports two values: 'tab' and 'window'.
158
+ # Use 'tab' if you'd like the new window to share an OS-level window
159
+ # with the current browsing context.
160
+ # Use 'window' otherwise
161
+ # @return [Hash] Containing 'handle' with the value of the window handle
162
+ # and 'type' with the value of the created window type
163
+ #
164
+ def new_window(type)
165
+ execute :new_window, {}, {type: type}
166
+ end
167
+
168
+ def switch_to_window(name)
169
+ execute :switch_to_window, {}, {handle: name}
170
+ end
171
+
172
+ def switch_to_frame(id)
173
+ id = find_element_by('id', id) if id.is_a? String
174
+ execute :switch_to_frame, {}, {id: id}
175
+ end
176
+
177
+ def switch_to_parent_frame
178
+ execute :switch_to_parent_frame
179
+ end
180
+
181
+ def switch_to_default_content
182
+ switch_to_frame nil
183
+ end
184
+
185
+ QUIT_ERRORS = [IOError].freeze
186
+
187
+ def quit
188
+ execute :delete_session
189
+ http.close
190
+ rescue *QUIT_ERRORS
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
119
254
 
120
- if oss_status
121
- WebDriver.logger.info 'Detected OSS dialect.'
122
- @dialect = :oss
123
- Capabilities.json_create(value)
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
273
+
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)
373
+ ActionBuilder.new self,
374
+ Interactions.pointer(:mouse, name: 'mouse'),
375
+ Interactions.key('keyboard'),
376
+ async
377
+ end
378
+ alias_method :actions, :action
379
+
380
+ def mouse
381
+ raise Error::UnsupportedOperationError, '#mouse is no longer supported, use #action instead'
382
+ end
383
+
384
+ def keyboard
385
+ raise Error::UnsupportedOperationError, '#keyboard is no longer supported, use #action instead'
386
+ end
387
+
388
+ def send_actions(data)
389
+ execute :actions, {}, {actions: data}
390
+ end
391
+
392
+ def release_actions
393
+ execute :release_actions
394
+ end
395
+
396
+ def print_page(options = {})
397
+ execute :print_page, {}, {options: options}
398
+ end
399
+
400
+ def click_element(element)
401
+ execute :element_click, id: element
402
+ end
403
+
404
+ def send_keys_to_element(element, keys)
405
+ # TODO: rework file detectors before Selenium 4.0
406
+ if @file_detector
407
+ local_files = keys.first.split("\n").map { |key| @file_detector.call(Array(key)) }.compact
408
+ if local_files.any?
409
+ keys = local_files.map { |local_file| upload(local_file) }
410
+ keys = Array(keys.join("\n"))
411
+ end
412
+ end
413
+
414
+ # Keep .split(//) for backward compatibility for now
415
+ text = keys.join('')
416
+ execute :element_send_keys, {id: element}, {value: text.split(//), text: text}
417
+ end
418
+
419
+ def upload(local_file)
420
+ unless File.file?(local_file)
421
+ WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!")
422
+ raise Error::WebDriverError, "You are trying to work with something that isn't a file."
143
423
  end
424
+
425
+ execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
426
+ end
427
+
428
+ def clear_element(element)
429
+ execute :element_clear, id: element
430
+ end
431
+
432
+ def submit_element(element)
433
+ form = find_element_by('xpath', "./ancestor-or-self::form", [:element, element])
434
+ execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
435
+ "e.initEvent('submit', true, true);" \
436
+ 'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
437
+ end
438
+
439
+ #
440
+ # element properties
441
+ #
442
+
443
+ def element_tag_name(element)
444
+ execute :get_element_tag_name, id: element
445
+ end
446
+
447
+ def element_attribute(element, name)
448
+ WebDriver.logger.info "Using script for :getAttribute of #{name}"
449
+ execute_atom :getAttribute, element, name
450
+ end
451
+
452
+ def element_dom_attribute(element, name)
453
+ execute :get_element_attribute, id: element, name: name
454
+ end
455
+
456
+ def element_property(element, name)
457
+ execute :get_element_property, id: element, name: name
458
+ end
459
+
460
+ def element_aria_role(element)
461
+ execute :get_element_aria_role, id: element
462
+ end
463
+
464
+ def element_aria_label(element)
465
+ execute :get_element_aria_label, id: element
466
+ end
467
+
468
+ def element_value(element)
469
+ element_property element, 'value'
470
+ end
471
+
472
+ def element_text(element)
473
+ execute :get_element_text, id: element
474
+ end
475
+
476
+ def element_location(element)
477
+ data = execute :get_element_rect, id: element
478
+
479
+ Point.new data['x'], data['y']
480
+ end
481
+
482
+ def element_rect(element)
483
+ data = execute :get_element_rect, id: element
484
+
485
+ Rectangle.new data['x'], data['y'], data['width'], data['height']
486
+ end
487
+
488
+ def element_location_once_scrolled_into_view(element)
489
+ send_keys_to_element(element, [''])
490
+ element_location(element)
491
+ end
492
+
493
+ def element_size(element)
494
+ data = execute :get_element_rect, id: element
495
+
496
+ Dimension.new data['width'], data['height']
497
+ end
498
+
499
+ def element_enabled?(element)
500
+ execute :is_element_enabled, id: element
501
+ end
502
+
503
+ def element_selected?(element)
504
+ execute :is_element_selected, id: element
505
+ end
506
+
507
+ def element_displayed?(element)
508
+ WebDriver.logger.info 'Using script for :isDisplayed'
509
+ execute_atom :isDisplayed, element
510
+ end
511
+
512
+ def element_value_of_css_property(element, prop)
513
+ execute :get_element_css_value, id: element, property_name: prop
514
+ end
515
+
516
+ #
517
+ # finding elements
518
+ #
519
+
520
+ def active_element
521
+ Element.new self, element_id_from(execute(:get_active_element))
522
+ end
523
+
524
+ alias_method :switch_to_active_element, :active_element
525
+
526
+ def find_element_by(how, what, parent_ref = [])
527
+ how, what = convert_locator(how, what)
528
+
529
+ return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
530
+
531
+ parent_type, parent_id = parent_ref
532
+ id = case parent_type
533
+ when :element
534
+ execute :find_child_element, {id: parent_id}, {using: how, value: what.to_s}
535
+ when :shadow_root
536
+ execute :find_shadow_child_element, {id: parent_id}, {using: how, value: what.to_s}
537
+ else
538
+ execute :find_element, {}, {using: how, value: what.to_s}
539
+ end
540
+
541
+ Element.new self, element_id_from(id)
542
+ end
543
+
544
+ def find_elements_by(how, what, parent_ref = [])
545
+ how, what = convert_locator(how, what)
546
+
547
+ return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
548
+
549
+ parent_type, parent_id = parent_ref
550
+ ids = case parent_type
551
+ when :element
552
+ execute :find_child_elements, {id: parent_id}, {using: how, value: what.to_s}
553
+ when :shadow_root
554
+ execute :find_shadow_child_elements, {id: parent_id}, {using: how, value: what.to_s}
555
+ else
556
+ execute :find_elements, {}, {using: how, value: what.to_s}
557
+ end
558
+
559
+ ids.map { |id| Element.new self, element_id_from(id) }
560
+ end
561
+
562
+ def shadow_root(element)
563
+ id = execute :get_element_shadow_root, id: element
564
+ ShadowRoot.new self, shadow_root_id_from(id)
144
565
  end
145
566
 
146
567
  private
@@ -164,7 +585,7 @@ module Selenium
164
585
  end
165
586
 
166
587
  WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
167
- http.call(verb, path, command_hash)
588
+ http.call(verb, path, command_hash)['value']
168
589
  end
169
590
 
170
591
  def escaper
@@ -172,23 +593,74 @@ module Selenium
172
593
  end
173
594
 
174
595
  def commands(command)
175
- raise NotImplementedError unless command == :new_session
176
-
177
596
  COMMANDS[command]
178
597
  end
179
598
 
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
599
+ def unwrap_script_result(arg)
600
+ case arg
601
+ when Array
602
+ arg.map { |e| unwrap_script_result(e) }
603
+ when Hash
604
+ element_id = element_id_from(arg)
605
+ return Element.new(self, element_id) if element_id
183
606
 
184
- {
185
- desiredCapabilities: oss_capabilities,
186
- capabilities: {
187
- firstMatch: [w3c_capabilities]
188
- }
189
- }
607
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
608
+ else
609
+ arg
610
+ end
190
611
  end
191
612
 
613
+ def element_id_from(id)
614
+ id['ELEMENT'] || id[Element::ELEMENT_KEY]
615
+ end
616
+
617
+ def shadow_root_id_from(id)
618
+ id[ShadowRoot::ROOT_KEY]
619
+ end
620
+
621
+ def prepare_capabilities_payload(capabilities)
622
+ capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
623
+ {capabilities: capabilities}
624
+ end
625
+
626
+ def convert_locator(how, what)
627
+ how = SearchContext::FINDERS[how.to_sym] || how
628
+
629
+ case how
630
+ when 'class name'
631
+ how = 'css selector'
632
+ what = ".#{escape_css(what.to_s)}"
633
+ when 'id'
634
+ how = 'css selector'
635
+ what = "##{escape_css(what.to_s)}"
636
+ when 'name'
637
+ how = 'css selector'
638
+ what = "*[name='#{escape_css(what.to_s)}']"
639
+ when 'tag name'
640
+ how = 'css selector'
641
+ end
642
+
643
+ if what.is_a?(Hash)
644
+ what = what.each_with_object({}) do |(h, w), hash|
645
+ h, w = convert_locator(h.to_s, w)
646
+ hash[h] = w
647
+ end
648
+ end
649
+
650
+ [how, what]
651
+ end
652
+
653
+ ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/.freeze
654
+ UNICODE_CODE_POINT = 30
655
+
656
+ # Escapes invalid characters in CSS selector.
657
+ # @see https://mathiasbynens.be/notes/css-escapes
658
+ def escape_css(string)
659
+ string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
660
+ string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
661
+
662
+ string
663
+ end
192
664
  end # Bridge
193
665
  end # Remote
194
666
  end # WebDriver