Testes de automação instáveis podem ser uma desgraça para os engenheiros de controle de qualidade, introduzindo incerteza e minando a confiabilidade dos conjuntos de testes. Com base na minha experiência como mentor de SDET e automação de controle de qualidade, este artigo oferece conselhos práticos para vencer a instabilidade. Embora eu forneça exemplos de JavaScript e Playwright, essas dicas universais são aplicáveis em qualquer linguagem e estrutura, ajudando você a escrever testes de automação robustos e confiáveis. Vamos nos aprofundar e garantir que seus testes permaneçam firmes.
1. Evite os métodos de espera implícitos
Ocasionalmente, você encontrará situações em que deverá esperar que elementos apareçam no DOM ou que seu aplicativo atinja um estado específico para prosseguir com seu cenário de teste. Mesmo com estruturas de automação modernas e inteligentes, como os recursos de espera automática do Playwright, haverá casos em que você precisará implementar métodos de espera personalizados. Por exemplo, considere um cenário com campos de texto e um botão de confirmação. Devido ao comportamento específico do servidor, o botão de confirmação só fica visível após aproximadamente cinco segundos após o preenchimento do formulário. Nesses casos, é essencial resistir à tentação de inserir uma espera implícita de cinco segundos entre duas etapas de teste.
textField.fill('Some text') waitForTime(5000) confirmationButton.click()
Em vez disso, use uma abordagem inteligente e aguarde o estado exato. Pode ser o elemento de texto que aparece após algum carregamento ou um elemento giratório do carregador que desaparece após a etapa atual ser concluída com sucesso. Você pode verificar se está pronto para a próxima etapa do teste.
button.click() waitFor(textField.isVisible()) textField.fill()
Claro, há casos em que você precisa esperar por algo que não pode verificar de forma inteligente. Sugiro adicionar comentários nesses locais ou até mesmo adicionar a explicação do motivo como parâmetro em seus métodos de espera ou algo parecido:
waitForTime(5000, {reason: "For unstable server processed something..."}
Um lucro adicional de usá-lo são os relatórios de teste onde você pode armazenar tais explicações para que seja óbvio para outra pessoa ou mesmo para você no futuro o que e por que tantos cinco segundos de espera aqui.
waitForTime(5000, {reason: "For unstable server processed something..."} button.click() waitFor(textField.isVisible()) textField.fill()
2. Use localizadores robustos e confiáveis para selecionar elementos
Os localizadores são uma parte crucial dos testes de automação e todos sabem disso. No entanto, apesar deste simples fato, muitos engenheiros de automação estão apostando na estabilidade e usando algo assim em seus testes.
//*[@id="editor_7"]/section/div[2]/div/h3[2]
Essa é uma abordagem boba porque a estrutura do DOM não é tão estática; algumas equipes diferentes às vezes podem alterá-lo, e isso depois que seus testes falharem. Portanto, use os localizadores robustos. A propósito, você é bem-vindo à leitura da minha história relacionada ao XPath.
3. Faça testes independentes um do outro
Ao executar o conjunto de testes com vários testes em um único thread, é crucial garantir que cada teste seja independente. Isto significa que o primeiro teste deve retornar o sistema ao seu estado original para que o próximo teste não falhe devido a condições inesperadas. Por exemplo, se um de seus testes modificar as configurações do usuário armazenadas no LocalStorage, limpar a entrada LocalStorage para Configurações do usuário após a primeira execução do teste será uma boa abordagem. Isto garante que o segundo teste será executado com as configurações padrão do usuário. Você também pode considerar ações como redefinir a página para a página de entrada padrão e limpar cookies e entradas do banco de dados para que cada novo teste comece com condições iniciais idênticas.
Por exemplo, no Playwright, você pode usar as anotações beforeAll(), beforeEach(), afterAll() e afterEach() para alcançá-lo.
Verifique o próximo conjunto de testes com alguns testes. A primeira é alterar as configurações de aparência do usuário e a segunda é verificar a aparência padrão.
test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goTo(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });
Se esse conjunto de testes for executado em paralelo, tudo correrá bem. No entanto, se esses testes forem executados um por um, você poderá enfrentar uma falha no teste porque um deles está interferindo no outro. O primeiro teste mudou a aparência de claro para escuro. O segundo teste verifica se a aparência atual é clara. Mas irá falhar, pois o primeiro teste já mudou a aparência padrão. Para resolver esse problema, adicionei o gancho afterEach() executado após cada teste. Neste caso, navegará até a visualização padrão e limpará a aparência do usuário removendo-o do Armazenamento Local.
test.afterEach(async ({ page }) => { await user.localStorage(appearanceSettings).clear() await user.goTo(views.home) }); test.describe('Test suite', () => { test('TC101 - update appearance settings to dark', async ({page}) => { await user.goto(views.settings); await user.setAppearance(scheme.dark); }); test('TC102 - check if default appearance is light', async ({page}) => { await user.goTo(views.settings); await user.checkAppearance(scheme.light); }); });
Usando esta abordagem, cada teste neste conjunto será independente.
4. Use tentativas automáticas com sabedoria
Ninguém está imune a encontrar testes instáveis, e uma solução popular para esse problema envolve o uso de novas tentativas. Você pode configurar novas tentativas para ocorrerem automaticamente, mesmo no nível de configuração de CI/CD. Por exemplo, você pode configurar novas tentativas automáticas para cada teste com falha, especificando uma contagem máxima de tentativas de três vezes. Qualquer teste que inicialmente falhar será repetido até três vezes até que o ciclo de execução do teste seja concluído. A vantagem é que, se o seu teste for apenas ligeiramente instável, ele poderá passar após algumas tentativas.
No entanto, a desvantagem é que ele pode falhar, resultando em consumo extra de tempo de execução. Além disso, esta prática pode inadvertidamente levar a um acúmulo de testes instáveis, já que muitos testes podem parecer “passados” na segunda ou terceira tentativa, e você pode rotulá-los incorretamente como estáveis. Portanto, não recomendo definir tentativas automáticas globalmente para todo o seu projeto de teste. Em vez disso, use novas tentativas seletivamente nos casos em que não for possível resolver imediatamente os problemas subjacentes do teste.
Por exemplo, imagine que você tem um caso de teste onde você deve fazer upload de alguns arquivos usando o evento 'filechooser'. Você recebe o erro <Timeout excedido enquanto aguarda o evento 'filechooser'>, que indica que o teste Playwright está aguardando a ocorrência de um evento 'filechooser', mas está demorando mais tempo limite.
async function uploadFile(page: Page, file) { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); }
Isso pode ocorrer por vários motivos, como comportamento inesperado no aplicativo ou ambiente lento. Por se tratar de um comportamento aleatório, a primeira coisa que você pode pensar é usar a repetição automática para este teste. No entanto, se você tentar novamente o teste inteiro, você corre o risco de perder tempo extra e obter o mesmo comportamento que obteve com o primeiro erro no próprio método uploadFile(), portanto, se o erro ocorrer, você não precisará tentar novamente o teste inteiro.
Para fazer isso, você pode usar a instrução try…catch padrão.
async function uploadFile(page: Page, file) { const maxRetries = 3; let retryCount = 0; while (retryCount < maxRetries) { try { const fileChooserPromise = page.waitForEvent('filechooser'); await clickOnElement(page, uploadButton); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(file); break; // Success, exit the loop } catch (error) { console.error(`Attempt ${retryCount + 1} failed: ${error}`); retryCount++; } } }
Essa abordagem economizará tempo extra e tornará seu teste menos complicado.
Para encerrar, o caminho para testes de automação confiáveis é simples. Você pode minimizar ou enfrentar ativamente desafios comuns e implementar estratégias inteligentes. Lembre-se de que esta jornada é contínua e seus testes se tornarão mais confiáveis com persistência. Diga adeus à instabilidade e dê as boas-vindas à estabilidade. Seu compromisso com a qualidade garante o sucesso do projeto. Bons testes!
Também publicado .