Mantenha os screenshots da sua app atualizada com RSpec
Se você me conhece, você me conhe por isto:
SEMPRE escreve testes automatizados, SEMPRE. Ponto.
Não apenas test unitário, mas todo tipo de test possível.
Mas eu também compartilho dessa ideia:
Se você está fazendo o mesmo de novo e de novo, por que não fazer com que uma máquina fazer o seu trabalho?
Com essas duas ideias jundas é possível tirar (e manter atualizado) os screenshots para documentação de qualquer app.
- Você já tem os testes automatizados, e
- Você pode tirar screenshot enquanto roda os tests
Vou mostrar aqui o que fiz para um dos meus projetos.
O model Doc
O primeiro código é para o model Doc, com ele é possível chamar o método all para ter a lista de documentação:
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
É possível que seja mais DRY, mas por enquanto está funcionando.
O DocsController
O controller é simples:
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
É mandatório criar as variáveis @title and @description, pois o layout docs precisa disso para setar as metatags.
A rote para isso é mais simples ainda:
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>
O layout aqui é mínimo, expanda-o de acordo com suas necessidades.
app/views/docs/index.html.erb:
<ul>
<% @docs.each do |doc| %>
<li><%= link_to doc.title, doc.url %></li>
<% end %>
</ul>
E app/views/docs/show.html.erb:
<%= render "header" %>
<h2><%= @doc.title %></h2>
<%= render file: @doc.path %>
Escrevendo a documentação
Primeiro, tire os screenshots. Mude o seu teste RSpec para algo do tipo:
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
Lembre-se: Você tem que configurar o tamanho do browser somente uma vez. E, caso queira, esse código pode ficar ainda mais DRY, basta criar um helper no spec/support.
ATENÇÃO: A primeira vez que rodar os testes você precisa criar o diretório app/assets/images/screenshots.
Se você prestou atenção ao meu código notou que toda a documentação está dentro do diretório docs. Mas além disso também existe um arquivo index.yml.
Eu fiz isso para não ter que adicionar mais um dependência e nem ter que criar um parser por mim mesmo.
O arquivo index.yml se parece com isso:
create_account:
title: "Create an Account"
description: "How to create an Account"
Você pode ter ainda um atributo lang caso sua app seja multi idiomas. Apenas certifique-se de mudar todo o resto.
Escrevendo a documentação ou por que tive todo esse trabalho?. 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" %>
[ ... ]
Como você pode ver a melhor parte é a documentação em si, é possível usar helpers, models, e até mesmo acessar o banco de dados (apenas certifique-se de adicionar uma camada de cache).
Isto é tudo. Eu tenho essa estrutura numa das minhas apps, mas também criei um racunho para no organize2, veja este commit.
Notas finais
- Como falei antes, se sua app for multi idiomas é bem fácil de dar suporte para isso.
- Você deve decidir quando atualizar os screenshots. Quando o RSpec roda sua build na maioria das vezes ele vai atualizar o arquivo de screenshot, mesmo que nenhuma mudança tenha sido feita.
- Se estiver muito chato de atualizar os screenshots configure o seu CI para faer isso para você. OU como parte do seu deploy, todos os testes podem ficar em
spec/features/docse quando o deploy for feito, basta rodar os testes só para este diretório. - Adicione isto para o seu
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]) }