page.getByText('Success').isVisible() retorna um booleano verificando o DOM exatamente uma vez: se o elemento ainda não renderizou, você recebe false mesmo que ele apareça 200ms depois. expect(page.getByText('Success')).toBeVisible() tenta novamente por até 5 segundos antes de falhar. Esse comportamento de retry é o núcleo do design de assertions do Playwright, e a maioria dos erros de iniciantes vem de não saber quais métodos o têm.
Como as assertions do Playwright funcionam
As assertions do Playwright usam a função expect() do @playwright/test. Não são as mesmas do expect do Jest. A versão do Playwright é assíncrona e tem lógica de retry nativa.
import { test, expect } from '@playwright/test';A diferença principal em relação à maioria dos frameworks de teste: as assertions do Playwright fazem retry automaticamente. Quando você escreve:
await expect(page.getByText('Welcome')).toBeVisible();O Playwright não verifica só uma vez. Verifica repetidamente por até 5 segundos (o expect timeout padrão), aguardando a condição se tornar verdadeira. Isso elimina a necessidade de chamadas manuais de waitFor em 90% dos casos.
Se a condição nunca se tornar verdadeira dentro do timeout, o teste falha com uma mensagem clara mostrando o que era esperado e o que realmente existia.
Assertions de elemento (baseadas em locator)
Essas verificam propriedades de um elemento específico na página.
toBeVisible / toBeHidden
// Elemento está renderizado e visível para o usuário
await expect(page.getByText('Dashboard')).toBeVisible();
// Elemento não está presente, ou está presente mas oculto (display:none, visibility:hidden, opacity:0)
await expect(page.getByRole('dialog')).toBeHidden();toBeHidden() é verdadeiro se o elemento não existe OU se existe mas está invisível. Use not.toBeAttached() se precisar confirmar especificamente que o elemento não está no DOM.
toHaveText / toContainText
// Correspondência exata de texto (remove espaços em branco automaticamente)
await expect(page.getByRole('heading', { level: 1 })).toHaveText('My Travel Items');
// Correspondência parcial de texto
await expect(page.getByRole('heading')).toContainText('Travel');
// Array: verificar texto de múltiplos elementos
await expect(page.getByRole('listitem')).toHaveText(['Tokyo', 'Paris', 'London']);
// Regex: correspondência por padrão
await expect(page.getByTestId('price')).toHaveText(/\$\d+\.\d{2}/);toHaveText com array verifica o texto completo de cada elemento na ordem. Muito útil para confirmar linhas de tabela ou listas ordenadas.
toHaveValue
Para elementos , e :
// Input de texto
await expect(page.getByLabel('Email')).toHaveValue('admin@becomeqa.com');
// Input vazio
await expect(page.getByLabel('Search')).toHaveValue('');
// Dropdown select
await expect(page.getByLabel('Status')).toHaveValue('completed');toBeChecked / not.toBeChecked
Para checkboxes e radio buttons:
await expect(page.getByLabel('Lembrar de mim')).toBeChecked();
await expect(page.getByLabel('Assinar newsletter')).not.toBeChecked();toBeEnabled / toBeDisabled
// Botão está clicável
await expect(page.getByRole('button', { name: 'Submit' })).toBeEnabled();
// Botão está desabilitado / tem atributo disabled
await expect(page.getByRole('button', { name: 'Save' })).toBeDisabled();Use isso para verificar que um botão de submit só fica habilitado após os campos obrigatórios serem preenchidos.
toBeEditable / toBeReadOnly
await expect(page.getByLabel('Username')).toBeEditable();
await expect(page.getByLabel('Created At')).toBeReadOnly();toHaveAttribute
Verifique qualquer atributo HTML:
// href em um link
await expect(page.getByRole('link', { name: 'Home' })).toHaveAttribute('href', '/');
// data-testid
await expect(page.getByTestId('status-badge')).toHaveAttribute('data-status', 'active');
// aria-label
await expect(page.getByRole('button', { name: 'Close' })).toHaveAttribute('aria-label', 'Close dialog');
// Correspondência de valor por regex
await expect(page.getByRole('img')).toHaveAttribute('src', /\/images\/.+\.svg/);toHaveClass
// Elemento tem essa classe CSS
await expect(page.getByTestId('alert')).toHaveClass(/error/);
// Lista exata de classes
await expect(page.getByTestId('button')).toHaveClass('btn btn-primary active');Atenção: toHaveClass verifica se a classe está presente, não se é a única. Use regex para correspondências parciais.
toHaveCount
Para coleções de elementos:
// Tabela tem cabeçalho + 5 linhas de dados
await expect(page.getByRole('row')).toHaveCount(6);
// Dropdown tem 4 opções
await expect(page.getByRole('option')).toHaveCount(4);
// Sem mensagens de erro
await expect(page.getByRole('alert')).toHaveCount(0);toHaveCSS
Verifique propriedades CSS computadas:
await expect(page.getByTestId('error-message')).toHaveCSS('color', 'rgb(220, 38, 38)');
await expect(page.getByRole('dialog')).toHaveCSS('display', 'flex');Use valores computados (rgb(...)) e não variáveis CSS ou propriedades shorthand.
Assertions de página
Essas verificam propriedades da página inteira, não de um elemento específico.
toHaveURL
// URL exata
await expect(page).toHaveURL('https://lab.becomeqa.com/dashboard');
// Correspondência parcial com regex
await expect(page).toHaveURL(/\/dashboard/);
// Com baseURL configurada no playwright.config.ts
await expect(page).toHaveURL('/dashboard');Use após uma navegação para confirmar que você chegou onde esperava.
toHaveTitle
await expect(page).toHaveTitle('My Travel Items | BecomeQA Lab');
await expect(page).toHaveTitle(/BecomeQA/);toHaveScreenshot (regressão visual)
// Primeira execução cria a screenshot de referência
// Execuções seguintes comparam com ela
await expect(page).toHaveScreenshot('dashboard.png');
// Com opções
await expect(page).toHaveScreenshot('dashboard.png', {
maxDiffPixels: 100, // permite diferenças menores de pixels
threshold: 0.2, // escala 0-1 de tolerância de diferença de cor
});Testes de regressão visual: a primeira execução salva uma imagem de referência. Todas as execuções seguintes comparam o estado atual com essa referência. Falha se diferem além do threshold.
Assertions de resposta de API
Ao usar a fixture request para testes de API:
test('GET /api/items retorna dados válidos', async ({ request }) => {
const response = await request.get('/api/items');
// Status code
expect(response.status()).toBe(200);
expect(response.ok()).toBeTruthy(); // true para 200-299
// Body da resposta
const items = await response.json();
expect(items).toHaveLength(5);
expect(items[0]).toHaveProperty('id');
expect(items[0]).toHaveProperty('title');
expect(items[0].status).toBe('planned');
// Headers
expect(response.headers()['content-type']).toContain('application/json');
});Atenção: response.ok() é um método nativo (não uma assertion) que retorna true para status codes 2xx.
Assertions de valores genéricos
Para valores que não são elementos, como variáveis, respostas de API e valores computados:
// Igualdade
expect(items.length).toBe(5);
expect(user.role).toBe('admin');
// Diferente
expect(errorCode).not.toBe(0);
// Truthy/falsy
expect(isVisible).toBeTruthy();
expect(errorMessage).toBeFalsy();
// Null/undefined
expect(result).toBeNull();
expect(result).not.toBeNull();
expect(result).toBeDefined();
expect(result).toBeUndefined();
// Números
expect(count).toBeGreaterThan(0);
expect(price).toBeGreaterThanOrEqual(9.99);
expect(discount).toBeLessThan(100);
// Arrays
expect(statuses).toContain('completed');
expect(items).toHaveLength(3);
expect(tags).toEqual(['qa', 'automation', 'playwright']); // correspondência exata de array
// Objetos
expect(user).toMatchObject({ email: 'admin@becomeqa.com', role: 'admin' }); // correspondência parcial
expect(user).toEqual({ id: 1, email: 'admin@becomeqa.com', role: 'admin' }); // correspondência exata
// Strings
expect(message).toContain('success');
expect(slug).toMatch(/^[a-z0-9-]+$/);Negação: not
Toda assertion pode ser negada com .not:
await expect(page.getByRole('dialog')).not.toBeVisible();
await expect(page.getByText('Error')).not.toBeAttached();
expect(response.status()).not.toBe(404);Soft assertions: coletar todas as falhas
Por padrão, a primeira assertion com falha para o teste. Soft assertions continuam rodando mesmo após uma falha e reportam todas no final:
test('dados do dashboard estão corretos', async ({ page }) => {
await page.goto('/dashboard');
// Essas não param na primeira falha
await expect.soft(page.getByRole('heading')).toHaveText('My Travel Items');
await expect.soft(page.getByRole('row')).toHaveCount(6);
await expect.soft(page).toHaveURL('/dashboard');
// Isso lança erro se qualquer soft assertion acima falhou
expect(test.info().errors).toHaveLength(0);
});Use soft assertions quando quiser ver tudo que está quebrado em uma execução, não apenas a primeira falha.
Mensagens de assertion customizadas
Quando uma assertion falha, o Playwright mostra o que era esperado vs. o que foi encontrado. Você pode adicionar uma mensagem customizada para tornar as falhas mais legíveis:
await expect(page.getByRole('heading'), 'Página deve mostrar o dashboard após login')
.toHaveText('My Travel Items');
expect(items.length, `Esperava 5 itens mas encontrou ${items.length}`)
.toBe(5);Configurando timeouts
O timeout padrão de assertion é 5 segundos. Você pode sobrescrevê-lo por assertion ou globalmente:
// Por assertion (10 segundos para uma operação lenta)
await expect(page.getByText('Relatório pronto')).toBeVisible({ timeout: 10000 });
// Global no playwright.config.ts
export default defineConfig({
expect: {
timeout: 10000, // todas as assertions aguardam até 10 segundos
},
});Não aumente timeouts para corrigir testes flaky. Um teste flaky com timeout maior continua flaky. Só falha mais devagar. Corrija a causa raiz.
Erros comuns
Usarpage.locator().isVisible() em vez de expect().toBeVisible()
// Errado: verifica uma vez, sem retry, retorna booleano
const visible = await page.getByText('Success').isVisible();
expect(visible).toBe(true);
// Certo: faz retry até estar visível ou timeout
await expect(page.getByText('Success')).toBeVisible();A primeira versão pode falhar de forma intermitente porque verifica exatamente uma vez. A segunda faz retry.
Assertar em locators desatualizados// Não salve locators antes da navegação e assertar depois
const heading = page.getByRole('heading');
await page.goto('/nova-pagina');
await expect(heading).toHaveText('Nova Página'); // pode estar stale
// Melhor: crie o locator próximo à assertion
await page.goto('/nova-pagina');
await expect(page.getByRole('heading')).toHaveText('Nova Página');expect(await locator.textContent()).toBe(...) em vez de toHaveText
// Errado: avalia uma vez, sem retry
expect(await page.getByRole('heading').textContent()).toBe('Dashboard');
// Certo: faz retry com auto-waiting
await expect(page.getByRole('heading')).toHaveText('Dashboard');Se uma lista carrega de forma assíncrona, assertar o count após confirmar que a lista está visível:
await expect(page.getByRole('list')).toBeVisible(); // espera a lista aparecer
await expect(page.getByRole('listitem')).toHaveCount(5); // depois conta os itensUm teste completo usando múltiplos tipos de assertion
import { test, expect } from '@playwright/test';
test('usuário pode adicionar e visualizar um item de viagem', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Login' }).click();
await page.getByLabel('Username').fill('admin@becomeqa.com');
await page.getByLabel('Password').fill('testpass123');
await page.getByRole('button', { name: 'Submit' }).click();
// Assertion de página: verificar navegação
await expect(page).toHaveURL('/dashboard');
// Assertion de visibilidade
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
// Assertion de texto
await expect(page.getByRole('heading', { level: 1 })).toHaveText('My Travel Items');
// Adicionar um novo item
await page.getByRole('button', { name: 'Add Item' }).click();
await page.getByLabel('Destination').fill('Tokyo');
await page.getByRole('button', { name: 'Save' }).click();
// Assertion de count: tabela deve ter mais uma linha
const rows = page.getByRole('row');
await expect(rows).toHaveCount(7); // cabeçalho + 6 itens
// Assertion de conteúdo de texto na nova linha
await expect(page.getByRole('cell', { name: 'Tokyo' })).toBeVisible();
// Assertion de status no novo item
const tokyoRow = page.getByRole('row').filter({ hasText: 'Tokyo' });
await expect(tokyoRow.getByRole('cell').last()).toHaveText('Planned');
});FAQ
Por que minha assertion passa localmente mas falha no CI?Timing. Máquinas de CI são mais lentas. O elemento existe mas demora mais para aparecer. Aumente o timeout de assertion no playwright.config.ts ou investigue por que o elemento carrega mais devagar em ambientes de CI.
toEqual e toBe?
toBe verifica igualdade por referência (mesmo objeto na memória, ou primitivos idênticos). toEqual verifica igualdade profunda (mesma estrutura e valores, funciona para objetos e arrays). Para comparar objetos e arrays, use toEqual. Para strings, números e booleanos, use toBe.
Quando usar toMatchObject vs toEqual?
toMatchObject é uma correspondência parcial. O objeto real pode ter mais propriedades do que você especifica. toEqual exige uma correspondência exata. Para respostas de API onde você quer verificar campos principais sem listar todos, use toMatchObject.
Meu toHaveText falha porque o texto real tem espaço em branco extra. Como corrigir?
toHaveText remove espaços em branco no início e no final automaticamente. Para espaço em branco interno (múltiplos espaços, quebras de linha), use regex: toHaveText(/destination:\s+Tokyo/i).
→ Veja também: Fixtures Personalizados no Playwright: O Padrão que Torna os Testes Legíveis | Depurando Testes Instáveis: Um Guia Prático