Os binários de navegador do Playwright dependem de cerca de duas dezenas de bibliotecas de sistema. Incompatibilidades entre as versões dessas bibliotecas no seu laptop e no CI causam falhas que não têm nada a ver com os seus testes. A imagem oficial do Playwright em mcr.microsoft.com/playwright empacota os três navegadores e todas as dependências em uma única imagem que roda de forma idêntica em qualquer lugar. A tag da imagem deve corresponder à versão do @playwright/test no package.json.

Por que o Docker importa para QA

Suites de testes de navegador são excepcionalmente sensíveis ao ambiente. O Playwright instala binários de Chromium, Firefox e WebKit que dependem de bibliotecas de sistema específicas: libglib, libnss, libatk, e mais umas duas dezenas. No seu laptop essas bibliotecas provavelmente estão na versão certa porque você instalou o Playwright recentemente. Na máquina de um colega de 2022, pode não estar. Em um runner de CI provisionado seis meses atrás com uma imagem Ubuntu diferente, a incompatibilidade vira uma falha de build às 2 da manhã.

A correção tradicional é uma página de wiki chamada "Dev Setup" que fica desatualizada no momento em que alguém a escreve. A correção do Docker é um arquivo no repositório chamado Dockerfile que descreve o ambiente exato que os testes precisam. Qualquer pessoa que rodar docker build obtém o mesmo ambiente, independente do sistema operacional ou do que instalou na semana passada.

Para QA especificamente, o Docker traz três benefícios concretos. Primeiro, paridade de ambiente: os testes rodam dentro do mesmo container localmente e no CI, então um teste passando localmente significa alguma coisa. Segundo, sem overhead de setup: um novo membro do time roda toda a suite de testes com dois comandos sem instalar Node, Playwright ou dependências de navegador. Terceiro, isolamento: as execuções de teste não interferem entre si nem com o que está instalado na máquina host.

O Docker não substitui entender as falhas dos seus testes. Ele remove a categoria de falhas causadas por diferenças de ambiente. As falhas restantes são bugs reais, e é exatamente isso que você quer.

Conceitos básicos de Docker para testers

Se você nunca trabalhou com Docker, o modelo mental é direto. Uma imagem é um snapshot de um sistema de arquivos: um sistema operacional, pacotes instalados, seu código e variáveis de ambiente, tudo congelado em um momento no tempo. Um container é uma instância em execução de uma imagem: pense na imagem como uma classe e no container como um objeto.

Você constrói uma imagem a partir de um Dockerfile, um arquivo de texto que lista os passos para construí-la. Você roda um container a partir de uma imagem com docker run. Quando o container termina, ele para. Nada que ele fez muda a imagem.

# Baixar uma imagem existente de um registry
docker pull node:20-slim

# Rodar um container a partir dessa imagem e executar um comando
docker run node:20-slim node --version

# Construir uma imagem a partir de um Dockerfile no diretório atual
docker build -t my-playwright-tests .

# Rodar um container a partir da imagem que você acabou de construir
docker run my-playwright-tests

O conceito-chave para fluxos de teste é que containers são efêmeros por padrão. Arquivos criados dentro de um container somem quando ele para, incluindo seus relatórios de teste. Isso se resolve com volume mounts, que vamos cobrir na seção de execução de testes.

Imagens são compostas de camadas. Cada instrução em um Dockerfile cria uma nova camada, e o Docker faz cache das camadas que não mudaram. É isso que torna os rebuilds rápidos. Se você muda o código de teste mas não as dependências, o Docker reutiliza a camada em cache e reconstrói apenas o que mudou.

A imagem oficial do Playwright para Docker

O time do Playwright publica e mantém uma imagem Docker oficial em mcr.microsoft.com/playwright. Essa imagem vem com tudo que os testes de navegador precisam: Node.js, o próprio pacote Playwright, e os três binários de navegador (Chromium, Firefox, WebKit) com as dependências de bibliotecas de sistema pré-instaladas.

# Baixar a imagem correspondente à sua versão do Playwright
docker pull mcr.microsoft.com/playwright:v1.52.0-jammy

# Ver o que tem dentro
docker run --rm mcr.microsoft.com/playwright:v1.52.0-jammy node --version
docker run --rm mcr.microsoft.com/playwright:v1.52.0-jammy npx playwright --version

O formato da tag é v{versão-playwright}-{codinome-ubuntu}. jammy é Ubuntu 22.04, a opção mais estável e amplamente usada. Uma variante noble para Ubuntu 24.04 também está disponível.

A imagem é grande (cerca de 1,8GB) porque contém três instalações completas de navegador. Esse tamanho é o preço de ter tudo pré-instalado sem precisar rodar playwright install --with-deps na inicialização do container. Para ambientes de CI onde a imagem é baixada repetidamente, é um bom tradeoff. O cache de camadas do Docker significa que você só baixa uma vez por runner, a menos que a tag mude.

A regra crítica: a tag da imagem deve corresponder à sua versão do @playwright/test no package.json. Se você usa v1.52.0-jammy mas o projeto tem "@playwright/test": "1.50.0", as incompatibilidades de API vão causar falhas confusas. Fixe as duas.

Escrevendo um Dockerfile para o seu projeto Playwright

Comece com a imagem oficial do Playwright como base. Aqui está um Dockerfile completo, pronto para produção:

# Use a imagem oficial do Playwright — fixe a versão para corresponder ao package.json
FROM mcr.microsoft.com/playwright:v1.52.0-jammy

# Defina o diretório de trabalho dentro do container
WORKDIR /app

# Copie os arquivos de pacotes primeiro para melhor cache de camadas
# Esta camada só é reconstruída quando as dependências mudam
COPY package.json package-lock.json ./

# Instale as dependências do projeto (os navegadores já estão na imagem base)
RUN npm ci

# Copie o restante do projeto
COPY . .

# Comando padrão: rodar todos os testes
CMD ["npx", "playwright", "test"]

A ordem das instruções COPY e RUN importa para o cache. Copiar package.json e package-lock.json antes do código-fonte garante que a camada do npm ci fica em cache. Ela é reutilizada sempre que você muda arquivos de teste sem tocar nas dependências. Se você copiasse tudo primeiro e depois rodasse npm ci, qualquer mudança em qualquer arquivo invalidaria o cache de dependências.

Note que não há passo npx playwright install. A imagem base já tem os navegadores. Isso torna o build significativamente mais rápido do que um setup que começa de uma imagem Node simples.

Adicione um arquivo .dockerignore junto do Dockerfile para excluir arquivos que não pertencem à imagem:

# .dockerignore
node_modules
playwright-report
test-results
.git
.github
*.md

Excluir node_modules é especialmente importante. Sem isso, o Docker copiaria seu node_modules local para a imagem antes de rodar o npm ci. Isso desperdiça tempo e pode introduzir binários específicos de plataforma que não funcionam dentro do container Linux.

Construa a imagem:

docker build -t playwright-tests:local .

A flag -t nomeia a imagem. playwright-tests:local é uma convenção para distinguir imagens construídas localmente de imagens de registry.

Rodando testes em um container

Com a imagem construída, rodar os testes é um comando:

docker run --rm playwright-tests:local

--rm remove automaticamente o container quando termina. Sem ele, containers parados se acumulam e consomem espaço em disco.

O problema é que os relatórios de teste são escritos dentro do container e somem quando ele para. Resolva com um volume mount usando -v:

docker run --rm \
  -v $(pwd)/playwright-report:/app/playwright-report \
  -v $(pwd)/test-results:/app/test-results \
  playwright-tests:local

A flag -v mapeia um diretório do host para um diretório do container. Quando o Playwright escreve o relatório HTML em /app/playwright-report dentro do container, ele na verdade escreve em $(pwd)/playwright-report na sua máquina host. Após o container parar, o relatório está lá, pronto para abrir.

No Windows, substitua $(pwd) por %cd% no Command Prompt ou ${PWD} no PowerShell:

docker run --rm `
  -v ${PWD}/playwright-report:/app/playwright-report `
  -v ${PWD}/test-results:/app/test-results `
  playwright-tests:local

Para rodar um subconjunto de testes, sobrescreva o CMD padrão adicionando argumentos:

# Rodar apenas testes com uma tag
docker run --rm playwright-tests:local npx playwright test --grep @smoke

# Rodar apenas Chromium
docker run --rm playwright-tests:local npx playwright test --project=chromium

# Rodar um arquivo específico
docker run --rm playwright-tests:local npx playwright test tests/login.spec.ts

Passe variáveis de ambiente com -e:

docker run --rm \
  -e BASE_URL=https://staging.example.com \
  -e TEST_USER=testuser \
  -e TEST_PASSWORD=secret \
  -v $(pwd)/playwright-report:/app/playwright-report \
  playwright-tests:local

Adicione um script no package.json para o comando docker run completo para que o time não precise lembrar das flags de volume mount. npm run test:docker é mais fácil de documentar e mais difícil de digitar errado do que o comando completo.

docker-compose para execuções de teste locais

Quando os testes rodam contra uma aplicação, você precisa da aplicação e do container de testes rodando ao mesmo tempo com acesso de rede entre eles. O docker-compose gerencia setups de múltiplos containers com um único arquivo.

Aqui está um docker-compose.yml para um setup típico onde os testes Playwright rodam contra uma aplicação web rodando localmente:

# docker-compose.yml
version: '3.8'

services:
  # Sua aplicação sendo testada
  app:
    image: your-app:latest
    ports:
      - '3000:3000'
    environment:
      NODE_ENV: test
      DATABASE_URL: postgres://user:pass@db:5432/testdb
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
      interval: 5s
      timeout: 5s
      retries: 10

  # Banco de dados (se a aplicação precisar)
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: testdb
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U user -d testdb']
      interval: 3s
      timeout: 3s
      retries: 15

  # Container de testes Playwright
  tests:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      BASE_URL: http://app:3000
    depends_on:
      app:
        condition: service_healthy
    volumes:
      - ./playwright-report:/app/playwright-report
      - ./test-results:/app/test-results
    # Não iniciar os testes automaticamente — rodar sob demanda
    profiles:
      - testing

O profiles: [testing] no serviço de testes significa que ele não vai iniciar quando você rodar docker-compose up sozinho (que iniciaria a aplicação e o banco). Para rodar os testes:

# Iniciar a aplicação e o banco
docker-compose up -d app db

# Rodar os testes
docker-compose run --rm tests

# Ou iniciar tudo e rodar os testes em um comando
docker-compose --profile testing run --rm tests

# Encerrar tudo
docker-compose down

Os serviços se comunicam usando seus nomes de serviço como hostnames. O container de testes define BASE_URL: http://app:3000, e o DNS interno do Docker resolve app para o endereço IP do container da aplicação. Seu playwright.config.tsprocess.env.BASE_URL, então localmente ele acessa localhost:3000 e no container acessa http://app:3000.

O depends_on com condition: service_healthy é importante. Sem ele, o Playwright iniciaria antes da aplicação estar pronta para aceitar conexões e todo teste falharia com um erro de conexão. O healthcheck faz polling até a aplicação responder, e só então o Docker inicia o container dependente.

Integrando Docker no GitHub Actions

O GitHub Actions pode usar um container Docker como ambiente de execução para um job inteiro, ou você pode rodar docker build e docker run como steps. Para o Playwright especificamente, usar o container diretamente como ambiente do job é a abordagem mais limpa.

# .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

    # Usa a imagem do Playwright como container do job
    container:
      image: mcr.microsoft.com/playwright:v1.52.0-jammy
      options: --user 1001

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

      - name: Install dependencies
        run: npm ci

      - 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: Upload test report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 14

O bloco container: no nível do job diz ao GitHub Actions para rodar cada step dentro do container Docker especificado em vez de diretamente no runner Ubuntu. Os navegadores já estão instalados na imagem, então você pula o step npx playwright install completamente.

--user 1001 define o usuário do container para corresponder ao UID do runner do GitHub Actions. Sem isso, erros de permissão de arquivo podem ocorrer quando o Actions tenta escrever artifacts.

Para times que também querem construir e fazer push da própria imagem de testes para um registry:

jobs:
  build-and-test:
    runs-on: ubuntu-latest

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

      - name: Build test image
        run: docker build -t playwright-tests:${{ github.sha }} .

      - name: Run tests
        run: |
          docker run --rm \
            -e BASE_URL=${{ vars.BASE_URL }} \
            -e TEST_USER=${{ secrets.TEST_USER }} \
            -e TEST_PASSWORD=${{ secrets.TEST_PASSWORD }} \
            -v ${{ github.workspace }}/playwright-report:/app/playwright-report \
            playwright-tests:${{ github.sha }}

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

Usar ${{ github.sha }} como tag da imagem vincula a imagem ao commit exato. Isso facilita correlacionar execuções de teste com versões de código quando fazendo push para um registry.

Problemas comuns e como resolver

Containers Docker introduzem um pequeno conjunto de problemas que aparecem regularmente quando se roda Playwright. Aqui está o que esperar e como lidar com cada um.

Erros de permissão de arquivo nos relatórios. Quando o Playwright escreve em um diretório montado por volume, os arquivos pertencem ao usuário com que o container roda (frequentemente root). Seu usuário host não consegue modificá-los ou deletá-los sem sudo. A correção é definir o usuário do container em tempo de execução:

docker run --rm \
  --user $(id -u):$(id -g) \
  -v $(pwd)/playwright-report:/app/playwright-report \
  playwright-tests:local

Isso roda o container como seu usuário host atual, então os arquivos de relatório pertencem a você. No CI, use --user 1001 como mostrado no workflow acima.

Modo headless e erros de display. O Playwright roda em modo headless por padrão, o que funciona dentro de containers sem nenhuma mudança. Se você está rodando em modo headed (para debug) dentro de um container, precisaria de um display virtual. Não faça isso. Use headless em containers e headed localmente. Se um teste se comporta diferente em headed vs. headless, isso é um bug legítimo para investigar, não um problema do Docker. Builds lentos por falta de cache de camadas. Se o rebuild da imagem leva 3+ minutos toda vez, o cache não está funcionando. A causa mais comum é copiar o projeto inteiro antes de instalar as dependências:

# Lento: qualquer mudança de arquivo invalida o cache do npm ci
COPY . .
RUN npm ci

# Rápido: só mudanças nos arquivos de pacotes invalidam o cache do npm ci
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

No CI, o cache de camadas do Docker exige configuração explícita. Adicione isso ao workflow:

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build test image
        uses: docker/build-push-action@v6
        with:
          context: .
          load: true
          tags: playwright-tests:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

As linhas cache-from: type=gha e cache-to: type=gha,mode=max usam o armazenamento de cache do GitHub Actions para cache de camadas do Docker. Sem isso, cada execução de CI constrói a imagem do zero.

A imagem oficial do Playwright inclui os três navegadores, fazendo com que tenha cerca de 1,8GB. Em um runner de CI lento, o primeiro pull pode levar 3-4 minutos. Após o pull inicial, o cache de camadas do Docker mantém os starts subsequentes rápidos. Se o registry tiver um mirror local configurado, use-o.
Testes não conseguem alcançar a aplicação. Dentro de um container Docker, localhost se refere ao próprio container, não à máquina host. Se a aplicação está rodando no host em localhost:3000, o container não consegue alcançá-la nesse endereço. Use o IP do bridge Docker do host (172.17.0.1 no Linux, host.docker.internal no Mac e Windows):

docker run --rm \
  -e BASE_URL=http://host.docker.internal:3000 \
  -v $(pwd)/playwright-report:/app/playwright-report \
  playwright-tests:local

Usando docker-compose, esse problema desaparece porque todos os serviços compartilham uma rede e se comunicam pelo nome do serviço.

FAQ

Preciso do Docker se já estou usando GitHub Actions?

Não necessariamente. Os runners do GitHub Actions funcionam bem para Playwright sem Docker: instale Node, rode playwright install --with-deps e pronto. O Docker se torna valioso quando você quer o mesmo ambiente localmente e no CI, ou quando os testes rodam contra uma stack de múltiplos serviços. Também é útil ao integrar novos membros que precisam rodar os testes sem setup manual.

Devo construir minha própria imagem ou usar a oficial do Playwright?

Comece com a imagem oficial como base do FROM. Você recebe atualizações de versões de navegador gratuitas quando sobe a tag, e a imagem é mantida por pessoas que conhecem as dependências do Playwright. Construa em cima dela apenas quando precisar de adições específicas do projeto, como fontes customizadas, uma versão específica do Node ou dados de teste pré-carregados.

Como faço debug de um teste que falha apenas no container?

Monte o diretório test-results e habilite gravação de vídeo no config do Playwright para testes que falham. Após a execução, a pasta test-results/ na sua máquina host vai conter vídeos e traces. Abra o trace com npx playwright show-trace test-results/.../trace.zip para ver exatamente o que aconteceu.

Posso rodar testes em paralelo dentro de um container?

Sim. A configuração workers do Playwright controla o paralelismo e funciona igual dentro de um container. O limite prático é a contagem de CPUs do container. Para suites grandes, rode múltiplos containers em paralelo (via matrix sharding do GitHub Actions) em vez de maximizar workers dentro de um único container. É mais fácil de raciocinar e mais fácil de escalar.

O container constrói bem mas os testes falham com "executable doesn't exist".

Isso geralmente significa incompatibilidade de versão entre a tag da imagem e a versão do @playwright/test. Verifique se mcr.microsoft.com/playwright:v1.52.0-jammy corresponde a "@playwright/test": "1.52.0" no package.json. Se as versões não se alinham, o Playwright procura os navegadores em um caminho que não existe na imagem.

Como passo um arquivo .env para o container?

Use --env-file com o docker run:

docker run --rm \
  --env-file .env.test \
  -v $(pwd)/playwright-report:/app/playwright-report \
  playwright-tests:local

Nunca faça commit de arquivos .env no repositório. Adicione .env* ao .gitignore e use secrets do CI para valores sensíveis.

→ Veja também: Docker para Engenheiros QA: Executando Testes em Contêineres | GitHub Actions para Testes Playwright: A Configuração Completa (2026) | Execução Paralela no Playwright: Workers, Shards e Sharding para Velocidade