Keep screenshots of your app update with RSpec
If you know me, you know me for one thing:
ALWAYS write automated test for your software, ALWAYS. Period.
And not only unit test but all kind of test you can.
But I also share this idea:
If you are doing the same thing again and again, why not make a machine do it?
With these two things together you can come with a solution for documenting your app.
- You have the automated test already, and
- You can take screenshots within the tests
I will show here what I did in one of my projects.
The Doc
model
First thing I needed was a model to call all
, I came up with this content:
class Doc
attr_reader :doc_id
def initialize(doc_id)
@doc_id = doc_id
end
def self.all
list.keys.map { |doc_id| Doc.new(doc_id) }
end
def self.list
if Rails.env.production?
@list ||= Psych.load(File.read(Rails.root.join(BASE_DIR, "index.yml")))
else
Psych.load(File.read(Rails.root.join(BASE_DIR, "index.yml")))
end
end
def title
list.fetch(@doc_id) { { "title" => "Not found" } }["title"]
end
def description
list.fetch(@doc_id) { { "description" => "" } }["description"]
end
def path
return not_found if invalid?
@path ||= Rails.root.join(BASE_DIR, @doc_id + ".html.erb")
end
def url
"/#{BASE_DIR}/#{@doc_id}"
end
def invalid?
!valid?
end
private
BASE_DIR = "docs".freeze
def not_found
Rails.root.join(BASE_DIR, "not_found.html.erb")
end
def whitelist
list.keys
end
def valid?
whitelist.include?(@doc_id)
end
def list
self.class.list
end
end
You can make it DRYer, but for now, it is working.
The DocsController
The controller is simpler:
class DocsController < ApplicationController
layout "docs"
def index
@docs = Doc.all
@title = "Docs"
@description = ""
end
def show
@doc = Doc.new(params[:id])
@title = @doc.title
@description = @doc.description
if @doc.invalid?
render "show", status: 404
end
end
end
It is mandatory to set @title
and @description
because the docs
layout needs them to correct set the metatags.
The route for this is very simple:
resources :docs, only: %i[index show]
Views
app/views/layouts/docs.html.erb
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MyApp - <%= @title %></title>
<meta name="description" content="<%= @description %>">
</head>
<body>
<%= yield %>
</body>
</html>
This is a proof of concept layout, so expand it as you need.
app/views/docs/index.html.erb
:
<ul>
<% @docs.each do |doc| %>
<li><%= link_to doc.title, doc.url %></li>
<% end %>
</ul>
And app/views/docs/show.html.erb
:
<%= render "header" %>
<h2><%= @doc.title %></h2>
<%= render file: @doc.path %>
Writing the documentation
First, you need the screenshots. Change your RSpec code to have something like this:
page.driver.browser.manage.window.resize_to(1024, 768)
page.driver.save_screenshot Rails.root.join("app/assets/images/screenshots/create_account_01.png"), full: true
Remember: you have to set the browser size only once. And you can make this code even DRYer by creating a helper or something in spec/support
.
WARNING: The first time you run the test you will have to create the directory app/assets/images/screenshots
.
If you had paid attention to my code you noticed that all the documentation is inside the docs
directory. But besides that, it is also required an index.yml
file.
I did not want to have another dependency to set a structure like Jekyll which you have the metadata on the top of the file and I did not want to have to much work doing the parser.
The index.yml
looks like this:
create_account:
title: "Create an Account"
description: "How to create an Account"
You can have an extra lang attribute if your app is multi-language. Just make sure to change everything else.
Writing the documentation. Why I had so much work to do this?
<p>
After loggining click in <em><%= Account.model_name.human(count: 2) %></em>:
</p>
<%= image_tag "screenshots/create_account_01.png" %>
[ ... ]
As you can see the best part is to write the documentation, you can use the regular helpers, models, and even access the database (just make sure you have a caching mechanism in front of it).
That is all. I already have this structure in one of my apps, but I also created a draft of this feature in my organize2 repo, check this commit.
Final notes
- As I mentioned before, if your app is multi-language it is pretty easy to add support to that
- It is up to you when to update the screenshots. When you run your build RSpec will regenerate the image even if you have not changed anything at all
- If you are too bored to manually update the screenshots add it to your CI, and upload it to S3 or other hosting services. OR as part of your deploy, you can set write all the documentation test in
spec/features/docs
for example and run only that tests when deploying - Add this to your
config/initializers/assets.rb
:
Rails.application.config.assets.paths << Rails.root.join("app/assets/images/screenshots")
Rails.application.config.assets.precompile << Rails.root.join("app/assets/images/screenshots")
.entries
.map(&:to_s)
.select { |path| path.match(%r[png]) }