Skip to content

Commit 7bd4500

Browse files
Merge pull request #145 from bg-playground/copilot/scaffold-playwright-e2e-tests
Scaffold Playwright E2E test suite
2 parents b99c71c + de0e07f commit 7bd4500

19 files changed

Lines changed: 1239 additions & 18 deletions

.github/workflows/e2e-tests.yml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
name: E2E Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'frontend/**'
9+
- 'backend/**'
10+
- 'docker-compose.test.yml'
11+
- '.github/workflows/e2e-tests.yml'
12+
pull_request:
13+
branches:
14+
- main
15+
paths:
16+
- 'frontend/**'
17+
- 'backend/**'
18+
- 'docker-compose.test.yml'
19+
- '.github/workflows/e2e-tests.yml'
20+
21+
jobs:
22+
e2e:
23+
name: Playwright E2E Tests
24+
runs-on: ubuntu-latest
25+
timeout-minutes: 30
26+
permissions:
27+
contents: read
28+
29+
steps:
30+
- name: Checkout repository
31+
uses: actions/checkout@v4
32+
33+
- name: Set up Node.js
34+
uses: actions/setup-node@v4
35+
with:
36+
node-version: '20'
37+
cache: 'npm'
38+
cache-dependency-path: frontend/package-lock.json
39+
40+
- name: Install frontend dependencies
41+
working-directory: frontend
42+
run: npm ci
43+
44+
- name: Install Playwright browsers
45+
working-directory: frontend
46+
run: npx playwright install --with-deps chromium
47+
48+
- name: Start test environment
49+
run: docker compose -f docker-compose.test.yml up -d --build
50+
51+
- name: Wait for backend to be healthy
52+
run: |
53+
echo "Waiting for backend health check..."
54+
for i in $(seq 1 24); do
55+
if curl -sf http://localhost:8001/health; then
56+
echo "Backend is healthy!"
57+
break
58+
fi
59+
echo "Attempt $i/24 — backend not ready yet, waiting 10s..."
60+
sleep 10
61+
done
62+
# Final check — fail loudly if still not healthy
63+
curl -sf http://localhost:8001/health || (echo "Backend failed to become healthy" && exit 1)
64+
65+
- name: Wait for frontend health check
66+
run: |
67+
timeout 60 bash -c '
68+
until curl -sf http://localhost:3001; do
69+
echo "Frontend not ready, retrying in 5s..."
70+
sleep 5
71+
done
72+
'
73+
74+
- name: Dump container logs (always)
75+
if: always()
76+
run: |
77+
echo "===== DB logs ====="
78+
docker compose -f docker-compose.test.yml logs db || true
79+
echo "===== Backend logs ====="
80+
docker compose -f docker-compose.test.yml logs backend || true
81+
echo "===== Frontend logs ====="
82+
docker compose -f docker-compose.test.yml logs frontend || true
83+
84+
- name: Upload container logs on failure
85+
if: failure()
86+
run: |
87+
mkdir -p /tmp/container-logs
88+
docker compose -f docker-compose.test.yml logs db > /tmp/container-logs/db.log 2>&1 || true
89+
docker compose -f docker-compose.test.yml logs backend > /tmp/container-logs/backend.log 2>&1 || true
90+
docker compose -f docker-compose.test.yml logs frontend > /tmp/container-logs/frontend.log 2>&1 || true
91+
92+
- name: Upload container logs artifact
93+
if: failure()
94+
uses: actions/upload-artifact@v4
95+
with:
96+
name: container-logs
97+
path: /tmp/container-logs/
98+
retention-days: 7
99+
100+
- name: Run Playwright tests
101+
working-directory: frontend
102+
env:
103+
PLAYWRIGHT_BASE_URL: http://localhost:3001
104+
PLAYWRIGHT_API_URL: http://localhost:8001
105+
E2E_ADMIN_EMAIL: admin@test.com
106+
E2E_ADMIN_PASSWORD: password123
107+
run: npx playwright test --project=chromium
108+
109+
- name: Upload Playwright HTML report on failure
110+
if: failure()
111+
uses: actions/upload-artifact@v4
112+
with:
113+
name: playwright-report
114+
path: frontend/playwright-report/
115+
retention-days: 7
116+
117+
- name: Upload screenshots on failure
118+
if: failure()
119+
uses: actions/upload-artifact@v4
120+
with:
121+
name: playwright-screenshots
122+
path: frontend/test-results/
123+
retention-days: 7
124+
125+
- name: Tear down test environment
126+
if: always()
127+
run: docker compose -f docker-compose.test.yml down -v --remove-orphans || true

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ frontend/.env
4949
frontend/.next/
5050
.next/
5151

52+
# Playwright
53+
frontend/playwright-report/
54+
frontend/test-results/
55+
5256
# Docker
5357
docker-compose.override.yml
5458

backend/alembic/env.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
# Import Base.metadata for autogenerate support.
2626
# All models must be imported so their tables are registered on Base.metadata.
27-
import app.models.audit_log # noqa: E402, F401
2827
import app.models.link # noqa: E402, F401
2928
import app.models.requirement # noqa: E402, F401
3029
import app.models.suggestion # noqa: E402, F401

backend/alembic/versions/d3e4f5a6b7c8_add_performance_indexes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""add performance indexes
22
33
Revision ID: d3e4f5a6b7c8
4-
Revises: c2d3e4f5a6b7
4+
Revises: e4f5a6b7c8d9
55
Create Date: 2026-02-23 03:00:00.000000
66
77
"""
@@ -12,7 +12,7 @@
1212

1313
# revision identifiers, used by Alembic.
1414
revision: str = "d3e4f5a6b7c8"
15-
down_revision: Union[str, None] = "c2d3e4f5a6b7"
15+
down_revision: Union[str, None] = "e4f5a6b7c8d9"
1616
branch_labels: Union[str, Sequence[str], None] = None
1717
depends_on: Union[str, Sequence[str], None] = None
1818

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""add notifications table
2+
3+
Revision ID: e4f5a6b7c8d9
4+
Revises: c2d3e4f5a6b7
5+
Create Date: 2026-02-26 00:00:00.000000
6+
"""
7+
8+
from typing import Sequence, Union
9+
10+
import sqlalchemy as sa
11+
12+
from alembic import op
13+
14+
revision: str = "e4f5a6b7c8d9"
15+
down_revision: Union[str, None] = "c2d3e4f5a6b7"
16+
branch_labels: Union[str, Sequence[str], None] = None
17+
depends_on: Union[str, Sequence[str], None] = None
18+
19+
20+
def upgrade() -> None:
21+
op.create_table(
22+
"notifications",
23+
sa.Column("id", sa.String(36), primary_key=True),
24+
sa.Column(
25+
"user_id",
26+
sa.String(36),
27+
sa.ForeignKey("users.id", ondelete="CASCADE"),
28+
nullable=False,
29+
),
30+
sa.Column("message", sa.Text(), nullable=False),
31+
sa.Column("read", sa.Boolean(), nullable=False, server_default="false"),
32+
sa.Column(
33+
"created_at",
34+
sa.DateTime(),
35+
nullable=False,
36+
server_default=sa.func.now(),
37+
),
38+
)
39+
40+
41+
def downgrade() -> None:
42+
op.drop_table("notifications")

backend/entrypoint.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,13 @@ set -e
44
echo "Running database migrations..."
55
alembic upgrade head
66

7+
# Optional: seed E2E test data after migrations
8+
if [ -n "${E2E_SEED_SQL}" ] && [ -f "${E2E_SEED_SQL}" ]; then
9+
echo "Seeding E2E test data from ${E2E_SEED_SQL}..."
10+
# Convert asyncpg URL to standard psql URL
11+
PSQL_URL=$(echo "${DATABASE_URL}" | sed 's|postgresql+asyncpg://|postgresql://|')
12+
psql "${PSQL_URL}" -f "${E2E_SEED_SQL}" || echo "Seed already applied (ignoring errors)."
13+
fi
14+
715
echo "Starting server..."
816
exec uvicorn app.main:app --host 0.0.0.0 --port 8000

docker-compose.test.yml

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
services:
2+
db:
3+
image: postgres:15-alpine
4+
container_name: bgstm-test-db
5+
environment:
6+
POSTGRES_USER: bgstm_test
7+
POSTGRES_PASSWORD: bgstm_test
8+
POSTGRES_DB: bgstm_test
9+
ports:
10+
- "5433:5432"
11+
healthcheck:
12+
test: ["CMD-SHELL", "pg_isready -U bgstm_test"]
13+
interval: 10s
14+
timeout: 5s
15+
retries: 5
16+
networks:
17+
- bgstm-test-network
18+
19+
backend:
20+
build:
21+
context: ./backend
22+
dockerfile: Dockerfile
23+
container_name: bgstm-test-backend
24+
environment:
25+
DATABASE_URL: postgresql+asyncpg://bgstm_test:bgstm_test@db:5432/bgstm_test
26+
SECRET_KEY: test-secret-key-for-e2e-tests-only
27+
API_V1_PREFIX: /api/v1
28+
PROJECT_NAME: "BGSTM AI Traceability (Test)"
29+
VERSION: "2.0.0"
30+
BACKEND_CORS_ORIGINS: '["http://localhost:3001", "http://localhost:3000", "http://frontend:80"]'
31+
PYTHONPATH: /app
32+
ACCESS_TOKEN_EXPIRE_MINUTES: "60"
33+
ALGORITHM: HS256
34+
E2E_SEED_SQL: /app/seed.sql
35+
volumes:
36+
- ./frontend/tests/e2e/fixtures/seed.sql:/app/seed.sql:ro
37+
ports:
38+
- "8001:8000"
39+
depends_on:
40+
db:
41+
condition: service_healthy
42+
healthcheck:
43+
test: ["CMD-SHELL", "curl -sf http://localhost:8000/health || exit 1"]
44+
interval: 15s
45+
timeout: 10s
46+
retries: 5
47+
start_period: 40s
48+
networks:
49+
- bgstm-test-network
50+
51+
frontend:
52+
build:
53+
context: ./frontend
54+
dockerfile: Dockerfile
55+
container_name: bgstm-test-frontend
56+
environment:
57+
VITE_API_BASE_URL: http://localhost:8001
58+
ports:
59+
- "3001:80"
60+
depends_on:
61+
backend:
62+
condition: service_healthy
63+
healthcheck:
64+
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80"]
65+
interval: 15s
66+
timeout: 10s
67+
retries: 5
68+
start_period: 20s
69+
networks:
70+
- bgstm-test-network
71+
72+
networks:
73+
bgstm-test-network:
74+
driver: bridge

0 commit comments

Comments
 (0)