How to Take Website Screenshots in Ruby
Use Ferrum and Chrome to capture website screenshots in Ruby, with full-page, element, format, timing, troubleshooting, and API options.

Ruby does not render web pages by itself. A Ruby screenshot script controls Chrome or Chromium, waits for the page to render, and asks the browser to capture pixels. For a direct Ruby workflow, Ferrum provides a high-level API over Chrome DevTools Protocol. You can also use Cuprite when your Ruby application already uses Capybara.
The smallest Ferrum program is:
require "ferrum"
browser = Ferrum::Browser.new
browser.go_to("https://example.com")
browser.screenshot(path: "page.png")
browser.quit
That script needs a Chrome or Chromium binary available to the process. Ferrum can locate a browser on PATH, use BROWSER_PATH, or receive a browser path through its options. See the Ferrum documentation for the current installation and browser configuration details.
1. Choose the Ruby screenshot architecture
| Use case | Recommended route | What you control |
|---|---|---|
| Standalone script, job, or service | Ferrum | Chrome lifecycle, navigation, viewport, capture options, and cleanup |
| Capybara system or feature tests | Cuprite | Capybara sessions while Ferrum drives Chrome |
| Existing Selenium suite | Selenium with headless Chrome | Your established Selenium setup and test abstractions |
| No browser installation in your deployment | Hosted screenshot API | HTTP authentication, rendering options, privacy, limits, and cost |
Ferrum describes itself as a Ruby API to Chrome and connects through Chrome DevTools Protocol without Selenium, WebDriver, or ChromeDriver. Cuprite is a Capybara driver built on Ferrum. A hosted API is useful when you do not want to operate a browser process, but compare its rendering behavior, authentication, privacy, limits, and pricing against your requirements before choosing one.
2. Install Ferrum and provide Chrome
Add Ferrum to your application’s dependencies, then install dependencies with the package manager used by your project:
# Gemfile
gem "ferrum"
bundle install
Install a compatible Chrome or Chromium binary in the runtime environment. If it is not discoverable on PATH, configure its location explicitly. Keep the browser installation in the same container or host that runs the Ruby process; a browser path on your laptop will not exist automatically in a production container.
require "ferrum"
browser = Ferrum::Browser.new(
browser_path: ENV.fetch("BROWSER_PATH", "/usr/bin/chromium")
)
begin
browser.go_to("https://example.com")
browser.screenshot(path: "page.png")
ensure
browser.quit
end
The ensure block matters for workers and repeated jobs. Chrome is an external process, so always close it when the capture finishes or fails.
3. Capture a viewport screenshot
A normal screenshot captures the visible browser viewport. Set the window size when the target layout depends on responsive breakpoints:

require "ferrum"
browser = Ferrum::Browser.new(
window_size: [1440, 900]
)
begin
browser.go_to("https://example.com")
browser.screenshot(
path: "viewport.png",
format: :png
)
ensure
browser.quit
end
Use a stable viewport for repeatable output. A different width can activate a mobile navigation menu, alter line wrapping, or change which images are visible. If your page needs a little time after navigation, wait for a selector or a deliberate delay rather than assuming the first response means the page is visually complete.
browser.go_to("https://example.com/dashboard")
browser.at_css("main.dashboard")
browser.screenshot(path: "dashboard.png")
The exact waiting method can depend on your Ferrum version and page behavior. A selector wait is usually more meaningful than a large fixed sleep because it follows a page condition.
4. Capture the full page, an element, or an area
Full page
browser.go_to("https://example.com/docs")
browser.screenshot(
path: "docs-full.png",
full: true
)
full: true captures the document rather than only the viewport. Long documents can create very tall images, and pages with lazy-loaded content may need scrolling or a page-specific readiness condition before capture. Verify the result with the actual site and browser version when exact layout matters.
One CSS-selected element
browser.go_to("https://example.com/pricing")
browser.screenshot(
path: "pricing-card.png",
selector: ".pricing-card"
)
Coordinate area
browser.screenshot(
path: "header.png",
area: { x: 0, y: 0, width: 1440, height: 180 }
)
Use one capture mode at a time. Ferrum documents that combinations such as full-page capture with a selector or area are ignored. When both selector and area are supplied, the selector takes precedence.
5. Select image format and output controls
PNG is the default. Ferrum documents PNG, JPEG/JPG, and WebP format names. The API can write directly to a path or return Base64 data, depending on the interface you use.
browser.screenshot(
path: "preview.webp",
format: :webp
)
browser.screenshot(
path: "photo.jpg",
format: :jpeg,
quality: 82
)
browser.screenshot(
path: "retina.png",
scale: 2
)
quality is meaningful for JPEG output. scale changes the output scale, while background_color controls the background where supported by the browser capture:
browser.screenshot(
path: "card.png",
selector: ".card",
background_color: "#ffffff",
scale: 1
)
Choose PNG for sharp text and diagrams, JPEG for photographic pages where smaller files matter, and WebP when your downstream systems accept it. Keep format and scale consistent if you compare screenshots over time.
6. Build a production-safe Ruby capture script
Production code should validate its URL, set a predictable viewport, close the browser on every path, and make failures visible to the job system. A small wrapper keeps those concerns in one place:
require "ferrum"
require "uri"
class WebsiteScreenshot
def initialize(browser_path: ENV["BROWSER_PATH"])
options = { window_size: [1440, 900] }
options[:browser_path] = browser_path if browser_path
@browser = Ferrum::Browser.new(**options)
end
def capture(url, output:, full: false, selector: nil, format: :png)
parsed = URI.parse(url)
raise ArgumentError, "Only HTTP(S) URLs are allowed" unless %w[http https].include?(parsed.scheme)
@browser.go_to(url)
capture_options = { path: output, format: format, full: full }
capture_options[:selector] = selector if selector
@browser.screenshot(**capture_options)
ensure
@browser.quit
end
end
WebsiteScreenshot.new.capture(
"https://example.com",
output: "example.png"
)
For untrusted input, also consider outbound network policy, private-address protection, authentication handling, and output-size limits. Those controls belong around the browser job and should match your application’s threat model.
7. Use Cuprite with Capybara
Cuprite fits a Capybara test suite when you want Capybara’s visit and selector APIs while Ferrum controls Chrome. Add the gem in the test group, select the JavaScript driver, and register a driver with the required window size:
# Gemfile
group :test do
gem "cuprite"
end
# test setup
require "capybara/cuprite"
Capybara.javascript_driver = :cuprite
Capybara.register_driver :cuprite do |app|
Capybara::Cuprite::Driver.new(
app,
window_size: [1440, 900]
)
end
In Docker, Cuprite’s README discusses a no-sandbox browser option. Treat that as an environment-specific setting: check the current project guidance and your container security model before enabling it.
8. Wait for the page you actually want
Navigation completion does not guarantee that fonts, client-side data, animations, or lazy images are ready. Pick a readiness rule:
- Wait for a stable selector such as the main content container.
- Use a short delay only for a known animation or a third-party widget.
- For data-heavy pages, expose a page-level “ready” marker after client rendering completes.
- For full-page captures, make sure lazy content has been loaded before saving.
Disable animations in a test or documentation environment when deterministic pixels matter. If the target is personalized, provide the required cookies or authenticated session through your browser setup, and avoid capturing secrets into shared artifacts.
9. Troubleshooting Ruby screenshots
| Symptom | Likely cause | Fix |
|---|---|---|
| Browser executable not found | Chrome/Chromium is absent or not on PATH. |
Install a compatible binary or set BROWSER_PATH/browser_path. |
| Blank or partial image | Capture happened before client rendering or lazy loading finished. | Wait for a meaningful selector, page-ready marker, or required content. |
| Mobile layout appears unexpectedly | Viewport width triggered a responsive breakpoint. | Set window_size explicitly. |
| Element capture is empty | Selector does not match, is hidden, or is outside the rendered state. | Confirm the selector after navigation and capture the element only after it is visible. |
| Full-page output is extremely tall | The document is long or contains expanded content. | Capture a selector or area, or intentionally segment the page. |
| Chrome remains after failures | Cleanup was only on the success path. | Put browser.quit in an ensure block. |
| Docker browser startup fails | Container permissions or sandbox configuration do not match Chrome’s needs. | Follow current Ferrum/Cuprite container guidance and review any no-sandbox decision with your security requirements. |
10. Performance, reliability, and cost decisions
Performance
Starting a browser for every image adds process startup work. For a controlled worker, reuse one browser for a batch while creating isolated pages or sessions according to your Ferrum version and application’s isolation needs. Keep the viewport and format stable, and avoid full-page captures when a single element is sufficient. Large pages, high scale values, and Web fonts or third-party scripts increase rendering and file work.
Reliability
Use bounded job timeouts, retry transient navigation failures, and record the target URL, viewport, format, and readiness condition with each artifact. Do not retry indefinitely: a persistent JavaScript error, authentication failure, or bot check will not be fixed by more attempts. Store screenshots atomically so a failed capture cannot overwrite a known-good file.
Cost
Local Ferrum captures consume your own compute, browser storage, bandwidth, and engineering time. A hosted API replaces browser operations with request pricing and provider limits. Compare the total cost of the browser runtime, queue, monitoring, and maintenance with the API’s plan and usage rules.
11. Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. The API accepts the URL and capture options over HTTP, so your Ruby service does not need to install or manage Chrome. See the ScreenshotNeo API documentation for the complete option list.

require "net/http"
require "uri"
params = URI.encode_www_form(
access_key: "YOUR_API_KEY",
url: "https://stripe.com"
)
uri = URI("https://api.screenshotneo.com/v1/shot?#{params}")
response = Net::HTTP.get_response(uri)
raise "Screenshot failed: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
File.binwrite("shot.webp", response.body)
The same request with cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and whether the request was billed. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
Other available controls include full-page capture with lazy images, CSS element capture, dark mode, device presets or custom viewports, retina scale, PDF paper settings, custom CSS and JavaScript, clicks, selector or network-idle waits, request and resource blocking, headers, cookies, user agent, Authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. The parameter names used by other screenshot APIs also work, which can simplify migration.
Plans include 1,000 screenshots per month free with no card, then Starter at $5 for 3,000, Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan.
Start with 1,000 free screenshots a month—no card required.
12. Ruby screenshot checklist
- Install Ferrum and a compatible Chrome or Chromium binary.
- Set the browser path explicitly when the runtime cannot find it.
- Choose a fixed viewport for repeatable responsive output.
- Wait for a meaningful selector or page-ready condition.
- Use exactly one of viewport, full page, selector, or area capture modes.
- Select PNG, JPEG, or WebP based on the downstream use.
- Close the browser in an
ensureblock. - Bound navigation and job time, and retry only transient failures.
- Use Cuprite when Capybara is already the test interface.
- Use a hosted API when operating Chrome is a poor fit for your deployment.
13. Frequently asked questions
Can Ruby take a screenshot without Chrome?
Not with the Ferrum workflow. Ferrum controls Chrome or Chromium, which performs the web rendering. A hosted screenshot API is the alternative when you do not want a browser binary in your application environment.
Should I use Ferrum or Cuprite?
Use Ferrum for a standalone Ruby script or service. Use Cuprite when Capybara already drives your system or feature tests.
What is the difference between full page and viewport capture?
Viewport capture saves the visible browser area. Full-page capture uses the document dimensions and can produce a very tall image.
Can I capture only a card or chart?
Yes. Pass a CSS selector to Ferrum’s screenshot method, or use an area when you need coordinate-based cropping.
Why does my screenshot differ between machines?
Browser version, viewport, installed fonts, device scale, timing, and third-party content can all change rendered pixels. Pin the environment and readiness condition when comparisons matter.