Skip to content

A Ruby gem for capturing web page screenshots with a clean, keyword-driven API

License

Notifications You must be signed in to change notification settings

maful/screentake

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Screentake

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.

Overview

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.

Features

  • 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

Installation

Add to your Gemfile:

gem "screentake"

Then run:

bundle install

Or install directly:

gem install screentake

If you plan to use the Ferrum driver, also add:

gem "ferrum", "~> 0.15"

Rails Installation

After adding screentake to your Gemfile, run the install generator:

bundle install
rails generate screentake:install

This 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.

Quick Start

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.base64

Configuration

Configure 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

Configuration Defaults

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

Usage Examples

Basic URL Screenshot

screenshot = Screentake.take(url: "https://example.com")
screenshot.save("output.png")

HTML Content Screenshot

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.

Full-Page Capture

screenshot = Screentake.take(
  url: "https://example.com/long-page",
  full_page: true,
)
screenshot.save("full_page.png")

Custom Viewport and Scale

# 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,
)

Selector-Based Screenshot

Capture only a specific element:

screenshot = Screentake.take(
  url: "https://example.com",
  selector: "#hero-section",
)
screenshot.save("hero.png")

Clip Region

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")

Different Formats

# 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.

Transparent Background

screenshot = Screentake.take(
  url: "https://example.com",
  transparent: true,
  format: :png,
)
screenshot.save("transparent.png")

Wait Strategies

# 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

Output Methods

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")

Debugging

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,
# }

Drivers

Cloudflare (Default)

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"]
end

Rate 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).

Ferrum (Local Chrome)

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
  }
end

The 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.

Choosing a Driver

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

API Reference

Screentake.take(**options)

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)

Screenshot#capture

Returns raw binary image data as a String.

Screenshot#base64

Returns the screenshot as a Base64-encoded String (strict encoding, no newlines).

Screenshot#save(path)

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.

Screenshot#debug

Returns a Hash of all resolved options for inspection.

Screentake.configure { |config| ... }

Yields the global Configuration object for setting defaults.

Screentake.reset_configuration!

Resets all configuration to defaults and shuts down the current driver. Useful in tests.

Error Handling

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}")
end

Error Hierarchy

Screentake::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

Development

Setup

git clone https://github.com/maful/screentake.git
cd screentake
bin/setup

Running Tests

rake test              # Run all tests
rake rubocop           # Run linter
rake                   # Run tests + rubocop (default)

Interactive Console

bin/console

Requirements

  • Ruby >= 3.1
  • Chrome or Chromium (for Ferrum driver only)

Contributing

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.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Screentake project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the code of conduct.

About

A Ruby gem for capturing web page screenshots with a clean, keyword-driven API

Resources

License

Code of conduct

Stars

Watchers

Forks

Sponsor this project