selenium-webdriver 3.142.7 → 4.0.3

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 (137) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +350 -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 +69 -63
  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 +154 -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 +11 -9
  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/zipper.rb +1 -9
  63. data/lib/selenium/webdriver/common.rb +23 -17
  64. data/lib/selenium/webdriver/devtools/console_event.rb +38 -0
  65. data/lib/selenium/webdriver/devtools/exception_event.rb +36 -0
  66. data/lib/selenium/webdriver/devtools/mutation_event.rb +37 -0
  67. data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
  68. data/lib/selenium/webdriver/devtools/request.rb +67 -0
  69. data/lib/selenium/webdriver/devtools/response.rb +66 -0
  70. data/lib/selenium/webdriver/devtools.rb +182 -0
  71. data/lib/selenium/webdriver/edge/driver.rb +7 -29
  72. data/lib/selenium/webdriver/edge/features.rb +44 -0
  73. data/lib/selenium/webdriver/edge/options.rb +11 -48
  74. data/lib/selenium/webdriver/{common/w3c_manager.rb → edge/profile.rb} +7 -19
  75. data/lib/selenium/webdriver/edge/service.rb +10 -26
  76. data/lib/selenium/webdriver/edge.rb +11 -14
  77. data/lib/selenium/webdriver/firefox/driver.rb +31 -19
  78. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  79. data/lib/selenium/webdriver/firefox/features.rb +66 -0
  80. data/lib/selenium/webdriver/firefox/options.rb +70 -49
  81. data/lib/selenium/webdriver/firefox/profile.rb +21 -71
  82. data/lib/selenium/webdriver/firefox/service.rb +5 -9
  83. data/lib/selenium/webdriver/firefox.rb +22 -20
  84. data/lib/selenium/webdriver/ie/driver.rb +1 -47
  85. data/lib/selenium/webdriver/ie/options.rb +13 -44
  86. data/lib/selenium/webdriver/ie/service.rb +13 -15
  87. data/lib/selenium/webdriver/ie.rb +8 -7
  88. data/lib/selenium/webdriver/remote/bridge.rb +558 -86
  89. data/lib/selenium/webdriver/remote/capabilities.rb +159 -123
  90. data/lib/selenium/webdriver/remote/commands.rb +163 -0
  91. data/lib/selenium/webdriver/remote/driver.rb +22 -12
  92. data/lib/selenium/webdriver/remote/http/common.rb +0 -5
  93. data/lib/selenium/webdriver/remote/http/default.rb +17 -20
  94. data/lib/selenium/webdriver/remote/http/persistent.rb +11 -6
  95. data/lib/selenium/webdriver/remote/response.rb +16 -47
  96. data/lib/selenium/webdriver/remote.rb +15 -13
  97. data/lib/selenium/webdriver/safari/driver.rb +3 -31
  98. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +3 -3
  99. data/lib/selenium/webdriver/safari/options.rb +10 -29
  100. data/lib/selenium/webdriver/safari/service.rb +4 -8
  101. data/lib/selenium/webdriver/safari.rb +17 -9
  102. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  103. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +63 -0
  104. data/lib/selenium/webdriver/support/cdp_client_generator.rb +108 -0
  105. data/lib/selenium/webdriver/support/color.rb +2 -2
  106. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  107. data/lib/selenium/webdriver/support/guards/guard.rb +89 -0
  108. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +22 -19
  109. data/lib/selenium/webdriver/support/guards.rb +95 -0
  110. data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
  111. data/lib/selenium/webdriver/support/select.rb +3 -3
  112. data/lib/selenium/webdriver/support.rb +1 -0
  113. data/lib/selenium/webdriver/version.rb +1 -1
  114. data/lib/selenium/webdriver.rb +12 -12
  115. data/selenium-webdriver.gemspec +28 -12
  116. metadata +128 -69
  117. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  118. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  119. data/lib/selenium/webdriver/common/keyboard.rb +0 -70
  120. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  121. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  122. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  123. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  124. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  125. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  126. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  127. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  128. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  129. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  130. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  131. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  132. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  133. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  134. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  135. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  136. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  137. 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