Skip to content

Архитектура

SaaS E2E использует Playwright с тремя проектами, каждый из которых отвечает за свой тип тестирования.

Общая архитектура

Playwright проекты

Три проекта выполняются последовательно/параллельно:

ПроектТипЗависимостиОписание
setupBrowserВыполняет browser login, сохраняет storageState
apiHTTP-onlyAPI тесты без браузера через APIRequestContext
clientBrowsersetupBrowser тесты с сохраненной сессией

Setup проект

Выполняет аутентификацию в браузере:

  1. Открывает /auth/sign-in в Chromium
  2. Заполняет email и password из environment
  3. Нажимает кнопку входа
  4. Ожидает навигацию (редирект с /auth/)
  5. Сохраняет storageState в playwright/.auth/user.json

Все client-тесты автоматически используют сохраненную сессию.

API проект

Тесты выполняются без браузера. Каждый тест получает authenticatedRequest или adminRequest через кастомные фикстуры, которые:

  1. Выполняют POST /api/auth/sign-in для получения JWT
  2. Создают APIRequestContext с заголовком Authorization: Bearer <token>
  3. Предоставляют готовый HTTP-клиент для тестов
typescript
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-элементы:

typescript
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 и навигируют на нужную страницу:

typescript
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 JWTPOST /api/auth/sign-inTEST_USER_EMAIL / TEST_USER_PASSWORDAPI тесты, browser setup
Admin JWTPOST /api/admin/auth/sign-inADMIN_USER_EMAIL / ADMIN_USER_PASSWORDAdmin тесты

Формат ответа API

Все API-эндпоинты возвращают ответы в формате:

json
{
    "success": true,
    "data": [...],
    "totalCount": 42,
    "page": 1
}

Хелпер expectPaginatedResponse валидирует эту структуру в каждом тесте.

Жизненный цикл тестовых данных

Тесты, создающие ресурсы (компании, ассистенты, папки), используют паттерн:

  1. beforeAll — аутентификация, получение companyId
  2. Тест созданияPOST запрос, сохранение createdId
  3. Тест чтенияGET по createdId
  4. Тест обновленияPATCH по createdId
  5. Тест удаленияDELETE по createdId
typescript
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);
});

Следующие шаги

SaaS E2E Test Documentation