Архитектура
SaaS E2E использует Playwright с тремя проектами, каждый из которых отвечает за свой тип тестирования.
Общая архитектура
Playwright проекты
Три проекта выполняются последовательно/параллельно:
| Проект | Тип | Зависимости | Описание |
|---|---|---|---|
| setup | Browser | — | Выполняет browser login, сохраняет storageState |
| api | HTTP-only | — | API тесты без браузера через APIRequestContext |
| client | Browser | setup | Browser тесты с сохраненной сессией |
Setup проект
Выполняет аутентификацию в браузере:
- Открывает
/auth/sign-inв Chromium - Заполняет email и password из environment
- Нажимает кнопку входа
- Ожидает навигацию (редирект с
/auth/) - Сохраняет
storageStateвplaywright/.auth/user.json
Все client-тесты автоматически используют сохраненную сессию.
API проект
Тесты выполняются без браузера. Каждый тест получает authenticatedRequest или adminRequest через кастомные фикстуры, которые:
- Выполняют
POST /api/auth/sign-inдля получения JWT - Создают
APIRequestContextс заголовкомAuthorization: Bearer <token> - Предоставляют готовый HTTP-клиент для тестов
import { expect, test } from "../fixtures/auth.fixture";
test("GET /api/companies — returns paginated list", async ({ authenticatedRequest }) => {
const response = await authenticatedRequest.get("/api/companies");
expect(response.ok()).toBe(true);
const body = await response.json();
expect(body.success).toBe(true);
expect(Array.isArray(body.data)).toBe(true);
});Client проект
Browser тесты загружают страницы saas-client и проверяют UI-элементы:
import { expect, test } from "@playwright/test";
test("dashboard page loads after login", async ({ page }) => {
await page.goto("/");
await page.waitForURL("**/companies/**");
await expect(page).toHaveURL(/companies/);
});Маршрутизация saas-client
Все аутентифицированные страницы используют паттерн:
/companies/:companyId/<section>Client-тесты сначала переходят на /, ожидают редирект на /companies/:id/..., извлекают companyId из URL и навигируют на нужную страницу:
await page.goto("/");
await page.waitForURL("**/companies/**");
const currentUrl = page.url();
const companyIdMatch = /companies\/([^/]+)/.exec(currentUrl);
await page.goto(`/companies/${companyIdMatch![1]}/assistants`);Аутентификация
Два типа JWT
| Тип | Эндпоинт | Credentials | Использование |
|---|---|---|---|
| User JWT | POST /api/auth/sign-in | TEST_USER_EMAIL / TEST_USER_PASSWORD | API тесты, browser setup |
| Admin JWT | POST /api/admin/auth/sign-in | ADMIN_USER_EMAIL / ADMIN_USER_PASSWORD | Admin тесты |
Формат ответа API
Все API-эндпоинты возвращают ответы в формате:
{
"success": true,
"data": [...],
"totalCount": 42,
"page": 1
}Хелпер expectPaginatedResponse валидирует эту структуру в каждом тесте.
Жизненный цикл тестовых данных
Тесты, создающие ресурсы (компании, ассистенты, папки), используют паттерн:
- beforeAll — аутентификация, получение
companyId - Тест создания —
POSTзапрос, сохранениеcreatedId - Тест чтения —
GETпоcreatedId - Тест обновления —
PATCHпоcreatedId - Тест удаления —
DELETEпоcreatedId
let companyId: string;
let createdId: string;
test.beforeAll(async ({ authenticatedRequest }) => {
const response = await authenticatedRequest.get("/api/companies");
const body = await response.json();
companyId = body.data[0]?.id;
});
test("POST creates resource", async ({ authenticatedRequest }) => {
const response = await authenticatedRequest.post("/api/resource", {
data: { name: "E2E Test", companyId }
});
const body = await response.json();
createdId = body.data.id;
});
test("DELETE removes resource", async ({ authenticatedRequest }) => {
const response = await authenticatedRequest.delete(`/api/resource/${createdId}`);
expect(response.ok()).toBe(true);
});Следующие шаги
- Фикстуры и хелперы — подробнее о кастомных фикстурах
- Конфигурация — настройка Playwright
- API тесты — все API тест-кейсы
- Client тесты — все browser тест-кейсы