UI-тестирование 1С через MCP-сервер 1c-testpilot (ROCTUP). Используй когда нужно управлять тестовым клиентом 1С напрямую — открывать формы, заполнять реквизиты, работать с табличными частями, сравнивать состояние формы до и после.
Installs into .claude/skills of the current project.
Are you the author of Testpilot 1c?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/afk-yar-testpilot-1c)
---
name: testpilot-1c
description: UI-тестирование 1С через MCP-сервер 1c-testpilot (ROCTUP). Используй когда нужно управлять тестовым клиентом 1С напрямую — открывать формы, заполнять реквизиты, работать с табличными частями, сравнивать состояние формы до и после.
argument-hint: "сценарий на естественном языке"
---
# /testpilot-1c — UI-тестирование через 1c-testpilot
Сервер ходит напрямую в `/TESTCLIENT` по протоколу тестового клиента. Базы-менеджера нет,
BSL-сценарии писать не надо, один шаг — один вызов инструмента.
Своего скилла у автора нет, вся справка лежит в описаниях инструментов и в `docs/TOOLS.md`.
Этот файл — только то, чего в описаниях нет или что там сформулировано неоднозначно.
## Окружение
Все пути и настройки конкретной машины собраны здесь; ниже по тексту — только имена.
Заполните таблицу своими значениями.
| Что | Где |
|---|---|
| Сервер testpilot (клон ROCTUP/1c-testpilot) | `<каталог testpilot>` — проверено на 1.7.1 |
| Точные сигнатуры действий | `docs/TOOLS.md` сервера |
| Python API и pytest | `docs/PYTHON_TESTING.md` сервера |
| Типы значений для обработки Testpilot | `onec/Testpilot/README.md` сервера, раздел «Типы значений» |
| Профили запуска | `profiles.yaml` сервера (образец — `profiles.example.yaml`) |
| Порты | свой порт на каждое назначение: агенты, скрипты, pytest |
| Образцы повторяемых прогонов | ваши скрипты и тесты на Python API |
| Снятые грабли 5, 6, 10, 12, 13 | `история-граблей.md` рядом с этим файлом |
## Инструменты
Схемы отложенные, тянуть через `ToolSearch` запросом
`select:mcp__1c-testpilot__tc_session,...`. Все десять групп разом — около 21 тыс.
токенов, поэтому грузи только нужные. Базовый набор на обычный сценарий:
`tc_session`, `tc_find`, `tc_window`, `tc_field`, `tc_table`.
## Канонический поток
1. `tc_session` action=launch_client, profile=`<проект>` — профили в `profiles.yaml` сервера.
2. `tc_find` action=wait_for_object_displayed, cls=`ManagedForm` — дождаться интерфейса.
3. `tc_window` action=execute_command, command=`e1cib/list/<Тип>.<Имя>` — открыть список.
4. `tc_find` action=find_object / find_objects — получить ref нужных элементов.
5. `tc_field` / `tc_table` — действия.
6. `tc_window` action=close_window (можно с `ref` окна). Если на форме вопрос о сохранении,
ответ `window_not_closed` — тогда action=answer_dialog.
7. `tc_session` action=stop_client. С 1.7.0 сначала штатный выход (15 с), потом
принудительный. На ERP вопрос «Exit the app?» сервер не распознает — в ответе
`shutdown: forced`, `shutdown_reason: shutdown_confirmation_required`; это норма.
## Режим наблюдения
Триггер — «хочу наблюдать», «хочу посмотреть», «сам покликаю». Смысл: пользователь
проверяет интерфейс глазами на готовой форме, а не поднимает базу и не повторяет
путь до неё руками.
Звать `launch_client` с обычным профилем проекта и `desktop='default'`.
Довести форму до нужного состояния и ОСТАНОВИТЬСЯ: не закрывать её и не звать
`stop_client`, пока пользователь не скажет.
Его ручные клики соединению не мешают: вызовы проходят без переподключения, ref
формы жив, свежий `get_context` показывает сделанные руками изменения. Устаревает
только ранее снятый снимок. Простой соединение не рвёт (с 1.7.0 сервер шлёт кадр
поддержания связи каждые 5 с).
Клиент, поднятый с `desktop='default'`, переживает конец сессии: он не в job-объекте,
в отличие от изолированного. Гасить его потом адресно по PID.
## Грабли
Каждая проверена на реальном прогоне, не выведена из документации. Номера с пропусками:
снятые грабли 5, 6, 10, 12, 13 лежат в `история-граблей.md`, а на номера ссылаются протоколы
прогонов. Грабли 1-4, 8, 11, 14 перепроверены на 1.7.1 2026-09-29 (стенд 1С:ERP 2.5), 7 и 9 — нет.
1. **Сразу после `launch_client` активного окна нет.** Любой вызов `tc_window` вернет
`no active window`. Ждать `tc_find` action=wait_for_object_displayed, cls=`ManagedForm`.
Класса `ClientApplicationWindow` на сервере НЕТ: ожидание его съедает весь таймаут
и возвращает `class_known: false`.
2. **Схема группы шире реальной сигнатуры действия.** Групповая схема принимает все
параметры группы, а конкретное действие отвергает лишние Python-ошибкой
`got an unexpected keyword argument`. Известные случаи: `read_rows` не принимает
`columns`, `choose_row` не принимает `row_column`/`row_value`/`column`/`conditions`
(сверено по коду 1.7.0). Точная сигнатура — грепом по `docs/TOOLS.md` сервера.
Общее правило: действия с таблицей работают по ТЕКУЩЕЙ строке, условие отбора
им передать нельзя. Строку сначала находят (`find_rows`, курсор не двигает),
потом встают на неё (`goto_row`), потом действуют (`choose_row` берёт только `ref`).
3. **В `set_fields` кладется ref поля ввода, а не ref формы** — иначе
`unsupported_element_type`. Сам адрес идет только внутри `entries`,
параметра `ref` верхнего уровня у действия нет.
4. **`goto_row` ищет вниз от текущей строки.** Строка выше курсора дает `found: false`
без ошибки. Для нее `direction`=`up`. В динамическом списке та же `found: false`
приходит на строку за пределами отрисованного окна, хотя `find_rows` ее находит;
лечится `goto_first_row` перед поиском.
7. **Отказ «read-only or its editability is unknown» склеивает два разных случая.**
Понять, чинить состояние формы или колонка нередактируема в принципе, по ответу
нельзя. Смотреть `ReadOnly` в `Form.xml` объекта.
8. **Ref живут в рамках окна.** После открытия новой формы искать элементы заново.
9. **Неожиданное окно проверять чтением формы.** Окно ошибки и окно предупреждения
из кода по ответу действия неразличимы (у обоих `title: 1С:Предприятие`, пустой `url`),
различает только `form_name`: `ErrorWindow` против `MessageBox`.
11. **Профиль годится только для запуска клиента.** `connect` с `profile` отвергается
как `profile_action_mismatch`: профиль описывает, как поднять клиент, а не как
подключиться к живому. Состояние выясняет `list_connections` — пусто значит
`launch_client`, есть подключение значит `connect` по порту и версии.
14. **Любую форму можно открыть по имени.** `e1cib/data/<Тип>.<Имя>.Форма.<ИмяФормы>`
без `?ref` открывает именно эту форму объекта или записи регистра, для новой формы
объекта — в режиме создания. Общие формы и формы обработок —
`e1cib/app/ОбщаяФорма.<Имя>` и `e1cib/app/Обработка.<Имя>.Форма.<ИмяФормы>`.
Проверено на 182 формах ERP 2026-09-23. Сверять `form_name` ответа с ожидаемым.
15. **`read_rows` на таблице неактивной вкладки вешает клиент.** На 1.7.1 в списке
номенклатуры ERP таблица `СписокКачества` (страница `СтраницаТоварыДругогоКачества`)
вернула одну пустую строку и `selection_cleanup_failed` через 30 с, дальше
«connection desynchronised» на всех вызовах. `disconnect` + `connect` НЕ лечит: после
переподключения `get_active_window` снова висит 30 с. Помогает только перезапуск
клиента. Воспроизведено на 1.7.0 и 1.7.1, 2026-09-29. Виснет шаг снятия выделения:
на 8.3 это `goto_row` без условия, и отдельный такой вызов виснет так же. Страница —
неактивная вкладка панели навигации `ВариантыНавигации`: код формы делает ее текущей
только в режиме «по товарам другого качества» (`ПодборТоваровСервер`). В сценариях не
встречается — наткнулись, когда скрипт выбрал не ту таблицу. Issue https://github.com/ROCTUP/1c-testpilot/issues/17
Таблицу выбирать по имени из `find_objects` cls=`Table`: у списка ERP их шесть, основная —
`СписокСтандартныйПоискНоменклатура`, а не `Список`.
## Повторяемый прогон: Python API и pytest
Агент проходит сценарий через MCP один раз, потом пишет скрипт или pytest-тест, который
гоняется без модели. Python API (`from testpilot import Client`) работает в процессе
скрипта и сам ходит в `/TESTCLIENT` по TCP, MCP-сервер не нужен. Документация и образцы —
в разделе «Окружение».
- **pytest — когда тестов несколько и у каждого свои проверки.** Fixture `testpilot`
поднимает клиент на каждый тест, это около 11 с. Прогон одного действия по списку
(формы) — простым скриптом с одним клиентом.
- **pytest стоит в `.venv` testpilot** (extra `[test]`). `TC1C_PROFILES_FILE` задавать
переменной окружения до запуска: плагин читает ее при загрузке.
- **Проверка данных — обработка Testpilot в профиле (`code_epf`), отдельным профилем.**
На 1.7.0 ее окно открывалось активным и ломало проверку стартовой страницы. На 1.7.1
после запуска активна стартовая страница (один прогон 2026-09-29), но запуск дольше
примерно на 11 с, поэтому в общие профили ее по-прежнему не добавлять.
- **В `execute_code` всегда запрещен идентификатор `Выполнить`**, в том числе
`Запрос.Выполнить()` — `forbidden_code`. Запросы — только `execute_query`. С 1.7.1
так же запрещен запуск программ (`ЗапуститьПриложение`, `КомандаСистемы`).
- **Дата параметром** — `{'$type': 'date', 'value': '2026-09-23T00:00:00'}`. Ссылку из
строки ответа запроса можно передать параметром в следующий запрос как есть.
Типы значений — README обработки Testpilot (см. «Окружение»).
- **`click` по «Провести» возвращается до конца проведения.** Заголовок окна еще
«(создание) *»; ждать номер в заголовке через `wait_until`.
- **Порты по назначению:** агенты — порт профиля, дымовой скрипт и pytest — свои
(раскладка — в «Окружении»). Иначе второй `launch_client` падает на занятом порту.
## Экономия контекста
- `tc_form` action=get_context на форме ERP возвращает 200+ элементов и стоит около
10 тыс. токенов, больше всех загруженных схем вместе. Звать один раз на разведку,
дальше искать точечно через `tc_find` action=find_objects по имени или классу.
- Регрессию гонять через снимки `tc_form`: сравнение крупной формы дает несколько
строк изменений вместо полного дерева, а само дерево остается на сервере.
- Заполнять пачками: `set_fields` берет до 100 полей за вызов, `add_rows` — строку
со всеми ячейками сразу.
## Проверяемость
Мутирующие действия возвращают `value_before` / `value_after` и признак `verified`,
поэтому отдельное чтение для подтверждения обычно не нужно. Сверка чисел идет как
`numeric_equivalent`: введенное `100` и показанное `100,00` расхождением не считаются.
`ok: true` означает лишь, что клиент принял команду. `close_window` с 1.7.1 проверяет
закрытие сам: форма с изменениями дает `window_not_closed`, и тогда ОБЯЗАТЕЛЬНО звать
`answer_dialog` — он возвращает текст вопроса и данный ответ. Пропуск этого шага оставляет
висеть модальный диалог, а `stop_client` потом просто убивает клиента, маскируя недоделанный шаг.
Скриншот (`tc_app` action=get_screenshot) — вспомогательный канал и приходит неполным
(`capture_complete: false`, всплывающие окна не попадают). Решения принимать по дереву.
## Ошибки прикладного кода
Поймал окно ошибки — зови `tc_app` action=get_current_error: в самом окне места падения нет.
С 1.7.1 он отвечает и на вложенной ошибке обработчика объекта, причина — в `details`.
- `error` заполнен — исключение в коде, там же модуль, строка и стек вызовов.
- `error` пуст — ошибка компиляции, модуль, строка и позиция лежат в элементе `ErrorInfo` окна.
## Известные ограничения окружения
- Изолированный рабочий стол (`desktop: isolated` в профиле) не мешает снимкам,
но у процесса пустой `MainWindowTitle` — искать его по командной строке, а не по заголовку.
- Клиент, поднятый не через `launch_client`, нельзя остановить через `stop_client`
(ответ `no client was launched through tc_launch_client`). К такому клиенту можно
подключиться через action=connect с указанием `port` и `version`, а гасить его
адресно по PID.