
Compartilhar:
Yonatan já participou de alguns projetos incríveis tanto no meio acadêmico quanto no setor privado — desde C/C++, passando por Matlab, até PHP e JavaScript. Ex-CTO da Webiks e arquiteto de software na WalkMe. Atualmente, ele é arquiteto de software na Vonage e instrutor na Egghead.
Como criar um sistema de testes de regressão visual usando o Playwright
Tempo de leitura: 10 minutos
Digamos que o Joe esteja trabalhando no componente reutilizável “botão”, faça uma alteração e verifique se o botão funciona como esperado. Será que o Joe sabe se os outros componentes continuam funcionando corretamente com essa alteração? Joe teria que saber quais componentes dependem do botão e verificar todos eles — certificando-se visualmente de que os botões estão corretos — em vários cenários. E quanto aos diferentes navegadores? A matriz de testes só cresce cada vez mais…
Vamos ajudar o Joe a aproveitar melhor o tempo dele automatizando o processo de teste visual!
Assim como o Joe, você tem um aplicativo em funcionamento ou criou seus componentes e quer compartilhá-los com o mundo. A cada alteração, você (ou alguém da sua equipe) passa horas revisando a interface do usuário para garantir que ela esteja perfeita até o último pixel. Mesmo assim, às vezes algumas coisas escapam à atenção humana. Vamos admitir: as máquinas são melhores do que nós em alinhar pixels. Elas também custam menos e podem fazer isso repetidamente.
É aqui que nossa história começa. Temos um série de componentes de interface do usuário. Também temos documentação para nossos componentes na qual podemos “brincar” com eles (ou seja, testá-los manualmente).
A documentação é criada a partir dos arquivos `readme` contidos na pasta de cada componente. Veja um exemplo.
Observe que os exemplos são trechos simples de HTML:
<vwc-icon type="heart-solid" connotation="accent"></vwc-icon>
<vwc-icon type="heart-solid" connotation="announcement"></vwc-icon>
<vwc-icon type="heart-solid" connotation="cta"></vwc-icon>
<vwc-icon type="heart-solid" connotation="success"></vwc-icon>
<vwc-icon type="heart-solid" connotation="alert"></vwc-icon>
<vwc-icon type="heart-solid" connotation="info"></vwc-icon>Voltaremos a esse assunto mais tarde.
Então… temos cerca de 50 componentes e uma maneira de visualizá-los enquanto os desenvolvemos e testamos manualmente. Isso é ótimo! É bom poder ver os componentes antes de lançá-los :).
Alguns desses componentes são compostos por vários elementos atômicos. Isso significa que alterar o componente “botão” pode afetar vários componentes complexos que utilizam esse botão.
Como configurar o Playwright em um projeto?
O primeiro passo é executar:
npm i -D playwright
Isso vai fazer várias coisas:
Adicione
playwrightao nosso arquivo package.json.Instalar
playwrightlocalmente.Instale
playwrightos navegadores localmente.
Também precisaremos criar um playwright.config arquivo. Para esta introdução (e porque é assim que eu pessoalmente faço), vamos usar a typescript versão, mas você pode usar JavaScript puro. Vamos criar um playwright.config.ts arquivo com o seguinte conteúdo:
import type {
PlaywrightTestConfig
} from '@playwright/test';
const config: PlaywrightTestConfig = {
testMatch: 'src/**/*.test.ts',
projects: [{
name: 'Chrome Stable',
use: {
browserName: 'chromium',
channel: 'chrome',
},
},
{
name: 'Desktop Safari',
use: {
browserName: 'webkit',
}
},
{
name: 'Desktop Firefox',
use: {
browserName: 'firefox',
}
},
]
};
export default config;A configuração acima faz o seguinte:
testMatch: Indica ao playwright onde encontrar os arquivos de teste. Observe que o caminho é relativo à localização do arquivo. No repositório do vivid, o arquivo de configuração está localizado na pasta components, e todos os componentes estão dentro dasrcpasta. Adotamos a convenção de nomear os arquivos de teste*.test.ts— assim, todos os arquivos que seguirem esse padrão serão incluídos na execução do teste.projects: Indica ao Playwright em quais configurações os testes devem ser executados. Neste caso, temos três configurações para cada um dos principais navegadores que gostaríamos de testar.
Há muito mais a se explorar na playwright configuração. Você pode ler mais sobre isso aqui.
Agora que playwright “sabe” onde obter os arquivos e em quais navegadores executar nossos testes, podemos começar a configurar os próprios testes.
Como escrever testes para o Playwright
Para criar um teste, precisamos criar um arquivo que seja localizado pelo testMatches. É por isso que, em nosso repositório, criamos um arquivo ui.test.ts para cada componente. Aqui está um exemplo de arquivo de teste:
test('should show the component', async ({
page
}: {
page: Page
}) => {
const template = `...`;
await loadComponents({
page,
components,
});
await loadTemplate({
page,
template,
});
const testWrapper = await page.locator('#wrapper');
await page.locator('#modal');
await page.waitForLoadState('networkidle');
await page.evaluate(() => {
const modal = (document.getElementById('modal') as Dialog);
modal.showModal();
return modal;
});
expect(await testWrapper?.screenshot()).toMatchSnapshot(
'./snapshots/dialog.png'
);
});Para acessar o arquivo completo, clique aqui.
Vamos analisar isso.
Bloco de teste do dramaturgo
A primeira coisa que percebemos depois que todas as importações são concluídas é que temos um test bloco. A função enviada ao test bloco aceita um objeto de teste. Uma das propriedades do objeto é a page propriedade. Essa page é a page representação no Playwright. Nós a usamos para manipular e observar a página testada.
Preparação da página de teste
Neste arquivo, temos um teste que gera um modelo (a template variável). Em seguida, ele carrega os componentes necessários e o modelo na página de teste usando funções utilitárias que criamos (falaremos mais sobre isso depois).
Aguardando o carregamento do HTML
As próximas linhas utilizam o page.locator método para obter os elementos que estamos procurando. Outro “efeito colateral” do uso do localizador é que ele determina quando nossos elementos estão visíveis na página. Isso garante que, quando executarmos os testes, nossos elementos já estejam no DOM.
Aguardando a obtenção dos recursos
Outra opção de espera é aguardar até que todo o tráfego de rede esteja inativo. Isso é importante para nós, pois, às vezes, queremos esperar até que ícones ou imagens sejam carregados a partir de uma CDN.
API programática para acionar elementos DOM
Por fim, há o evaluate método do page objeto. Esse método avalia o código JavaScript dentro da página. Nesse caso, este é o código avaliado:
await page.evaluate(() => {
const modal = (document.getElementById('modal') as Dialog);
modal.showModal();
return modal;
});Nesse caso, obtemos o modal elemento e chamamos seu showModal método. Dessa forma, podemos testar a API programática dos elementos DOM.
Gerar e comparar instantâneos
A linha final é a “mágica” da regressão visual no Playwright:
expect(await testWrapper?.screenshot()).toMatchSnapshot(
'./snapshots/dialog.png'
);O locator objeto resolvido testWrapper possui um screenshot método. Quando invocamos esse método e usamos a toMatchSnapshot expectativa, ele realiza uma das seguintes ações:
Se houver um snapshot no caminho fornecido a
toMatchSnapshot, ele compara o instantâneo capturado com aquele que existe no sistema de arquivos. Se eles não corresponderem, o teste falha.Se não houver nenhum snapshot no caminho, ele gera um novo.
Como carregar a página de teste durante um teste?
Na última seção, vimos um exemplo de como os testes podem ser escritos. Usamos um page objeto para manipular nossa página de teste, mas isso levanta algumas questões — o que é essa página misteriosa? De onde vem o conteúdo HTML/CSS/JS?
Em essência, o page possui um método goto. Esse método aceita uma URL e a carrega no page. Isso funciona bem se você tiver um aplicativo em funcionamento. Nesse caso, basta disponibilizar seu aplicativo. Isso pode ser feito usando o servidor de desenvolvimento do seu bundler, um servidor de HTML estático ou até mesmo testando seu ambiente de controle de qualidade, desenvolvimento ou produção.
Seus testes geralmente começariam com um goto e ficariam mais ou menos assim:
test('should show text hello world', async ({page}) => {
await page.goto(myAppUrl);
const headerText = await manipulateApp(page);
expect(headerText).toEqual('Hello World');
});Nós goto o aplicativo ou a página de teste, manipulamos de alguma forma e, então, esperamos que algum recurso exista ou seja igual a algo.
Como podemos disponibilizar componentes individuais para comparação de instantâneos?
Em vivid, assim como em outras bibliotecas de componentes de interface do usuário, não há um aplicativo real para testar. Temos nossa documentação — mas testá-la também significaria testar outros elementos que não estão relacionados aos nossos componentes. Na verdade, a maior parte dos dados visuais na documentação não está relacionada aos componentes.
O melhor a se fazer seria criar uma página de teste específica para cada componente. Para isso, servimos todo o nosso projeto usando http-server. Nosso comando de regressão visual fica mais ou menos assim: npm run build & npx http-server -s & npx playwright test.
Isso significa que compilamos nossos componentes, disponibilizamos o repositório e, consequentemente, nossa compilação, e então iniciamos os testes. Também temos um arquivo HTML vazio que é disponibilizado, e o utilizamos no page.goto comando da seguinte maneira:
await page.goto('[http://127.0.0.1:8080/scripts/visual-tests/index.html](http://127.0.0.1:8080/scripts/visual-tests/index.html)');
Este arquivo é um arquivo HTML vazio. Poderíamos criar um arquivo HTML manualmente para cada teste — mas qual seria a graça nisso? Vamos ver como podemos começar a automatizar a geração de nossas páginas de teste a partir do nosso código.
Como inserir arquivos JS e estilos em nossa página usando o Playwright?
Agora temos uma página HTML para goto. Ela ainda não serve para nada, pois precisamos poder inserir o código dos nossos componentes.
Para isso, podemos analisar a função de utilidade que vimos no exemplo do arquivo de teste — loadComponents.
loadComponents é uma função que aceita um array com nomes de componentes e utiliza o método do Playwright addScriptTag para adicionar os componentes:
export async function loadComponents({
page,
components,
styleUrls = defaultStyles,
}: {
page: Page,
components: string[],
styleUrls ? : string[]
}) {
await page.goto('http://127.0.0.1:8080/scripts/visual-tests/index.html');
(async function() {
for (const component of components) {
await page.addScriptTag({
url: `http://127.0.0.1:8080/dist/libs/components/${component}/index.js`,
type: 'module',
});
}
})();
const styleTags$ = styleUrls.map(url => page.addStyleTag({
url
}));
await Promise.all(styleTags$);
}A mágica acontece dentro do loop `async for`, no meio da função: ele percorre todos os componentes do array e usa addScriptTag para carregá-lo em nossa página de demonstração.
Isso também ocorre com as tags de estilo que estão uma linha abaixo. A função é executada quando todas as tags de estilo estão no DOM.
Dessa forma, em cada teste, pegamos a mesma página em branco e carregamos os componentes que queremos testar. Por exemplo, se quisermos testar elementos de formulário, definimos um array com text-field, text-area, select, button e outros elementos de formulário. A loadComponents função irá inseri-los na página de teste para nós.
Nosso tráfego de rede ficaria assim caso tivéssemos configurado nossa matriz da seguinte forma: [icon, button, focus, dialog, text-field,layout]:
A list of JS files from the network tab
Lembre-se de que também temos modelos carregados:
A list of css files from the network tab
Uhuuu — o JS e o CSS foram carregados!
Como exibir os componentes na página HTML?
Agora temos uma página HTML com os scripts e estilos necessários já incorporados. O que nos resta fazer é incorporar o código HTML que realmente utilizará nossos componentes.
Digamos que queiramos testar um botão. Vamos criar uma string com o seguinte formato:
<vwc-button label=”Click Me”></vwc-button>
Podemos adicionar mais recursos ao botão, como conotação ou aparência, ou até mesmo escolher um componente diferente (afinal, se quiséssemos testar a caixa de diálogo, criar um trecho de código de um botão seria bastante inútil).
Agora queremos que esse HTML seja exibido em nossa página de teste. Para isso, vamos usar um truque com o page.addScriptTag. Na função utilitária loadTemplate, vamos aceitar a página e a string do modelo. Em seguida, adicionaremos um script à página que insere o modelo HTML nela:
export async function loadTemplate({
page,
template
}: {
page: Page,
template: string
}) {
const wrappedTemplate = `<div id="wrapper">${template}</div>`;
await page.addScriptTag({
content: `
document.body.innerHTML = \`${wrappedTemplate}\`;
`,
});
}A função, em primeiro lugar, envolve nosso modelo com uma div string e, em seguida, adiciona um script à página que substitui o innerHTML do corpo pela nossa string HTML envolvida.
Então, se tivermos uma string HTML como esta:
<vwc-dialog id="modal"
icon="info"
headline="Headline"
text="This is the content that I want to show, and I will show it!!!"
>
</vwc-dialog>Obtemos o seguinte resultado no navegador:
The test page and the devtools
Exibindo nosso modal. À direita do modal, você pode ver o código HTML que inserimos na página.
Geração e comparação de capturas de tela no Playwright
Agora que nossa página de teste está pronta, podemos fazer nosso snapshot. Já abordamos isso na visão geral, mas aqui vai um lembrete:
const testWrapper = await page.locator('#wrapper');
await page.locator('#modal');
await page.waitForLoadState('networkidle');
await page.evaluate(() => {
const modal = (document.getElementById('modal') as Dialog);
modal.showModal();
return modal;
});
expect(await testWrapper?.screenshot()).toMatchSnapshot(
'./snapshots/dialog.png'
); Passo 1: Aguarde até que a página esteja pronta
Esperamos que o wrapper e o modal estejam na página usando o page.locator método e aguardamos o networkidle evento para garantir que todos os nossos recursos estejam carregados.
Etapa 2: Editar a página
Assim que a página estiver pronta, avaliamos o script que utiliza a showModal API no elemento modal.
Etapa 3: Faça uma captura de tela e valide-a
Agora, se tudo estiver funcionando corretamente, chegamos à nossa expect linha que captura o instantâneo e o compara com um instantâneo anterior.
O testWrapper.screenshot método faz parte da playwright.locator API. Ele captura uma imagem do elemento e nos permite usar a toMatchSnapshot API de expectativa. Nesse caso, associamos a captura a um caminho de arquivo (o match path) — é onde a captura de tela é salva.
Caso esta seja a primeira vez que executamos o teste, uma captura de tela é gerada no match path. Todas as alterações que fizermos serão comparadas ao resultado original, garantindo que qualquer mudança na aparência do nosso componente faça com que o teste falhe e que não lancemos alterações visuais indesejadas.
Etapa 4: Atualizar as capturas de tela
Nesse caso, queremos mesmo mudar a aparência, playwright há um update-snapshots opção que atualiza o snapshot. Para isso, temos uma update tarefa em nosso repositório, mas no Playwright funciona assim:
npx playwright test --update-snapshots
Resumo
Agora temos um sistema de regressão visual totalmente funcional para componentes da interface do usuário. Criar esse tipo de teste para componentes da interface do usuário é um pouco mais complicado do que testar uma aplicação.
Ao testar uma aplicação, basta carregá-la por meio de um servidor externo e, em seguida, usar goto o endereço. Ao testar componentes, você precisa desse mesmo servidor, mas, dessa vez, ele apontará para uma página em branco que precisamos criar durante o próprio teste.
As duas funções de utilidade, loadComponents e loadTemplate aquelas que utilizavam o Playwright addScriptTag e addStyleTag, são o segredo que nos permite testar cada componente isoladamente, bastando para isso criar nossos trechos de HTML.
Usamos um código muito semelhante para criar um teste para cada componente da nossa biblioteca ou para cada página/história de usuário do nosso aplicativo. Dessa forma, criamos um instantâneo para cada componente/página/história de usuário e garantimos que o usuário obtenha o mesmo resultado ao seguir o mesmo procedimento. Tudo isso ocorre automaticamente nos navegadores que definimos em nossa configuração (Chrome, Firefox e Safari). Melhor qualidade – economia de tempo.
Agora temos um mecanismo local de testes de regressão visual automatizados em funcionamento. O objetivo de criá-lo é poder executar esses testes localmente, para que o desenvolvedor possa obter um feedback rápido e corrigir os erros antes mesmo de enviar suas alterações.
Mas ainda há muito mais que podemos melhorar:
Como podemos automatizar a geração de testes?
Como podemos nos proteger contra a instabilidade (a instabilidade é um problema conhecido nos testes de ponta a ponta)?
Como podemos integrar e otimizar nossos testes no GitHub Actions?
E quanto ao modo de desenvolvimento e depuração para os testes visuais?
Abordarei esses tópicos no próximo artigo. Enquanto isso, você pode dar uma olhada no nosso repositório Vivid, nossa configuração do Playwright e documentação sobre testes de interface do usuário para ver como funciona.
Compartilhar:
Yonatan já participou de alguns projetos incríveis tanto no meio acadêmico quanto no setor privado — desde C/C++, passando por Matlab, até PHP e JavaScript. Ex-CTO da Webiks e arquiteto de software na WalkMe. Atualmente, ele é arquiteto de software na Vonage e instrutor na Egghead.