# Регрессионное тестирование прикладного решения Навык `/web-test` умеет не только разово выполнить сценарий в браузере, но и сопровождать прикладное решение полноценным набором автотестов: каждый тест — отдельный файл, с шагами, проверками, тегами, отчётом и видеозаписью падений. После каждой правки конфигурации модель прогоняет весь набор и показывает, что ожидаемо ведёт себя как раньше, а что сломалось. ``` правка конфигурации → загрузка → обновление → публикация → прогон тестов → отчёт ``` Это про прикладное решение в целом, не про разовую проверку одной формы. Для разовых сценариев («открой накладную, проверь сумму») по-прежнему удобнее интерактивный режим из [web-test-guide.md](web-test-guide.md). ## Предусловия - База опубликована через Apache (`/web-publish`). - Установлен Node.js 18+, зависимости подняты: `cd .claude/skills/web-test/scripts && npm install`. - ffmpeg — нужен только если хотите видеозапись прогона как доказательство падения. Без него падения фиксируются скриншотами. Установка описана в [web-test-recording-guide.md](web-test-recording-guide.md). ## Как это устроено Набор тестов живёт в каталоге `tests/` вашего проекта. Каждое прикладное решение — отдельная подпапка. Внутри подпапки: - `_hooks.mjs` — подготовка стенда (восстановление базы, публикация) и общая очистка после прогона. Необязателен. - `webtest.config.mjs` — адрес базы и набор пользователей (например, кладовщик и менеджер для процессов согласования). Необязателен — если в проекте один пользователь и один URL, можно обойтись без него. - Сами тесты — файлы `*.test.mjs`, сгруппированные по функциональным папкам. ``` tests/ моя-конфигурация/ _hooks.mjs webtest.config.mjs 01-вход/ 01-открытие-базы.test.mjs 02-контрагенты/ 01-создание.test.mjs 02-правка-телефона.test.mjs 03-поступление-товаров/ 01-оформление.test.mjs 02-проведение.test.mjs 04-отчёт-остатки/ 01-формирование.test.mjs 05-согласование/ 01-полный-цикл.test.mjs ``` Порядок выполнения — по алфавиту, поэтому удобно префиксовать папки и файлы номерами. Это даёт предсказуемый сценарий: сначала вход, потом справочники, потом документы, потом отчёты, в конце — процессы с несколькими пользователями. ## Быстрый старт Самый короткий путь от нуля до зелёного теста — попросить модель пройти ваш сценарий руками и зафиксировать его как тест: ``` > Покрой регрессом справочник Контрагенты в моей конфигурации. > Нужны проверки: создание, правка телефона, удаление. ``` Что сделает модель: 1. Соберёт информацию о справочнике через `/meta-info` и `/form-info` — посмотрит реквизиты и форму элемента, чтобы знать правильные имена полей. 2. Подключится к опубликованной базе в интерактивном режиме и **руками пройдёт** каждый сценарий — создание, правка, удаление. Это нужно, чтобы зафиксировать настоящие имена кнопок, увидеть, какие диалоги показывает 1С, понять, требуется ли подтверждение сохранения. 3. Зафиксирует пройденный сценарий как файл `tests/<ваша-конфигурация>/02-контрагенты/01-создание.test.mjs`. 4. Запустит его и покажет результат. При следующих прогонах ничего этого делать не нужно — модель просто запустит готовый набор. ## Сценарии работы с моделью ### Покрытие регрессом доработанного объекта ``` > Я добавил в справочник Номенклатура реквизит "Цена" и "Активен". > Покрой это регрессом — создание, редактирование, фильтрация по активности ``` Модель: - посмотрит структуру справочника и формы (через `/meta-info`, `/form-info`); - интерактивно проверит, как ведут себя новые поля в браузере; - сгенерирует 2-3 тестовых файла под папкой `02-номенклатура/`; - прогонит — покажет, что зелёное, что красное. ### Тест процесса с несколькими пользователями ``` > Сделай тест для процесса согласования приходных накладных. > Кладовщик создаёт накладную, менеджер утверждает, > кладовщик видит обновлённый статус ``` Модель настроит в `webtest.config.mjs` двух пользователей (с разными URL базы — например, `app-clerk` и `app-manager`), напишет тест, который оркестрирует переключение между ними, и положит его в `05-согласование/`. ```js export const contexts = ['кладовщик', 'менеджер']; export default async function({ кладовщик, менеджер, step, assert }) { await step('Кладовщик создаёт накладную', async () => { await кладовщик.navigateSection('Склад'); await кладовщик.openCommand('Приходные накладные'); await кладовщик.clickElement('Создать'); // ... }); await step('Менеджер утверждает', async () => { await менеджер.navigateSection('Согласование'); // ... }); // ... } ``` Учтите ограничение по лицензиям 1С: каждый одновременно открытый пользователь — это занятая клиентская лицензия. Если в наборе много многопользовательских тестов, а на стенде лицензий впритык, прогоны начнут спотыкаться на «свободных лицензий не осталось». Модель освобождает сессии между тестами автоматически (закрывает контексты после процессного теста), но если стенд ограничен — закладывайте это в планирование набора: один-два многопользовательских сценария вместо десяти. ### Воспроизведение ошибки тестом ``` > При проведении накладной без заполненного контрагента у нас не появляется > ошибка валидации, документ просто проводится с пустым контрагентом — это баг. > Зафиксируй это падающим тестом ``` Модель воспроизведёт сценарий, напишет тест с проверкой «должна быть ошибка», получит красный — потом, когда вы поправите конфигурацию и попросите перепрогнать, тест станет зелёным. Это документирует ожидаемое поведение в виде кода. ### Прогон регресса после изменений ``` > Я обновил расширение, накатил в базу. Прогони регресс ``` Модель запустит весь набор, дождётся завершения и расскажет: - сколько тестов прошло, сколько упало, сколько пропущено; - по каждому упавшему — что именно сломалось (название шага, сообщение об ошибке, ссылка на скриншот); - классифицирует падения: это ошибка в самом тесте (нужно поправить тест), ошибка в приложении (баг, который вы внесли изменением), или нестабильность стенда (Apache не ответил вовремя, лицензия не освободилась). ``` > Прогони только тесты по контрагентам с подробным отчётом ``` Запустит подмножество — фильтр по тегу или папке, с записью JSON-отчёта. ### Подготовка автономного стенда Если вы хотите, чтобы регресс можно было запустить «с нуля» — даже на чистой машине без подготовленной базы, — модель настроит автоматическую подготовку стенда: ``` > Сделай, чтобы перед прогоном тестов база восстанавливалась из эталона, > а после прогона публикация снималась ``` Это пишется один раз в файле `_hooks.mjs`: при запуске тестов запускается подготовка (через навыки `/db-create`, `/db-load-xml`, `/web-publish`), а после — очистка. Внутри предусмотрено кэширование: если ничего не менялось со прошлого прогона, повторная подготовка занимает доли секунды. ## Пример организации покрытия Допустим, у нас условное прикладное решение «Учёт поступлений товаров» — справочники контрагентов и номенклатуры, документ приходной накладной, отчёт остатков, процесс согласования с двумя пользователями. Логично организовать набор так: ``` tests/учёт-поступлений/ _hooks.mjs # подготовка: восстановление базы + публикация webtest.config.mjs # URL базы, контексты кладовщика и менеджера 01-вход/ 01-открытие-базы.test.mjs # базовая работоспособность: вход проходит, разделы видны 02-навигация-по-разделам.test.mjs # обход всех разделов конфигурации 02-контрагенты/ 01-создание.test.mjs # создание, проверка появления в списке 02-редактирование.test.mjs # правка реквизита, проверка сохранения 03-удаление.test.mjs # удаление с подтверждением 03-номенклатура/ 01-создание.test.mjs 02-фильтр-по-активности.test.mjs # быстрая фильтрация списка 04-поступление-товаров/ 01-оформление.test.mjs # заполнение шапки и табличной части 02-проведение.test.mjs # проведение документа, проверка движений 03-отмена-проведения.test.mjs 04-валидация-обязательных.test.mjs # негативный тест: пустой контрагент → ошибка 05-отчёт-остатки/ 01-формирование.test.mjs 02-отбор-по-складу.test.mjs 03-расшифровка.test.mjs # переход из ячейки отчёта в исходный документ 06-согласование/ 01-полный-цикл.test.mjs # многопользовательский тест ``` Принципы: - **Папки — по бизнес-функции**, не по типу метаданных. Лучше `04-поступление-товаров/` (что делает пользователь), чем `документы/` (что лежит в дереве конфигурации). - **Цифровые префиксы** — на папке и на файле. Гарантируют, что сначала отработают базовые проверки (вход, справочники), потом сложные (документы, отчёты, процессы). При падении базы остальное и так не пройдёт — нет смысла занимать стенд получасом. - **Один файл — одна логически связанная история.** Не «всё про контрагентов в одном файле», а «отдельно создание, отдельно правка, отдельно удаление». Когда падает — сразу видно, какой именно сценарий сломан. - **Негативные тесты тоже есть.** «Документ без контрагента не проводится» — такой же важный регресс, как и позитивный сценарий, особенно после правок в обработчиках проверки заполнения. - **Процессные тесты — в конце.** Они самые хрупкие (зависят от двух сессий, лицензий, синхронизации) и самые длинные. Если упадут — у вас уже есть данные от предыдущих тестов. ## Анатомия одного теста Пользователь, как правило, тест не пишет — генерирует модель. Но прочитать и поправить полезно уметь. Стандартный файл выглядит так: ```js export const name = 'Создание контрагента'; export const tags = ['контрагенты', 'базовая-проверка']; export const timeout = 60000; export default async function({ navigateSection, openCommand, clickElement, fillFields, readTable, closeForm, assert, step }) { await step('Открыть список контрагентов', async () => { await navigateSection('Продажи'); await openCommand('Контрагенты'); }); await step('Создать нового контрагента', async () => { await clickElement('Создать'); await fillFields({ 'Наименование': 'ТД Тест', 'ИНН': '7707083893' }); await clickElement('Записать и закрыть'); }); await step('Убедиться, что элемент появился в списке', async () => { const t = await readTable(); assert.tableHasRow(t, r => r['Наименование'] === 'ТД Тест'); }); } ``` Что здесь есть: - **`name`** — человекочитаемое имя теста. Появится в отчёте. - **`tags`** — теги для фильтрации. Можно прогонять не весь набор, а только нужные: `--tags=контрагенты`. - **`timeout`** — сколько максимум тест может идти. По умолчанию 30 секунд, для длинных сценариев увеличиваем. - **Тело теста** — функция, которая получает API браузера (см. [SKILL.md](../.claude/skills/web-test/SKILL.md)) плюс `assert` и `step`. - **`step('имя', async () => {...})`** — обёртка шага. Имена шагов попадают в отчёт, при падении видно, какой именно шаг сломался. - **`assert.*`** — проверки. `assert.tableHasRow`, `assert.equal`, `assert.ok` и т.д. Если проверка не выполнилась — тест считается упавшим. Имена шагов и теста — по-русски, описательные. Они показываются и в консоли, и в отчётах. ## Запуск и отчёты ### Простой прогон ``` > Прогони регресс ``` Модель запустит весь набор, дождётся, покажет сводку: ``` ✓ Открытие базы (2.1s) ✓ Создание контрагента (8.4s) ✗ Проведение приходной накладной (12.7s) └ Заполнить табличную часть (5.2s) Не найден столбец "Цена" в табличной части "Товары" screenshot: tests/учёт-поступлений/error-shot.png 23 passed, 1 failed, 0 skipped (3m 42s) ``` ### Подробный отчёт ``` > Прогони регресс и сохрани подробный отчёт ``` Модель добавит флаг записи отчёта (JSON или Allure) — потом по нему можно листать историю прогонов, видеть длительности шагов, открывать прикреплённые скриншоты. Allure — стандартный визуальный отчёт с категориями падений, графиками, таймлайном. Чтобы посмотреть отчёт после прогона: ```bash # Allure CLI устанавливается отдельно (npm install -g allure-commandline) allure serve allure-results ``` ### Категории падений в Allure Без дополнительной настройки Allure складывает все упавшие тесты в один общий список «Defects». Если в прогоне упало 15 тестов, не сразу понятно, что из этого — пятнадцать разных проблем или одна и та же ошибка (например, нехватка лицензии на стенде), которая зацепила пятнадцать тестов подряд. Чтобы Allure группировал падения по причинам, рядом с тестами кладётся каталог `_allure/` с файлом `categories.json`. Подчёркивание в имени каталога — чтобы он не воспринимался как папка с тестами; раннер копирует его содержимое в отчёт. ``` tests/моя-конфигурация/ _allure/ categories.json # классификация падений environment.properties # необязательно: URL, версия 1С, ветка git executor.json # необязательно: метаданные сборки CI _hooks.mjs 01-вход/ ... ``` `categories.json` — это список регулярных выражений, по которым ошибка теста относится к той или иной группе: ```json [ { "name": "Нехватка лицензий 1С", "matchedStatuses": ["failed", "broken"], "messageRegex": ".*Не обнаружено свободной лицензии.*" }, { "name": "Ошибка приложения 1С", "matchedStatuses": ["failed"], "messageRegex": ".*(ВызватьИсключение|В поле введены некорректные данные|Произошла ошибка).*" }, { "name": "Элемент не найден", "matchedStatuses": ["failed"], "messageRegex": ".*(clickElement|fillFields|selectValue).*not found.*" }, { "name": "Превышен лимит времени теста", "matchedStatuses": ["failed", "broken"], "messageRegex": "Timeout \\(\\d+ms\\)" }, { "name": "Несовпадение ожидания и факта", "matchedStatuses": ["failed"], "messageRegex": "(Expected|AssertionError).*" } ] ``` Когда вы попросите модель в первый раз настроить регресс, она положит шаблонный `categories.json` со стандартными классами. По мере того как вы будете находить новые типичные причины падений (например, специфичные для вашего расширения тексты ошибок), категории дополняются. В виджете «Categories» итогового отчёта вы увидите примерно так: ``` Нехватка лицензий 1С — 12 падений Ошибка приложения 1С — 2 падения Несовпадение ожидания и факта — 1 падение ``` — и сразу понятно, что 12 падений — это один стенд-баг, а двумя «ошибками приложения» нужно разобраться по существу. Помимо `categories.json` в каталог `_allure/` можно положить ещё два стандартных файла: - **`environment.properties`** — список `ключ=значение` (URL базы, версия платформы 1С, имя ветки git, номер сборки). Покажется в отчёте в виджете «Environment». Полезно, когда регресс гоняется на нескольких стендах или после каждого билда — видно, на чём именно был получен результат. Этот файл удобно генерировать прямо в подготовке стенда (`_hooks.mjs`), а не держать статичной копией. - **`executor.json`** — метаданные системы сборки: ссылка на Jenkins-задачу, идентификатор запуска GitHub Actions и т.д. Нужен только если регресс запускается на сервере сборки. При локальном прогоне ничего класть не надо. ### Прогон части набора ``` > Прогони только тесты по поступлениям товаров > Прогони только базовые проверки > Прогони только упавший вчера тест с проведением накладной ``` Модель выберет нужное подмножество — по папке, по тегу или по имени теста. ### Принудительная пересборка стенда Если хотите, чтобы перед прогоном база восстановилась с нуля: ``` > Прогони регресс с полной пересборкой стенда ``` Это передаст в подготовку флаг типа `--rebuild-stand` — `_hooks.mjs` пересоздаст базу из эталона. Полезно после крупных правок или если подозреваете, что предыдущие прогоны загрязнили данные. ## Что делать, когда тест упал Модель проанализирует падение и отнесёт его к одной из трёх категорий: 1. **Ошибка в самом тесте.** Например, переименовали реквизит — тест ищет старое имя поля. Решение: модель обновит тест. 2. **Ошибка в приложении.** Это и есть то, ради чего регресс существует: что-то поменялось в конфигурации, и сценарий, который раньше работал, теперь не отрабатывает. Модель опишет, что именно произошло, со скриншотом и трассировкой стека 1С, если ошибка была серверной. 3. **Нестабильность стенда.** Apache не ответил, не освободилась лицензия, база отвалилась. Это лечится не правкой теста, а починкой подготовки стенда в `_hooks.mjs` или, реже, повторным прогоном с одним повтором. Просите модель не «исправь упавший тест», а «разберись с падением» — иначе она может молча подкрутить ожидание под текущее поведение, замаскировав настоящий баг. ## Полезные подробности ### Тестовые данные В прикладном решении обычно нужны какие-то стартовые данные: пара контрагентов, номенклатура, заведённые организации. Их кладём не в каждый тест, а один раз в подготовку стенда (`_hooks.mjs`) — после восстановления базы загружаются эталонные данные, на которых работают все тесты. Если конкретному тесту нужны свои данные (например, документ, который мы будем редактировать), он создаёт их сам в начале и убирает в конце. ### Имена документов и уникальность Тесты прогоняются многократно. Если тест создаёт документ «Накладная-Тест», следующий прогон может натолкнуться на старую запись. Решение — добавлять к имени метку времени: ```js const метка = 'Тест-' + Date.now(); await fillFields({ 'Комментарий': метка }); // ... const t = await readTable(); assert.tableHasRow(t, r => r['Комментарий'] === метка); ``` Модель это делает автоматически, но если правите тест руками — держите в голове. ### Видео при падении Можно включить запись видео всех тестов — тогда при падении прикладывается не только скриншот, но и MP4 со всей сессией: ``` > Прогони регресс с записью видео ``` Размер прогона при этом растёт (на 2-3 минутах теста выходит 5-10 МБ), но при отладке сложного падения видео экономит кучу времени. ### Многоязычные конфигурации Если у вас есть конфигурация с командами и реквизитами на нескольких языках, тесты пишутся под один язык (как правило, тот, в котором ведётся работа в проде). При смене языка интерфейса в браузере тесты не пройдут — модель видит другие подписи кнопок. ## Где смотреть дальше - API браузера, которое вызывают тесты — [SKILL.md](../.claude/skills/web-test/SKILL.md). - Подробная инструкция для модели по написанию тестов (на английском, технический документ) — [.claude/skills/web-test/regress.md](../.claude/skills/web-test/regress.md). - Интерактивный режим без тестов — [web-test-guide.md](web-test-guide.md). - Запись видеоинструкций — [web-test-recording-guide.md](web-test-recording-guide.md).