# Тестирование через веб-клиент 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) Одна команда: открывает браузер → логинится → выполняет сценарий → закрывает браузер → завершает процесс. Не оставляет висящих процессов. ```bash RUN=".claude/skills/web-test/scripts/run.mjs" node $RUN run http://localhost:8081/erp scenario.js ``` Claude пишет `.js` файл со сценарием и запускает его. Ответ — JSON: ```json { "ok": true, "output": "...console.log output...", "elapsed": 12.3 } ``` При ошибке — автоматический скриншот: ```json { "ok": false, "error": "Element not found", "screenshot": "error-shot.png" } ``` ### Интерактивный режим (start/exec/stop) Браузер остаётся открытым между командами. Состояние (открытые вкладки, формы) сохраняется. ```bash 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`) | Пошаговое исследование, отладка, разговор с пользователем | ## Пример: автономный сценарий Сравнение остатков по двум складам — один файл, один запуск: ```js // 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 для горячих клавиш и нестандартных операций | ## Клавиатурные сочетания Через `getPage().keyboard.press()`: | Клавиша | Контекст | Действие | |---------|----------|----------| | `F8` | Ссылочное поле | Создать новый элемент | | `Shift+F4` | Ссылочное поле | Очистить значение | | `F4` | Ссылочное поле | Форма выбора | | `Alt+F` | Список/таблица | Расширенный поиск | ## Типичные ошибки | Проблема | Решение | |----------|---------| | `no_form` — форма не открыта | Добавьте `await wait(2)` после навигации | | `not_found` — элемент не найден | Проверьте имя через `getFormState()` | | Зацикливание поиска | Элемент не существует — навык остановится после 2 попыток | | Пустой `readSpreadsheet()` | Увеличьте `await wait(N)` перед чтением | ## Особенности - **Headed mode** — 1С требует видимый браузер, headless не поддерживается - **Время запуска** — первое подключение к 1С занимает 30-60 секунд (ожидание встроено) - **Fuzzy matching** — все поиски: точное совпадение → начало строки → вхождение - **Clipboard paste** — поля заполняются через Ctrl+V (корректно триггерит события 1С) - **Неразрывные пробелы** — 1С использует `\u00a0`, внутри API нормализация автоматическая ## Связанные навыки - [Веб-публикация](web-guide.md) — `/web-publish`, `/web-info`, `/web-stop`, `/web-unpublish` - [Базы данных](db-guide.md) — `/db-load-xml`, `/db-update`, `/db-run` - [Расширения](cfe-guide.md) — `/cfe-init`, `/cfe-borrow`, `/cfe-patch-method`