selenium-webdriver 4.0.0.beta4 → 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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +70 -0
  3. data/README.md +1 -1
  4. data/lib/selenium/webdriver/atoms/getAttribute.js +25 -25
  5. data/lib/selenium/webdriver/chrome/driver.rb +4 -0
  6. data/lib/selenium/webdriver/chrome/features.rb +44 -4
  7. data/lib/selenium/webdriver/chrome/options.rb +24 -1
  8. data/lib/selenium/webdriver/common/driver.rb +5 -1
  9. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +51 -0
  10. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +77 -0
  11. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +45 -0
  12. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +38 -0
  13. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -6
  14. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +17 -0
  15. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +87 -18
  16. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -11
  17. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +77 -0
  18. data/lib/selenium/webdriver/common/driver_extensions/prints_page.rb +1 -1
  19. data/lib/selenium/webdriver/common/element.rb +17 -8
  20. data/lib/selenium/webdriver/common/error.rb +12 -0
  21. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  22. data/lib/selenium/webdriver/common/manager.rb +3 -13
  23. data/lib/selenium/webdriver/common/options.rb +13 -5
  24. data/lib/selenium/webdriver/common/shadow_root.rb +87 -0
  25. data/lib/selenium/webdriver/common/socket_poller.rb +30 -19
  26. data/lib/selenium/webdriver/common/target_locator.rb +28 -0
  27. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  28. data/lib/selenium/webdriver/common/window.rb +0 -4
  29. data/lib/selenium/webdriver/common.rb +6 -0
  30. data/lib/selenium/webdriver/devtools/pinned_script.rb +59 -0
  31. data/lib/selenium/webdriver/devtools/request.rb +27 -17
  32. data/lib/selenium/webdriver/devtools/response.rb +66 -0
  33. data/lib/selenium/webdriver/devtools.rb +50 -12
  34. data/lib/selenium/webdriver/edge/features.rb +5 -0
  35. data/lib/selenium/webdriver/firefox/driver.rb +5 -0
  36. data/lib/selenium/webdriver/firefox/features.rb +14 -0
  37. data/lib/selenium/webdriver/firefox/options.rb +28 -1
  38. data/lib/selenium/webdriver/firefox.rb +0 -1
  39. data/lib/selenium/webdriver/ie/options.rb +3 -1
  40. data/lib/selenium/webdriver/ie/service.rb +1 -1
  41. data/lib/selenium/webdriver/remote/bridge.rb +39 -23
  42. data/lib/selenium/webdriver/remote/capabilities.rb +3 -2
  43. data/lib/selenium/webdriver/remote/commands.rb +4 -0
  44. data/lib/selenium/webdriver/remote/driver.rb +3 -1
  45. data/lib/selenium/webdriver/remote.rb +1 -1
  46. data/lib/selenium/webdriver/safari/driver.rb +1 -1
  47. data/lib/selenium/webdriver/safari/options.rb +7 -0
  48. data/lib/selenium/webdriver/support/event_firing_bridge.rb +2 -2
  49. data/lib/selenium/webdriver/version.rb +1 -1
  50. data/lib/selenium/webdriver.rb +1 -0
  51. data/selenium-webdriver.gemspec +3 -2
  52. metadata +31 -3
@@ -112,8 +112,7 @@ module Selenium
112
112
 
113
113
  devtools.runtime.add_binding(name: '__webdriver_attribute')
114
114
  execute_script(mutation_listener)
115
- script = devtools.page.add_script_to_evaluate_on_new_document(source: mutation_listener)
116
- pinned_scripts[mutation_listener] = script['identifier']
115
+ devtools.page.add_script_to_evaluate_on_new_document(source: mutation_listener)
117
116
 
118
117
  devtools.runtime.on(:binding_called, &method(:log_mutation_event))
119
118
  end
@@ -139,10 +138,6 @@ module Selenium
139
138
  @mutation_listener ||= read_atom(:mutationListener)
140
139
  end
141
140
 
142
- def pinned_scripts
143
- @pinned_scripts ||= {}
144
- end
145
-
146
141
  end # HasLogEvents
147
142
  end # DriverExtensions
148
143
  end # WebDriver
@@ -38,13 +38,30 @@ module Selenium
38
38
  # @param [Hash] conditions
39
39
  # @option conditions [Integer] :latency
40
40
  # @option conditions [Integer] :throughput
41
+ # @option conditions [Integer] :upload_throughput
42
+ # @option conditions [Integer] :download_throughput
41
43
  # @option conditions [Boolean] :offline
42
44
  #
43
45
 
44
46
  def network_conditions=(conditions)
47
+ conditions[:latency] ||= 0
48
+ unless conditions.key?(:throughput)
49
+ conditions[:download_throughput] ||= -1
50
+ conditions[:upload_throughput] ||= -1
51
+ end
52
+ conditions[:offline] = false unless conditions.key?(:offline)
53
+
45
54
  @bridge.network_conditions = conditions
46
55
  end
47
56
 
57
+ #
58
+ # Resets Chromium network emulation settings.
59
+ #
60
+
61
+ def delete_network_conditions
62
+ @bridge.delete_network_conditions
63
+ end
64
+
48
65
  end # HasNetworkConditions
49
66
  end # DriverExtensions
50
67
  end # WebDriver
@@ -28,39 +28,108 @@ module Selenium
28
28
  # a stubbed response instead.
29
29
  #
30
30
  # @example Log requests and pass through
31
- # driver.intercept do |request|
31
+ # driver.intercept do |request, &continue|
32
32
  # puts "#{request.method} #{request.url}"
33
- # request.continue
33
+ # continue.call(request)
34
34
  # end
35
35
  #
36
- # @example Stub response for image requests
37
- # driver.intercept do |request|
36
+ # @example Stub requests for images
37
+ # driver.intercept do |request, &continue|
38
38
  # if request.url.match?(/\.png$/)
39
- # request.respond(body: File.read('myfile.png'))
40
- # else
41
- # request.continue
39
+ # request.url = 'https://upload.wikimedia.org/wikipedia/commons/d/d5/Selenium_Logo.png'
42
40
  # end
41
+ # continue.call(request)
43
42
  # end
44
43
  #
45
- # @param [#call] block which is called when request is interecepted
46
- # @yieldparam [DevTools::Request]
44
+ # @example Log responses and pass through
45
+ # driver.intercept do |request, &continue|
46
+ # continue.call(request) do |response|
47
+ # puts "#{response.code} #{response.body}"
48
+ # end
49
+ # end
50
+ #
51
+ # @example Mutate specific response
52
+ # driver.intercept do |request, &continue|
53
+ # continue.call(request) do |response|
54
+ # response.body << 'Added by Selenium!' if request.url.include?('/myurl')
55
+ # end
56
+ # end
57
+ #
58
+ # @param [Proc] block which is called when request is intercepted
59
+ # @yieldparam [DevTools::Request] request
60
+ # @yieldparam [Proc] continue block which proceeds with the request and optionally yields response
47
61
  #
48
62
 
49
- def intercept
63
+ def intercept(&block)
50
64
  devtools.network.set_cache_disabled(cache_disabled: true)
51
65
  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')
66
+ id = params['requestId']
67
+ if params.key?('responseStatusCode') || params.key?('responseErrorReason')
68
+ intercept_response(id, params, &pending_response_requests.delete(id))
69
+ else
70
+ intercept_request(id, params, &block)
71
+ end
72
+ end
73
+ devtools.fetch.enable(patterns: [{requestStage: 'Request'}, {requestStage: 'Response'}])
74
+ end
75
+
76
+ private
77
+
78
+ def pending_response_requests
79
+ @pending_response_requests ||= {}
80
+ end
81
+
82
+ def intercept_request(id, params, &block)
83
+ original = DevTools::Request.from(id, params)
84
+ mutable = DevTools::Request.from(id, params)
85
+
86
+ block.call(mutable) do |&continue| # rubocop:disable Performance/RedundantBlockCall
87
+ pending_response_requests[id] = continue
88
+
89
+ if original == mutable
90
+ devtools.fetch.continue_request(request_id: id)
91
+ else
92
+ devtools.fetch.continue_request(
93
+ request_id: id,
94
+ url: mutable.url,
95
+ method: mutable.method,
96
+ post_data: mutable.post_data,
97
+ headers: mutable.headers.map do |k, v|
98
+ {name: k, value: v}
99
+ end
100
+ )
101
+ end
102
+ end
103
+ end
104
+
105
+ def intercept_response(id, params)
106
+ return devtools.fetch.continue_request(request_id: id) unless block_given?
107
+
108
+ body = fetch_response_body(id)
109
+ original = DevTools::Response.from(id, body, params)
110
+ mutable = DevTools::Response.from(id, body, params)
111
+ yield mutable
112
+
113
+ if original == mutable
114
+ devtools.fetch.continue_request(request_id: id)
115
+ else
116
+ devtools.fetch.fulfill_request(
117
+ request_id: id,
118
+ body: (Base64.strict_encode64(mutable.body) if mutable.body),
119
+ response_code: mutable.code,
120
+ response_headers: mutable.headers.map do |k, v|
121
+ {name: k, value: v}
122
+ end
58
123
  )
59
- yield request
60
124
  end
61
- devtools.fetch.enable
62
125
  end
63
126
 
127
+ def fetch_response_body(id)
128
+ devtools.fetch.get_response_body(request_id: id).dig('result', 'body')
129
+ rescue Error::WebDriverError
130
+ # CDP fails to get body on certain responses (301) and raises:
131
+ # Can only get response body on requests captured after headers received.
132
+ end
64
133
  end # HasNetworkInterception
65
134
  end # DriverExtensions
66
135
  end # WebDriver
@@ -23,26 +23,26 @@ module Selenium
23
23
  module HasPermissions
24
24
 
25
25
  #
26
- # Returns permissions.
26
+ # Set one permission.
27
27
  #
28
- # @return [Hash]
28
+ # @param [String] name which permission to set
29
+ # @param [String] value what to set the permission to
29
30
  #
30
31
 
31
- def permissions
32
- @bridge.permissions
32
+ def add_permission(name, value)
33
+ @bridge.set_permission(name, value)
33
34
  end
34
35
 
35
36
  #
36
- # Sets permissions.
37
+ # Set multiple permissions.
37
38
  #
38
- # @example
39
- # driver.permissions = {'getUserMedia' => true}
40
- #
41
- # @param [Hash<Symbol, Boolean>] permissions
39
+ # @param [Hash] opt key/value pairs to set permissions
42
40
  #
43
41
 
44
- def permissions=(permissions)
45
- @bridge.permissions = permissions
42
+ def add_permissions(opt)
43
+ opt.each do |key, value|
44
+ @bridge.set_permission(key, value)
45
+ end
46
46
  end
47
47
 
48
48
  end # HasPermissions
@@ -0,0 +1,77 @@
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 HasPinnedScripts
24
+
25
+ #
26
+ # Returns the list of all pinned scripts.
27
+ #
28
+ # @return [Array<DevTools::PinnedScript>]
29
+ #
30
+
31
+ def pinned_scripts
32
+ @pinned_scripts ||= []
33
+ end
34
+
35
+ #
36
+ # Pins JavaScript snippet that is available during the whole
37
+ # session on every page. This allows to store and call
38
+ # scripts without sending them over the wire every time.
39
+ #
40
+ # @example
41
+ # script = driver.pin_script('return window.location.href')
42
+ # driver.execute_script(script)
43
+ # # navigate to a new page
44
+ # driver.execute_script(script)
45
+ #
46
+ # @param [String] script
47
+ # @return [DevTools::PinnedScript]
48
+ #
49
+
50
+ def pin_script(script)
51
+ script = DevTools::PinnedScript.new(script)
52
+ pinned_scripts << script
53
+
54
+ devtools.page.enable
55
+ devtools.runtime.evaluate(expression: script.callable)
56
+ response = devtools.page.add_script_to_evaluate_on_new_document(source: script.callable)
57
+ script.devtools_identifier = response.dig('result', 'identifier')
58
+
59
+ script
60
+ end
61
+
62
+ #
63
+ # Unpins script making it undefined for the subsequent calls.
64
+ #
65
+ # @param [DevTools::PinnedScript]
66
+ #
67
+
68
+ def unpin_script(script)
69
+ devtools.runtime.evaluate(expression: script.remove)
70
+ devtools.page.remove_script_to_evaluate_on_new_document(identifier: script.devtools_identifier)
71
+ pinned_scripts.delete(script)
72
+ end
73
+
74
+ end # HasPinnedScripts
75
+ end # DriverExtensions
76
+ end # WebDriver
77
+ end # Selenium
@@ -34,7 +34,7 @@ module Selenium
34
34
 
35
35
  def save_print_page(path, **options)
36
36
  File.open(path, 'wb') do |file|
37
- content = Base64.decode64 print_page(options)
37
+ content = Base64.decode64 print_page(**options)
38
38
  file << content
39
39
  end
40
40
  end
@@ -143,7 +143,7 @@ module Selenium
143
143
  #
144
144
 
145
145
  def dom_attribute(name)
146
- bridge.element_dom_attribute self, name
146
+ bridge.element_dom_attribute @id, name
147
147
  end
148
148
 
149
149
  #
@@ -157,7 +157,7 @@ module Selenium
157
157
  #
158
158
 
159
159
  def property(name)
160
- bridge.element_property self, name
160
+ bridge.element_property @id, name
161
161
  end
162
162
 
163
163
  #
@@ -167,7 +167,7 @@ module Selenium
167
167
  #
168
168
 
169
169
  def aria_role
170
- bridge.element_aria_role self
170
+ bridge.element_aria_role @id
171
171
  end
172
172
 
173
173
  #
@@ -177,7 +177,7 @@ module Selenium
177
177
  #
178
178
 
179
179
  def accessible_name
180
- bridge.element_aria_label self
180
+ bridge.element_aria_label @id
181
181
  end
182
182
 
183
183
  #
@@ -317,6 +317,16 @@ module Selenium
317
317
  bridge.element_size @id
318
318
  end
319
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
+
320
330
  #-------------------------------- sugar --------------------------------
321
331
 
322
332
  #
@@ -336,14 +346,13 @@ module Selenium
336
346
  #
337
347
  alias_method :[], :attribute
338
348
 
339
- #
340
- # for SearchContext and execute_script
341
349
  #
342
350
  # @api private
351
+ # @see SearchContext
343
352
  #
344
353
 
345
354
  def ref
346
- @id
355
+ [:element, @id]
347
356
  end
348
357
 
349
358
  #
@@ -379,7 +388,7 @@ module Selenium
379
388
  end
380
389
 
381
390
  def screenshot
382
- bridge.element_screenshot(self)
391
+ bridge.element_screenshot(@id)
383
392
  end
384
393
  end # Element
385
394
  end # WebDriver
@@ -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
  #
@@ -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
@@ -104,25 +104,19 @@ module Selenium
104
104
  @timeouts ||= Timeouts.new(@bridge)
105
105
  end
106
106
 
107
- #
108
- # @api beta This API may be changed or removed in a future release.
109
- #
110
-
111
107
  def logs
112
108
  WebDriver.logger.deprecate('Manager#logs', 'Chrome::Driver#logs')
113
109
  @logs ||= Logs.new(@bridge)
114
110
  end
115
111
 
116
112
  #
117
- # Create a new top-level browsing context
118
- # https://w3c.github.io/webdriver/#new-window
119
113
  # @param type [Symbol] Supports two values: :tab and :window.
120
- # Use :tab if you'd like the new window to share an OS-level window
121
- # with the current browsing context.
122
- # Use :window otherwise
123
114
  # @return [String] The value of the window handle
124
115
  #
125
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
126
120
  case type
127
121
  when :tab, :window
128
122
  result = @bridge.new_window(type)
@@ -137,10 +131,6 @@ module Selenium
137
131
  end
138
132
  end
139
133
 
140
- #
141
- # @api beta This API may be changed or removed in a future release.
142
- #
143
-
144
134
  def window
145
135
  @window ||= Window.new(@bridge)
146
136
  end
@@ -21,7 +21,8 @@ module Selenium
21
21
  module WebDriver
22
22
  class Options
23
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
24
+ set_window_rect timeouts unhandled_prompt_behavior strict_file_interactability
25
+ web_socket_url].freeze
25
26
 
26
27
  class << self
27
28
  attr_reader :driver_path
@@ -90,7 +91,8 @@ module Selenium
90
91
  # @param [Boolean, String, Integer] value Value of the option
91
92
  #
92
93
 
93
- def add_option(name, value)
94
+ def add_option(name, value = nil)
95
+ @options[name.keys.first] = name.values.first if value.nil? && name.is_a?(Hash)
94
96
  @options[name] = value
95
97
  end
96
98
 
@@ -123,10 +125,14 @@ module Selenium
123
125
 
124
126
  private
125
127
 
128
+ def w3c?(key)
129
+ W3C_OPTIONS.include?(key) || key.to_s.include?(':')
130
+ end
131
+
126
132
  def process_w3c_options(options)
127
- w3c_options = options.select { |key, _val| W3C_OPTIONS.include?(key) }
133
+ w3c_options = options.select { |key, _val| w3c?(key) }
128
134
  w3c_options[:unhandled_prompt_behavior] &&= w3c_options[:unhandled_prompt_behavior]&.to_s&.tr('_', ' ')
129
- options.delete_if { |key, _val| W3C_OPTIONS.include?(key) }
135
+ options.delete_if { |key, _val| w3c?(key) }
130
136
  w3c_options
131
137
  end
132
138
 
@@ -173,6 +179,8 @@ module Selenium
173
179
  def camel_case(str)
174
180
  str.gsub(/_([a-z])/) { Regexp.last_match(1).upcase }
175
181
  end
176
- end # Options
182
+ end
183
+
184
+ # Options
177
185
  end # WebDriver
178
186
  end # Selenium
@@ -0,0 +1,87 @@
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 ShadowRoot
23
+ ROOT_KEY = 'shadow-6066-11e4-a52e-4f735466cecf'
24
+
25
+ include SearchContext
26
+
27
+ #
28
+ # Creates a new shadow root
29
+ #
30
+ # @api private
31
+ #
32
+
33
+ def initialize(bridge, id)
34
+ @bridge = bridge
35
+ @id = id
36
+ end
37
+
38
+ def inspect
39
+ format '#<%<class>s:0x%<hash>x id=%<id>s>', class: self.class, hash: hash * 2, id: @id.inspect
40
+ end
41
+
42
+ def ==(other)
43
+ other.is_a?(self.class) && ref == other.ref
44
+ end
45
+ alias_method :eql?, :==
46
+
47
+ def hash
48
+ @id.hash ^ @bridge.hash
49
+ end
50
+
51
+ #
52
+ # @api private
53
+ # @see SearchContext
54
+ #
55
+
56
+ def ref
57
+ [:shadow_root, @id]
58
+ end
59
+
60
+ #
61
+ # Convert to a ShadowRoot JSON Object for transmission over the wire.
62
+ # @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#basic-terms-and-concepts
63
+ #
64
+ # @api private
65
+ #
66
+
67
+ def to_json(*)
68
+ JSON.generate as_json
69
+ end
70
+
71
+ #
72
+ # For Rails 3 - http://jonathanjulian.com/2010/04/rails-to_json-or-as_json/
73
+ #
74
+ # @api private
75
+ #
76
+
77
+ def as_json(*)
78
+ {ROOT_KEY => @id}
79
+ end
80
+
81
+ private
82
+
83
+ attr_reader :bridge
84
+
85
+ end # ShadowRoot
86
+ end # WebDriver
87
+ end # Selenium
@@ -65,26 +65,37 @@ module Selenium
65
65
  arr << Errno::EALREADY if Platform.wsl?
66
66
  }.freeze
67
67
 
68
- def listening?
69
- addr = Socket.getaddrinfo(@host, @port, Socket::AF_INET, Socket::SOCK_STREAM)
70
- sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
71
- sockaddr = Socket.pack_sockaddr_in(@port, addr[0][3])
72
-
73
- begin
74
- sock.connect_nonblock sockaddr
75
- rescue Errno::EINPROGRESS
76
- retry if socket_writable?(sock) && conn_completed?(sock)
77
- raise Errno::ECONNREFUSED
78
- rescue *CONNECTED_ERRORS
79
- # yay!
68
+ if Platform.jruby?
69
+ # we use a plain TCPSocket here since JRuby has issues closing socket
70
+ # see https://github.com/jruby/jruby/issues/5709
71
+ def listening?
72
+ TCPSocket.new(@host, @port).close
73
+ true
74
+ rescue *NOT_CONNECTED_ERRORS
75
+ false
76
+ end
77
+ else
78
+ def listening?
79
+ addr = Socket.getaddrinfo(@host, @port, Socket::AF_INET, Socket::SOCK_STREAM)
80
+ sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
81
+ sockaddr = Socket.pack_sockaddr_in(@port, addr[0][3])
82
+
83
+ begin
84
+ sock.connect_nonblock sockaddr
85
+ rescue Errno::EINPROGRESS
86
+ retry if socket_writable?(sock) && conn_completed?(sock)
87
+ raise Errno::ECONNREFUSED
88
+ rescue *CONNECTED_ERRORS
89
+ # yay!
90
+ end
91
+
92
+ sock.close
93
+ true
94
+ rescue *NOT_CONNECTED_ERRORS
95
+ sock&.close
96
+ WebDriver.logger.debug("polling for socket on #{[@host, @port].inspect}")
97
+ false
80
98
  end
81
-
82
- sock.close
83
- true
84
- rescue *NOT_CONNECTED_ERRORS
85
- sock&.close
86
- WebDriver.logger.debug("polling for socket on #{[@host, @port].inspect}")
87
- false
88
99
  end
89
100
 
90
101
  def socket_writable?(sock)