Playwright Avançado
Gerencie autenticação, testes de API, mocking de rotas, cenários de múltiplas abas, viewports de celular e auditorias de acessibilidade.
Busque em todas as páginas da documentação
Gerencie autenticação, testes de API, mocking de rotas, cenários de múltiplas abas, viewports de celular e auditorias de acessibilidade.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida -- pronto para copiar e colar.
import { test, expect } from "@playwright/test";
// Estado de autenticação compartilhado
test.use({ storageState: "e2e/.auth/user.json" });
// Testes de API
const response = await request.get("/api/users");
expect(response.ok()).toBe(true);
// Mock de rotas de API
await page.route("/api/products", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([{ id: 1, name: "Widget" }]),
});
});
// Viewport de celular
test.use({ ...devices["iPhone 14"] });
// Testes de acessibilidade
import AxeBuilder from "@axe-core/playwright";
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);Quando usar isso: Quando seus testes E2E precisam lidar com sessões de login, testar APIs diretamente, fazer mock de respostas de rede ou verificar a acessibilidade.
// e2e/auth.setup.ts
import { test as setup, expect } from "@playwright/test";
const authFile = "e2e/.auth/user.json";
setup("authenticate", async ({ page }) => {
// Navegar para a página de login
await page.goto("/login");
// Preencher credenciais
await page.getByLabel("Email").fill("test@example.com");
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: /sign in/i }).click();
// Aguardar redirecionamento para o dashboard
await expect(page).toHaveURL("/dashboard");
// Salvar estado de autenticação (cookies, localStorage)
await page.context().storageState({ path: authFile });
});// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
projects: [
// Projeto de setup executa primeiro para autenticar
{
name: "setup",
testMatch: /.*\.setup\.ts/,
},
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
storageState: "e2e/.auth/user.json",
},
dependencies: ["setup"],
},
{
name: "mobile",
use: {
...devices["iPhone 14"],
storageState: "e2e/.auth/user.json",
},
dependencies: ["setup"],
},
],
});// e2e/dashboard.spec.ts
import { test, expect } from "@playwright/test";
// Todos os testes neste arquivo são autenticados (via configuração do projeto)
test.describe("Authenticated Dashboard", () => {
test("mostra nome do usuário no cabeçalho", async ({ page }) => {
await page.goto("/dashboard");
await expect(page.getByTestId("user-menu")).toContainText("test@example.com");
});
test("exibe pedidos recentes", async ({ page }) => {
await page.goto("/dashboard");
await expect(page.getByRole("table")).toBeVisible();
const rows = page.getByRole("row");
await expect(rows).toHaveCount(6); // 1 cabeçalho + 5 linhas de dados
});
test("pode atualizar perfil", async ({ page }) => {
await page.goto("/settings/profile");
await page.getByLabel("Display name").fill("Test User Updated");
await page.getByRole("button", { name: /save/i }).click();
await expect(page.getByRole("status")).toContainText("Profile updated");
});
});// e2e/api.spec.ts
import { test, expect } from "@playwright/test";
test.describe("API Tests", () => {
test("GET /api/products retorna produtos", async ({ request }) => {
const response = await request.get("/api/products");
expect(response.ok()).toBe(true);
const products = await response.json();
expect(products.length).toBeGreaterThan(0);
expect(products[0]).toHaveProperty("id");
expect(products[0]).toHaveProperty("name");
expect(products[0]).toHaveProperty("price");
});
test("POST /api/products cria um produto", async ({ request }) => {
const response = await request.post("/api/products", {
data: {
name: "New Widget",
price: 29.99,
},
});
expect(response.status()).toBe(201);
const product = await response.json();
expect(product.name).toBe("New Widget");
});
test("GET /api/products/:id retorna 404 para produto inexistente", async ({ request }) => {
const response = await request.get("/api/products/nonexistent");
expect(response.status()).toBe(404);
});
});// e2e/mocked.spec.ts
import { test, expect } from "@playwright/test";
test.describe("With Mocked API", () => {
test("mostra estado vazio quando não há produtos", async ({ page }) => {
// Mock da API para retornar array vazio
await page.route("/api/products", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([]),
});
});
await page.goto("/products");
await expect(page.getByText(/no products found/i)).toBeVisible();
});
test("lida com erro de API graciosamente", async ({ page }) => {
await page.route("/api/products", (route) => {
route.fulfill({
status: 500,
contentType: "application/json",
body: JSON.stringify({ error: "Internal Server Error" }),
});
});
await page.goto("/products");
await expect(page.getByRole("alert")).toContainText(/error/i);
});
test("modifica resposta da API", async ({ page }) => {
await page.route("/api/products", async (route) => {
// Busca resposta real e a modifica
const response = await route.fetch();
const json = await response.json();
const modified = json.map((p: { price: number }) => ({
...p,
price: 0, // tudo grátis!
}));
await route.fulfill({ response, body: JSON.stringify(modified) });
});
await page.goto("/products");
await expect(page.getByText("$0.00").first()).toBeVisible();
});
});// e2e/accessibility.spec.ts
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";
test.describe("Accessibility", () => {
test("página inicial não tem violações de a11y", async ({ page }) => {
await page.goto("/");
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"])
.analyze();
expect(results.violations).toEqual([]);
});
test("página de formulário não tem violações de a11y", async ({ page }) => {
await page.goto("/contact");
const results = await new AxeBuilder({ page })
.include("#contact-form")
.analyze();
expect(results.violations).toEqual([]);
});
test("relata violações específicas", async ({ page }) => {
await page.goto("/products");
const results = await new AxeBuilder({ page }).analyze();
// Loga violações para depuração
for (const violation of results.violations) {
console.log(`${violation.id}: ${violation.description}`);
for (const node of violation.nodes) {
console.log(` ${node.html}`);
}
}
expect(results.violations).toEqual([]);
});
});O que isso demonstra:
request do Playwrightpage.route() para cenários controladosstorageState salva e restaura cookies e localStorage, para que testes autenticados não repitam o logindependenciespage.route() intercepta requisições de rede que correspondem a um padrão de URL e permite que você as preencha, aborte ou modifiquerequest é um fixture do Playwright para fazer requisições HTTP diretamente -- útil para testes de API sem uma página de navegadorAxeBuilder executa o motor de acessibilidade axe-core dentro da página e retorna violações com metadados detalhadosTestando em múltiplas abas:
test("abre produto em nova aba", async ({ page, context }) => {
await page.goto("/products");
// Aguarda nova página (aba)
const newPagePromise = context.waitForEvent("page");
await page.getByRole("link", { name: /widget/i }).click({
modifiers: ["Meta"], // Cmd+Click abre nova aba
});
const newPage = await newPagePromise;
await newPage.waitForLoadState();
await expect(newPage).toHaveURL(/\/products\/widget/);
await expect(newPage.getByRole("heading")).toHaveText("Widget");
});Testando com diferentes viewports:
test.describe("navegação mobile", () => {
test.use({
viewport: { width: 375, height: 667 },
isMobile: true,
hasTouch: true,
});
test("mostra menu hambúrguer", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("button", { name: /menu/i })).toBeVisible();
await expect(page.getByRole("navigation")).not.toBeVisible();
});
});Múltiplas funções de autenticação:
// playwright.config.ts
projects: [
{ name: "admin-setup", testMatch: /admin\.setup\.ts/ },
{ name: "user-setup", testMatch: /user\.setup\.ts/ },
{
name: "admin-tests",
use: { storageState: "e2e/.auth/admin.json" },
dependencies: ["admin-setup"],
testMatch: /.*admin.*\.spec\.ts/,
},
{
name: "user-tests",
use: { storageState: "e2e/.auth/user.json" },
dependencies: ["user-setup"],
testMatch: /.*user.*\.spec\.ts/,
},
],// Manipulador de rota tipado
await page.route("/api/users", async (route: Route) => {
const method = route.request().method();
if (method === "GET") {
await route.fulfill({ body: JSON.stringify([]) });
} else {
await route.continue();
}
});
// Resposta de API tipada
interface Product {
id: number;
name: string;
price: number;
}
const response = await request.get("/api/products");
const products: Product[] = await response.json();Arquivo storageState no git -- O arquivo de estado de autenticação contém tokens de sessão. Correção: Adicione e2e/.auth/ ao .gitignore.
Expiração do estado de autenticação -- Se as sessões expirarem rapidamente, o setup pode precisar ser executado com mais frequência. Correção: Use tokens de teste de longa duração ou execute o setup antes de cada arquivo de teste com test.beforeEach.
page.route deve ser chamado antes da navegação -- Rotas definidas após page.goto() não interceptam as requisições iniciais de carregamento da página. Correção: Chame page.route() antes de page.goto().
Falsos positivos do axe-core -- Algumas violações podem ser intencionais (por exemplo, contraste de cores em elementos de marca). Correção: Use .exclude() para exceções conhecidas ou .disableRules() para regras específicas:
new AxeBuilder(\{ page \})
.exclude("#brand-banner")
.disableRules(["color-contrast"])
.analyze();Testes de API e autenticação -- O fixture request não usa storageState por padrão. Correção: Use request.newContext({ storageState: "e2e/.auth/user.json" }) ou configure-o na configuração do projeto.
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| Cypress intercept | Você usa Cypress e precisa de mocking de rotas | Você usa Playwright |
| Lighthouse CI | Você precisa de auditorias de performance e a11y em CI | Você quer asserções granulares de violações de a11y |
| jest-axe | Você quer testes de a11y em testes unitários (jsdom) | Você precisa de renderização real do navegador para resultados precisos |
| Supertest | Você quer testes de integração de API sem um navegador | Você precisa de contexto do navegador (cookies, autenticação) |
storageState.Não. Ele contém tokens de sessão. Adicione e2e/.auth/ ao .gitignore.
await page.route("/api/products", (route) => {
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify([{ id: 1, name: "Widget" }]),
});
});Chame page.route() antes de page.goto().
Ele faz requisições HTTP diretamente sem uma página de navegador -- útil para testes de API:
const response = await request.get("/api/products");
expect(response.ok()).toBe(true);Instale @axe-core/playwright e use:
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa"])
.analyze();
expect(results.violations).toEqual([]);Crie projetos de setup e arquivos de estado de armazenamento separados para cada função. Use padrões testMatch para direcionar os testes para o projeto correto:
{ name: "admin-tests", use: { storageState: "e2e/.auth/admin.json" } }Rotas definidas após a navegação não interceptam as requisições iniciais de carregamento da página. Sempre registre mocks de rota antes de navegar.
const newPagePromise = context.waitForEvent("page");
await page.getByRole("link").click({ modifiers: ["Meta"] });
const newPage = await newPagePromise;
await newPage.waitForLoadState();test.use({
viewport: { width: 375, height: 667 },
isMobile: true,
hasTouch: true,
});Ou use presets de dispositivo: test.use({ ...devices["iPhone 14"] }).
Exclua elementos específicos ou desative regras:
new AxeBuilder({ page })
.exclude("#brand-banner")
.disableRules(["color-contrast"])
.analyze();await page.route("/api/users", async (route: Route) => {
const method = route.request().method();
if (method === "GET") {
await route.fulfill({ body: JSON.stringify([]) });
} else {
await route.continue();
}
});Não. Use request.newContext({ storageState: "e2e/.auth/user.json" }) ou configure-o nas configurações do projeto.
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥