Como criar relatórios no Nightwatch JS v1.7.12

Olá pessoal! Hoje venho compartilhar como consegui gerar relatórios personalizados no Nightwatch.js utilizando a versão 1.7.12.

Essa necessidade surgiu a partir de uma demanda no meu trabalho. Apesar do Nightwatch exibir no console todas as informações sobre os testes — como quais passaram, quais falharam, tempo total de execução, entre outros —, ele não oferecia uma forma nativa de gerar relatórios visuais, pelo menos não nessa versão.

Para mim, como desenvolvedor, isso não era exatamente um problema. Porém, o gestor da minha equipe precisava de algo mais visual e compartilhável sobre os resultados dos testes automatizados. Esses relatórios são úteis para indicar quais cenários passaram e quais quebraram, possibilitando um melhor controle sobre o processo de homologação do sistema entre as equipes.

Se você já tem familiaridade com ferramentas de testes automatizados, talvez esteja se perguntando:

“Por que não usar uma biblioteca de relatórios como o Allure ou o Mochawesome?”
“Ou verificar se o próprio Nightwatch não possui suporte a isso?”

E a resposta está na versão. Esse projeto em específico roda em um sistema legado, usando Node.js 12, o que me obrigou a utilizar a versão 1.7.12 do Nightwatch. Atualizar todo o ecossistema exigiria um esforço considerável por parte da empresa.

Sistemas legados acabam ficando assim quando, ao longo dos anos, vários desenvolvedores entram e saem do projeto, instalam bibliotecas sem controlar versões, não documentam decisões importantes e deixam o sistema cada vez mais difícil de manter. O resultado? Um verdadeiro “monstro do Frankenstein”, onde alterar algo em um canto pode quebrar o sistema em outro.

Uma solução alternativa

Após avaliar opções como o Allure e o Mochawesome (que não eram compatíveis com Node 12) e constatar que o Nightwatch só passou a oferecer suporte nativo a relatórios a partir da versão 2.2, precisei buscar outra alternativa.

Foi então que encontrei um excelente artigo no blog TestersDock, onde mencionavam uma solução simples e eficaz, baseada em um projeto criado por Denis Denisov. A proposta era utilizar o template engine Handlebars para gerar relatórios HTML personalizados de forma simples.

Objetivo

O objetivo era gerar um arquivo.html visual e organizado, contendo os resultados dos testes automatizados:

  • Navegador utilizado;

  • Data e hora da execução;

  • Tempo total;

  • Total de casos de teste executados;

  • Quantos passaram, falharam ou apresentaram erro.

Pacotes utilizados

npm install handlebars

O Handlebars é um template engine que permite gerar HTML dinâmico a partir de dados. Ele é usado para preencher automaticamente os valores no layout HTML do relatório — como tempo total, quantidade de testes passados, etc.

2. fs (nativo do Node.js)

Não precisa instalar. É o módulo de sistema de arquivos do Node. Aqui, ele é usado para:

  • Ler o conteúdo do arquivo .hbs (template do HTML)
  • Escrever o relatório final como um arquivo .html no disco

3. path (nativo do Node.js)

Também já vem com o Node.js. Usado para manipular caminhos de arquivos e diretórios de forma segura e multiplataforma, evitando erros com barras invertidas (\) ou barras normais (/) entre sistemas operacionais.

O que é o arquivo .hbs?

O arquivo html-reporter.hbs é um template Handlebars — por isso sua extensão .hbs. Ele funciona como um modelo de HTML contendo placeholders que serão preenchidos dinamicamente com os dados reais dos testes.

Exemplo de placeholder:

<strong>Browser:</strong> {{browser}}

No momento da geração do relatório, o {{browser}} será substituído por “Chrome”, “Firefox”, ou qualquer navegador que tenha sido utilizado.

Passo a passo de implementação

1. Crie o arquivo html-reporter.hbs na raiz do seu projeto e cole nele o conteúdo do template disponibilizado abaixo:

<!DOCTYPE html>
<html>

<head>
  <title>Test Results - {{browser}}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <style>
    html,
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 0;
    }

    body {
      padding: 10px 40px;
    }

    table {
      width: 100%;
      margin-bottom: 20px;
    }

    td {
      padding: 7px;
      border-top: none;
      border-left: 1px black solid;
      border-bottom: 1px black solid;
      border-right: none;
    }

    td.pass {
      color: #003b07;
      background: #86e191;
    }

    td.skip {
      color: #7d3a00;
      background: #ffd24a;
    }

    td.fail {
      color: #5e0e00;
      background: #ff9c8a;
    }

    tr:last-child {
      border-top: 1px black solid;
    }

    tr:last-child td {
      border-top: 1px black solid;
    }

    tr:first-child td {
      border-top: 1px black solid;
    }

    td:last-child {
      border-right: 1px black solid;
    }

    tr.overview td {
      padding-bottom: 0px;
      border-bottom: none;
    }

    tr.overview.last td {
      padding-bottom: 3px;
    }

    ul.assertions {
      list-style-type: none;
    }

    span.error {
      color: #AD2B2B;
    }

    span.success {
      color: #53891E;
    }

    .stacktrace {
      display: inline;
    }

    .stacktrace code {
      display: none;
    }

    #nightwatch-logo {
      position: absolute;
      top: 20px;
      right: 33px;
      width: 70px;
      height: 75px;
      background: transparent url('http://nightwatchjs.org/img/logo-nightwatch.png') no-repeat;
      background-size: 70px 75px;
    }

    /* Estilos para impressão */
    @media print {
      body {
        padding: 0;
        margin: 0;
        color: black;
        background: white;
      }

      #nightwatch-logo {
        display: none;
      }

      a::after {
        content: "";
      }

      .stacktrace code {
        display: block;
        white-space: pre-wrap;
      }

      .stacktrace a {
        display: none;
      }
    }
  </style>
</head>

<body>
  <h1>Test Results</h1>

  <table border="0" cellpadding="0" cellspacing="0">
    <tr class="overview">
      <td colspan="3" title="{{browser}}"><strong>Browser:</strong> {{browser}}</td>
    </tr>
    <tr class="overview">
      <td colspan="3"><strong>Timestamp:</strong> {{timestamp}}</td>
    </tr>
    <tr class="overview">
      <td colspan="3"><strong>Total Time:</strong> {{totalTime}}</td>
    </tr>
    <tr class="overview last">
      <td colspan="3"><strong>Assertions:</strong> {{totalAssertions}}<br></td>
    </tr>
    <tr class="overview last">
      <td colspan="3"><strong>Test Cases:</strong> {{totalCases}}<br></td>
    </tr>
    <tr>
      <td class="pass"><strong>{{results.passed}}</strong> passed</td>
      <td class="skip"><strong>{{results.errors}}</strong> errors</td>
      <td class="fail"><strong>{{results.failed}}</strong> failures</td>
    </tr>
    <tr>
      <td class="pass"><strong>{{testCasesPassed}}</strong> test cases passed</td>
      <td class="skip"><strong>{{testCasesWithError}}</strong> test cases with error</td>
      <td class="fail"><strong>{{testCasesFailed}}</strong> test cases failed</td>
    </tr>
  </table>

  {{#each results.modules}}
  <h2>{{@key}}</h2>

  {{#each this.completed}}
  <h3>{{@key}}</h3>
  <p>
    {{#if this.failed}}
    <span class="error">&#10006; {{@key}}</span> – <strong>{{this.failed}}</strong> assertion(s) failed.
  <ul class="assertions">
    {{#each this.assertions}}
    <li>
      {{#if failure}}
      <span class="error">&#10006;</span>
      {{else}}
      <span class="success">&#10004;</span>
      {{/if}}

      {{this.message}}

      {{#if this.failure}}
      <br><span class="error">{{this.failure}}</span>
      {{/if}}

      {{#if this.stacktrace}}
      <div class="stacktrace">
        <a href="#">view stacktrace</a>
        <code><pre>{{this.stacktrace}}</pre></code>
      </div>
      {{/if}}
    </li>
    {{/each}}
  </ul>
  {{else}}
  <span class="success">&#10004; {{@key}}</span> – All assertions passed.
  {{/if}}
  </p>

  <p>
    {{#if this.failed}}
    <span class="error"><strong>FAILED:</strong></span>
    <span class="error"><strong>{{this.failed}}</strong></span> assertions failed and
    <span class="success"><strong>{{this.passed}}</span></strong> passed. ({{this.time}}s)
    {{else}}
    <span class="success"><strong>OK.</strong></span>
    <span class="success"><strong>{{this.passed}}</strong></span> assertions passed. ({{this.time}}s)
    {{/if}}
  </p>
  {{/each}}

  {{#if this.skipped}}
  <h4>skipped</h4>
  <ul>
    {{#each this.skipped}}
    <li>{{this}}</li>
    {{/each}}
  </ul>
  {{/if}}

  <hr>
  {{/each}}

  <div id="nightwatch-logo"></div>

  <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
  <script>
    $(function () {
      $('div.stacktrace').on('click', 'a', function (evt) {
        evt.preventDefault();
        var $link = $(this);
        var $code = $link.parent().find('code');
        $code.is(':visible') ? $link.text('hide stacktrace') :
          $link.text('view stacktrace');
        $code.toggle();
      });
    });
  </script>
</body>

</html>

2. Crie o arquivo html-reporter.js com a lógica de leitura dos resultados e geração do relatório, um exemplo está disponível abaixo.

const fs = require('fs');
const path = require('path');
const handlebars = require('handlebars');

module.exports = {
  write: function (results, options, done) {

    const reportFilename = options.filename_prefix + (Math.floor(Date.now() / 1000)) + '.html';
    const reportFilePath = path.join(__dirname, options.output_folder, reportFilename);

    // Calcula o tempo total REAL de execução
    let totalTimeMs = 0;

    Object.values(results.modules).forEach(module => {
      if (module.completed) {
        Object.values(module.completed).forEach(testCase => {
          if (testCase.timeMs) {
            totalTimeMs += testCase.timeMs;
          }
        });
      }
    });

    let totalCases = 0;

    Object.values(results.modules).forEach(module => {
      if (module.completed) {
        totalCases += Object.keys(module.completed).length;
      }
    });
    
    const minutes = Math.floor(totalTimeMs / 60000);
    const seconds = Math.round((totalTimeMs % 60000) / 1000);
    const totalTimeFormatted = `${minutes}m ${seconds}s`;

    let testCasesPassed = 0;
    let testCasesFailed = 0;
    let testCasesWithError = 0;

    Object.values(results.modules).forEach(module => {
      if (module.completed) {
        Object.values(module.completed).forEach(testCase => {
          if (testCase.failed > 0) {
            testCasesFailed++;
          } else if (testCase.errors && testCase.errors.length > 0) {
            testCasesWithError++;
          } else {
            testCasesPassed++;
          }
        });
      }
    });

    fs.readFile('html-reporter.hbs', function (err, data) {
      if (err) throw err;

      const template = data.toString();

      const html = handlebars.compile(template)({
        results    : results,
        options    : options,
        timestamp  : new Date().toString(),
        browser    : options.filename_prefix.split('_').join(' '),

        totalAssertions: results.passed + results.failed + results.errors,

        totalCases: totalCases,

        totalTime  : totalTimeFormatted,

        testCasesPassed,
        testCasesFailed,
        testCasesWithError
      });

      fs.writeFile(reportFilePath, html, function (err) {
        if (err) throw err;
        console.log('Report generated: ' + reportFilePath);
        done();
      });
    });
  }
};

3. Execute os testes com o seguinte comando:

npx nightwatch tests --reporter html-reporter.js

Após a execução, um arquivo HTML será gerado automaticamente na pasta tests_output configurada no arquivo nightwatch.conf.js do seu projeto.

Resultado

O relatório HTML gerado terá a seguinte aparência:

Exemplo de relatório personalizado gerado pelo Handlebars.

Caso você queira, é possível personalizar livremente a aparência do relatório, você pode fazer isso editando o arquivo html-reporter.hbs para alterar: layout visual; cores; ícones; organização dos dados; etc.

O relatório gerado pode ser aberto em qualquer navegador e traz um resumo completo da execução:

  • Testes que passaram;
  • Testes que falharam;
  • Testes que apresentaram erro;
  • Tempo total de execução;
  • Total de test cases e assertions.

Essa abordagem traz clareza, rastreabilidade e profissionalismo para a execução dos testes automatizados, permitindo que gestores e times de desenvolvimento acompanhem com facilidade os status da aplicação em diferentes ciclos de entrega.

Créditos

Este projeto é uma adaptação da solução proposta por Denis Denisov, publicada originalmente em um gist. A ele, todo o crédito pela base da ideia e estrutura.

Conclusão

Trabalhar com sistemas legados nem sempre é fácil — e, na maioria das vezes, exige criatividade, paciência e conhecimento técnico para contornar limitações impostas por versões antigas de bibliotecas e ambientes desatualizados.

Foi justamente nessa situação que precisei pensar fora da caixa. Sem poder contar com ferramentas modernas de relatórios por conta das dependências do projeto, busquei uma solução alternativa que fosse simples, compatível com a stack atual e que entregasse exatamente o que o time precisava: um relatório visual, acessível e informativo.

Ao adaptar a ideia do Denis Denisov e personalizá-la para minha realidade, consegui entregar valor real ao time, trazendo visibilidade sobre a execução dos testes e facilitando a comunicação entre QAs, desenvolvedores e gestão. Mais do que apenas automatizar testes, conseguimos dar um passo importante em direção à maturidade do processo de qualidade do sistema.

Se você também está preso a um ambiente legado, saiba que ainda é possível inovar. Às vezes, as melhores soluções nascem justamente das limitações.

Gostou dessa abordagem? Já teve que contornar alguma limitação técnica em projetos legados? Comenta aqui embaixo, vou adorar trocar ideia com você sobre isso!

Compartilhe:

Davi Teixeira

Mestrando, Analista de Testes/QA e Desenvolvedor Web.

Todos os Posts

Davi Teixeira

QA e Desenvolvedor Web | Graduado em Sistemas de Informação

All Posts

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Posts Relacionados

Desenvolvedor de Software especializado em Desenvolvimento Front-end e Qualidade de Software.

Contato

Categorias

Copyright © 2025 - daviteixeiradev - Todos os direitos reservados.