Esquecer o --with-deps ao instalar o Playwright no GitHub Actions é a razão mais comum de testes passarem localmente e falharem no CI. Bibliotecas de sistema como libgtk e libnss não estão instaladas em runners Ubuntu frescos. O segundo erro mais comum é omitir if: always() no step de upload de artefato. Sem ele, você perde o relatório HTML exatamente quando os testes falham e mais precisa dele.

Por que GitHub Actions

O GitHub Actions é a plataforma de CI dominante para projetos novos. A pesquisa JetBrains State of CI/CD 2025 mostrou que 62% dos desenvolvedores usam para projetos pessoais e 41% nas organizações, ambos os números maiores do que qualquer outra ferramenta.

As razões práticas são diretas: é gratuito para repositórios públicos, gratuito para 2.000 minutos por mês em repositórios privados e está integrado ao GitHub. Não há servidor separado para provisionar, conta de terceiros para conectar ou webhook para configurar. Você faz push de um arquivo YAML e os testes começam a rodar.

Para o Playwright especificamente, o GitHub Actions funciona bem porque os runners Ubuntu fornecidos têm tudo que o Playwright precisa. Dependências de navegador, fontes e bibliotecas de sistema estão disponíveis via --with-deps. O time oficial do Playwright testa nesses runners. Você está no caminho suportado.

O workflow mínimo funcional

Crie o arquivo .github/workflows/playwright.yml na raiz do projeto. É onde o GitHub procura definições de workflow.

mkdir -p .github/workflows

Aqui está o workflow mínimo completo:

# .github/workflows/playwright.yml
name: Playwright Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 60

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        run: npx playwright test

      - name: Upload test report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14

Faça commit desse arquivo, push, e abra a aba Actions no repositório GitHub. Você vai ver o workflow aparecer e rodar em segundos.

Alguns detalhes que valem entender. npm ci em vez de npm install instala exatamente o que está no package-lock.json sem modificá-lo. Esse é o comando certo para ambientes de CI porque dá builds reproduzíveis. npx playwright install --with-deps instala os navegadores junto com as dependências de sistema (libgtk, libnss, etc.) que não estão presentes em um runner Ubuntu novo. Esquecer o --with-deps é a causa mais comum do Playwright falhar no CI quando funciona localmente.

O if: always() no step de upload é essencial. Sem ele, o artefato só sobe quando o workflow tem sucesso, o que significa que você perde o relatório exatamente quando os testes falham e você mais precisa dele.

O timeout-minutes: 60 no job é uma proteção. Um teste travado pode consumir toda a cota de minutos mensal antes de você perceber.

Armazenando e baixando relatórios de testes

O step upload-artifact no workflow acima salva o diretório playwright-report/ inteiro como um zip para download. Após uma execução terminar, vá para a página do run, depois Summary, depois Artifacts, depois playwright-report. Baixe, descompacte e abra index.html no navegador.

O relatório HTML mostra quais testes passaram, quais falharam, screenshots de falhas, traces e vídeo se você tiver gravação ativada. É assim que você investiga falhas sem acessar o runner de CI diretamente.

Se você está gerando múltiplos relatórios (digamos, um por navegador em um matrix run), dê um nome único a cada artefato:

      - name: Upload test report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ matrix.browser }}
          path: playwright-report/
          retention-days: 14

retention-days: 14 mantém os relatórios por duas semanas, suficiente para a maioria dos fluxos de debugging sem acumular custos desnecessários de armazenamento. O GitHub permite até 90 dias.

Variáveis de ambiente e segredos

Nunca coloque credenciais ou URLs específicas de ambiente no arquivo YAML do workflow. Os GitHub Secrets são o lugar certo para qualquer coisa sensível.

Vá para o repositório, depois Settings, depois Secrets and variables, depois Actions, depois New repository secret. Adicione os segredos que os testes precisam: TEST_USER, TEST_PASSWORD e quaisquer chaves de API.

Referencie-os no workflow em env: no step de testes:

      - name: Run Playwright tests
        run: npx playwright test
        env:
          BASE_URL: https://staging.example.com
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

Nos testes, leia-os com process.env:

const baseURL = process.env.BASE_URL ?? 'http://localhost:3000';
const user = process.env.TEST_USER ?? '';
const password = process.env.TEST_PASSWORD ?? '';

O GitHub mascara valores de segredos nos logs. Se um segredo aparecer no output, é substituído por *. Valores não sensíveis como BASE_URL também podem ser armazenados como variáveis de repositório (não segredos), que são visíveis na UI. Use variáveis para URLs e feature flags, segredos para credenciais.

Segredos não estão disponíveis para workflows disparados por pull requests de forks. Isso é uma feature de segurança: um PR de fork poderia ler seus segredos. Se você precisa testar PRs de forks com segredos, veja o evento pull_request_target, mas entenda as implicações de segurança antes de usá-lo.

Cache para execuções mais rápidas

Um run de workflow novo instala módulos Node e navegadores do Playwright do zero toda vez. Em um runner sem cache, são 2 a 3 minutos antes de um único teste rodar. O cache reduz isso significativamente.

A action setup-node com cache: 'npm' já cuida do cache de node_modules automaticamente. Para os navegadores do Playwright, adicione um step de cache dedicado:

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

      - name: Install Playwright browsers
        run: npx playwright install --with-deps
        if: steps.playwright-cache.outputs.cache-hit != 'true'

A chave do cache inclui um hash do package-lock.json. Quando você atualiza versões do Playwright, o lock file muda, o hash muda, o cache não é encontrado e os navegadores reinstalam. Quando nada muda, o cache é encontrado e você pula a instalação completamente.

Essa otimização importa mais em suites grandes. Uma suite de 200 testes com cache ativado pode ir de 8 minutos para 4 minutos só pulando downloads repetidos de navegadores.

Rodando apenas em branches específicas

A seção on: controla quando o workflow roda. O exemplo mínimo já filtra para main e develop. Você pode ser mais específico:

on:
  push:
    branches:
      - main
      - develop
      - 'release/**'
  pull_request:
    branches:
      - main
      - develop
  workflow_dispatch:

workflow_dispatch: adiciona um botão "Run workflow" na aba Actions, permitindo dispará-lo manualmente. Útil para rodar testes contra uma branch específica sob demanda sem fazer push de um commit.

Você também pode rodar testes em agendamento. Runs noturnos de smoke tests pegam problemas que aparecem apenas em certas janelas de tempo ou após acúmulo de dados:

on:
  schedule:
    - cron: '0 3 * * *'   # 3h UTC toda noite
  push:
    branches: [main]
  pull_request:
    branches: [main]

Dentro de um job, você pode adicionar condições de branch em steps específicos usando if::

      - name: Run full suite
        run: npx playwright test
        if: github.ref == 'refs/heads/main'

      - name: Run smoke tests only
        run: npx playwright test --grep @smoke
        if: github.event_name == 'pull_request'

Esse padrão dá feedback rápido para PRs a partir de uma suite de smoke enquanto os runs da branch main recebem a execução completa.

Matrix strategy para testes cross-browser

O Playwright suporta Chromium, Firefox e WebKit. Rodar os três em cada PR é caro em tempo e minutos, mas você ainda quer cobertura cross-browser. A matrix strategy roda jobs paralelos com configurações diferentes:

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 60

    strategy:
      fail-fast: false
      matrix:
        browser: [chromium, firefox, webkit]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ matrix.browser }}-${{ hashFiles('package-lock.json') }}

      - name: Install Playwright browsers
        run: npx playwright install ${{ matrix.browser }} --with-deps
        if: steps.playwright-cache.outputs.cache-hit != 'true'

      - name: Run tests
        run: npx playwright test --project=${{ matrix.browser }}
        env:
          BASE_URL: ${{ vars.BASE_URL }}
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

      - name: Upload report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-${{ matrix.browser }}
          path: playwright-report/
          retention-days: 14

fail-fast: false significa que se o Firefox falhar, os jobs de Chromium e WebKit continuam rodando. Com fail-fast: true (o padrão), qualquer falha de job na matrix cancela o restante, geralmente não é o que você quer ao debugar problemas cross-browser. npx playwright install ${{ matrix.browser }} --with-deps instala apenas o navegador para aquele job, não os três. É mais rápido do que instalar tudo em cada job.
Rode a matrix apenas em pushes para main, não em cada PR. Adicione if: github.ref == 'refs/heads/main' ao job ou use o filtro de branch no trigger on.push. Seus PRs ficam rápidos; sua branch main recebe cobertura completa.

Sharding: dividindo testes entre jobs

O sharding divide a suite de testes entre vários jobs paralelos. Onde a matrix strategy roda configurações diferentes, o sharding roda os mesmos testes mais rápido ao dividi-los:

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 30

    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install chromium --with-deps

      - name: Run shard
        run: npx playwright test --shard=${{ matrix.shard }}/4
        env:
          BASE_URL: ${{ vars.BASE_URL }}
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

      - name: Upload shard report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report-shard-${{ matrix.shard }}
          path: playwright-report/
          retention-days: 14

O Playwright distribui os testes igualmente entre os shards com base em dados de tempo de execuções anteriores, se você fornecer um blob report. Sem isso, usa ordenação de arquivo. Com quatro shards, uma suite de 20 minutos roda em aproximadamente 5 minutos.

Você pode combinar sharding com a matrix strategy. Um padrão comum é quatro shards por navegador para suites grandes:

    strategy:
      matrix:
        browser: [chromium, firefox]
        shard: [1, 2, 3, 4]

Isso cria 8 jobs paralelos. Atenção: cada job usa minutos de runner. 8 jobs rodando por 5 minutos cada consome 40 minutos da sua cota, o mesmo que rodar tudo sequencialmente. A vantagem é o tempo de relógio de parede, não o consumo de cota.

Para juntar relatórios de shards em um único relatório HTML, use o blob reporter. Defina reporter: 'blob' no config do Playwright para runs de CI, e adicione um job de merge:

  merge-reports:
    if: always()
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - name: Download blob reports
        uses: actions/download-artifact@v4
        with:
          path: all-blob-reports
          pattern: blob-report-*
          merge-multiple: true
      - name: Merge into HTML report
        run: npx playwright merge-reports --reporter html ./all-blob-reports
      - name: Upload merged report
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14

Postando resultados em comentários de PR

Ter os resultados dos testes aparecendo diretamente no pull request torna as falhas muito mais visíveis. Isso exige uma action separada após o run de testes terminar.

A abordagem mais simples usa a action dawidd6/action-junit-report, que lê output XML do JUnit e posta um comentário. Primeiro, adicione o reporter JUnit ao config do Playwright:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ['html'],
    ['junit', { outputFile: 'results.xml' }],
  ],
});

Depois adicione um step de relatório ao workflow:

      - name: Run Playwright tests
        run: npx playwright test
        env:
          BASE_URL: ${{ vars.BASE_URL }}
          TEST_USER: ${{ secrets.TEST_USER }}
          TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}

      - name: Post test results to PR
        uses: dawidd6/action-junit-report@v4
        if: always()
        with:
          report_paths: results.xml
          github_token: ${{ secrets.GITHUB_TOKEN }}
          check_name: Playwright Test Results

GITHUB_TOKEN está disponível automaticamente em todo workflow. Sem necessidade de configuração manual de segredo.

O comentário mostra contagens de passou/falhou, nomes dos testes e mensagens de falha diretamente na conversa do PR. Os revisores não precisam navegar para a aba Actions para saber se os testes passaram.

FAQ

Testes passam localmente mas falham no GitHub Actions. Por onde começo?

Verifique nessa ordem: variáveis de ambiente, BASE_URL e timing. Um segredo não definido no Actions será undefined, não um erro. BASE_URL hardcoded para localhost não vai funcionar no CI. Runners de CI são mais lentos que máquinas de dev. Aumente os timeouts no playwright.config.ts ou adicione retries: 1 especificamente para CI usando process.env.CI ? 1 : 0.

Como vejo o relatório HTML de um run que falhou?

Vá para o run de workflow que falhou, depois Summary, depois role até Artifacts, depois baixe playwright-report. Extraia o zip e abra index.html no navegador. O relatório inclui screenshots e traces para testes que falharam.

Preciso commitar os navegadores do Playwright no repositório?

Não. O workflow os instala em tempo de execução via npx playwright install. Com cache configurado, runs subsequentes pulam o download. Commitar binários de navegador sobrecarregaria o repositório com centenas de megabytes.

Meu workflow está lento. O que tem mais impacto?

O cache é a primeira otimização: salvar os navegadores do Playwright corta 1 a 2 minutos por run. O sharding é a segunda: dividir uma suite de 15 minutos em 3 shards traz o tempo para 5 minutos. Rodar apenas Chromium em PRs e matrix completa na main é a terceira: triplica a velocidade de cada run de PR.

Posso usar a imagem Docker do Playwright em vez de instalar os navegadores?

Sim. Defina container: mcr.microsoft.com/playwright:v1.52.0-jammy no job e pule o step de instalação. O tradeoff: containers Docker iniciam mais devagar do que runners Ubuntu bare, e você fica preso a uma versão específica do Playwright na tag da imagem. É uma escolha razoável para times que querem evitar o step de instalação completamente.

Como rodo apenas testes relacionados a arquivos alterados?

O Playwright não suporta isso nativamente. Alguns times usam filtros baseados em path no workflow, disparando o job de testes apenas quando arquivos em certos diretórios mudam. Adicione paths: em on.pull_request para limitar quando o workflow roda. Seleção completa de testes baseada em mudanças de código exige ferramentas externas e raramente vale a complexidade até a suite ultrapassar 10 minutos.

→ Veja também: CI/CD para QA: GitHub Actions, Jenkins e GitLab Comparados | Depurando Testes Instáveis: Um Guia Prático | Execução Paralela no Playwright: Workers, Shards e Sharding para Velocidade | Relatórios de Testes Playwright: HTML Integrado vs Allure | GitLab CI + Playwright: Guia Completo de Configuração