DevTools

Cheatsheet Playwright

Framework da Microsoft para testes end-to-end e automação web

Voltar às linguagens
Playwright
125 cards encontrados
Categorias:
Versões:

Instalação e Setup


12 cards
Iniciar projecto
npm init playwright@latest

Assistente oficial que cria a estrutura completa. Gera playwright.config.ts, pasta tests/ e GitHub Actions. Pergunta linguagem, browsers e CI.

Estrutura de ficheiros
tests/
  login.spec.ts
  carrinho.spec.ts
  fixtures/
    auth.ts
playwright.config.ts
package.json

Testes vivem em ficheiros .spec.ts. fixtures/ guarda fixtures custom. O config fica na raiz. Cada ficheiro pode ter múltiplos testes organizados por funcionalidade.

Global setup e teardown
export default defineConfig({
  globalSetup: "./global-setup.ts",
  globalTeardown: "./global-teardown.ts",
});

// global-setup.ts
export default async function () {
  // seed BD, criar dados de teste
}

globalSetup corre uma vez antes de todos os testes. globalTeardown corre no final. Ideal para seed de BD ou limpar recursos. Recebe config como argumento.

Instalar browsers
npx playwright install
npx playwright install chromium
npx playwright install --with-deps

install descarrega Chromium, Firefox e WebKit. --with-deps instala dependências do sistema (Linux). Pode instalar apenas um browser específico.

Primeiro teste
import { test, expect } from "@playwright/test";

test("página inicial tem título", async ({ page }) => {
  await page.goto("/");
  await expect(page).toHaveTitle(/Minha App/);
});

test() define um teste com nome e callback. page é injectado via fixtures. expect() faz asserções com auto-retry. goto() navega para o URL (usa baseURL).

TypeScript e imports
import { test, expect, type Page } from "@playwright/test";

async function login(page: Page, user: string) {
  await page.goto("/login");
  await page.getByLabel("Email").fill(user);
  await page.getByRole("button", { name: "Entrar" }).click();
}

type Page tipifica parâmetros em helpers. Funções auxiliares recebem page como argumento. TypeScript dá autocomplete e detecta erros. Totalmente suportado nativamente.

Codegen (gravar acções)
npx playwright codegen wikipedia.org
npx playwright codegen --target=python
npx playwright codegen --device="iPhone 13"

codegen abre um browser e grava as acções como código. --target muda a linguagem de output. --device simula dispositivos móveis. Óptimo para aprender selectores.

Projects (multi-browser)
export default defineConfig({
  projects: [
    { name: "chromium", use: { ...devices["Desktop Chrome"] } },
    { name: "firefox", use: { ...devices["Desktop Firefox"] } },
    { name: "mobile", use: { ...devices["iPhone 13"] } },
  ],
});

projects corre os testes em múltiplos browsers. devices pré-configura viewport e user-agent. Cada project pode ter config próprio. Os testes correm em todos por padrão.

Ambientes e variáveis
// .env
BASE_URL=http://localhost:3000

// playwright.config.ts
use: {
  baseURL: process.env.BASE_URL || "http://localhost:3000",
}

// correr com env:
// BASE_URL=https://staging.pt npx playwright test

process.env lê variáveis de ambiente. Permite testar contra staging ou produção. dotenv pode carregar ficheiros .env. Nunca commite credenciais.

Ficheiro de configuração
// playwright.config.ts
import { defineConfig } from "@playwright/test";

export default defineConfig({
  testDir: "./tests",
  timeout: 30000,
  retries: 2,
  workers: 4,
  use: {
    baseURL: "http://localhost:3000",
    headless: true,
  },
});

defineConfig tipifica a configuração. testDir define onde estão os testes. timeout é o limite por teste. use configura opções partilhadas por todos os testes.

Web server automático
export default defineConfig({
  webServer: {
    command: "npm run dev",
    url: "http://localhost:3000",
    reuseExistingServer: !process.env.CI,
    timeout: 120000,
  },
});

webServer inicia a app antes dos testes. command é o comando de arranque. reuseExistingServer evita reiniciar em dev. Em CI, força sempre um servidor novo.

Extensões e plugins
// eslint-plugin-playwright
// .eslintrc
{
  "plugins": ["playwright"],
  "extends": ["plugin:playwright/recommended"]
}

// @axe-core/playwright (acessibilidade)
import AxeBuilder from "@axe-core/playwright";

eslint-plugin-playwright aplica boas práticas via lint. @axe-core/playwright testa acessibilidade. A comunidade tem plugins para reporting, visuais e mais. Instale via npm.

Estrutura de Testes


13 cards
Teste simples
test("faz login com sucesso", async ({ page }) => {
  await page.goto("/login");
  await page.getByLabel("Email").fill("ana@site.pt");
  await page.getByLabel("Password").fill("123456");
  await page.getByRole("button", { name: "Entrar" }).click();
  await expect(page).toHaveURL("/dashboard");
});

Cada test() recebe fixtures via destructuring. page é o browser context isolado. O teste falha se qualquer expect() não passar. Nomes devem descrever o comportamento.

Fixtures disponíveis
test("exemplo", async ({
  page,       // Page - página principal
  context,    // BrowserContext - contexto isolado
  browser,    // Browser - instância do browser
  request,    // APIRequestContext - pedidos HTTP
}) => {
  // ...
});

page é a página com todas as interacções. context permite criar múltiplas páginas. browser é a instância de baixo nível. request faz chamadas API directas.

Testes paralelos
test.describe.configure({ mode: "parallel" });

test("teste A", async ({ page }) => { });
test("teste B", async ({ page }) => { });
test("teste C", async ({ page }) => { });

mode: "parallel" corre testes do grupo simultaneamente. Por padrão, testes no mesmo ficheiro são sequenciais. Cada teste tem browser isolado. Acelera suites grandes.

Soft assertions
test("verifica múltiplos campos", async ({ page }) => {
  await expect.soft(page.getByText("Nome")).toHaveText("Ana");
  await expect.soft(page.getByText("Email")).toHaveText("ana@site.pt");
  await expect.soft(page.getByText("Idade")).toHaveText("30");
  // todas são verificadas mesmo se uma falhar
});

expect.soft() não para o teste ao falhar. Todas as asserções são executadas. O relatório mostra todas as falhas. Ideal para verificar múltiplos campos de uma vez.

Agrupar com describe
test.describe("Carrinho de compras", () => {
  test("adiciona item", async ({ page }) => { });
  test("remove item", async ({ page }) => { });
  test("calcula total", async ({ page }) => { });
});

test.describe() agrupa testes relacionados. O nome aparece como prefixo no relatório. Pode ser aninhado. Útil para organizar por funcionalidade ou módulo.

Fixtures custom
import { test as base } from "@playwright/test";

export const test = base.extend({
  loggedInPage: async ({ page }, use) => {
    await page.goto("/login");
    await page.getByLabel("Email").fill("admin@site.pt");
    await page.getByRole("button").click();
    await use(page);
  },
});

// uso: test("...", async ({ loggedInPage }) => { });

base.extend() cria fixtures personalizadas. O código antes de use() é o setup. use(page) entrega ao teste. Código depois é teardown. Reutilizável em múltiplos ficheiros.

Testes seriais
test.describe.configure({ mode: "serial" });

test("cria conta", async ({ page }) => { });
test("faz login", async ({ page }) => { });
test("verifica perfil", async ({ page }) => { });

mode: "serial" garante ordem e para se um falhar. Útil quando testes dependem do estado anterior. Se o primeiro falha, os restantes são skip. Evita cascata de erros.

Hooks beforeEach/afterEach
test.describe("Painel admin", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/admin/login");
    await login(page);
  });

  test.afterEach(async ({ page }) => {
    await page.evaluate(() => localStorage.clear());
  });
});

beforeEach corre antes de cada teste do grupo. afterEach corre depois. Ideal para setup repetitivo como login. Reduz duplicação de código entre testes.

Opções por ficheiro (test.use)
test.use({
  baseURL: "http://localhost:8080",
  viewport: { width: 1920, height: 1080 },
  locale: "pt-PT",
  timezoneId: "Europe/Lisbon",
});

test.use() configura todos os testes do ficheiro. viewport define resolução. locale e timezoneId testam internacionalização. Sobrepõe o config global.

Retries
// config global:
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
});

// por teste:
test("operação instável", async ({ page }) => { });
test.describe.configure({ retries: 3 });

retries re-executa testes que falham. Em CI use 2, em local 0. Testes flaky passam na retry. O relatório mostra tentativas. Não abuse — corrija a causa raiz.

Hooks beforeAll/afterAll
test.describe("API tests", () => {
  test.beforeAll(async () => {
    await seedDatabase();
  });

  test.afterAll(async () => {
    await cleanupDatabase();
  });
});

beforeAll corre uma vez antes de todos os testes do grupo. afterAll corre uma vez no final. Para setup caro (seed BD, criar servidor). Não recebe page por padrão.

Skip e only
test.skip("teste quebrado", async ({ page }) => { });
test.only("foca neste", async ({ page }) => { });
test.fixme("precisa de arranjo", async ({ page }) => { });

// condicional:
test.skip(process.env.CI === "true", "Só local");

test.skip() ignora o teste. test.only() corre apenas esse (debug). test.fixme() marca como conhecido broken. Skip condicional aceita booleano e razão.

Anotações e metadata
test("checkout", async ({ page }) => {
  test.slow();           // triplica timeout
  test.setTimeout(60000); // timeout custom
});

// anotações no relatório:
test.info().annotations.push({
  type: "issue",
  description: "https://github.com/app/issues/42",
});

test.slow() triplica o timeout para testes lentos. setTimeout() define limite custom. annotations adiciona metadata ao relatório HTML. Útil para ligar a issues.

Localizadores


13 cards
Por role (recomendado)
page.getByRole("button", { name: "Enviar" })
page.getByRole("heading", { name: "Bem-vindo" })
page.getByRole("link", { name: "Sobre" })
page.getByRole("checkbox", { name: "Aceito" })

getByRole() localiza por papel de acessibilidade. É o método recomendado pela equipa Playwright. name filtra pelo texto acessível. Reflete como utilizadores e screen readers vêem a página.

Por test id
page.getByTestId("btn-submit")
page.getByTestId("nav-menu")

// no HTML:
// <button data-testid="btn-submit">Enviar</button>

getByTestId() usa atributo data-testid. Selector estável que não muda com CSS ou texto. Configure o atributo no config se diferente. Último recurso quando role/text não bastam.

Posição e navegação
lista.first()
lista.last()
lista.nth(2)

// navegação na hierarquia:
row.locator("span")        // descendente
page.locator("div").locator("p")  // encadear

first(), last() e nth() seleccionam por posição. locator() dentro de outro localiza descendentes. Encadear narrow down progressivamente. Index é 0-based.

Locators vs ElementHandle
// Recomendado: Locator (lazy, auto-wait)
const btn = page.getByRole("button", { name: "OK" });
await btn.click();

// Evitar: ElementHandle (eager, sem retry)
const el = await page.$(".botao");
await el?.click();

Locator é lazy e re-avalia a cada acção. Faz auto-wait e retry automaticamente. ElementHandle é eager e pode ficar stale. Prefira sempre Locators — são mais robustos.

Por texto
page.getByText("Bem-vindo")
page.getByText("Bem-vindo", { exact: true })
page.getByText(/bem-vindo/i)

getByText() localiza pelo texto visível. Sem exact, faz match parcial. exact: true exige correspondência total. Aceita regex para patterns flexíveis.

Por alt e title
page.getByAltText("Logo da empresa")
page.getByTitle("Definições")

getByAltText() localiza imagens pelo alt. getByTitle() localiza pelo atributo title (tooltip). Ambos são acessíveis e descritivos. Prefira alt para imagens.

Localizar por conteúdo
// elemento que contém outro:
page.locator("div", { has: page.getByText("Preço") })

// elemento com texto específico:
page.locator("li", { hasText: "Produto A" })

// layout: item à direita de outro
page.getByText("Total").locator("..")

has localiza pai que contém filho específico. hasText filtra por texto interno. .. sobe para o elemento pai. Útil para localizar containers sem test-id.

Por label
page.getByLabel("Email")
page.getByLabel("Password", { exact: true })

getByLabel() localiza inputs pelo label associado. Ideal para formulários. Funciona com <label> e aria-label. Mais resiliente que selectores CSS.

CSS e XPath
page.locator(".botao.primary")
page.locator("#formulario-login")
page.locator("xpath=//div[@class='card']")
page.locator("div.card >> text=Detalhes")

locator() aceita selectores CSS e XPath. xpath= prefixa XPath explícito. >> encadeia selectores. Use como último recurso — prefira locators semânticos.

Múltiplos elementos
const items = page.getByRole("listitem");
const count = await items.count();

for (let i = 0; i < count; i++) {
  const texto = await items.nth(i).textContent();
  console.log(texto);
}

// ou: allTextContents()
const textos = await items.allTextContents();

count() retorna quantos elementos correspondem. nth(i) acede a cada um. allTextContents() extrai texto de todos. Locators são lazy — avaliam ao interagir.

Por placeholder
page.getByPlaceholder("nome@exemplo.pt")
page.getByPlaceholder("Pesquisar...")

getByPlaceholder() localiza pelo atributo placeholder. Útil quando não há label visível. Menos robusto que getByLabel(). O placeholder pode mudar com redesigns.

Filtrar localizadores
const linhas = page.getByRole("listitem");
linhas.filter({ hasText: "Ativo" })
linhas.filter({ has: page.getByRole("button") })
linhas.filter({ hasText: "Ana" }).filter({ hasText: "Admin" })

filter() refina um localizador existente. hasText filtra por texto contido. has filtra por elemento filho. Pode encadear múltiplos filtros para precisão.

Espera por localizador
await page.getByText("Carregando").waitFor({ state: "hidden" });
await page.getByRole("dialog").waitFor({ state: "visible" });
await locator.waitFor({ timeout: 10000 });

waitFor() espera até o elemento estar no estado desejado. state: "visible" espera aparecer. state: "hidden" espera desaparecer. timeout custom sobrepõe o global.

Ações e Interações


13 cards
Clicar
await locator.click();
await locator.click({ button: "right" });
await locator.click({ clickCount: 2 });
await locator.click({ modifiers: ["Control"] });

click() faz auto-wait por visibilidade e estabilidade. button: "right" é clique direito. clickCount: 2 é duplo clique. modifiers simula Ctrl/Shift+click.

Select / dropdown
await select.selectOption("pt");
await select.selectOption({ label: "Português" });
await select.selectOption({ value: "pt" });
await select.selectOption(["a", "b"]);  // multi-select

selectOption() escolhe por value, label ou index. Para multi-select, passe array. Dispara evento change. Funciona com <select> nativos.

Scroll
await locator.scrollIntoViewIfNeeded();
await page.mouse.wheel(0, 500);
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));

scrollIntoViewIfNeeded() torna o elemento visível. mouse.wheel() faz scroll por pixels. evaluate() executa JS nativo para scroll. Acções de click já fazem scroll automático.

Acções em sequência
await page.getByLabel("Nome").fill("Ana");
await page.getByLabel("Email").fill("ana@site.pt");
await page.getByRole("combobox").selectOption("PT");
await page.getByRole("checkbox").check();
await page.getByRole("button", { name: "Registar" }).click();

Cada acção faz auto-wait antes de executar. Não precisa de sleep() ou waits manuais. A sequência é determinística. Se um elemento não aparece, o teste falha com timeout claro.

Preencher e limpar
await input.fill("texto novo");
await input.clear();
await input.fill("");  // também limpa
await input.type("lento", { delay: 100 });

fill() define o valor instantaneamente. clear() limpa o campo. type() simula digitação tecla a tecla com delay. fill() dispara eventos de input/change.

Hover e foco
await locator.hover();
await locator.focus();
await locator.blur();

// hover com posição:
await locator.hover({ position: { x: 10, y: 5 } });

hover() move o rato sobre o elemento (tooltips, menus). focus() dá foco de teclado. blur() remove foco. position especifica ponto exacto do hover.

Acção + navegação
await Promise.all([
  page.waitForURL("**/dashboard"),
  botao.click(),
]);

// ou com waitForNavigation:
await Promise.all([
  page.waitForNavigation(),
  link.click(),
]);

Promise.all() espera navegação e clique simultaneamente. Evita race conditions. waitForURL() espera um URL específico. Padrão essencial para cliques que navegam.

Teclas e atalhos
await input.press("Enter");
await input.press("Tab");
await page.keyboard.press("Control+A");
await page.keyboard.type("Olá mundo");
await page.keyboard.down("Shift");
await page.keyboard.up("Shift");

press() simula uma tecla no elemento. keyboard.press() envia globalmente. Combinações com + (Control+A). down()/up() para manter pressionado.

Drag and drop
await origem.dragTo(alvo);

// ou manualmente:
await origem.hover();
await page.mouse.down();
await alvo.hover();
await page.mouse.up();

dragTo() arrasta um elemento para outro. Para controlo fino use mouse.down()/mouse.up(). Funciona com bibliotecas de DnD. Auto-wait por ambos os elementos.

Evaluate (JavaScript)
const titulo = await page.evaluate(() => document.title);
await page.evaluate((el) => el.classList.add("ativo"), locator);
const dados = await page.evaluate(() => JSON.parse(localStorage.getItem("app")));

evaluate() executa JavaScript no browser. Retorna valores serializáveis. Pode passar elementos como argumento. Use quando não há API Playwright para a acção.

Checkbox e radio
await checkbox.check();
await checkbox.uncheck();
await radio.check();  // selecciona

// verificar estado:
const marcado = await checkbox.isChecked();

check() marca (idempotente). uncheck() desmarca. Para radio buttons, check() selecciona a opção. isChecked() retorna o estado actual como booleano.

Upload de ficheiros
await input.setInputFiles("foto.png");
await input.setInputFiles(["a.png", "b.png"]);

// sem input visível (filechooser):
const [fileChooser] = await Promise.all([
  page.waitForEvent("filechooser"),
  botao.click(),
]);
await fileChooser.setFiles("doc.pdf");

setInputFiles() define ficheiros num input file. Para upload via botão, use filechooser event. Aceita paths ou buffers. Múltiplos ficheiros via array.

Touch e mobile
await locator.tap();
await page.touchscreen.tap(100, 200);

// swipe:
await page.touchscreen.tap(50, 300);
// usar mouse para simular swipe
await page.mouse.move(50, 300);
await page.mouse.down();
await page.mouse.move(300, 300);
await page.mouse.up();

tap() simula toque em mobile. touchscreen.tap() com coordenadas. Para swipe use sequência de mouse. Configure hasTouch: true no project para emular touch.

Asserções


13 cards
Visibilidade
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
await expect(locator).toBeAttached();
await expect(locator).not.toBeAttached();

toBeVisible() verifica que está visível no viewport. toBeHidden() verifica oculto ou inexistente. toBeAttached() verifica presença no DOM (mesmo oculto). Auto-retry até timeout.

Contagem
await expect(itens).toHaveCount(3);
await expect(itens).not.toHaveCount(0);

toHaveCount() verifica número de elementos correspondentes. Auto-retry até a contagem estabilizar. Útil para listas dinâmicas após filtros ou adições.

Negação
await expect(locator).not.toBeVisible();
await expect(locator).not.toHaveText("Erro");
await expect(page).not.toHaveURL("/login");

.not nega qualquer asserção. Também faz auto-retry (espera a condição negativa). Útil para verificar que erros não aparecem ou modais fecharam.

Asserções em sequência
const card = page.getByTestId("produto-1");
await expect(card).toBeVisible();
await expect(card.getByRole("heading")).toHaveText("Produto A");
await expect(card.getByText("Preço")).toContainText("19,99€");
await expect(card.getByRole("button")).toBeEnabled();

Encadeie asserções para verificar componentes completos. Cada expect() espera independentemente. Se uma falha, o teste para (excepto soft). Organize por ordem visual para debugging fácil.

Texto
await expect(locator).toHaveText("Olá Mundo");
await expect(locator).toContainText("Mundo");
await expect(locator).toHaveText(/olá/i);
await expect(lista).toHaveText(["A", "B", "C"]);

toHaveText() verifica texto exacto (normaliza espaços). toContainText() verifica substring. Aceita regex. Para listas, passe array para verificar todos os itens.

Atributos e CSS
await expect(link).toHaveAttribute("href", "/sobre");
await expect(el).toHaveClass(/ativo/);
await expect(el).toHaveCSS("color", "rgb(255, 0, 0)");
await expect(el).toHaveId("main-content");

toHaveAttribute() verifica atributos HTML. toHaveClass() verifica classes (aceita regex). toHaveCSS() verifica estilos computados. toHaveId() verifica o ID.

Auto-retry e timeout
// timeout global: 5s por padrão
// timeout custom por asserção:
await expect(locator).toBeVisible({ timeout: 10000 });

// asserções re-tentam automaticamente
// até ao timeout — nunca use sleep()

Asserções fazem polling automático até passar ou expirar. timeout custom sobrepõe o global (5s). Elimina a necessidade de waitForTimeout(). Torna testes determinísticos.

Valor de input
await expect(input).toHaveValue("abc");
await expect(input).toHaveValue(/@site\.pt$/);
await expect(input).toBeEmpty();

toHaveValue() verifica o valor actual do input. Aceita string exacta ou regex. toBeEmpty() verifica campo vazio. Funciona com text, number, date inputs.

Página (URL e título)
await expect(page).toHaveURL("/dashboard");
await expect(page).toHaveURL(/.*\/dashboard/);
await expect(page).toHaveTitle("Painel - App");
await expect(page).toHaveTitle(/Painel/);

toHaveURL() verifica o URL actual (aceita regex). toHaveTitle() verifica o título da página. Auto-retry espera navegação completar. Essencial após redirects.

Asserções genéricas
import { expect } from "@playwright/test";

expect(2 + 2).toBe(4);
expect([1, 2, 3]).toContain(2);
expect({ nome: "Ana" }).toEqual({ nome: "Ana" });
expect("olá mundo").toMatch(/mundo/);

O expect do Playwright inclui asserções genéricas. toBe() compara valores. toContain() verifica inclusão. toEqual() compara objectos. Sem auto-retry (são síncronas).

Estado de elementos
await expect(checkbox).toBeChecked();
await expect(checkbox).not.toBeChecked();
await expect(botao).toBeEnabled();
await expect(botao).toBeDisabled();
await expect(input).toBeFocused();
await expect(input).toBeEditable();

toBeChecked() para checkboxes/radios. toBeEnabled()/toBeDisabled() verificam interactividade. toBeFocused() verifica foco activo. toBeEditable() verifica que aceita input.

Screenshot comparison
await expect(page).toHaveScreenshot("home.png");
await expect(locator).toHaveScreenshot("card.png");
await expect(page).toHaveScreenshot("home.png", {
  maxDiffPixelRatio: 0.01,
});

toHaveScreenshot() compara com screenshot de referência. Na primeira execução cria o baseline. maxDiffPixelRatio define tolerância. Detecta regressões visuais automaticamente.

API response assertions
const response = await request.get("/api/users");
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);

const body = await response.json();
expect(body.users).toHaveLength(5);

request fixture faz chamadas HTTP directas. ok() verifica status 2xx. status() retorna o código exacto. json() faz parse do body. Ideal para testar APIs sem UI.

Navegação e Página


12 cards
Ir para URL
await page.goto("https://site.pt");
await page.goto("/login");  // usa baseURL
await page.goto("/admin", { waitUntil: "networkidle" });

goto() navega para um URL. Com baseURL configurado, paths relativos funcionam. waitUntil controla quando considera carregado: load, domcontentloaded ou networkidle.

URL e título actuais
const url = page.url();
const titulo = await page.title();
const conteudo = await page.content();

page.url() retorna o URL actual (síncrono). page.title() retorna o título (async). page.content() retorna o HTML completo. Úteis para debugging e asserções custom.

Emulação de dispositivo
import { devices } from "@playwright/test";

test.use({ ...devices["iPhone 13"] });

// ou inline:
test.use({
  viewport: { width: 375, height: 812 },
  userAgent: "Mozilla/5.0 (iPhone...)",
  hasTouch: true,
  isMobile: true,
});

devices tem presets de viewport, UA e touch. hasTouch activa eventos touch. isMobile activa comportamento mobile. Teste responsividade sem CSS hacks.

Voltar / avançar / recarregar
await page.goBack();
await page.goForward();
await page.reload();
await page.reload({ waitUntil: "networkidle" });

goBack() e goForward() navegam no histórico. reload() recarrega a página actual. Todos aceitam waitUntil. Úteis para testar persistência de estado.

Estado de carregamento
await page.waitForLoadState("load");
await page.waitForLoadState("domcontentloaded");
await page.waitForLoadState("networkidle");

load espera recursos (imagens, CSS). domcontentloaded espera DOM parseado. networkidle espera 500ms sem pedidos de rede. O mais strict é networkidle mas pode ser lento.

Geolocalização e permissões
test.use({
  geolocation: { latitude: 38.72, longitude: -9.14 },
  permissions: ["geolocation"],
});

// no teste:
await page.goto("/mapa");
await expect(page.getByText("Lisboa")).toBeVisible();

geolocation simula coordenadas GPS. permissions concede permissões sem dialog. Funciona com camera, microphone, notifications. Testa features location-based.

Esperar URL
await page.waitForURL("**/dashboard");
await page.waitForURL(/.*\/users\/\d+/);
await page.waitForURL("**/login", { timeout: 5000 });

waitForURL() espera até o URL corresponder. Aceita glob (**) ou regex. Essencial após redirects assíncronos. timeout custom para operações lentas.

Frames e iframes
const frame = page.frameLocator("iframe#editor");
await frame.getByRole("button", { name: "Salvar" }).click();

// frame por name ou URL:
const frame2 = page.frameLocator('iframe[name="pagamento"]');

frameLocator() acede a conteúdo dentro de iframes. Todas as operações funcionam normalmente dentro do frame. Encadeie para iframes aninhados. Essencial para embeds de pagamento.

Emulação de rede
test.use({
  // simular rede lenta:
  contextOptions: {
    // offline:
  },
});

// offline:
await context.setOffline(true);
await page.reload();
await expect(page.getByText("Sem conexão")).toBeVisible();
await context.setOffline(false);

setOffline(true) simula perda de conexão. Testa estados offline e retry. setOffline(false) restaura. Essencial para PWAs e apps com modo offline.

Nova página / separador
const novaPagina = await context.newPage();
await novaPagina.goto("/");

// capturar popup:
const [popup] = await Promise.all([
  context.waitForEvent("page"),
  page.getByRole("link", { name: "Abrir" }).click(),
]);
await popup.waitForLoadState();

context.newPage() abre novo separador. waitForEvent("page") captura popups abertos por cliques. Cada página é independente. Popups partilham o mesmo context (cookies).

Cookies e storage
// cookies:
await context.addCookies([{ name: "tema", value: "escuro", url: "/" }]);
const cookies = await context.cookies();

// localStorage:
await page.evaluate(() => localStorage.setItem("token", "abc"));
const valor = await page.evaluate(() => localStorage.getItem("token"));

addCookies() injecta cookies no context. cookies() lista todos. evaluate() acede a localStorage/sessionStorage. Útil para preparar estado sem login manual.

Múltiplos contextos
const userA = await browser.newContext();
const userB = await browser.newContext();

const pageA = await userA.newPage();
const pageB = await userB.newPage();

// sessions independentes (cookies separados)
await pageA.goto("/login");
await pageB.goto("/login");

browser.newContext() cria sessões isoladas. Cookies e storage são independentes. Ideal para testar chat, permissões multi-utilizador. Cada context é como um browser novo.

Avançado


13 cards
Traces (depuração)
// config: gravar trace em falhas
export default defineConfig({
  use: {
    trace: "on-first-retry",
    // "on" | "off" | "retain-on-failure"
  },
});

// manual:
await context.tracing.start({ screenshots: true, snapshots: true });
await context.tracing.stop({ path: "trace.zip" });

trace grava screenshots, DOM snapshots e rede. on-first-retry grava só na retry. Abra com npx playwright show-trace trace.zip. Mostra timeline completa do teste.

Downloads
const [download] = await Promise.all([
  page.waitForEvent("download"),
  page.getByRole("button", { name: "Descarregar" }).click(),
]);

const path = await download.path();
const nome = download.suggestedFilename();
await download.saveAs("downloads/" + nome);

waitForEvent("download") captura o download. suggestedFilename() dá o nome original. saveAs() guarda no disco. path() retorna o path temporário. Combine com Promise.all.

Clock e timers
await page.clock.install({ time: new Date("2025-01-01") });
await page.clock.pauseAt(new Date("2025-06-15"));
await page.clock.fastForward("30:00");
await page.clock.runFor(5000);
await page.clock.resume();

clock.install() controla o tempo no browser. pauseAt() congela numa data. fastForward() avança sem esperar. Teste countdowns, sessões expiradas e agendamentos.

Sharding (CI)
// dividir testes em 4 máquinas:
npx playwright test --shard=1/4
npx playwright test --shard=2/4
npx playwright test --shard=3/4
npx playwright test --shard=4/4

// merge reports:
npx playwright merge-reports ./reports

--shard divide a suite em partes para CI paralelo. Cada shard corre em máquina diferente. merge-reports combina resultados. Reduz tempo total em pipelines CI/CD.

Screenshots
await page.screenshot({ path: "full.png", fullPage: true });
await locator.screenshot({ path: "componente.png" });

// config: screenshot em falha
use: {
  screenshot: "only-on-failure",
}

screenshot() captura a página ou elemento. fullPage: true inclui scroll completo. only-on-failure no config guarda só quando falha. Anexado ao relatório HTML.

Visual comparison
await expect(page).toHaveScreenshot("homepage.png", {
  maxDiffPixels: 100,
  mask: [page.locator(".data-dinamica")],
});

// actualizar baselines:
// npx playwright test --update-snapshots

toHaveScreenshot() compara pixel a pixel. mask esconde áreas dinâmicas (datas, avatars). --update-snapshots regenera baselines. Detecta regressões CSS automaticamente.

Aria snapshots
await expect(page.getByRole("list")).toMatchAriaSnapshot(`
  - listitem: Produto A
  - listitem: Produto B
  - listitem:
    - text: Produto C
    - button: Remover
`);

toMatchAriaSnapshot() verifica a árvore de acessibilidade. Mais resiliente que HTML. Ignora detalhes de implementação. Ideal para verificar estrutura semântica da página.

Vídeo
// config:
use: {
  video: "on-first-retry",
  // "on" | "off" | "retain-on-failure"
}

// manual:
const video = page.video();
await video.saveAs("teste.webm");
await video.delete();

video grava a execução do teste. on-first-retry grava só na retry (poupa espaço). Ficheiros em test-results/. Útil para debug visual de falhas intermitentes.

Acessibilidade (a11y)
import AxeBuilder from "@axe-core/playwright";

test("sem erros de acessibilidade", async ({ page }) => {
  await page.goto("/");
  const results = await new AxeBuilder({ page }).analyze();
  expect(results.violations).toEqual([]);
});

@axe-core/playwright integra Axe com Playwright. analyze() verifica WCAG automaticamente. violations lista problemas encontrados. Pode filtrar por tags (wcag2a, wcag2aa).

Error handling em testes
test("graceful failure", async ({ page }) => {
  await page.goto("/");

  // capturar erros de consola:
  const errors: string[] = [];
  page.on("console", (msg) => {
    if (msg.type() === "error") errors.push(msg.text());
  });

  page.on("pageerror", (err) => errors.push(err.message));

  await page.getByRole("button").click();
  expect(errors).toHaveLength(0);
});

page.on("console") captura logs do browser. page.on("pageerror") captura excepções JS. Verifique zero erros após acções. Detecta bugs silenciosos que não quebram a UI.

Diálogos (alert/confirm)
page.on("dialog", (dialog) => {
  console.log(dialog.message());
  dialog.accept();       // OK
  // dialog.dismiss();   // Cancel
});

// prompt com valor:
page.on("dialog", (d) => d.accept("resposta"));

page.on("dialog") regista handler para alerts. accept() clica OK. dismiss() clica Cancel. Para prompts, accept(valor) preenche. Sem handler, dialogs são dismiss.

Component testing
// playwright-ct.config.ts
import { defineConfig } from "@playwright/experimental-ct-react";

// teste de componente:
import { test, expect } from "@playwright/experimental-ct-react";
import { Button } from "./Button";

test("botão renderiza", async ({ mount }) => {
  const component = await mount(<Button label="Click" />);
  await expect(component).toContainText("Click");
});

experimental-ct-react testa componentes isolados. mount() renderiza sem app completa. Suporta React, Vue e Svelte. Mais rápido que E2E para testar UI components.

Reporting custom
// config:
reporter: [
  ["html", { open: "never" }],
  ["json", { outputFile: "results.json" }],
  ["junit", { outputFile: "results.xml" }],
  ["list"],
],

reporter aceita múltiplos formatos simultâneos. html gera relatório visual interactivo. json e junit para CI. list mostra progresso no terminal.

CLI e Configuração


12 cards
Correr testes
npx playwright test
npx playwright test tests/login.spec.ts
npx playwright test --project=chromium
npx playwright test -g "faz login"

Comando base corre todos os testes. Especifique ficheiro para correr só um. --project filtra por browser. -g filtra por nome do teste (grep). Combine filtros livremente.

Relatórios
npx playwright show-report
npx playwright show-report --port=9000

// formatos no config:
reporter: [["html"], ["list"], ["json", { outputFile: "r.json" }]]

show-report abre o relatório HTML no browser. Mostra testes, falhas, traces e screenshots. --port muda a porta. O relatório é auto-contido (pode partilhar a pasta).

Filtrar e repetir
npx playwright test --grep "login|registo"
npx playwright test --grep-invert "lento"
npx playwright test --repeat-each=3
npx playwright test --max-failures=5

--grep filtra por regex no nome. --grep-invert exclui. --repeat-each repete cada teste N vezes (detecta flaky). --max-failures para após N falhas.

Modo UI
npx playwright test --ui

--ui abre interface gráfica interactiva. Mostra timeline, DOM snapshots e network. Permite correr testes individuais ou filtrar. Watch mode recorre ao guardar ficheiro. Essencial para desenvolvimento.

Ver trace
npx playwright show-trace trace.zip
npx playwright show-trace test-results/trace.zip

show-trace abre o Trace Viewer. Mostra timeline com screenshots, DOM, rede e consola. Pode navegar passo-a-passo. Essencial para debug de falhas em CI (onde não há browser visível).

Codegen avançado
npx playwright codegen --target=python
npx playwright codegen --device="Pixel 5"
npx playwright codegen --viewport-size=1920,1080
npx playwright codegen --save-storage=auth.json
npx playwright codegen --load-storage=auth.json

--target gera em Python, Java, C#. --device emula mobile. --save-storage grava cookies ao fechar. --load-storage inicia autenticado. Óptimo para prototipar testes.

Modo debug
npx playwright test --debug
npx playwright test login.spec.ts --debug

--debug abre o Playwright Inspector. Browser visível com controlos passo-a-passo. Mostra selectores sugeridos ao hover. Permite executar acções uma a uma. Ideal para criar e debug de testes.

Workers e paralelismo
npx playwright test --workers=4
npx playwright test --workers=50%

// config:
export default defineConfig({
  workers: process.env.CI ? 4 : undefined,
  fullyParallel: true,
});

workers define processos paralelos. fullyParallel: true paraleliza testes dentro de ficheiros. Mais workers = mais rápido (até ao limite CPU). Em CI use 4, em local auto.

CI (GitHub Actions)
# .github/workflows/playwright.yml
- uses: actions/setup-node@v4
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
  if: failure()
  with:
    name: playwright-report
    path: playwright-report/

Instale browsers com --with-deps no CI. Corra testes headless (padrão). Upload do relatório em falha para debug. O init do Playwright gera o workflow automaticamente.

Headed e slowMo
npx playwright test --headed
npx playwright test --headed --slow-mo=500

// config:
use: {
  headless: false,
  launchOptions: { slowMo: 1000 },
}

--headed mostra o browser durante execução. --slow-mo atrasa acções (ms). Útil para observar o que o teste faz. Em CI use headless (padrão) para velocidade.

Timeouts
export default defineConfig({
  timeout: 30000,          // por teste
  expect: { timeout: 5000 }, // por asserção
  use: {
    actionTimeout: 10000,   // por acção
    navigationTimeout: 15000,
  },
});

timeout é o limite total por teste. expect.timeout para asserções. actionTimeout para cliques/fills. navigationTimeout para goto/waitForURL. Ajuste conforme a app.

Extensões e output
npx playwright test --output=test-results
npx playwright test --config=staging.config.ts
npx playwright test --list
npx playwright test --last-failed

--output define pasta de artefactos. --config usa config alternativo (staging). --list lista testes sem correr. --last-failed recorre só os que falharam. Muito útil no dia-a-dia.

Rede e Mocking


12 cards
Interceptar pedidos
await page.route("**/api/**", (route) => {
  console.log(route.request().url());
  route.continue();
});

page.route() intercepta pedidos que correspondem ao pattern. route.continue() deixa passar normalmente. Permite logging, modificação ou bloqueio. Pattern usa glob com **.

Modificar resposta
await page.route("**/api/config", async (route) => {
  const response = await route.fetch();
  const json = await response.json();
  json.featureFlag = true;
  route.fulfill({ json });
});

route.fetch() faz o pedido real e captura a resposta. Modifique o JSON e devolva com fulfill(). Ideal para activar feature flags ou alterar dados sem mock completo.

Mock de erro
await page.route("**/api/dados", (route) => {
  route.fulfill({ status: 500, body: "Erro interno" });
});

await page.goto("/dashboard");
await expect(page.getByText("Erro ao carregar")).toBeVisible();

Simule erros HTTP para testar tratamento. status: 500 simula erro de servidor. Verifique mensagens de erro na UI. Teste resiliência sem quebrar o backend.

Mock de resposta
await page.route("**/api/users", (route) => {
  route.fulfill({
    status: 200,
    contentType: "application/json",
    json: [{ id: 1, nome: "Ana" }],
  });
});

route.fulfill() retorna resposta simulada. json serializa automaticamente. status define o código HTTP. Elimina dependência do backend em testes de UI.

Esperar resposta
const [response] = await Promise.all([
  page.waitForResponse("**/api/login"),
  page.getByRole("button", { name: "Entrar" }).click(),
]);

expect(response.status()).toBe(200);
const body = await response.json();

waitForResponse() espera uma resposta específica. Combine com Promise.all() e a acção que a dispara. Verifique status e body. Essencial para testar flows com API.

HAR replay
// gravar:
await context.routeFromHAR("har/gravação.har", {
  update: true,
});

// reproduzir:
await context.routeFromHAR("har/gravação.har", {
  notFound: "fallback",
});

routeFromHAR() reproduz respostas gravadas em HAR. update: true grava novos pedidos. notFound: "fallback" passa ao servidor se não existir. Mock realista sem manutenção.

Bloquear pedidos
await page.route("**/*.{png,jpg,jpeg,gif}", (route) => route.abort());
await page.route("**/analytics/**", (route) => route.abort());
await page.route("**/*.css", (route) => route.abort());

route.abort() cancela o pedido. Bloquear imagens acelera testes. Bloquear analytics evita ruído. Aceita patterns glob com extensões. Útil para testes focados em funcionalidade.

Esperar pedido
const [request] = await Promise.all([
  page.waitForRequest("**/api/checkout"),
  botaoComprar.click(),
]);

expect(request.method()).toBe("POST");
const postData = request.postDataJSON();
expect(postData.total).toBe(49.99);

waitForRequest() captura o pedido enviado. method() verifica o verbo HTTP. postDataJSON() faz parse do body. Valide que o frontend envia dados correctos.

API testing directo
test("API cria user", async ({ request }) => {
  const response = await request.post("/api/users", {
    data: { nome: "Ana", email: "ana@site.pt" },
  });
  expect(response.ok()).toBeTruthy();
  const user = await response.json();
  expect(user.id).toBeDefined();
});

request fixture faz chamadas HTTP sem browser. data envia JSON no body. Ideal para setup de testes ou testar APIs puras. Mais rápido que testes com UI.

Modificar pedido
await page.route("**/api/**", (route) => {
  route.continue({
    headers: {
      ...route.request().headers(),
      "X-Custom": "teste",
    },
  });
});

route.continue() com opções modifica o pedido. Pode alterar headers, method, postData. Útil para injectar tokens ou simular condições. O pedido segue para o servidor real.

Mock com delay
await page.route("**/api/lento", async (route) => {
  await new Promise((r) => setTimeout(r, 3000));
  route.fulfill({ json: { dados: "ok" } });
});

// testar loading state:
await expect(spinner).toBeVisible();
await expect(spinner).toBeHidden({ timeout: 5000 });

Adicione delay artificial para testar loading states. setTimeout simula latência. Verifique que spinners aparecem e desaparecem. Testa UX sob condições lentas.

Remover routes
const handler = (route) => route.abort();
await page.route("**/analytics/**", handler);

// mais tarde, remover:
await page.unroute("**/analytics/**", handler);

// remover todas:
await page.unrouteAll();

unroute() remove um interceptor específico. unrouteAll() remove todos. Útil quando um mock só é necessário para parte do teste. Evita interferência entre testes.

Autenticação e Estado


12 cards
Login via UI
async function login(page: Page) {
  await page.goto("/login");
  await page.getByLabel("Email").fill("admin@site.pt");
  await page.getByLabel("Password").fill("password");
  await page.getByRole("button", { name: "Entrar" }).click();
  await page.waitForURL("/dashboard");
}

Função helper reutilizável para login. waitForURL() confirma sucesso. Pode ser usada em beforeEach ou fixture. Abordagem simples mas lenta se repetida muitas vezes.

Múltiplos utilizadores
// auth/user.setup.ts → user.json
// auth/admin.setup.ts → admin.json

test.use({ storageState: "auth/admin.json" });
test("acesso admin", async ({ page }) => {
  await page.goto("/admin");
  await expect(page.getByText("Painel")).toBeVisible();
});

Gere um storageState por tipo de utilizador. Cada ficheiro tem cookies de uma role. Testes admin usam admin.json, testes user usam user.json. Limpo e escalável.

Permissões e roles
test.describe("Admin", () => {
  test.use({ storageState: "auth/admin.json" });

  test("acede a /admin", async ({ page }) => {
    await page.goto("/admin");
    await expect(page.getByRole("heading")).toHaveText("Administração");
  });
});

test.describe("User", () => {
  test.use({ storageState: "auth/user.json" });

  test("não acede a /admin", async ({ page }) => {
    await page.goto("/admin");
    await expect(page).toHaveURL("/");
  });
});

Teste permissões com diferentes estados de auth. Admin acede, user é redireccionado. test.use() por grupo define o storageState. Cobertura completa de RBAC.

Guardar estado (storageState)
// global-setup.ts: gravar sessão
const browser = await chromium.launch();
const page = await browser.newPage();
await login(page);
await page.context().storageState({ path: "auth.json" });
await browser.close();

storageState() guarda cookies e localStorage em JSON. Execute uma vez no global setup. O ficheiro auth.json é reutilizado por todos os testes. Evita login repetido.

Login via API (rápido)
// setup: login sem UI
test("autenticar", async ({ request }) => {
  const response = await request.post("/api/login", {
    data: { email: "admin@site.pt", password: "pass" },
  });
  const { token } = await response.json();
  // guardar token em storageState
});

Login via request é muito mais rápido que via UI. Não abre browser. Ideal para gerar tokens no setup. Combine com storageState para injectar cookies.

Tokens e headers
// injectar token em todos os pedidos:
await context.route("**/api/**", (route) => {
  route.continue({
    headers: {
      ...route.request().headers(),
      Authorization: `Bearer ${token}`,
    },
  });
});

Intercepte pedidos API para injectar Authorization. Útil quando o token não está em cookies. route.continue() com headers modificados. Alternativa ao storageState para APIs token-based.

Reutilizar autenticação
// playwright.config.ts:
export default defineConfig({
  use: {
    storageState: "auth.json",
  },
});

// ou por ficheiro:
test.use({ storageState: "auth.json" });

storageState no config carrega cookies/localStorage. Todos os testes começam autenticados. Sem login em cada teste — muito mais rápido. O ficheiro é gerado no global setup.

Logout e sessão inválida
test("logout redirecciona", async ({ page }) => {
  await page.goto("/dashboard");
  await page.getByRole("button", { name: "Sair" }).click();
  await expect(page).toHaveURL("/login");
});

test("sessão expirada", async ({ page }) => {
  await context.clearCookies();
  await page.goto("/dashboard");
  await expect(page).toHaveURL("/login");
});

clearCookies() simula sessão expirada. Verifique redirects para login. Teste que áreas protegidas não são acessíveis. Essencial para segurança da aplicação.

2FA e verificação
test("login com 2FA", async ({ page }) => {
  await page.goto("/login");
  await page.getByLabel("Email").fill("user@site.pt");
  await page.getByLabel("Password").fill("pass");
  await page.getByRole("button").click();

  await page.getByLabel("Código").fill("123456");
  await page.getByRole("button", { name: "Verificar" }).click();
  await expect(page).toHaveURL("/dashboard");
});

Teste o flow completo de two-factor. Preencha código TOTP (mock ou real). Verifique redirect após verificação. Em CI, use códigos fixos ou API para gerar TOTP.

Setup project (dependências)
export default defineConfig({
  projects: [
    { name: "setup", testMatch: /.*\.setup\.ts/ },
    {
      name: "chromium",
      use: {
        ...devices["Desktop Chrome"],
        storageState: "auth.json",
      },
      dependencies: ["setup"],
    },
  ],
});

dependencies garante que setup corre antes. O project setup gera auth.json. Os outros projects usam o estado guardado. Padrão oficial do Playwright para auth.

OAuth e SSO
// mock do provider OAuth:
await page.route("**/oauth/authorize**", (route) => {
  const url = new URL(route.request().url());
  const redirect = url.searchParams.get("redirect_uri");
  route.fulfill({
    status: 302,
    headers: { Location: `${redirect}?code=test-code` },
  });
});

Mock do redirect OAuth evita provider real. Intercepte /oauth/authorize e devolva redirect com code. O flow continua como se fosse real. Testa integração sem dependências externas.

Isolamento de sessão
// cada teste tem contexto limpo por padrão:
test("teste A", async ({ page }) => {
  // cookies vazios, sem storage
});

// partilhar contexto entre testes (cuidado):
test.describe("sequência", () => {
  test.describe.configure({ mode: "serial" });
  let context: BrowserContext;

  test.beforeAll(async ({ browser }) => {
    context = await browser.newContext();
  });
});

Por padrão cada teste é isolado (context limpo). Para sequências com estado partilhado use serial + context manual. Prefira isolamento — evita testes flaky e acoplamento.