How to Take Full-Page Screenshots in Ruby on Rails
Capture Rails system-test screenshots, save HTML for failures, and use Playwright when you need a documented full-page image.
For a Rails system test, call take_screenshot after the browser reaches the state you want to inspect. Rails saves the image under tmp/screenshots by default, and you can change that directory with Capybara.save_path. However, the Rails helper documentation describes a screenshot of the current page and does not document a full_page option. If you require a capture of the entire scrollable document, use a browser API that explicitly supports full-page screenshots, such as Playwright’s fullPage: true option.
Choose the capture method
| Need | Use | Why |
|---|---|---|
| Capture the current state during a Rails system test | take_screenshot |
It is built into Rails system-testing helpers and is convenient for debugging. |
| Capture automatically when a test fails | take_failed_screenshot |
Rails can create an artifact during teardown when the test failed. |
| Guarantee a full scrollable-page image | Playwright with fullPage: true or full_page=True |
The option explicitly requests a full-page capture. |
| Capture production URLs without maintaining a browser | ScreenshotNeo | One HTTP request returns an image or PDF and handles page cleanup before capture. |
Capture a screenshot in a Rails system test
Rails 8.0.4 documents ActionDispatch::SystemTesting::TestHelpers::ScreenshotHelper#take_screenshot as taking a screenshot of the current page in the browser. Use it after navigation and interactions have produced the state you want to review.
require "application_system_test_case"
class CheckoutTest < ApplicationSystemTestCase
test "shows the checkout summary" do
visit checkout_path
fill_in "Email", with: "developer@example.com"
click_on "Continue"
take_screenshot
end
end
Run the test with your normal Rails command:
bin/rails test:system
Unless configured otherwise, look in tmp/screenshots. The helper can be called more than once; Rails uses sequential filenames for the captures produced during a test run.
Change the screenshot directory
# test/application_system_test_case.rb
require "test_helper"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1200 ]
Capybara.save_path = Rails.root.join("tmp", "system-test-artifacts")
end
Use an absolute or Rails-root-relative path that your test process can write. Create the directory in CI if your runner does not create it automatically.
Save HTML with the screenshot
When a visual artifact is not enough, ask the helper to save the page HTML as well. The HTML file preserves the markup and state that existed when the screenshot was taken, which helps diagnose missing content, incorrect selectors, and failed JavaScript initialization.
take_screenshot(html: true)
Rails also documents an environment-variable form for requesting HTML. Check the API reference for Rails version installed in your project before relying on a version-specific variable name.
Capture failures automatically
Rails provides take_failed_screenshot as a teardown helper. It checks that the test failed, screenshot support is available, and a Capybara session exists before capturing an artifact.
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1200 ]
teardown do
take_failed_screenshot
end
end
Keep the helper in the base system-test class so every system test gets the same failure behavior. The exact hook shape can differ between Rails versions, so confirm it against your installed Rails API.
When take_screenshot is not full-page
A viewport screenshot shows only the visible browser area. The cited Rails API describes the current page but does not promise that content below the viewport is stitched into one image. Do not assume that calling take_screenshot produces a complete long-page capture.
For a strict full-scroll requirement, use an automation API with an explicit full-page setting. Playwright defines a full-page screenshot as the full scrollable page as if it fit on a very tall screen.
Full-page capture with Playwright
JavaScript
import { chromium } from "playwright";
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
viewport: { width: 1440, height: 1000 }
});
await page.goto("http://127.0.0.1:3000/checkout", {
waitUntil: "networkidle"
});
await page.screenshot({
path: "tmp/checkout-full.png",
fullPage: true,
type: "png"
});
await browser.close();
Start Rails in another process before running this script, for example with bin/rails server. Use a test-only URL or authentication setup appropriate for your application.
Python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page(viewport={"width": 1440, "height": 1000})
page.goto("http://127.0.0.1:3000/checkout", wait_until="networkidle")
page.screenshot(path="tmp/checkout-full.png", full_page=True, type="png")
browser.close()
Playwright CLI
npx playwright screenshot --full-page http://127.0.0.1:3000/checkout tmp/checkout-full.png
The CLI also supports a custom image type and a high-resolution mode. Screenshots are useful for visual review; Playwright’s accessibility snapshots are better when the goal is to understand page structure or read text programmatically.
Full-page screenshot options that matter
| Option | What it controls | Typical choice |
|---|---|---|
fullPage/full_page |
Whether Playwright captures the complete scrollable document | true for a long-page artifact |
path |
Output filename | Store under a CI artifact directory |
type |
PNG or JPEG output | PNG for lossless diffs; JPEG for smaller photographic files |
quality |
JPEG quality | Set only for JPEG; it has no effect on PNG |
clip |
A rectangular region to capture | Use when full-page output is too broad |
scale |
CSS-pixel or device-pixel output scale | CSS scale for smaller, stable review images; device scale for high-DPI fidelity |
mask |
Locators whose content should be hidden | Mask timestamps, user names, or rotating content |
| Animation handling | Whether animations continue during capture | Disable or freeze motion for repeatable visual comparisons |
CSS scale produces one image pixel per CSS pixel. Device scale preserves device-pixel density and can make output roughly twice as large on a high-DPI context. Choose based on whether compact, stable diffs or physical-pixel fidelity matters.
Make long-page captures reliable
- Wait for the state you need. Prefer a selector that proves the page is ready over an arbitrary sleep. For example, wait for the order summary or a page-specific loading indicator to disappear.
- Control viewport dimensions. Keep width and height fixed in local runs and CI so responsive breakpoints do not change the result.
- Handle lazy content. Full-page capture can expose sections that were never initialized. Scroll or trigger the application’s lazy-loading mechanism before the screenshot, then wait for the content selectors.
- Freeze unstable data. Use test fixtures, fixed clocks, deterministic IDs, and masked regions for values that change on every run.
- Keep authentication explicit. Log in through the test flow or load a test storage state; never depend on a developer’s browser profile.
- Save artifacts on failure. Keep screenshots and HTML together so a visual mismatch can be traced back to markup.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Only the visible viewport is saved | The Rails helper captures the current browser page and has no documented full-page option | Use Playwright with fullPage: true or full_page=True. |
| No screenshot file appears | The path is relative to an unexpected working directory or the directory is not writable | Set Capybara.save_path to a known directory and verify CI permissions. |
| Screenshot shows a loading spinner | Capture ran before the application finished rendering | Wait for a meaningful ready selector; use network idle only when the page actually becomes idle. |
| Images or cards are missing below the fold | Lazy loading waits for scrolling or an intersection observer | Scroll through the page or invoke the page’s supported load trigger, then wait for each key selector. |
| Different results on each run | Animations, timestamps, randomized data, ads, or remote content | Freeze test data and time, disable motion, mask dynamic regions, and stub external services. |
| Full-page image is extremely large | Very long document combined with device-pixel scale | Use CSS scale, capture a relevant clip, or split the document into purposeful sections. |
| Playwright script cannot connect to Rails | The Rails server is not running or the URL is not reachable from the process | Start Rails first, use 127.0.0.1 and the correct port, and ensure the process waits for the server. |
Performance, reliability, and cost
A full-page image requires more layout and encoding work than a viewport image. Large documents consume more memory, take longer to write, and can exceed CI artifact limits. PNG preserves detail but is usually larger; JPEG reduces size at the cost of compression artifacts. Keep the browser context and viewport consistent, and avoid capturing pages that contain unrelated infinite-scroll content.
For visual regression, compare images only after the same fonts, assets, data, animations, and viewport are loaded. A screenshot proves appearance at one point in time; it does not replace accessibility or DOM assertions.
For production or scheduled captures, running and patching a browser yourself adds operational work. ScreenshotNeo provides a screenshot API with PNG, JPEG, WebP, and PDF output. Its pricing bills only clean shots; bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
Or skip the browser setup
ScreenshotNeo can capture a URL with one request. See the ScreenshotNeo API documentation for all options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Before capture, cookie and consent banners, newsletter popups, and chat widgets are removed. Bot checks, blank pages, and failed loads are never billed. An MCP server lets Claude, Cursor, and other MCP clients take screenshots with take_screenshot, inspect pages with get_page_info, and create PDFs with capture_pdf. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does Rails have a documented full_page argument?
The cited Rails screenshot helper documentation does not document one. Use Playwright when full-scroll behavior must be explicit.
Can I call take_screenshot more than once?
Yes. Rails documents sequential filenames for multiple screenshots in a test.
Should I use a screenshot to read page content?
No. Use DOM assertions or an accessibility snapshot for structure and text; use screenshots for visual evidence.
Which format is best for visual diffs?
PNG is lossless and generally the safest default. Use JPEG when file size matters and small compression differences are acceptable.
What should I check before adapting Playwright to Rails?
Confirm the Ruby or Rails integration, browser installation, authentication approach, and API version in your project. The documented examples here use Playwright JavaScript and Python APIs.


