Skip to content

Commit 746f227

Browse files
committed
Aggregate and cleanup all CDP event threads on quit
When we quit driver, there should be no leftover event callback threads. Otherwise, they would fail upon sending CDP command or waiting for its response. Now the threads are aggregated to the thread group and killed before closing the socket.
1 parent abfe9f2 commit 746f227

File tree

2 files changed

+44
-13
lines changed

2 files changed

+44
-13
lines changed

rb/lib/selenium/webdriver/devtools.rb

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class DevTools
3131
autoload :Response, 'selenium/webdriver/devtools/response'
3232

3333
def initialize(url:)
34+
@callback_threads = ThreadGroup.new
3435
@messages = []
3536
@session_id = nil
3637
@url = url
@@ -41,6 +42,7 @@ def initialize(url:)
4142
end
4243

4344
def close
45+
@callback_threads.list.each(&:exit)
4446
socket.close
4547
end
4648

@@ -93,27 +95,24 @@ def process_handshake
9395
end
9496

9597
def attach_socket_listener
96-
socket_listener = Thread.new do
98+
Thread.new do
99+
Thread.current.abort_on_exception = true
100+
Thread.current.report_on_exception = false
101+
97102
until socket.eof?
98103
incoming_frame << socket.readpartial(1024)
99104

100105
while (frame = incoming_frame.next)
101-
# Firefox will periodically fail on unparsable empty frame
102-
break if frame.to_s.empty?
103-
104-
message = JSON.parse(frame.to_s)
105-
@messages << message
106-
WebDriver.logger.debug "DevTools <- #{message}"
106+
message = process_frame(frame)
107107
next unless message['method']
108108

109+
params = message['params']
109110
callbacks[message['method']].each do |callback|
110-
callback_thread = Thread.new(message['params'], &callback)
111-
callback_thread.abort_on_exception = true
111+
@callback_threads.add(callback_thread(params, &callback))
112112
end
113113
end
114114
end
115115
end
116-
socket_listener.abort_on_exception = true
117116
end
118117

119118
def start_session
@@ -127,6 +126,34 @@ def incoming_frame
127126
@incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
128127
end
129128

129+
def process_frame(frame)
130+
message = frame.to_s
131+
132+
# Firefox will periodically fail on unparsable empty frame
133+
return {} if message.empty?
134+
135+
message = JSON.parse(message)
136+
@messages << message
137+
WebDriver.logger.debug "DevTools <- #{message}"
138+
139+
message
140+
end
141+
142+
def callback_thread(params)
143+
Thread.new do
144+
Thread.current.abort_on_exception = true
145+
146+
# We might end up blocked forever when we have an error in event.
147+
# For example, if network interception event raises error,
148+
# the browser will keep waiting for the request to be proceeded
149+
# before returning back to the original thread. In this case,
150+
# we should at least print the error.
151+
Thread.current.report_on_exception = true
152+
153+
yield params
154+
end
155+
end
156+
130157
def wait
131158
@wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
132159
end

rb/spec/integration/selenium/webdriver/devtools_spec.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
module Selenium
2323
module WebDriver
2424
describe DevTools, exclusive: {browser: %i[chrome edge firefox_nightly]} do
25-
before(:all) { quit_driver }
26-
27-
after { quit_driver }
25+
after { reset_driver! }
2826

2927
it 'sends commands' do
3028
driver.devtools.page.navigate(url: url_for('xhtmlTest.html'))
@@ -42,6 +40,12 @@ module WebDriver
4240
expect(callback).to have_received(:call).at_least(:once)
4341
end
4442

43+
it 'propagates errors in events' do
44+
driver.devtools.page.enable
45+
driver.devtools.page.on(:load_event_fired) { raise "This is fine!" }
46+
expect { driver.navigate.to url_for('xhtmlTest.html') }.to raise_error(RuntimeError, "This is fine!")
47+
end
48+
4549
context 'authentication', except: {browser: :firefox_nightly,
4650
reason: 'Fetch.enable is not yet supported'} do
4751
let(:username) { SpecSupport::RackServer::TestApp::BASIC_AUTH_CREDENTIALS.first }

0 commit comments

Comments
 (0)