Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
23 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 откроет форму и проверит ожидаемое поведение.
Открытие внешней обработки
> Открой обработку build/РедакторДвижений.epf в веб-клиенте и покажи что на форме
Claude откроет EPF через Ctrl+O, автоматически обработает диалог безопасности (если есть) и прочитает форму.
Пошаговая отладка
> Запусти браузер на базе 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": "Тестовая проверка: запись запрещена", "screenshot": "error-shot.png",
"stack": { "raw": "...", "entries": [{"location": "Модуль(4)", "code": "ВызватьИсключение..."}] } }
Стек извлекается автоматически — через OpenReport (платформенные исключения) или "О программе" → "Информация для техподдержки" (ВызватьИсключение).
Интерактивный режим (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
Расшифровка отчёта
// 1. Сформировать отчёт
await clickElement('Сформировать');
await wait(5);
const report = await readSpreadsheet();
// 2. Двойной клик по ячейке → диалог "Выбор поля"
await clickElement({ row: 0, column: 'К6' }, { dblclick: true });
// 3. Выбрать поле расшифровки
await clickElement('Регистратор');
await clickElement('Выбрать');
await wait(10);
// 4. Прочитать результат
const drilldown = await readSpreadsheet();
console.log('Расшифровка:', JSON.stringify(drilldown.rows));
API
Все функции доступны как глобальные переменные в скриптах. console.log() выводит данные в ответ.
Навигация
| Функция | Описание | Возвращает |
|---|---|---|
navigateSection(name) |
Перейти в раздел (fuzzy match) | { sections, commands } |
openCommand(name) |
Открыть команду из панели функций | form state |
navigateLink(path) |
Открыть по пути метаданных (Документ.ЗаказКлиента) |
form state |
openFile(path) |
Открыть внешнюю обработку/отчёт (EPF/ERF) через «Файл → Открыть» | form state |
switchTab(name) |
Переключить открытую вкладку | form state |
Чтение
| Функция | Описание | Возвращает |
|---|---|---|
getFormState() |
Структура формы: поля, кнопки, таблица, фильтры, состояние окон | { form, formCount, openForms, 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 — подробнее
Основной способ «увидеть» что на экране:
- form — номер активной формы,
nullкогда ничего не открыто (десктоп) - formCount — количество открытых форм.
0= десктоп. Работает даже если панель открытых окон скрыта - openForms —
[0, 1, 2]— номера всех открытых форм в DOM - modal —
trueкогда активная форма — модальный диалог, блокирующий интерфейс - openTabs —
[{ name, active? }]из панели открытых окон (только когда панель включена в настройках 1С) - 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('Нет') - platformDialogs —
[{ type, title }]— платформенные диалоги (О программе, Информация для техподдержки). Невидимы для обычного определения форм, но блокируют интерфейс.closeForm()закрывает их. Автоочистка черезdismissPendingErrorsперед каждым action
readTable — подробнее
Каждая строка — объект { columnName: value }. Специальные поля для иерархии и дерева:
_kind: 'group'— группа в иерархическом списке_tree: 'expanded'|'collapsed'— состояние узла дерева_level: N— уровень вложенности_selected: true— строка выделена (подсвечена). Используйте сclickElement({ modifier: 'ctrl'|'shift' })для проверки мультиселекции- На объекте результата:
hierarchical: true,viewMode: 'tree'
clickElement — клик по ячейке SpreadsheetDocument
Для расшифровки отчётов первый аргумент clickElement принимает объект { row, column } вместо текста. Координаты соответствуют выводу readSpreadsheet():
const report = await readSpreadsheet();
// report.data[0] = { 'К1': 'Материалы строительные', 'К6': '150 000' }
// По индексу строки данных + имя колонки
await clickElement({ row: 0, column: 'К6' }, { dblclick: true });
// По значению ячейки в строке (fuzzy match)
await clickElement({ row: { 'К1': 'Материалы' }, column: 'К6' }, { dblclick: true });
// Строка итогов
await clickElement({ row: 'totals', column: 'К6' }, { dblclick: true });
Текстовый поиск тоже работает — если элемент не найден в основном DOM, clickElement ищет в SpreadsheetDocument iframe'ах:
await clickElement('150 000', { dblclick: true }); // найдёт ячейку в отчёте
Действия
| Функция | Описание | Возвращает |
|---|---|---|
clickElement(text, {dblclick?, modifier?}) |
Клик по кнопке/ссылке/строке. {dblclick: true} для открытия, {modifier: 'ctrl'|'shift'} для мультиселекции. Первый аргумент может быть {row, column} для клика по ячейке SpreadsheetDocument (см. выше) |
form state или { submenu } |
fillFields({name: value}) |
Заполнить поля (текст, чекбокс, радио, ссылки, DCS-фильтры). Пустое значение (''/null) = очистка |
{ filled: [{field, ok, method}], form } |
selectValue(field, search, opts?) |
Выбрать из справочника. search: текст, {поле: значение} или ''/null для очистки. { type } для составного типа |
form state с selected |
fillTableRow(fields, {tab?, add?, row?}) |
Заполнить строку. Значение: строка, { value, type } для составного типа, ''/null для очистки |
form state |
deleteTableRow(row, {tab?}) |
Удалить строку по индексу | form state |
closeForm({save?}) |
Закрыть форму. save: false = "Нет", save: true = "Да". Возвращает closed: true/false |
form state с closed |
filterList(text, {field?, exact?}) |
Фильтр списка. Без field = все колонки, с field = расширенный поиск | form state |
unfilterList({field?}) |
Снять фильтры (все или конкретный) | form state |
fillFields — типы полей
| Значение | Тип поля | Метод |
|---|---|---|
'Андромеда Плюс' |
Ссылочное | clipboard paste + typeahead |
'5000' |
Текст | clipboard paste |
'true' / 'да' |
Чекбокс | toggle |
'Оплата поставщику' |
Радио | fuzzy match по меткам |
'Склад бытовой техники' (DCS) |
Фильтр отчёта | авто-включение чекбокса + заполнение |
'' / null |
Любое (кроме чекбокс/радио) | очистка через Shift+F4 |
Утилиты
| Функция | Описание |
|---|---|
screenshot() |
Скриншот (PNG Buffer) |
wait(seconds) |
Пауза, возвращает form state |
getPage() |
Сырой Playwright Page для горячих клавиш и нестандартных операций |
startRecording(path, opts?) |
Начать запись видео (CDP screencast → ffmpeg → MP4) |
stopRecording() |
Остановить запись, вернуть { file, duration, size } |
showCaption(text, opts?) |
Текстовая подпись поверх страницы (speech — текст озвучки) |
hideCaption() |
Убрать подпись |
showTitleSlide(text, opts?) |
Полноэкранный титульный слайд (subtitle, background, speech) |
hideTitleSlide() |
Убрать титульный слайд |
showImage(path, opts?) |
Полноэкранное изображение (style: blur/dark/light/full, speech) |
hideImage() |
Убрать изображение |
addNarration(videoPath, opts?) |
Озвучка видео по субтитрам (Edge TTS / ElevenLabs / OpenAI) |
getCaptions() |
Субтитры из текущей/последней записи |
isRecording() |
Идёт ли запись (boolean) |
setHighlight(on) |
Включить/выключить авто-выделение элементов при действиях |
isHighlightMode() |
Активен ли режим авто-выделения (boolean) |
highlight(text) |
Ручное выделение элемента (по имени, fuzzy match) |
unhighlight() |
Снять выделение |
Клавиатурные сочетания
const page = await getPage();
await page.keyboard.press('F8'); // пример: создать новый элемент в сфокусированном ссылочном поле
| Клавиша | Контекст | Действие |
|---|---|---|
F8 |
Ссылочное поле | Создать новый элемент |
Shift+F4 |
Любое поле | Очистить значение (автоматизировано: fillFields({ поле: '' })) |
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для обработки - Панель разделов —
navigateSection()работает при любом расположении панели (сбоку, сверху), но требует режим «Картинка и текст» или «Текст». Режим «Только картинки» не поддерживается — API не может прочитать имена разделов из иконок
Связанные навыки
- Запись видеоинструкций — запись видео, субтитры, подсветка, TTS-озвучка
- Веб-публикация —
/web-publish,/web-info,/web-stop,/web-unpublish - Базы данных —
/db-load-xml,/db-update,/db-run - Расширения —
/cfe-init,/cfe-borrow,/cfe-patch-method