selenium-webdriver 3.142.7 → 4.16.0

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