← Voltar ao Blog

TDD com Playwright MCP e CI: pipeline Claude Code em 47 min

O ciclo Red-Green-Refactor sempre foi vendido como disciplina, quase moral. Você escreve o teste, vê o vermelho, faz o verde, refatora. Em papel bonito. Na prática, meu loop de feedback ficava em 12 minutos por causa do CI, e eu passava metade da tarde alternando entre a aba do editor e a aba do Actions esperando o “amarelinho ficar verde”.

Este post é o desenho de um pipeline em que o Claude Code escreve o teste Playwright via MCP e o CI roda em headless com @playwright/mcp 0.0.78 (Microsoft, Apache-2.0). Os arquivos de configuração aqui são um exemplo montado para o texto, e os tempos e custos são estimativa, não medição de produção. O que eu rodo de verdade é a parte de cima, a separação de fases em subagentes, e é dela que vem o resto.

Se você já leu meu texto sobre as 24 horas em que meu agente de IA gerou 8 incidentes de segurança, pode pular direto para a seção “Permissões no runner”. O resto do artigo assume que você ainda não passou por essa dor.

Por que TDD com Claude Code, e não Claude Code sozinho

O erro que eu já cometi, e que motiva o desenho todo, é deixar o Claude Code escrever teste e implementação na mesma sessão. O CI fica verde e a sensação é boa. O problema aparece depois: o teste sabe demais sobre a implementação.

Existe um termo para isso: contaminação de contexto. Quando o mesmo agente que implementa também escreve o teste, o teste não verifica correção. Ele carimba a implementação. É o equivalente digital de corrigir a própria prova de casa. Você acerta 100 %, mas não aprende nada.

A solução que virou padrão para mim é separar as fases em subagentes independentes. Um subagente escreve o teste sem enxergar código de produção. Outro implementa sem ver o raciocínio do primeiro. O terceiro refatora. Cada um tem contexto isolado. O teste não pode trapacear porque literalmente não viu o gabarito.

Comparação: mesma sessão vs subagentes separados no Red-Green-Refactor

Setup: Playwright MCP em 3 comandos

Playwright MCP é um servidor MCP mantido pela Microsoft que expõe o navegador via árvore de acessibilidade em vez de screenshots. Isso muda tudo em CI headless: você não precisa de OCR nem de modelo visão para o agente entender a tela.

Instalação:

npx @playwright/mcp@latest --headless

Configuração no .mcp.json da raiz do projeto:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["@playwright/mcp@latest", "--headless"]
    }
  }
}

Requer Node.js 18+. Sem plano pago, sem métrica de uso, sem rate limit. Roda 100 % na sua máquina (ou no runner do GitHub Actions, no nosso caso). Ao rodar uma tool, o servidor devolve um snapshot estruturado da página: elementos, roles, texto. É o mesmo mecanismo que o Claude Code usa para clicar em botões sem “ver” a tela.

O passo que decide a spec: a matriz de cobertura

Antes de acionar qualquer subagente eu monto uma matriz de cobertura junto com os requisitos. Ela lista o que precisa ser verificado, por perspectiva, e é ela que vai para o tdd-test-writer no lugar de uma frase solta.

AlvoCaminho felizValores limiteCasos de erroDependência de estadoEfeitos colaterais
Botão de export CSVexporta com filtro de data ativointervalo vazio, um único diadownload falha, sessão expiradafiltro ligado e desligadopadrão do nome do arquivo

O nome do campo, o nome do botão e a convenção do nome do arquivo entram aqui, escritos por mim. É neste ponto que a regra de negócio fica fixada, e por isso essa etapa não desce para subagente nenhum: ela é decisão, não execução.

Pular esse passo é o que faz o fluxo desandar. Se o subagente de teste recebe apenas “adiciona um botão de export CSV”, ele preenche as lacunas por conta própria, e o subagente de implementação persegue essa invenção só para o CI ficar verde. O isolamento de contexto impede que o teste seja derivado da implementação. Ele não resolve ambiguidade de especificação, e não é para isso que ele serve.

O agente que escreve o teste (subagente 1)

O subagente tdd-test-writer recebe os requisitos e a matriz de cobertura. Nunca vê src/. O prompt do agente:

# .claude/agents/tdd-test-writer.md

Você é um escritor de testes. Sua ÚNICA função é escrever testes que falham.

Regras:
- Você NÃO PODE ler nem referenciar arquivos de implementação (src/**)
- Escreva testes baseados APENAS na descrição da feature
- Cubra caminho feliz e casos limite (input vazio, mobile 375px, etc.)
- Use Playwright MCP para inspecionar a UI real quando existir
- Rode os testes e confirme que FALHAM antes de terminar

Recebendo a matriz, o agente abre o servidor local via mcp__playwright__browser_navigate, confirma que o botão ainda não existe, e escreve o teste a partir do que a matriz fixou, e não do que ele imagina que a implementação vai ser.

O teste sai assim:

test('exporta CSV com filtro de data ativo', async ({ page }) => {
  await page.goto('http://localhost:3000/dashboard');
  await page.getByLabel('De').fill('2026-07-01');
  await page.getByRole('button', { name: 'Export CSV' }).click();
  const download = await page.waitForEvent('download');
  expect(download.suggestedFilename()).toMatch(/^dashboard-2026-07-01/);
});

Repare no getByRole('button', { name: 'Export CSV' }). Isso é Playwright MCP guiando o agente para seletores estáveis, em vez daquele .btn-primary.mt-4 frágil que quebra sempre que alguém troca de Tailwind.

O CI que fecha o loop em 47 segundos

Aqui está o workflow inteiro (removi comentários para caber):

# .github/workflows/tdd-loop.yml
name: TDD Loop
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  tdd:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    permissions:
      contents: read
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
      - run: npm ci
      - run: npx playwright install chromium --with-deps
      - run: npm run dev &
      - run: npx wait-on http://localhost:3000

      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          claude_args: |
            --mcp-config '{
              "mcpServers": {
                "playwright": {
                  "command": "npx",
                  "args": ["@playwright/mcp@latest", "--headless"]
                }
              }
            }'
            --model claude-sonnet-4-6
            --allowed-tools "Read,Edit,Bash(npm run test),mcp__playwright__*"
          prompt: |
            Rode o teste falhando em tests/. Se um subagente
            tdd-test-writer já adicionou tests/*.spec.ts nesta PR,
            invoque o subagente tdd-implementer para escrever a
            implementação mínima em src/ que faça o teste passar.
            NÃO modifique arquivos em tests/.

Os pontos de atenção que quebraram na minha primeira tentativa:

  • --allowed-tools restringe o subagente ao mínimo. Sem isso ele tenta rodar git push no meio da PR e o Actions bloqueia (bem que devia).
  • --headless no MCP é obrigatório. Sem ele o Chromium tenta abrir X server no runner e trava por 30 s antes de morrer.
  • timeout-minutes: 5 é um teto psicológico. Se o loop passar de 5 minutos, o desenho está errado, não a máquina.

A estimativa de tempo por etapa, para dimensionar o desenho (não é medição de produção):

EtapaAntesDepois
Escrever teste (local)3 min12 s (subagente)
Push + CI cold start4 min1 min (cache)
Rodar teste no CI3 min25 s (headless + a11y)
Ler resultado + iterar2 min10 s (comment no PR)
Total12 min47 s

Permissões no runner: o campo minado

Quando o Claude Code roda no CI, ele herda o GITHUB_TOKEN. Se você deixar permissions: write-all (o default de muitos templates), o agente pode fechar issues, deletar branches, e criar releases.

O mínimo viável para essa pipeline:

permissions:
  contents: read      # ler o código
  pull-requests: write # comentar na PR

Nada de issues: write, nada de actions: write, nada de packages: write. Se o agente tentar algo fora desse escopo, o GitHub bloqueia e você vê no log. É a diferença entre “meu agente é bem comportado” e “meu agente é estruturalmente incapaz de fazer besteira”.

O detalhe cruel: --allowed-tools restringe o Claude Code, mas não impede o subagente que ele invoca via MCP de fazer qualquer coisa que o token permita. Por isso a camada de permissions: no workflow é mais importante do que a whitelist de tools.

Quanto custa isso em BRL

Estimando o consumo de uma execução da pipeline (test-writer + implementer + refactorer), na ordem de:

  • 45 000 tokens de input (contexto do repositório + resposta do MCP)
  • 4 000 tokens de output (código + explicação)

Com Claude Sonnet 4.6 a US$ 3 / M input e US$ 15 / M output:

input:  45 000 × $3  / 1 000 000 = $0.135
output: 4 000  × $15 / 1 000 000 = $0.060
total: $0.195 por execução ≈ R$ 1,07 (câmbio 5,5)

Com Sonnet 5 (lançado em 30 de junho de 2026 a US$ 2 / US$ 10 até 31 de agosto), o mesmo loop cai para US$ 0.13 ≈ R$ 0,72. Um dev que roda 40 PRs por dia gasta R$ 40 / dia, ou seja, menos do que uma dose de café especial. E o CI para uma dose de café.

Se você comparar com um freelancer QA júnior a R$ 60 / hora rodando teste manual, o breakeven é uma execução por hora.

Onde eu ainda tropeço

Duas coisas que ainda me incomodam nessa arquitetura:

1. Debug de teste flaky é pior. Quando o teste passa localmente e falha no CI, o subagente tdd-implementer não tem contexto do que o tdd-test-writer “queria dizer”. Ele fica adivinhando. Solução parcial: um README.md em tests/ que descreve invariantes de negócio (nada de detalhes técnicos), e que ambos os subagentes leem.

2. Playwright MCP em CI paralelo compete por porta. Se o workflow roda 3 shards, os 3 tentam abrir Chromium na 9222. Solução feia mas funcional: PLAYWRIGHT_MCP_PORT=$((9222 + RANDOM % 100)).

O que fica

TDD não é sobre disciplina moral. É sobre isolamento de contexto. Quando o agente que escreve o teste não vê o gabarito, o teste vira uma spec real. E quando a spec é executável no CI em 47 segundos, ela deixa de ser “aquela coisa que a gente devia fazer” e vira o loop de feedback padrão.

Pela estimativa acima, o custo por PR fica na casa de R$ 1, abaixo do que vale o tempo que eu passaria olhando o Actions. Quem for montar isso, meça no próprio repositório antes de tratar o número como seu.

Se você já se queimou com agente sem permissions: explícito, o outro texto que eu recomendo é as 24 horas em que meu agente gerou 8 incidentes de segurança — os padrões de escape de sandbox que aparecem lá são exatamente os que essa pipeline previne.


ken imoto · WebRTC & Voice AI engineer · kenimoto.dev · TabNews

Practical Claude Code Livro relacionado Practical Claude Code Tutorial de Claude Code | padrões de CLAUDE.md, design de Plan Mode e workflows de equipe Ver a página do livro →