E2E w Dockerze i efemerycznych środowiskach

Odtwarzalne środowiska testowe to fundament niezawodnego CI/CD. Naucz się używać Docker Compose do lokalnego i CI testowania E2E oraz uruchamiać testy smoke przeciwko efemerycznym deploymentom.

Ten post to część 3 serii Jakość w CI/CD. Część 2 omawiała GitHub Actions — workflow, cache’owanie i integrację Playwrighta.


Wprowadzenie — problem “działa na moim komputerze”

Test suite, który przechodzi lokalnie, ale failuje w CI, jest gorszy niż brak test suite. Szkoli developerów do ignorowania niepowodzeń CI.

Pierwotna przyczyna to niespójność środowiskowa. Twoja lokalna maszyna ma inną:

  • Wersję Node
  • Wersję bazy danych
  • Zmienne środowiskowe
  • Opóźnienie sieciowe
  • Zachowanie systemu plików

Docker rozwiązuje to, pakując aplikację i jej zależności w kontener, który działa identycznie wszędzie.

Ten post omawia, jak używać Docker Compose do odtwarzalnego testowania E2E lokalnie i w CI oraz jak uruchamiać testy smoke przeciwko efemerycznym środowiskom preview.


Dlaczego Docker dla testów E2E

Odtwarzalność — Ten sam kontener działa na laptopach developerów, runnerach CI i serwerach produkcyjnych. Koniec z “działa na moim komputerze”.

Izolacja — Każde uruchomienie testu startuje z czystego stanu. Brak pozostałości danych z poprzednich uruchomień.

Równoległość — Uruchamiaj wiele izolowanych środowisk jednocześnie dla równoległego wykonywania testów.

Prostota — Developerzy uruchamiają docker compose up zamiast ręcznie instalować Postgres, Redis i Elasticsearch.

:::tip[Docker vs. mocki] Mockowanie zewnętrznych zależności (baz danych, kolejek wiadomości) w testach jest szybkie, ale kruche. Mocki odstają od prawdziwego zachowania. Docker pozwala testować przeciwko prawdziwej bazie danych z minimalnym narzutem. :::


Docker Compose dla lokalnego E2E

Docker Compose orkiestruje aplikacje wielokontenerowe. Zdefiniuj swoją aplikację, bazę danych i zależności w docker-compose.yml.

Przykład: aplikacja webowa + Postgres

# docker-compose.yml
version: '3.8'

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpass
      POSTGRES_DB: testdb
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U testuser"]
      interval: 5s
      timeout: 5s
      retries: 5

  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://testuser:testpass@db:5432/testdb
      NODE_ENV: test
    depends_on:
      db:
        condition: service_healthy

Kluczowe funkcje:

  • Serwis db uruchamia Postgres 16
  • healthcheck zapewnia, że Postgres jest gotowy przed startem aplikacji
  • Serwis app czeka na db do bycia zdrowym (depends_on z condition)
  • Zmienne środowiskowe skonfigurowane dla trybu testowego

Uruchamianie testów E2E z Docker Compose

# Uruchom serwisy
docker compose up -d

# Poczekaj, aż aplikacja będzie gotowa
npx wait-on http://localhost:3000

# Uruchom testy Playwright
npm run test:e2e

# Zatrzymaj serwisy
docker compose down

Alternatywnie, skryptuj to w package.json:

{
  "scripts": {
    "test:e2e:docker": "docker compose up -d && npx wait-on http://localhost:3000 && npm run test:e2e; docker compose down"
  }
}

:::warning[Persystencja danych] Domyślnie Docker Compose persystuje dane w wolumenach. Dla testów chcesz czystego stanu przy każdym uruchomieniu. Użyj docker compose down -v aby usunąć wolumeny i zresetować stan. :::


Docker Compose w CI

Ten sam docker-compose.yml działa w GitHub Actions.

Workflow GitHub Actions z Docker Compose

name: E2E Tests

on: [pull_request]

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      
      - run: npm ci
      
      - name: Start services with Docker Compose
        run: docker compose up -d
      
      - name: Wait for app to be ready
        run: npx wait-on http://localhost:3000 --timeout 60000
      
      - name: Install Playwright browsers
        run: npx playwright install --with-deps chromium
      
      - name: Run E2E tests
        run: npm run test:e2e
      
      - name: Stop services
        if: always()
        run: docker compose down -v
      
      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: playwright-report/

Dlaczego to działa:

  • docker compose up -d startuje serwisy w tle
  • wait-on blokuje, aż aplikacja odpowiada na http://localhost:3000
  • Playwright uruchamia testy przeciwko http://localhost:3000
  • docker compose down -v sprząta kontenery i wolumeny (if: always() zapewnia sprzątanie nawet jeśli testy failują)

:::info[Wydajność CI] Startowanie kontenerów Docker dodaje 20–40 sekund do uruchomień CI. To akceptowalne dla testów E2E, ale zbyt wolne dla testów jednostkowych. Używaj Dockera tylko dla suitów testów integracyjnych i E2E. :::


Seedowanie danych testowych

Testy E2E potrzebują realistycznych danych. Zasiej bazę danych przed uruchomieniem testów.

Skrypt seedowania bazy danych

// scripts/seed-test-db.ts
import { Client } from 'pg';

const client = new Client({
  connectionString: process.env.DATABASE_URL,
});

async function seed() {
  await client.connect();
  
  await client.query(`
    INSERT INTO users (email, name, role)
    VALUES 
      ('admin@test.com', 'Admin User', 'admin'),
      ('user@test.com', 'Regular User', 'user');
  `);
  
  await client.query(`
    INSERT INTO products (name, price, stock)
    VALUES 
      ('Product A', 29.99, 100),
      ('Product B', 49.99, 50);
  `);
  
  await client.end();
  console.log('Test database seeded');
}

seed().catch(console.error);

Uruchom seedowanie po starcie serwisów:

      - name: Start services
        run: docker compose up -d
      
      - name: Wait for app
        run: npx wait-on http://localhost:3000
      
      - name: Seed test data
        run: npm run seed:test
      
      - name: Run E2E tests
        run: npm run test:e2e

:::tip[Idempotentne seedy] Rób skrypty seedowania idempotentne — bezpieczne do wielokrotnego uruchamiania. Użyj INSERT ... ON CONFLICT DO NOTHING lub DELETE FROM table przed insertem. To zapobiega niepowodzeniom przy wielokrotnym uruchamianiu testów podczas developmentu. :::


Efemeryczne środowiska preview

Środowiska efemeryczne to tymczasowe deploymenty tworzone dla każdego pull requesta. Pozwalają testować aplikację w środowisku podobnym do produkcyjnego przed merge’em.

Serwisy jak Vercel, Netlify i Heroku Review Apps automatycznie deployują środowiska preview dla każdego PR.

Deploymenty preview Vercel

Vercel automatycznie deployuje każdy push na unikalny URL:

https://my-app-pr123-abc1234.vercel.app

Ten URL jest publikowany jako komentarz w pull requeście.

Uruchamianie testów smoke przeciwko deploymentom preview

Poczekaj na zakończenie deploymentu preview, potem uruchom testy smoke przeciwko niemu.

name: Preview E2E

on:
  pull_request:

jobs:
  preview-smoke-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      
      - run: npm ci
      
      - name: Wait for Vercel deployment
        uses: UnlyEd/github-action-await-vercel@v1
        with:
          deployment-url: ${{ github.event.pull_request.head.sha }}
          timeout: 300
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      
      - name: Install Playwright
        run: npx playwright install --with-deps chromium
      
      - name: Run smoke tests against preview
        env:
          BASE_URL: ${{ steps.await-vercel.outputs.url }}
        run: npm run test:e2e:smoke
      
      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: preview-smoke-report
          path: playwright-report/

Kluczowe funkcje:

  • Akcja await-vercel czeka na zakończenie deploymentu Vercel
  • Zmienna środowiskowa BASE_URL kieruje testy na URL preview
  • Testy smoke działają przeciwko deployowanemu preview, nie localhost

Konfigurowanie Playwrighta dla dynamicznego Base URL

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

export default defineConfig({
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
  },
  projects: [
    {
      name: 'chromium',
      use: { browserName: 'chromium' },
    },
  ],
});

Teraz testy używają URL preview, gdy BASE_URL jest ustawiony:

test('homepage loads', async ({ page }) => {
  await page.goto('/');  // Używa baseURL z configu
  await expect(page.locator('h1')).toContainText('Welcome');
});

:::warning[Ograniczenia środowisk preview] Środowiska preview często używają oddzielnych baz danych lub mockowych danych. Nie zakładaj parytetu danych z produkcją. Testuj renderowanie UI, nawigację i autentykację — nie integralność danych produkcyjnych. :::


Porównanie lokalnego Dockera vs. środowisk preview

AspektLokalny DockerŚrodowisko preview
Szybkość20–40s startup60–180s deployment
IzolacjaPełna kontrola nad serwisamiZarządzane przez chmurę, ograniczona kontrola
DaneSkrypty seed, pełna kontrolaDane mockowe lub oddzielne testowe DB
Przypadek użyciaTesty integracyjne/E2E w CITesty smoke na prawdziwej infrastrukturze
KosztDarmowy (działa na runnerze CI)Zależy od tieru platformy

Rekomendacja: Używaj Docker Compose dla pełnych suitów testów E2E w CI. Używaj środowisk preview dla lekkich testów smoke, które walidują deployment i routing.


Praktyczny przykład — pełny workflow E2E CI

Połącz Docker Compose dla lokalnego testowania i testy smoke preview w jednym workflow.

name: E2E Tests

on: [pull_request]

jobs:
  e2e-local:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      
      - name: Start services
        run: docker compose up -d
      
      - name: Wait for app
        run: npx wait-on http://localhost:3000 --timeout 60000
      
      - name: Seed test data
        run: npm run seed:test
      
      - name: Install Playwright
        run: npx playwright install --with-deps chromium
      
      - name: Run full E2E suite
        run: npm run test:e2e
      
      - name: Stop services
        if: always()
        run: docker compose down -v
      
      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: e2e-report
          path: playwright-report/

  preview-smoke:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      
      - name: Wait for Vercel
        id: await-vercel
        uses: UnlyEd/github-action-await-vercel@v1
        with:
          deployment-url: ${{ github.event.pull_request.head.sha }}
          timeout: 300
        env:
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
      
      - name: Install Playwright
        run: npx playwright install --with-deps chromium
      
      - name: Run smoke tests
        env:
          BASE_URL: ${{ steps.await-vercel.outputs.url }}
        run: npm run test:e2e:smoke
      
      - name: Upload report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: preview-smoke-report
          path: playwright-report/

Dwa joby:

  1. e2e-local — Pełna suita E2E przeciwko serwissom Docker Compose
  2. preview-smoke — Testy smoke przeciwko deploymentowi preview Vercel

Oba działają równolegle. Całkowity czas CI: ~5–7 minut.


Podsumowanie — środowiska jako kod

Odtwarzalne środowiska testowe to fundament niezawodnego CI/CD. Docker Compose czyni trywialnym pakowanie aplikacji, zależności i danych testowych w przenośną, wersjonowaną definicję środowiska.

Zasady:

  • Używaj Docker Compose dla lokalnych i CI testów E2E
  • Seeduj dane testowe w powtarzalny, idempotentny sposób
  • Sprzątaj kontenery i wolumeny po każdym uruchomieniu
  • Uruchamiaj testy smoke przeciwko deploymentom preview, aby walidować prawdziwą infrastrukturę
  • Separuj odpowiedzialności — pełne E2E w Dockerze, testy smoke w preview

Gdy “działa na moim komputerze” staje się “działa w kontenerze”, niepowodzenia CI stają się możliwe do działania i godne zaufania.

Zadanie na ten tydzień: Stwórz docker-compose.yml dla swojego projektu z aplikacją i jej główną zależnością (baza danych, cache, itp.). Uruchom jeden test E2E przeciwko niemu lokalnie. Zmierz czas od docker compose up do zakończenia testu.


Dalej w tej serii: Część 4 — Raporty, artefakty i triage awarii w CI