A Ruby gem for capturing web page screenshots with a clean, keyword-driven API. Supports both the Cloudflare Browser Rendering API and a local Ferrum (headless Chrome) driver.
Screentake provides a simple interface for turning URLs or raw HTML into PNG, JPEG, or WebP screenshots. It handles viewport configuration, device scale factors, full-page captures, element-specific screenshots, and various wait strategies out of the box.
The gem ships with two interchangeable drivers. The Cloudflare driver sends rendering requests to the Cloudflare Browser Rendering API -- no local browser required. The Ferrum driver uses headless Chrome on your own machine via the Ferrum gem. Both drivers share the same options interface, so switching between them is a one-line configuration change.
- URL and HTML rendering -- pass a URL or an HTML string
- Multiple output formats -- PNG, JPEG, WebP
- Viewport control -- width, height, and device scale factor (1-4x)
- Full-page capture -- screenshot the entire scrollable page
- Element targeting -- capture a specific CSS selector
- Clip regions -- crop to an arbitrary rectangle
- Transparent backgrounds -- omit the default white background
- Wait strategies -- wait for load, DOMContentLoaded, network idle, a selector, or a fixed timeout
- Automatic retries -- Cloudflare driver retries on 429 rate limits with exponential backoff
- Three output methods -- raw binary, Base64, or save directly to a file
- Rails auto-integration -- Railtie loaded automatically when Rails is present
- Driver pattern -- swap between Cloudflare and Ferrum without changing application code
Add to your Gemfile:
gem "screentake"Then run:
bundle installOr install directly:
gem install screentakeIf you plan to use the Ferrum driver, also add:
gem "ferrum", "~> 0.15"After adding screentake to your Gemfile, run the install generator:
bundle install
rails generate screentake:installThis creates config/initializers/screentake.rb with default configuration. Set your Cloudflare credentials via environment variables (CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN) or edit the initializer directly.
require "screentake"
Screentake.configure do |config|
config.cloudflare_account_id = ENV["CLOUDFLARE_ACCOUNT_ID"]
config.cloudflare_api_token = ENV["CLOUDFLARE_API_TOKEN"]
end
# Take a screenshot and save it
screenshot = Screentake.take(url: "https://example.com")
screenshot.save("example.png")
# Or get the binary data directly
binary = screenshot.capture
# Or get Base64 for embedding in HTML/JSON
encoded = screenshot.base64Configure Screentake globally with Screentake.configure:
Screentake.configure do |config|
# Driver selection (:cloudflare or :ferrum)
config.driver = :cloudflare
# Cloudflare credentials (required for :cloudflare driver)
config.cloudflare_account_id = ENV["CLOUDFLARE_ACCOUNT_ID"]
config.cloudflare_api_token = ENV["CLOUDFLARE_API_TOKEN"]
# Default viewport dimensions
config.default_viewport = { width: 1280, height: 800 }
# Device scale factor (1-4, default: 2)
config.default_scale = 2
# Default image format (:png, :jpeg, :webp)
config.default_format = :png
# Navigation timeout in milliseconds (default: 30000)
config.timeout = 30_000
# Logger instance for debug/retry output (default: nil)
config.logger = Rails.logger
# Ferrum browser options (only used with :ferrum driver)
config.ferrum_options = { headless: true }
end| Option | Default | Description |
|---|---|---|
driver |
:cloudflare |
Which rendering backend to use |
cloudflare_account_id |
nil |
Your Cloudflare account ID |
cloudflare_api_token |
nil |
Your Cloudflare API token |
default_viewport |
{ width: 1280, height: 800 } |
Browser viewport size |
default_scale |
2 |
Device scale factor (Retina) |
default_format |
:png |
Output image format |
timeout |
30000 |
Navigation timeout (ms) |
logger |
nil |
Logger for debug output |
ferrum_options |
{ headless: true } |
Options passed to Ferrum::Browser.new |
screenshot = Screentake.take(url: "https://example.com")
screenshot.save("output.png")html = <<~HTML
<html>
<body style="background: #1a1a2e; color: #eee; padding: 40px;">
<h1>Hello from Screentake</h1>
</body>
</html>
HTML
screenshot = Screentake.take(html: html)
screenshot.save("rendered.png")Note: url: and html: are mutually exclusive. Passing both raises ArgumentError.
screenshot = Screentake.take(
url: "https://example.com/long-page",
full_page: true,
)
screenshot.save("full_page.png")# Using width: and height:
screenshot = Screentake.take(
url: "https://example.com",
width: 1920,
height: 1080,
scale: 3,
)
# Using size: shorthand
screenshot = Screentake.take(
url: "https://example.com",
size: [375, 812], # iPhone viewport
scale: 3,
)Capture only a specific element:
screenshot = Screentake.take(
url: "https://example.com",
selector: "#hero-section",
)
screenshot.save("hero.png")Crop to an arbitrary rectangle:
screenshot = Screentake.take(
url: "https://example.com",
clip: { x: 0, y: 0, width: 600, height: 400 },
)
screenshot.save("cropped.png")# JPEG with quality setting
screenshot = Screentake.take(
url: "https://example.com",
format: :jpeg,
quality: 85,
)
screenshot.save("output.jpg")
# WebP with quality setting
screenshot = Screentake.take(
url: "https://example.com",
format: :webp,
quality: 90,
)
screenshot.save("output.webp")Note: quality: is only valid for JPEG and WebP. Passing it with PNG format raises InvalidOptionError.
The .save method also infers format from the file extension. Saving to output.jpg will produce a JPEG regardless of the format: option.
screenshot = Screentake.take(
url: "https://example.com",
transparent: true,
format: :png,
)
screenshot.save("transparent.png")# Wait until there are zero network connections for 500ms
screenshot = Screentake.take(
url: "https://example.com",
wait_until: :networkidle0,
)
# Wait for a specific element to appear
screenshot = Screentake.take(
url: "https://example.com",
wait_for_selector: ".chart-loaded",
)
# Add a fixed delay (in milliseconds, 1-30000)
screenshot = Screentake.take(
url: "https://example.com",
wait_for_timeout: 2000,
)Available wait_until values:
| Value | Description |
|---|---|
:load |
Wait for the load event |
:domcontentloaded |
Wait for DOMContentLoaded event |
:networkidle0 |
Wait until zero network connections for 500ms (default) |
:networkidle2 |
Wait until two or fewer network connections for 500ms |
Every Screentake.take call returns a Screenshot object with three output methods:
screenshot = Screentake.take(url: "https://example.com")
# Raw binary data
binary = screenshot.capture
# Base64-encoded string
encoded = screenshot.base64
# Save to file (creates parent directories automatically, returns the path)
path = screenshot.save("screenshots/example.png")Inspect the resolved options for a screenshot:
screenshot = Screentake.take(url: "https://example.com", full_page: true)
pp screenshot.debug
# {
# source_type: :url,
# source: "https://example.com",
# width: 1280,
# height: 800,
# scale: 2,
# format: :png,
# quality: nil,
# full_page: true,
# selector: nil,
# clip: nil,
# transparent: false,
# wait_until: :networkidle0,
# wait_for_selector: nil,
# wait_for_timeout: nil,
# driver: :cloudflare,
# }The Cloudflare driver sends requests to the Cloudflare Browser Rendering API. No local browser installation needed.
Screentake.configure do |config|
config.driver = :cloudflare
config.cloudflare_account_id = ENV["CLOUDFLARE_ACCOUNT_ID"]
config.cloudflare_api_token = ENV["CLOUDFLARE_API_TOKEN"]
endRate limits: Cloudflare enforces 2 browsers per minute and 2 concurrent requests. The driver automatically retries up to 3 times on HTTP 429 responses with exponential backoff (1s, 2s, 4s).
The Ferrum driver runs a local headless Chrome instance via the Ferrum gem. It requires Chrome or Chromium installed on the machine.
# Gemfile
gem "ferrum", "~> 0.15"Screentake.configure do |config|
config.driver = :ferrum
config.ferrum_options = {
headless: true,
# browser_path: "/usr/bin/chromium", # custom Chrome path
# timeout: 60, # Ferrum process timeout
}
endThe Ferrum driver lazily starts the browser on the first screenshot, reuses it for subsequent requests (each screenshot gets its own browser context), and automatically shuts it down on process exit.
| Cloudflare | Ferrum | |
|---|---|---|
| Setup | API credentials only | Chrome/Chromium installed locally |
| Infrastructure | No local browser needed | Runs on your server |
| Rate limits | 2/min, 2 concurrent | Limited by your hardware |
| Cost | Cloudflare pricing applies | Free (your compute) |
| Best for | Production, serverless | Development, CI, high volume |
Creates a Screenshot instance. Validates all options eagerly -- invalid options raise immediately, before any network request.
Source options (exactly one required):
| Option | Type | Description |
|---|---|---|
url: |
String |
URL to capture (http/https only) |
html: |
String |
Raw HTML content to render |
Viewport options:
| Option | Type | Default | Description |
|---|---|---|---|
width: |
Integer |
1280 |
Viewport width (1-10000) |
height: |
Integer |
800 |
Viewport height (1-10000) |
size: |
Array |
-- | Shorthand [width, height] |
scale: |
Numeric |
2 |
Device scale factor (1-4) |
Capture options:
| Option | Type | Default | Description |
|---|---|---|---|
format: |
Symbol |
:png |
:png, :jpeg, or :webp |
quality: |
Integer |
nil |
1-100, JPEG/WebP only |
full_page: |
Boolean |
false |
Capture full scrollable page |
selector: |
String |
nil |
CSS selector to capture |
clip: |
Hash |
nil |
{ x:, y:, width:, height: } |
transparent: |
Boolean |
false |
Transparent background |
Wait options:
| Option | Type | Default | Description |
|---|---|---|---|
wait_until: |
Symbol |
:networkidle0 |
Page load strategy |
wait_for_selector: |
String |
nil |
Wait for this CSS selector |
wait_for_timeout: |
Numeric |
nil |
Fixed delay in ms (1-30000) |
Returns raw binary image data as a String.
Returns the screenshot as a Base64-encoded String (strict encoding, no newlines).
Writes the screenshot to path, creating parent directories as needed. Infers format from the file extension (.jpg/.jpeg to JPEG, .webp to WebP, .png to PNG). Returns the path as a String.
Returns a Hash of all resolved options for inspection.
Yields the global Configuration object for setting defaults.
Resets all configuration to defaults and shuts down the current driver. Useful in tests.
All Screentake errors inherit from Screentake::Error, so you can rescue broadly or handle specific cases:
begin
screenshot = Screentake.take(url: "https://example.com")
screenshot.save("output.png")
rescue Screentake::RateLimitError
# Cloudflare 429 after all retries exhausted
retry_later
rescue Screentake::AuthenticationError => e
# Invalid Cloudflare credentials
Rails.logger.error("Screentake auth failed: #{e.message}")
rescue Screentake::TimeoutError
# Page took too long to load
handle_timeout
rescue Screentake::ElementNotFoundError
# Selector matched no elements
use_fallback_image
rescue Screentake::Error => e
# Catch-all for any Screentake error
Rails.logger.error("Screenshot failed: #{e.message}")
endScreentake::Error
Screentake::NotImplementedError
Screentake::InvalidUrlError
Screentake::InvalidHtmlError
Screentake::InvalidDimensionError
Screentake::InvalidOptionError
Screentake::InvalidClipError
Screentake::ElementNotFoundError
Screentake::TimeoutError
Screentake::RenderError
Screentake::DriverError
Screentake::CloudflareError
Screentake::RateLimitError
Screentake::AuthenticationError
Screentake::FerrumError
Screentake::BrowserNotFoundError
Screentake::BrowserCrashedError
git clone https://github.com/maful/screentake.git
cd screentake
bin/setuprake test # Run all tests
rake rubocop # Run linter
rake # Run tests + rubocop (default)bin/console- Ruby >= 3.1
- Chrome or Chromium (for Ferrum driver only)
Bug reports and pull requests are welcome on GitHub at https://github.com/maful/screentake. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Screentake project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the code of conduct.