selenium-webdriver 3.142.7 → 4.0.0.alpha1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +21 -43
  3. data/lib/selenium/webdriver.rb +2 -4
  4. data/lib/selenium/webdriver/chrome/bridge.rb +3 -21
  5. data/lib/selenium/webdriver/chrome/driver.rb +12 -39
  6. data/lib/selenium/webdriver/chrome/options.rb +3 -7
  7. data/lib/selenium/webdriver/chrome/profile.rb +2 -2
  8. data/lib/selenium/webdriver/chrome/service.rb +4 -9
  9. data/lib/selenium/webdriver/common.rb +7 -16
  10. data/lib/selenium/webdriver/common/action_builder.rb +97 -249
  11. data/lib/selenium/webdriver/common/driver.rb +2 -4
  12. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +1 -1
  13. data/lib/selenium/webdriver/common/element.rb +3 -6
  14. data/lib/selenium/webdriver/common/error.rb +27 -203
  15. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -5
  16. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +13 -13
  17. data/lib/selenium/webdriver/common/manager.rb +1 -1
  18. data/lib/selenium/webdriver/common/options.rb +148 -24
  19. data/lib/selenium/webdriver/common/service.rb +16 -34
  20. data/lib/selenium/webdriver/common/socket_poller.rb +2 -2
  21. data/lib/selenium/webdriver/common/w3c_options.rb +45 -0
  22. data/lib/selenium/webdriver/edge.rb +0 -1
  23. data/lib/selenium/webdriver/edge/driver.rb +14 -10
  24. data/lib/selenium/webdriver/edge/service.rb +6 -7
  25. data/lib/selenium/webdriver/firefox.rb +2 -6
  26. data/lib/selenium/webdriver/firefox/binary.rb +3 -80
  27. data/lib/selenium/webdriver/firefox/bridge.rb +47 -0
  28. data/lib/selenium/webdriver/firefox/driver.rb +44 -22
  29. data/lib/selenium/webdriver/firefox/marionette/driver.rb +1 -1
  30. data/lib/selenium/webdriver/firefox/options.rb +2 -2
  31. data/lib/selenium/webdriver/firefox/profile.rb +25 -14
  32. data/lib/selenium/webdriver/firefox/service.rb +4 -4
  33. data/lib/selenium/webdriver/ie/driver.rb +5 -18
  34. data/lib/selenium/webdriver/ie/options.rb +2 -2
  35. data/lib/selenium/webdriver/ie/service.rb +4 -4
  36. data/lib/selenium/webdriver/remote.rb +2 -6
  37. data/lib/selenium/webdriver/remote/bridge.rb +515 -69
  38. data/lib/selenium/webdriver/remote/capabilities.rb +77 -99
  39. data/lib/selenium/webdriver/remote/commands.rb +156 -0
  40. data/lib/selenium/webdriver/remote/driver.rb +12 -5
  41. data/lib/selenium/webdriver/remote/http/default.rb +0 -9
  42. data/lib/selenium/webdriver/remote/response.rb +16 -47
  43. data/lib/selenium/webdriver/safari.rb +1 -1
  44. data/lib/selenium/webdriver/safari/bridge.rb +3 -3
  45. data/lib/selenium/webdriver/safari/driver.rb +4 -1
  46. data/lib/selenium/webdriver/safari/service.rb +4 -4
  47. data/lib/selenium/webdriver/support/select.rb +1 -1
  48. data/lib/selenium/webdriver/version.rb +1 -1
  49. data/selenium-webdriver.gemspec +3 -3
  50. metadata +14 -5
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Licensed to the Software Freedom Conservancy (SFC) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The SFC licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+
20
+ module Selenium
21
+ module WebDriver
22
+ module Firefox
23
+ module Bridge
24
+
25
+ COMMANDS = {
26
+ install_addon: [:post, 'session/:session_id/moz/addon/install'],
27
+ uninstall_addon: [:post, 'session/:session_id/moz/addon/uninstall']
28
+ }.freeze
29
+
30
+ def commands(command)
31
+ COMMANDS[command] || super
32
+ end
33
+
34
+ def install_addon(path, temporary)
35
+ payload = {path: path}
36
+ payload[:temporary] = temporary unless temporary.nil?
37
+ execute :install_addon, {}, payload
38
+ end
39
+
40
+ def uninstall_addon(id)
41
+ execute :uninstall_addon, {}, {id: id}
42
+ end
43
+
44
+ end # Bridge
45
+ end # Firefox
46
+ end # WebDriver
47
+ end # Selenium
@@ -20,30 +20,52 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Firefox
23
- module Driver
24
- class << self
25
-
26
- #
27
- # Instantiates correct Firefox driver implementation
28
- # @return [Marionette::Driver, Legacy::Driver]
29
- #
30
-
31
- def new(**opts)
32
- if marionette?(opts)
33
- Firefox::Marionette::Driver.new(opts)
34
- else
35
- Firefox::Legacy::Driver.new(opts)
36
- end
37
- end
38
-
39
- private
40
-
41
- def marionette?(opts)
42
- opts.delete(:marionette) != false &&
43
- (!opts[:desired_capabilities] || opts[:desired_capabilities][:marionette] != false)
44
- end
23
+
24
+ #
25
+ # Driver implementation for Firefox using GeckoDriver.
26
+ # @api private
27
+ #
28
+
29
+ class Driver < WebDriver::Driver
30
+ include DriverExtensions::HasAddons
31
+ include DriverExtensions::HasWebStorage
32
+ include DriverExtensions::TakesScreenshot
33
+
34
+ def initialize(opts = {})
35
+ opts[:desired_capabilities] = create_capabilities(opts)
36
+
37
+ opts[:url] ||= service_url(opts)
38
+
39
+ listener = opts.delete(:listener)
40
+ desired_capabilities = opts.delete(:desired_capabilities)
41
+
42
+ @bridge = Remote::Bridge.new(opts)
43
+ @bridge.extend Bridge
44
+ @bridge.create_session(desired_capabilities)
45
+
46
+ super(@bridge, listener: listener)
45
47
  end
46
48
 
49
+ def browser
50
+ :firefox
51
+ end
52
+
53
+ def quit
54
+ super
55
+ ensure
56
+ @service&.stop
57
+ end
58
+
59
+ private
60
+
61
+ def create_capabilities(opts)
62
+ caps = opts.delete(:desired_capabilities) { Remote::Capabilities.firefox }
63
+ options = opts.delete(:options) { Options.new }
64
+ options = options.as_json
65
+ caps.merge!(options) unless options.empty?
66
+
67
+ caps
68
+ end
47
69
  end # Driver
48
70
  end # Firefox
49
71
  end # WebDriver
@@ -42,7 +42,7 @@ module Selenium
42
42
  desired_capabilities = opts.delete(:desired_capabilities)
43
43
  bridge = Remote::Bridge.new(opts)
44
44
  capabilities = bridge.create_session(desired_capabilities)
45
- @bridge = Remote::W3C::Bridge.new(capabilities, bridge.session_id, **opts)
45
+ @bridge = Remote::W3C::Bridge.new(capabilities, bridge.session_id, opts)
46
46
  @bridge.extend Marionette::Bridge
47
47
 
48
48
  super(@bridge, listener: listener)
@@ -20,7 +20,7 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Firefox
23
- class Options < WebDriver::Common::Options
23
+ class Options
24
24
  attr_reader :args, :prefs, :options, :profile
25
25
  attr_accessor :binary, :log_level
26
26
 
@@ -139,7 +139,7 @@ module Selenium
139
139
  opts[:prefs] = @prefs unless @prefs.empty?
140
140
  opts[:log] = {level: @log_level} if @log_level
141
141
 
142
- {KEY => generate_as_json(opts)}
142
+ {KEY => opts}
143
143
  end
144
144
 
145
145
  private
@@ -73,20 +73,9 @@ module Selenium
73
73
  model_prefs = read_model_prefs
74
74
 
75
75
  if model_prefs.empty?
76
- @native_events = DEFAULT_ENABLE_NATIVE_EVENTS
77
- @secure_ssl = DEFAULT_SECURE_SSL
78
- @untrusted_issuer = DEFAULT_ASSUME_UNTRUSTED_ISSUER
79
- @load_no_focus_lib = DEFAULT_LOAD_NO_FOCUS_LIB
80
-
81
- @additional_prefs = {}
76
+ assign_default_preferences
82
77
  else
83
- # TODO: clean this up
84
- @native_events = model_prefs.delete(WEBDRIVER_PREFS[:native_events]) == 'true'
85
- @secure_ssl = model_prefs.delete(WEBDRIVER_PREFS[:untrusted_certs]) != 'true'
86
- @untrusted_issuer = model_prefs.delete(WEBDRIVER_PREFS[:untrusted_issuer]) == 'true'
87
- # not stored in profile atm, so will always be false.
88
- @load_no_focus_lib = model_prefs.delete(WEBDRIVER_PREFS[:load_no_focus_lib]) == 'true'
89
- @additional_prefs = model_prefs
78
+ assign_updated_preferences(model_prefs)
90
79
  end
91
80
 
92
81
  @extensions = {}
@@ -116,7 +105,7 @@ module Selenium
116
105
  raise TypeError, "expected one of #{VALID_PREFERENCE_TYPES.inspect}, got #{value.inspect}:#{value.class}"
117
106
  end
118
107
 
119
- if value.is_a?(String) && Util.stringified?(value)
108
+ if value.is_a?(String) && stringified?(value)
120
109
  raise ArgumentError, "preference values must be plain strings: #{key.inspect} => #{value.inspect}"
121
110
  end
122
111
 
@@ -195,6 +184,24 @@ module Selenium
195
184
 
196
185
  private
197
186
 
187
+ def assign_default_preferences
188
+ @native_events = DEFAULT_ENABLE_NATIVE_EVENTS
189
+ @secure_ssl = DEFAULT_SECURE_SSL
190
+ @untrusted_issuer = DEFAULT_ASSUME_UNTRUSTED_ISSUER
191
+ @load_no_focus_lib = DEFAULT_LOAD_NO_FOCUS_LIB
192
+
193
+ @additional_prefs = {}
194
+ end
195
+
196
+ def assign_updated_preferences(model_prefs)
197
+ @native_events = model_prefs.delete(WEBDRIVER_PREFS[:native_events]) == 'true'
198
+ @secure_ssl = model_prefs.delete(WEBDRIVER_PREFS[:untrusted_certs]) != 'true'
199
+ @untrusted_issuer = model_prefs.delete(WEBDRIVER_PREFS[:untrusted_issuer]) == 'true'
200
+ # not stored in profile atm, so will always be false.
201
+ @load_no_focus_lib = model_prefs.delete(WEBDRIVER_PREFS[:load_no_focus_lib]) == 'true'
202
+ @additional_prefs = model_prefs
203
+ end
204
+
198
205
  def set_manual_proxy_preference(key, value)
199
206
  return unless value
200
207
 
@@ -275,6 +282,10 @@ module Selenium
275
282
  end
276
283
  end
277
284
  end
285
+
286
+ def stringified?(str)
287
+ /^".*"$/.match?(str)
288
+ end
278
289
  end # Profile
279
290
  end # Firefox
280
291
  end # WebDriver
@@ -25,14 +25,14 @@ module Selenium
25
25
  #
26
26
 
27
27
  class Service < WebDriver::Service
28
- @default_port = 4444
29
- @executable = 'geckodriver'
30
- @missing_text = <<~ERROR
28
+ DEFAULT_PORT = 4444
29
+ EXECUTABLE = 'geckodriver'
30
+ MISSING_TEXT = <<~ERROR
31
31
  Unable to find Mozilla geckodriver. Please download the server from
32
32
  https://github.com/mozilla/geckodriver/releases and place it somewhere on your PATH.
33
33
  More info at https://developer.mozilla.org/en-US/docs/Mozilla/QA/Marionette/WebDriver.
34
34
  ERROR
35
- @shutdown_supported = false
35
+ SHUTDOWN_SUPPORTED = false
36
36
 
37
37
  private
38
38
 
@@ -37,7 +37,11 @@ module Selenium
37
37
  opts[:url] ||= service_url(opts)
38
38
 
39
39
  listener = opts.delete(:listener)
40
- @bridge = Remote::Bridge.handshake(**opts)
40
+ desired_capabilities = opts.delete(:desired_capabilities)
41
+
42
+ @bridge = Remote::Bridge.new(opts)
43
+ @bridge.create_session(desired_capabilities)
44
+
41
45
  super(@bridge, listener: listener)
42
46
  end
43
47
 
@@ -56,23 +60,6 @@ module Selenium
56
60
  def create_capabilities(opts)
57
61
  caps = opts.delete(:desired_capabilities) { Remote::Capabilities.internet_explorer }
58
62
  options = opts.delete(:options) { Options.new }
59
-
60
- if opts.delete(:introduce_flakiness_by_ignoring_security_domains)
61
- WebDriver.logger.deprecate ':introduce_flakiness_by_ignoring_security_domains',
62
- 'Selenium::WebDriver::IE::Options#ignore_protected_mode_settings='
63
- options.ignore_protected_mode_settings = true
64
- end
65
-
66
- native_events = opts.delete(:native_events)
67
- unless native_events.nil?
68
- WebDriver.logger.deprecate ':native_events', 'Selenium::WebDriver::IE::Options#native_events='
69
- options.native_events = native_events
70
- end
71
-
72
- # Backward compatibility with older IEDriverServer versions
73
- caps[:ignore_protected_mode_settings] = options.ignore_protected_mode_settings
74
- caps[:native_events] = options.native_events
75
-
76
63
  options = options.as_json
77
64
  caps.merge!(options) unless options.empty?
78
65
 
@@ -20,7 +20,7 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module IE
23
- class Options < WebDriver::Common::Options
23
+ class Options
24
24
  KEY = 'se:ieOptions'
25
25
  SCROLL_TOP = 0
26
26
  SCROLL_BOTTOM = 1
@@ -130,7 +130,7 @@ module Selenium
130
130
  opts['ie.browserCommandLineSwitches'] = @args.to_a.join(' ') if @args.any?
131
131
  opts.merge!(@options)
132
132
 
133
- {KEY => generate_as_json(opts)}
133
+ {KEY => opts}
134
134
  end
135
135
  end # Options
136
136
  end # IE
@@ -25,14 +25,14 @@ module Selenium
25
25
  #
26
26
 
27
27
  class Service < WebDriver::Service
28
- @default_port = 5555
29
- @executable = 'IEDriverServer'
30
- @missing_text = <<~ERROR
28
+ DEFAULT_PORT = 5555
29
+ EXECUTABLE = 'IEDriverServer'
30
+ MISSING_TEXT = <<~ERROR
31
31
  Unable to find IEDriverServer. Please download the server from
32
32
  http://selenium-release.storage.googleapis.com/index.html and place it somewhere on your PATH.
33
33
  More info at https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver.
34
34
  ERROR
35
- @shutdown_supported = true
35
+ SHUTDOWN_SUPPORTED = true
36
36
 
37
37
  private
38
38
 
@@ -26,10 +26,6 @@ require 'selenium/webdriver/remote/server_error'
26
26
  require 'selenium/webdriver/remote/http/common'
27
27
  require 'selenium/webdriver/remote/http/default'
28
28
 
29
+ require 'selenium/webdriver/remote/bridge'
29
30
  require 'selenium/webdriver/remote/capabilities'
30
- require 'selenium/webdriver/remote/oss/bridge'
31
- require 'selenium/webdriver/remote/oss/commands'
32
-
33
- require 'selenium/webdriver/remote/w3c/bridge'
34
- require 'selenium/webdriver/remote/w3c/capabilities'
35
- require 'selenium/webdriver/remote/w3c/commands'
31
+ require 'selenium/webdriver/remote/commands'
@@ -22,48 +22,11 @@ module Selenium
22
22
  module Remote
23
23
  class Bridge
24
24
  include Atoms
25
- include BridgeHelper
26
25
 
27
26
  PORT = 4444
28
- COMMANDS = {
29
- new_session: [:post, 'session']
30
- }.freeze
31
27
 
32
28
  attr_accessor :context, :http, :file_detector
33
- attr_reader :capabilities, :dialect
34
-
35
- #
36
- # Implements protocol handshake which:
37
- #
38
- # 1. Creates session with driver.
39
- # 2. Sniffs response.
40
- # 3. Based on the response, understands which dialect we should use.
41
- #
42
- # @return [OSS:Bridge, W3C::Bridge]
43
- #
44
- def self.handshake(**opts)
45
- desired_capabilities = opts.delete(:desired_capabilities) { Capabilities.new }
46
-
47
- if desired_capabilities.is_a?(Symbol)
48
- unless Capabilities.respond_to?(desired_capabilities)
49
- raise Error::WebDriverError, "invalid desired capability: #{desired_capabilities.inspect}"
50
- end
51
-
52
- desired_capabilities = Capabilities.__send__(desired_capabilities)
53
- end
54
-
55
- bridge = new(opts)
56
- capabilities = bridge.create_session(desired_capabilities, opts.delete(:options))
57
-
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'
65
- end
66
- end
29
+ attr_reader :capabilities
67
30
 
68
31
  #
69
32
  # Initializes the bridge with the given server URL
@@ -86,7 +49,7 @@ module Selenium
86
49
  end
87
50
 
88
51
  uri = url.is_a?(URI) ? url : URI.parse(url)
89
- uri.path += '/' unless uri.path =~ %r{\/$}
52
+ uri.path += '/' unless %r{\/$}.match?(uri.path)
90
53
 
91
54
  http_client.server_url = uri
92
55
 
@@ -95,37 +58,18 @@ module Selenium
95
58
  end
96
59
 
97
60
  #
98
- # Creates session handling both OSS and W3C dialects.
61
+ # Creates session.
99
62
  #
100
63
 
101
64
  def create_session(desired_capabilities, options = nil)
102
65
  response = execute(:new_session, {}, merged_capabilities(desired_capabilities, options))
103
66
 
104
67
  @session_id = response['sessionId']
105
- oss_status = response['status']
106
- value = response['value']
107
-
108
- if value.is_a?(Hash)
109
- @session_id = value['sessionId'] if value.key?('sessionId')
110
-
111
- if value.key?('capabilities')
112
- value = value['capabilities']
113
- elsif value.key?('value')
114
- value = value['value']
115
- end
116
- end
68
+ capabilities = response['capabilities']
117
69
 
118
70
  raise Error::WebDriverError, 'no sessionId in returned payload' unless @session_id
119
71
 
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)
128
- end
72
+ @capabilities = Capabilities.json_create(capabilities)
129
73
  end
130
74
 
131
75
  #
@@ -143,6 +87,466 @@ module Selenium
143
87
  end
144
88
  end
145
89
 
90
+ def status
91
+ execute :status
92
+ end
93
+
94
+ def get(url)
95
+ execute :get, {}, {url: url}
96
+ end
97
+
98
+ def implicit_wait_timeout=(milliseconds)
99
+ timeout('implicit', milliseconds)
100
+ end
101
+
102
+ def script_timeout=(milliseconds)
103
+ timeout('script', milliseconds)
104
+ end
105
+
106
+ def timeout(type, milliseconds)
107
+ type = 'pageLoad' if type == 'page load'
108
+ execute :set_timeout, {}, {type => milliseconds}
109
+ end
110
+
111
+ #
112
+ # alerts
113
+ #
114
+
115
+ def accept_alert
116
+ execute :accept_alert
117
+ end
118
+
119
+ def dismiss_alert
120
+ execute :dismiss_alert
121
+ end
122
+
123
+ def alert=(keys)
124
+ execute :send_alert_text, {}, {value: keys.split(//), text: keys}
125
+ end
126
+
127
+ def alert_text
128
+ execute :get_alert_text
129
+ end
130
+
131
+ #
132
+ # navigation
133
+ #
134
+
135
+ def go_back
136
+ execute :back
137
+ end
138
+
139
+ def go_forward
140
+ execute :forward
141
+ end
142
+
143
+ def url
144
+ execute :get_current_url
145
+ end
146
+
147
+ def title
148
+ execute :get_title
149
+ end
150
+
151
+ def page_source
152
+ execute_script('var source = document.documentElement.outerHTML;' \
153
+ 'if (!source) { source = new XMLSerializer().serializeToString(document); }' \
154
+ 'return source;')
155
+ end
156
+
157
+ #
158
+ # Create a new top-level browsing context
159
+ # https://w3c.github.io/webdriver/#new-window
160
+ # @param type [String] Supports two values: 'tab' and 'window'.
161
+ # Use 'tab' if you'd like the new window to share an OS-level window
162
+ # with the current browsing context.
163
+ # Use 'window' otherwise
164
+ # @return [Hash] Containing 'handle' with the value of the window handle
165
+ # and 'type' with the value of the created window type
166
+ #
167
+ def new_window(type)
168
+ execute :new_window, {}, {type: type}
169
+ end
170
+
171
+ def switch_to_window(name)
172
+ execute :switch_to_window, {}, {handle: name}
173
+ end
174
+
175
+ def switch_to_frame(id)
176
+ id = find_element_by('id', id) if id.is_a? String
177
+ execute :switch_to_frame, {}, {id: id}
178
+ end
179
+
180
+ def switch_to_parent_frame
181
+ execute :switch_to_parent_frame
182
+ end
183
+
184
+ def switch_to_default_content
185
+ switch_to_frame nil
186
+ end
187
+
188
+ QUIT_ERRORS = [IOError].freeze
189
+
190
+ def quit
191
+ execute :delete_session
192
+ http.close
193
+ rescue *QUIT_ERRORS
194
+ end
195
+
196
+ def close
197
+ execute :close_window
198
+ end
199
+
200
+ def refresh
201
+ execute :refresh
202
+ end
203
+
204
+ #
205
+ # window handling
206
+ #
207
+
208
+ def window_handles
209
+ execute :get_window_handles
210
+ end
211
+
212
+ def window_handle
213
+ execute :get_window_handle
214
+ end
215
+
216
+ def resize_window(width, height, handle = :current)
217
+ raise Error::WebDriverError, 'Switch to desired window before changing its size' unless handle == :current
218
+
219
+ set_window_rect(width: width, height: height)
220
+ end
221
+
222
+ def window_size(handle = :current)
223
+ raise Error::UnsupportedOperationError, 'Switch to desired window before getting its size' unless handle == :current
224
+
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
+ raise Error::UnsupportedOperationError, 'Switch to desired window before changing its size' unless handle == :current
235
+
236
+ execute :maximize_window
237
+ end
238
+
239
+ def full_screen_window
240
+ execute :fullscreen_window
241
+ end
242
+
243
+ def reposition_window(x, y)
244
+ set_window_rect(x: x, y: y)
245
+ end
246
+
247
+ def window_position
248
+ data = execute :get_window_rect
249
+ Point.new data['x'], data['y']
250
+ end
251
+
252
+ def set_window_rect(x: nil, y: nil, width: nil, height: nil)
253
+ params = {x: x, y: y, width: width, height: height}
254
+ params.update(params) { |_k, v| Integer(v) unless v.nil? }
255
+ execute :set_window_rect, {}, params
256
+ end
257
+
258
+ def window_rect
259
+ data = execute :get_window_rect
260
+ Rectangle.new data['x'], data['y'], data['width'], data['height']
261
+ end
262
+
263
+ def screenshot
264
+ execute :take_screenshot
265
+ end
266
+
267
+ #
268
+ # HTML 5
269
+ #
270
+
271
+ def local_storage_item(key, value = nil)
272
+ if value
273
+ execute_script("localStorage.setItem('#{key}', '#{value}')")
274
+ else
275
+ execute_script("return localStorage.getItem('#{key}')")
276
+ end
277
+ end
278
+
279
+ def remove_local_storage_item(key)
280
+ execute_script("localStorage.removeItem('#{key}')")
281
+ end
282
+
283
+ def local_storage_keys
284
+ execute_script('return Object.keys(localStorage)')
285
+ end
286
+
287
+ def clear_local_storage
288
+ execute_script('localStorage.clear()')
289
+ end
290
+
291
+ def local_storage_size
292
+ execute_script('return localStorage.length')
293
+ end
294
+
295
+ def session_storage_item(key, value = nil)
296
+ if value
297
+ execute_script("sessionStorage.setItem('#{key}', '#{value}')")
298
+ else
299
+ execute_script("return sessionStorage.getItem('#{key}')")
300
+ end
301
+ end
302
+
303
+ def remove_session_storage_item(key)
304
+ execute_script("sessionStorage.removeItem('#{key}')")
305
+ end
306
+
307
+ def session_storage_keys
308
+ execute_script('return Object.keys(sessionStorage)')
309
+ end
310
+
311
+ def clear_session_storage
312
+ execute_script('sessionStorage.clear()')
313
+ end
314
+
315
+ def session_storage_size
316
+ execute_script('return sessionStorage.length')
317
+ end
318
+
319
+ def location
320
+ raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting location'
321
+ end
322
+
323
+ def set_location(_lat, _lon, _alt)
324
+ raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting location'
325
+ end
326
+
327
+ def network_connection
328
+ raise Error::UnsupportedOperationError, 'The W3C standard does not currently support getting network connection'
329
+ end
330
+
331
+ def network_connection=(_type)
332
+ raise Error::UnsupportedOperationError, 'The W3C standard does not currently support setting network connection'
333
+ end
334
+
335
+ #
336
+ # javascript execution
337
+ #
338
+
339
+ def execute_script(script, *args)
340
+ result = execute :execute_script, {}, {script: script, args: args}
341
+ unwrap_script_result result
342
+ end
343
+
344
+ def execute_async_script(script, *args)
345
+ result = execute :execute_async_script, {}, {script: script, args: args}
346
+ unwrap_script_result result
347
+ end
348
+
349
+ #
350
+ # cookies
351
+ #
352
+
353
+ def manage
354
+ @manage ||= WebDriver::Manager.new(self)
355
+ end
356
+
357
+ def add_cookie(cookie)
358
+ execute :add_cookie, {}, {cookie: cookie}
359
+ end
360
+
361
+ def delete_cookie(name)
362
+ execute :delete_cookie, name: name
363
+ end
364
+
365
+ def cookie(name)
366
+ execute :get_cookie, name: name
367
+ end
368
+
369
+ def cookies
370
+ execute :get_all_cookies
371
+ end
372
+
373
+ def delete_all_cookies
374
+ execute :delete_all_cookies
375
+ end
376
+
377
+ #
378
+ # actions
379
+ #
380
+
381
+ def action(async = false)
382
+ ActionBuilder.new self,
383
+ Interactions.pointer(:mouse, name: 'mouse'),
384
+ Interactions.key('keyboard'),
385
+ async
386
+ end
387
+ alias_method :actions, :action
388
+
389
+ def mouse
390
+ raise Error::UnsupportedOperationError, '#mouse is no longer supported, use #action instead'
391
+ end
392
+
393
+ def keyboard
394
+ raise Error::UnsupportedOperationError, '#keyboard is no longer supported, use #action instead'
395
+ end
396
+
397
+ def send_actions(data)
398
+ execute :actions, {}, {actions: data}
399
+ end
400
+
401
+ def release_actions
402
+ execute :release_actions
403
+ end
404
+
405
+ def click_element(element)
406
+ execute :element_click, id: element
407
+ end
408
+
409
+ def send_keys_to_element(element, keys)
410
+ # TODO: rework file detectors before Selenium 4.0
411
+ if @file_detector
412
+ local_files = keys.first.split("\n").map { |key| @file_detector.call(Array(key)) }.compact
413
+ if local_files.any?
414
+ keys = local_files.map { |local_file| upload(local_file) }
415
+ keys = Array(keys.join("\n"))
416
+ end
417
+ end
418
+
419
+ # Keep .split(//) for backward compatibility for now
420
+ text = keys.join('')
421
+ execute :element_send_keys, {id: element}, {value: text.split(//), text: text}
422
+ end
423
+
424
+ def upload(local_file)
425
+ unless File.file?(local_file)
426
+ WebDriver.logger.debug("File detector only works with files. #{local_file.inspect} isn`t a file!")
427
+ raise Error::WebDriverError, "You are trying to work with something that isn't a file."
428
+ end
429
+
430
+ execute :upload_file, {}, {file: Zipper.zip_file(local_file)}
431
+ end
432
+
433
+ def clear_element(element)
434
+ execute :element_clear, id: element
435
+ end
436
+
437
+ def submit_element(element)
438
+ form = find_element_by('xpath', "./ancestor-or-self::form", element)
439
+ execute_script("var e = arguments[0].ownerDocument.createEvent('Event');" \
440
+ "e.initEvent('submit', true, true);" \
441
+ 'if (arguments[0].dispatchEvent(e)) { arguments[0].submit() }', form.as_json)
442
+ end
443
+
444
+ def screen_orientation=(orientation)
445
+ execute :set_screen_orientation, {}, {orientation: orientation}
446
+ end
447
+
448
+ def screen_orientation
449
+ execute :get_screen_orientation
450
+ end
451
+
452
+ #
453
+ # element properties
454
+ #
455
+
456
+ def element_tag_name(element)
457
+ execute :get_element_tag_name, id: element
458
+ end
459
+
460
+ def element_attribute(element, name)
461
+ WebDriver.logger.info "Using script for :getAttribute of #{name}"
462
+ execute_atom :getAttribute, element, name
463
+ end
464
+
465
+ def element_property(element, name)
466
+ execute :get_element_property, id: element.ref, name: name
467
+ end
468
+
469
+ def element_value(element)
470
+ element_property element, 'value'
471
+ end
472
+
473
+ def element_text(element)
474
+ execute :get_element_text, id: element
475
+ end
476
+
477
+ def element_location(element)
478
+ data = execute :get_element_rect, id: element
479
+
480
+ Point.new data['x'], data['y']
481
+ end
482
+
483
+ def element_rect(element)
484
+ data = execute :get_element_rect, id: element
485
+
486
+ Rectangle.new data['x'], data['y'], data['width'], data['height']
487
+ end
488
+
489
+ def element_location_once_scrolled_into_view(element)
490
+ send_keys_to_element(element, [''])
491
+ element_location(element)
492
+ end
493
+
494
+ def element_size(element)
495
+ data = execute :get_element_rect, id: element
496
+
497
+ Dimension.new data['width'], data['height']
498
+ end
499
+
500
+ def element_enabled?(element)
501
+ execute :is_element_enabled, id: element
502
+ end
503
+
504
+ def element_selected?(element)
505
+ execute :is_element_selected, id: element
506
+ end
507
+
508
+ def element_displayed?(element)
509
+ WebDriver.logger.info 'Using script for :isDisplayed'
510
+ execute_atom :isDisplayed, element
511
+ end
512
+
513
+ def element_value_of_css_property(element, prop)
514
+ execute :get_element_css_value, id: element, property_name: prop
515
+ end
516
+
517
+ #
518
+ # finding elements
519
+ #
520
+
521
+ def active_element
522
+ Element.new self, element_id_from(execute(:get_active_element))
523
+ end
524
+
525
+ alias_method :switch_to_active_element, :active_element
526
+
527
+ def find_element_by(how, what, parent = nil)
528
+ how, what = convert_locators(how, what)
529
+
530
+ id = if parent
531
+ execute :find_child_element, {id: parent}, {using: how, value: what}
532
+ else
533
+ execute :find_element, {}, {using: how, value: what}
534
+ end
535
+ Element.new self, element_id_from(id)
536
+ end
537
+
538
+ def find_elements_by(how, what, parent = nil)
539
+ how, what = convert_locators(how, what)
540
+
541
+ ids = if parent
542
+ execute :find_child_elements, {id: parent}, {using: how, value: what}
543
+ else
544
+ execute :find_elements, {}, {using: how, value: what}
545
+ end
546
+
547
+ ids.map { |id| Element.new self, element_id_from(id) }
548
+ end
549
+
146
550
  private
147
551
 
148
552
  #
@@ -164,7 +568,7 @@ module Selenium
164
568
  end
165
569
 
166
570
  WebDriver.logger.info("-> #{verb.to_s.upcase} #{path}")
167
- http.call(verb, path, command_hash)
571
+ http.call(verb, path, command_hash)['value']
168
572
  end
169
573
 
170
574
  def escaper
@@ -172,23 +576,65 @@ module Selenium
172
576
  end
173
577
 
174
578
  def commands(command)
175
- raise NotImplementedError unless command == :new_session
176
-
177
579
  COMMANDS[command]
178
580
  end
179
581
 
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
582
+ def merged_capabilities(capabilities, options = nil)
583
+ capabilities.merge!(options.as_json) if options
183
584
 
184
585
  {
185
- desiredCapabilities: oss_capabilities,
186
586
  capabilities: {
187
- firstMatch: [w3c_capabilities]
587
+ firstMatch: [capabilities]
188
588
  }
189
589
  }
190
590
  end
191
591
 
592
+ def unwrap_script_result(arg)
593
+ case arg
594
+ when Array
595
+ arg.map { |e| unwrap_script_result(e) }
596
+ when Hash
597
+ element_id = element_id_from(arg)
598
+ return Element.new(self, element_id) if element_id
599
+
600
+ arg.each { |k, v| arg[k] = unwrap_script_result(v) }
601
+ else
602
+ arg
603
+ end
604
+ end
605
+
606
+ def element_id_from(id)
607
+ id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf']
608
+ end
609
+
610
+ def convert_locators(how, what)
611
+ case how
612
+ when 'class name'
613
+ how = 'css selector'
614
+ what = ".#{escape_css(what)}"
615
+ when 'id'
616
+ how = 'css selector'
617
+ what = "##{escape_css(what)}"
618
+ when 'name'
619
+ how = 'css selector'
620
+ what = "*[name='#{escape_css(what)}']"
621
+ when 'tag name'
622
+ how = 'css selector'
623
+ end
624
+ [how, what]
625
+ end
626
+
627
+ ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]\(\)])/.freeze
628
+ UNICODE_CODE_POINT = 30
629
+
630
+ # Escapes invalid characters in CSS selector.
631
+ # @see https://mathiasbynens.be/notes/css-escapes
632
+ def escape_css(string)
633
+ string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
634
+ string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..-1]}" if !string.empty? && string[0].match?(/[[:digit:]]/)
635
+
636
+ string
637
+ end
192
638
  end # Bridge
193
639
  end # Remote
194
640
  end # WebDriver