mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-11 00:14:56 +03:00
f424d2ac70
clickElement как последний fallback (без table) фокусирует одноимённое
поле ввода, не меняя значение — возвращает focused:{field,id,ok}.
Закрывает пробел: клавиши F4/Shift+F4 требовали сфокусированного поля,
но штатного примитива фокуса не было.
- dom/forms.mjs: резолв input.editInput/textarea по имени/заголовку
последним шагом findClickTargetScript; имена полей в available
- click-form.mjs: focusFormField (клик по инпуту + isInputFocused → ok)
- click.mjs: ветка диспетчера kind === field
- SKILL.md + docs/web-test-guide.md: focused в extras, пример focus→F4
- tests: 19-focus-field.test.mjs (focus/F4/регресс/негатив)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
412 lines
30 KiB
Markdown
412 lines
30 KiB
Markdown
# Тестирование через веб-клиент 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)
|
||
|
||
Одна команда: открывает браузер → логинится → выполняет сценарий → закрывает браузер → завершает процесс. Не оставляет висящих процессов.
|
||
|
||
```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": "Тестовая проверка: запись запрещена", "screenshot": "error-shot.png",
|
||
"stack": { "raw": "...", "entries": [{"location": "Модуль(4)", "code": "ВызватьИсключение..."}] } }
|
||
```
|
||
Стек извлекается автоматически — через OpenReport (платформенные исключения) или "О программе" → "Информация для техподдержки" (ВызватьИсключение).
|
||
|
||
### Интерактивный режим (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`
|
||
|
||
### Расшифровка отчёта
|
||
|
||
```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) | form state с `navigated`, `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'`
|
||
|
||
**Виртуализация и `hasMore`.** 1С виртуализирует и динамические списки, и табличные части — в DOM лежит только окно видимых строк. Поля `total` / `shown` — это размер DOM-окна, а **не** размер коллекции. Чтобы понять, есть ли строки за пределами окна, используйте `hasMore`:
|
||
|
||
```js
|
||
const t = await readTable();
|
||
// t.hasMore = { above: false, below: true } — открыли список, есть строки ниже
|
||
// t.hasMore = { above: true, below: false } — пролистали в конец
|
||
// t.hasMore = { above: false, below: false } — всё помещается / нет страниц
|
||
```
|
||
|
||
`hasMore.below` присутствует всегда. `hasMore.above` тоже обычно есть — определяется по кнопкам пагинации (`vertButtonScroll`, есть у большинства дин-списков) или треку скроллбара (у табчастей). Отсутствует только в редких случаях, когда у грида нет ни кнопок, ни видимого скроллбара — тогда трактуйте отсутствие как «неизвестно».
|
||
|
||
**Колонки-картинки.** Ячейки, где выводится иконка (статусы, этапы, индикатор ЭДО, скрепка «есть файл»), читаются как `'pic:<N>'` при наличии иконки (`N` — индекс кадра/состояния) и `''` при её отсутствии. Присутствие читается как truthy, разные иконки различаются по индексу:
|
||
|
||
```js
|
||
const t = await readTable();
|
||
if (t.rows[0]['Присоединенные файлы']) { /* у строки есть прикреплённый файл */ }
|
||
t.rows[0]['ЭДО'] === 'pic:1'; // подключён к 1С-ЭДО ('pic:0' = нет)
|
||
```
|
||
|
||
Колонки без текста в заголовке (одна иконка) тоже попадают в `columns`, именуются по тултипу заголовка или `'(picture)'` — служебное имя колонки 1С в браузер не передаёт. Картиночные значения — **только для чтения и ассертов**: отбирать/фильтровать строки по `'pic:N'` нельзя (фильтр по такому значению бросает понятную ошибку, расширенный поиск 1С такое поле не покажет). Для выбора строки фильтруйте по текстовой колонке; кликать по картиночной ячейке можно по индексу строки.
|
||
|
||
#### clickElement — клик по ячейке (spreadsheet или грид формы)
|
||
|
||
Первый аргумент `clickElement` принимает объект `{ row, column }` вместо текста. Маршрутизация автоматическая: если на форме отрисован SpreadsheetDocument (отчёт) — кликаем туда (drill-down), иначе — по ячейке грида (табчасть, список). Параметр `table: 'ИмяГрида'` принудительно указывает грид, если на форме одновременно есть отчёт и таблицы.
|
||
|
||
**SpreadsheetDocument (drill-down отчёта).** Координаты соответствуют выводу `readSpreadsheet()`:
|
||
|
||
```js
|
||
const report = await readSpreadsheet();
|
||
// report.data[0] = { 'К1': 'Материалы строительные', 'К6': '150 000' }
|
||
|
||
await clickElement({ row: 0, column: 'К6' }, { dblclick: true }); // по индексу
|
||
await clickElement({ row: { 'К1': 'Материалы' }, column: 'К6' }, { dblclick: true }); // по фильтру
|
||
await clickElement({ row: 'totals', column: 'К6' }, { dblclick: true }); // итоги
|
||
await clickElement('150 000', { dblclick: true }); // fallback: по тексту в iframe'ах
|
||
```
|
||
|
||
**Грид формы (табчасть документа, список каталога/журнала).** Колонка вне viewport — авто-скролл по горизонтали (с учётом frozen-колонок). `scroll: true | number` включает reveal-loop через PageDown для filter-row за пределами DOM-окна:
|
||
|
||
```js
|
||
await clickElement({ row: 0, column: 'Количество' }, { table: 'Товары', dblclick: true });
|
||
await clickElement({ row: { 'Номенклатура': 'Бумага' }, column: 'Цена' }, { table: 'Товары' });
|
||
await clickElement(
|
||
{ row: { 'Номер': '0000-000601' }, column: 'Сумма' },
|
||
{ table: 'Реализации', scroll: true } // PageDown loop, лимит по умолчанию 50
|
||
);
|
||
```
|
||
|
||
**Подводные камни:**
|
||
- `row: <число>` — индекс в **текущем DOM-окне**, не абсолютный (1С виртуализирует длинные списки). Для произвольной строки в длинном списке — `row: { col: val }` + `scroll: true`.
|
||
- `scroll: true` идёт только **вниз** (PageDown). Для вверх — `page.keyboard.press('Home')` через `getPage()` или сначала `filterList`.
|
||
- На дубликаты при фильтре — первая подходящая строка. Уточняйте фильтр для disambiguation.
|
||
|
||
### Действия
|
||
|
||
Все action-функции возвращают **плоский form state** (как `getFormState()`) с action-specific extras (`clicked`, `focused`, `selected`, `filled`, `notFilled`, `closed`, `opened`, `navigated`, `deleted`, `filtered`, `unfiltered`). Errors всегда на верхнем уровне `.errors` — exec-wrapper автоматически throw'ает на soft validation errors (`modal`/`balloon`).
|
||
|
||
| Функция | Описание | Возвращает |
|
||
|---------|----------|------------|
|
||
| `clickElement(text, {dblclick?, modifier?, table?, scroll?})` | Клик по кнопке/ссылке/строке. `{dblclick: true}` для открытия, `{modifier: 'ctrl'\|'shift'}` для мультиселекции. Первый аргумент может быть `{row, column}` для клика по ячейке spreadsheet или грида формы (`table` форсит грид; `scroll: true \| number` включает reveal-loop через PageDown — см. выше). Если `text` не совпал ни с одним контролом и `table` не задан — как последний fallback фокусирует одноимённое поле ввода (без изменения значения), см. раздел про клавиши | form state (`clicked` / `focused` / `submenu`) |
|
||
| `fillFields({name: value})` | Заполнить поля (текст, чекбокс, радио, ссылки, DCS-фильтры). Пустое значение (`''`/`null`) = очистка | form state с `filled` |
|
||
| `selectValue(field, search, opts?)` | Выбрать из справочника. search: текст, `{поле: значение}` или `''`/`null` для очистки. `{ type }` для составного типа | form state с `selected` |
|
||
| `fillTableRow(fields, {tab?, add?, row?})` | Заполнить строку. Значение: строка, `{ value, type }` для составного типа, `''`/`null` для очистки | form state с `filled` (per-field ошибки как items `ok: false`, см. ниже) + `notFilled?` |
|
||
| `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()` | Снять выделение |
|
||
|
||
## Клавиатурные сочетания
|
||
|
||
Чтобы клавиша применилась к нужному полю, его сперва надо сфокусировать. `clickElement('ИмяПоля')` (без `table`) ставит фокус, ничего не меняя, и возвращает `focused: { field, id, ok }` — после этого жмём клавишу через `getPage()`:
|
||
|
||
```js
|
||
await clickElement('Контрагент'); // фокус на ссылочное поле (focused.ok)
|
||
const page = await getPage();
|
||
await page.keyboard.press('F4'); // открыть форму выбора
|
||
```
|
||
|
||
| Клавиша | Контекст | Действие |
|
||
|---------|----------|----------|
|
||
| `F8` | Ссылочное поле | Создать новый элемент (может требовать прав/настройки в 1С) |
|
||
| `Shift+F4` | Любое поле | Очистить значение (автоматизировано: `fillFields({ поле: '' })`) |
|
||
| `F4` | Ссылочное поле | Форма выбора |
|
||
| `Alt+F` | Список/таблица | Расширенный поиск |
|
||
|
||
## Типичные ошибки
|
||
|
||
Большинство функций бросают исключение при ошибке. Сценарий прерывается на проблемном шаге с информативным сообщением. В интерактиве — `try/catch` для обработки.
|
||
|
||
**Исключение — `fillTableRow`**: на per-field ошибках не throws, а возвращает их в `filled[]` как items с `ok: false` (`{ field, ok: false, error: 'code', message: '...' }`). Это позволяет частичное восстановление: например при `error: 'composite_type'` модель может retry'нуть конкретную ячейку с `{ value, type }` синтаксисом, не перезаполняя всю строку. Проверка — `r.filled.filter(f => !f.ok)`. Жёсткие ошибки (нет формы, table не найдена) и soft validation errors от 1С (balloon/modal) — всё равно throws.
|
||
|
||
| Проблема | Решение |
|
||
|----------|---------|
|
||
| `no form found` — форма не открыта | Добавьте `await wait(2)` после навигации |
|
||
| `not found. Available: ...` — элемент не найден | Проверьте имя через `getFormState()`, используйте вариант из Available |
|
||
| `fillFields: N of M field(s) failed` | Текст ошибки содержит список проблемных полей и доступные варианты |
|
||
| `fillTableRow` вернул item с `ok: false` | См. поле `error` — `composite_type` → retry с `{value, type}`; `column_not_found` → проверьте имя поля через `readTable`; `not_found` → уточните значение поиска |
|
||
| Пустой `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 не может прочитать имена разделов из иконок
|
||
|
||
## Связанные навыки
|
||
|
||
- [Запись видеоинструкций](web-test-recording-guide.md) — запись видео, субтитры, подсветка, TTS-озвучка
|
||
- [Веб-публикация](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`
|