selenium-webdriver 3.142.7 → 4.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +685 -5
  3. data/Gemfile +6 -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 -90
  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 +77 -78
  14. data/lib/selenium/webdriver/atoms/mutationListener.js +55 -0
  15. data/lib/selenium/webdriver/atoms.rb +5 -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 +48 -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 +7 -29
  32. data/lib/selenium/webdriver/chrome.rb +5 -20
  33. data/lib/selenium/webdriver/chromium/driver.rb +60 -0
  34. data/lib/selenium/webdriver/{chrome/bridge.rb → chromium/features.rb} +48 -17
  35. data/lib/selenium/webdriver/chromium/options.rb +243 -0
  36. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  37. data/lib/selenium/webdriver/chromium.rb +29 -0
  38. data/lib/selenium/webdriver/common/action_builder.rb +126 -238
  39. data/lib/selenium/webdriver/common/child_process.rb +124 -0
  40. data/lib/selenium/webdriver/common/driver.rb +82 -43
  41. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  42. data/lib/selenium/webdriver/common/driver_extensions/{has_location.rb → full_page_screenshot.rb} +13 -11
  43. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  44. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +49 -0
  45. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +87 -0
  46. data/lib/selenium/webdriver/common/driver_extensions/{has_touch_screen.rb → has_bidi.rb} +9 -9
  47. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +86 -0
  48. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +36 -0
  49. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +42 -0
  50. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  51. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +41 -0
  52. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
  53. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +36 -0
  54. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +143 -0
  55. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_logs.rb} +4 -4
  56. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +16 -1
  57. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +69 -0
  58. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -13
  59. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +75 -0
  60. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  61. data/lib/selenium/webdriver/common/driver_finder.rb +45 -0
  62. data/lib/selenium/webdriver/common/element.rb +89 -29
  63. data/lib/selenium/webdriver/common/error.rb +53 -194
  64. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  65. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  66. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -22
  67. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  68. data/lib/selenium/webdriver/common/interactions/key_actions.rb +10 -6
  69. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  70. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  71. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  72. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +71 -82
  73. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  74. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  75. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  76. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  77. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  78. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  79. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  80. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  81. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  82. data/lib/selenium/webdriver/common/{w3c_manager.rb → interactions/wheel_input.rb} +14 -17
  83. data/lib/selenium/webdriver/common/keys.rb +1 -0
  84. data/lib/selenium/webdriver/common/local_driver.rb +46 -0
  85. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  86. data/lib/selenium/webdriver/common/logger.rb +119 -19
  87. data/lib/selenium/webdriver/common/manager.rb +11 -38
  88. data/lib/selenium/webdriver/common/options.rb +154 -23
  89. data/lib/selenium/webdriver/common/platform.rb +15 -52
  90. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  91. data/lib/selenium/webdriver/common/profile_helper.rb +11 -9
  92. data/lib/selenium/webdriver/common/proxy.rb +8 -5
  93. data/lib/selenium/webdriver/common/search_context.rb +7 -9
  94. data/lib/selenium/webdriver/common/selenium_manager.rb +140 -0
  95. data/lib/selenium/webdriver/common/service.rb +26 -143
  96. data/lib/selenium/webdriver/common/service_manager.rb +144 -0
  97. data/lib/selenium/webdriver/common/shadow_root.rb +86 -0
  98. data/lib/selenium/webdriver/common/socket_lock.rb +4 -4
  99. data/lib/selenium/webdriver/common/socket_poller.rb +4 -4
  100. data/lib/selenium/webdriver/common/takes_screenshot.rb +65 -0
  101. data/lib/selenium/webdriver/common/target_locator.rb +31 -4
  102. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  103. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  104. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  105. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  106. data/lib/selenium/webdriver/common/wait.rb +1 -1
  107. data/lib/selenium/webdriver/common/websocket_connection.rb +164 -0
  108. data/lib/selenium/webdriver/common/window.rb +6 -10
  109. data/lib/selenium/webdriver/common/zipper.rb +4 -10
  110. data/lib/selenium/webdriver/common.rb +43 -20
  111. data/lib/selenium/webdriver/devtools/console_event.rb +36 -0
  112. data/lib/selenium/webdriver/devtools/exception_event.rb +34 -0
  113. data/lib/selenium/webdriver/devtools/mutation_event.rb +35 -0
  114. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  115. data/lib/selenium/webdriver/devtools/pinned_script.rb +57 -0
  116. data/lib/selenium/webdriver/devtools/request.rb +65 -0
  117. data/lib/selenium/webdriver/devtools/response.rb +64 -0
  118. data/lib/selenium/webdriver/devtools.rb +96 -0
  119. data/lib/selenium/webdriver/edge/driver.rb +11 -27
  120. data/lib/selenium/webdriver/edge/features.rb +48 -0
  121. data/lib/selenium/webdriver/edge/options.rb +18 -43
  122. data/lib/selenium/webdriver/edge/profile.rb +33 -0
  123. data/lib/selenium/webdriver/edge/service.rb +8 -23
  124. data/lib/selenium/webdriver/edge.rb +11 -16
  125. data/lib/selenium/webdriver/firefox/driver.rb +38 -19
  126. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  127. data/lib/selenium/webdriver/firefox/features.rb +70 -0
  128. data/lib/selenium/webdriver/firefox/options.rb +73 -61
  129. data/lib/selenium/webdriver/firefox/profile.rb +20 -72
  130. data/lib/selenium/webdriver/firefox/service.rb +3 -25
  131. data/lib/selenium/webdriver/firefox/util.rb +1 -1
  132. data/lib/selenium/webdriver/firefox.rb +17 -28
  133. data/lib/selenium/webdriver/ie/driver.rb +5 -45
  134. data/lib/selenium/webdriver/ie/features.rb +34 -0
  135. data/lib/selenium/webdriver/ie/options.rb +14 -44
  136. data/lib/selenium/webdriver/ie/service.rb +3 -27
  137. data/lib/selenium/webdriver/ie.rb +4 -16
  138. data/lib/selenium/webdriver/remote/bridge/commands.rb +164 -0
  139. data/lib/selenium/webdriver/remote/bridge.rb +574 -88
  140. data/lib/selenium/webdriver/remote/capabilities.rb +144 -158
  141. data/lib/selenium/webdriver/remote/driver.rb +45 -14
  142. data/lib/selenium/webdriver/remote/features.rb +75 -0
  143. data/lib/selenium/webdriver/remote/http/common.rb +3 -8
  144. data/lib/selenium/webdriver/remote/http/curb.rb +1 -3
  145. data/lib/selenium/webdriver/remote/http/default.rb +24 -33
  146. data/lib/selenium/webdriver/remote/response.rb +17 -49
  147. data/lib/selenium/webdriver/remote/server_error.rb +1 -1
  148. data/lib/selenium/webdriver/remote.rb +15 -12
  149. data/lib/selenium/webdriver/safari/driver.rb +7 -29
  150. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +7 -5
  151. data/lib/selenium/webdriver/safari/options.rb +12 -27
  152. data/lib/selenium/webdriver/safari/service.rb +13 -11
  153. data/lib/selenium/webdriver/safari.rb +14 -20
  154. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  155. data/lib/selenium/webdriver/support/color.rb +24 -24
  156. data/lib/selenium/webdriver/support/event_firing_bridge.rb +5 -5
  157. data/lib/selenium/webdriver/support/guards/guard.rb +90 -0
  158. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +21 -20
  159. data/lib/selenium/webdriver/support/guards.rb +95 -0
  160. data/lib/selenium/webdriver/support/relative_locator.rb +50 -0
  161. data/lib/selenium/webdriver/support/select.rb +6 -4
  162. data/lib/selenium/webdriver/support.rb +1 -0
  163. data/lib/selenium/webdriver/version.rb +1 -1
  164. data/lib/selenium/webdriver.rb +19 -17
  165. data/selenium-webdriver.gemspec +36 -18
  166. metadata +161 -91
  167. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  168. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +0 -58
  169. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  170. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  171. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  172. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  173. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  174. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  175. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  176. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  177. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  178. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  179. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  180. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  181. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  182. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  183. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -60
  184. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  185. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  186. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  187. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  188. data/lib/selenium/webdriver/remote/w3c/commands.rb +0 -157
@@ -21,128 +21,562 @@ 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
75
99
  #
76
100
 
77
- def initialize(opts = {})
78
- opts = opts.dup
101
+ def timeouts
102
+ execute :get_timeouts, {}
103
+ end
79
104
 
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)
105
+ def timeouts=(timeouts)
106
+ execute :set_timeout, {}, timeouts
107
+ end
83
108
 
84
- unless opts.empty?
85
- raise ArgumentError, "unknown option#{'s' if opts.size != 1}: #{opts.inspect}"
86
- end
109
+ #
110
+ # alerts
111
+ #
87
112
 
88
- uri = url.is_a?(URI) ? url : URI.parse(url)
89
- uri.path += '/' unless uri.path =~ %r{\/$}
113
+ def accept_alert
114
+ execute :accept_alert
115
+ end
90
116
 
91
- http_client.server_url = uri
117
+ def dismiss_alert
118
+ execute :dismiss_alert
119
+ end
92
120
 
93
- @http = http_client
94
- @file_detector = nil
121
+ def alert=(keys)
122
+ execute :send_alert_text, {}, {value: keys.chars, text: keys}
123
+ end
124
+
125
+ def alert_text
126
+ execute :get_alert_text
95
127
  end
96
128
 
97
129
  #
98
- # Creates session handling both OSS and W3C dialects.
130
+ # navigation
99
131
  #
100
132
 
101
- def create_session(desired_capabilities, options = nil)
102
- response = execute(:new_session, {}, merged_capabilities(desired_capabilities, options))
133
+ def go_back
134
+ execute :back
135
+ end
103
136
 
104
- @session_id = response['sessionId']
105
- oss_status = response['status']
106
- value = response['value']
137
+ def go_forward
138
+ execute :forward
139
+ end
107
140
 
108
- if value.is_a?(Hash)
109
- @session_id = value['sessionId'] if value.key?('sessionId')
141
+ def url
142
+ execute :get_current_url
143
+ end
110
144
 
111
- if value.key?('capabilities')
112
- value = value['capabilities']
113
- elsif value.key?('value')
114
- value = value['value']
115
- end
145
+ def title
146
+ execute :get_title
147
+ end
148
+
149
+ def page_source
150
+ execute :get_page_source
151
+ end
152
+
153
+ #
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
162
+ #
163
+ def new_window(type)
164
+ execute :new_window, {}, {type: type}
165
+ end
166
+
167
+ def switch_to_window(name)
168
+ execute :switch_to_window, {}, {handle: name}
169
+ end
170
+
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
175
+
176
+ def switch_to_parent_frame
177
+ execute :switch_to_parent_frame
178
+ end
179
+
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
119
228
 
120
- if oss_status
121
- WebDriver.logger.info 'Detected OSS dialect.'
122
- @dialect = :oss
123
- Capabilities.json_create(value)
124
- else
125
- WebDriver.logger.info 'Detected W3C dialect.'
126
- @dialect = :w3c
127
- W3C::Capabilities.json_create(value)
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'
128
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
129
272
  end
130
273
 
131
274
  #
132
- # Returns the current session ID.
275
+ # HTML 5
133
276
  #
134
277
 
135
- def session_id
136
- @session_id || raise(Error::WebDriverError, 'no current session exists')
278
+ def local_storage_item(key, value = nil)
279
+ if value
280
+ execute_script("localStorage.setItem('#{key}', '#{value}')")
281
+ else
282
+ execute_script("return localStorage.getItem('#{key}')")
283
+ end
137
284
  end
138
285
 
139
- def browser
140
- @browser ||= begin
141
- name = @capabilities.browser_name
142
- name ? name.tr(' ', '_').to_sym : 'unknown'
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}')")
143
307
  end
144
308
  end
145
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
+
326
+ #
327
+ # javascript execution
328
+ #
329
+
330
+ def execute_script(script, *args)
331
+ result = execute :execute_script, {}, {script: script, args: args}
332
+ unwrap_script_result result
333
+ end
334
+
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
+ keys = upload_if_necessary(keys) if @file_detector
395
+ text = keys.join
396
+ execute :element_send_keys, {id: element}, {value: text.chars, text: text}
397
+ end
398
+
399
+ def clear_element(element)
400
+ execute :element_clear, id: element
401
+ end
402
+
403
+ def submit_element(element)
404
+ script = "/* submitForm */ var form = arguments[0];\n" \
405
+ "while (form.nodeName != \"FORM\" && form.parentNode) {\n " \
406
+ "form = form.parentNode;\n" \
407
+ "}\n" \
408
+ "if (!form) { throw Error('Unable to find containing form element'); }\n" \
409
+ "if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n" \
410
+ "var e = form.ownerDocument.createEvent('Event');\n" \
411
+ "e.initEvent('submit', true, true);\n" \
412
+ "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
413
+
414
+ execute_script(script, Element::ELEMENT_KEY => element)
415
+ rescue Error::JavascriptError
416
+ raise Error::UnsupportedOperationError, 'To submit an element, it must be nested inside a form element'
417
+ end
418
+
419
+ #
420
+ # element properties
421
+ #
422
+
423
+ def element_tag_name(element)
424
+ execute :get_element_tag_name, id: element
425
+ end
426
+
427
+ def element_attribute(element, name)
428
+ WebDriver.logger.debug "Using script for :getAttribute of #{name}", id: :script
429
+ execute_atom :getAttribute, element, name
430
+ end
431
+
432
+ def element_dom_attribute(element, name)
433
+ execute :get_element_attribute, id: element, name: name
434
+ end
435
+
436
+ def element_property(element, name)
437
+ execute :get_element_property, id: element, name: name
438
+ end
439
+
440
+ def element_aria_role(element)
441
+ execute :get_element_aria_role, id: element
442
+ end
443
+
444
+ def element_aria_label(element)
445
+ execute :get_element_aria_label, id: element
446
+ end
447
+
448
+ def element_value(element)
449
+ element_property element, 'value'
450
+ end
451
+
452
+ def element_text(element)
453
+ execute :get_element_text, id: element
454
+ end
455
+
456
+ def element_location(element)
457
+ data = execute :get_element_rect, id: element
458
+
459
+ Point.new data['x'], data['y']
460
+ end
461
+
462
+ def element_rect(element)
463
+ data = execute :get_element_rect, id: element
464
+
465
+ Rectangle.new data['x'], data['y'], data['width'], data['height']
466
+ end
467
+
468
+ def element_location_once_scrolled_into_view(element)
469
+ send_keys_to_element(element, [''])
470
+ element_location(element)
471
+ end
472
+
473
+ def element_size(element)
474
+ data = execute :get_element_rect, id: element
475
+
476
+ Dimension.new data['width'], data['height']
477
+ end
478
+
479
+ def element_enabled?(element)
480
+ execute :is_element_enabled, id: element
481
+ end
482
+
483
+ def element_selected?(element)
484
+ execute :is_element_selected, id: element
485
+ end
486
+
487
+ def element_displayed?(element)
488
+ WebDriver.logger.debug 'Using script for :isDisplayed', id: :script
489
+ execute_atom :isDisplayed, element
490
+ end
491
+
492
+ def element_value_of_css_property(element, prop)
493
+ execute :get_element_css_value, id: element, property_name: prop
494
+ end
495
+
496
+ #
497
+ # finding elements
498
+ #
499
+
500
+ def active_element
501
+ Element.new self, element_id_from(execute(:get_active_element))
502
+ end
503
+
504
+ alias switch_to_active_element active_element
505
+
506
+ def find_element_by(how, what, parent_ref = [])
507
+ how, what = convert_locator(how, what)
508
+
509
+ return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
510
+
511
+ parent_type, parent_id = parent_ref
512
+ id = case parent_type
513
+ when :element
514
+ execute :find_child_element, {id: parent_id}, {using: how, value: what.to_s}
515
+ when :shadow_root
516
+ execute :find_shadow_child_element, {id: parent_id}, {using: how, value: what.to_s}
517
+ else
518
+ execute :find_element, {}, {using: how, value: what.to_s}
519
+ end
520
+
521
+ Element.new self, element_id_from(id)
522
+ end
523
+
524
+ def find_elements_by(how, what, parent_ref = [])
525
+ how, what = convert_locator(how, what)
526
+
527
+ return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
528
+
529
+ parent_type, parent_id = parent_ref
530
+ ids = case parent_type
531
+ when :element
532
+ execute :find_child_elements, {id: parent_id}, {using: how, value: what.to_s}
533
+ when :shadow_root
534
+ execute :find_shadow_child_elements, {id: parent_id}, {using: how, value: what.to_s}
535
+ else
536
+ execute :find_elements, {}, {using: how, value: what.to_s}
537
+ end
538
+
539
+ ids.map { |id| Element.new self, element_id_from(id) }
540
+ end
541
+
542
+ def shadow_root(element)
543
+ id = execute :get_element_shadow_root, id: element
544
+ ShadowRoot.new self, shadow_root_id_from(id)
545
+ end
546
+
547
+ #
548
+ # virtual-authenticator
549
+ #
550
+
551
+ def add_virtual_authenticator(options)
552
+ authenticator_id = execute :add_virtual_authenticator, {}, options.as_json
553
+ VirtualAuthenticator.new(self, authenticator_id, options)
554
+ end
555
+
556
+ def remove_virtual_authenticator(id)
557
+ execute :remove_virtual_authenticator, {authenticatorId: id}
558
+ end
559
+
560
+ def add_credential(credential, id)
561
+ execute :add_credential, {authenticatorId: id}, credential
562
+ end
563
+
564
+ def credentials(authenticator_id)
565
+ execute :get_credentials, {authenticatorId: authenticator_id}
566
+ end
567
+
568
+ def remove_credential(credential_id, authenticator_id)
569
+ execute :remove_credential, {credentialId: credential_id, authenticatorId: authenticator_id}
570
+ end
571
+
572
+ def remove_all_credentials(authenticator_id)
573
+ execute :remove_all_credentials, {authenticatorId: authenticator_id}
574
+ end
575
+
576
+ def user_verified(verified, authenticator_id)
577
+ execute :set_user_verified, {authenticatorId: authenticator_id}, {isUserVerified: verified}
578
+ end
579
+
146
580
  private
147
581
 
148
582
  #
@@ -163,8 +597,8 @@ module Selenium
163
597
  raise ArgumentError, "#{opts.inspect} invalid for #{command.inspect}"
164
598
  end
165
599
 
166
- WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
167
- http.call(verb, path, command_hash)
600
+ WebDriver.logger.debug("-> #{verb.to_s.upcase} #{path}", id: :command)
601
+ http.call(verb, path, command_hash)['value']
168
602
  end
169
603
 
170
604
  def escaper
@@ -172,23 +606,75 @@ module Selenium
172
606
  end
173
607
 
174
608
  def commands(command)
175
- raise NotImplementedError unless command == :new_session
176
-
177
609
  COMMANDS[command]
178
610
  end
179
611
 
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
612
+ def unwrap_script_result(arg)
613
+ case arg
614
+ when Array
615
+ arg.map { |e| unwrap_script_result(e) }
616
+ when Hash
617
+ element_id = element_id_from(arg)
618
+ return Element.new(self, element_id) if element_id
619
+
620
+ shadow_root_id = shadow_root_id_from(arg)
621
+ return ShadowRoot.new self, shadow_root_id if shadow_root_id
622
+
623
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
624
+ else
625
+ arg
626
+ end
627
+ end
628
+
629
+ def element_id_from(id)
630
+ id['ELEMENT'] || id[Element::ELEMENT_KEY]
631
+ end
632
+
633
+ def shadow_root_id_from(id)
634
+ id[ShadowRoot::ROOT_KEY]
635
+ end
636
+
637
+ def prepare_capabilities_payload(capabilities)
638
+ capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
639
+ {capabilities: capabilities}
640
+ end
641
+
642
+ def convert_locator(how, what)
643
+ how = SearchContext::FINDERS[how.to_sym] || how
644
+
645
+ case how
646
+ when 'class name'
647
+ how = 'css selector'
648
+ what = ".#{escape_css(what.to_s)}"
649
+ when 'id'
650
+ how = 'css selector'
651
+ what = "##{escape_css(what.to_s)}"
652
+ when 'name'
653
+ how = 'css selector'
654
+ what = "*[name='#{escape_css(what.to_s)}']"
655
+ end
656
+
657
+ if what.is_a?(Hash)
658
+ what = what.each_with_object({}) do |(h, w), hash|
659
+ h, w = convert_locator(h.to_s, w)
660
+ hash[h] = w
661
+ end
662
+ end
183
663
 
184
- {
185
- desiredCapabilities: oss_capabilities,
186
- capabilities: {
187
- firstMatch: [w3c_capabilities]
188
- }
189
- }
664
+ [how, what]
190
665
  end
191
666
 
667
+ ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
668
+ UNICODE_CODE_POINT = 30
669
+
670
+ # Escapes invalid characters in CSS selector.
671
+ # @see https://mathiasbynens.be/notes/css-escapes
672
+ def escape_css(string)
673
+ string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
674
+ string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
675
+
676
+ string
677
+ end
192
678
  end # Bridge
193
679
  end # Remote
194
680
  end # WebDriver