Some checks failed
- Global setup: единый логин всех пользователей перед тестами (без rate limit) - Playwright проекты: testMatch привязка файлов к проектам (убрал 5x дублирование) - LP порт: 3004 → 3050 (реальный порт из ecosystem.config) - Theme тесты: мок API через page.route() (предотвращение 401→logout) - Web UI тесты: защита response?.status() ?? 200 от undefined - getCachedTokens(): чтение токенов из файла вместо loginAs в каждом beforeAll Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
72 lines
2.2 KiB
TypeScript
72 lines
2.2 KiB
TypeScript
import type { APIRequestContext } from '@playwright/test';
|
|
import { readFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
const API_URL = process.env.E2E_API_URL || 'http://localhost:3000';
|
|
|
|
export interface AuthTokens {
|
|
accessToken: string;
|
|
refreshToken: string;
|
|
}
|
|
|
|
/** Read pre-cached tokens from global setup (preferred — no rate limit issues) */
|
|
export function getCachedTokens(userKey: string): AuthTokens {
|
|
const tokenPath = join(__dirname, '.auth-tokens.json');
|
|
const data = JSON.parse(readFileSync(tokenPath, 'utf-8'));
|
|
if (!data[userKey]) {
|
|
throw new Error(
|
|
`No cached tokens for user "${userKey}". Available: ${Object.keys(data).join(', ')}`,
|
|
);
|
|
}
|
|
return data[userKey];
|
|
}
|
|
|
|
/** Login via API (use only when you need a fresh token, e.g. for refresh testing) */
|
|
export async function loginAs(
|
|
request: APIRequestContext,
|
|
phone: string,
|
|
password: string,
|
|
): Promise<AuthTokens> {
|
|
const maxRetries = 8;
|
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
const response = await request.post(`${API_URL}/v1/auth/login`, {
|
|
data: { phone, password },
|
|
});
|
|
|
|
if (response.status() === 429) {
|
|
// Rate limited — wait and retry
|
|
const delay = (attempt + 1) * 12_000; // 12s, 24s, 36s...
|
|
await new Promise((r) => setTimeout(r, delay));
|
|
continue;
|
|
}
|
|
|
|
if (!response.ok()) {
|
|
throw new Error(`Login failed: ${response.status()} ${await response.text()}`);
|
|
}
|
|
|
|
const body = await response.json();
|
|
return {
|
|
accessToken: body.accessToken,
|
|
refreshToken: body.refreshToken,
|
|
};
|
|
}
|
|
|
|
throw new Error(`Login failed: rate limited after ${maxRetries} retries`);
|
|
}
|
|
|
|
export function authHeaders(tokens: AuthTokens) {
|
|
return {
|
|
Authorization: `Bearer ${tokens.accessToken}`,
|
|
};
|
|
}
|
|
|
|
// Test users (must match seed data from prisma/seed.ts)
|
|
export const TEST_USERS = {
|
|
trainer: { phone: '+70000000010', password: 'trainer123' },
|
|
coordinator: { phone: '+70000000003', password: 'admin123' },
|
|
manager: { phone: '+70000000003', password: 'admin123' }, // coordinator has stats access
|
|
receptionist: { phone: '+70000000004', password: 'admin123' },
|
|
clubAdmin: { phone: '+70000000002', password: 'admin123' },
|
|
superAdmin: { phone: '+70000000001', password: 'admin123' },
|
|
};
|