selenium-webdriver 4.47.0 → 4.49.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.
@@ -77,16 +77,20 @@ module Selenium
77
77
  elsif Platform.mac?
78
78
  "#{directory}/macos/selenium-manager"
79
79
  elsif Platform.linux?
80
- "#{directory}/linux/selenium-manager"
80
+ "#{directory}/#{linux_directory}/selenium-manager"
81
81
  elsif Platform.unix?
82
82
  WebDriver.logger.warn('Selenium Manager binary may not be compatible with Unix',
83
83
  id: %i[selenium_manager unix_binary])
84
- "#{directory}/linux/selenium-manager"
84
+ "#{directory}/#{linux_directory}/selenium-manager"
85
85
  else
86
86
  raise Error::WebDriverError, "unsupported platform: #{Platform.os}"
87
87
  end
88
88
  end
89
89
 
90
+ def linux_directory
91
+ RbConfig::CONFIG['host_cpu'].to_s.downcase == 'aarch64' ? 'linux-arm64' : 'linux-x86_64'
92
+ end
93
+
90
94
  def execute_command(*command)
91
95
  WebDriver.logger.debug("Executing Process #{command}", id: :selenium_manager)
92
96
 
@@ -21,6 +21,7 @@ require 'websocket'
21
21
 
22
22
  module Selenium
23
23
  module WebDriver
24
+ # @api private
24
25
  class WebSocketConnection
25
26
  CONNECTION_ERRORS = [
26
27
  Errno::ECONNRESET, # connection is aborted (browser process was killed)
@@ -35,6 +36,15 @@ module Selenium
35
36
 
36
37
  MAX_LOG_MESSAGE_SIZE = 9999
37
38
 
39
+ # websocket-ruby defaults to a 20MB limit and silently drops larger
40
+ # frames, which can stall the listener. CDP payloads (e.g. large data:
41
+ # URLs) can exceed that, so raise the ceiling for our connections.
42
+ # The gem only exposes the limit as process-global state, so it is
43
+ # raised (never lowered) and not restored on close - concurrent
44
+ # connections share the value. Other bindings rely on their own
45
+ # websocket clients' limits; this constant only affects websocket-ruby.
46
+ MAX_FRAME_SIZE = 100 * 1024 * 1024 # 100MB
47
+
38
48
  def initialize(url:)
39
49
  @callback_threads = ThreadGroup.new
40
50
 
@@ -46,26 +56,19 @@ module Selenium
46
56
  @session_id = nil
47
57
  @url = url
48
58
 
59
+ apply_frame_size_limit
49
60
  process_handshake
50
61
  @socket_thread = attach_socket_listener
51
62
  end
52
63
 
64
+ # Idempotent: the listener may already have initiated shutdown (see
65
+ # #frame_dropped?), so always close the socket and join threads rather
66
+ # than short-circuiting on @closing.
53
67
  def close
54
- @closing_mtx.synchronize do
55
- return if @closing
56
-
57
- @closing = true
58
- end
59
-
60
- begin
61
- socket.close
62
- rescue *CONNECTION_ERRORS => e
63
- WebDriver.logger.debug "WebSocket listener closed: #{e.class}: #{e.message}", id: :ws
64
- # already closed
65
- end
68
+ close_socket
66
69
 
67
70
  # Let threads unwind instead of calling exit
68
- @socket_thread&.join(0.5)
71
+ @socket_thread&.join(0.5) unless @socket_thread == Thread.current
69
72
  @callback_threads.list.each do |thread|
70
73
  thread.join(0.5)
71
74
  rescue StandardError => e
@@ -97,6 +100,9 @@ module Selenium
97
100
  end
98
101
 
99
102
  def send_cmd(**payload)
103
+ # IOError to match what writing to an already-closed socket raises
104
+ raise IOError, 'WebSocket connection is closed' if @closing
105
+
100
106
  id = next_id
101
107
  data = payload.merge(id: id)
102
108
  WebDriver.logger.debug "WebSocket -> #{data}"[...MAX_LOG_MESSAGE_SIZE], id: :ws
@@ -109,7 +115,11 @@ module Selenium
109
115
  raise e, "WebSocket is closed (#{e.class}: #{e.message})"
110
116
  end
111
117
 
112
- wait.until { @messages_mtx.synchronize { messages.delete(id) } }
118
+ wait.until do
119
+ raise IOError, 'WebSocket connection closed while waiting for a response' if @closing
120
+
121
+ @messages_mtx.synchronize { messages.delete(id) }
122
+ end
113
123
  end
114
124
 
115
125
  private
@@ -132,22 +142,51 @@ module Selenium
132
142
 
133
143
  incoming_frame << socket.readpartial(1024)
134
144
 
135
- while (frame = incoming_frame.next)
136
- break if @closing
137
-
138
- message = process_frame(frame)
139
- next unless message['method']
140
-
141
- @messages_mtx.synchronize { callbacks[message['method']].dup }.each do |callback|
142
- @callback_threads.add(callback_thread(message['params'], &callback))
143
- end
144
- end
145
+ process_incoming_frames
146
+ break if frame_dropped?
145
147
  end
146
148
  rescue *CONNECTION_ERRORS, WebSocket::Error => e
147
149
  WebDriver.logger.debug "WebSocket listener closed: #{e.class}: #{e.message}", id: :ws
148
150
  end
149
151
  end
150
152
 
153
+ def close_socket
154
+ @closing_mtx.synchronize { @closing = true }
155
+ socket.close
156
+ rescue *CONNECTION_ERRORS => e
157
+ WebDriver.logger.debug "WebSocket socket closed: #{e.class}: #{e.message}", id: :ws
158
+ end
159
+
160
+ def process_incoming_frames
161
+ while (frame = incoming_frame.next)
162
+ break if @closing
163
+
164
+ message = process_frame(frame)
165
+ next unless message['method']
166
+
167
+ @messages_mtx.synchronize { callbacks[message['method']].dup }.each do |callback|
168
+ @callback_threads.add(callback_thread(message['params'], &callback))
169
+ end
170
+ end
171
+ end
172
+
173
+ # True when the buffered frame could not be decoded (e.g. exceeds MAX_FRAME_SIZE).
174
+ # websocket-ruby swallows the error and keeps returning nil, so surface it and close
175
+ # the connection here instead of leaving a dead listener on an open socket.
176
+ def frame_dropped?
177
+ return false unless incoming_frame.error?
178
+
179
+ WebDriver.logger.error("WebSocket frame dropped (#{incoming_frame.error}); if payloads can legitimately " \
180
+ "exceed #{WebSocket.max_frame_size} bytes, set WebSocket.max_frame_size " \
181
+ 'to a higher value', id: :ws)
182
+ close_socket
183
+ true
184
+ end
185
+
186
+ def apply_frame_size_limit
187
+ WebSocket.max_frame_size = MAX_FRAME_SIZE if WebSocket.max_frame_size < MAX_FRAME_SIZE
188
+ end
189
+
151
190
  def incoming_frame
152
191
  @incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
153
192
  end
@@ -204,8 +243,7 @@ module Selenium
204
243
  end
205
244
 
206
245
  def next_id
207
- @id ||= 0
208
- @id += 1
246
+ @id = (@id || 0) + 1
209
247
  end
210
248
  end # BiDi
211
249
  end # WebDriver
@@ -19,6 +19,6 @@
19
19
 
20
20
  module Selenium
21
21
  module WebDriver
22
- VERSION = '4.47.0'
22
+ VERSION = '4.49.0'
23
23
  end # WebDriver
24
24
  end # Selenium
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selenium-webdriver
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.47.0
4
+ version: 4.49.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Rodionov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-08-10 00:00:00.000000000 Z
13
+ date: 2026-09-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: base64
@@ -271,7 +271,8 @@ files:
271
271
  - LICENSE
272
272
  - NOTICE
273
273
  - README.md
274
- - bin/linux/selenium-manager
274
+ - bin/linux-arm64/selenium-manager
275
+ - bin/linux-x86_64/selenium-manager
275
276
  - bin/macos/selenium-manager
276
277
  - bin/selenium-manager-THIRD-PARTY-NOTICES.txt
277
278
  - bin/selenium-manager.cdx.json