You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Ferrum::Frame#wait_for_selector (also reachable from Page/Browser), which waits for an element to show up
in the DOM. It retries at_css/at_xpath every interval until a match is found or time runs out, returning nil
on timeout [#82]
Ferrum::Page#on(:response)/Ferrum::Worker#on(:response), a callback fired once a request's response has fully
loaded (on CDP's Network.loadingFinished, not Network.responseReceived, so exchange.response.body is always
available), yielding the request's Network::Exchange. Doesn't require network.intercept to be set up, and is
never fired for requests that fail to load [#294]
Ferrum::Browser#quit/Ferrum::Browser::Process#stop accept wait: false to return immediately and run process
killing and user-data-directory cleanup on a background thread instead of blocking; the call returns the Thread
so callers can #join it if they need cleanup to have finished, e.g. before process exit or before reusing a
fixed port. Default (wait: true) keeps the previous synchronous behavior; #restart always waits.
Ferrum::Page::Stream#close_stream, a public method that releases a CDP IO stream handle (IO.close).
Ferrum::Page#set_viewport's mobile: option now also enables touch emulation
(Emulation.setTouchEmulationEnabled), alongside the existing device metrics override, so it fully emulates a
mobile device's viewport and touch support together. [#94]
Changed
Ferrum::TimeoutError names the CDP command and session it was waiting on, and exposes them as #command and #session_id. Waits that aren't for a command keep the old message [#470]
Ferrum::Page::Stream#stream now closes the CDP stream handle (IO.close) once it's been fully read, so streams
opened for Ferrum::Browser#pdf and Ferrum::Page::Tracing#record no longer keep their backing storage alive in
the browser; a failed IO.close is raised to the caller.
Fixed
An out-of-process iframe, e.g. Chrome's PDF viewer for an embedded PDF, appearing while a web worker was connected
raised 'Page.enable' wasn't found in the event thread. Only pages and iframes are now searched for its parent [#640]
A browser that stopped reading its websocket froze the whole client: a large command blocked in a socket write
forever while holding the send lock. The write now gives up after the command's timeout and raises Ferrum::DeadBrowserError [#644]
A browser context whose Target.disposeBrowserContext timed out stayed registered, so every later #reset asked
again and raised Disposal of browser context ... is already pending for the rest of the process. It's now
forgotten either way, and #reset disposes the remaining contexts before raising the first error [#641]
An error in the client or websocket thread was re-raised in the main thread wherever it happened to be, or took
the process down with it, since Utils::Thread.spawn defaulted to abort_on_exception: true and neither thread
rescued. Ferrum's threads no longer abort, they report to stderr and mark the connection dead [#470]
Commands in flight when the browser died waited out :protocol_timeout each before raising, so a dead browser
turned into a long parade of slow failures. They're released as soon as the socket closes, and a command sent
after that raises Ferrum::DeadBrowserError right away [#470]
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
disposed, so #reset won't clear such a browser. [#578] [#627]
Browser.setDownloadBehavior raised Failed to find browser context for id for a page in the browser's implicit
context, so connecting to a browser that came up with its own startup window and passing :save_path died on the
first page. Ferrum::Target#context_id, and with it Ferrum::Page#context_id, is now nil for that context,
which is how Chrome wants it addressed, by omitting browserContextId [#546]
Connecting to a remote browser with :url drops query string when probing /json/version, endpoint might answer
401 and Ferrum died with undefined method 'host' for nil. in case of parse issues Ferrum::NoWebSocketUrlError is raised.
Targets of a type Ferrum::Contexts doesn't track were left attached and paused by auto-attach for the lifetime
of the browser; Chrome opens two browser_ui ones per browser context. They're now resumed and detached from
Ferrum::Browser::Process registered two ObjectSpace finalizers on the same object, a directory remover and
a process killer. Ruby runs them FIFO so directory could have been deleted before process dead, and browser could
have restored its content on exit.
Ferrum::Browser::Process::Killer.kill waited on the browser's leader pid with a blocking, unbounded Process.wait after escalating to KILL. That method is installed as an ObjectSpace finalizer, so it can run
while the interpreter is shutting down, where a blocking wait risks hanging the process instead of letting it
exit.
--no-crashpad, added to the default Chrome flags in 0.18.0 to stop chrome_crashpad_handler from spawning, is
not a Chromium switch at all, so Chrome silently ignored it and two handler processes kept starting per browser.
Removed. There is no replacement: the only switch that does stop them, --disable-crashpad-for-testing, is meant
for Chromium's own test harness and breaks a normally launched browser — child processes die at startup with Crashing due to FD ownership violation and the network service crash-loops, while the browser process itself
survives and answers CDP, so every navigation returns net::ERR_ABORTED against an about:blank document.
Reap the handlers with an init in the container instead (docker run --init, init: true, or tini); Chrome's
crashpad behaviour is now documented under "The crashpad handler" in the customization docs [#610]
#evaluate/#evaluate_on/etc. resolved an object/array result by making two CDP round trips
(Runtime.callFunctionOn to check for a cyclic reference, then Runtime.getProperties) per nested
object or array, recursively — even though the cyclic check already walks the entire reachable
object graph from wherever it's invoked in a single round trip, making every check below the top one
provably redundant. An expression returning an array of 1,000 small objects (each with one nested
object, as in [#528]) therefore issued about 4,000 CDP round trips instead of 2,000, each one a
chance to trip the per-command timeout while the browser was still working through the backlog;
once the websocket happened to already be closed at that point, the timeout surfaced as Ferrum::DeadBrowserError instead of Ferrum::TimeoutError. The cyclic check now runs once, at the
top level of the result, and that answer is reused for every nested object/array instead of
rechecking each one.
Ferrum::Contexts#connect_worker/#detach_unless_manually_attached only rescued Ferrum::BrowserError, not Ferrum::TimeoutError.
A worker's attach or service-worker detach that timed out (e.g. on a loaded CI runner) would escape unrescued
into Client::Subscriber's dispatch thread and kill it, silently breaking further Target.* event delivery
for the rest of that browser's life, raising Ferrum::NoSuchTargetError
Ferrum::Browser::Process killed the browser's leader pid alone with SIGUSR1, a signal Chromium has no shutdown
handler for; it now sends SIGTERM, escalating to SIGKILL, to the whole process group, so renderer/GPU/zygote
child processes are no longer orphaned after #quit. A leader process that exited promptly on TERM used to
short-circuit escalation to KILL for the rest of its process group, so a child that ignored TERM
(e.g. a stuck renderer) was left running forever; termination now keeps polling the group until it's actually empty
or the timeout fires.
Removing the user data directory after #quit silently gave up on any error, potentially leaking the temp
directory forever with no indication; it now retries with exponential backoff on transient errors
(Errno::ENOTEMPTY/EBUSY/EACCES/EPERM, since Chrome can briefly hold file locks right after being killed)
and warns if it still can't be removed.
:host option is respected again when Ferrum spawns Chrome itself. Chrome dropped support for --remote-debugging-address and always binds/logs 127.0.0.1, so Ferrum now substitutes the requested :host back into the address it uses to talk to Chrome (e.g. Ferrum::Browser.new(host: "ferrum.localhost")),
instead of always hitting 127.0.0.1 [#552]
Ferrum::Frame::Runtime#call no longer retries Ferrum::NodeNotFoundError/Ferrum::NoExecutionContextError when
evaluating on an existing node (on:, e.g. #evaluate_on/#evaluate_func). Chrome reporting that a resolved
node/object id is gone can't be fixed by retrying the same command, so the retry only added up to INTERMITTENT_ATTEMPTS * INTERMITTENT_SLEEP of dead time per stale node before raising anyway; this showed up as
flaky/slow Capybara specs that queried collections of nodes shortly after the DOM replaced them, since Capybara's
own retry loop (which re-finds nodes) already handles this case correctly one layer up [#360]
Removed
The now no-op --remote-debugging-address Chrome flag, which Chrome has stopped honoring [#552]
Experimental Firefox support (browser_name: :firefox). Ferrum is Chrome/CDP only now