selenium-webdriver 4.0.0.alpha5 → 4.0.0.beta3

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 (87) hide show
  1. checksums.yaml +4 -4
  2. data/lib/selenium/devtools.rb +30 -0
  3. data/lib/selenium/server.rb +18 -26
  4. data/lib/selenium/webdriver.rb +1 -3
  5. data/lib/selenium/webdriver/atoms/findElements.js +93 -93
  6. data/lib/selenium/webdriver/atoms/getAttribute.js +75 -59
  7. data/lib/selenium/webdriver/atoms/isDisplayed.js +72 -72
  8. data/lib/selenium/webdriver/atoms/mutationListener.js +55 -0
  9. data/lib/selenium/webdriver/chrome.rb +1 -1
  10. data/lib/selenium/webdriver/chrome/driver.rb +28 -6
  11. data/lib/selenium/webdriver/chrome/{bridge.rb → features.rb} +6 -8
  12. data/lib/selenium/webdriver/chrome/options.rb +54 -37
  13. data/lib/selenium/webdriver/chrome/profile.rb +6 -3
  14. data/lib/selenium/webdriver/chrome/service.rb +4 -2
  15. data/lib/selenium/webdriver/common.rb +7 -2
  16. data/lib/selenium/webdriver/common/driver.rb +86 -26
  17. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +89 -0
  18. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +6 -1
  19. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +5 -8
  20. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +149 -0
  21. data/lib/selenium/webdriver/{edge_chrome/bridge.rb → common/driver_extensions/has_logs.rb} +7 -7
  22. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +6 -27
  23. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +67 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +1 -0
  25. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  26. data/lib/selenium/webdriver/common/element.rb +66 -12
  27. data/lib/selenium/webdriver/common/interactions/interaction.rb +4 -1
  28. data/lib/selenium/webdriver/common/logger.rb +6 -3
  29. data/lib/selenium/webdriver/common/manager.rb +11 -1
  30. data/lib/selenium/webdriver/common/options.rb +90 -11
  31. data/lib/selenium/webdriver/common/platform.rb +3 -1
  32. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  33. data/lib/selenium/webdriver/common/proxy.rb +4 -1
  34. data/lib/selenium/webdriver/common/search_context.rb +4 -1
  35. data/lib/selenium/webdriver/common/service.rb +13 -114
  36. data/lib/selenium/webdriver/common/service_manager.rb +151 -0
  37. data/lib/selenium/webdriver/common/socket_poller.rb +19 -30
  38. data/lib/selenium/webdriver/common/takes_screenshot.rb +63 -0
  39. data/lib/selenium/webdriver/common/target_locator.rb +4 -4
  40. data/lib/selenium/webdriver/devtools.rb +144 -0
  41. data/lib/selenium/webdriver/devtools/console_event.rb +38 -0
  42. data/lib/selenium/webdriver/{edge_html/driver.rb → devtools/exception_event.rb} +10 -13
  43. data/lib/selenium/webdriver/devtools/mutation_event.rb +37 -0
  44. data/lib/selenium/webdriver/devtools/request.rb +57 -0
  45. data/lib/selenium/webdriver/edge.rb +7 -29
  46. data/lib/selenium/webdriver/{edge_chrome → edge}/driver.rb +10 -4
  47. data/lib/selenium/webdriver/edge/features.rb +39 -0
  48. data/lib/selenium/webdriver/{edge_chrome → edge}/options.rb +12 -3
  49. data/lib/selenium/webdriver/{edge_chrome → edge}/profile.rb +2 -2
  50. data/lib/selenium/webdriver/{edge_chrome → edge}/service.rb +2 -2
  51. data/lib/selenium/webdriver/firefox.rb +5 -1
  52. data/lib/selenium/webdriver/firefox/driver.rb +19 -3
  53. data/lib/selenium/webdriver/firefox/{bridge.rb → features.rb} +3 -3
  54. data/lib/selenium/webdriver/firefox/options.rb +25 -31
  55. data/lib/selenium/webdriver/firefox/profile.rb +12 -2
  56. data/lib/selenium/webdriver/firefox/service.rb +1 -1
  57. data/lib/selenium/webdriver/ie/driver.rb +1 -2
  58. data/lib/selenium/webdriver/ie/options.rb +7 -20
  59. data/lib/selenium/webdriver/ie/service.rb +4 -2
  60. data/lib/selenium/webdriver/remote/bridge.rb +50 -42
  61. data/lib/selenium/webdriver/remote/capabilities.rb +127 -71
  62. data/lib/selenium/webdriver/remote/commands.rb +3 -0
  63. data/lib/selenium/webdriver/remote/driver.rb +10 -3
  64. data/lib/selenium/webdriver/remote/http/common.rb +0 -5
  65. data/lib/selenium/webdriver/remote/http/default.rb +8 -7
  66. data/lib/selenium/webdriver/remote/http/persistent.rb +6 -0
  67. data/lib/selenium/webdriver/safari.rb +8 -1
  68. data/lib/selenium/webdriver/safari/driver.rb +3 -4
  69. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +3 -3
  70. data/lib/selenium/webdriver/safari/options.rb +1 -33
  71. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  72. data/lib/selenium/webdriver/support/color.rb +2 -2
  73. data/lib/selenium/webdriver/support/event_firing_bridge.rb +1 -1
  74. data/lib/selenium/webdriver/support/guards.rb +95 -0
  75. data/lib/selenium/webdriver/support/guards/guard.rb +89 -0
  76. data/lib/selenium/webdriver/support/guards/guard_condition.rb +52 -0
  77. data/lib/selenium/webdriver/support/select.rb +2 -2
  78. data/lib/selenium/webdriver/version.rb +1 -1
  79. metadata +69 -32
  80. data/CHANGES +0 -1725
  81. data/Gemfile +0 -4
  82. data/LICENSE +0 -202
  83. data/README.md +0 -35
  84. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -65
  85. data/lib/selenium/webdriver/edge_html/options.rb +0 -91
  86. data/lib/selenium/webdriver/edge_html/service.rb +0 -47
  87. data/selenium-webdriver.gemspec +0 -48
@@ -17,14 +17,14 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
- require 'selenium/webdriver/chrome/bridge'
21
-
22
20
  module Selenium
23
21
  module WebDriver
24
- module EdgeChrome
25
- module Bridge
26
- include Selenium::WebDriver::Chrome::Bridge
27
- end # Bridge
28
- end # EdgeChrome
22
+ module DriverExtensions
23
+ module HasLogs
24
+ def logs
25
+ @logs ||= Logs.new(@bridge)
26
+ end
27
+ end # HasLogs
28
+ end # DriverExtensions
29
29
  end # WebDriver
30
30
  end # Selenium
@@ -17,40 +17,19 @@
17
17
  # specific language governing permissions and limitations
18
18
  # under the License.
19
19
 
20
+ # TODO: Deprecated; Delete after 4.0 release
20
21
  module Selenium
21
22
  module WebDriver
22
23
  module DriverExtensions
23
24
  module HasNetworkConnection
24
25
  def network_connection_type
25
- connection_value = @bridge.network_connection
26
-
27
- connection_type = values_to_type[connection_value]
28
-
29
- # In case the connection type is not recognized return the
30
- # connection value.
31
- connection_type || connection_value
32
- end
33
-
34
- def network_connection_type=(connection_type)
35
- raise ArgumentError, 'Invalid connection type' unless valid_type? connection_type
36
-
37
- connection_value = type_to_values[connection_type]
38
-
39
- @bridge.network_connection = connection_value
40
- end
41
-
42
- private
43
-
44
- def type_to_values
45
- {airplane_mode: 1, wifi: 2, data: 4, all: 6, none: 0}
46
- end
47
-
48
- def values_to_type
49
- type_to_values.invert
26
+ raise Error::UnsupportedOperationError,
27
+ 'The W3C standard does not currently support getting network connection'
50
28
  end
51
29
 
52
- def valid_type?(type)
53
- type_to_values.key? type
30
+ def network_connection_type=(*)
31
+ raise Error::UnsupportedOperationError,
32
+ 'The W3C standard does not currently support setting network connection'
54
33
  end
55
34
  end # HasNetworkConnection
56
35
  end # DriverExtensions
@@ -0,0 +1,67 @@
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 DriverExtensions
23
+ module HasNetworkInterception
24
+
25
+ #
26
+ # Intercepts requests coming from browser allowing
27
+ # to either pass them through like proxy or provide
28
+ # a stubbed response instead.
29
+ #
30
+ # @example Log requests and pass through
31
+ # driver.intercept do |request|
32
+ # puts "#{request.method} #{request.url}"
33
+ # request.continue
34
+ # end
35
+ #
36
+ # @example Stub response for image requests
37
+ # driver.intercept do |request|
38
+ # if request.url.match?(/\.png$/)
39
+ # request.respond(body: File.read('myfile.png'))
40
+ # else
41
+ # request.continue
42
+ # end
43
+ # end
44
+ #
45
+ # @param [#call] block which is called when request is interecepted
46
+ # @yieldparam [DevTools::Request]
47
+ #
48
+
49
+ def intercept
50
+ devtools.network.set_cache_disabled(cache_disabled: true)
51
+ devtools.fetch.on(:request_paused) do |params|
52
+ request = DevTools::Request.new(
53
+ devtools: devtools,
54
+ id: params['requestId'],
55
+ url: params.dig('request', 'url'),
56
+ method: params.dig('request', 'method'),
57
+ headers: params.dig('request', 'headers')
58
+ )
59
+ yield request
60
+ end
61
+ devtools.fetch.enable
62
+ end
63
+
64
+ end # HasNetworkInterception
65
+ end # DriverExtensions
66
+ end # WebDriver
67
+ end # Selenium
@@ -22,6 +22,7 @@ module Selenium
22
22
  module DriverExtensions
23
23
  module HasRemoteStatus
24
24
  def remote_status
25
+ WebDriver.logger.deprecate('#remote_status', '#status')
25
26
  @bridge.status
26
27
  end
27
28
  end # HasRemoteStatus
@@ -19,43 +19,41 @@
19
19
 
20
20
  module Selenium
21
21
  module WebDriver
22
- #
23
- # @api private
24
- #
25
-
26
22
  module DriverExtensions
27
- module Rotatable
28
- ORIENTATIONS = %i[landscape portrait].freeze
29
-
23
+ module PrintsPage
24
+ #
25
+ # Save a page as a PDF to the given path
30
26
  #
31
- # Change the screen orientation
27
+ # @example Save Printed Page
28
+ # driver.save_print_page('../printed_page.pdf')
32
29
  #
33
- # @param [:landscape, :portrait] orientation
30
+ # @param [String] path to where the pdf should be saved
34
31
  #
35
32
  # @api public
36
33
  #
37
34
 
38
- def rotation=(orientation)
39
- unless ORIENTATIONS.include?(orientation)
40
- raise ArgumentError, "expected #{ORIENTATIONS.inspect}, got #{orientation.inspect}"
35
+ def save_print_page(path, **options)
36
+ File.open(path, 'wb') do |file|
37
+ content = Base64.decode64 print_page(options)
38
+ file << content
41
39
  end
42
-
43
- bridge.screen_orientation = orientation.to_s.upcase
44
40
  end
45
- alias_method :rotate, :rotation=
46
41
 
47
42
  #
48
- # Get the current screen orientation
43
+ # Return a Base64 encoded Print Page as a string
49
44
  #
50
- # @return [:landscape, :portrait] orientation
45
+ # @see https://w3c.github.io/webdriver/#print-page
51
46
  #
52
47
  # @api public
53
48
  #
54
49
 
55
- def orientation
56
- bridge.screen_orientation.to_sym.downcase
50
+ def print_page(**options)
51
+ options[:pageRanges] = Array(options.delete(:page_ranges)) || []
52
+ options[:shrinkToFit] = options.delete(:shrink_to_fit) { true }
53
+
54
+ @bridge.print_page(options)
57
55
  end
58
- end # Rotatable
56
+ end # PrintsPage
59
57
  end # DriverExtensions
60
58
  end # WebDriver
61
59
  end # Selenium
@@ -23,6 +23,7 @@ module Selenium
23
23
  ELEMENT_KEY = 'element-6066-11e4-a52e-4f735466cecf'
24
24
 
25
25
  include SearchContext
26
+ include TakesScreenshot
26
27
 
27
28
  #
28
29
  # Creates a new Element
@@ -91,13 +92,18 @@ module Selenium
91
92
  end
92
93
 
93
94
  #
94
- # Get the value of a the given attribute of the element. Will return the current value, even if
95
- # this has been modified after the page has been loaded. More exactly, this method will return
96
- # the value of the given attribute, unless that attribute is not present, in which case the
97
- # value of the property with the same name is returned. If neither value is set, nil is
98
- # returned. The "style" attribute is converted as best can be to a text representation with a
99
- # trailing semi-colon. The following are deemed to be "boolean" attributes, and will
100
- # return either "true" or "false":
95
+ # This method attempts to provide the most likely desired current value for the attribute
96
+ # of the element, even when that desired value is actually a JavaScript property.
97
+ # It is implemented with a custom JavaScript atom. To obtain the exact value of the attribute or property,
98
+ # use #dom_attribute or #property methods respectively.
99
+ #
100
+ # More exactly, this method will return the value of the property with the given name,
101
+ # if it exists. If it does not, then the value of the attribute with the given name is returned.
102
+ # If neither exists, null is returned.
103
+ #
104
+ # The "style" attribute is converted as best can be to a text representation with a trailing semi-colon.
105
+ #
106
+ # The following are deemed to be "boolean" attributes, and will return either "true" or "false":
101
107
  #
102
108
  # async, autofocus, autoplay, checked, compact, complete, controls, declare, defaultchecked,
103
109
  # defaultselected, defer, disabled, draggable, ended, formnovalidate, hidden, indeterminate,
@@ -105,22 +111,46 @@ module Selenium
105
111
  # nowrap, open, paused, pubdate, readonly, required, reversed, scoped, seamless, seeking,
106
112
  # selected, spellcheck, truespeed, willvalidate
107
113
  #
108
- # Finally, the following commonly mis-capitalized attribute/property names are evaluated as
109
- # expected:
114
+ # Finally, the following commonly mis-capitalized attribute/property names are evaluated as expected:
110
115
  #
111
- # class, readonly
116
+ # When the value of "class" is requested, the "className" property is returned.
117
+ # When the value of "readonly" is requested, the "readOnly" property is returned.
112
118
  #
113
119
  # @param [String] name attribute name
114
120
  # @return [String, nil] attribute value
115
121
  #
122
+ # @see #dom_attribute
123
+ # @see #property
124
+ #
116
125
 
117
126
  def attribute(name)
118
127
  bridge.element_attribute self, name
119
128
  end
120
129
 
121
130
  #
122
- # Get the value of a the given property with the same name of the element. If the value is not
123
- # set, nil is returned.
131
+ # Gets the value of a declared HTML attribute of this element.
132
+ #
133
+ # As opposed to the #attribute method, this method
134
+ # only returns attributes declared in the element's HTML markup.
135
+ #
136
+ # If the attribute is not set, nil is returned.
137
+ #
138
+ # @param [String] name attribute name
139
+ # @return [String, nil] attribute value
140
+ #
141
+ # @see #attribute
142
+ # @see #property
143
+ #
144
+
145
+ def dom_attribute(name)
146
+ bridge.element_dom_attribute self, name
147
+ end
148
+
149
+ #
150
+ # Gets the value of a JavaScript property of this element
151
+ # This will return the current value,
152
+ # even if this has been modified after the page has been loaded.
153
+ # If the value is not set, nil is returned.
124
154
  #
125
155
  # @param [String] name property name
126
156
  # @return [String, nil] property value
@@ -130,6 +160,26 @@ module Selenium
130
160
  bridge.element_property self, name
131
161
  end
132
162
 
163
+ #
164
+ # Gets the computed WAI-ARIA role of element
165
+ #
166
+ # @return [String]
167
+ #
168
+
169
+ def aria_role
170
+ bridge.element_aria_role self
171
+ end
172
+
173
+ #
174
+ # Gets the computed WAI-ARIA label of element.
175
+ #
176
+ # @return [String]
177
+ #
178
+
179
+ def accessible_name
180
+ bridge.element_aria_label self
181
+ end
182
+
133
183
  #
134
184
  # Get the text content of this element
135
185
  #
@@ -327,6 +377,10 @@ module Selenium
327
377
 
328
378
  tn == 'option' || (tn == 'input' && %w[radio checkbox].include?(type))
329
379
  end
380
+
381
+ def screenshot
382
+ bridge.element_screenshot(self)
383
+ end
330
384
  end # Element
331
385
  end # WebDriver
332
386
  end # Selenium
@@ -26,7 +26,10 @@ module Selenium
26
26
  attr_reader :source
27
27
 
28
28
  def initialize(source)
29
- raise TypeError, "#{source.type} is not a valid input type" unless Interactions::SOURCE_TYPES.include? source.type
29
+ unless Interactions::SOURCE_TYPES.include? source.type
30
+ raise TypeError,
31
+ "#{source.type} is not a valid input type"
32
+ end
30
33
 
31
34
  @source = source
32
35
  end
@@ -40,7 +40,7 @@ module Selenium
40
40
  :close,
41
41
  :debug, :debug?,
42
42
  :info, :info?,
43
- :warn, :warn?,
43
+ :warn?,
44
44
  :error, :error?,
45
45
  :fatal, :fatal?,
46
46
  :level, :level=
@@ -108,10 +108,11 @@ module Selenium
108
108
  #
109
109
  # @param [String] old
110
110
  # @param [String, nil] new
111
- # @param [Symbol, Array<Sybmol>] id
111
+ # @param [Symbol, Array<Symbol>] id
112
+ # @param [String] reference
112
113
  # @yield appends additional message to end of provided template
113
114
  #
114
- def deprecate(old, new = nil, id: [], &block)
115
+ def deprecate(old, new = nil, id: [], reference: '', &block)
115
116
  id = Array(id)
116
117
  return if @ignored.include?(:deprecations) || (@ignored & id).any?
117
118
 
@@ -123,6 +124,8 @@ module Selenium
123
124
  else
124
125
  ' and will be removed in a future release.'
125
126
  end
127
+ message << " See explanation for this deprecation: #{reference}." unless reference.empty?
128
+
126
129
  warn message, &block
127
130
  end
128
131
 
@@ -36,6 +36,7 @@ module Selenium
36
36
  # @option opts [String] :value A value
37
37
  # @option opts [String] :path ('/') A path
38
38
  # @option opts [String] :secure (false) A boolean
39
+ # @option opts [String] :same_site (Strict or Lax) currently supported only in chrome 80+ versions
39
40
  # @option opts [Time,DateTime,Numeric,nil] :expires (nil) Expiry date, either as a Time, DateTime, or seconds since epoch.
40
41
  #
41
42
  # @raise [ArgumentError] if :name or :value is not specified
@@ -45,9 +46,15 @@ module Selenium
45
46
  raise ArgumentError, 'name is required' unless opts[:name]
46
47
  raise ArgumentError, 'value is required' unless opts[:value]
47
48
 
48
- opts[:path] ||= '/'
49
+ # NOTE: This is required because of https://bugs.chromium.org/p/chromedriver/issues/detail?id=3732
49
50
  opts[:secure] ||= false
50
51
 
52
+ same_site = opts.delete(:same_site)
53
+ opts[:sameSite] = same_site if same_site
54
+
55
+ http_only = opts.delete(:http_only)
56
+ opts[:httpOnly] = http_only if http_only
57
+
51
58
  obj = opts.delete(:expires)
52
59
  opts[:expiry] = seconds_from(obj).to_i if obj
53
60
 
@@ -102,6 +109,7 @@ module Selenium
102
109
  #
103
110
 
104
111
  def logs
112
+ WebDriver.logger.deprecate('Manager#logs', 'Chrome::Driver#logs')
105
113
  @logs ||= Logs.new(@bridge)
106
114
  end
107
115
 
@@ -169,6 +177,8 @@ module Selenium
169
177
  path: cookie['path'],
170
178
  domain: cookie['domain'] && strip_port(cookie['domain']),
171
179
  expires: cookie['expiry'] && datetime_at(cookie['expiry']),
180
+ same_site: cookie['sameSite'],
181
+ http_only: cookie['httpOnly'],
172
182
  secure: cookie['secure']
173
183
  }
174
184
  end
@@ -20,9 +20,54 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  class Options
23
+ W3C_OPTIONS = %i[browser_name browser_version platform_name accept_insecure_certs page_load_strategy proxy
24
+ set_window_rect timeouts unhandled_prompt_behavior strict_file_interactability].freeze
25
+
26
+ class << self
27
+ attr_reader :driver_path
28
+
29
+ def chrome(**opts)
30
+ Chrome::Options.new(**opts)
31
+ end
32
+
33
+ def firefox(**opts)
34
+ Firefox::Options.new(**opts)
35
+ end
36
+
37
+ def ie(**opts)
38
+ IE::Options.new(**opts)
39
+ end
40
+ alias_method :internet_explorer, :ie
41
+
42
+ def edge(**opts)
43
+ Edge::Options.new(**opts)
44
+ end
45
+ alias_method :microsoftedge, :edge
46
+
47
+ def safari(**opts)
48
+ Safari::Options.new(**opts)
49
+ end
50
+
51
+ def set_capabilities
52
+ (W3C_OPTIONS + self::CAPABILITIES.keys).each do |key|
53
+ next if method_defined? key
54
+
55
+ define_method key do
56
+ @options[key]
57
+ end
58
+
59
+ define_method "#{key}=" do |value|
60
+ @options[key] = value
61
+ end
62
+ end
63
+ end
64
+ end
65
+
23
66
  attr_accessor :options
24
67
 
25
68
  def initialize(options: nil, **opts)
69
+ self.class.set_capabilities
70
+
26
71
  @options = if options
27
72
  WebDriver.logger.deprecate(":options as keyword for initializing #{self.class}",
28
73
  "custom values directly in #new constructor",
@@ -31,6 +76,7 @@ module Selenium
31
76
  else
32
77
  opts
33
78
  end
79
+ @options[:browser_name] = self.class::BROWSER
34
80
  end
35
81
 
36
82
  #
@@ -48,6 +94,14 @@ module Selenium
48
94
  @options[name] = value
49
95
  end
50
96
 
97
+ def ==(other)
98
+ return false unless other.is_a? self.class
99
+
100
+ as_json == other.as_json
101
+ end
102
+
103
+ alias_method :eql?, :==
104
+
51
105
  #
52
106
  # @api private
53
107
  #
@@ -55,22 +109,36 @@ module Selenium
55
109
  def as_json(*)
56
110
  options = @options.dup
57
111
 
58
- opts = self.class::CAPABILITIES.each_with_object({}) do |(capability_alias, capability_name), hash|
112
+ w3c_options = options.select { |key, _val| W3C_OPTIONS.include?(key) }
113
+ options.delete_if { |key, _val| W3C_OPTIONS.include?(key) }
114
+
115
+ self.class::CAPABILITIES.each do |capability_alias, capability_name|
59
116
  capability_value = options.delete(capability_alias)
60
- hash[capability_name] = capability_value unless capability_value.nil?
117
+ options[capability_name] = capability_value unless capability_value.nil?
61
118
  end
62
- opts.merge(options)
119
+ browser_options = defined?(self.class::KEY) ? {self.class::KEY => options} : options
120
+
121
+ process_browser_options(browser_options)
122
+ generate_as_json(w3c_options.merge(browser_options))
63
123
  end
64
124
 
65
125
  private
66
126
 
67
- def generate_as_json(value)
68
- if value.respond_to?(:as_json)
127
+ def process_browser_options(_browser_options)
128
+ nil
129
+ end
130
+
131
+ def camelize?(_key)
132
+ true
133
+ end
134
+
135
+ def generate_as_json(value, camelize_keys: true)
136
+ if value.is_a?(Hash)
137
+ process_json_hash(value, camelize_keys)
138
+ elsif value.respond_to?(:as_json)
69
139
  value.as_json
70
- elsif value.is_a?(Hash)
71
- value.each_with_object({}) { |(key, val), hash| hash[convert_json_key(key)] = generate_as_json(val) }
72
140
  elsif value.is_a?(Array)
73
- value.map(&method(:generate_as_json))
141
+ value.map { |val| generate_as_json(val, camelize_keys: camelize_keys) }
74
142
  elsif value.is_a?(Symbol)
75
143
  value.to_s
76
144
  else
@@ -78,15 +146,26 @@ module Selenium
78
146
  end
79
147
  end
80
148
 
81
- def convert_json_key(key)
82
- key = camel_case(key) if key.is_a?(Symbol)
149
+ def process_json_hash(value, camelize_keys)
150
+ value.each_with_object({}) do |(key, val), hash|
151
+ next if val.respond_to?(:empty?) && val.empty?
152
+
153
+ camelize = camelize_keys ? camelize?(key) : false
154
+ key = convert_json_key(key, camelize: camelize)
155
+ hash[key] = generate_as_json(val, camelize_keys: camelize)
156
+ end
157
+ end
158
+
159
+ def convert_json_key(key, camelize: true)
160
+ key = key.to_s if key.is_a?(Symbol)
161
+ key = camel_case(key) if camelize
83
162
  return key if key.is_a?(String)
84
163
 
85
164
  raise TypeError, "expected String or Symbol, got #{key.inspect}:#{key.class}"
86
165
  end
87
166
 
88
167
  def camel_case(str)
89
- str.to_s.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
168
+ str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
90
169
  end
91
170
  end # Options
92
171
  end # WebDriver