Playwright, BDD, Cucumber & a minha opinião sobre isso

O que é BDD

BDD é um termo em inglês: “Behavior-Driven Development“, em português é equivalente a “Desenvolvimento Orientado por Comportamento” ou “Desenvolvimento Orientado a Comportamento“.

É uma técnica utilizada no desenvolvimento de software que tem como objetivo principal aproximar equipes técnicas e não técnicas, através de uma linguagem chamada “Gherkin” onde os comportamentos de um sistemas são descritos utilizando “Given-When-Then“, traduzindo: “Dado-Quando-Então” (com adição das palavras “And” e “But”, “E” e “Mas”).

Na prática, a descrição dos requisitos dos sistemas ficaria mais ou menos assim para uma tela de login:

Given the customer is on the sign-in page
When the <email> and <password> are informed
And the "OK" button is hit
Then the dashboard page is displayed
Dado que o usuário está na tela de login
Quando o <email> e a <senha> são preenchidos
E o botão "OK" é acionado
Então a página de tarefas é exibida

Todos os comportamentos do sistema são descritos de forma semelhantes a esta listada acima.

Para casos de testes, existem formas de listar quais dados podem ser usados para substituir os campos <email> e <password>/<senha>, conhecidas como “Scenario Outline” ou traduzindo: “Esquemas do Cenário” que é basicamente uma tabela de dados.

Existem também formas de listar “Background” ou “Pré-Condições” quando necessário.

Na automação de testes, o framework que suporta esta técnica é conhecido como “Cucumber” (a tradução para o Português é pepino mas chamamos de Cucumber mesmo – pronuncia-se quiu-câmber). O Cucumber faz a interpretação do código entre a linguagem Gherkin e a linguagem de programação do projeto.

Ao utilizar esse framework, é comumente necessário criar pelo menos:

  1. um arquivo chamado feature file com a descrição dos cenários como vimos acima;
  2. um arquivo chamado step definition, na linguagem de programação que você deseja (TypeScript, Java, Python, etc);
  3. um arquivo de configuração para o framework Cucumber;
  4. e os demais arquivos (page objects, data files, hooks, etc).

Qual é a minha opinião sobre BDD

Esta técnica se tornou muito popular e é comum encontrarmos muitas empresas que a utilizam na área de automação de testes.

Na minha experiência, a maioria destas empresas se perderam do objetivo principal (que seria aproximar pessoas técnicas e não técnicas), onde idealmente os Product Managers/Business Analysts – Gerentes de Produto/Analista de Negócios escreveriam estas regras para facilitar a compreensão de clientes, e os demais membros do time (engenheiros de desenvolvimento e testes) reutilizariam o que foi descrito para trabalhos futuros.

O que acontece na prática é que a forma de documentação de requisitos evoluiu bastante nos últimos anos. Tem empresas usando Postits (online), Excel, Jira, Confluence, outras usando Notion, outras usando Figma, etc, então não existe escrita dos cenários por comportamento e os engenheiros de teste acabam sendo os únicos a utilizar esta técnica.

Sendo assim, na maioria das vezes o uso de BDD na automação não agrega valor significativo ao projeto, causando a necessidade de instalar mais um framework e implementar mais um arquivo.

Mais um framework e mais um arquivo são tarefas que devemos evitar nos projetos sempre possível. A cada framework gera-se a instalação de mais uma dependência que será necessária ser executada para rodar os testes. Isso adiciona tempo de execução na sua etapa de testes (e se for configurado de forma errada pode onerar também o deploy do projeto). Sobre criar mais um arquivo, é a mesma ideia, é mais um lugar para dar manutenção e mais dados para serem baixados ao executar uma etapa de instalação em uma pipeline por exemplo, resultando em mais tempo.

Precisamos nos atentar ao tempo de execução de testes tanto local quanto em pipelines e tentar reduzi-lo o máximo possível.

É importante considerar que o BDD tem várias vantagens e várias desvantagens. Assim como decidir qual framework utilizar para um projeto deve considerar vários aspectos, decidir sobre o uso e a continuidade de uso de BDD também deve considerar vários aspectos.

Recentemente, eu trabalhei em um projeto onde o time estava com dificuldade de descrever cenários de testes para uma parte complexa do sistema que seria implementado utilizando RTL – React Testing Library – usado para testar componentes React. O time de desenvolvimento utilizou o padrão de escrita de BDD para ajudar nestes testes. Neste caso, o uso de BDD (parcialmente) foi uma excelente técnica para resolver este problema do projeto. Vou contar mais detalhes como lidamos com esta demanda de forma inteligente e prática logo abaixo neste post, na sessão “Alternativas ao BDD no Playwright – BDD sem Cucumber”.

Como implementar BDD com Cucumber usando Playwright

Confira o meu repositório de exemplo.

O Cucumber não tem suporte nativo para TypeScript, você precisa usar JavaScript ou precisa de um transpiler (tradutor) para JavaScript. Como vamos usar TypeScript, vamos usar o ts-node para isso.

Se seu projeto já utiliza algum outro transpiler (como Babel), não será necessário instalar e/ou configurar o ts-node.

Os passos abaixo consideram um projeto sem nenhum transpiler configurado.

Outra opção seria usar o playwright-bdd que já tem toda a configuração de transpiler. Optei por não utilizar este framework para entendermos um pouco como tudo funciona.

1. Instalação

1.1. Instalar o Playwright (com TypeScript): npm init playwright@latest

1.2. Instalar o Cucumber: npm install -D @cucumber/cucumber

1.3. Instalar o typescript: npm install -D typescript

1.4. Instalar o ts-node: npm install -D ts-node

Explicações:

  • o parâmetro -D instala como devDependency no package.json. Isso significa que num projeto completo, essas dependências não serão compiladas para gerar o “executável” do projeto. Como estamos rodando testes, não existe “executável” neste projeto, portanto essas dependências devem ficar nesta sessão.
  • precisamos instalar o typescript e o ts-node pois eles farão a tradução do TypeScript para JavaScript para que o Cucumber consiga interpretar os comandos.

2. Configuração

2.1. Criar o tsconfig.json na raiz do projeto: npx -p typescript tsc --init

2.2. Abrir o tsconfig.json

2.2.1. Remover o comentário de "allowSyntheticDefaultImports": true,

2.2.2. Remover o comentário de "resolveJsonModule": true,

2.2.3. Ajustar o target para “ESNext”: "target": "ESNext",

2.3. Criar o arquivo cucumber.mjs na raiz do projeto: criar novo arquivo manualmente mesmo

export default {
  format: ['html:bdd-tests/reports/cucumber-report.html'],
  parallel: 2,
  paths: ['bdd-tests/features/**/*.feature'],
  require: ['bdd-tests/step-definition/**/*.ts','bdd-tests/support/**/*.ts'],
  requireModule: ['ts-node/register'],
}

Explicações:

  • No arquivo tsconfig.json ajustamos as configurações para a compilação ser realizada de forma correta já que a sintaxe muda dependendo de qual versão do JavaScript você utiliza. Para mais informações veja aqui.
  • No arquivo cucumber.mjs, definimos o formato do report e o local onde será gravado, quantos testes em paralelo, o caminho dos feature files, o caminho dos step definition files e dos arquivos de suporte e fazemos o require do ts-node para ser utilizado durante a compilação. Para mais configurações veja aqui.
  • A extensão .mjs significa “(M)odular (J)ava(S)cript”. E utiliza módulos ECMAScript ao invés de módulos CommonJS. A principal diferença é o uso de import & export (no ECMAScript) ao invés de require & module.exports (no CommonJS):
CommonJS
ECMAScript

Fonte: https://codingforseo.com/blog/mjs-vs-cjs-files/

IMPORTANTE: Nem começamos a criar os arquivos de teste e eu já cansei! rsrs

3. Implementação dos arquivos de teste

3.1. Criar o Feature file: /bdd-tests/features/example.feature

Feature: Playwright website

Scenario: Has Title
    Given I am at the playwright website
    # When I open the page
    Then the title has the text "Playwright"

Scenario: Get Started Link
    Given I am at the playwright website
    When I click at "Get Started"
    Then the URL has the text "intro"

O primeiro cenário abre a página do Playwright e verifica se o título tem a palavra “Playwright”.

O segundo cenário abre a página do Playwright, clica no botão “Get Started” e confirma se a URL tem o texto “intro”.

Estes são os mesmos cenários implementados no arquivo /tests/example.spec.ts que é criado ao instalar o Playwright.

3.2. Criar o arquivo que integra Playwright e Cucumber: /bdd-tests/support/custom-world.ts

import { setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber';
import { BrowserContext, Page, PlaywrightTestOptions } from '@playwright/test';

export interface CucumberWorldConstructorParams {
  parameters: { [key: string]: string };
}

export interface ICustomWorld extends World {
  context?: BrowserContext;
  page?: Page;
  playwrightOptions?: PlaywrightTestOptions;
}

export class CustomWorld extends World implements ICustomWorld {
  constructor(options: IWorldOptions) {
    super(options);
  }
}

setWorldConstructor(CustomWorld);

O Cucumber utiliza a classe World. Esta classe possui um escopo isolado e exposto para cada step e hooks através do “this“. Ou seja, permite o reuso de variáveis e métodos porém utiliza uma nova instância para cada execução de teste.

Neste arquivo, estamos criando uma interface ICustomWorld para ser usada nos testes e ter acesso ao BrowserContext, Page e PlaywrightTestOptions do Playwright, permitindo interagir com o browser e seus elementos.

3.3. Criar o arquivo que iniciará e encerrará o browser: bdd-tests/support/hooks.ts

import { Before, After, BeforeAll, AfterAll } from '@cucumber/cucumber';
import { chromium, ChromiumBrowser } from '@playwright/test';
import { ICustomWorld } from './custom-world';

declare global {
    var browser: ChromiumBrowser;
}

BeforeAll(async function () {
    global.browser = await chromium.launch({
        headless: false,
    });
});

AfterAll(async function () {
    await global.browser.close();
});

Before(async function (this: ICustomWorld) {
    this.context = await global.browser.newContext();
    this.page = await this.context?.newPage();
});

After(async function (this: ICustomWorld) {
    await this.page?.close();
    await this.context?.close();
});

Neste caso iremos utilizar somente o chromium para simplificar o exemplo. Mas você pode adicionar quaisquer browser que desejar (que seja suportado pelos frameworks). Veja as Referências no final do artigo para exemplos.

3.4. Criar o step definition file: bdd-tests/step-definition/example.step.ts

import { Given, When, Then } from '@cucumber/cucumber';
import { expect } from '@playwright/test';
import { ICustomWorld } from '../support/custom-world';

Given('I am at the playwright website', async function (this: ICustomWorld) {
    await this.page!.goto('https://playwright.dev/');
});
  
When('I click at "Get Started"', async function (this: ICustomWorld) {
    await this.page!.getByRole('link', { name: 'Get started' }).click();
});
  
Then('the title has the text "Playwright"', async function (this: ICustomWorld) {
    await expect(this.page!).toHaveTitle(/Playwrights/);//this line is intentionally failing so we can see the reports
});

Then('the URL has the text "intro"', async function (this: ICustomWorld) {
    await expect(this.page!).toHaveURL(/.*intro/);
});

Este arquivo contem as ações para executar os testes e as interações com o browser. Bem semelhante ao arquivo tests/example.spec.ts. O passo “the title has the text “Playwright”” foi implementado intencionalmente para falhar para que possamos ver o report.

Podemos ver que utilizamos o “this: ICustomWorld” como parâmetro de cada step. Desta forma, conseguimos interagir com o browser.

O símbolo ! depois do this.page é chamado “Non-null assertion operator” e quer dizer (no TypeScript) para o verificador de tipos que esta variável não será null ou undefined em momento algum e que ele deve não alertar sobre erros de tipagem (pois em alguns momentos o verificador não consegue identificar). Sem ele, teremos um erro. Veja mais aqui.

4. Execução dos testes

Vamos rodar os testes usando Cucumber. Em seu terminal, rode:

npx cucumber-js

Como configuramos no arquivo bdd-tests/support/hooks.ts para o modo headless: false, o browser irá abrir durante a execução dos testes. O resultado deve ser 1 teste passando e 1 teste falhando. O arquivo bdd-tests/reports/cucumber-report.html será criado e ao abri-lo, isso é o que veremos:

Não vou entrar em detalhes em relação ao relatório pois é possível utilizar outros reporters mais completos, porém é um relatório que temos acesso ao básico pelo menos 🙂

A execução foi bem rápida, e podemos ver que o step “Then the title has the text “Playwright”” falhou, como esperado.

Deixei na sessão “Referências Complementares”, um exemplo mais completo para caso tenha interesse.

Alternativas ao BDD no Playwright – BDD sem Cucumber

O BDD tem várias vantagens, e esta forma que vimos não é a única forma de utilizá-lo. Se você e seu time decidiram que sim é necessário utilizar BDD, apresento uma forma mais simples, com menos arquivos. O BDD no Playwright SEM criar os feature files.

Em um projeto que trabalhei, o time identificou que BDD iria auxiliar no levantamento dos cenários e organização dos testes. Então decidimos usar para esta necessidade específica. Na verdade nem foi para testes com Playwright, foi para testes utilizando RTL (React Testing Library). Desde então, eu percebi que poderíamos fazer algo semelhante com Playwright e talvez possa ser útil pra você também.

1. Instalação

Só a instalação do Playwright já é suficiente. Vamos instalar da mesma forma que instalamos para o Cucumber (se você já fez esta etapa, não precisa fazer novamente):

npm init playwright@latest

2. Configuração

Abra o arquivo playwright.config.ts e altere a linha 19 para “retries: 2,“.

Ficará assim:

Não é necessário nenhuma outra configuração especial. O Playwright já oferece cross-browser, paralelismo e relatórios de teste.

3. Implementação dos arquivos de teste

Crie um novo arquivo tests/example-bdd.spec.ts:

import { test, expect } from '@playwright/test';

test.describe('Feature: Playwright website', () => {
  test('Scenario: Has Title', async ({ page }) => {
    
    await test.step('Given I am at the playwright website', async () => {
      await page.goto('https://playwright.dev/');
    });

    await test.step('Then the title has the text "Playwright"', async () => {
            await expect(page).toHaveTitle(/Playwrights/); //this line is intentionally failing so we can see the reports
    });
  });
  
  test('Scenario: Get Started Link', async ({ page }) => {
    await test.step('Given I am at the playwright website', async () => {
      await page.goto('https://playwright.dev/');
    });
    
    await test.step('When I click at "Get Started"', async () => {
      await page.getByRole('link', { name: 'Get started' }).click();
    });

    await test.step('Then the URL has the text "intro"', async () => {
      await expect(page).toHaveURL(/.*intro/);
    });
  });

});

Explicação:

  • Usamos o test.describe para nomear a Feature. Esse método não é obrigatório no Playwright, mas bem útil.
  • Usamos o test para nomear o Scenario. Playwright permite nomear como você preferir, então tudo bem.
  • Usamos o test.step para os eventos When e Then quando necessário. Também não obrigatório, mas pode ser utilizado desta forma.

E é somente isto que precisa ser feito (considerando o mesmo cenário implementado com Cucumber).

4. Execução dos testes

Para rodar os testes, rodamos através do Playwright mesmo. Em seu terminal, rode:

npx playwright test

Os testes serão executados em modo headless, e 3 browsers: chromium, firefox, webkit que é o padrão do Playwright. Também já vem com retry e o relatório irá abrir automaticamente já que um dos testes falhou (intencionalmente). Se você clicar em um dos testes falhando, irá ver detalhadamente o valor esperado versos recebido; qual a linha de código que falhou; e apesar de não ter screenshot (poderíamos ter colocado a linha “screenshot: 'only-on-failure'", no playwright.config.ts), se você navegar até a aba “Retry 1”, rolar a página até o final, e clicar na imagem da área “Traces”. Será possível ver cada step que aconteceu no browser, muito legal!!!

Conclusão

Na minha opinião, o uso de BDD com Cucumber tem que agregar valor DEMAIS ao negócio para justificar o uso (e eu nunca presenciei um caso como esse!). Com tantos arquivos a mais, com tantas limitações, e com o fato de deixarmos de aproveitar as inúmeras vantagens que o Playwright oferece nativamente, seria muito difícil me convencer que seu uso é realmente válido. Logo minha primeira orientação é: NÃO UTILIZAR (lembrando mais uma vez que cada caso deve ser avaliado individualmente).

Comparando os arquivos tests/example.spec.ts e bdd-tests/step-definition/example.step.ts é mais o menos a mesma quantidade de linhas portanto mais imports e mais palavras. É mais código pra ler, é mais código pra compilar. Isso tudo gera TEMPO. Tempo adicionado ao seu trabalho e à pipeline do time. E sem considerar os arquivos bdd-tests/support/hooks.ts e bdd-tests/support/custom-world.ts que também são linhas de código e também precisarão ser lidas, atualizadas e compiladas com uma certa frequência.

No entanto, o uso de BDD sem Cucumber para auxiliar na escrita dos testes, sem o uso de feature file, é uma alternativa melhorzinha para alguns cenários em projetos (como vimos na sessão “Alternativas ao BDD no Playwright – BDD sem Cucumber”). É importante documentar como deverá ser usado para que o código tenha um padrão estabelecido claramente e tanto a criação quanto a manutenção sejam simples.

Eu ainda acredito que o código fica muito mais limpo sem o uso de BDD, seja com ou sem Cucumber, e a criação e manutenção de testes ficam bem mais simples.

Aqui vão algumas perguntas para você e seu time discutirem sobre o uso de BDD no projeto:

Se o projeto for novo:

  1. Tem alguma necessidade contratual de usar BDD e de entregar o código fonte ao cliente?
    – se tiver, discuta com o gerente de projetos se tem como alterar essa cláusula. 🙂
  2. Existe alguma interação com pessoas não técnicas no time que precisam validar os cenários?
    – se tiver, a escrita de feature files será executada por alguém de fora do time de desenvolvimento & testes?
    — se não for, você não precisa de feature files no seu projeto de testes automatizados. Pode simplesmente consumir os arquivos para ajudar na implementação dos testes. Nenhuma pessoa não técnica irá abrir a sua suíte de testes pra revisar os feature files.
    — e se for, mesmo assim você não precisa de feature files no seu projeto!
  3. Quanto tempo esse projeto vai durar e qual tamanho a suite de testes pode atingir?
    — a medida que o projeto cresce, caso utilize Cucumber, a quantidade de arquivos irá multiplicar por 2 e potencialmente o tamanho do seu projeto também. Imagine novos membros no time, a quantidade de arquivos que essa pessoa terá que se familiarizar. Imagine ao mudar um cenário, ter que atualizar no mínimo 2 lugares (feature file e step definition). Imagine organizar testes com frases tão grandes “the title has the text “Playwright”” para evitar duplicidade (comparado ao possível nome de uma função “checkPageTitle“). Imagine o tempo para baixar, compilar e executar todo o projeto na sua pipeline.
  4. Qual o valor o uso de BDD REALMENTE irá agregar neste projeto?
  5. Estamos dispostos a dedicar um tempo (dias, talvez semanas) para reconfigurar manualmente tudo que o Playwright nativamente já nos oferece (cross-browser, debug, relatórios, paralelismo, viewport, retries, trace)?

Se o projeto já tiver implementação:

  1. Qual o percentual de manutenção em código existente versus implementação de código novo no projeto?
    – se o projeto praticamente não tem manutenção, ou tem uma seria interessante avaliar a possibilidade de descontinuar o uso de BDD; para novos códigos o time não utiliza. Pode-se avaliar uma estratégia de migração para a nova arquitetura, como por exemplo alguns dias de hackathon para migrar todo o código legado para a nova arquitetura. Ou eventualmente aceitar 2 arquiteturas diferentes em um mesmo código. Não há problema desde que haja uma boa documentação e orientação de uso do código.
    – se o projeto tem praticamente só manutenção, pode-se também avaliar a possibilidade de um projeto de migração. Para provar que essa é uma estratégia válida, pode-se fazer uma POC medindo o tempo de alterar um teste (baseado em um requisito) e o tempo para construir o teste sem o uso do BDD. Outra estratégia seria em também usar hackathon para migrar os arquivos mais demandantes, e deixar os demais num projeto legado.
    – considere o uso de ferramentas de Inteligência Artificial como “ChatGPT” ou “GitHub Copilot” para ajudar na migração. Você pode solicitar que gere um código em uma certa linguagem baseado num código existente.
  2. Qual o valor o uso de BDD REALMENTE está agregando neste projeto?

Boa discussão!

Referências Complementares:

1 thought on “Playwright, BDD, Cucumber & a minha opinião sobre isso

Leave a Reply

Your email address will not be published. Required fields are marked *