Customization
You can customize options with the following code in your test setup:
Ferrum::Browser.new(options)
- options
Hash:headless(Boolean) - Set browser as headless or not,trueby default.:incognito(Boolean) - Create an incognito profile for the browser startup window,trueby default.:dockerize(Boolean) - Provide CLI flags to the browser to run it in a container,falseby default.:xvfb(Boolean) - Run browser in a virtual framebuffer,falseby default.:flatten(Boolean) - Use one websocket connection to the browser and all the pages in flatten mode,trueby default. When set tofalse, each page/target opens its own dedicated websocket connection instead of sharing the browser's connection.:window_size(Array) - The dimensions of the browser window in which to test, expressed as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]:extensions(Array[String | Hash]) - An array of paths to files or JS source code to be preloaded into the browser e.g.:["/path/to/script.js", { source: "window.secret = 'top'" }]:logger(Object responding toputs) - When present, debug output is written to this object.:slowmo(Integer | Float) - Set a delay in seconds to wait before sending command. Useful companion of headless option, so that you have time to see changes.:timeout(Numeric) - The number of seconds we'll wait for a response when communicating with browser. Default is 5.:js_errors(Boolean) - When true, JavaScript errors get re-raised in Ruby.:pending_connection_errors(Boolean) - RaisePendingConnectionsErrorwhen main frame is still waiting for slow responses and timeout is reached. Default is false.:browser_name(Symbol) -:chromeby default, only experimental support for:firefoxfor now.:browser_path(String) - Path to Chrome binary, you can also set ENV variable asBROWSER_PATH=some/path/chrome bundle exec rspec.:browser_options(Hash) - Additional command line options, see them all e.g.{ "ignore-certificate-errors" => nil }:ignore_default_browser_options(Boolean) - Ferrum has a number of default options it passes to the browser, if you set this totruethen only options you put in:browser_optionswill be passed to the browser, except required ones of course.:port(Integer) - Remote debugging port for headless Chrome.:host(String) - Host we communicate with when spawning browser,127.0.0.1by default. Chrome always listens on127.0.0.1regardless of this option, so the host must resolve to127.0.0.1for Ferrum to actually be able to connect.:url(String) - URL for a running instance of Chrome. If this is set, a browser process will not be spawned.:ws_url(String) - Websocket url for a running instance of Chrome. If this is set, a browser process will not be spawned. It's higher priority than:url, setting both doesn't make sense.:process_timeout(Integer) - How long to wait for the Chrome process to respond on startup.:ws_max_receive_size(Integer) - How big messages to accept from Chrome over the web socket, in bytes. Defaults to 64MB. Incoming messages larger than this will cause aFerrum::DeadBrowserError.:proxy(Hash) - Specify proxy settings, read more:save_path(String) - Path to save attachments with Content-Disposition header.:env(Hash) - Environment variables you'd like to pass through to the process
The crashpad handler
Chrome starts two chrome_crashpad_handler processes per browser on Linux. Their only job is to collect and upload
crash reports, which no automated browser has any use for, but Ferrum does not disable them, because there is no
safe way to.
Handlers Chrome starts on Linux:
| Chrome | --headless | --headless=new | headful |
|---|---|---|---|
| 127 and older | 0 | 2 | 2 |
| 128 and newer | 2 | 2 | 2 |
New headless always starts them. What changed in 128 is what bare --headless means: before it, that selected old
headless, a separate lightweight shell with no crash handler; from 128 on it selects new headless, which is full
Chrome. Headful has always behaved like new headless.
No flag turns them off safely
| Flag | What it does |
|---|---|
--disable-breakpad | Stops crash reporting, not the handler process |
--disable-crash-reporter | Same |
--no-crashpad | Not a Chromium switch at all, so Chrome ignores it |
--disable-crashpad-for-testing | Real, and it does stop them — but it breaks the browser |
Do not pass --disable-crashpad-for-testing. It is named for-testing because it is meant for Chromium's own test
harness, where the process tree is fully controlled. In a normally launched browser, child processes die at startup
with Crashing due to FD ownership violation, and the network service crash-loops.
The failure is near-invisible: the browser process survives, so it starts normally and CDP answers every command.
There is simply no network service behind it, so every navigation returns net::ERR_ABORTED and the document stays
about:blank.
Operating system differences
On macOS one handler starts rather than two, and it exits when the browser does, so nothing accumulates.
It is also invisible to pstree, because it double-forks and reparents to launchd and is therefore never a
descendant of the process that started Chrome. Look for it globally instead:
$ ps -Ao pid=,ppid=,comm= | grep crashpad
13270 1 .../Helpers/chrome_crashpad_handler
ppid is 1, and the pid usually lands just after Chrome's own.
Why this matters most in Docker
The handler is harmless outside a container. It is not in Chrome's process group, so the signal Ferrum sends on
teardown never reaches it — and does not need to: it watches the browser and exits by itself once Chrome is gone.
What is left behind is an exit status, and because the handler double-forks away from Chrome its parent is pid 1.
Collecting it is PID 1's job which doesn't exist in a container, so the handlers pile up as <defunct> entries.
See Docker.
Examples
# Run in headful mode with custom window size
Ferrum::Browser.new(headless: false, window_size: [1920, 1080])
# Connect to an existing Chrome instance
Ferrum::Browser.new(url: "http://localhost:9222")
# Enable JavaScript error raising
Ferrum::Browser.new(js_errors: true)
# Set custom timeout and slowmo for debugging
Ferrum::Browser.new(timeout: 10, slowmo: 0.5)
# Use custom Chrome binary
Ferrum::Browser.new(browser_path: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
# Add custom browser options
Ferrum::Browser.new(browser_options: { "disable-web-security" => nil })