Mantenha os screenshots da sua app atualizada com RSpec

Faça com que a sua build atualize os screenshots da sua app

#automated-test, #dev, #ruby

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.

  1. Você já tem os testes automatizados, e
  2. 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/docs e 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]) }