ScreenshotNeo

BlogHow-to

How to take a Selenium screenshot in Ruby with headless Chrome

Capture a PNG with Ruby, Selenium, and headless Chrome. Set up the driver, wait for page content, handle common errors, and choose viewport or full-page capture.

By the ScreenshotNeo team4 October 20267 min read

Use Selenium WebDriver’s Ruby binding to start Chrome with --headless=new, navigate to a page, and call save_screenshot('screenshot.png'). That saves the current viewport as a PNG. Put driver.quit in an ensure block so Chrome closes even if navigation or capture fails.

require 'selenium-webdriver'

options = Selenium::WebDriver::Options.chrome
options.add_argument('--headless=new')

driver = Selenium::WebDriver.for(:chrome, options: options)

begin
  driver.get('https://example.com')
  driver.save_screenshot('screenshot.png')
ensure
  driver.quit
end

The screenshot file is written relative to the script’s current working directory. Use an absolute path if you need a predictable destination. Selenium’s Ruby API describes this method as saving a PNG screenshot of the viewport to a path. See the Selenium Ruby screenshot API.

1. Install Ruby Selenium and prepare Chrome

Install Selenium WebDriver in the same Ruby environment that runs your script:

gem install selenium-webdriver

Alternatively, add it to a project’s Gemfile and run Bundler:

source 'https://rubygems.org'
gem 'selenium-webdriver'
bundle install

Install Chrome or Chromium on the machine where the script runs. Recent Selenium releases include Selenium Manager, which can locate or manage a compatible driver in supported environments. A separate driver-management gem is usually unnecessary as a first step. Manager may need network access to obtain a driver; controlled or offline builds may need a driver binary configured explicitly.

Chrome and ChromeDriver should have matching major version numbers. See Selenium Manager documentation and Selenium’s Chrome-specific guidance.

2. Capture a page after it is ready

driver.get waits for the page’s load event according to the WebDriver page-load strategy, but modern pages may continue rendering content afterward. For dynamic pages, wait for the specific element that should appear in the screenshot. This is more reliable than sleeping for a fixed number of seconds.

require 'selenium-webdriver'

options = Selenium::WebDriver::Options.chrome
options.add_argument('--headless=new')

driver = Selenium::WebDriver.for(:chrome, options: options)

begin
  driver.manage.window.size = Selenium::WebDriver::Dimension.new(1440, 1000)
  driver.get('https://example.com')

  wait = Selenium::WebDriver::Wait.new(timeout: 15)
  wait.until { driver.find_element(css: 'main h1').displayed? }

  driver.save_screenshot(File.expand_path('tmp/screenshot.png'))
ensure
  driver.quit
end

Create the destination directory first. Selenium writes the image but does not create missing parent directories:

require 'fileutils'
FileUtils.mkdir_p('tmp')

Place that line before the capture. If the target selector differs, replace main h1 with a selector that marks the content you need. A selector wait confirms presence and visibility, but it cannot guarantee every animation, lazy-loaded image, or network request has finished. Add a page-specific readiness condition when those affect the result.

3. Configure headless Chrome and viewport size

Current Selenium Ruby guidance configures headless mode as a Chrome argument:

options.add_argument('--headless=new')

The older options.headless = true style has been removed from current guidance. Set window dimensions explicitly when screenshots must be comparable across runs; the default window size can vary by environment. The screenshot reflects the viewport’s rendered pixels, which can also vary with browser version, device scale, installed fonts, and page timing.

driver.manage.window.size = Selenium::WebDriver::Dimension.new(1365, 900)

Set the size before capture, and preferably before navigation when page layout responds to viewport width. A viewport screenshot does not automatically include the entire document.

4. Viewport versus full-page screenshots

save_screenshot('file.png') captures the current browsing context’s viewport. The Ruby API has an optional full_page: setting only where the active driver supports it. Support is not universal; an unsupported driver can raise an unsupported-operation error. Check the driver’s support before relying on full-page output in a portable script.

For viewport output, set window size and capture normally. For a full document image, use a driver and browser combination that explicitly supports the option, and handle unsupported operation as a capability limitation rather than assuming every Chrome setup can provide it through the same call.

# Only use this where the current driver supports full_page.
driver.save_screenshot('full-page.png', full_page: true)

If you only need a one-off Chrome capture and do not need Ruby-driven navigation or test assertions, Chrome’s command-line screenshot mode is another option:

chrome --headless --screenshot="screenshot.png" --window-size="1365,900" "https://example.com"

The Chrome command-line route is separate from Selenium. Choose Selenium when the capture belongs in Ruby automation, needs waits or interactions, or is part of a test. See Chrome’s headless documentation.

5. Make screenshot scripts reliable

  • Always close the session: use ensure with driver.quit, including when navigation, waits, or file writes raise errors.
  • Wait for the page state you need: wait for a meaningful selector or condition instead of relying on an arbitrary delay.
  • Control the viewport: specify dimensions for repeatable layout, and keep browser, fonts, and device scale consistent for visual comparisons.
  • Use a writable path: create the parent directory and use an absolute path in jobs where the working directory may differ.
  • Keep driver versions compatible: Chrome and ChromeDriver need matching major versions when managing binaries yourself.
  • Keep sessions bounded: reuse a session for related captures when appropriate, but quit it when the work finishes so failed jobs do not leave browser processes behind.

For CI or containers, verify that Chrome is installed and can start under the runtime’s user and filesystem permissions. Do not add browser flags blindly; diagnose the startup exception and environment first.

6. Troubleshooting common errors

Symptom Likely cause Fix
LoadError: cannot load such file -- selenium-webdriver The gem is missing from the Ruby environment running the script, or Bundler is not being used. Install the gem with gem install selenium-webdriver, or run through the project’s Bundler setup.
Driver or browser cannot be found Chrome/Chromium is not installed or discoverable, or Selenium Manager cannot obtain a driver in this network environment. Install a supported browser, check executable availability, and inspect Selenium Manager diagnostics. In restricted builds, provide a compatible driver binary explicitly.
Session creation fails with a version mismatch The ChromeDriver major version does not match Chrome’s major version. Update or pin the driver to match the installed browser. Check the actual browser and driver versions used by the runtime.
Screenshot path does not exist or is denied The parent directory is absent or the process lacks write permission. Create the directory with FileUtils.mkdir_p, choose a writable path, and check the script’s working directory.
Screenshot is blank or content is missing Capture happened before client-rendered content appeared, navigation reached an unexpected page, or the page is blocked or still loading. Check the current URL and page state, then wait for the exact visible content needed. Handle authentication or site-specific access requirements in the test.
Full-page option raises an unsupported-operation error The active driver does not implement full-page screenshots through that API. Use viewport capture, or select a driver and supported capture method that provides full-page output. Do not assume full_page: works in every setup.
Chrome exits immediately or cannot start in a container Browser installation, runtime permissions, filesystem access, or container constraints prevent startup. Read the full startup exception, verify Chrome can run as the configured user, and correct the specific environment issue before changing options.
Chrome processes remain after a failed run The script exited before cleanup. Put all work after session creation inside begin/ensure and call driver.quit in the ensure clause.

7. Performance, reliability, and cost

A Selenium screenshot starts or uses a real browser session, so capture time includes browser startup, navigation, page scripts, and any explicit waits. Reusing a session for multiple related pages can avoid repeated startup, while independent jobs may benefit from isolated sessions. Keep concurrency within the CPU and memory available to the machine; the documentation does not establish a universal speed or capacity figure.

Reliability depends on controlling the browser and page state. Pin compatible browser and driver versions in repeatable builds, set the viewport, wait for meaningful content, and ensure cleanup. Remote sites can change markup, load timing, or access behavior, so selectors and readiness conditions should be maintained like other test code.

The software cost is the infrastructure and engineering effort to install, update, and run Chrome and its driver. Selenium and Chrome documentation cited here do not provide a per-screenshot price or performance benchmark for this workflow.

8. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. If you need a screenshot without managing Chrome and ChromeDriver in your Ruby environment, request the image directly. See the ScreenshotNeo API documentation for the available options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets Claude, Cursor, and other MCP clients take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. The same API also supports PNG, JPEG, or WebP output and PDF capture.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

9. Frequently asked questions

Does Selenium save screenshots as PNG?

Yes. The Ruby save_screenshot method writes PNG output; use a filename ending in .png.

Does headless Chrome need a visible desktop?

No. Headless mode runs Chrome without a normal visible browser window, though Chrome and its compatible driver still need to be installed or made available to the runtime.

Can I use this in a Ruby test?

Yes. It fits naturally into test or automation code that needs to navigate, wait for state, and capture the resulting viewport. Put browser cleanup in the test framework’s teardown or an ensure block.

Does the screenshot include browser chrome?

No. WebDriver captures the page viewport, not the operating system window frame or browser toolbar.