Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
17 KiB
Тестирование через веб-клиент 1С
Навык /web-test автоматизирует действия в веб-клиенте 1С через Playwright — навигация по разделам, заполнение форм, чтение таблиц и отчётов, фильтрация списков. Замыкает цикл: правка исходников → загрузка → обновление → публикация → автоматическое тестирование.
Навык
| Навык | Скрипт | Описание |
|---|---|---|
/web-test |
.mjs (Node.js) |
Автоматизация 1С через браузер — навигация, формы, таблицы, отчёты |
Предусловия
- База опубликована через Apache (
/web-publish) - Node.js 18+ установлен
- Зависимости установлены:
cd .claude/skills/web-test/scripts && npm install
Рабочий цикл
/web-publish → /web-test → результат
↑ |
└── правки → /db-load-xml → /db-update ──┘
Сценарии использования
Навигация и чтение данных
> Открой базу erp в браузере, перейди в раздел Склад и покажи какие команды там есть
Claude откроет браузер, перейдёт в раздел и покажет список команд.
> Открой список поступлений товаров и покажи первые 10 строк
Claude откроет список и прочитает таблицу.
Поиск и открытие элементов
> Найди в списке номенклатуры товар "Вентилятор" и открой его карточку
Claude отфильтрует список, откроет найденный элемент двойным кликом и прочитает реквизиты формы.
> Открой справочник Контрагенты и найди "Торговый дом"
Claude может работать с иерархическими справочниками — поиск автоматически сглаживает иерархию.
Создание документа
> Создай заказ клиента: организация "Андромеда Плюс", контрагент "Торговый дом Комплексный",
> добавь строку: номенклатура "Вентилятор", количество 5
Claude откроет форму создания, заполнит шапку и добавит строку в табличную часть.
Работа с отчётами
> Открой отчёт "Остатки и доступность товаров",
> установи отбор Склад = "Склад бытовой техники", сформируй и прочитай результат
Claude заполнит фильтры отчёта по человекочитаемым именам (не надо знать технические имена DCS), нажмёт "Сформировать" и прочитает структурированные данные: заголовки, строки, итоги.
Сравнение данных
> Сформируй отчёт по остаткам для "Склад бытовой техники" и "Западный склад",
> сравни итоги по доступным товарам
Claude напишет сценарий, который сформирует отчёт дважды с разными фильтрами и сравнит результаты.
Проверка после загрузки расширения
> Загрузи расширение ТестОшибки и проверь через браузер, что при создании заказа клиента
> появляется ошибка "Тестовая ошибка из расширения"
Claude загрузит расширение через /db-load-xml, затем через /web-test откроет форму и проверит ожидаемое поведение.
Пошаговая отладка
> Запусти браузер на базе erp
> Перейди в раздел Продажи
> Посмотри что на форме
> Открой первый заказ
> Какие реквизиты заполнены?
Claude будет выполнять команды по одной, показывая состояние формы между шагами.
Режимы работы
Автономный режим (run)
Одна команда: открывает браузер → логинится → выполняет сценарий → закрывает браузер → завершает процесс. Не оставляет висящих процессов.
RUN=".claude/skills/web-test/scripts/run.mjs"
node $RUN run http://localhost:8081/erp scenario.js
Claude пишет .js файл со сценарием и запускает его. Ответ — JSON:
{ "ok": true, "output": "...console.log output...", "elapsed": 12.3 }
При ошибке — автоматический скриншот:
{ "ok": false, "error": "Element not found", "screenshot": "error-shot.png" }
Интерактивный режим (start/exec/stop)
Браузер остаётся открытым между командами. Состояние (открытые вкладки, формы) сохраняется.
node $RUN start http://localhost:8081/erp # запустить сессию (фоновый процесс)
cat <<'SCRIPT' | node $RUN exec - # выполнить скрипт
await navigateSection('Продажи');
SCRIPT
node $RUN shot current-state.png # скриншот
node $RUN stop # завершить сессию
Когда какой
| Режим | Когда использовать |
|---|---|
Автономный (run) |
Готовый сценарий целиком, субагенты, CI |
Интерактивный (start/exec) |
Пошаговое исследование, отладка, разговор с пользователем |
Пример: автономный сценарий
Сравнение остатков по двум складам — один файл, один запуск:
// scenario-compare-stocks.js
// 1. Открыть отчёт
await navigateSection('Склад и доставка');
await openCommand('Отчеты по складу');
await clickElement('Остатки и доступность товаров', { dblclick: true });
// 2. Первый склад
await fillFields({ 'Склад': 'Склад бытовой техники' });
await clickElement('Сформировать');
await wait(5);
const report1 = await readSpreadsheet();
console.log('=== Склад бытовой техники ===');
console.log('Строк:', report1.data?.length, '| Доступно:', report1.totals?.['Доступно']);
// 3. Второй склад
await fillFields({ 'Склад': 'Западный склад' });
await clickElement('Сформировать');
await wait(5);
const report2 = await readSpreadsheet();
console.log('=== Западный склад ===');
console.log('Строк:', report2.data?.length, '| Доступно:', report2.totals?.['Доступно']);
// 4. Сравнение
const parse = s => parseFloat((s || '0').replace(/\s/g, '').replace(',', '.'));
const diff = parse(report1.totals?.['Доступно']) - parse(report2.totals?.['Доступно']);
console.log('Разница:', diff.toFixed(0));
await closeForm({ save: false });
Запуск: node $RUN run http://localhost:8081/erp scenario-compare-stocks.js
API
Все функции доступны как глобальные переменные в скриптах. console.log() выводит данные в ответ.
Навигация
| Функция | Описание | Возвращает |
|---|---|---|
navigateSection(name) |
Перейти в раздел (fuzzy match) | { sections, commands } |
openCommand(name) |
Открыть команду из панели функций | form state |
navigateLink(path) |
Открыть по пути метаданных (Документ.ЗаказКлиента) |
form state |
switchTab(name) |
Переключить открытую вкладку | form state |
Чтение
| Функция | Описание | Возвращает |
|---|---|---|
getFormState() |
Структура формы: поля, кнопки, таблица, фильтры | { fields, buttons, tabs, table, filters, reportSettings? } |
readTable({ maxRows?, offset? }) |
Данные таблицы с пагинацией | { columns, rows: [{col: val}], total } |
readSpreadsheet() |
Результат отчёта | { title?, headers?, data?, totals?, total } |
getSections() |
Разделы и команды | { activeSection, sections, commands } |
getPageState() |
Разделы + открытые вкладки | { activeSection, activeTab, sections, tabs } |
getFormState — подробнее
Основной способ «увидеть» что на экране:
- fields —
[{ name, value, label?, actions?, required? }].actions= select/clear/open.required: true= незаполненное обязательное поле - table —
{ name, columns, rowCount }(метаданные; для данных —readTable()) - reportSettings — DCS-фильтры в читаемом виде:
[{ name: "Склад", enabled: true, value: "..." }] - errorModal — 1С показала ошибку
- confirmation — диалог Да/Нет, вызовите
clickElement('Да')илиclickElement('Нет')
readTable — подробнее
Каждая строка — объект { columnName: value }. Специальные поля для иерархии и дерева:
_kind: 'group'— группа в иерархическом списке_tree: 'expanded'|'collapsed'— состояние узла дерева_level: N— уровень вложенности- На объекте результата:
hierarchical: true,viewMode: 'tree'
Действия
| Функция | Описание | Возвращает |
|---|---|---|
clickElement(text, {dblclick?}) |
Клик по кнопке/ссылке/строке. {dblclick: true} для открытия из списка |
form state или { submenu } |
fillFields({name: value}) |
Заполнить поля (текст, чекбокс, радио, ссылки, DCS-фильтры) | { filled: [{field, ok, method}], form } |
selectValue(field, search) |
Выбрать из справочника (dropdown или форма подбора) | form state с selected |
fillTableRow(fields, {tab?, add?, row?}) |
Заполнить строку таблицы. add: true = новая, row: N = редактирование |
form state |
deleteTableRow(row, {tab?}) |
Удалить строку по индексу | form state |
closeForm({save?}) |
Закрыть форму. save: false = "Нет", save: true = "Да" |
form state |
filterList(text, {field?, exact?}) |
Фильтр списка. Без field = все колонки, с field = расширенный поиск | form state |
unfilterList({field?}) |
Снять фильтры (все или конкретный) | form state |
fillFields — типы полей
| Значение | Тип поля | Метод |
|---|---|---|
'Андромеда Плюс' |
Ссылочное | clipboard paste + typeahead |
'5000' |
Текст | clipboard paste |
'true' / 'да' |
Чекбокс | toggle |
'Оплата поставщику' |
Радио | fuzzy match по меткам |
'Склад бытовой техники' (DCS) |
Фильтр отчёта | авто-включение чекбокса + заполнение |
Утилиты
| Функция | Описание |
|---|---|
screenshot() |
Скриншот (PNG Buffer) |
wait(seconds) |
Пауза, возвращает form state |
getPage() |
Сырой Playwright Page для горячих клавиш и нестандартных операций |
startRecording(path, opts?) |
Начать запись видео (CDP screencast → ffmpeg → MP4) |
stopRecording() |
Остановить запись, вернуть { file, duration, size } |
showCaption(text, opts?) |
Текстовая подпись поверх страницы (для видеозаписей) |
hideCaption() |
Убрать подпись |
showTitleSlide(text, opts?) |
Полноэкранный титульный слайд (\n → перенос, subtitle, background) |
hideTitleSlide() |
Убрать титульный слайд |
isRecording() |
Идёт ли запись (boolean) |
setHighlight(on) |
Включить/выключить авто-выделение элементов при действиях |
isHighlightMode() |
Активен ли режим авто-выделения (boolean) |
highlight(text) |
Ручное выделение элемента (по имени, fuzzy match) |
unhighlight() |
Снять выделение |
Клавиатурные сочетания
Через getPage().keyboard.press():
| Клавиша | Контекст | Действие |
|---|---|---|
F8 |
Ссылочное поле | Создать новый элемент |
Shift+F4 |
Ссылочное поле | Очистить значение |
F4 |
Ссылочное поле | Форма выбора |
Alt+F |
Список/таблица | Расширенный поиск |
Типичные ошибки
Все функции бросают исключение при ошибке (не возвращают { error }). Сценарий прерывается на проблемном шаге с информативным сообщением. В интерактиве — try/catch для обработки.
| Проблема | Решение |
|---|---|
no form found — форма не открыта |
Добавьте await wait(2) после навигации |
not found. Available: ... — элемент не найден |
Проверьте имя через getFormState(), используйте вариант из Available |
fillFields: N of M field(s) failed |
Текст ошибки содержит список проблемных полей и доступные варианты |
Пустой readSpreadsheet() |
Увеличьте await wait(N) перед чтением |
Особенности
- Headed mode — 1С требует видимый браузер, headless не поддерживается
- Время запуска — первое подключение к 1С занимает 30-60 секунд (ожидание встроено)
- Fuzzy matching — все поиски: точное совпадение → начало строки → вхождение. Буквы ё и е считаются эквивалентными
- Clipboard paste — поля заполняются через Ctrl+V (корректно триггерит события 1С)
- Неразрывные пробелы — 1С использует
\u00a0, внутри API нормализация автоматическая - Ошибки — все функции бросают исключение при ошибке (сценарий прерывается),
try/catchдля обработки
Связанные навыки
- Веб-публикация —
/web-publish,/web-info,/web-stop,/web-unpublish - Базы данных —
/db-load-xml,/db-update,/db-run - Расширения —
/cfe-init,/cfe-borrow,/cfe-patch-method