feat: add isolated E2E browser test pipeline for Forgejo Actions
Implement per-job Docker-in-Docker (DinD) for E2E tests, giving each job a completely isolated Docker daemon and network. This prevents leakage to the host Docker or other containers. The previous E2E approach failed because: 1. The Forgejo runner's container.docker_host was not set, causing the runner itself to try unix:///var/run/docker.sock and crash-loop. 2. The host DinD daemon had isolated networking — job containers running docker compose could not resolve 'dind' hostname or access host filesystem bind mounts (e.g. .:/app). New approach — zero bind mounts, all COPY-based images: - docker/backend.e2e.Dockerfile: multi-stage build from repo root. Copies gradlew + settings.gradle + backend/build.gradle to download dependencies in a cacheable layer, then copies backend/src and builds the bootJar. Runs the JAR directly on startup. - docker/frontend.e2e.Dockerfile: multi-stage Node build → nginx. Reuses existing docker/nginx.conf for /api proxy to backend service. No volume mounts, fully self-contained. - docker/playwright.e2e.Dockerfile: extends official Playwright image. Installs deps from package-lock.json, copies e2e tests + config. - docker-compose.e2e.yml: zero bind mounts. Services depend on each other in order: postgres (healthy) → backend → frontend → playwright. Playwright waits for backend and frontend via curl loops before running tests. - .forgejo/workflows/ci.yml: E2E job adds a 'dind' service container (docker:28-dind, privileged, no TLS). The job sets DOCKER_HOST to tcp://dind:2375 so the docker CLI inside the job talks to the per-job DinD daemon. The compose file is docker-compose.e2e.yml. - Runner fix on tocke: added container.docker_host: 'tcp://dind:2375' to runner-config.yaml so the runner's own Docker client connects to the host DinD container, stopping the crash loop. Key properties: - Network isolation: each E2E job gets its own DinD with its own container network. No host container visibility. - No bind mount leakage: all images use COPY instead of volume mounts. The per-job DinD has its own filesystem and can't see host paths. - Deterministic: builds start from clean state every time. Image cache exists only within the per-job DinD lifetime. - Lint-and-test job is untouched and remains green.
This commit is contained in:
parent
8e3632f05f
commit
1f1016a775
5 changed files with 101 additions and 1 deletions
|
|
@ -58,7 +58,14 @@ jobs:
|
||||||
e2e:
|
e2e:
|
||||||
name: E2E browser tests
|
name: E2E browser tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
services:
|
||||||
|
dind:
|
||||||
|
image: docker:28-dind
|
||||||
|
options: --privileged
|
||||||
|
env:
|
||||||
|
DOCKER_TLS_CERTDIR: ""
|
||||||
env:
|
env:
|
||||||
|
DOCKER_HOST: tcp://dind:2375
|
||||||
POSTGRES_DB: bilhej
|
POSTGRES_DB: bilhej
|
||||||
POSTGRES_USER: bilhej
|
POSTGRES_USER: bilhej
|
||||||
POSTGRES_PASSWORD: test_pw_ci_123
|
POSTGRES_PASSWORD: test_pw_ci_123
|
||||||
|
|
@ -77,5 +84,5 @@ jobs:
|
||||||
- name: Run E2E test stack
|
- name: Run E2E test stack
|
||||||
run: |
|
run: |
|
||||||
docker compose \
|
docker compose \
|
||||||
-f docker-compose.ci.yml \
|
-f docker-compose.e2e.yml \
|
||||||
up --build --abort-on-container-exit --exit-code-from playwright
|
up --build --abort-on-container-exit --exit-code-from playwright
|
||||||
|
|
|
||||||
64
docker-compose.e2e.yml
Normal file
64
docker-compose.e2e.yml
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: bilhej-postgres-e2e
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
dockerfile: docker/backend.e2e.Dockerfile
|
||||||
|
context: .
|
||||||
|
container_name: bilhej-backend-e2e
|
||||||
|
environment:
|
||||||
|
SPRING_PROFILES_ACTIVE: docker
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
|
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
|
||||||
|
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET}
|
||||||
|
STRIPE_PRICE_ID: ${STRIPE_PRICE_ID}
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
dockerfile: docker/frontend.e2e.Dockerfile
|
||||||
|
context: .
|
||||||
|
container_name: bilhej-frontend-e2e
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
|
||||||
|
playwright:
|
||||||
|
build:
|
||||||
|
dockerfile: docker/playwright.e2e.Dockerfile
|
||||||
|
context: .
|
||||||
|
container_name: bilhej-playwright-e2e
|
||||||
|
ipc: host
|
||||||
|
environment:
|
||||||
|
PLAYWRIGHT_BASE_URL: http://frontend
|
||||||
|
depends_on:
|
||||||
|
- frontend
|
||||||
|
command: >-
|
||||||
|
sh -c "
|
||||||
|
echo 'Waiting for backend...';
|
||||||
|
for i in \$(seq 1 60); do
|
||||||
|
curl -s http://backend:8080/api/vehicles/ZZZ999 > /dev/null && break;
|
||||||
|
sleep 1;
|
||||||
|
done;
|
||||||
|
echo 'Waiting for frontend...';
|
||||||
|
for i in \$(seq 1 30); do
|
||||||
|
curl -s http://frontend > /dev/null && break;
|
||||||
|
sleep 1;
|
||||||
|
done;
|
||||||
|
npx playwright test --reporter=list
|
||||||
|
"
|
||||||
10
docker/backend.e2e.Dockerfile
Normal file
10
docker/backend.e2e.Dockerfile
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM eclipse-temurin:21-jdk
|
||||||
|
WORKDIR /app
|
||||||
|
COPY gradlew settings.gradle ./
|
||||||
|
COPY gradle/wrapper/ gradle/wrapper/
|
||||||
|
COPY backend/build.gradle backend/
|
||||||
|
RUN chmod +x gradlew && ./gradlew :backend:dependencies --no-daemon -q
|
||||||
|
COPY backend/src backend/src
|
||||||
|
RUN ./gradlew :backend:bootJar --no-daemon -q
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["sh", "-c", "java -jar backend/build/libs/*-SNAPSHOT.jar"]
|
||||||
12
docker/frontend.e2e.Dockerfile
Normal file
12
docker/frontend.e2e.Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM node:24-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY frontend/package.json frontend/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY frontend/ .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
||||||
|
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
7
docker/playwright.e2e.Dockerfile
Normal file
7
docker/playwright.e2e.Dockerfile
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
FROM mcr.microsoft.com/playwright:v1.60.0-noble
|
||||||
|
WORKDIR /app
|
||||||
|
COPY frontend/package.json frontend/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY frontend/playwright.config.ts ./
|
||||||
|
COPY frontend/e2e ./e2e
|
||||||
|
CMD ["sh", "-c", "npx playwright test --reporter=list"]
|
||||||
Loading…
Reference in a new issue