tests
This commit is contained in:
17
tests/e2e/api/.auth/session.json
Normal file
17
tests/e2e/api/.auth/session.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"cookies": [
|
||||
{
|
||||
"name": "gp_session",
|
||||
"value": "gp_9f9c4115-2a02-4be7-9aec-72ddb3c7cdbf",
|
||||
"domain": "localhost",
|
||||
"path": "/",
|
||||
"expires": -1,
|
||||
"httpOnly": true,
|
||||
"secure": false,
|
||||
"sameSite": "Lax"
|
||||
}
|
||||
],
|
||||
"userId": "68fd953d-4f4a-47b6-83b9-ec361238e4f1",
|
||||
"email": "smoke-test-1767897520573@example.com",
|
||||
"password": "Password123"
|
||||
}
|
||||
122
tests/e2e/api/api-auth.setup.ts
Normal file
122
tests/e2e/api/api-auth.setup.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* API Authentication Setup for E2E Tests
|
||||
*
|
||||
* This setup creates authentication sessions for both regular and admin users
|
||||
* that are persisted across all tests in the suite.
|
||||
*/
|
||||
|
||||
import { test as setup } from '@playwright/test';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL ?? process.env.NEXT_PUBLIC_API_BASE_URL ?? 'http://localhost:3101';
|
||||
|
||||
// Define auth file paths
|
||||
const USER_AUTH_FILE = path.join(__dirname, '.auth/user-session.json');
|
||||
const ADMIN_AUTH_FILE = path.join(__dirname, '.auth/admin-session.json');
|
||||
|
||||
setup('Authenticate regular user', async ({ request }) => {
|
||||
console.log(`[AUTH SETUP] Creating regular user session at: ${API_BASE_URL}`);
|
||||
|
||||
// Wait for API to be ready
|
||||
const maxAttempts = 30;
|
||||
let apiReady = false;
|
||||
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
const response = await request.get(`${API_BASE_URL}/health`);
|
||||
if (response.ok()) {
|
||||
apiReady = true;
|
||||
console.log(`[AUTH SETUP] API is ready after ${i + 1} attempts`);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue trying
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
if (!apiReady) {
|
||||
throw new Error('API failed to become ready');
|
||||
}
|
||||
|
||||
// Create test user and establish cookie-based session
|
||||
const testEmail = `smoke-test-${Date.now()}@example.com`;
|
||||
const testPassword = 'Password123';
|
||||
|
||||
// Signup
|
||||
const signupResponse = await request.post(`${API_BASE_URL}/auth/signup`, {
|
||||
data: {
|
||||
email: testEmail,
|
||||
password: testPassword,
|
||||
displayName: 'Smoke Tester',
|
||||
username: `smokeuser${Date.now()}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!signupResponse.ok()) {
|
||||
throw new Error(`Signup failed: ${signupResponse.status()}`);
|
||||
}
|
||||
|
||||
const signupData = await signupResponse.json();
|
||||
const testUserId = signupData?.user?.userId ?? null;
|
||||
console.log('[AUTH SETUP] Test user created:', testUserId);
|
||||
|
||||
// Login to establish cookie session
|
||||
const loginResponse = await request.post(`${API_BASE_URL}/auth/login`, {
|
||||
data: {
|
||||
email: testEmail,
|
||||
password: testPassword
|
||||
}
|
||||
});
|
||||
|
||||
if (!loginResponse.ok()) {
|
||||
throw new Error(`Login failed: ${loginResponse.status()}`);
|
||||
}
|
||||
|
||||
console.log('[AUTH SETUP] Regular user session established');
|
||||
|
||||
// Get cookies and save to auth file
|
||||
const context = request.context();
|
||||
const cookies = context.cookies();
|
||||
|
||||
// Ensure auth directory exists
|
||||
await fs.mkdir(path.dirname(USER_AUTH_FILE), { recursive: true });
|
||||
|
||||
// Save cookies to file
|
||||
await fs.writeFile(USER_AUTH_FILE, JSON.stringify({ cookies }, null, 2));
|
||||
console.log(`[AUTH SETUP] Saved user session to: ${USER_AUTH_FILE}`);
|
||||
});
|
||||
|
||||
setup('Authenticate admin user', async ({ request }) => {
|
||||
console.log(`[AUTH SETUP] Creating admin user session at: ${API_BASE_URL}`);
|
||||
|
||||
// Use seeded admin credentials
|
||||
const adminEmail = 'demo.admin@example.com';
|
||||
const adminPassword = 'Demo1234!';
|
||||
|
||||
// Login as admin
|
||||
const loginResponse = await request.post(`${API_BASE_URL}/auth/login`, {
|
||||
data: {
|
||||
email: adminEmail,
|
||||
password: adminPassword
|
||||
}
|
||||
});
|
||||
|
||||
if (!loginResponse.ok()) {
|
||||
throw new Error(`Admin login failed: ${loginResponse.status()}`);
|
||||
}
|
||||
|
||||
console.log('[AUTH SETUP] Admin user session established');
|
||||
|
||||
// Get cookies and save to auth file
|
||||
const context = request.context();
|
||||
const cookies = context.cookies();
|
||||
|
||||
// Ensure auth directory exists
|
||||
await fs.mkdir(path.dirname(ADMIN_AUTH_FILE), { recursive: true });
|
||||
|
||||
// Save cookies to file
|
||||
await fs.writeFile(ADMIN_AUTH_FILE, JSON.stringify({ cookies }, null, 2));
|
||||
console.log(`[AUTH SETUP] Saved admin session to: ${ADMIN_AUTH_FILE}`);
|
||||
});
|
||||
@@ -12,7 +12,7 @@
|
||||
* npm run test:e2e:website (which runs everything in Docker)
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { test, expect, request } from '@playwright/test';
|
||||
import * as fs from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -42,33 +42,28 @@ interface TestReport {
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL ?? process.env.NEXT_PUBLIC_API_BASE_URL ?? 'http://localhost:3101';
|
||||
|
||||
// Auth file paths
|
||||
const USER_AUTH_FILE = path.join(__dirname, '.auth/user-session.json');
|
||||
const ADMIN_AUTH_FILE = path.join(__dirname, '.auth/admin-session.json');
|
||||
|
||||
test.describe('API Smoke Tests', () => {
|
||||
// Aggregate across the whole suite (used for final report).
|
||||
const allResults: EndpointTestResult[] = [];
|
||||
|
||||
let testResults: EndpointTestResult[] = [];
|
||||
|
||||
test.beforeAll(async ({ request }) => {
|
||||
test.beforeAll(async () => {
|
||||
console.log(`[API SMOKE] Testing API at: ${API_BASE_URL}`);
|
||||
|
||||
// Wait for API to be ready
|
||||
const maxAttempts = 30;
|
||||
let apiReady = false;
|
||||
// Verify auth files exist
|
||||
const userAuthExists = await fs.access(USER_AUTH_FILE).then(() => true).catch(() => false);
|
||||
const adminAuthExists = await fs.access(ADMIN_AUTH_FILE).then(() => true).catch(() => false);
|
||||
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
try {
|
||||
const response = await request.get(`${API_BASE_URL}/health`);
|
||||
if (response.ok()) {
|
||||
apiReady = true;
|
||||
console.log(`[API SMOKE] API is ready after ${i + 1} attempts`);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue trying
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
if (!userAuthExists || !adminAuthExists) {
|
||||
throw new Error('Auth files not found. Run global setup first.');
|
||||
}
|
||||
|
||||
if (!apiReady) {
|
||||
throw new Error('API failed to become ready');
|
||||
}
|
||||
console.log('[API SMOKE] Auth files verified');
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
@@ -76,6 +71,7 @@ test.describe('API Smoke Tests', () => {
|
||||
});
|
||||
|
||||
test('all public GET endpoints respond correctly', async ({ request }) => {
|
||||
testResults = [];
|
||||
const endpoints = [
|
||||
// Race endpoints
|
||||
{ method: 'GET' as const, path: '/races/all', name: 'Get all races' },
|
||||
@@ -85,7 +81,7 @@ test.describe('API Smoke Tests', () => {
|
||||
{ method: 'GET' as const, path: '/races/reference/penalty-types', name: 'Get penalty types reference' },
|
||||
|
||||
// League endpoints
|
||||
{ method: 'GET' as const, path: '/leagues/all', name: 'Get all leagues' },
|
||||
{ method: 'GET' as const, path: '/leagues/all-with-capacity', name: 'Get all leagues' },
|
||||
{ method: 'GET' as const, path: '/leagues/available', name: 'Get available leagues' },
|
||||
|
||||
// Team endpoints
|
||||
@@ -95,52 +91,53 @@ test.describe('API Smoke Tests', () => {
|
||||
{ method: 'GET' as const, path: '/drivers/leaderboard', name: 'Get driver leaderboard' },
|
||||
{ method: 'GET' as const, path: '/drivers/total-drivers', name: 'Get total drivers count' },
|
||||
|
||||
// Dashboard endpoints (may require auth, but should handle gracefully)
|
||||
{ method: 'GET' as const, path: '/dashboard/overview', name: 'Get dashboard overview' },
|
||||
|
||||
// Analytics endpoints
|
||||
{ method: 'GET' as const, path: '/analytics/metrics', name: 'Get analytics metrics' },
|
||||
|
||||
// Sponsor endpoints
|
||||
{ method: 'GET' as const, path: '/sponsors/pricing', name: 'Get sponsorship pricing' },
|
||||
|
||||
// Payments endpoints
|
||||
{ method: 'GET' as const, path: '/payments/wallet', name: 'Get wallet (may require auth)' },
|
||||
|
||||
// Notifications endpoints
|
||||
{ method: 'GET' as const, path: '/notifications/unread', name: 'Get unread notifications' },
|
||||
|
||||
// Features endpoint
|
||||
{ method: 'GET' as const, path: '/features', name: 'Get feature flags' },
|
||||
|
||||
// Hello endpoint
|
||||
{ method: 'GET' as const, path: '/hello', name: 'Hello World' },
|
||||
|
||||
// Media endpoints
|
||||
{ method: 'GET' as const, path: '/media/avatar/non-existent-id', name: 'Get non-existent avatar' },
|
||||
|
||||
// Driver by ID
|
||||
{ method: 'GET' as const, path: '/drivers/non-existent-id', name: 'Get non-existent driver' },
|
||||
];
|
||||
|
||||
console.log(`\n[API SMOKE] Testing ${endpoints.length} public endpoints...`);
|
||||
console.log(`\n[API SMOKE] Testing ${endpoints.length} public GET endpoints...`);
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
await testEndpoint(request, endpoint);
|
||||
}
|
||||
|
||||
// Check for presenter errors
|
||||
const presenterErrors = testResults.filter(r => r.hasPresenterError);
|
||||
if (presenterErrors.length > 0) {
|
||||
console.log('\n❌ PRESENTER ERRORS FOUND:');
|
||||
presenterErrors.forEach(r => {
|
||||
console.log(` ${r.method} ${r.endpoint} - ${r.error}`);
|
||||
// Check for failures
|
||||
const failures = testResults.filter(r => !r.success);
|
||||
if (failures.length > 0) {
|
||||
console.log('\n❌ FAILURES FOUND:');
|
||||
failures.forEach(r => {
|
||||
console.log(` ${r.method} ${r.endpoint} - ${r.status} - ${r.error || r.response}`);
|
||||
});
|
||||
}
|
||||
|
||||
// Assert no presenter errors
|
||||
expect(presenterErrors.length).toBe(0);
|
||||
// Assert all endpoints succeeded
|
||||
expect(failures.length).toBe(0);
|
||||
});
|
||||
|
||||
test('POST endpoints handle requests gracefully', async ({ request }) => {
|
||||
testResults = [];
|
||||
const endpoints = [
|
||||
{ method: 'POST' as const, path: '/auth/login', name: 'Login', body: { email: 'test@example.com', password: 'test' } },
|
||||
{ method: 'POST' as const, path: '/auth/signup', name: 'Signup', body: { email: 'test@example.com', password: 'test', name: 'Test User' } },
|
||||
{ method: 'POST' as const, path: '/races/123/register', name: 'Register for race', body: { driverId: 'test-driver' } },
|
||||
{ method: 'POST' as const, path: '/races/protests/file', name: 'File protest', body: { raceId: '123', driverId: '456', description: 'Test protest' } },
|
||||
{ method: 'POST' as const, path: '/leagues/123/join', name: 'Join league', body: { driverId: 'test-driver' } },
|
||||
{ method: 'POST' as const, path: '/teams/123/join', name: 'Join team', body: { driverId: 'test-driver' } },
|
||||
// Auth endpoints (no auth required)
|
||||
{ method: 'POST' as const, path: '/auth/signup', name: 'Signup', requiresAuth: false, body: { email: `test-smoke-${Date.now()}@example.com`, password: 'Password123', displayName: 'Smoke Test', username: 'smoketest' } },
|
||||
{ method: 'POST' as const, path: '/auth/login', name: 'Login', requiresAuth: false, body: { email: 'demo.driver@example.com', password: 'Demo1234!' } },
|
||||
|
||||
// Protected endpoints (require auth)
|
||||
{ method: 'POST' as const, path: '/races/123/register', name: 'Register for race', requiresAuth: true, body: { driverId: 'test-driver' } },
|
||||
{ method: 'POST' as const, path: '/races/protests/file', name: 'File protest', requiresAuth: true, body: { raceId: '123', protestingDriverId: 'driver-1', accusedDriverId: 'driver-2', incident: { lap: 1, description: 'Test protest' } } },
|
||||
{ method: 'POST' as const, path: '/leagues/league-1/join', name: 'Join league', requiresAuth: true, body: {} },
|
||||
{ method: 'POST' as const, path: '/teams/123/join', name: 'Join team', requiresAuth: true, body: { teamId: '123' } },
|
||||
];
|
||||
|
||||
console.log(`\n[API SMOKE] Testing ${endpoints.length} POST endpoints...`);
|
||||
@@ -155,13 +152,14 @@ test.describe('API Smoke Tests', () => {
|
||||
});
|
||||
|
||||
test('parameterized endpoints handle missing IDs gracefully', async ({ request }) => {
|
||||
testResults = [];
|
||||
const endpoints = [
|
||||
{ method: 'GET' as const, path: '/races/non-existent-id', name: 'Get non-existent race' },
|
||||
{ method: 'GET' as const, path: '/races/non-existent-id/results', name: 'Get non-existent race results' },
|
||||
{ method: 'GET' as const, path: '/leagues/non-existent-id', name: 'Get non-existent league' },
|
||||
{ method: 'GET' as const, path: '/teams/non-existent-id', name: 'Get non-existent team' },
|
||||
{ method: 'GET' as const, path: '/drivers/non-existent-id', name: 'Get non-existent driver' },
|
||||
{ method: 'GET' as const, path: '/media/avatar/non-existent-id', name: 'Get non-existent avatar' },
|
||||
{ method: 'GET' as const, path: '/races/non-existent-id', name: 'Get non-existent race', requiresAuth: false },
|
||||
{ method: 'GET' as const, path: '/races/non-existent-id/results', name: 'Get non-existent race results', requiresAuth: false },
|
||||
{ method: 'GET' as const, path: '/leagues/non-existent-id', name: 'Get non-existent league', requiresAuth: false },
|
||||
{ method: 'GET' as const, path: '/teams/non-existent-id', name: 'Get non-existent team', requiresAuth: false },
|
||||
{ method: 'GET' as const, path: '/drivers/non-existent-id', name: 'Get non-existent driver', requiresAuth: false },
|
||||
{ method: 'GET' as const, path: '/media/avatar/non-existent-id', name: 'Get non-existent avatar', requiresAuth: false },
|
||||
];
|
||||
|
||||
console.log(`\n[API SMOKE] Testing ${endpoints.length} parameterized endpoints with invalid IDs...`);
|
||||
@@ -170,14 +168,88 @@ test.describe('API Smoke Tests', () => {
|
||||
await testEndpoint(request, endpoint);
|
||||
}
|
||||
|
||||
// Check for failures
|
||||
const failures = testResults.filter(r => !r.success);
|
||||
expect(failures.length).toBe(0);
|
||||
});
|
||||
|
||||
test('authenticated endpoints respond correctly', async () => {
|
||||
testResults = [];
|
||||
|
||||
// Load user auth cookies
|
||||
const userAuthData = await fs.readFile(USER_AUTH_FILE, 'utf-8');
|
||||
const userCookies = JSON.parse(userAuthData).cookies;
|
||||
|
||||
// Create new API request context with user auth
|
||||
const userContext = await request.newContext({
|
||||
storageState: {
|
||||
cookies: userCookies,
|
||||
origins: [{ origin: API_BASE_URL, localStorage: [] }]
|
||||
}
|
||||
});
|
||||
|
||||
const endpoints = [
|
||||
// Dashboard
|
||||
{ method: 'GET' as const, path: '/dashboard/overview', name: 'Dashboard Overview' },
|
||||
|
||||
// Analytics
|
||||
{ method: 'GET' as const, path: '/analytics/metrics', name: 'Analytics Metrics' },
|
||||
|
||||
// Notifications
|
||||
{ method: 'GET' as const, path: '/notifications/unread', name: 'Unread Notifications' },
|
||||
];
|
||||
|
||||
console.log(`\n[API SMOKE] Testing ${endpoints.length} authenticated endpoints...`);
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
await testEndpoint(userContext, endpoint);
|
||||
}
|
||||
|
||||
// Check for presenter errors
|
||||
const presenterErrors = testResults.filter(r => r.hasPresenterError);
|
||||
expect(presenterErrors.length).toBe(0);
|
||||
|
||||
// Clean up
|
||||
await userContext.dispose();
|
||||
});
|
||||
|
||||
test('admin endpoints respond correctly', async () => {
|
||||
testResults = [];
|
||||
|
||||
// Load admin auth cookies
|
||||
const adminAuthData = await fs.readFile(ADMIN_AUTH_FILE, 'utf-8');
|
||||
const adminCookies = JSON.parse(adminAuthData).cookies;
|
||||
|
||||
// Create new API request context with admin auth
|
||||
const adminContext = await request.newContext({
|
||||
storageState: {
|
||||
cookies: adminCookies,
|
||||
origins: [{ origin: API_BASE_URL, localStorage: [] }]
|
||||
}
|
||||
});
|
||||
|
||||
const endpoints = [
|
||||
// Payments (requires admin capability)
|
||||
{ method: 'GET' as const, path: '/payments/wallets?leagueId=league-1', name: 'Wallets' },
|
||||
];
|
||||
|
||||
console.log(`\n[API SMOKE] Testing ${endpoints.length} admin endpoints...`);
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
await testEndpoint(adminContext, endpoint);
|
||||
}
|
||||
|
||||
// Check for presenter errors
|
||||
const presenterErrors = testResults.filter(r => r.hasPresenterError);
|
||||
expect(presenterErrors.length).toBe(0);
|
||||
|
||||
// Clean up
|
||||
await adminContext.dispose();
|
||||
});
|
||||
|
||||
async function testEndpoint(
|
||||
request: import('@playwright/test').APIRequestContext,
|
||||
endpoint: { method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; path: string; name?: string; body?: unknown }
|
||||
endpoint: { method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'; path: string; name?: string; body?: unknown; requiresAuth?: boolean }
|
||||
): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
const fullUrl = `${API_BASE_URL}${endpoint.path}`;
|
||||
@@ -186,22 +258,26 @@ test.describe('API Smoke Tests', () => {
|
||||
|
||||
try {
|
||||
let response;
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
// Playwright's request context handles cookies automatically
|
||||
// No need to set Authorization header for cookie-based auth
|
||||
|
||||
switch (endpoint.method) {
|
||||
case 'GET':
|
||||
response = await request.get(fullUrl);
|
||||
response = await request.get(fullUrl, { headers });
|
||||
break;
|
||||
case 'POST':
|
||||
response = await request.post(fullUrl, { data: endpoint.body || {} });
|
||||
response = await request.post(fullUrl, { data: endpoint.body || {}, headers });
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await request.put(fullUrl, { data: endpoint.body || {} });
|
||||
response = await request.put(fullUrl, { data: endpoint.body || {}, headers });
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await request.delete(fullUrl);
|
||||
response = await request.delete(fullUrl, { headers });
|
||||
break;
|
||||
case 'PATCH':
|
||||
response = await request.patch(fullUrl, { data: endpoint.body || {} });
|
||||
response = await request.patch(fullUrl, { data: endpoint.body || {}, headers });
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -211,13 +287,15 @@ test.describe('API Smoke Tests', () => {
|
||||
const bodyText = await response.text().catch(() => '');
|
||||
|
||||
// Check for presenter errors
|
||||
const hasPresenterError =
|
||||
const hasPresenterError =
|
||||
bodyText.includes('Presenter not presented') ||
|
||||
bodyText.includes('presenter not presented') ||
|
||||
(body && body.message && body.message.includes('Presenter not presented')) ||
|
||||
(body && body.error && body.error.includes('Presenter not presented'));
|
||||
|
||||
const success = status < 400 && !hasPresenterError;
|
||||
// Success is 200-299 status, or 404 for non-existent resources, and no presenter error
|
||||
const isNotFound = status === 404;
|
||||
const success = (status >= 200 && status < 300 || isNotFound) && !hasPresenterError;
|
||||
|
||||
const result: EndpointTestResult = {
|
||||
endpoint: endpoint.path,
|
||||
@@ -234,6 +312,7 @@ test.describe('API Smoke Tests', () => {
|
||||
}
|
||||
|
||||
testResults.push(result);
|
||||
allResults.push(result);
|
||||
|
||||
if (hasPresenterError) {
|
||||
console.log(` ❌ PRESENTER ERROR: ${status} - ${body?.message || bodyText.substring(0, 100)}`);
|
||||
@@ -266,23 +345,24 @@ test.describe('API Smoke Tests', () => {
|
||||
}
|
||||
|
||||
testResults.push(result);
|
||||
allResults.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
async function generateReport(): Promise<void> {
|
||||
const summary = {
|
||||
total: testResults.length,
|
||||
success: testResults.filter(r => r.success).length,
|
||||
failed: testResults.filter(r => !r.success).length,
|
||||
presenterErrors: testResults.filter(r => r.hasPresenterError).length,
|
||||
avgResponseTime: testResults.reduce((sum, r) => sum + r.responseTime, 0) / testResults.length || 0,
|
||||
total: allResults.length,
|
||||
success: allResults.filter(r => r.success).length,
|
||||
failed: allResults.filter(r => !r.success).length,
|
||||
presenterErrors: allResults.filter(r => r.hasPresenterError).length,
|
||||
avgResponseTime: allResults.reduce((sum, r) => sum + r.responseTime, 0) / allResults.length || 0,
|
||||
};
|
||||
|
||||
const report: TestReport = {
|
||||
timestamp: new Date().toISOString(),
|
||||
summary,
|
||||
results: testResults,
|
||||
failures: testResults.filter(r => !r.success),
|
||||
results: allResults,
|
||||
failures: allResults.filter(r => !r.success),
|
||||
};
|
||||
|
||||
// Write JSON report
|
||||
@@ -304,7 +384,7 @@ test.describe('API Smoke Tests', () => {
|
||||
|
||||
if (summary.presenterErrors > 0) {
|
||||
md += `## Presenter Errors\n\n`;
|
||||
const presenterFailures = testResults.filter(r => r.hasPresenterError);
|
||||
const presenterFailures = allResults.filter(r => r.hasPresenterError);
|
||||
presenterFailures.forEach((r, i) => {
|
||||
md += `${i + 1}. **${r.method} ${r.endpoint}**\n`;
|
||||
md += ` - Status: ${r.status}\n`;
|
||||
@@ -314,7 +394,7 @@ test.describe('API Smoke Tests', () => {
|
||||
|
||||
if (summary.failed > 0 && summary.presenterErrors < summary.failed) {
|
||||
md += `## Other Failures\n\n`;
|
||||
const otherFailures = testResults.filter(r => !r.success && !r.hasPresenterError);
|
||||
const otherFailures = allResults.filter(r => !r.success && !r.hasPresenterError);
|
||||
otherFailures.forEach((r, i) => {
|
||||
md += `${i + 1}. **${r.method} ${r.endpoint}**\n`;
|
||||
md += ` - Status: ${r.status}\n`;
|
||||
|
||||
Reference in New Issue
Block a user