From 6075fbeb09b7011c8f115803a061ce9836cdc52a Mon Sep 17 00:00:00 2001 From: Dmitry Vorotilin Date: Fri, 30 Oct 2026 15:05:34 +0300 Subject: [PATCH] ref(page): handle :pending_connection_errors in Cuprite instead of passing it to Ferrum Ferrum deprecates the browser option and PendingConnectionsError, so Cuprite keeps the option out of Ferrum's options and raises its own PendingConnectionsError when go_to returns nil on a timeout. It inherits from Ferrum's while that exists, so rescuing it keeps working with a warning, and from Ferrum::StatusError once it's removed. --- CHANGELOG.md | 3 +++ docs/2-customization.md | 3 ++- lib/capybara/cuprite/browser.rb | 3 ++- lib/capybara/cuprite/driver.rb | 2 -- lib/capybara/cuprite/errors.rb | 31 +++++++++++++++++++++++++++++++ lib/capybara/cuprite/options.rb | 3 ++- lib/capybara/cuprite/page.rb | 10 ++++++++++ spec/features/driver_spec.rb | 20 ++++++++++++++++++++ 8 files changed, 70 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20685259..36a939c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ `current_title` deprecated in Ferrum 0.21 [rubycdp/ferrum#687] - `:js_errors` is handled by Cuprite instead of being passed to Ferrum, which deprecates it. Behavior is the same [rubycdp/ferrum#688] +- `:pending_connection_errors` is handled by Cuprite instead of being passed to Ferrum, which deprecates it. `visit` + raises `Capybara::Cuprite::PendingConnectionsError`, a subclass of Ferrum's, and rescuing Ferrum's warns + [rubycdp/ferrum#689] ### Fixed diff --git a/docs/2-customization.md b/docs/2-customization.md index a5cf418e..eef5e706 100644 --- a/docs/2-customization.md +++ b/docs/2-customization.md @@ -21,6 +21,8 @@ Cuprite-specific options are: * options `Hash` * `:js_errors` (Boolean) - Re-raise JavaScript errors from the page as `Ferrum::JavaScriptError` in Ruby, `false` by default. See [JavaScript errors](/docs/cuprite/javascript#javascript-errors). + * `:pending_connection_errors` (Boolean) - Raise `Capybara::Cuprite::PendingConnectionsError` from + `visit`, naming the requests still pending, when the page doesn't load in time, `true` by default. * `:screen_size` (Array) - Size used by `maximize_window`, `[1366, 768]` by default. * `:raise_on_unhandled_modal` (Boolean) - What to do when a JavaScript dialog is opened but not handled by `accept_*`/`dismiss_*`. When `false` (default) a warning is printed, when `true` @@ -41,7 +43,6 @@ Cuprite-specific options are: Cuprite changes a few Ferrum defaults: * `:save_path` - defaults to `Capybara.save_path` if it's set. -* `:pending_connection_errors` - `true` by default. * `:browser_options` - `"remote-allow-origins" => "*"` is always added, so that [debug](/docs/cuprite/debugging) can open DevTools, and so is `"disable-web-security" => nil`, so that tests can reach into cross-origin frames. diff --git a/lib/capybara/cuprite/browser.rb b/lib/capybara/cuprite/browser.rb index 81611608..6d8db60f 100644 --- a/lib/capybara/cuprite/browser.rb +++ b/lib/capybara/cuprite/browser.rb @@ -10,9 +10,10 @@ class Browser < Ferrum::Browser def initialize(options = nil) device = options&.dig(:emulate) options = Devices.fetch(device).except(:user_agent).merge(options) if device - super(options&.except(:js_errors)) + super(options&.except(:js_errors, :pending_connection_errors)) @options.raise_on_js_errors = options&.dig(:js_errors) + @options.raise_on_pending_connections = options.nil? || options.fetch(:pending_connection_errors, true) @options.raise_on_unhandled_modal = options&.dig(:raise_on_unhandled_modal) @options.url_blacklist = prepare_wildcards(options&.dig(:url_blacklist)) @options.url_whitelist = prepare_wildcards(options&.dig(:url_whitelist)) diff --git a/lib/capybara/cuprite/driver.rb b/lib/capybara/cuprite/driver.rb index 4804a1f2..a927b48e 100644 --- a/lib/capybara/cuprite/driver.rb +++ b/lib/capybara/cuprite/driver.rb @@ -47,8 +47,6 @@ def initialize(app, options = {}) @screen_size ||= DEFAULT_MAXIMIZE_SCREEN_SIZE @options[:save_path] ||= File.expand_path(Capybara.save_path) if Capybara.save_path - @options[:pending_connection_errors] = true unless @options.key?(:pending_connection_errors) - # It's set for debug() to make devtools tab open correctly. @options[:browser_options] ||= {} unless @options[:browser_options][:"remote-allow-origins"] diff --git a/lib/capybara/cuprite/errors.rb b/lib/capybara/cuprite/errors.rb index 61c0843b..37eae8db 100644 --- a/lib/capybara/cuprite/errors.rb +++ b/lib/capybara/cuprite/errors.rb @@ -52,6 +52,37 @@ def initialize(message) end end + FERRUM_PENDING_CONNECTIONS_ERROR = Ferrum.const_defined?(:PendingConnectionsError, false) + + class PendingConnectionsError < (FERRUM_PENDING_CONNECTIONS_ERROR ? Ferrum::PendingConnectionsError : Ferrum::StatusError) + attr_reader :url, :pendings + + def initialize(url, pendings = []) + super(url) + @url = url + @pendings = pendings + end + + def message + message = "Request to #{@url} reached server, but there are still pending connections" + @pendings.empty? ? message : "#{message}: #{@pendings.join(', ')}" + end + end + + module DeprecatedPendingConnectionsError + def ===(other) + matched = super + if matched && equal?(Ferrum::PendingConnectionsError) && other.is_a?(Cuprite::PendingConnectionsError) + warn "Ferrum::PendingConnectionsError is deprecated, use Capybara::Cuprite::PendingConnectionsError", + uplevel: 1 + end + matched + end + end + + Ferrum::PendingConnectionsError.singleton_class.prepend(DeprecatedPendingConnectionsError) if + FERRUM_PENDING_CONNECTIONS_ERROR + class ObsoleteNode < ClientError attr_reader :node diff --git a/lib/capybara/cuprite/options.rb b/lib/capybara/cuprite/options.rb index da67114b..ba1b75f8 100644 --- a/lib/capybara/cuprite/options.rb +++ b/lib/capybara/cuprite/options.rb @@ -4,7 +4,8 @@ module Ferrum class Browser class Options attr_writer :window_size - attr_accessor :url_blacklist, :url_whitelist, :raise_on_unhandled_modal, :raise_on_js_errors + attr_accessor :url_blacklist, :url_whitelist, :raise_on_unhandled_modal, :raise_on_js_errors, + :raise_on_pending_connections def mobile @options.fetch(:mobile, nil) diff --git a/lib/capybara/cuprite/page.rb b/lib/capybara/cuprite/page.rb index 22c21499..b79103e5 100644 --- a/lib/capybara/cuprite/page.rb +++ b/lib/capybara/cuprite/page.rb @@ -11,6 +11,16 @@ class Page < Ferrum::Page include Frames include Modals + def go_to(url = nil) + frame_id = super + return frame_id if frame_id || !@options.raise_on_pending_connections + + pendings = network.traffic.select(&:pending?).map(&:url).compact + raise PendingConnectionsError.new(combine_url!(url), pendings) + end + alias goto go_to + alias go go_to + def set_viewport(width: 0, height: 0, scale_factor: @options.scale_factor, mobile: @options.mobile) super end diff --git a/spec/features/driver_spec.rb b/spec/features/driver_spec.rb index f403e714..142809da 100644 --- a/spec/features/driver_spec.rb +++ b/spec/features/driver_spec.rb @@ -773,6 +773,26 @@ def create_screenshot(file, *args) @session.driver.browser.timeout = old_timeout end + it "warns when Ferrum::PendingConnectionsError is rescued" do + @driver.browser.page + old_timeout = @driver.browser.timeout + @driver.browser.timeout = 0.1 + expect do + @driver.visit(session_url("/cuprite/really_slow")) + rescue Ferrum::PendingConnectionsError + nil + end.to output(/#{__FILE__}:\d+: warning: Ferrum::PendingConnectionsError is deprecated/).to_stderr + ensure + @driver.browser.timeout = old_timeout + end + + it "does not report open resources when pending_connection_errors is disabled" do + driver = Capybara::Cuprite::Driver.new(@session.app, pending_connection_errors: false, timeout: 0.5) + expect { driver.visit(session_url("/cuprite/really_slow")) }.not_to raise_error + ensure + driver&.quit + end + it "does not report open resources where there are none" do old_timeout = @session.driver.browser.timeout begin