From 0d2c25a8f7529698d0b8d6f063fb0f56f3df5717 Mon Sep 17 00:00:00 2001 From: kamask Date: Tue, 10 Nov 2026 07:55:01 +0300 Subject: [PATCH] docs(testing): move test harness descriptions to guide/tests Co-Authored-By: Claude Sonnet 5.5 --- README.md | 4 +- docs/README.md | 44 +++++++++++------- docs/guide/shared.md | 4 +- docs/guide/tests/api-harness.md | 11 +++++ docs/guide/tests/config-tests.md | 10 ++++ docs/guide/tests/e2e-harness.md | 12 +++++ docs/guide/tests/integration-harness.md | 14 ++++++ docs/testing.md | 62 +++++-------------------- 8 files changed, 90 insertions(+), 71 deletions(-) create mode 100644 docs/guide/tests/api-harness.md create mode 100644 docs/guide/tests/config-tests.md create mode 100644 docs/guide/tests/e2e-harness.md create mode 100644 docs/guide/tests/integration-harness.md diff --git a/README.md b/README.md index 4f09c00..2c05e59 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ pnpm start # приложение на http://localhost:4200/ | `pnpm lint` | ESLint | | `pnpm test` | unit-тесты | | `pnpm test:api` | API-тесты PocketBase: схема, правила доступа, изоляция пользователей | -| `pnpm test:integration` | репозитории приложения против тестового PocketBase | +| `pnpm test:integration` | репозитории приложения на тестовом PocketBase | | `pnpm test:lint` | тесты конфигов ESLint, dev-прокси и Playwright; первый шаг `test`, `test:api`, `test:integration`, `e2e` | | `pnpm e2e` | сценарии в браузере и проверка доступности (axe) | | `pnpm e2e:prod` | сценарии на production-сборке | @@ -88,7 +88,7 @@ docs/ документация разработчика - [architecture.md](docs/architecture.md) — общая схема, слои, DI, SSR и изоляция данных, nginx, принципы UI; - [data-model.md](docs/data-model.md) — коллекции PocketBase, поля и правила доступа; -- [testing.md](docs/testing.md) — уровни тестов и тестовый PocketBase; +- [testing.md](docs/testing.md) — уровни тестов и тестовый PocketBase; устройство стендов — в [docs/guide/tests/](docs/guide/tests/); - `docs/guide/` — руководство по каждой части приложения: где лежит код, как им пользоваться и как его расширить. Пополняется вместе с кодом. ## Production diff --git a/docs/README.md b/docs/README.md index 92c9f6a..11598b5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -4,28 +4,32 @@ ## Основы -| Документ | О чём | -| --- | --- | -| [architecture.md](architecture.md) | общая схема, слои внутри фичи, DI, SSR и изоляция данных, nginx, UI-принципы | -| [data-model.md](data-model.md) | коллекции PocketBase, поля, правила доступа, публичный маршрут | -| [testing.md](testing.md) | уровни тестов, тестовый PocketBase, контрактные тесты | +| Документ | О чём | +| ---------------------------------- | ------------------------------------------------------------------------------------------ | +| [architecture.md](architecture.md) | общая схема, слои внутри фичи, DI, SSR и изоляция данных, nginx, UI-принципы | +| [data-model.md](data-model.md) | коллекции PocketBase, поля, правила доступа, публичный маршрут | +| [testing.md](testing.md) | уровни тестов, тестовый PocketBase, контрактные тесты, правила (стенды — в `guide/tests/`) | ## Руководство (`guide/`) Страницы появляются вместе с соответствующим кодом. -| Страница | О чём | -| --- | --- | -| `guide/project-structure.md` | карта репозитория: `src/app`, `pocketbase/`, `tests/`, `scripts/`; где что искать | -| `guide/adding-a-feature.md` | пошаговый пример добавления новой сущности от миграции до экрана (на примере меток) | -| [guide/shared.md](guide/shared.md) | общая инфраструктура: клиент PocketBase, доменные ошибки, общие UI-компоненты | -| `guide/auth.md` | вход, регистрация, сессия, cookie, guard'ы, работа с текущим пользователем | -| `guide/tags.md` | метки: репозиторий, диалог управления, компонент выбора | -| `guide/clients.md` | клиенты: телефоны, поиск, форма, точки расширения карточки | -| `guide/orders.md` | заявки: статусы, фильтры, форма, вложения | -| `guide/share.md` | публичная ссылка и публичная страница | -| `guide/ssr.md` | как устроен серверный рендер: cookie, TransferState, что можно и нельзя на сервере | -| `guide/ui.md` | дизайн-токены, темы, общие компоненты, правила доступности | +| Страница | О чём | +| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | +| `guide/project-structure.md` | карта репозитория: `src/app`, `pocketbase/`, `tests/`, `scripts/`; где что искать | +| `guide/adding-a-feature.md` | пошаговый пример добавления новой сущности от миграции до экрана (на примере меток) | +| [guide/shared.md](guide/shared.md) | общая инфраструктура: клиент PocketBase, доменные ошибки, общие UI-компоненты | +| [guide/tests/api-harness.md](guide/tests/api-harness.md) | стенд API-тестов: Vitest в Node, `global-setup`, проверка типов | +| [guide/tests/integration-harness.md](guide/tests/integration-harness.md) | стенд интеграционных тестов: цель `test-integration` builder'а Angular | +| [guide/tests/e2e-harness.md](guide/tests/e2e-harness.md) | стенд e2e: Playwright, два `webServer`, фикстуры, axe | +| [guide/tests/config-tests.md](guide/tests/config-tests.md) | тесты конфигов: ESLint-границы, dev-прокси, Playwright | +| `guide/auth.md` | вход, регистрация, сессия, cookie, guard'ы, работа с текущим пользователем | +| `guide/tags.md` | метки: репозиторий, диалог управления, компонент выбора | +| `guide/clients.md` | клиенты: телефоны, поиск, форма, точки расширения карточки | +| `guide/orders.md` | заявки: статусы, фильтры, форма, вложения | +| `guide/share.md` | публичная ссылка и публичная страница | +| `guide/ssr.md` | как устроен серверный рендер: cookie, TransferState, что можно и нельзя на сервере | +| `guide/ui.md` | дизайн-токены, темы, общие компоненты, правила доступности | ## Как писать страницу руководства @@ -37,21 +41,27 @@ Одно-два предложения: что делает фича и для чего. ## Где лежит + Дерево папок фичи с пояснением каждой важной папки и файла. ## Публичный API + Что экспортирует `index.ts`: абстрактные классы, типы, компоненты (селектор, входы, выходы), функции `provideXxx()`, токены расширения. По каждому — одна строка назначения. ## Как пользоваться + Короткие примеры кода для типовых задач: получить данные, встроить компонент, обработать ошибку. ## Как расширить или заменить + Как подменить реализацию через DI, как подключиться к точке расширения, что поменять при добавлении поля. ## Тесты + Где лежат тесты фичи, какой контракт у репозитория, какие фейки доступны. ## Подводные камни + Неочевидное: ограничения SSR, правила доступа, особенности PocketBase. ``` diff --git a/docs/guide/shared.md b/docs/guide/shared.md index 8ca1767..ed23171 100644 --- a/docs/guide/shared.md +++ b/docs/guide/shared.md @@ -46,8 +46,8 @@ async list(): Promise { ## Тесты - `to-domain-error.spec.ts`, `pocketbase-client.spec.ts` — unit. -- `pocketbase-client.integration.spec.ts` — клиент из DI против тестового PocketBase, изоляция двух пользователей и гостя. -- `tests/lint/eslint-rules.spec.ts` — границы ESLint, см. [testing.md](../testing.md). +- `pocketbase-client.integration.spec.ts` — клиент из DI на тестовом PocketBase, изоляция двух пользователей и гостя. +- `tests/lint/eslint-rules.spec.ts` — границы ESLint, см. [config-tests.md](tests/config-tests.md). ## Подводные камни diff --git a/docs/guide/tests/api-harness.md b/docs/guide/tests/api-harness.md new file mode 100644 index 0000000..208b08c --- /dev/null +++ b/docs/guide/tests/api-harness.md @@ -0,0 +1,11 @@ +# Стенд API-тестов + +Тесты бэкенда (схема, миграции, правила доступа, изоляция пользователей) на чистом Vitest в Node, с запросами к временному PocketBase. Общие правила и устройство тестового PocketBase — в [testing.md](../../testing.md). + +`pnpm test:api` = `tsc -p tsconfig.tests.json && vitest run --config vitest.api.config.mts`: чистый Vitest в Node, без Angular. + +- Сначала проверяются типы тестового кода (`tsconfig.tests.json`: `tests/**`, `vitest.*.config.mts` и `proxy.conf.mjs`; `allowJs` нужен, чтобы `tests/lint/` импортировали `.mjs`-конфиг): сам Vitest типы не проверяет. +- `vitest.api.config.mts`: `include: tests/api/**/*.api.spec.ts`, `globalSetup: tests/support/global-setup.ts`, таймауты 20 с (запас на старт PocketBase внутри теста). +- `global-setup.ts` поднимает один PocketBase на прогон и передаёт тестам `pbUrl`, `pbDataDir`, `pbSuperuser` через `project.provide`; в тестах — `inject('pbUrl')` и т. д. из `vitest`. Типы этих значений — `tests/support/provided-context.d.ts`. +- Тесты самого стенда — `tests/api/harness.api.spec.ts`. Проверка уборки по `SIGINT`/`SIGTERM` запускает фикстуру `tests/support/fixtures/hold-pocketbase.ts` отдельным процессом `node`: она поднимает PocketBase, печатает `{ url, dataDir }` и ждёт сигнала. +- Поэтому `pocketbase-server.ts` выполняется чистым Node без сборки: в нём только импорты `node:*` и стираемый TypeScript (без `enum`, `namespace`, параметров-свойств). diff --git a/docs/guide/tests/config-tests.md b/docs/guide/tests/config-tests.md new file mode 100644 index 0000000..5531b48 --- /dev/null +++ b/docs/guide/tests/config-tests.md @@ -0,0 +1,10 @@ +# Тесты конфигов (ESLint, dev-прокси, Playwright) + +Проверки конфигурации, поломка которой иначе остаётся незамеченной. Общие правила — в [testing.md](../../testing.md). + +`pnpm test:lint` = `tsc -p tsconfig.tests.json && vitest run --config vitest.lint.config.mts`: чистый Vitest в Node, файлы `tests/lint/**/*.spec.ts`. + +- Проверяют, что ESLint держит границы, от которых зависит изоляция данных при SSR: `new PocketBase(` и импорт класса `PocketBase` (в том числе под другим именем и через `pocketbase/*`) только в фабрике `shared/pocketbase/pocketbase-client.ts` (и в тестовых хелперах `tests/`), импорт `pocketbase` и `pocketbase/*` запрещён в `domain/` и `ui/`. +- `tests/lint/pb-dev-proxy.spec.ts` проверяет `proxy.conf.mjs` (dev-прокси `ng serve`): правило `/pb/`, `target` из `PB_URL` с тем же значением по умолчанию, что у серверного провайдера (в том числе при пустой переменной), и срезание префикса `/pb`. +- Способ ESLint-тестов: `ESLint#lintText(code, { filePath })` на коротких фрагментах кода, проверяется `ruleId` сообщений. Конфиг ESLint ломается молча (опечатка в glob, слишком широкое исключение), и `pnpm lint` при этом остаётся зелёным; тест это ловит. +- Запускаются первым шагом в `pnpm test`, `pnpm test:api`, `pnpm test:integration` и `pnpm e2e`, чтобы поломка правила обнаружилась при любом прогоне. ESLint — Node-библиотека, в бандл builder'а Angular её не подключить, поэтому это отдельный набор. diff --git a/docs/guide/tests/e2e-harness.md b/docs/guide/tests/e2e-harness.md new file mode 100644 index 0000000..cae487f --- /dev/null +++ b/docs/guide/tests/e2e-harness.md @@ -0,0 +1,12 @@ +# Стенд e2e + +Сценарии в браузере (Playwright) и проверка доступности (axe) на `ng serve` с тестовым PocketBase. Общие правила и устройство тестового PocketBase — в [testing.md](../../testing.md). + +`pnpm e2e` = `pnpm test:lint && playwright test`. Перед первым запуском: `pnpm exec playwright install chromium`. + +- `playwright.config.ts`: тесты `tests/e2e/**/*.e2e.ts`, один проект `chromium`, при падении — скриншот и трейс в `test-results/` (в `.gitignore`). +- Два `webServer`: тестовый PocketBase (`node tests/support/serve-test-pocketbase.ts 8190` — тот же `startPocketBase()`, только с фиксированным портом) и `ng serve --port 4300` с `PB_URL` тестового PocketBase: на него смотрят и SSR, и dev-прокси `/pb`. Адреса и порты — `tests/e2e/urls.ts`; они не пересекаются с `pnpm pb:serve` (8090) и `pnpm start` (4200), а `pb_data` разработчика не используется. +- Серверы останавливаются по `SIGTERM` (`gracefulShutdown`): по умолчанию Playwright шлёт `SIGKILL`, и стенд не успел бы удалить временную папку PocketBase. +- Без `CI` уже запущенные серверы переиспользуются, повторов нет; при `CI=true` серверы всегда свои, повтор упавшего теста один. Это проверяет `tests/lint/playwright-config.spec.ts` (см. [config-tests.md](config-tests.md)). +- Фикстуры (`tests/e2e/fixtures.ts`) не зависят от UI входа и суперпользователя (при переиспользовании PocketBase его пароль неизвестен): `user` регистрируется через открытую регистрацию `users`, `api` — SDK, вошедший как `user`, `signedInPage` — страница в **отдельном** контексте браузера с cookie `pb_auth` в формате `exportToCookie`, поэтому обычная `page` остаётся гостевой. +- `expectNoA11yViolations(page)` (`tests/e2e/a11y.ts`): axe со всеми правилами (WCAG и best-practice, как требует CLAUDE.md), падает со списком `правило: описание (элементов: N)`. diff --git a/docs/guide/tests/integration-harness.md b/docs/guide/tests/integration-harness.md new file mode 100644 index 0000000..cf55c65 --- /dev/null +++ b/docs/guide/tests/integration-harness.md @@ -0,0 +1,14 @@ +# Стенд интеграционных тестов + +PocketBase-реализации репозиториев и сервисов на настоящем PocketBase, внутри Angular-окружения (`TestBed`). Общие правила и устройство тестового PocketBase — в [testing.md](../../testing.md). + +`pnpm test:integration` = `ng run pchelpcrm:test-integration`: отдельная цель builder'а `@angular/build:unit-test` в `angular.json`. + +- `include: ["**/*.integration.spec.ts"]` в цели `test-integration`; у цели `test` (`pnpm test`) — `exclude` тех же файлов, поэтому unit-тесты не требуют PocketBase. +- `watch: false` в цели: прогон завершается сам; слежение за файлами — разово `pnpm test:integration --watch`. +- `runnerConfig: "vitest.integration.config.mts"` добавляет `globalSetup: tests/support/global-setup.ts` — тот же файл, что у API-тестов, он вызывает `startPocketBase()`. Список файлов в этом конфиге не задаётся: builder его игнорирует. +- В тестах работают `TestBed` и `inject()` Angular, URL — `inject('pbUrl')` из `vitest`, хелперы импортируются из `tests/support/clients.ts`. + +Почему цель builder'а, а не отдельный `vitest.integration.config.ts` с плагином: спеки собираются тем же компилятором Angular и с той же инициализацией TestBed, что и в `pnpm test`, без своего плагина компиляции и его настройки. + +`tests/support/clients.ts` компилируется в двух программах — `tsconfig.tests.json` и `tsconfig.spec.json`. Типы Node подключены в обеих (спеки выполняются Vitest в Node, а код в `src/` вроде серверного провайдера `PB_URL` читает `process.env`), а `tests/support/provided-context.d.ts` с типами `inject('pbUrl')` перечислен в `include` обоих tsconfig: `references` в `tsconfig.json` программы не объединяют. diff --git a/docs/testing.md b/docs/testing.md index e3d8908..fdf20f0 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -4,58 +4,20 @@ ## Уровни тестов -| Уровень | Что проверяет | Где лежит | Команда | -| --------------------- | -------------------------------------------------------------------------- | --------------------------------------------- | ----------------------- | -| **API** (e2e бэкенда) | схема, миграции, правила доступа, изоляция пользователей, хуки PocketBase | `tests/api/**/*.api.spec.ts` | `pnpm test:api` | -| **Unit** | доменные функции, компоненты и сервисы с фейками вместо PocketBase | рядом с кодом, `src/**/*.spec.ts` | `pnpm test` | -| **Integration** | PocketBase-реализации репозиториев и сервисов против настоящего PocketBase | рядом с кодом, `src/**/*.integration.spec.ts` | `pnpm test:integration` | -| **E2E** | пользовательские сценарии в браузере, SSR, доступность (axe) | `tests/e2e/**/*.e2e.ts` | `pnpm e2e` | -| **E2E prod** | сценарии на production-сборке, включая изоляцию данных при SSR | `tests/e2e/` (проект `ssr-prod`) | `pnpm e2e:prod` | +| Уровень | Что проверяет | Где лежит | Команда | +| --------------------- | ------------------------------------------------------------------------- | --------------------------------------------- | ----------------------- | +| **API** (e2e бэкенда) | схема, миграции, правила доступа, изоляция пользователей, хуки PocketBase | `tests/api/**/*.api.spec.ts` | `pnpm test:api` | +| **Unit** | доменные функции, компоненты и сервисы с фейками вместо PocketBase | рядом с кодом, `src/**/*.spec.ts` | `pnpm test` | +| **Integration** | PocketBase-реализации репозиториев и сервисов на настоящем PocketBase | рядом с кодом, `src/**/*.integration.spec.ts` | `pnpm test:integration` | +| **E2E** | пользовательские сценарии в браузере, SSR, доступность (axe) | `tests/e2e/**/*.e2e.ts` | `pnpm e2e` | +| **E2E prod** | сценарии на production-сборке, включая изоляцию данных при SSR | `tests/e2e/` (проект `ssr-prod`) | `pnpm e2e:prod` | -Команды появляются по мере того, как настраиваются соответствующие стенды. +Команды появляются по мере того, как настраиваются соответствующие стенды. Устройство каждого стенда описано в `guide/tests/`: -## Стенд API-тестов - -`pnpm test:api` = `tsc -p tsconfig.tests.json && vitest run --config vitest.api.config.mts`: чистый Vitest в Node, без Angular. - -- Сначала проверяются типы тестового кода (`tsconfig.tests.json`: `tests/**`, `vitest.*.config.mts` и `proxy.conf.mjs`; `allowJs` нужен, чтобы `tests/lint/` импортировали `.mjs`-конфиг): сам Vitest типы не проверяет. -- `vitest.api.config.mts`: `include: tests/api/**/*.api.spec.ts`, `globalSetup: tests/support/global-setup.ts`, таймауты 20 с (запас на старт PocketBase внутри теста). -- `global-setup.ts` поднимает один PocketBase на прогон и передаёт тестам `pbUrl`, `pbDataDir`, `pbSuperuser` через `project.provide`; в тестах — `inject('pbUrl')` и т. д. из `vitest`. Типы этих значений — `tests/support/provided-context.d.ts`. -- Тесты самого стенда — `tests/api/harness.api.spec.ts`. Проверка уборки по `SIGINT`/`SIGTERM` запускает фикстуру `tests/support/fixtures/hold-pocketbase.ts` отдельным процессом `node`: она поднимает PocketBase, печатает `{ url, dataDir }` и ждёт сигнала. -- Поэтому `pocketbase-server.ts` выполняется чистым Node без сборки: в нём только импорты `node:*` и стираемый TypeScript (без `enum`, `namespace`, параметров-свойств). - -## Стенд интеграционных тестов - -`pnpm test:integration` = `ng run pchelpcrm:test-integration`: отдельная цель builder'а `@angular/build:unit-test` в `angular.json`. - -- `include: ["**/*.integration.spec.ts"]` в цели `test-integration`; у цели `test` (`pnpm test`) — `exclude` тех же файлов, поэтому unit-тесты не требуют PocketBase. -- `watch: false` в цели: прогон завершается сам; слежение за файлами — разово `pnpm test:integration --watch`. -- `runnerConfig: "vitest.integration.config.mts"` добавляет `globalSetup: tests/support/global-setup.ts` — тот же файл, что у API-тестов, он вызывает `startPocketBase()`. Список файлов в этом конфиге не задаётся: builder его игнорирует. -- В тестах работают `TestBed` и `inject()` Angular, URL — `inject('pbUrl')` из `vitest`, хелперы импортируются из `tests/support/clients.ts`. - -Почему цель builder'а, а не отдельный `vitest.integration.config.ts` с плагином: спеки собираются тем же компилятором Angular и с той же инициализацией TestBed, что и в `pnpm test`, без своего плагина компиляции и его настройки. - -`tests/support/clients.ts` компилируется в двух программах — `tsconfig.tests.json` и `tsconfig.spec.json`. Типы Node подключены в обеих (спеки выполняются Vitest в Node, а код в `src/` вроде серверного провайдера `PB_URL` читает `process.env`), а `tests/support/provided-context.d.ts` с типами `inject('pbUrl')` перечислен в `include` обоих tsconfig: `references` в `tsconfig.json` программы не объединяют. - -## Стенд e2e - -`pnpm e2e` = `pnpm test:lint && playwright test`. Перед первым запуском: `pnpm exec playwright install chromium`. - -- `playwright.config.ts`: тесты `tests/e2e/**/*.e2e.ts`, один проект `chromium`, при падении — скриншот и трейс в `test-results/` (в `.gitignore`). -- Два `webServer`: тестовый PocketBase (`node tests/support/serve-test-pocketbase.ts 8190` — тот же `startPocketBase()`, только с фиксированным портом) и `ng serve --port 4300` с `PB_URL` тестового PocketBase: на него смотрят и SSR, и dev-прокси `/pb`. Адреса и порты — `tests/e2e/urls.ts`; они не пересекаются с `pnpm pb:serve` (8090) и `pnpm start` (4200), а `pb_data` разработчика не используется. -- Серверы останавливаются по `SIGTERM` (`gracefulShutdown`): по умолчанию Playwright шлёт `SIGKILL`, и стенд не успел бы удалить временную папку PocketBase. -- Без `CI` уже запущенные серверы переиспользуются, повторов нет; при `CI=true` серверы всегда свои, повтор упавшего теста один. Это проверяет `tests/lint/playwright-config.spec.ts`. -- Фикстуры (`tests/e2e/fixtures.ts`) не зависят от UI входа и суперпользователя (при переиспользовании PocketBase его пароль неизвестен): `user` регистрируется через открытую регистрацию `users`, `api` — SDK, вошедший как `user`, `signedInPage` — страница в **отдельном** контексте браузера с cookie `pb_auth` в формате `exportToCookie`, поэтому обычная `page` остаётся гостевой. -- `expectNoA11yViolations(page)` (`tests/e2e/a11y.ts`): axe со всеми правилами (WCAG и best-practice, как требует CLAUDE.md), падает со списком `правило: описание (элементов: N)`. - -## Тесты конфигов (ESLint, dev-прокси, Playwright) - -`pnpm test:lint` = `tsc -p tsconfig.tests.json && vitest run --config vitest.lint.config.mts`: чистый Vitest в Node, файлы `tests/lint/**/*.spec.ts`. - -- Проверяют, что ESLint держит границы, от которых зависит изоляция данных при SSR: `new PocketBase(` и импорт класса `PocketBase` (в том числе под другим именем и через `pocketbase/*`) только в фабрике `shared/pocketbase/pocketbase-client.ts` (и в тестовых хелперах `tests/`), импорт `pocketbase` и `pocketbase/*` запрещён в `domain/` и `ui/`. -- `tests/lint/pb-dev-proxy.spec.ts` проверяет `proxy.conf.mjs` (dev-прокси `ng serve`): правило `/pb/`, `target` из `PB_URL` с тем же значением по умолчанию, что у серверного провайдера (в том числе при пустой переменной), и срезание префикса `/pb`. -- Способ ESLint-тестов: `ESLint#lintText(code, { filePath })` на коротких фрагментах кода, проверяется `ruleId` сообщений. Конфиг ESLint ломается молча (опечатка в glob, слишком широкое исключение), и `pnpm lint` при этом остаётся зелёным; тест это ловит. -- Запускаются первым шагом в `pnpm test`, `pnpm test:api`, `pnpm test:integration` и `pnpm e2e`, чтобы поломка правила обнаружилась при любом прогоне. ESLint — Node-библиотека, в бандл builder'а Angular её не подключить, поэтому это отдельный набор. +- [Стенд API-тестов](guide/tests/api-harness.md) +- [Стенд интеграционных тестов](guide/tests/integration-harness.md) +- [Стенд e2e](guide/tests/e2e-harness.md) +- [Тесты конфигов (ESLint, dev-прокси, Playwright)](guide/tests/config-tests.md) ## Контрактные тесты репозиториев