Article

How to Take Screenshots with Ruby on Rails

·
Corinn Pope
·
14 min read

Rails developers often need to take screenshots within their programs for a variety of reasons.

If you're a Rails developer, you may find yourself taking screenshots to:

  • Show a bug that's occurring during automated testing
  • Programmatically grab screenshots from a URL instead of asking your users to upload potentially inconsistent files
  • Track changes on your competitor’s pricing and landing pages
  • Ensure your i18n translations look great when accessed from their respective countries
  • And more...

Fortunately, there are a few gems and tools to make this very easy in Rails applications.

In this blog post, we’ll show you 4 different ways to take website screenshots using Rails. So whether you're a beginner or an experienced developer looking to integrate screenshots into your next app, read on for some tips and tricks!

4 Ways to Take Screenshots Programmatically with Rails

In this article, we'll cover 4 of the most popular ways to take screenshots programmatically. In our examples, we'll create a simple app that lets a user fill out a text box with a URL of their choice and click a button to retrieve and view the screenshot.

We'll be using Rails 7 throughout this article. However, these examples should work for older versions of Rails as well. We'll cover the following options:

  1. Puppeteer & Grover
  2. Cloudinary + URL2png
  3. html2canvas
  4. Urlbox

First up, Puppeteer & Grover...

Puppeteer & Grover

Puppeteer is an API that allows a user to control Chrome or Chromium in either headless or full (non-headless) mode. It’s a versatile package with many features and is commonly used to take screenshots.

To best use Puppeteer with Rails, there’s a handy gem built on top of Puppeteer called Grover. Grover makes it easy to call a .to_pdf type method to create a screenshot in the format of your choosing.

Basic set-up and configuration

Let’s set up the project by firing up a new Rails app and moving into our new app. In the terminal, we'll add the following command:

rails new puppeteer && cd puppeteer

Next, we’ll need to install Puppeteer and Grover to take screenshots. In your gemfile, add the following line:

gem grover

Then go ahead and run bundle install.

We'll also want to get Puppeteer installed, so type yarn add puppeteer into your terminal to set up your project with Puppeteer.

Adding a route and creating our view

That's it for the configuration work. Our next step is to create a basic view where we'll be able to see our text field and button. First up, we'll add a default route so we can see our form as soon as we fire up our server. In your routes.rb file, add root "screenshot#show" before the last end tag.

Before we load our page, we'll want to make sure that we have something to show our visitors. In the app/views/ folder, screen a folder called screenshot and inside it, add a file called show.html.erb.

Inside this show view, we'll add a form to capture a URL.

<%= form_with(url: root_path, method: :get) do |form| %>
	<%= form.text_field :screenshot_url %>
	<%= form.submit %>
<% end %>
 
<% if @image %>
	<%= image_tag "data:image/png;base64,#{Base64.strict_encode64(@image)}" %>
<% end %>

This form will submit to the show method in our soon to be created screenshot_controller file.

Then, if Puppeteer comes back with an image, we'll display it in an image tag.

Setting up the controller

Now that we have a show view, we'll need a controller action for it. In app/controllers/ create a file called screenshot_controller.rb. Inside that file, drop the following code:

class  ScreenshotController < ApplicationController
	def  show
		if  params[:screenshot_url]
			url  =  ActionController::Base.helpers.sanitize(params[:screenshot_url])
			@image  =  Grover.new(url).to_png
		end
	end
end

This code will take the URL the user entered in the form (if it exists) and convert it into a .png with Grover. @image will give us a PNG data file that we'll then take and display in the view.

Go ahead and start your rails server, enter in the URL of a site you want to screenshot, click the button, and watch as it appears in the view. Pretty cool, huh?

If you want to customize your configuration further, you can update an initializer file with the options you prefer.

To produce the image above, ours is set to the following:

# config/initializers/grover.rb
Grover.configure  do  |config|
	config.options  =  {
		user_agent:  'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
		emulate_media:  'screen',
		bypass_csp:  true,
		media_features:  [{  name:  'prefers-color-scheme',  value:  'dark'  }],
		vision_deficiency:  'deuteranopia',
		extra_http_headers:  {  'Accept-Language':  'en-US'  },
		cache:  false,
		timeout:  0,  # Timeout in ms. A value of `0` means 'no timeout'
		launch_args:  ['--font-render-hinting=medium'],
		wait_until:  'domcontentloaded'
	}
end
screenshot taken with Puppeteer with vision deficiency preview

Benefits and Drawbacks of Using Puppeteer for Screenshots

The advantage of Puppeteer is that it is well maintained and has plenty of options to customize your screenshots with - for example, color blindness simulation, PDF support, setting locations, and disabling javascript.

However, because it is so versatile, it’s easy to get lost in the amount of features and options you can choose from. Plus, If you’re going to be taking lots of screenshots, you may need to maintain your own cluster which can increase the number of systems you’ll need to spin up and maintain.


Cloudinary + URL2png

Cloudinary is a fantastic service for storing and serving media files. They also integrate with a screenshot service called URL2png to make taking screenshots easy.

For this example, we'll need to do a bit more setup than Puppeteer.

  1. You'll first need to sign up for a Cloudinary account.
  2. Once you have your account set up, you'll need to register for the URL2png add-on through their website.

One important thing to note is that URL2png does not offer free versions on their homepage, but with a Cloudinary account, you can get up to 50 free screenshots.

Basic set up and confirmation

Before we get into installing the gem, let's first get a fresh Rails app started by typing the following into the terminal:

rails new cloudinary && cd cloudinary

In your gemfile, add the following line:

gem cloudinary

Once it's in your gem file, run bundle install.

In order to access Cloudinary through its API, you'll also need a way to send Cloudinary your API key and secret. When you're logged in to the Cloudinary application, you can download your cloudinary.yml file at https://cloudinary.com/console/cloudinary.yml & add it to your config folder. Beware! This file contains secrets so if you’re following these steps for a production app, please replace your keys with a reference to the credential file where you’ll keep them safe!

Views, controllers, and route

Similar to Puppeteer above, we'll now set up a basic route, view, and controller action. We'll create a root route in routes.rb file by adding root "screenshot#show" before the last end tag.

Then, in the app/views/ folder, screen a folder called screenshot and inside it, add a file called show.html.erb and add the following code to it:

<%= form_with(url: root_path, method: :get) do |form| %>
	<%= form.text_field :screenshot_url %>
	<%= form.submit %>
<% end %>
 
<% if @screenshot_url %>
	<%= cl_image_tag(@screenshot_url.to_s , :sign_url => true, :type => "url2png") %>
<% end %>

That's it for the view. To be able to access that view with the route we've created, we'll need to go to app/controllers/ and create a file called screenshot_controller.rb. Inside that file, drop the following code:

class  ScreenshotController < ApplicationController
	def  show
		if  params[:screenshot_url]
			@screenshot_url  =  params[:screenshot_url]
		end
	end
end

Start your Rails server with rails s and you should be able to enter a url and get a screenshot of it.

Benefits and Drawbacks of Using Cloudinary & URL2png for Screenshots

We'd recommend the Cloudinary + URL2png option if you already use Cloudinary and need a handful of screenshots or want to take advantage of the many image transformations Cloudinary offers.

However, you'll need to manage multiple accounts and, if you want to take more than 50 screenshots, will need to sign up for a plan with URL2png and depending on your usage, potentially a plan with Cloudinary as well.


html2canvas

While not a gem, html2canvas is easy to integrate into a Rails app with a little sprinkling of javascript with help from StimulusJS.

However, html2canvas only lets you take screenshots of your own app. So we'll modify our screenshot button app a little here.

Let's get set up. Just like the previous options, we'll create a new app, set up our app, create our view/route/controller and try it out.

Setup

Because html2canvas is a JavaScript library, we're going to use StimulusJS to make integrating all of the JavaScript-related pieces really easy for ourselves.

In the terminal, run:

rails new htm2canvas && cd html2canvas

Then, inside your gemfile, add gem 'stimulus-rails' then bundle install. This will create a few files for you, including hello_controller.rb which we'll use in just a few steps.

We'll then add html2canvas by installing it via npm with the following command:

npm install html2canvas

Because we're using Rails 7, we can pin html2canvas to our import map by running:

./bin/importmap pin html2canvas

Adding a route and creating our view and controller files

Similar to the examples above, we'll add a basic route, view, and controller action.

First up, create a root route in routes.rb file by adding root "screenshot#show" before the last end tag.

Then, create a new file called show.html.erb in app/views/screenshot. We'll update show.html.erb to look like this:

<div data-controller="hello">
  <button data-action="click->hello#capture">Get screenshot</button>
</div>

The data-controller tells StimulusJS we want to use the controller named "hello" that's located at app/javascript/controllers/hello_controller.js. Inside that file, we will have a method called capture that will use html2canvas to take a screenshot of our site. Remember, html2canvas can not take screenshots of external URLs.

Inside hello_controller.js we'll add the following:

import { Controller } from "@hotwired/stimulus";
import html2canvas from "html2canvas";
 
export default class extends Controller {
  capture() {
    html2canvas(document.body).then(function (canvas) {
      var screenshot = canvas.toDataURL("image/jpg");
      window.open(screenshot, "_blank");
    });
  }
}

Now, when you run your Rails server, you should see a button that can take a screenshot of your application and display it in a new window.

Benefits and Drawbacks of Using html2canvas for Screenshots

Again, the use case for html2canvas is rather narrow and does not offer many customizations. However, for simple screenshots of your own application, html2canvas may be just what you're looking for.


Urlbox

Next up is Urlbox. Setup for Urlbox is easier than others for this example. They're also working on a gem that'll help speed this process up even faster.

Let's spin up a new app and call it urlbox

rails new urlbox && cd urlbox

Adding a route and creating our view and controller files

Similar to the examples above, we'll add a basic route, view, and controller action.

First up, create a root route in routes.rb file by adding root "screenshot#show" before the last end tag.

Then, create a new file called show.html.erb in app/views/screenshot. We'll update show.html.erb to look like this:

<%= form_with(url: root_path, method: :get) do |form| %>
	<%= form.text_field :screenshot_url %>
	<%= form.submit %>
<% end %>
 
<%= image_tag @image if @image %>

This will create a button that submits the user entered URL address to Urlbox and then displays it if it exists.

In the app/controlls/screenshot_controller.rb, we'll add the code below. While we'll put this in the controller for this example, you could extract this into a concern or as a module in you /lib folder.

class  ScreenshotController < ApplicationController
	require  'openssl'
	require  'open-uri'
 
	def  show
		if  params[:screenshot_url]
			url  =  ActionController::Base.helpers.sanitize(params[:screenshot_url])
			@image  =  urlbox(url,  width:  1920,  height:  1080,  block_ads:  true,  hide_cookie_banners:  true,  retina:  true)
		end
	end
 
	private
 
	def  encodeURIComponent(val)
		ERB::Util.url_encode(val)
	end
 
	def  urlbox(url,  options={},  format='png')
		urlbox_apikey  =  'pasteyourAPIkeyhere'
		urlbox_secret  =  'yoursecretkeygoeshere'
 
		query  =  {
			:url  =>  url,  # required - the url you want to screenshot
			:force  =>  options[:force],  # optional - boolean - whether you want to generate a new screenshot rather than receive a previously cached one - this also overwrites the previously cached image
			:full_page  =>  options[:full_page],  # optional - boolean - return a screenshot of the full screen
			:thumb_width  =>  options[:thumb_width],  # optional - number - thumbnail the resulting screenshot using this width in pixels
			:width  =>  options[:width],  # optional - number - set viewport width to use (in pixels)
			:height  =>  options[:height],  # optional - number - set viewport height to use (in pixels)
			:quality  =>  options[:quality],  # optional - number (0-100) - set quality of the screenshot
			:block_ads  =>  options[:block_ads],  # optional - boolean - block ads from loading
			:hide_cookie_banners  =>  options[:hide_cookie_banners],  # optional - boolean - hide cookie banners
			:retina  =>  options[:retina],  # optional - boolean - return a retina screenshot
			# add more options here as you wish
		}
 
		query_string  =  query.
			sort_by  {|s|  s[0].to_s  }.
			select  {|s|  s[1]  }.
			map  {|s|  s.map  {|v|  encodeURIComponent(v.to_s)  }.join('=')  }.
			join('&')
			token  =  OpenSSL::HMAC.hexdigest('sha1',  urlbox_secret,  query_string)
			"https://api.urlbox.io/v1/#{urlbox_apikey}/#{token}/#{format}?#{query_string}"
	end
end

Don't forget to swap your own API key in for the fillers in the def urlbox method!

Once you have this in your controller, fire up your server by running rails server in your terminal. Enter a URL (ex. https://wsj.com) and a full-size, retina-quality screenshot of the site will appear - without ads and any cookie banners you would usually get. Heads up, because the image quality is so high, it can take a couple of seconds to load!

screenshot taken of the NYT sans ads with Urlbox

Using Webhooks to Render Screenshots Asynchronously with Urlbox

Urlbox offers a webhook option to make rendering screenshots asynchronously incredibly easy. Simply pass your webhook URL in as an option and Urlbox will send a POST request back to that url with data about the screenshot in JSON format once it has completed rendering. 🤯

Let's add it to our sample app.

First up, in your routes.rb file, add the following line:

post 'screenshot/urlbox_webhook', to: 'screenshot#urlbox_webhook'

This will create a route that Urlbox will be able to POST the screenshot data to. We'll do more with this route shortly.

In order to test out our webhook, we'll use ngrok. If you haven't installed it yet, there are great instructions on how to do so here.

Let's fire ngrok up with the following command:

ngrok http 3000

Once we have ngrok up and running, we can start with making some modifications to our options and call to Urlbox in with the following lines in screenshot_controller.rb:

In the def urlbox method inside the query definition where we have our existing options, we'll add the following option:

:webhook_url => options[:webhook_url]

We'll then update our call to Urlbox to include information for our new option: @image = urlbox(url, width: 1920, height: 1080, block_ads: true, hide_cookie_banners: true, retina: false, dark_mode: true, webhook_url: "https://12a3-45-67-89-000.ngrok.io/screenshot/urlbox_webhook")

Note: you'll want to replace the numbers & letters part of ngrok URL with the ones visible in your ngrok terminal.

Next up, we'll create a controller action for our new webhook route. This will allow us to take the data Urlbox gives us and do things with it.

In screenshot_controller.rb we'll add the following under our show action:

def  urlbox_webhook
  data  =  JSON.parse(request.body.read)
  # Do something cool with our asynchronously rendered screenshot
end

That's it! Now, when we ask Urlbox for our screenshot, we'll also get a POST request. If you visit your web interface's inspect page (typically at http://127.0.0.1:4040/inspect/http), you should be able to see the following:

{
  "event": "render.succeeded",
  "renderId": "19a59ab6-a5aa-4cde-86cb-d2b23302fd84",
  "result": {
    "renderUrl": "https://renders.urlbox.io/urlbox1/renders/6215a3df94d7588f7d910513/2022/7/6/19a59ab6-a5aa-4cde-86cb-d2b23302fd84.png",
    "size": 34097
  },
  "meta": {
    "startTime": "2022-07-06T17:49:18.593Z",
    "endTime": "2022-07-06T17:49:21.103Z"
  }
}

Urlbox's webhook option is a great way to process screenshots in the background so that you and your users can continue to perform tasks while waiting for your Urlbox renderings to come back.

Benefits and Drawbacks of Using Urlbox for Screenshots

You may notice the different options where we define query above. Urlbox has a large amount of options to choose from including retina-quality images, ad blocking, selector targeting, and more. You can check out the long list of options here.

Plus, if you don't want to store your screenshots locally or in ActiveStorage, Urlbox lets you set an S3 bucket (docs here) that you've configured in your account so that you can send all your screenshots there automatically. Once you have Urlbox set up, you can also connect it to Zapier so non-engineering staff can create workflows around the screenshots you're capturing.


Now go take some screenshots in Rails!

As you can see, there are multiple ways to generate screenshots in your Ruby on Rails apps. Some approaches like URL2png are good for simple, straightforward use cases while others, like Urlbox, allow for multiple options like S3 integration, webhooks, target selection, retina-quality captures, and more.

If you’re looking for an easy way to take website screenshots or PDFs and add them to your Rails project, give Urlbox a try. It’s free to get started, and as you can see in the example above, setup is super easy. Just sign up for an account, generate a token, and start taking screenshots!