diff --git a/CHANGELOG.md b/CHANGELOG.md index 4578991a..6f477cd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ the browser; a failed `IO.close` is raised to the caller. ### Fixed +- A raising callback killed `Client::Subscriber`'s dispatch thread and with it every event for the rest of the + process: pages created later raised `NoSuchTargetError`, open ones lost their execution contexts. Callbacks are + now rescued one by one and reported to stderr [#470] - `create_page` raised `Failed to find browser context with id` against a browser that came up with its own startup window (browserless). That window lives in the browser's implicit context, which Chrome below 145 won't address by id. Ferrum now detects it and exposes as `Ferrum::Context#implicit?`. It's never diff --git a/lib/ferrum/client/subscriber.rb b/lib/ferrum/client/subscriber.rb index 263bd996..4e973f70 100644 --- a/lib/ferrum/client/subscriber.rb +++ b/lib/ferrum/client/subscriber.rb @@ -133,6 +133,11 @@ def call(message) @on[event]&.each_with_index do |block, index| # In case of multiple callbacks we provide current index and total block.call(params, index, total) + rescue StandardError => e + # A raising callback used to terminate the dispatch thread, and with it every + # event for the rest of the browser's life: targets stopped being registered + # and any page created later raised `NoSuchTargetError`. Report and carry on. + warn("Ferrum: #{event} callback raised #{e.class}: #{e.message}\n #{e.backtrace&.first}") end end end diff --git a/spec/client_spec.rb b/spec/client_spec.rb new file mode 100644 index 00000000..34e3de39 --- /dev/null +++ b/spec/client_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +describe Ferrum::Client do + describe "event callbacks" do + let(:remote) { Ferrum::Browser.new(base_url: base_url, timeout: 3, protocol_timeout: 3) } + + after { remote.quit } + + it "keeps dispatching events when a callback raises" do + page = remote.create_page + + expect do + remote.client.on("Target.targetCreated") { raise Ferrum::TimeoutError } + + 2.times { remote.create_page } + page.go_to("/") + end.to output(/Target.targetCreated callback raised Ferrum::TimeoutError/).to_stderr + + expect(page.body).to include("Hello world!") + end + end +end