selenium-webdriver 4.0.0.alpha2 → 4.0.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 (113) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +338 -4
  3. data/Gemfile +3 -1
  4. data/LICENSE +1 -1
  5. data/NOTICE +2 -0
  6. data/README.md +4 -5
  7. data/lib/selenium/server.rb +21 -29
  8. data/lib/selenium/webdriver/atoms/findElements.js +122 -0
  9. data/lib/selenium/webdriver/atoms/getAttribute.js +100 -7
  10. data/lib/selenium/webdriver/atoms/isDisplayed.js +76 -78
  11. data/lib/selenium/webdriver/atoms/mutationListener.js +55 -0
  12. data/lib/selenium/webdriver/chrome/driver.rb +26 -56
  13. data/lib/selenium/webdriver/chrome/features.rb +106 -0
  14. data/lib/selenium/webdriver/chrome/options.rb +127 -52
  15. data/lib/selenium/webdriver/chrome/profile.rb +8 -5
  16. data/lib/selenium/webdriver/chrome/service.rb +4 -6
  17. data/lib/selenium/webdriver/chrome.rb +10 -9
  18. data/lib/selenium/webdriver/common/driver.rb +110 -19
  19. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +43 -0
  20. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +51 -0
  21. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +89 -0
  22. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +77 -0
  23. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +38 -0
  24. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +45 -0
  25. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +43 -0
  26. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +38 -0
  27. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +5 -8
  28. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +144 -0
  29. data/lib/selenium/webdriver/common/driver_extensions/has_logs.rb +30 -0
  30. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +17 -0
  31. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +6 -27
  32. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +136 -0
  33. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -11
  34. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +77 -0
  35. data/lib/selenium/webdriver/common/driver_extensions/has_remote_status.rb +1 -0
  36. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  37. data/lib/selenium/webdriver/common/element.rb +79 -16
  38. data/lib/selenium/webdriver/common/error.rb +12 -0
  39. data/lib/selenium/webdriver/common/interactions/interaction.rb +4 -1
  40. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  41. data/lib/selenium/webdriver/common/logger.rb +50 -15
  42. data/lib/selenium/webdriver/common/manager.rb +14 -14
  43. data/lib/selenium/webdriver/common/options.rb +186 -0
  44. data/lib/selenium/webdriver/common/platform.rb +6 -1
  45. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  46. data/lib/selenium/webdriver/common/profile_helper.rb +10 -2
  47. data/lib/selenium/webdriver/common/proxy.rb +6 -3
  48. data/lib/selenium/webdriver/common/search_context.rb +7 -3
  49. data/lib/selenium/webdriver/common/service.rb +23 -113
  50. data/lib/selenium/webdriver/common/service_manager.rb +151 -0
  51. data/lib/selenium/webdriver/common/shadow_root.rb +87 -0
  52. data/lib/selenium/webdriver/common/socket_lock.rb +2 -2
  53. data/lib/selenium/webdriver/common/takes_screenshot.rb +66 -0
  54. data/lib/selenium/webdriver/common/target_locator.rb +32 -4
  55. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  56. data/lib/selenium/webdriver/common/wait.rb +1 -1
  57. data/lib/selenium/webdriver/common/window.rb +0 -4
  58. data/lib/selenium/webdriver/common.rb +17 -2
  59. data/lib/selenium/webdriver/devtools/console_event.rb +38 -0
  60. data/lib/selenium/webdriver/devtools/exception_event.rb +36 -0
  61. data/lib/selenium/webdriver/devtools/mutation_event.rb +37 -0
  62. data/lib/selenium/webdriver/{chrome/bridge.rb → devtools/pinned_script.rb} +26 -17
  63. data/lib/selenium/webdriver/devtools/request.rb +67 -0
  64. data/lib/selenium/webdriver/devtools/response.rb +66 -0
  65. data/lib/selenium/webdriver/devtools.rb +182 -0
  66. data/lib/selenium/webdriver/edge/driver.rb +5 -31
  67. data/lib/selenium/webdriver/edge/features.rb +44 -0
  68. data/lib/selenium/webdriver/edge/options.rb +11 -48
  69. data/lib/selenium/webdriver/edge/profile.rb +33 -0
  70. data/lib/selenium/webdriver/edge/service.rb +9 -24
  71. data/lib/selenium/webdriver/edge.rb +11 -13
  72. data/lib/selenium/webdriver/firefox/driver.rb +20 -30
  73. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  74. data/lib/selenium/webdriver/firefox/{bridge.rb → features.rb} +23 -4
  75. data/lib/selenium/webdriver/firefox/options.rb +70 -49
  76. data/lib/selenium/webdriver/firefox/profile.rb +16 -77
  77. data/lib/selenium/webdriver/firefox/service.rb +1 -5
  78. data/lib/selenium/webdriver/firefox.rb +22 -16
  79. data/lib/selenium/webdriver/ie/driver.rb +1 -34
  80. data/lib/selenium/webdriver/ie/options.rb +13 -44
  81. data/lib/selenium/webdriver/ie/service.rb +9 -11
  82. data/lib/selenium/webdriver/ie.rb +8 -7
  83. data/lib/selenium/webdriver/remote/bridge.rb +112 -86
  84. data/lib/selenium/webdriver/remote/capabilities.rb +120 -62
  85. data/lib/selenium/webdriver/remote/commands.rb +7 -0
  86. data/lib/selenium/webdriver/remote/driver.rb +15 -12
  87. data/lib/selenium/webdriver/remote/http/common.rb +0 -5
  88. data/lib/selenium/webdriver/remote/http/default.rb +17 -11
  89. data/lib/selenium/webdriver/remote/http/persistent.rb +11 -6
  90. data/lib/selenium/webdriver/remote.rb +15 -9
  91. data/lib/selenium/webdriver/safari/driver.rb +3 -34
  92. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +6 -6
  93. data/lib/selenium/webdriver/safari/options.rb +10 -29
  94. data/lib/selenium/webdriver/safari/service.rb +0 -4
  95. data/lib/selenium/webdriver/safari.rb +16 -8
  96. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  97. data/lib/selenium/webdriver/support/cdp/domain.rb.erb +63 -0
  98. data/lib/selenium/webdriver/support/cdp_client_generator.rb +108 -0
  99. data/lib/selenium/webdriver/support/color.rb +2 -2
  100. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  101. data/lib/selenium/webdriver/support/guards/guard.rb +89 -0
  102. data/lib/selenium/webdriver/support/guards/guard_condition.rb +52 -0
  103. data/lib/selenium/webdriver/support/guards.rb +95 -0
  104. data/lib/selenium/webdriver/support/relative_locator.rb +51 -0
  105. data/lib/selenium/webdriver/support/select.rb +2 -2
  106. data/lib/selenium/webdriver/support.rb +1 -0
  107. data/lib/selenium/webdriver/version.rb +1 -1
  108. data/lib/selenium/webdriver.rb +10 -8
  109. data/selenium-webdriver.gemspec +29 -13
  110. metadata +125 -51
  111. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  112. data/lib/selenium/webdriver/firefox/binary.rb +0 -110
  113. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
@@ -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,29 +111,73 @@ 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 @id, 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
127
157
  #
128
158
 
129
159
  def property(name)
130
- bridge.element_property self, name
160
+ bridge.element_property @id, name
161
+ end
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 @id
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 @id
131
181
  end
132
182
 
133
183
  #
@@ -267,6 +317,16 @@ module Selenium
267
317
  bridge.element_size @id
268
318
  end
269
319
 
320
+ #
321
+ # Returns the shadow root of an element.
322
+ #
323
+ # @return [WebDriver::ShadowRoot]
324
+ #
325
+
326
+ def shadow_root
327
+ bridge.shadow_root @id
328
+ end
329
+
270
330
  #-------------------------------- sugar --------------------------------
271
331
 
272
332
  #
@@ -286,14 +346,13 @@ module Selenium
286
346
  #
287
347
  alias_method :[], :attribute
288
348
 
289
- #
290
- # for SearchContext and execute_script
291
349
  #
292
350
  # @api private
351
+ # @see SearchContext
293
352
  #
294
353
 
295
354
  def ref
296
- @id
355
+ [:element, @id]
297
356
  end
298
357
 
299
358
  #
@@ -327,6 +386,10 @@ module Selenium
327
386
 
328
387
  tn == 'option' || (tn == 'input' && %w[radio checkbox].include?(type))
329
388
  end
389
+
390
+ def screenshot
391
+ bridge.element_screenshot(@id)
392
+ end
330
393
  end # Element
331
394
  end # WebDriver
332
395
  end # Selenium
@@ -61,6 +61,12 @@ module Selenium
61
61
 
62
62
  class StaleElementReferenceError < WebDriverError; end
63
63
 
64
+ #
65
+ # A command failed because the referenced shadow root is no longer attached to the DOM.
66
+ #
67
+
68
+ class DetachedShadowRootError < WebDriverError; end
69
+
64
70
  #
65
71
  # The target element is in an invalid state, rendering it impossible to interact with, for
66
72
  # example if you click a disabled element.
@@ -93,6 +99,12 @@ module Selenium
93
99
 
94
100
  class NoSuchWindowError < WebDriverError; end
95
101
 
102
+ #
103
+ # The element does not have a shadow root.
104
+ #
105
+
106
+ class NoSuchShadowRootError < WebDriverError; end
107
+
96
108
  #
97
109
  # An illegal attempt was made to set a cookie under a different domain than the current page.
98
110
  #
@@ -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
@@ -30,14 +30,14 @@ module Selenium
30
30
 
31
31
  def as_json(*)
32
32
  {
33
- 'level' => level,
34
33
  'timestamp' => timestamp,
34
+ 'level' => level,
35
35
  'message' => message
36
36
  }
37
37
  end
38
38
 
39
39
  def to_s
40
- "#{level} #{time}: #{message}"
40
+ "#{time} #{level}: #{message}"
41
41
  end
42
42
 
43
43
  def time
@@ -40,13 +40,17 @@ 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=
47
47
 
48
- def initialize
49
- @logger = create_logger($stdout)
48
+ #
49
+ # @param [String] progname Allow child projects to use Selenium's Logger pattern
50
+ #
51
+ def initialize(progname = 'Selenium')
52
+ @logger = create_logger(progname)
53
+ @ignored = []
50
54
  end
51
55
 
52
56
  #
@@ -73,28 +77,63 @@ module Selenium
73
77
  @logger.instance_variable_get(:@logdev).dev
74
78
  end
75
79
 
80
+ #
81
+ # Will not log the provided ID.
82
+ #
83
+ # @param [Array, Symbol] id
84
+ #
85
+ def ignore(id)
86
+ Array(id).each { |ignore| @ignored << ignore }
87
+ end
88
+
89
+ #
90
+ # Overrides default #warn to skip ignored messages by provided id
91
+ #
92
+ # @param [String] message
93
+ # @param [Symbol, Array<Sybmol>] id
94
+ # @yield see #deprecate
95
+ #
96
+ def warn(message, id: [])
97
+ id = Array(id)
98
+ return if (@ignored & id).any?
99
+
100
+ msg = id.empty? ? message : "[#{id.map(&:inspect).join(', ')}] #{message} "
101
+ msg += " #{yield}" if block_given?
102
+
103
+ @logger.warn { msg }
104
+ end
105
+
76
106
  #
77
107
  # Marks code as deprecated with/without replacement.
78
108
  #
79
109
  # @param [String] old
80
110
  # @param [String, nil] new
111
+ # @param [Symbol, Array<Symbol>] id
112
+ # @param [String] reference
113
+ # @yield appends additional message to end of provided template
81
114
  #
82
- def deprecate(old, new = nil)
83
- message = +"[DEPRECATION] #{old} is deprecated"
115
+ def deprecate(old, new = nil, id: [], reference: '', &block)
116
+ id = Array(id)
117
+ return if @ignored.include?(:deprecations) || (@ignored & id).any?
118
+
119
+ ids = id.empty? ? '' : "[#{id.map(&:inspect).join(', ')}] "
120
+
121
+ message = +"[DEPRECATION] #{ids}#{old} is deprecated"
84
122
  message << if new
85
123
  ". Use #{new} instead."
86
124
  else
87
- ' and will be removed in the next releases.'
125
+ ' and will be removed in a future release.'
88
126
  end
127
+ message << " See explanation for this deprecation: #{reference}." unless reference.empty?
89
128
 
90
- warn message
129
+ warn message, &block
91
130
  end
92
131
 
93
132
  private
94
133
 
95
- def create_logger(output)
96
- logger = ::Logger.new(output)
97
- logger.progname = 'Selenium'
134
+ def create_logger(name)
135
+ logger = ::Logger.new($stdout)
136
+ logger.progname = name
98
137
  logger.level = default_level
99
138
  logger.formatter = proc do |severity, time, progname, msg|
100
139
  "#{time.strftime('%F %T')} #{severity} #{progname} #{msg}\n"
@@ -104,11 +143,7 @@ module Selenium
104
143
  end
105
144
 
106
145
  def default_level
107
- if $DEBUG || ENV.key?('DEBUG')
108
- :debug
109
- else
110
- :warn
111
- end
146
+ $DEBUG || ENV.key?('DEBUG') ? :debug : :warn
112
147
  end
113
148
  end # Logger
114
149
  end # WebDriver
@@ -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
 
@@ -97,24 +104,19 @@ module Selenium
97
104
  @timeouts ||= Timeouts.new(@bridge)
98
105
  end
99
106
 
100
- #
101
- # @api beta This API may be changed or removed in a future release.
102
- #
103
-
104
107
  def logs
108
+ WebDriver.logger.deprecate('Manager#logs', 'Chrome::Driver#logs')
105
109
  @logs ||= Logs.new(@bridge)
106
110
  end
107
111
 
108
112
  #
109
- # Create a new top-level browsing context
110
- # https://w3c.github.io/webdriver/#new-window
111
113
  # @param type [Symbol] Supports two values: :tab and :window.
112
- # Use :tab if you'd like the new window to share an OS-level window
113
- # with the current browsing context.
114
- # Use :window otherwise
115
114
  # @return [String] The value of the window handle
116
115
  #
117
116
  def new_window(type = :tab)
117
+ WebDriver.logger.deprecate('Manager#new_window', 'TargetLocator#new_window', id: :new_window) do
118
+ 'e.g., `driver.switch_to.new_window(:tab)`'
119
+ end
118
120
  case type
119
121
  when :tab, :window
120
122
  result = @bridge.new_window(type)
@@ -129,10 +131,6 @@ module Selenium
129
131
  end
130
132
  end
131
133
 
132
- #
133
- # @api beta This API may be changed or removed in a future release.
134
- #
135
-
136
134
  def window
137
135
  @window ||= Window.new(@bridge)
138
136
  end
@@ -169,6 +167,8 @@ module Selenium
169
167
  path: cookie['path'],
170
168
  domain: cookie['domain'] && strip_port(cookie['domain']),
171
169
  expires: cookie['expiry'] && datetime_at(cookie['expiry']),
170
+ same_site: cookie['sameSite'],
171
+ http_only: cookie['httpOnly'],
172
172
  secure: cookie['secure']
173
173
  }
174
174
  end
@@ -0,0 +1,186 @@
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
+ 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
25
+ web_socket_url].freeze
26
+
27
+ class << self
28
+ attr_reader :driver_path
29
+
30
+ def chrome(**opts)
31
+ Chrome::Options.new(**opts)
32
+ end
33
+
34
+ def firefox(**opts)
35
+ Firefox::Options.new(**opts)
36
+ end
37
+
38
+ def ie(**opts)
39
+ IE::Options.new(**opts)
40
+ end
41
+ alias_method :internet_explorer, :ie
42
+
43
+ def edge(**opts)
44
+ Edge::Options.new(**opts)
45
+ end
46
+ alias_method :microsoftedge, :edge
47
+
48
+ def safari(**opts)
49
+ Safari::Options.new(**opts)
50
+ end
51
+
52
+ def set_capabilities
53
+ (W3C_OPTIONS + self::CAPABILITIES.keys).each do |key|
54
+ next if method_defined? key
55
+
56
+ define_method key do
57
+ @options[key]
58
+ end
59
+
60
+ define_method "#{key}=" do |value|
61
+ @options[key] = value
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ attr_accessor :options
68
+
69
+ def initialize(options: nil, **opts)
70
+ self.class.set_capabilities
71
+
72
+ @options = if options
73
+ WebDriver.logger.deprecate(":options as keyword for initializing #{self.class}",
74
+ "custom values directly in #new constructor",
75
+ id: :options_options)
76
+ opts.merge(options)
77
+ else
78
+ opts
79
+ end
80
+ @options[:browser_name] = self.class::BROWSER
81
+ end
82
+
83
+ #
84
+ # Add a new option not yet handled by bindings.
85
+ #
86
+ # @example Leave Chrome open when chromedriver is killed
87
+ # options = Selenium::WebDriver::Chrome::Options.new
88
+ # options.add_option(:detach, true)
89
+ #
90
+ # @param [String, Symbol] name Name of the option
91
+ # @param [Boolean, String, Integer] value Value of the option
92
+ #
93
+
94
+ def add_option(name, value = nil)
95
+ @options[name.keys.first] = name.values.first if value.nil? && name.is_a?(Hash)
96
+ @options[name] = value
97
+ end
98
+
99
+ def ==(other)
100
+ return false unless other.is_a? self.class
101
+
102
+ as_json == other.as_json
103
+ end
104
+
105
+ alias_method :eql?, :==
106
+
107
+ #
108
+ # @api private
109
+ #
110
+
111
+ def as_json(*)
112
+ options = @options.dup
113
+
114
+ w3c_options = process_w3c_options(options)
115
+
116
+ self.class::CAPABILITIES.each do |capability_alias, capability_name|
117
+ capability_value = options.delete(capability_alias)
118
+ options[capability_name] = capability_value if !capability_value.nil? && !options.key?(capability_name)
119
+ end
120
+ browser_options = defined?(self.class::KEY) ? {self.class::KEY => options} : options
121
+
122
+ process_browser_options(browser_options)
123
+ generate_as_json(w3c_options.merge(browser_options))
124
+ end
125
+
126
+ private
127
+
128
+ def w3c?(key)
129
+ W3C_OPTIONS.include?(key) || key.to_s.include?(':')
130
+ end
131
+
132
+ def process_w3c_options(options)
133
+ w3c_options = options.select { |key, _val| w3c?(key) }
134
+ w3c_options[:unhandled_prompt_behavior] &&= w3c_options[:unhandled_prompt_behavior]&.to_s&.tr('_', ' ')
135
+ options.delete_if { |key, _val| w3c?(key) }
136
+ w3c_options
137
+ end
138
+
139
+ def process_browser_options(_browser_options)
140
+ nil
141
+ end
142
+
143
+ def camelize?(_key)
144
+ true
145
+ end
146
+
147
+ def generate_as_json(value, camelize_keys: true)
148
+ if value.is_a?(Hash)
149
+ process_json_hash(value, camelize_keys)
150
+ elsif value.respond_to?(:as_json)
151
+ value.as_json
152
+ elsif value.is_a?(Array)
153
+ value.map { |val| generate_as_json(val, camelize_keys: camelize_keys) }
154
+ elsif value.is_a?(Symbol)
155
+ value.to_s
156
+ else
157
+ value
158
+ end
159
+ end
160
+
161
+ def process_json_hash(value, camelize_keys)
162
+ value.each_with_object({}) do |(key, val), hash|
163
+ next if val.respond_to?(:empty?) && val.empty?
164
+
165
+ camelize = camelize_keys ? camelize?(key) : false
166
+ key = convert_json_key(key, camelize: camelize)
167
+ hash[key] = generate_as_json(val, camelize_keys: camelize)
168
+ end
169
+ end
170
+
171
+ def convert_json_key(key, camelize: true)
172
+ key = key.to_s if key.is_a?(Symbol)
173
+ key = camel_case(key) if camelize
174
+ return key if key.is_a?(String)
175
+
176
+ raise TypeError, "expected String or Symbol, got #{key.inspect}:#{key.class}"
177
+ end
178
+
179
+ def camel_case(str)
180
+ str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
181
+ end
182
+ end
183
+
184
+ # Options
185
+ end # WebDriver
186
+ end # Selenium
@@ -57,6 +57,8 @@ module Selenium
57
57
  :jenkins
58
58
  elsif ENV['APPVEYOR']
59
59
  :appveyor
60
+ elsif ENV['GITHUB_ACTIONS']
61
+ :github
60
62
  end
61
63
  end
62
64
 
@@ -95,7 +97,10 @@ module Selenium
95
97
  def wsl?
96
98
  return false unless linux?
97
99
 
98
- File.read('/proc/version').include?('Microsoft')
100
+ File.read('/proc/version').downcase.include?('microsoft')
101
+ rescue Errno::EACCES
102
+ # the file cannot be accessed on Linux on DeX
103
+ false
99
104
  end
100
105
 
101
106
  def cygwin?
@@ -32,12 +32,10 @@ module Selenium
32
32
 
33
33
  def self.free?(port)
34
34
  Platform.interfaces.each do |host|
35
- begin
36
- TCPServer.new(host, port).close
37
- rescue *IGNORED_ERRORS => ex
38
- WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{ex.message})")
39
- # ignored - some machines appear unable to bind to some of their interfaces
40
- end
35
+ TCPServer.new(host, port).close
36
+ rescue *IGNORED_ERRORS => e
37
+ WebDriver.logger.debug("port prober could not bind to #{host}:#{port} (#{e.message})")
38
+ # ignored - some machines appear unable to bind to some of their interfaces
41
39
  end
42
40
 
43
41
  true
@@ -31,8 +31,16 @@ module Selenium
31
31
  base.extend ClassMethods
32
32
  end
33
33
 
34
+ def self.decoded(json)
35
+ JSON.parse(json).fetch('zip')
36
+ end
37
+
38
+ def encoded
39
+ Zipper.zip(layout_on_disk)
40
+ end
41
+
34
42
  def as_json(*)
35
- {"zip" => Zipper.zip(layout_on_disk)}
43
+ {"zip" => encoded}
36
44
  end
37
45
 
38
46
  def to_json(*)
@@ -63,7 +71,7 @@ module Selenium
63
71
 
64
72
  module ClassMethods
65
73
  def from_json(json)
66
- data = JSON.parse(json).fetch('zip')
74
+ data = decoded(json)
67
75
 
68
76
  # can't use Tempfile here since it doesn't support File::BINARY mode on 1.8
69
77
  # can't use Dir.mktmpdir(&blk) because of http://jira.codehaus.org/browse/JRUBY-4082
@@ -127,7 +127,10 @@ module Selenium
127
127
  end
128
128
 
129
129
  def type=(type)
130
- raise ArgumentError, "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}" unless TYPES.key? type
130
+ unless TYPES.key? type
131
+ raise ArgumentError,
132
+ "invalid proxy type: #{type.inspect}, expected one of #{TYPES.keys.inspect}"
133
+ end
131
134
 
132
135
  if defined?(@type) && type != @type
133
136
  raise ArgumentError, "incompatible proxy type #{type.inspect} (already set to #{@type.inspect})"
@@ -138,10 +141,10 @@ module Selenium
138
141
 
139
142
  def as_json(*)
140
143
  json_result = {
141
- 'proxyType' => TYPES[type],
144
+ 'proxyType' => TYPES[type].downcase,
142
145
  'ftpProxy' => ftp,
143
146
  'httpProxy' => http,
144
- 'noProxy' => no_proxy,
147
+ 'noProxy' => no_proxy.is_a?(String) ? no_proxy.split(', ') : no_proxy,
145
148
  'proxyAutoconfigUrl' => pac,
146
149
  'sslProxy' => ssl,
147
150
  'autodetect' => auto_detect,