selenium-webdriver 3.142.7 → 4.9.0

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