Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ if [ -n "${E2E_SEED_SQL}" ] && [ -f "${E2E_SEED_SQL}" ]; then
echo "Seeding E2E test data from ${E2E_SEED_SQL}..."
# Convert asyncpg URL to standard psql URL
PSQL_URL=$(echo "${DATABASE_URL}" | sed 's|postgresql+asyncpg://|postgresql://|')
if ! psql "${PSQL_URL}" -f "${E2E_SEED_SQL}"; then
if ! psql "${PSQL_URL}" -v ON_ERROR_STOP=1 -f "${E2E_SEED_SQL}"; then
echo "ERROR: Seed script failed! Backend will not start with incomplete test data."
echo "Check the SQL for type mismatches or missing tables."
exit 1
Expand Down
1 change: 1 addition & 0 deletions frontend/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const allProjects = [

export default defineConfig({
testDir: './tests/e2e',
globalSetup: './tests/e2e/global-setup.ts',
timeout: process.env.CI ? 60_000 : 30_000,
fullyParallel: false,
forbidOnly: !!process.env.CI,
Expand Down
94 changes: 71 additions & 23 deletions frontend/tests/e2e/fixtures/seed.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-- E2E test seed data
-- Passwords are bcrypt hashes of "password123"
-- This file is run AFTER alembic upgrade head via the backend entrypoint
\set ON_ERROR_STOP on

-- ============================================================
-- Users
Expand Down Expand Up @@ -60,29 +61,76 @@ ON CONFLICT (id) DO NOTHING;
-- ============================================================
-- Test Cases (5 sample)
-- ============================================================
INSERT INTO test_cases (id, title, description, type, priority, status, automation_status, created_at, updated_at)
VALUES
('20000000-0000-0000-0000-000000000001'::uuid,
'TC-001: Login with valid credentials',
'Verify that a user can log in with valid email and password.',
'functional', 'high', 'ready', 'automated', NOW(), NOW()),
('20000000-0000-0000-0000-000000000002'::uuid,
'TC-002: Login with invalid credentials',
'Verify that an error message is shown for invalid credentials.',
'functional', 'high', 'ready', 'automated', NOW(), NOW()),
('20000000-0000-0000-0000-000000000003'::uuid,
'TC-003: Export PDF report',
'Verify that the traceability matrix can be exported as a PDF.',
'functional', 'medium', 'draft', 'manual', NOW(), NOW()),
('20000000-0000-0000-0000-000000000004'::uuid,
'TC-004: Role enforcement for admin actions',
'Verify that only admin users can access administrative features.',
'functional', 'high', 'ready', 'manual', NOW(), NOW()),
('20000000-0000-0000-0000-000000000005'::uuid,
'TC-005: API response time under load',
'Measure API response times with 100 concurrent requests.',
'performance', 'low', 'draft', 'manual', NOW(), NOW())
ON CONFLICT (id) DO NOTHING;
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema = current_schema()
AND table_name = 'test_cases'
AND column_name = 'auto_registered'
) THEN
INSERT INTO test_cases
(id, external_id, title, description, type, priority, status, automation_status, auto_registered, created_at, updated_at)
VALUES
('20000000-0000-0000-0000-000000000001'::uuid,
'TC-001',
'TC-001: Login with valid credentials',
'Verify that a user can log in with valid email and password.',
'functional', 'high', 'ready', 'automated', false, NOW(), NOW()),
('20000000-0000-0000-0000-000000000002'::uuid,
'TC-002',
'TC-002: Login with invalid credentials',
'Verify that an error message is shown for invalid credentials.',
'functional', 'high', 'ready', 'automated', false, NOW(), NOW()),
('20000000-0000-0000-0000-000000000003'::uuid,
'TC-003',
'TC-003: Export PDF report',
'Verify that the traceability matrix can be exported as a PDF.',
'functional', 'medium', 'draft', 'manual', false, NOW(), NOW()),
('20000000-0000-0000-0000-000000000004'::uuid,
'TC-004',
'TC-004: Role enforcement for admin actions',
'Verify that only admin users can access administrative features.',
'functional', 'high', 'ready', 'manual', false, NOW(), NOW()),
('20000000-0000-0000-0000-000000000005'::uuid,
'TC-005',
'TC-005: API response time under load',
'Measure API response times with 100 concurrent requests.',
'performance', 'low', 'draft', 'manual', false, NOW(), NOW())
ON CONFLICT (id) DO NOTHING;
ELSE
INSERT INTO test_cases
(id, external_id, title, description, type, priority, status, automation_status, created_at, updated_at)
VALUES
('20000000-0000-0000-0000-000000000001'::uuid,
'TC-001',
'TC-001: Login with valid credentials',
'Verify that a user can log in with valid email and password.',
'functional', 'high', 'ready', 'automated', NOW(), NOW()),
('20000000-0000-0000-0000-000000000002'::uuid,
'TC-002',
'TC-002: Login with invalid credentials',
'Verify that an error message is shown for invalid credentials.',
'functional', 'high', 'ready', 'automated', NOW(), NOW()),
('20000000-0000-0000-0000-000000000003'::uuid,
'TC-003',
'TC-003: Export PDF report',
'Verify that the traceability matrix can be exported as a PDF.',
'functional', 'medium', 'draft', 'manual', NOW(), NOW()),
('20000000-0000-0000-0000-000000000004'::uuid,
'TC-004',
'TC-004: Role enforcement for admin actions',
'Verify that only admin users can access administrative features.',
'functional', 'high', 'ready', 'manual', NOW(), NOW()),
('20000000-0000-0000-0000-000000000005'::uuid,
'TC-005',
'TC-005: API response time under load',
'Measure API response times with 100 concurrent requests.',
'performance', 'low', 'draft', 'manual', NOW(), NOW())
ON CONFLICT (id) DO NOTHING;
END IF;
END $$;

-- ============================================================
-- Existing links (3)
Expand Down
69 changes: 69 additions & 0 deletions frontend/tests/e2e/global-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { apiLogin, API_URL } from './helpers/api';

type PaginatedResponse<T> = {
items: T[];
total: number;
};

type SeededTestCase = {
external_id: string | null;
title: string;
};

const API_PREFIX = '/api/v1';
const REQUIRED_TEST_CASE_IDS = ['TC-001', 'TC-002', 'TC-003', 'TC-004', 'TC-005'] as const;

async function getPaginated<T>(path: string, token: string): Promise<PaginatedResponse<T>> {
const response = await fetch(`${API_URL}${API_PREFIX}${path}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
const body = await response.text();
throw new Error(`[E2E seed smoke check] GET ${path} failed with ${response.status}: ${body}`);
}

return (await response.json()) as PaginatedResponse<T>;
}

export default async function globalSetup(): Promise<void> {
const email = process.env.E2E_ADMIN_EMAIL || 'admin@test.com';
const password = process.env.E2E_ADMIN_PASSWORD || 'password123';
const token = await apiLogin(email, password);

const [requirements, testCases, links] = await Promise.all([
getPaginated('/requirements?page=1&page_size=200', token),
getPaginated<SeededTestCase>('/test-cases?page=1&page_size=200', token),
getPaginated('/links?page=1&page_size=200', token),
]);

if (requirements.total < 5) {
throw new Error(`[E2E seed smoke check] requirements seed incomplete: expected >= 5, found ${requirements.total}`);
}

if (testCases.total < 5) {
throw new Error(`[E2E seed smoke check] test_cases seed incomplete: expected >= 5, found ${testCases.total}`);
}

if (links.total < 3) {
throw new Error(
`[E2E seed smoke check] requirement_test_case_links seed incomplete: expected >= 3, found ${links.total}`,
);
}

const availableIds = new Set(
testCases.items
.map((testCase) => testCase.external_id)
.filter((externalId): externalId is string => typeof externalId === 'string'),
);

const missingSeededCases = REQUIRED_TEST_CASE_IDS.filter((requiredId) => !availableIds.has(requiredId));

if (missingSeededCases.length > 0) {
throw new Error(
`[E2E seed smoke check] seeded test_cases missing external_id(s): ${missingSeededCases.join(', ')}`,
);
}
}
Loading