selenium-webdriver 4.32.0 → 4.40.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +63 -5
  3. data/Gemfile +1 -1
  4. data/LICENSE +1 -1
  5. data/NOTICE +1 -1
  6. data/README.md +1 -1
  7. data/bin/linux/selenium-manager +0 -0
  8. data/bin/macos/selenium-manager +0 -0
  9. data/bin/windows/selenium-manager.exe +0 -0
  10. data/lib/selenium/server.rb +29 -4
  11. data/lib/selenium/webdriver/atoms/findElements.js +63 -49
  12. data/lib/selenium/webdriver/atoms/getAttribute.js +17 -6
  13. data/lib/selenium/webdriver/atoms/isDisplayed.js +34 -23
  14. data/lib/selenium/webdriver/atoms.rb +2 -2
  15. data/lib/selenium/webdriver/bidi/browser.rb +71 -0
  16. data/lib/selenium/webdriver/bidi/browsing_context.rb +3 -2
  17. data/lib/selenium/webdriver/bidi/log_handler.rb +5 -0
  18. data/lib/selenium/webdriver/bidi/network/cookies.rb +9 -8
  19. data/lib/selenium/webdriver/bidi/network/credentials.rb +4 -0
  20. data/lib/selenium/webdriver/bidi/network/headers.rb +4 -0
  21. data/lib/selenium/webdriver/bidi/network/intercepted_auth.rb +4 -0
  22. data/lib/selenium/webdriver/bidi/network/intercepted_item.rb +4 -0
  23. data/lib/selenium/webdriver/bidi/network/intercepted_request.rb +20 -4
  24. data/lib/selenium/webdriver/bidi/network/intercepted_response.rb +24 -6
  25. data/lib/selenium/webdriver/bidi/network/url_pattern.rb +5 -1
  26. data/lib/selenium/webdriver/bidi/network.rb +18 -9
  27. data/lib/selenium/webdriver/bidi/session.rb +4 -0
  28. data/lib/selenium/webdriver/bidi.rb +1 -1
  29. data/lib/selenium/webdriver/chrome/driver.rb +4 -3
  30. data/lib/selenium/webdriver/chromium/options.rb +2 -2
  31. data/lib/selenium/webdriver/common/child_process.rb +4 -0
  32. data/lib/selenium/webdriver/common/driver.rb +8 -13
  33. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +7 -1
  34. data/lib/selenium/webdriver/common/error.rb +10 -3
  35. data/lib/selenium/webdriver/common/interactions/key_actions.rb +4 -4
  36. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +10 -10
  37. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +6 -6
  38. data/lib/selenium/webdriver/common/keys.rb +9 -0
  39. data/lib/selenium/webdriver/common/local_driver.rb +11 -1
  40. data/lib/selenium/webdriver/common/manager.rb +2 -0
  41. data/lib/selenium/webdriver/common/options.rb +30 -11
  42. data/lib/selenium/webdriver/common/platform.rb +1 -3
  43. data/lib/selenium/webdriver/common/proxy.rb +1 -0
  44. data/lib/selenium/webdriver/common/service.rb +10 -10
  45. data/lib/selenium/webdriver/common/service_manager.rb +29 -3
  46. data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
  47. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +4 -4
  48. data/lib/selenium/webdriver/common/wait.rb +5 -2
  49. data/lib/selenium/webdriver/common/websocket_connection.rb +73 -37
  50. data/lib/selenium/webdriver/common/zipper.rb +12 -2
  51. data/lib/selenium/webdriver/devtools.rb +1 -1
  52. data/lib/selenium/webdriver/edge/driver.rb +4 -3
  53. data/lib/selenium/webdriver/firefox/driver.rb +4 -3
  54. data/lib/selenium/webdriver/firefox/service.rb +2 -2
  55. data/lib/selenium/webdriver/ie/driver.rb +4 -3
  56. data/lib/selenium/webdriver/remote/bidi_bridge.rb +4 -2
  57. data/lib/selenium/webdriver/remote/bridge.rb +12 -4
  58. data/lib/selenium/webdriver/remote/driver.rb +2 -2
  59. data/lib/selenium/webdriver/remote/http/common.rb +32 -0
  60. data/lib/selenium/webdriver/safari/driver.rb +4 -3
  61. data/lib/selenium/webdriver/support/block_event_listener.rb +2 -2
  62. data/lib/selenium/webdriver/support/event_firing_bridge.rb +3 -3
  63. data/lib/selenium/webdriver/version.rb +1 -1
  64. data/lib/selenium/webdriver.rb +5 -6
  65. data/selenium-webdriver.gemspec +2 -2
  66. metadata +7 -12
  67. data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +0 -35
  68. data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +0 -35
  69. data/lib/selenium/webdriver/bidi/log/filter_by.rb +0 -40
  70. data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +0 -33
  71. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +0 -33
  72. data/lib/selenium/webdriver/bidi/log_inspector.rb +0 -147
@@ -68,9 +68,9 @@ module Selenium
68
68
  # @api private
69
69
  #
70
70
 
71
- def initialize(bridge: nil, listener: nil, **opts)
71
+ def initialize(bridge: nil, listener: nil, **)
72
72
  @devtools = nil
73
- bridge ||= create_bridge(**opts)
73
+ bridge ||= create_bridge(**)
74
74
  @bridge = listener ? Support::EventFiringBridge.new(bridge, listener) : bridge
75
75
  add_extensions(@bridge.browser)
76
76
  end
@@ -131,8 +131,8 @@ module Selenium
131
131
  # @see ActionBuilder
132
132
  #
133
133
 
134
- def action(**opts)
135
- bridge.action(**opts)
134
+ def action(**)
135
+ bridge.action(**)
136
136
  end
137
137
 
138
138
  #
@@ -225,8 +225,8 @@ module Selenium
225
225
  # The value returned from the script.
226
226
  #
227
227
 
228
- def execute_script(script, *args)
229
- bridge.execute_script(script, *args)
228
+ def execute_script(script, *)
229
+ bridge.execute_script(script, *)
230
230
  end
231
231
 
232
232
  # Execute an asynchronous piece of JavaScript in the context of the
@@ -244,8 +244,8 @@ module Selenium
244
244
  # @return [WebDriver::Element,Integer,Float,Boolean,NilClass,String,Array]
245
245
  #
246
246
 
247
- def execute_async_script(script, *args)
248
- bridge.execute_async_script(script, *args)
247
+ def execute_async_script(script, *)
248
+ bridge.execute_async_script(script, *)
249
249
  end
250
250
 
251
251
  #
@@ -326,11 +326,6 @@ module Selenium
326
326
  end
327
327
  end
328
328
 
329
- def service_url(service)
330
- @service_manager = service.launch
331
- @service_manager.uri
332
- end
333
-
334
329
  def screenshot
335
330
  bridge.screenshot
336
331
  end
@@ -39,7 +39,13 @@ module Selenium
39
39
 
40
40
  begin
41
41
  Zip::File.open("#{file_name}.zip") do |zip|
42
- zip.each { |entry| zip.extract(entry, "#{target_directory}#{file_name}") }
42
+ zip.each do |entry|
43
+ if Zipper::RUBYZIP_V3
44
+ zip.extract(entry, file_name, destination_directory: target_directory)
45
+ else
46
+ zip.extract(entry, "#{target_directory}#{file_name}")
47
+ end
48
+ end
43
49
  end
44
50
  ensure
45
51
  FileUtils.rm_f("#{file_name}.zip")
@@ -38,9 +38,9 @@ module Selenium
38
38
  ERROR_URL = 'https://www.selenium.dev/documentation/webdriver/troubleshooting/errors'
39
39
 
40
40
  URLS = {
41
- NoSuchElementError: "#{ERROR_URL}#no-such-element-exception",
42
- StaleElementReferenceError: "#{ERROR_URL}#stale-element-reference-exception",
43
- InvalidSelectorError: "#{ERROR_URL}#invalid-selector-exception",
41
+ NoSuchElementError: "#{ERROR_URL}#nosuchelementexception",
42
+ StaleElementReferenceError: "#{ERROR_URL}#staleelementreferenceexception",
43
+ InvalidSelectorError: "#{ERROR_URL}#invalidselectorexception",
44
44
  NoSuchDriverError: "#{ERROR_URL}/driver_location"
45
45
  }.freeze
46
46
 
@@ -119,6 +119,13 @@ module Selenium
119
119
 
120
120
  class NoSuchWindowError < WebDriverError; end
121
121
 
122
+ #
123
+ # A command to find a devtools target could not be satisfied because
124
+ # the target could not be found.
125
+ #
126
+
127
+ class NoSuchTargetError < WebDriverError; end
128
+
122
129
  #
123
130
  # The element does not have a shadow root.
124
131
  #
@@ -44,8 +44,8 @@ module Selenium
44
44
  # @return [ActionBuilder] A self reference
45
45
  #
46
46
 
47
- def key_down(*args, device: nil)
48
- key_action(*args, action: :create_key_down, device: device)
47
+ def key_down(*, device: nil)
48
+ key_action(*, action: :create_key_down, device: device)
49
49
  end
50
50
 
51
51
  #
@@ -71,8 +71,8 @@ module Selenium
71
71
  # @return [ActionBuilder] A self reference
72
72
  #
73
73
 
74
- def key_up(*args, device: nil)
75
- key_action(*args, action: :create_key_up, device: device)
74
+ def key_up(*, device: nil)
75
+ key_action(*, action: :create_key_up, device: device)
76
76
  end
77
77
 
78
78
  #
@@ -46,8 +46,8 @@ module Selenium
46
46
  # @return [ActionBuilder] A self reference.
47
47
  #
48
48
 
49
- def pointer_down(button = :left, device: nil, **opts)
50
- button_action(button, :create_pointer_down, device: device, **opts)
49
+ def pointer_down(button = :left, device: nil, **)
50
+ button_action(button, :create_pointer_down, device: device, **)
51
51
  end
52
52
 
53
53
  #
@@ -63,8 +63,8 @@ module Selenium
63
63
  # @return [ActionBuilder] A self reference.
64
64
  #
65
65
 
66
- def pointer_up(button = :left, device: nil, **opts)
67
- button_action(button, :create_pointer_up, device: device, **opts)
66
+ def pointer_up(button = :left, device: nil, **)
67
+ button_action(button, :create_pointer_up, device: device, **)
68
68
  end
69
69
 
70
70
  #
@@ -122,13 +122,13 @@ module Selenium
122
122
  # @raise [MoveTargetOutOfBoundsError] if the provided offset is outside the document's boundaries.
123
123
  #
124
124
 
125
- def move_by(right_by, down_by, device: nil, duration: default_move_duration, **opts)
125
+ def move_by(right_by, down_by, device: nil, duration: default_move_duration, **)
126
126
  pointer = pointer_input(device)
127
127
  pointer.create_pointer_move(duration: duration,
128
128
  x: Integer(right_by),
129
129
  y: Integer(down_by),
130
130
  origin: Interactions::PointerMove::POINTER,
131
- **opts)
131
+ **)
132
132
  tick(pointer)
133
133
  self
134
134
  end
@@ -150,13 +150,13 @@ module Selenium
150
150
  # @raise [MoveTargetOutOfBoundsError] if the provided x or y value is outside the document's boundaries.
151
151
  #
152
152
 
153
- def move_to_location(x, y, device: nil, duration: default_move_duration, **opts)
153
+ def move_to_location(x, y, device: nil, duration: default_move_duration, **)
154
154
  pointer = pointer_input(device)
155
155
  pointer.create_pointer_move(duration: duration,
156
156
  x: Integer(x),
157
157
  y: Integer(y),
158
158
  origin: Interactions::PointerMove::VIEWPORT,
159
- **opts)
159
+ **)
160
160
  tick(pointer)
161
161
  self
162
162
  end
@@ -336,9 +336,9 @@ module Selenium
336
336
 
337
337
  private
338
338
 
339
- def button_action(button, action, device: nil, **opts)
339
+ def button_action(button, action, device: nil, **)
340
340
  pointer = pointer_input(device)
341
- pointer.send(action, button, **opts)
341
+ pointer.send(action, button, **)
342
342
  tick(pointer)
343
343
  self
344
344
  end
@@ -49,16 +49,16 @@ module Selenium
49
49
  KIND[pointer]
50
50
  end
51
51
 
52
- def create_pointer_move(duration: 0, x: 0, y: 0, origin: nil, **opts)
53
- add_action(PointerMove.new(self, duration, x, y, origin: origin, **opts))
52
+ def create_pointer_move(duration: 0, x: 0, y: 0, origin: nil, **)
53
+ add_action(PointerMove.new(self, duration, x, y, origin: origin, **))
54
54
  end
55
55
 
56
- def create_pointer_down(button, **opts)
57
- add_action(PointerPress.new(self, :down, button, **opts))
56
+ def create_pointer_down(button, **)
57
+ add_action(PointerPress.new(self, :down, button, **))
58
58
  end
59
59
 
60
- def create_pointer_up(button, **opts)
61
- add_action(PointerPress.new(self, :up, button, **opts))
60
+ def create_pointer_up(button, **)
61
+ add_action(PointerPress.new(self, :up, button, **))
62
62
  end
63
63
 
64
64
  def create_pointer_cancel
@@ -75,6 +75,13 @@ module Selenium
75
75
  subtract: "\ue027",
76
76
  decimal: "\ue028",
77
77
  divide: "\ue029",
78
+ numpad_multiply: "\ue024",
79
+ numpad_add: "\ue025",
80
+ numpad_comma: "\ue026",
81
+ numpad_subtract: "\ue027",
82
+ numpad_decimal: "\ue028",
83
+ numpad_divide: "\ue029",
84
+ numpad_enter: "\ue007",
78
85
  f1: "\ue031",
79
86
  f2: "\ue032",
80
87
  f3: "\ue033",
@@ -95,6 +102,8 @@ module Selenium
95
102
  right_control: "\ue051",
96
103
  right_alt: "\ue052",
97
104
  right_meta: "\ue053",
105
+ options: "\ue052",
106
+ function: "\ue051", # macOS Function key, same as right_control
98
107
  numpad_page_up: "\ue054",
99
108
  numpad_page_down: "\ue055",
100
109
  numpad_end: "\ue056",
@@ -27,7 +27,17 @@ module Selenium
27
27
  caps = process_options(options, service)
28
28
  url = service_url(service)
29
29
 
30
- [caps, url]
30
+ begin
31
+ yield(caps, url) if block_given?
32
+ rescue Selenium::WebDriver::Error::WebDriverError
33
+ @service_manager&.stop
34
+ raise
35
+ end
36
+ end
37
+
38
+ def service_url(service)
39
+ @service_manager = service.launch
40
+ @service_manager.uri
31
41
  end
32
42
 
33
43
  def process_options(options, service)
@@ -79,6 +79,8 @@ module Selenium
79
79
  #
80
80
 
81
81
  def delete_cookie(name)
82
+ raise ArgumentError, 'Cookie name cannot be null or empty' if name.nil? || name.to_s.strip.empty?
83
+
82
84
  @bridge.delete_cookie name
83
85
  end
84
86
 
@@ -29,26 +29,26 @@ module Selenium
29
29
  class << self
30
30
  attr_reader :driver_path
31
31
 
32
- def chrome(**opts)
33
- Chrome::Options.new(**opts)
32
+ def chrome(**)
33
+ Chrome::Options.new(**)
34
34
  end
35
35
 
36
- def firefox(**opts)
37
- Firefox::Options.new(**opts)
36
+ def firefox(**)
37
+ Firefox::Options.new(**)
38
38
  end
39
39
 
40
- def ie(**opts)
41
- IE::Options.new(**opts)
40
+ def ie(**)
41
+ IE::Options.new(**)
42
42
  end
43
43
  alias internet_explorer ie
44
44
 
45
- def edge(**opts)
46
- Edge::Options.new(**opts)
45
+ def edge(**)
46
+ Edge::Options.new(**)
47
47
  end
48
48
  alias microsoftedge edge
49
49
 
50
- def safari(**opts)
51
- Safari::Options.new(**opts)
50
+ def safari(**)
51
+ Safari::Options.new(**)
52
52
  end
53
53
 
54
54
  def set_capabilities
@@ -71,6 +71,8 @@ module Selenium
71
71
  def initialize(**opts)
72
72
  self.class.set_capabilities
73
73
 
74
+ opts[:web_socket_url] = opts.delete(:bidi) if opts.key?(:bidi)
75
+
74
76
  @options = opts
75
77
  @options[:browser_name] = self.class::BROWSER
76
78
  end
@@ -91,6 +93,14 @@ module Selenium
91
93
  @options[name] = value
92
94
  end
93
95
 
96
+ def enable_bidi!
97
+ @options[:web_socket_url] = true
98
+ end
99
+
100
+ def bidi?
101
+ !!@options[:web_socket_url]
102
+ end
103
+
94
104
  def ==(other)
95
105
  return false unless other.is_a? self.class
96
106
 
@@ -131,11 +141,20 @@ module Selenium
131
141
 
132
142
  def process_w3c_options(options)
133
143
  w3c_options = options.select { |key, val| w3c?(key) && !val.nil? }
134
- w3c_options[:unhandled_prompt_behavior] &&= w3c_options[:unhandled_prompt_behavior]&.to_s&.tr('_', ' ')
144
+ w3c_options[:unhandled_prompt_behavior] &&=
145
+ process_unhandled_prompt_behavior_value(w3c_options[:unhandled_prompt_behavior])
135
146
  options.delete_if { |key, _val| w3c?(key) }
136
147
  w3c_options
137
148
  end
138
149
 
150
+ def process_unhandled_prompt_behavior_value(value)
151
+ if value.is_a?(Hash)
152
+ value.transform_values { |v| process_unhandled_prompt_behavior_value(v) }
153
+ else
154
+ value&.to_s&.tr('_', ' ')
155
+ end
156
+ end
157
+
139
158
  def process_browser_options(_browser_options)
140
159
  nil
141
160
  end
@@ -51,9 +51,7 @@ module Selenium
51
51
  end
52
52
 
53
53
  def ci
54
- if ENV['TRAVIS']
55
- :travis
56
- elsif ENV['JENKINS']
54
+ if ENV['JENKINS']
57
55
  :jenkins
58
56
  elsif ENV['APPVEYOR']
59
57
  :appveyor
@@ -77,6 +77,7 @@ module Selenium
77
77
  alias eql? ==
78
78
 
79
79
  def ftp=(value)
80
+ WebDriver.logger.deprecate('FTP proxy support', nil, id: :ftp_proxy)
80
81
  self.type = :manual
81
82
  @ftp = value
82
83
  end
@@ -28,27 +28,27 @@ module Selenium
28
28
  class << self
29
29
  attr_reader :driver_path
30
30
 
31
- def chrome(**opts)
32
- Chrome::Service.new(**opts)
31
+ def chrome(**)
32
+ Chrome::Service.new(**)
33
33
  end
34
34
 
35
- def firefox(**opts)
36
- Firefox::Service.new(**opts)
35
+ def firefox(**)
36
+ Firefox::Service.new(**)
37
37
  end
38
38
 
39
- def ie(**opts)
40
- IE::Service.new(**opts)
39
+ def ie(**)
40
+ IE::Service.new(**)
41
41
  end
42
42
  alias internet_explorer ie
43
43
 
44
- def edge(**opts)
45
- Edge::Service.new(**opts)
44
+ def edge(**)
45
+ Edge::Service.new(**)
46
46
  end
47
47
  alias microsoftedge edge
48
48
  alias msedge edge
49
49
 
50
- def safari(**opts)
51
- Safari::Service.new(**opts)
50
+ def safari(**)
51
+ Safari::Service.new(**)
52
52
  end
53
53
 
54
54
  def driver_path=(path)
@@ -127,10 +127,36 @@ module Selenium
127
127
  end
128
128
 
129
129
  def connect_until_stable
130
- socket_poller = SocketPoller.new @host, @port, START_TIMEOUT
131
- return if socket_poller.connected?
130
+ deadline = current_time + START_TIMEOUT
132
131
 
133
- raise Error::WebDriverError, cannot_connect_error_text
132
+ loop do
133
+ error = check_connection_error
134
+ return unless error
135
+
136
+ raise Error::WebDriverError, "#{cannot_connect_error_text}: #{error}" if current_time > deadline
137
+
138
+ sleep 0.1
139
+ end
140
+ end
141
+
142
+ def check_connection_error
143
+ response = Net::HTTP.start(@host, @port, open_timeout: 0.5, read_timeout: 1) do |http|
144
+ http.get('/status', {'Connection' => 'close'})
145
+ end
146
+
147
+ return "status returned #{response.code}\n#{response.body}" unless response.is_a?(Net::HTTPSuccess)
148
+
149
+ status = JSON.parse(response.body)
150
+ ready = status['ready'] || status.dig('value', 'ready')
151
+ "driver not ready: #{response.body}" unless ready
152
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE, Errno::ETIMEDOUT,
153
+ Errno::EADDRNOTAVAIL, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout,
154
+ EOFError, SocketError, Net::HTTPBadResponse, JSON::ParserError => e
155
+ "#{e.class}: #{e.message}"
156
+ end
157
+
158
+ def current_time
159
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
134
160
  end
135
161
 
136
162
  def cannot_connect_error_text
@@ -93,7 +93,7 @@ module Selenium
93
93
  true
94
94
  rescue *NOT_CONNECTED_ERRORS
95
95
  sock&.close
96
- WebDriver.logger.debug("polling for socket on #{[@host, @port].inspect}", id: :driver_service)
96
+ WebDriver.logger.debug("polling for socket on #{[@host, @port].inspect}", id: :socket_poller)
97
97
  false
98
98
  end
99
99
  end
@@ -26,12 +26,12 @@ module Selenium
26
26
  module WebDriver
27
27
  class Credential
28
28
  class << self
29
- def resident(**opts)
30
- Credential.new(resident_credential: true, **opts)
29
+ def resident(**)
30
+ Credential.new(resident_credential: true, **)
31
31
  end
32
32
 
33
- def non_resident(**opts)
34
- Credential.new(resident_credential: false, **opts)
33
+ def non_resident(**)
34
+ Credential.new(resident_credential: false, **)
35
35
  end
36
36
 
37
37
  def encode(byte_array)
@@ -29,7 +29,7 @@ module Selenium
29
29
  # @param [Hash] opts Options for this instance
30
30
  # @option opts [Numeric] :timeout (5) Seconds to wait before timing out.
31
31
  # @option opts [Numeric] :interval (0.2) Seconds to sleep between polls.
32
- # @option opts [String] :message Exception mesage if timed out.
32
+ # @option opts [String] :message Exception message if timed out.
33
33
  # @option opts [Array, Exception] :ignore Exceptions to ignore while polling (default: Error::NoSuchElementError)
34
34
  #
35
35
 
@@ -37,7 +37,8 @@ module Selenium
37
37
  @timeout = opts.fetch(:timeout, DEFAULT_TIMEOUT)
38
38
  @interval = opts.fetch(:interval, DEFAULT_INTERVAL)
39
39
  @message = opts[:message]
40
- @ignored = Array(opts[:ignore] || Error::NoSuchElementError)
40
+ @message_provider = opts[:message_provider]
41
+ @ignored = Array(opts[:ignore] || Error::NoSuchElementError)
41
42
  end
42
43
 
43
44
  #
@@ -64,6 +65,8 @@ module Selenium
64
65
 
65
66
  msg = if @message
66
67
  @message.dup
68
+ elsif @message_provider
69
+ @message_provider.call
67
70
  else
68
71
  "timed out after #{@timeout} seconds"
69
72
  end