selenium-webdriver 3.142.7 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
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