Claude Skill

1c-vanessa-steps

Реестр шагов Vanessa Automation (BDD для 1С:Предприятие) - 1569 шагов с описаниями, поиск шага по смыслу и валидация готового сценария .feature. Используй ВСЕГДА, когда пишешь, правишь или проверяешь сценарии Vanessa на Gherkin: модель уверенно сочиняет шаги, которых в Vanessa не

LLM Mart · 0 points · 14 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download Desko77-claude-code-skills-1c-skills_1c-vanessa-steps-ac33656.zip · 117 KB
Part of desko77/claude-code-skills-1c — 48 skills

Install

skills CLI npx skills add https://github.com/Desko77/claude-code-skills-1c/tree/main/skills/1c-vanessa-steps
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install desko77-claude-code-skills-1c@llmmart
Git git clone https://github.com/Desko77/claude-code-skills-1c.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole desko77/claude-code-skills-1c collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Шаги Vanessa Automation: поиск и валидация

Библиотека из 1569 шагов Vanessa Automation с описаниями и типами, плюс два инструмента: поиск подходящего шага и проверка готового сценария.

Зачем. Шаг Gherkin - это не свободный текст, а вызов конкретной реализации из Vanessa. Модель, не имея реестра, пишет правдоподобную фразу ("И я телепортирую документ в прошлое"), сценарий выглядит корректно и падает только на прогоне в базе - дорого и не сразу понятно. Реестр закрывает это на этапе написания.

When to Use

  • Пишешь новый сценарий Vanessa - сначала найди шаги, потом составляй из найденного.
  • Правишь чужой сценарий и не уверен, существует ли шаг.
  • Получил сценарий от заказчика или из другого проекта - проверь перед запуском.
  • Нужно понять, каким шагом делается конкретное действие (открыть форму, заполнить поле, проверить значение, подключить TestClient, работать с файлами).

When NOT to Use

  • Юнит-тесты на YaXUnit - это не Gherkin, реестр к ним не относится (см. testing-patterns.md).
  • Отладка падающего прогона, где шаг ЕСТЬ, но ведет себя не так - это про данные и конфигурацию, а не про реестр шагов.

Порядок работы

1. Найти шаг вместо того, чтобы его сочинить

python skills/1c-vanessa-steps/scripts/search_steps.py --query "открыть форму документа" --top 5 --format human
python skills/1c-vanessa-steps/scripts/search_steps.py --category UI --subcategory Формы --format human
python skills/1c-vanessa-steps/scripts/search_steps.py --stats

Запрос - на русском, словами задачи. Вывод --format json удобен для дальнейшей обработки, human - для чтения.

Критерий: каждый шаг будущего сценария найден в реестре. Не нашел - это сигнал, что действие делается иначе (другим шагом или последовательностью), а НЕ разрешение придумать формулировку.

2. Собрать сценарий

Структура файла, обязательные строки заголовка, типовые связки - references/scenario-templates.md. Правила формулировок и работа с параметрами шагов - references/writing-scenarios.md.

3. Проверить перед сдачей

python skills/1c-vanessa-steps/scripts/validate.py "путь/к/сценарию.feature"

Отчет дает: сколько шагов валидно, какие строки не найдены в реестре, и рекомендации. Ошибки структуры (нет строки языка или кодировки) он тоже ловит.

Критерий завершения: невалидных шагов ноль. Один невалидный шаг - сценарий не готов, даже если остальные девяносто девять на месте.

4. Разбор спорного случая

Когда шаг похож на существующий, но не совпадает дословно, помогает разбор по смыслу: references/semantic-validation.md - как сопоставляются параметры и почему близкий по виду шаг может быть другим шагом.

Состав

Путь Что
data/steps-library.json реестр: 1569 шагов, 13 категорий, 374 подкатегории
scripts/search_steps.py поиск по запросу, категории, подкатегории; статистика
scripts/validate.py проверка .feature: существование шагов и структура файла
scripts/indexer.py построение индексов для ускорения поиска на больших прогонах
references/steps-compact.md весь реестр компактным списком - когда нужен просмотр глазами
references/writing-scenarios.md как писать сценарии: формулировки, параметры, типовые ошибки
references/scenario-templates.md готовые шаблоны сценариев
references/semantic-validation.md сопоставление шагов по смыслу, разбор параметров

Зависимостей нет - только стандартная библиотека Python 3.8+.

Ограничения

  • Реестр - снимок библиотеки шагов на момент сборки. Своя сборка Vanessa, плагины или шаги из подключенных библиотек проекта в нем отсутствуют: шаг, не найденный валидатором, может быть проектным. Проверять по своей поставке, а не считать вывод валидатора приговором.
  • Валидатор проверяет СУЩЕСТВОВАНИЕ шага и структуру файла, а не осмысленность сценария. Логику проверки, порядок шагов и тестовые данные он не оценивает.

Источник

Реестр шагов и инструменты поиска и валидации взяты из проекта va-ai (MIT, (c) Nikolay Shirokov) и адаптированы под раскладку этого набора. Сам Vanessa Automation - отдельный проект, см. vanessa-automation.

Files (claude-code-skills-1c)
  • data
    • steps-library.json 694.2 KB
      [
        {
          "ИмяШага": "И Я устанавливаю название фичи 'Моя дивная чудная галактика'",
          "ОписаниеШага": "Задает для сценария название фичи в отчете Allure",
          "ПолныйТипШага": "Прочее.Allure"
        },
        {
          "ИмяШага": "Когда Я устанавливаю название эпика 'Моя дивная чудная вселенная'",
          "ОписаниеШага": "Задает для сценария название эпика в отчете Allure",
          "ПолныйТипШага": "Прочее.Allure"
        },
        {
          "ИмяШага": "И Я устанавливаю название истории 'Мой дивный чудный мир'",
          "ОписаниеШага": "Задает для сценария название истории в отчете Allure",
          "ПолныйТипШага": "Прочее.Allure"
        },
        {
          "ИмяШага": "И Я устанавливаю метке 'Метка' значение 'Значение'",
          "ОписаниеШага": "Добавляет к сценарию произвольную метку со значением в отчет Allure",
          "ПолныйТипШага": "Прочее.Allure"
        },
        {
          "ИмяШага": "И я устанавливаю описание сценария для отчета Allure 'Описание'",
          "ОписаниеШага": "Устанавливает описания сценария в отчете Allure",
          "ПолныйТипШага": "Прочее.Allure"
        },
        {
          "ИмяШага": "И я запоминаю значение поля \"ИмяПоля\" в буфер обмена",
          "ОписаниеШага": "Помещает значение, типа текст или двоичные данные, указанного поля в буфер обмена. Необходимо включить использование компоненты VanessaExt. Поле ищется по заголовку.",
          "ПолныйТипШага": "Прочее.Буфер обмена"
        },
        {
          "ИмяШага": "И я запоминаю значение поля с именем \"ИмяПоля\" в буфер обмена",
          "ОписаниеШага": "Помещает значение, типа текст или двоичные данные, указанного поля в буфер обмена. Необходимо включить использование компоненты VanessaExt.  Поле ищется по имени.",
          "ПолныйТипШага": "Прочее.Буфер обмена"
        },
        {
          "ИмяШага": "И я вставляю текстовое значение буфера обмена в поле с именем \"\" ",
          "ОписаниеШага": "Помещает текстовое значение буфера обмена в указанное поле. Необходимо включить использование компоненты VanessaExt.",
          "ПолныйТипШага": "Прочее.Буфер обмена"
        },
        {
          "ИмяШага": "И я запоминаю значение буфера обмена в переменную \"ИмяПеременной\"",
          "ОписаниеШага": "Запоминает значение буфера обмена в переменную. Необходимо включить использование компоненты VanessaExt.",
          "ПолныйТипШага": "Прочее.Буфер обмена"
        },
        {
          "ИмяШага": "И в таблице \"ИмяТаблицы\" я запоминаю значение поля \"Заголовок поля\" в буфер обмена",
          "ОписаниеШага": "Помещает значение, типа текст или двоичные данные, указанного поля в буфер обмена. Необходимо включить использование компоненты VanessaExt. Поле ищется по заголовку.",
          "ПолныйТипШага": "Прочее.Буфер обмена"
        },
        {
          "ИмяШага": "И в таблице \"ИмяТаблицы\" я запоминаю значение поля с именем \"ИмяПоля\" в буфер обмена",
          "ОписаниеШага": "Помещает значение, типа текст или двоичные данные, указанного поля в буфер обмена. Необходимо включить использование компоненты VanessaExt.  Поле ищется по имени.",
          "ПолныйТипШага": "Прочее.Буфер обмена"
        },
        {
          "ИмяШага": "И в окне создания фотографии я делаю отмену",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И в окне создания фотографии я делаю фото",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И в окне создания фотографии я включаю вспышку",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И в окне создания фотографии я меняю камеру",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И в окне создания фотографии я подтверждаю фото",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И в мобильном я нажимаю на координаты \"Х\" и \"У\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И в мобильном я нажимаю на относительные координаты \"Х\" и \"У\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "Затем клик на картинку \"ИмяКартинки\"",
          "ОписаниеШага": "Эмулирует клик левой кнопкой мышки по картинке. Картинка, поиск которой будет происходить на экране, должна находится в каталоге проекта.",
          "ПолныйТипШага": "Прочее.VanessaExt.Клик на картинку"
        },
        {
          "ИмяШага": "Дано клик правой кнопкой на картинку \"ИмяКартинки\"",
          "ОписаниеШага": "Эмулирует клик правой кнопкой мышки по картинке. Картинка, поиск которой будет происходить на экране, должна находится в каталоге проекта.",
          "ПолныйТипШага": "Прочее.VanessaExt.Клик на картинку"
        },
        {
          "ИмяШага": "Дано курсор к картинке \"ИмяКартинки\"",
          "ОписаниеШага": "Эмулирует перемещение курсора мышки к картинке. Картинка, поиск которой будет происходить на экране, должна находится в каталоге проекта.",
          "ПолныйТипШага": "Прочее.VanessaExt.Курсор к картинке"
        },
        {
          "ИмяШага": "Дано двойной клик на картинку \"ИмяКартинки\"",
          "ОписаниеШага": "Делает двойной клик по картинке. Картинка, поиск которой будет происходить на экране, должна находится в каталоге проекта.",
          "ПолныйТипШага": "Прочее.VanessaExt.Клик на картинку"
        },
        {
          "ИмяШага": "Затем я рисую стрелку от \"Картинка1\" до \"Картинка2\"\n\t\t|'Цвет'          | 'Зеленый'        |\n\t\t|'Длительность'  | '2000'           |\n\t\t|'ТочкаКуда'     | 'СерединаНиз'    |\n\t\t|'Толщина'  \t | '10'           \t|",
          "ОписаниеШага": "Рисует стрелку от одной картинки до другой. Поиск картинок на экране делается с помощью SikuliX. Если первый параметр равен пустой сроке, тогда точкой \"от\" будет центр экрана. Дополнительные параметры задаются через таблицу Gherkin. \n- Цвет (Color) - строка, из перечисления WebЦвета, например \"Зеленый\".\n- Длительность (Duration) - сколько милисекунд стрелка будет находится на экране, например - 2000.\n- ТочкаКуда (PointTo), ТочкаОткуда (PointFrom) - место, в которое будет указывать/исходить стрелка. Возможные значения: СерединаНиз (MiddleDown), СерединаВерх (MiddleUp), СерединаПраво (MiddleRight), СерединаЛево (MiddleLeft), Центр (Center).\n- Толщина (Width) - толщина стрелки, например - 10.",
          "ПолныйТипШага": "Прочее.VanessaExt.Стрелка к картинке"
        },
        {
          "ИмяШага": "И я жду картинки \"ИмяКартинки\" в течение 20 секунд",
          "ОписаниеШага": "Ожидает появления картинки на экране в течение нужного числа секунд.",
          "ПолныйТипШага": "Прочее.VanessaExt.Ожидание появления картинки"
        },
        {
          "ИмяШага": "И я определяю координаты картинки \"ИмяКартинки\" как \"ИмяПеременной\"",
          "ОписаниеШага": "Определяет коордианты картинки с помощью SikuliX и сохраняет структуру с координатами в переменную",
          "ПолныйТипШага": "Прочее.VanessaExt.Определение координат картинки"
        },
        {
          "ИмяШага": "И я рисую рамку вокруг картинки \"ИмяКартинки\"\n\t\t|'Цвет'          | 'Зеленый'        |\n\t\t|'Длительность'  | '2000'           |\n\t\t|'Прозрачность'\t | '127'           \t|\n\t\t|'Толщина'  \t | '3'           \t|",
          "ОписаниеШага": "Рисует рамку вокруг картинки.\n- Цвет (Color) - строка, из перечисления WebЦвета, например \"Зеленый\".\n- Цвет (Color) - строка, из перечисления WebЦвета, например \"Зеленый\".\n- Прозрачность (Transparency) - прозрачный эффект, изменяется от 0 до 255.\n- Толщина (Width) - толщина рамки, например - 3.",
          "ПолныйТипШага": "Прочее.VanessaExt.Рамка вокруг картинки"
        },
        {
          "ИмяШага": "Если есть картинка \"ИмяКартинки\" Тогда",
          "ОписаниеШага": "Условие. Проверяет, есть ли на экране нужная картинка.",
          "ПолныйТипШага": "Прочее.VanessaExt.Проверка существования картинки"
        },
        {
          "ИмяШага": "И я перетаскиваю картинку \"ИмяКартинки1\" к \"ИмяКартинки2\"",
          "ОписаниеШага": "Эмулирует Drag-and-drop от \"ИмяКартинки1\" к \"ИмяКартинки2\". Картинки, поиск которых будет происходить на экране, должны находится в каталоге проекта.",
          "ПолныйТипШага": "Прочее.VanessaExt.Перетаскивание картинки"
        },
        {
          "ИмяШага": "И я показываю подсказку \"ТекстПодсказки\" EnjoyHint у картинки \"ИмяКартинки\" на экране\n\t\t|'Длительность'  | '2000'           |\n\t\t|'Прозрачность'\t | '127'           \t|\n\t\t|'Толщина'  \t | '3'           \t|",
          "ОписаниеШага": "Рисует фигуры на экране. Происходит затемнение экрана, кроме указанной картинки. Аналог эффекта EnjoyHint в браузере.",
          "ПолныйТипШага": "Прочее.VanessaExt.Универсальный EnjoyHint"
        },
        {
          "ИмяШага": "И я показываю на экране текст \"Произвольный текст\"\n\t\t|'X'             | '800'          |\n\t\t|'Y'             | '600'          |\n\t\t|'Длительность'  | '2000'         |\n\t\t|'Шрифт'         | 'arial'        |\n\t\t|'Цвет'          | 'red'          |\n\t\t|'Размер'        | '13'           |",
          "ОписаниеШага": "Отображает произвольный текст на экране по указанным координатам.",
          "ПолныйТипШага": "Прочее.VanessaExt.Вывод текста"
        },
        {
          "ИмяШага": "И я получаю координаты картинки \"ИмяКартинки\"",
          "ОписаниеШага": "Получает координаты картинки и сохраняет их в переменные left,top,right,bottom,height,width, которые определяют прямоугольник картинки.",
          "ПолныйТипШага": "Прочее.VanessaExt.Получить координаты картинки"
        },
        {
          "ИмяШага": "И я делаю эффект затемнения у картинки \"ИмяКартинки\"\n\t| 'Имя'          | 'Значение'  |\n\t| 'text'         | 'Мой текст' |\n\t| 'color'        | 32768       |\n\t| 'transparency' | 127         |\n\t| 'duration'     | 4000        |\n\t| 'thickness'    | 6           |\n\t| 'frameDelay'   | 20          |",
          "ОписаниеШага": "Выполняет поиск картинки на экране и создаёт на экране эффект затемнения вокруг неё.\nТаблица параметров является необязательной.\nВ ней передаются параметры отрисовки стрелки.\nТакже все параметры таблицы являются необязательными.",
          "ПолныйТипШага": "Прочее.VanessaExt.Эффект затемнения у картинки"
        },
        {
          "ИмяШага": "Когда Я открываю форму документа \"АвтоВзаимозачет\" заполненного на основании проведенного \"ПоступлениеАвтомобилей\" номер \"АИ00000002\" от \"12.03.2017\"",
          "ОписаниеШага": "Открытие формы на основании проведенного документа",
          "ПолныйТипШага": "UI.Формы.Ввод на основании"
        },
        {
          "ИмяШага": "И я открываю форму документа \"АвтоВзаимозачет\" заполненного на основании проведенного \"ПоступлениеАвтомобилей\"",
          "ОписаниеШага": "Открытие формы на основании проведенного документа",
          "ПолныйТипШага": "UI.Формы.Ввод на основании"
        },
        {
          "ИмяШага": "Когда Я открываю форму документа \"АвтоВзаимозачет\" заполненного на основании не проведенного \"ПоступлениеАвтомобилей\" номер \"АВ00000080\" от \"04.09.2016\"",
          "ОписаниеШага": "Открытие формы на основании не проведенного документа",
          "ПолныйТипШага": "UI.Формы.Ввод на основании"
        },
        {
          "ИмяШага": "И я открываю форму документа \"АвтоВзаимозачет\" заполненного на основании не проведенного \"ПоступлениеАвтомобилей\"",
          "ОписаниеШага": "Открытие формы на основании не проведенного документа",
          "ПолныйТипШага": "UI.Формы.Ввод на основании"
        },
        {
          "ИмяШага": "Когда Я удаляю пользователя \"ИмяПользователя\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "Когда в базе есть пользователь \"VanessaUser1\" с паролем \"1\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "Когда в базе есть пользователь \"VanessaUser1\" с паролем \"1\" с языком \"en\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И    заполняю список таблицы ДанныеКлиентовТестирования",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "Когда Я подключаю клиент тестирования с параметрами:\n    | 'Имя'             | 'Синоним'        | 'Тип клиента' |  'Порт' | 'Строка соединения'                     | 'Логин' | 'Пароль' | 'Запускаемая обработка' |  'Дополнительные параметры строки запуска'  |\n    | 'ИмяПрофиля'      | 'СинонимПрофиля' | 'Тонкий'      |  '1538' | 'Srvr=\"localhost:1941\";Ref=\"Test\";' | 'Админ' | 'Админ'  | 'c:\\СуперОбработка.epf' |  '/UC'                                      |",
          "ОписаниеШага": "Подключает TestClient. Все параметры подключения указываются в таблице. Запускает новый сеанс, если не найден запущенный ранее.\nЕсли нужно переподключить уже запущеный клиент тестирования нельзя передавать в шаг значение порта.",
          "ПолныйТипШага": "Подключение TestClient.Новое подключение к базе.Произвольная база"
        },
        {
          "ИмяШага": "Когда Я подключаю клиент тестирования с параметрами один на информационную базу:\n    | 'Имя'             | 'Синоним'        | 'Порт' | 'Строка соединения'                     | 'Логин' | 'Пароль' | 'Запускаемая обработка' |  'Дополнительные параметры строки запуска'  |\n    | 'ИмяПрофиля'      | 'СинонимПрофиля' | '1538' | 'Srvr=\"localhost:1941\";Ref=\"Test\";' | 'Админ' | 'Админ'  | 'c:\\СуперОбработка.epf' |  '/UC'                                      |",
          "ОписаниеШага": "Подключает TestClient. Все параметры подключения указываются в таблице. Запускает новый сеанс, если не найден запущенный ранее. Все другие сеансы с информационной базой предварительно закрываются.\nЕсли нужно переподключить уже запущеный клиент тестирования нельзя передавать в шаг значение порта.",
          "ПолныйТипШага": "Подключение TestClient.Новое подключение к базе.Произвольная база"
        },
        {
          "ИмяШага": "Затем Я подключаю уже запущенный клиент тестирования \"ИмяПрофиля\"",
          "ОписаниеШага": "Подключает запущенный ранее клиент тестирования, который мог быть запущен из другой программы. Если сеанс не найден - выдаёт исключение.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Активизировать TestClient"
        },
        {
          "ИмяШага": "Затем Я подключаю уже запущенный клиент тестирования \"ИмяПрофиля\" в течение 10 секунд",
          "ОписаниеШага": "Подключает запущенный ранее клиент тестирования, который мог быть запущен из другой программы в течение указанного числа секунд. Если сеанс не найден - выдаёт исключение.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Активизировать TestClient"
        },
        {
          "ИмяШага": "Когда Я подключаю уже запущенный клиент тестирования с параметрами:\n    | 'Имя'             | 'Синоним'        | 'Порт' | 'Строка соединения'                     | 'Логин' | 'Пароль' | 'Запускаемая обработка' |  'Дополнительные параметры строки запуска'  |\n    | 'ИмяПрофиля'      | 'СинонимПрофиля' | '1538' | 'Srvr=\"localhost:1941\";Ref=\"Test\";' | 'Админ' | 'Админ'  | 'c:\\СуперОбработка.epf' |  '/UC'                                      |",
          "ОписаниеШага": "Подключает запущенный ранее клиент тестирования, который мог быть запущен из другой программы. Если сеанс не найден - выдаёт исключение.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Активизировать TestClient"
        },
        {
          "ИмяШага": "Когда Я подключаю уже запущенный клиент тестирования с параметрами в течение 10 секунд:\n    | 'Имя'             | 'Синоним'        | 'Порт' | 'Строка соединения'                     | 'Логин' | 'Пароль' | 'Запускаемая обработка' |  'Дополнительные параметры строки запуска'  |\n    | 'ИмяПрофиля'      | 'СинонимПрофиля' | '1538' | 'Srvr=\"localhost:1941\";Ref=\"Test\";' | 'Админ' | 'Админ'  | 'c:\\СуперОбработка.epf' |  '/UC'                                      |",
          "ОписаниеШага": "Подключает запущенный ранее клиент тестирования, который мог быть запущен из другой программы. Если сеанс не найден - выдаёт исключение.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Активизировать TestClient"
        },
        {
          "ИмяШага": "Когда Я копирую текущий профиль TestClient с установкой параметров:\n    | 'Имя'             | 'Синоним'        | 'Тип клиента' | 'Порт' | 'Строка соединения'                     | 'Логин' | 'Пароль' | 'Запускаемая обработка' |  'Дополнительные параметры строки запуска'  |\n    | 'ИмяПрофиля'      | 'СинонимПрофиля' | 'Тонкий'      | '1538' | 'Srvr=\"localhost:1941\";Ref=\"Test\";' | 'Админ' | 'Админ'  | 'c:\\СуперОбработка.epf' |  '/UC'                                      |",
          "ОписаниеШага": "Копирует текущий активный профиль TestClient и устанавилвает в копии новые значение параметров. Если профиль с требуемым именем уже есть, то он будет перезаписан, иначе будет создан новый. Все параметры подключения указываются в таблице Gherkin, в которой должно быть две строки: первая строка с именами параметров, вторая строка со значениями параметров.",
          "ПолныйТипШага": "Подключение TestClient.Новое подключение к базе.Произвольная база"
        },
        {
          "ИмяШага": "И я закрываю сеанс TESTCLIENT",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я закрываю сеанс текущего клиента тестирования",
          "ОписаниеШага": "Закрывает активный сеанс клиента тестирования, если он есть. При этом происходит попытка закрыть модальные диалоги, если они появляются.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Закрыть сеанс TestClient"
        },
        {
          "ИмяШага": "И я закрываю главное окно TestClient",
          "ОписаниеШага": "Закрывает главное окно TestClient. Равносильно нажатию на крестик на главном окне приложения. Обработку диалогов, которые могут возникнуть при закрыти приложения, должен обеспечить сам сценарий.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Закрыть главное окно сеанса"
        },
        {
          "ИмяШага": "И я подключаю TestClient \"ИмяКлиента\" логин \"Логин\" пароль \"Пароль\"",
          "ОписаниеШага": "Позволяет подключить TestClient с нужным логином и паролем. Если указанный профиль уже существует, то будет использован переданный профиль. Если такого профиля нет, то он будет создан на основе текущего подключенного клиента тестирования (если он есть) или профиля \"Этот клиент\", который в себе содержит строку подключения к тойже базе, в которой запущен TestManager",
          "ПолныйТипШага": "Подключение TestClient.Новое подключение к базе.Текущая база"
        },
        {
          "ИмяШага": "И я активизирую TestClient \"ИмяПрофиляTestClient\"",
          "ОписаниеШага": "Позволяет переключить контекст на нужный TestClient. Поиск TestClient в таблице подключений просиходит по имени или синониму.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Активизировать TestClient"
        },
        {
          "ИмяШага": "И я закрываю TestClient \"ИмяПрофиляTestClient\"",
          "ОписаниеШага": "Закрывает указанный TestClient Перед этим закрывает все окна в нём.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Закрыть сеанс TestClient"
        },
        {
          "ИмяШага": "И я подключаю профиль TestClient \"ИмяПрофиляTestClient\"",
          "ОписаниеШага": "Позволяет подключить TestClient по имени уже существующего профиля",
          "ПолныйТипШага": "Подключение TestClient.Новое подключение к базе.Произвольная база"
        },
        {
          "ИмяШага": "И я подключаю профиль TestClient \"ИмяПрофиляTestClient\" один на информационную базу",
          "ОписаниеШага": "Позволяет подключить TestClient по имени уже существующего профиля, все другие сеансы с информационной базой предварительно закрываются",
          "ПолныйТипШага": "Подключение TestClient.Новое подключение к базе.Произвольная база"
        },
        {
          "ИмяШага": "Дано Я открыл новый сеанс TestClient или подключил уже существующий",
          "ОписаниеШага": "Открывает новый Клиент тестирования для профиля указанного в текущей строке таблицы клиентов тестирования. Если строка в таблице клиентов тестирования не менялась, то активной строкой будет первая строка, в которой всегда содержится профиль для подключения к той же базе, в которой запущен сеанс Менеджера тестирования.",
          "ПолныйТипШага": "Подключение TestClient.Новое подключение к базе.Текущая база"
        },
        {
          "ИмяШага": "Дано Я открыл новый сеанс TestClient или подключил уже существующий без загрузки настроек",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "Дано Я открыл новый сеанс TestClient как TestManager или подключил уже существующий без загрузки настроек",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "Дано Я открыл сеанс TestClient от имени \"МойПользователь\" с паролем \"МойПароль\" или подключаю уже существующий",
          "ОписаниеШага": "Позволяет подключить TestClient с нужным логином и паролем в тойже базе, в которой запущен TestManager, с помощью профиля \"Этот клиент\". При этом предыдущие подключения к этому профилю должны быть закрыты.",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я устанавливаю текущему TestClient синоним \"МойСиноним\"",
          "ОписаниеШага": "Позволяет подключить указать дополнительный синоним для текущего профиля TestClient. Синоним может использовать как дополнительное имя сеанса TestClient.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Установить синоним"
        },
        {
          "ИмяШага": "И в таблице клиентов тестирования я активизирую строку 'ИмяКлиентаТестирования'",
          "ОписаниеШага": "Актвизирует строку с нужным клиентом тестирования. Поиск строки просходит с начала по имени, затем по синониму.",
          "ПолныйТипШага": "Подключение TestClient.Прочее.Активизировать строку"
        },
        {
          "ИмяШага": "И я читаю данные текущего профиля клиента тестирования",
          "ОписаниеШага": "Считывает данные текущего профиля (активная строка в таблице клиентов тестирования) клиента тестировани в переменные: ИмяКлиентаТестирования, СинонимКлиентаТестирования, ТипКлиентаТестирования, ПутьКИнфобазеКлиентаТестирования, ПортЗапускаКлиентаТестирования, ДопПараметрыКлиентаТестирования, ИмяКомпьютераКлиентаТестирования, PIDКлиентаТестирования, ДескрипторОкнаКлиентаТестирования",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Чтение данных клиента тестирования"
        },
        {
          "ИмяШага": "И я активизирую окно текущего клиента тестирования",
          "ОписаниеШага": "Активизирует главное окно текущего клиента тестирования. Использует VanessaExt.",
          "ПолныйТипШага": "Подключение TestClient.Работа с подключенными TestClient.Активизировать TestClient"
        },
        {
          "ИмяШага": "Дано Я запускаю сценарий открытия TestClient или подключаю уже существующий",
          "ОписаниеШага": "Подключает новый TestClient если необходимо и закрывает в нём все окна.",
          "ПолныйТипШага": "Подключение TestClient.Новое подключение к базе.Текущая база"
        },
        {
          "ИмяШага": "И я делаю клик по элементу клиента тестирования 'Заголовок' 'Тип' UI Automation",
          "ОписаниеШага": "Выполняет поиск элемента с помощью механизма UI Automation. Только под Windows. Необходимо включить использование компоненты VanessaExt. Нужно обязательно передать параметр 'Заголовок'. Параметр 'Тип' необязательный.",
          "ПолныйТипШага": "Прочее.UI Automation.Клик по элементу"
        },
        {
          "ИмяШага": "И я делаю клик по элементу формы 'PID' 'Заголовок' 'Тип' UI Automation",
          "ОписаниеШага": "Выполняет поиск элемента с помощью механизма UI Automation и делает клик по нему левой кнопкой мышки. Только под Windows. Необходимо включить использование компоненты VanessaExt. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation. Нужно обязательно передать параметр 'Заголовок'. Параметр 'Тип' необязательный.",
          "ПолныйТипШага": "Прочее.UI Automation.Клик по элементу"
        },
        {
          "ИмяШага": "И я делаю клик по элементу формы 'PID' 'Заголовок' 'Тип' правой кнопкой UI Automation",
          "ОписаниеШага": "Выполняет поиск элемента с помощью механизма UI Automation и делает клик по нему правой кнопкой мышки. Только под Windows. Необходимо включить использование компоненты VanessaExt. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation. Нужно обязательно передать параметр 'Заголовок'. Параметр 'Тип' необязательный.",
          "ПолныйТипШага": "Прочее.UI Automation.Клик по элементу"
        },
        {
          "ИмяШага": "И я делаю двойной клик по элементу формы 'PID' 'Заголовок' 'Тип' UI Automation",
          "ОписаниеШага": "Выполняет поиск элемента с помощью механизма UI Automation. Только под Windows. Необходимо включить использование компоненты VanessaExt. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation. Нужно обязательно передать параметр 'Заголовок'. Параметр 'Тип' необязательный.",
          "ПолныйТипШага": "Прочее.UI Automation.Клик по элементу"
        },
        {
          "ИмяШага": "И я делаю клик по элементу формы 'PID' 'Подсказка' UI Automation. Поиск по подсказке",
          "ОписаниеШага": "Выполняет поиск элемента с помощью механизма UI Automation. Только под Windows. Необходимо включить использование компоненты VanessaExt. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation. Нужно обязательно передать параметр 'Подсказка'.",
          "ПолныйТипШага": "Прочее.UI Automation.Клик по элементу"
        },
        {
          "ИмяШага": "И я рисую рамку вокруг элементов формы UI Automation \"PID\"\n\t| 'Имя/ID' | 'Тип' |\n\t| 'Поле1'  | 'Тип1' |\n\t| 'Поле2'  | 'Тип2' |\n\n\t| 'Имя'          | 'Значение' |\n\t| 'color'        | 32768      |\n\t| 'transparency' | 127        |\n\t| 'duration'     | 4000       |\n\t| 'thickness'    | 6          |\n\t| 'frameDelay'   | 20         |",
          "ОписаниеШага": "Рисует рамку вокруг элементов, которые переданы таблицей. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation. Вторая таблица является необязательной. В ней передаются параметры отрисовки прямоугольника.",
          "ПолныйТипШага": "Прочее.UI Automation.Эффекты"
        },
        {
          "ИмяШага": "И я рисую стрелку к элементам формы UI Automation \"PID\"\n\t| 'Имя/ID' | 'Тип' |\n\t| 'Поле1'  | 'Тип1' |\n\t| 'Поле2'  | 'Тип2' |\n\n\t| 'Имя'          | 'Значение' |\n\t| 'color'        | 32768      |\n\t| 'transparency' | 127        |\n\t| 'duration'     | 4000       |\n\t| 'thickness'    | 6          |\n\t| 'frameDelay'   | 20         |",
          "ОписаниеШага": "Рисует стрелку к элементам, которые переданы таблицей. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation. Вторая таблица является необязательной. В ней передаются параметры отрисовки стрелки.",
          "ПолныйТипШага": "Прочее.UI Automation.Эффекты"
        },
        {
          "ИмяШага": "И я делаю эффект затемнения у элементов формы UI Automation \"PID\"\n\t| 'Имя/ID' | 'Тип' |\n\t| 'Поле1'  | 'Тип1' |\n\t| 'Поле2'  | 'Тип2' |\n\n\t| 'Имя'          | 'Значение'  |\n\t| 'text'         | 'Мой текст' |\n\t| 'color'        | 32768       |\n\t| 'transparency' | 127         |\n\t| 'duration'     | 4000        |\n\t| 'thickness'    | 6           |\n\t| 'frameDelay'   | 20          |",
          "ОписаниеШага": "Создаёт на экране эффект затемнения с текстовой подсказкой к элементам, которые переданы таблицей. В первой колонке можно указывать заголовок элемента или его ID. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation. Вторая таблица является необязательной. В ней передаются параметры отрисовки стрелки.",
          "ПолныйТипШага": "Прочее.UI Automation.Эффекты"
        },
        {
          "ИмяШага": "И я делаю эффект затемнения у элементов формы UI Automation \"PID\" по подсказке\n\t| 'Подсказка' |\n\t| 'Текст'     |\n\n\t| 'Имя'          | 'Значение'  |\n\t| 'text'         | 'Мой текст' |\n\t| 'color'        | 32768       |\n\t| 'transparency' | 127         |\n\t| 'duration'     | 4000        |\n\t| 'thickness'    | 6           |\n\t| 'frameDelay'   | 20          |",
          "ОписаниеШага": "Создаёт на экране эффект затемнения с текстовой подсказкой к элементам, которые переданы таблицей. В первой колонке нужно указывать подсказку элемента формы. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation. Вторая таблица является необязательной. В ней передаются параметры отрисовки стрелки.",
          "ПолныйТипШага": "Прочее.UI Automation.Эффекты"
        },
        {
          "ИмяШага": "И я запоминаю родителя элемента \"Заголовок/ID\" с типом \"Тип\" процесса \"PID\" в переменную \"ИмяПеременной\" UI Automation",
          "ОписаниеШага": "Запоминает ID родителя элемента с указанным заголовком/ID и типом в переменную. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation.",
          "ПолныйТипШага": "Прочее.UI Automation.Запомнить родителя элемента"
        },
        {
          "ИмяШага": "И я запоминаю элемент \"Заголовок\" с типом \"Тип\" процесса \"PID\" в переменную \"ИмяПеременной\" UI Automation",
          "ОписаниеШага": "Запоминает ID элемента с указанным заголовком и типом в переменную. В параметр PID можно передать строку \"Этот сеанс\", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation.",
          "ПолныйТипШага": "Прочее.UI Automation.Запомнить ID элемента"
        },
        {
          "ИмяШага": "И я делаю подсветку элементов VA UI Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю подсветку элементов VA с их заголовком UI Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я запоминаю ID элемента формы Vanessa Automation с именем \"ИмяПоля\" в переменную \"ИмяПеременной\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю клик в поле \"ID\" UI Automation",
          "ОписаниеШага": "Делает клик по полю с указанным ID.",
          "ПолныйТипШага": "Прочее.UI Automation.Клик по элементу"
        },
        {
          "ИмяШага": "И я делаю подсказку балун у элемента \"$IDЭлемента$\" UI Automation\n\t| 'Имя'        | 'Значение' |\n\t| 'color'      | 3289800    |\n\t| 'background' | 15793151   |\n\t| 'fontColor'  | 0          |\n\t| 'fontName'   | 'Calibri'  |\n\t| 'fontSize'   | 12         |\n\t| 'duration'   | 3000       |\n\t| 'thickness'  | 4          |\n\t| 'text'       | 'Текст'    |\n\t| 'radius'     | 5          |",
          "ОписаниеШага": "Показывает подсказку балун для элемента с заданным ID и параметрам переданным в таблице. Все параметры являются необязательными и имеют значение по умолчанию.",
          "ПолныйТипШага": "Прочее.UI Automation.Эффекты"
        },
        {
          "ИмяШага": "И я перемещаю мышку к каждой кнопке панели \"$IDПанели$\" Vanessa Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я запоминаю элемент с типом \"Тип\" c родителем \"$IDЭлемента$\" в переменную \"IDЭлемента\" Vanessa Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я запоминаю родителя элемента с именем \"Имя\" в переменную \"ИмяПеременной\" Vanessa Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я запоминаю родителя родителя элемента с именем \"ИмяЭлемента\" в переменную \"IDРодителя\" Vanessa Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я запоминаю элемент с именем \"Имя\" в переменную \"ИмяПеременной\" Vanessa Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я запоминаю элемент UI Automation в переменную \"ИмяПеременной\"\n\t| 'Имя'                               | 'Значение'   |\n\t| 'Name'                              | 'Заголовок'  |\n\t| 'ProcessId'                         | 'ЭтотСеанс'  |\n\t| 'Parent'                            | 'IdРодителя' |\n\t| 'ControlType'                       | 'Тип'        |\n\t| 'HelpText'                          | 'Подсказка'  |\n\t| 'LegacyIAccessibleKeyboardShortcut' | 'Хоткей'     |",
          "ОписаниеШага": "Запоминает ID элемента UI Automation согласно переданным параметрам в таблице",
          "ПолныйТипШага": "Прочее.UI Automation.Запомнить ID элемента"
        },
        {
          "ИмяШага": "И я делаю клик по элементу \"ID\" UI Automation",
          "ОписаниеШага": "Делает клик по элементу UI Automation с переданным ID.",
          "ПолныйТипШага": "Прочее.UI Automation.Клик по элементу"
        },
        {
          "ИмяШага": "И Я делаю подсветку группы элементов VA \"ГруппаVanessaEditorВыполнениеСценариев\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю клик по элементу формы VA UI Automation 'ЭтотСеанс' 'ИмяЭлемента' правой кнопкой UI Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю клик по элементу формы VA UI Automation 'ЭтотСеанс' 'ИмяЭлемента' UI Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю клик по элементу формы VA UI Automation 'ЭтотСеанс' 'ИмяЭлемента'",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я перемещаю курсор к элементу формы VA UI Automation 'ЭтотСеанс' 'ИмяЭлемента' UI Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я перемещаю курсор к элементу формы VA UI Automation 'ЭтотСеанс' 'ИмяЭлемента'",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И служебный шаг UI Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я перемещаю курсор к заголовку Vanessa Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю клик по заголовку Vanessa Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И тестовый шаг для рамки вокруг текста рекдактора",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И существует элемент UI Automation \"PID\" с именем \"Имя\" и типом \"Тип\"",
          "ОписаниеШага": "Проверяет, что существует нужный элемент UI Automation.",
          "ПолныйТипШага": "Прочее.UI Automation.Существование элемента"
        },
        {
          "ИмяШага": "И я делаю подсветку текста в редакторе VA в режиме обучения в строке 1 'Текст'",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю подсветку текста в редакторе VA в режиме обучения с 3 по 5 строку \"Секция тегов фичи\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю подсветку фрагмента текста в редакторе VA в режиме обучения \"Функциональность:\" \"Ключевое слово Функциональность\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю подсветку в окне сообщений по части строки 'Пустой адрес снипета' 'Текст'",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я ожидаю нажатие на элемент с именем \"Имя\" и типом \"Тип\" показывая текст \"Текст\" пока не появится элемент с именем \"Имя\" и типом \"Тип\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я ожидаю пока в поле с именем \"Имя\" и типом \"Тип\" не будет строки \"Значение\"",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я делаю подсветку фрагмента текста в редакторе VA в строке 1 в режиме обучения \"ИскомыйТекст\" 'Подсказка'",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я создаю состояние Onboarding",
          "ОписаниеШага": "Задает состояние с помощью таблицы.\n| 'Имя'                | 'Номер'           |\n| 'ИмяЭлемента'        | 'Номер'           |\n| 'Текст'              | 'Номер документа' |\n| 'НачальноеСостояние' | 'Нет'             |\n| 'КонечноеСостояние'  | 'Нет'             |\n| 'Окно'               | 'Заказ клиента*'  |\n| 'Переход'            | 'Дата'            |\n",
          "ПолныйТипШага": "Прочее.UI Automation.Onboarding"
        },
        {
          "ИмяШага": "И я запускаю Onboarding",
          "ОписаниеШага": "Запускает показ пользователю справки Onboarding, по созданным ранее состояниям.",
          "ПолныйТипШага": "Прочее.UI Automation.Onboarding"
        },
        {
          "ИмяШага": "И я запоминаю элемент с типом \"Тип\" подчиненный \"$IDПанели$\" в переменную \"IDТаблицы\" Vanessa Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я жду появления элемента UI Automation \"PID\" с именем \"Имя\" и типом \"Тип\" в течение 10 секунд",
          "ОписаниеШага": "Ожидает появления у указанного процесса элемента с нужным именем и типом. Если отбор по типу не нужен, то нужно передать пустое значение параметра.",
          "ПолныйТипШага": "Прочее.UI Automation.Ожидание появления элемента"
        },
        {
          "ИмяШага": "И я делаю подсветку расширенной подсказки элемента VA \"ИмяЭлемента\" \"ТекстПодсказки\" UI Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я запоминаю ID элемента VA с типом \"Тип\" следующим за элементом \"ИмяЭлемента\" в переменную \"ИмяПеременной\" UI Automation",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И я перемещаю курсор к элементу формы  'PID' 'Имя' 'Тип' UI Automation",
          "ОписаниеШага": "Перемещает курсор мышки к указанному элементу формы. Параметр \"Тип\" является необязательным.",
          "ПолныйТипШага": "Прочее.UI Automation.Курсор к элементу формы"
        },
        {
          "ИмяШага": "И я перемещаю курсор мышки к тексту в строке редактора 8 VA в режиме обучения",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "И получаю данные текущего элемента формы активного окна UI Automation",
          "ОписаниеШага": "Получает данные текущего элемента формы текщего окна windows с помощью механизма UI Automation.\nСоздает переменные, если есть соответствующие свойства у активного элемента: uia_id,uia_Type,uia_name,uia_value,uia_HelpText",
          "ПолныйТипШага": "Прочее.UI Automation.Получение данных активного элемента формы"
        },
        {
          "ИмяШага": "Я работаю в сеансе, запущенного с ключом TESTMANAGER",
          "ОписаниеШага": "",
          "ПолныйТипШага": "Служебные"
        },
        {
          "ИмяШага": "Я запускаю сеанс 1С с ключом TESTCLIENT",
          "Оп
  • references
    • scenario-templates.md 30.9 KB
      # Практическое руководство: Шаблоны сценариев Vanessa Automation
      
      ## Содержание
      1. [Базовые шаблоны](#базовые-шаблоны)
      2. [Работа со справочниками](#работа-со-справочниками)
      3. [Работа с документами](#работа-с-документами)
      4. [Проверка отчетов](#проверка-отчетов)
      5. [Тестирование обработок](#тестирование-обработок)
      6. [Комплексные сценарии](#комплексные-сценарии)
      
      ---
      
      ## Базовые шаблоны
      
      ### Шаблон 1: Минимальный сценарий
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      Функционал: [Название функционала]
          Как [роль пользователя]
          Я хочу [действие]
          Чтобы [цель/результат]
      
      Сценарий: [Название сценария]
          Дано [предусловие]
          Когда [действие]
          Тогда [ожидаемый результат]
      ```
      
      ### Шаблон 2: Сценарий с контекстом
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Категория
      Функционал: [Название функционала]
          Как [роль]
          Я хочу [действие]
          Чтобы [цель]
      
      Контекст:
          Дано Я запускаю сценарий открытия TestClient или подключаю уже существующий
          И Я закрыл все окна клиентского приложения
      
      @ТегСценария
      Сценарий: [Название сценария]
          Дано [предусловие]
          Когда [действие]
          Тогда [проверка]
      ```
      
      ---
      
      ## Работа со справочниками
      
      ### Создание элемента справочника
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Справочники @Создание
      Функционал: Создание элементов справочника "Номенклатура"
          Как менеджер склада
          Я хочу создавать новые товары
          Чтобы вести учет номенклатуры
      
      Контекст:
          Дано Я запускаю сценарий открытия TestClient или подключаю уже существующий
          И Я закрыл все окна клиентского приложения
      
      Сценарий: Создание нового товара через форму
          # Открываем список справочника
          Дано Я открываю навигационную ссылку "e1cib/list/Справочник.Номенклатура"
          
          # Создаем новый элемент
          Когда я нажимаю на кнопку с именем "FormCreate"
          Тогда открылось окно "Номенклатура (создание)"
          
          # Заполняем основные поля
          И в поле "Наименование" я ввожу текст "Товар тестовый"
          И в поле "Артикул" я ввожу текст "TEST-001"
          
          # Выбираем из справочника
          И из выпадающего списка "ВидНоменклатуры" я выбираю по строке "Товар"
          И из выпадающего списка "ЕдиницаИзмерения" я выбираю по строке "шт"
          
          # Записываем и проверяем
          И я нажимаю на кнопку с именем "FormWrite"
          Тогда элемент формы с именем "Наименование" стал равен "Товар тестовый"
          И я запоминаю значение поля "Код" в переменную "КодТовара"
          
          # Закрываем форму
          И я закрываю текущее окно
      ```
      
      ### Редактирование элемента справочника
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Справочники @Редактирование
      Функционал: Редактирование справочника "Контрагенты"
          Как бухгалтер
          Я хочу изменять реквизиты контрагента
          Чтобы актуализировать информацию
      
      Сценарий: Изменение ИНН контрагента
          # Открываем существующий элемент
          Дано Я открываю навигационную ссылку "e1cib/data/Справочник.Контрагенты?ref=a1b2c3d4e5f6"
          Тогда открылось окно "Контрагент*"
          
          # Изменяем поле
          Когда в поле "ИНН" я ввожу текст "9876543210"
          И в поле "КПП" я ввожу текст "987654321"
          
          # Сохраняем
          И я нажимаю на кнопку "Записать и закрыть"
          
          # Проверяем, что изменения сохранились
          И Я открываю навигационную ссылку "e1cib/data/Справочник.Контрагенты?ref=a1b2c3d4e5f6"
          Тогда элемент формы с именем "ИНН" стал равен "9876543210"
      ```
      
      ### Поиск в справочнике
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Справочники @Поиск
      Функционал: Поиск элементов справочника
          Как пользователь
          Я хочу быстро находить нужные элементы
          Чтобы эффективно работать с системой
      
      Сценарий: Поиск контрагента по наименованию
          Дано Я открываю навигационную ссылку "e1cib/list/Справочник.Контрагенты"
          
          # Используем строку поиска
          Когда в поле "СтрокаПоиска" я ввожу текст "ООО Рога"
          И я нажимаю клавишу "Enter"
          
          # Проверяем результаты
          Тогда таблица "Список" содержит "1" строк
          И в таблице "Список" в поле "Наименование" я вижу текст "ООО Рога и копыта"
      ```
      
      ---
      
      ## Работа с документами
      
      ### Создание документа с табличной частью
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Документы @Создание
      Функционал: Создание документа "Заказ покупателя"
          Как менеджер по продажам
          Я хочу оформлять заказы от покупателей
          Чтобы зафиксировать заявки клиентов
      
      Контекст:
          Дано Я запускаю сценарий открытия TestClient или подключаю уже существующий
          И Я закрыл все окна клиентского приложения
      
      Сценарий: Создание заказа с несколькими товарами
          # Создаем новый документ
          Дано Я открываю навигационную ссылку "e1cib/list/Документ.ЗаказПокупателя"
          Когда я нажимаю на кнопку "Создать"
          Тогда открылось окно "Заказ покупателя (создание)"
          
          # Заполняем шапку документа
          И в поле "Дата" я ввожу текущую дату
          И из выпадающего списка "Организация" я выбираю по строке "ООО Моя фирма"
          И из выпадающего списка "Покупатель" я выбираю по строке "ООО Рога и копыта"
          И из выпадающего списка "Склад" я выбираю по строке "Основной склад"
          
          # Заполняем табличную часть
          И в таблице "Товары" я нажимаю на кнопку с именем "ТоварыДобавить"
          И в таблице "Товары" из выпадающего списка "Номенклатура" я выбираю по строке "Товар 1"
          И в таблице "Товары" в поле "Количество" я ввожу текст "10"
          И в таблице "Товары" в поле "Цена" я ввожу текст "100.00"
          И в таблице "Товары" я завершаю редактирование строки
          
          # Добавляем вторую позицию
          И в таблице "Товары" я нажимаю на кнопку с именем "ТоварыДобавить"
          И в таблице "Товары" из выпадающего списка "Номенклатура" я выбираю по строке "Товар 2"
          И в таблице "Товары" в поле "Количество" я ввожу текст "5"
          И в таблице "Товары" в поле "Цена" я ввожу текст "200.00"
          И в таблице "Товары" я завершаю редактирование строки
          
          # Проверяем итоги
          Тогда таблица "Товары" содержит "2" строк
          И элемент формы с именем "СуммаДокумента" стал равен "2 000,00"
          
          # Записываем документ
          И я нажимаю на кнопку "Записать"
          И я запоминаю значение поля "Номер" в переменную "НомерЗаказа"
      ```
      
      ### Проведение документа
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Документы @Проведение
      Функционал: Проведение документа "Поступление товаров"
          Как кладовщик
          Я хочу провести приходную накладную
          Чтобы отразить поступление товаров в учете
      
      Сценарий: Проведение документа поступления
          # Открываем непроведенный документ
          Дано Я открываю навигационную ссылку "e1cib/data/Документ.ПоступлениеТоваров?ref=a1b2c3d4e5f6"
          Тогда открылось окно "Поступление товаров*"
          
          # Проверяем статус
          Тогда элемент формы с именем "Проведен" стал равен "Нет"
          
          # Проводим документ
          Когда я нажимаю на кнопку "Провести и закрыть"
          
          # Открываем заново и проверяем
          И Я открываю навигационную ссылку "e1cib/data/Документ.ПоступлениеТоваров?ref=a1b2c3d4e5f6"
          Тогда элемент формы с именем "Проведен" стал равен "Да"
      ```
      
      ### Проверка движений документа (рекомендуемый подход)
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Документы @ПроверкаДвижений
      Функционал: Проверка движений документа "Реализация товаров"
          Как разработчик
          Я хочу проверить корректность движений
          Чтобы убедиться в правильности учета
      
      Контекст:
          Дано Существует эталонная база с подготовленными документами
      
      Сценарий: Проверка остатков после реализации
          # Отменяем проведение для возврата к начальному состоянию
          И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
          
          # Проводим документ программно (без открытия формы)
          И я выполняю проведение документа по навигационной ссылке "e1cib/data/Документ.РеализацияТоваров?ref=a1b2c3d4e5f6g7h8i9j0" (расширение)
          
          # Проверяем движения через отчет
          И вариант отчета "ОстаткиТоваров" "Остатки на складах" равен макету "ЭталонПослеРеализации" (расширение)
          
          # Возвращаем базу в исходное состояние
          И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
      ```
      
      ### Создание документа на основании другого
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Документы @СозданиеНаОсновании
      Функционал: Создание документов на основании
          Как менеджер
          Я хочу создавать документы на основании существующих
          Чтобы упростить ввод данных
      
      Сценарий: Создание отгрузки на основании заказа
          # Открываем заказ
          Дано Я открываю навигационную ссылку "e1cib/data/Документ.ЗаказПокупателя?ref=a1b2c3d4e5f6"
          Тогда открылось окно "Заказ покупателя*"
          
          # Запоминаем ссылку на заказ
          И я запоминаю навигационную ссылку активного объекта в переменную "СсылкаНаЗаказ"
          
          # Создаем на основании
          Когда я нажимаю на кнопку "Создать на основании"
          И в меню "Создать на основании" я выбираю "Отгрузка товаров"
          Тогда открылось окно "Отгрузка товаров (создание)"
          
          # Проверяем, что документ основание заполнился
          Тогда элемент формы с именем "ДокументОснование" стал равен "$СсылкаНаЗаказ$"
          
          # Проверяем, что табличная часть заполнилась
          Тогда таблица "Товары" стала равной по шаблону:
              | Номенклатура | Количество |
              | *            | >0         |
          
          # Проводим
          И я нажимаю на кнопку "Провести и закрыть"
      ```
      
      ---
      
      ## Проверка отчетов
      
      ### Формирование и проверка отчета
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Отчеты @Формирование
      Функционал: Формирование отчета "Остатки товаров"
          Как аналитик
          Я хочу получать актуальные данные об остатках
          Чтобы контролировать наличие товаров
      
      Сценарий: Формирование отчета по складу
          # Открываем отчет
          Дано Я открываю навигационную ссылку "e1cib/app/Отчет.ОстаткиТоваров"
          Тогда открылось окно "Остатки товаров"
          
          # Устанавливаем период
          И в поле "Период" я ввожу текущую дату
          
          # Выбираем склад
          И из выпадающего списка "Склад" я выбираю по строке "Основной склад"
          
          # Формируем
          Когда я нажимаю на кнопку "Сформировать"
          
          # Проверяем, что отчет сформировался
          Тогда табличный документ "Результат" содержит строки:
              | Номенклатура | Количество |
              | *            | *          |
          
          # Проверяем конкретные значения
          И в табличном документе "Результат" я ищу строку где "Номенклатура" равно "Товар 1"
          Тогда в ячейке "Количество" значение больше "0"
      ```
      
      ### Сравнение отчета с эталоном
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Отчеты @Эталон
      Функционал: Проверка отчетов по эталону
          Как тестировщик
          Я хочу сравнивать отчеты с эталонными значениями
          Чтобы обнаружить отклонения в учете
      
      Сценарий: Сравнение остатков с эталоном
          # Подготовка данных
          И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
          И я выполняю проведение документа по навигационной ссылке "e1cib/data/Документ.ПоступлениеТоваров?ref=..." (расширение)
          
          # Проверка через отчет
          И вариант отчета "ОстаткиТоваров" "Остатки на складах" равен макету "ЭталонПослеПоступления" (расширение)
          
          # Очистка
          И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
      ```
      
      ---
      
      ## Тестирование обработок
      
      ### Запуск обработки с параметрами
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Обработки @Выполнение
      Функционал: Запуск обработки "Загрузка данных"
          Как администратор
          Я хочу загружать данные из файла
          Чтобы быстро наполнить справочники
      
      Сценарий: Загрузка номенклатуры из Excel
          # Открываем обработку
          Дано Я открываю внешнюю обработку "ЗагрузкаНоменклатуры.epf"
          Тогда открылось окно "Загрузка номенклатуры"
          
          # Выбираем файл
          И я нажимаю на кнопку "Выбрать файл"
          И я выбираю файл "C:\Data\nomenclature.xlsx"
          Тогда элемент формы с именем "ФайлДанных" стал равен "nomenclature.xlsx"
          
          # Запускаем обработку
          Когда я нажимаю на кнопку "Загрузить"
          
          # Ожидаем завершения
          И пауза "5"
          
          # Проверяем результат
          Тогда открылось окно "Результат загрузки"
          И элемент формы с именем "ЗагруженоЭлементов" стал равен "100"
      ```
      
      ---
      
      ## Комплексные сценарии
      
      ### Полный цикл продажи
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Комплексный @ЦиклПродажи
      Функционал: Полный цикл продажи товаров
          Как тестировщик
          Я хочу проверить весь процесс продажи
          Чтобы убедиться в корректности всех этапов
      
      Контекст:
          Дано Я запускаю сценарий открытия TestClient или подключаю уже существующий
          И Я закрыл все окна клиентского приложения
      
      Сценарий: От заказа до оплаты
          # 1. Создаем заказ покупателя
          Дано Я открываю навигационную ссылку "e1cib/list/Документ.ЗаказПокупателя"
          Когда я нажимаю на кнопку "Создать"
          Тогда открылось окно "Заказ покупателя (создание)"
          И из выпадающего списка "Покупатель" я выбираю по строке "ООО Рога и копыта"
          И в таблице "Товары" я нажимаю на кнопку "Добавить"
          И в таблице "Товары" из выпадающего списка "Номенклатура" я выбираю по строке "Товар 1"
          И в таблице "Товары" в поле "Количество" я ввожу текст "10"
          И в таблице "Товары" я завершаю редактирование строки
          И я нажимаю на кнопку "Провести и закрыть"
          И я запоминаю навигационную ссылку активного объекта в переменную "СсылкаНаЗаказ"
          
          # 2. Создаем отгрузку на основании заказа
          И Я открываю навигационную ссылку "$СсылкаНаЗаказ$"
          Когда я нажимаю на кнопку "Создать на основании"
          И в меню я выбираю "Отгрузка товаров"
          Тогда открылось окно "Отгрузка товаров (создание)"
          И я нажимаю на кнопку "Провести и закрыть"
          И я запоминаю навигационную ссылку активного объекта в переменную "СсылкаНаОтгрузку"
          
          # 3. Создаем реализацию на основании отгрузки
          И Я открываю навигационную ссылку "$СсылкаНаОтгрузку$"
          Когда я нажимаю на кнопку "Создать на основании"
          И в меню я выбираю "Реализация товаров"
          Тогда открылось окно "Реализация товаров (создание)"
          И я нажимаю на кнопку "Провести и закрыть"
          И я запоминаю навигационную ссылку активного объекта в переменную "СсылкаНаРеализацию"
          
          # 4. Создаем оплату
          И Я открываю навигационную ссылку "$СсылкаНаРеализацию$"
          Когда я нажимаю на кнопку "Создать на основании"
          И в меню я выбираю "Поступление на расчетный счет"
          Тогда открылось окно "Поступление на расчетный счет (создание)"
          И я нажимаю на кнопку "Провести и закрыть"
          
          # 5. Проверяем взаиморасчеты
          Дано Я открываю навигационную ссылку "e1cib/app/Отчет.ВзаиморасчетыСКонтрагентами"
          И из выпадающего списка "Контрагент" я выбираю по строке "ООО Рога и копыта"
          Когда я нажимаю на кнопку "Сформировать"
          Тогда в табличном документе "Результат" итоговая задолженность равна "0"
      ```
      
      ### Работа с несколькими окнами
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Комплексный @МногоОкон
      Функционал: Работа с несколькими окнами
          Как пользователь
          Я хочу работать с несколькими документами одновременно
          Чтобы сравнивать и копировать данные
      
      Сценарий: Копирование данных между документами
          # Открываем первый документ
          Дано Я открываю навигационную ссылку "e1cib/data/Документ.ЗаказПокупателя?ref=aaaa"
          Тогда открылось окно "Заказ покупателя*"
          И я запоминаю заголовок формы в переменную "ОкноЗаказа"
          
          # Копируем данные
          И я запоминаю значение поля "Покупатель" в переменную "Покупатель"
          
          # Открываем второй документ
          И Я открываю навигационную ссылку "e1cib/data/Документ.ЗаказПокупателя?ref=bbbb"
          Тогда открылось окно "Заказ покупателя*"
          
          # Вставляем данные
          И из выпадающего списка "Покупатель" я выбираю по строке "$Покупатель$"
          И я нажимаю на кнопку "Записать"
          И я закрываю текущее окно
          
          # Возвращаемся к первому окну
          И Я активизирую окно "$ОкноЗаказа$"
          И я закрываю текущее окно
      ```
      
      ---
      
      ## Дополнительные паттерны
      
      ### Использование циклов (через подсценарии)
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Паттерн @Цикл
      Функционал: Массовое создание элементов
          Как тестировщик
          Я хочу создать несколько элементов
          Чтобы подготовить тестовые данные
      
      Сценарий: Создание 10 товаров
          # Используем счетчик
          И я запоминаю значение выражения "1" в переменную "Счетчик"
          
          # Первая итерация
          И я выполняю подсценарий "СозданиеТовара" с параметром "$Счетчик$"
          И я запоминаю значение выражения "$Счетчик$ + 1" в переменную "Счетчик"
          
          # Вторая итерация
          И я выполняю подсценарий "СозданиеТовара" с параметром "$Счетчик$"
          И я запоминаю значение выражения "$Счетчик$ + 1" в переменную "Счетчик"
          
          # ... и так далее до 10
      ```
      
      ### Обработка ошибок
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @Паттерн @ОбработкаОшибок
      Функционал: Обработка ошибок при вводе
          Как тестировщик
          Я хочу проверить валидацию данных
          Чтобы убедиться в корректности проверок
      
      Сценарий: Попытка провести незаполненный документ
          Дано Я открываю навигационную ссылку "e1cib/list/Документ.ЗаказПокупателя"
          Когда я нажимаю на кнопку "Создать"
          Тогда открылось окно "Заказ покупателя (создание)"
          
          # Пытаемся провести без заполнения
          Когда я нажимаю на кнопку "Провести"
          
          # Ожидаем сообщение об ошибке
          Тогда появилось предупреждение "Не заполнено поле Покупатель"
          
          # Закрываем предупреждение
          И я нажимаю на кнопку "ОК"
          
          # Проверяем, что документ не проведен
          Тогда элемент формы с именем "Проведен" стал равен "Нет"
      ```
      
      ---
      
      ## Лучшие практики
      
      ### ✅ Правильно
      
      1. **Используйте осмысленные имена переменных:**
      ```gherkin
      И я запоминаю значение поля "Покупатель" в переменную "ТекущийПокупатель"
      ```
      
      2. **Добавляйте проверки после каждого критичного действия:**
      ```gherkin
      И я нажимаю на кнопку "Провести"
      Тогда элемент формы с именем "Проведен" стал равен "Да"
      ```
      
      3. **Группируйте шаги логически:**
      ```gherkin
      # Заполнение шапки документа
      ...
      # Заполнение табличной части
      ...
      # Проведение документа
      ...
      ```
      
      ### ❌ Неправильно
      
      1. **Не создавайте слишком длинные сценарии (более 50 шагов)**
      2. **Не дублируйте одинаковые последовательности шагов** - используйте подсценарии
      3. **Не смешивайте тестирование UI и тестирование движений** в одном сценарии
      4. **Не используйте "магические" значения** - используйте переменные
      
      ---
      
      ## Полезные команды и настройки
      
      ### Запуск сценариев
      
      ```bash
      # Запуск всех сценариев
      1cv8 /Execute vanessa-automation.epf /C"StartFeaturePlayer;VAParams=VAParams.json"
      
      # Запуск с конкретным тегом
      1cv8 /Execute vanessa-automation.epf /C"StartFeaturePlayer;VAParams=VAParams.json;Tags=@Smoke"
      
      # Запуск с исключением тегов
      1cv8 /Execute vanessa-automation.epf /C"StartFeaturePlayer;VAParams=VAParams.json;ExcludeTags=@Draft"
      ```
      
      ### VAParams.json - минимальная конфигурация
      
      ```json
      {
        "КаталогФич": "./features",
        "ВыполнитьСценарии": true,
        "ЗавершитьРаботуСистемы": true,
        "ВыгружатьСтатусВыполненияСценариевВФайл": true,
        "ПутьКФайлуДляВыгрузкиСтатусаВыполненияСценариев": "./status.txt",
        "СписокТеговИсключение": ["@Draft", "@Ignore"]
      }
      ```
      
      ---
      
      **Примечание:** Все шаги в примерах взяты из официальной библиотеки Vanessa Automation. При создании своих сценариев используйте только шаги из библиотеки или создавайте композитные шаги через подсценарии.
      
    • semantic-validation.md 24.7 KB
      # Руководство по семантической валидации для AI-ассистента
      
      > **Версия:** 1.0  
      > **Для:** AI-ассистенты работающие с Vanessa Automation  
      > **Цель:** Предотвращение зацикливания и неправильного применения рекомендаций валидатора
      
      ---
      
      ## 🎯 Главное правило
      
      **НИКОГДА не применяйте рекомендацию валидатора без семантической проверки!**
      
      Высокая алгоритмическая схожесть (85%+) НЕ означает семантическую релевантность.
      
      ---
      
      ## 🔍 Обязательный чек-лист перед применением рекомендации
      
      Для **каждой** рекомендации проверьте ВСЕ пункты:
      
      ```
      ☐ 1. Действие совпадает? (нажимаю vs ввожу vs выбираю)
      ☐ 2. Тип UI элемента совпадает? (кнопка vs гиперссылка vs поле)
      ☐ 3. Контекст совпадает? (в таблице vs в форме vs без контекста)
      ☐ 4. Параметры совместимы? (заголовок vs имя элемента)
      
      Если ВСЕ ✅ → можно применять
      Если хотя бы один ❌ → НЕ применять, спросить пользователя
      ```
      
      ---
      
      ## 📋 Детальные правила проверки
      
      ### 1. Проверка ДЕЙСТВИЯ
      
      #### Категории действий
      
      ```yaml
      Группа "Клик/Нажатие":
        - нажимаю
        - кликаю  
        - выбираю (на элемент)
        - щелкаю
        
        ✅ Взаимозаменяемы внутри группы
        ❌ НЕ заменяются на другие группы
      
      Группа "Ввод данных":
        - ввожу
        - устанавливаю
        - задаю
        - заполняю
        
        ✅ Взаимозаменяемы внутри группы
        ❌ НЕ заменяются на другие группы
      
      Группа "Выбор из списка":
        - выбираю из списка
        - выбираю из выпадающего списка
        - выбираю по строке
        - выбираю точное значение
        
        ✅ Взаимозаменяемы с учетом параметров
        ❌ НЕ заменяются на "Клик" или "Ввод"
      
      Группа "Проверки":
        - проверяю
        - сравниваю
        - убеждаюсь
        - жду
        
        ✅ Взаимозаменяемы внутри группы
      
      Группа "Навигация":
        - открываю
        - перехожу
        - закрываю
        - активизирую
        
        ⚠️ Каждое действие специфично, осторожно
      ```
      
      #### Примеры проверки действия
      
      ```gherkin
      # ✅ ПРАВИЛЬНО - действие совпадает
      Оригинал:    "я нажимаю кнопку"
      Рекомендация: "я нажимаю на кнопку"
      Действие: нажимаю → нажимаю ✅
      ```
      
      ```gherkin
      # ❌ НЕПРАВИЛЬНО - разные действия!
      Оригинал:    "я ввожу в поле"
      Рекомендация: "из выпадающего списка я выбираю"
      Действие: ввожу → выбираю ❌
      
      ⚠️ Это РАЗНЫЕ действия! Тест будет работать некорректно!
      → НЕ применять, обратиться к пользователю
      ```
      
      ```gherkin
      # ❌ НЕПРАВИЛЬНО - схожие слова, но разная семантика
      Оригинал:    "я выбираю кнопку"  (клик)
      Рекомендация: "я выбираю из списка"  (выбор значения)
      Действие: выбираю (клик) → выбираю (из списка) ❌
      
      ⚠️ Слово одинаковое, но КОНТЕКСТ разный!
      → НЕ применять
      ```
      
      ---
      
      ### 2. Проверка ТИПА UI ЭЛЕМЕНТА
      
      #### Иерархия элементов
      
      ```
      Кнопочные элементы (взаимозаменяемы):
        ├─ кнопка
        ├─ кнопка с именем
        ├─ кнопка командного интерфейса
        └─ кнопка выбора
      
      Поля ввода (взаимозаменяемы):
        ├─ поле
        ├─ поле с именем
        └─ поле ввода
      
      Элементы выбора (НЕ взаимозаменяемы):
        ├─ выпадающий список
        ├─ список
        ├─ дерево
        └─ переключатель
      
      Ссылочные элементы (НЕ взаимозаменяемы с кнопками!):
        ├─ гиперссылка
        └─ навигационная ссылка
      
      Табличные элементы:
        ├─ таблица
        └─ табличная часть
      
      Элементы форм:
        ├─ форма
        ├─ панель
        └─ группа
      ```
      
      #### Примеры проверки элемента
      
      ```gherkin
      # ✅ ПРАВИЛЬНО - элементы одной категории
      Оригинал:    "нажимаю кнопку"
      Рекомендация: "нажимаю на кнопку с именем"
      Элемент: кнопка → кнопка с именем ✅
      (разные селекторы, но тот же тип элемента)
      ```
      
      ```gherkin
      # ❌ КРИТИЧЕСКАЯ ОШИБКА - разные типы элементов!
      Оригинал:    "нажимаю на кнопку \"Создать\""
      Рекомендация: "нажимаю на гиперссылку \"Создать\""
      Элемент: кнопка → гиперссылка ❌
      
      ⚠️ Это РАЗНЫЕ элементы интерфейса!
      Кнопка ≠ Гиперссылка
      Схожесть 87% - ОБМАНЧИВА!
      
      → НЕ применять, обратиться к пользователю:
         "Валидатор предлагает заменить кнопку на гиперссылку.
          Это разные UI элементы. Что в вашем интерфейсе?
          1. Кнопка (обычная кнопка)
          2. Гиперссылка (текстовая ссылка)"
      ```
      
      ```gherkin
      # ❌ НЕПРАВИЛЬНО - разные элементы ввода
      Оригинал:    "в поле \"Наименование\" я ввожу"
      Рекомендация: "из выпадающего списка \"Наименование\" я выбираю"
      Элемент: поле → выпадающий список ❌
      
      ⚠️ Поле ввода и выпадающий список - РАЗНЫЕ элементы!
      → НЕ применять
      ```
      
      ```gherkin
      # ⚠️ ОСТОРОЖНО - может быть ок, нужна проверка
      Оригинал:    "в поле \"Покупатель\" я ввожу"
      Рекомендация: "из выпадающего списка \"Покупатель\" я выбираю"
      
      Анализ:
      - В 1С поля с выбором могут работать и как поле ввода, и как список
      - Но это РАЗНЫЙ способ заполнения
      
      → Обратиться к пользователю:
         "Поле 'Покупатель' в вашей форме:
          1. Поле ввода (можно набирать текст)
          2. Поле с выбором (нужно выбирать из списка)
          3. Комбинированное (можно и то, и другое)"
      ```
      
      ---
      
      ### 3. Проверка КОНТЕКСТА
      
      #### Типы контекста
      
      ```yaml
      Табличный контекст:
        - "в таблице \"Имя\""
        - "в табличной части \"Имя\""
        
        ✅ Взаимозаменяемы
        ❌ НЕ заменяются на контекст формы
      
      Контекст формы:
        - "в форме"
        - без контекста (подразумевается форма)
        
        ✅ Часто взаимозаменяемы
      
      Контекст дерева:
        - "в дереве \"Имя\""
        
        ❌ Специфичен, не заменяется
      
      Отсутствие контекста:
        - Шаги без указания контекста
        
        ✅ Обычно работают везде
      ```
      
      #### Примеры проверки контекста
      
      ```gherkin
      # ✅ ПРАВИЛЬНО - контекст совпадает
      Оригинал:    "в таблице \"Товары\" я нажимаю на кнопку"
      Рекомендация: "в таблице \"ИмяТаблицы\" я нажимаю на кнопку"
      Контекст: в таблице → в таблице ✅
      ```
      
      ```gherkin
      # ❌ НЕПРАВИЛЬНО - разный контекст
      Оригинал:    "в таблице \"Товары\" я активизирую поле"
      Рекомендация: "я активизирую поле"  (контекст формы)
      Контекст: в таблице → без контекста (форма) ❌
      
      ⚠️ Шаг для таблицы → шаг для формы
      → НЕ применять
      ```
      
      ```gherkin
      # ⚠️ ВОЗМОЖНО ОК - нужна проверка
      Оригинал:    "я нажимаю кнопку"  (без контекста)
      Рекомендация: "в таблице \"Имя\" я нажимаю кнопку"
      Контекст: без контекста → в таблице ⚠️
      
      Анализ:
      - Добавляется БОЛЕЕ специфичный контекст
      - Может не подойти, если кнопка НЕ в таблице
      
      → Если в исходном сценарии НЕТ упоминания таблицы:
         НЕ применять, обратиться к пользователю
      ```
      
      ---
      
      ### 4. Проверка ПАРАМЕТРОВ
      
      #### Типы параметров
      
      ```yaml
      Заголовок (Caption):
        - "Наименование" (как видит пользователь)
        - "Создать"
        - "Товары"
        
        ✅ Человекочитаемый текст
      
      Имя элемента (Name):
        - "FormCreate" (программное имя)
        - "Номенклатура" (имя реквизита)
        - "Goods" (англ. имя)
        
        ✅ Программный идентификатор
      
      Значение/Данные:
        - "ООО Рога и копыта"
        - "123.45"
        - "01.01.2024"
        
        ✅ Вводимые данные
      ```
      
      #### Примеры проверки параметров
      
      ```gherkin
      # ✅ ПРАВИЛЬНО - параметр просто шаблон
      Оригинал:    "я нажимаю на кнопку \"Создать\""
      Рекомендация: "я нажимаю на кнопку \"ЗаголовокКнопки\""
      Параметр: "Создать" → "{}" (шаблон) ✅
      
      → Применяю с сохранением оригинального параметра:
        "я нажимаю на кнопку \"Создать\""
      ```
      
      ```gherkin
      # ⚠️ ОСТОРОЖНО - разные селекторы
      Оригинал:    "я нажимаю на кнопку \"Создать\""  (по заголовку)
      Рекомендация: "я нажимаю на кнопку с именем \"ИмяКнопки\""  (по имени)
      
      Анализ:
      - "по заголовку" ≠ "с именем"
      - Это РАЗНЫЕ способы поиска элемента
      - Заголовок "Создать" ≠ Имя "FormCreate"
      
      → НЕ применять автоматически
      → Обратиться к пользователю:
         "Для кнопки нужно использовать:
          1. Заголовок (как видно на экране): \"Создать\"
          2. Программное имя: \"FormCreate\"
          
          Что указано в вашей конфигурации?"
      ```
      
      ```gherkin
      # ❌ НЕПРАВИЛЬНО - параметры несовместимы
      Оригинал:    "в поле \"Наименование\" я ввожу текст \"Текст\""
      Рекомендация: "из выпадающего списка \"Поле\" я выбираю по строке \"Значение\""
      
      Параметры:
      - Оригинал: 1 параметр (поле)
      - Рекомендация: 2 параметра (поле, значение для поиска)
      
      → НЕ применять, разная структура параметров
      ```
      
      ---
      
      ## 🚨 Антипаттерны (НИКОГДА не делайте так)
      
      ### Антипаттерн 1: Слепое применение первой рекомендации
      
      ```python
      # ❌ НЕПРАВИЛЬНО
      if error.suggestions:
          apply_first_suggestion(error.suggestions[0])
      ```
      
      ```python
      # ✅ ПРАВИЛЬНО
      if error.suggestions:
          for suggestion in error.suggestions:
              analysis = semantic_check(original=error.step, suggested=suggestion)
              if analysis.is_safe and analysis.confidence == "high":
                  apply_with_explanation(suggestion, analysis)
                  break
          else:
              # Ни одна рекомендация не подошла
              escalate_to_human(error)
      ```
      
      ---
      
      ### Антипаттерн 2: Игнорирование счетчика попыток
      
      ```python
      # ❌ НЕПРАВИЛЬНО - ЗАЦИКЛИВАНИЕ!
      while not validated:
          apply_fixes()  # бесконечный цикл
      ```
      
      ```python
      # ✅ ПРАВИЛЬНО
      MAX_ATTEMPTS = 3
      attempt = 0
      
      while not validated and attempt < MAX_ATTEMPTS:
          apply_fixes()
          attempt += 1
      
      if attempt >= MAX_ATTEMPTS:
          escalate_to_human(reason="Max attempts reached")
      ```
      
      ---
      
      ### Антипаттерн 3: Применение рекомендации с предупреждениями
      
      ```yaml
      # Рекомендация от валидатора:
      suggestion:
        text: "И я нажимаю на гиперссылку \"Текст\""
        similarity: 0.92
        warnings:
          - "Different UI element type: 'кнопка' vs 'гиперссылка'"
      ```
      
      ```python
      # ❌ НЕПРАВИЛЬНО - игнорирование warning
      if suggestion.similarity > 0.9:
          apply(suggestion)  # ОШИБКА! Есть warning!
      ```
      
      ```python
      # ✅ ПРАВИЛЬНО
      if suggestion.similarity > 0.9:
          if suggestion.warnings:
              escalate_to_human(
                  error=error,
                  suggestion=suggestion,
                  reason=f"High similarity but has warnings: {suggestion.warnings}"
              )
          else:
              apply(suggestion)
      ```
      
      ---
      
      ## 📊 Таблица принятия решений
      
      | Ситуация | Действие | Confidence | Escalate |
      |----------|----------|------------|----------|
      | Все проверки ✅ + similarity > 90% | ✅ Применить | High | Нет |
      | Все проверки ✅ + similarity 70-90% | ✅ Применить с объяснением | Medium | Нет |
      | Элемент ❌ но similarity > 95% | ⚠️ Спросить пользователя | Low | **Да** |
      | Действие ❌ | ❌ НЕ применять | None | **Да** |
      | Контекст ❌ | ❌ НЕ применять | None | **Да** |
      | Есть warnings | ⚠️ Спросить пользователя | Low | **Да** |
      | Попытка #3 | - | - | **Да** |
      | Нет рекомендаций | - | - | **Да** |
      
      ---
      
      ## 🎓 Практические примеры
      
      ### Пример 1: Идеальный случай
      
      **Ситуация:**
      ```gherkin
      Ошибка: И я нажимаю кнопку "Записать"
      ```
      
      **Рекомендация:**
      ```gherkin
      И я нажимаю на кнопку "ЗаголовокКнопки"
      ```
      
      **Семантический анализ:**
      ```yaml
      Действие: нажимаю → нажимаю ✅
      Элемент: кнопка → кнопка ✅
      Контекст: - → - ✅
      Параметр: "Записать" → "{}" ✅ (шаблон)
      Similarity: 95%
      Warnings: []
      ```
      
      **Решение:**
      ```
      ✅ ПРИМЕНИТЬ с сохранением параметра:
      И я нажимаю на кнопку "Записать"
      
      💬 Комментарий для пользователя:
      "Добавлен предлог 'на' согласно синтаксису библиотеки Vanessa."
      ```
      
      ---
      
      ### Пример 2: Опасная ситуация (разные элементы)
      
      **Ситуация:**
      ```gherkin
      Ошибка: И я нажимаю кнопку "Печать"
      ```
      
      **Рекомендация #1:**
      ```gherkin
      И я нажимаю на кнопку "Заголовок" (similarity: 94%)
      ```
      
      **Рекомендация #2:**
      ```gherkin
      И я нажимаю на гиперссылку "Текст" (similarity: 88%)
      ```
      
      **Семантический анализ #1:**
      ```yaml
      Действие: нажимаю → нажимаю ✅
      Элемент: кнопка → кнопка ✅
      Similarity: 94%
      Warnings: []
      Confidence: HIGH
      ```
      
      **Семантический анализ #2:**
      ```yaml
      Действие: нажимаю → нажимаю ✅
      Элемент: кнопка → гиперссылка ❌ РАЗНЫЕ!
      Similarity: 88%
      Warnings: ["Different UI element type"]
      Confidence: LOW
      ```
      
      **Решение:**
      ```
      ✅ Применить рекомендацию #1:
      И я нажимаю на кнопку "Печать"
      
      ❌ НЕ применять рекомендацию #2:
      Причина: Разные типы UI элементов (кнопка vs гиперссылка)
      ```
      
      ---
      
      ### Пример 3: Критический случай (зацикливание)
      
      **Попытка #1:**
      ```gherkin
      Ошибка: И я выбираю товар из справочника "Номенклатура"
      Рекомендация: И из выпадающего списка "{}" я выбираю по строке "{}"
      Применено: И из выпадающего списка "Номенклатура" я выбираю по строке ""
      → Ошибка: второй параметр пустой
      ```
      
      **Попытка #2:**
      ```gherkin
      Исправление: И из выпадающего списка "Номенклатура" я выбираю по строке "товар"
      → Ошибка: шаг по-прежнему не проходит валидацию
      ```
      
      **Попытка #3:**
      ```gherkin
      Исправление: И из выпадающего списка "Номенклатура" я выбираю точное значение "товар"
      → Ошибка: другой шаг, та же проблема
      ```
      
      **Решение:**
      ```
      🚨 MAX_ATTEMPTS (3) достигнут
      
      🤝 ESCALATE TO HUMAN:
      
      "Не удалось автоматически исправить шаг за 3 попытки.
      
      Исходный шаг:
        И я выбираю товар из справочника \"Номенклатура\"
      
      История попыток:
      1. Применена рекомендация с выбором из списка - не хватало параметра
      2. Добавлен параметр 'товар' - шаг все равно не валиден
      3. Изменен тип выбора - проблема сохранилась
      
      Возможные причины:
      - Неправильный подход к выбору из справочника
      - Нужен шаг открытия формы выбора элемента
      - Требуется использование навигационной ссылки
      
      Рекомендации валидатора:
      1. И из выпадающего списка \"{}\" я выбираю по строке \"{}\"
      2. И я нажимаю кнопку выбора у поля \"{}\"
      
      Пожалуйста, укажите:
      1. Как должен выбираться элемент справочника в вашей конфигурации?
      2. Или предоставьте правильный шаг для этого действия"
      ```
      
      ---
      
      ## 🛡️ Защита от зацикливания - обязательный код (MAX_ATTEMPTS = 3)
      
      ### Шаблон для каждого AI-ассистента
      
      ```python
      class ValidationLoop:
          MAX_ATTEMPTS = 3
          
          def __init__(self):
              self.attempt = 0
              self.history = []
          
          def validate_and_fix(self, scenario_file):
              while self.attempt < self.MAX_ATTEMPTS:
                  result = run_validator(scenario_file)
                  
                  if result.is_valid:
                      return SUCCESS
                  
                  # Применяем исправления
                  applied_fixes = self.apply_semantic_fixes(result.errors)
                  
                  if not applied_fixes:
                      # Не смогли применить ни одного исправления
                      self.escalate_to_human(
                          reason="No applicable fixes found",
                          errors=result.errors
                      )
                      return ESCALATED
                  
                  self.history.append({
                      "attempt": self.attempt,
                      "errors": result.errors,
                      "fixes": applied_fixes
                  })
                  
                  self.attempt += 1
              
              # Зациклились
              self.escalate_to_human(
                  reason="Maximum attempts reached",
                  errors=result.errors,
                  history=self.history
              )
              return ESCALATED
      ```
      
      ---
      
      ## ✅ Чек-лист финальной проверки
      
      Перед отправкой исправленного сценария пользователю:
      
      ```
      ☐ Все автоисправления применены корректно?
      ☐ Семантическая проверка пройдена для всех замен?
      ☐ Счетчик попыток не превышен?
      ☐ Нет ли warnings в примененных рекомендациях?
      ☐ Все ли параметры заполнены (нет пустых "")?
      ☐ Сохранена ли логика оригинального сценария?
      ☐ Добавлены ли комментарии к неочевидным исправлениям?
      ```
      
      ---
      
      ## 📞 Шаблоны обращения к пользователю
      
      ### Шаблон 1: Неуверенность в выборе
      
      ```markdown
      ⚠️ **Нужна ваша помощь с выбором шага**
      
      **Строка {line}:**
      ```gherkin
      {original_step}
      ```
      
      **Валидатор предлагает 2 варианта:**
      
      1. `{suggestion_1}` (схожесть: {similarity_1}%)
         - Преимущества: {pros_1}
         - Недостатки: {cons_1}
      
      2. `{suggestion_2}` (схожесть: {similarity_2}%)
         - Преимущества: {pros_2}
         - Недостатки: {cons_2}
      
      **Мой анализ:**
      {semantic_analysis}
      
      **Какой вариант использовать?**
      [ ] Вариант 1
      [ ] Вариант 2
      [ ] Другой (укажите)
      ```
      
      ---
      
      ### Шаблон 2: Критическое несовпадение
      
      ```markdown
      🚫 **Обнаружено критическое несовпадение**
      
      **Строка {line}:**
      ```gherkin
      {original_step}
      ```
      
      **Проблема:**
      {problem_description}
      
      **Рекомендация валидатора:**
      ```gherkin
      {suggestion}
      ```
      
      **Почему НЕ могу применить автоматически:**
      - {reason_1}
      - {reason_2}
      
      **Ваше решение:**
      [ ] Применить рекомендацию (я понимаю риски)
      [ ] Исправить шаг вручную
      [ ] Использовать другой подход к тестированию
      ```
      
      ---
      
      ### Шаблон 3: Зацикливание
      
      ```markdown
      🔄 **Зациклилсяна исправлении ошибки**
      
      **Строка {line}:**
      ```gherkin
      {original_step}
      ```
      
      **Попытки исправления ({attempt_count}):**
      
      **Попытка #1:**
      - Применено: `{fix_1}`
      - Результат: {result_1}
      
      **Попытка #2:**
      - Применено: `{fix_2}`
      - Результат: {result_2}
      
      **Попытка #3:**
      - Применено: `{fix_3}`
      - Результат: {result_3}
      
      **Вывод:**
      Не могу автоматически исправить эту ошибку.
      
      **Возможные причины:**
      {analysis_of_root_cause}
      
      **Рекомендую:**
      {recommendations}
      
      **Нужна ваша помощь для продолжения.**
      ```
      
      ---
      
      ## 📚 Дополнительные ресурсы
      
      - [`../architecture/ai-validator-integration.md`](../architecture/ai-validator-integration.md) - Общая архитектура
      - [`../data/step-categories.yaml`](../data/step-categories.yaml) - Полная категоризация шагов
      - [`../examples/semantic-check-examples.md`](../examples/semantic-check-examples.md) - Больше примеров
      
      ---
      
      **Помните:** Лучше лишний раз спросить пользователя, чем сломать сценарий!
      
    • steps-compact.md 54.2 KB
      # Компактный справочник шагов Vanessa Automation
      
      ## Наиболее используемые шаги по категориям
      
      
      ### UI.Окна
      
      ```gherkin
      И я закрываю все окна клиентского приложения
      ```
      *Закрывает все окна текущего клиента тестирования.*
      
      ```gherkin
      И я закрываю все окна клиентского приложения кроме "Нужное окно"
      ```
      *Закрывает все окна текущего TestClient, кроме нужного окна.*
      
      ```gherkin
      Тогда открылось окно "Заголовок окна"
      ```
      *Проверяет, что на экране есть активное окно с нужым заголовком. Возможно передавать сразу несколько заголовков окон через таблицу Gherkin.*
      
      ```gherkin
      Тогда открылась форма "Справочник.Справочник1.Форма.ФормаЭлемента"
      ```
      *Проверяет, что на экране есть активное окно с нужной формой. Возможно передавать сразу несколько заголовков форм через таблицу Gherkin.*
      
      ```gherkin
      Тогда открылась форма с именем 'Справочник.Справочник1.Форма.ФормаЭлемента'
      ```
      *Проверяет, что на экране есть активное окно с нужной формой. Возможно передавать сразу несколько имен форм через таблицу Gherkin.*
      
      ```gherkin
      И я активизирую окно "Заголовок окна"
      ```
      *Активизирует окно с нужным заголовком*
      
      ```gherkin
      И я активизирую форму "Заголовок формы"
      ```
      *Активизирует окно, в котором будет найдена форма с нужным заголовком.*
      
      ```gherkin
      И я активизирую форму с именем "ИмяФормы"
      ```
      *Активизирует окно, в котором будет найдена форма с нужным именем.*
      
      ```gherkin
      И я жду закрытия окна "Заголовок окна" в течение 20 секунд
      ```
      *Ожидает, что окно исчезнет в течение указанного числа секунд. Поиск окна происходит среди всех открытых окон. Поэтому шаг будет ждать пока закроются все окна, удовлетворяющие переданному параметру "Заголовок окна".*
      
      ```gherkin
      И я жду открытия окна "Заголовок окна" в течение 20 секунд
      ```
      *Ожидает, что окно появится в течение указанного числа секунд. Окно не обязательно должно быть активным окном. Поиск производится среди всех окон.*
      
      ```gherkin
      И я жду, что активным окном станет "Заголовок окна" в течение 10 секунд
      ```
      *Ожидает, что указанное окно станет активным окном в течение указанного времени.*
      
      ```gherkin
      И я жду открытия окна отличного от "Заголовок окна" в течение 20 секунд
      ```
      *Ожидает что откроется другое окно, отличное от переданного в течение указанного числа секунд. Используется, чтобы определить смену окон.*
      
      ```gherkin
      И я закрываю окно "Заголовок окна"
      ```
      *Закрывает окно с нужным заголовком*
      
      ```gherkin
      И я закрываю текущее окно
      ```
      *Закрывает активное окно*
      
      ```gherkin
      И я сохраняю навигационную ссылку текущего окна в переменную "ИмяПеременной"
      ```
      *Запоминает навигационную ссылку текущего окна, если оно находится в панели открытых окон.*
      
      ```gherkin
      И я запоминаю заголовок текущего окна как "ЗаголовокОкна"
      ```
      *Сохранение значения заголовка текущего окна в переменную.*
      
      ```gherkin
      И я запоминаю количество окон в переменную "ИмяПеременной"
      ```
      *Сохраняет в переменную количество открытых окон в текущем сеансе клиента тестирования.*
      
      ```gherkin
      Дано Открылось окно предупреждения с заголовком "Заголовок окна" и текстом "Текст"
      ```
      *Шаг проверяет, что сейчас на экране появилось окно предупреждения.*
      
      ```gherkin
      Дано В активном окне открылась форма с заголовком "Заголовок формы"
      ```
      *Шаг проверяет, что в активном окне есть нужная форма.*
      
      ```gherkin
      Если появилось окно с заголовком "Заголовок окна" Тогда
      ```
      *Условие. Проверяет, что среди всех окон есть окно с нужным заголовком*
      
      ```gherkin
      Если текущее окно имеет заголовок "Заголовок окна" Тогда
      ```
      *Условие. Проверяет, что в данный момент активным окном является окно с нужным заголовком*
      
      ```gherkin
      Если текущее окно "не равно" ожидаемому окну "Заголовок окна" Тогда
      ```
      *Условие. Проверяет что текущее окно равно/неравно нужному окну.*
      
      ```gherkin
      Если появилось окно с заголовком из таблицы  Тогда
      ```
      *Условие. Проверяет, что есть окно с заголовком из таблицы*
      
      ```gherkin
      Если это начальная страница Тогда
      ```
      *Условие. Проверяет, то текущее окно это начальная страница*
      
      ```gherkin
      Если это не начальная страница Тогда
      ```
      *Условие. Проверяет, то текущее окно это не начальная страница*
      
      ```gherkin
      Если появилось окно с заголовком "Заголовок окна" в течение 10 секунд Тогда
      ```
      *Условие. Проверяет, что на экране появилось (не обязательно является активным) окно с нужным заголовком в течение нужного числа секунд*
      
      
      ### UI.Формы.Поля
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу текст "ЗначениеПоля"
      ```
      *Вводит текст в поле. Также вводит текст в форматированный документ и поле HTML документа.*
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу текущую дату
      ```
      *Вводит текстовое представление значения текущей даты в поле. Например "01.01.2018".*
      
      ```gherkin
      И в поле с именем 'ИмяПоля' я ввожу текущую дату
      ```
      *Вводит текстовое представление значения текущей даты в поле. Например "01.01.2018".*
      
      ```gherkin
      И я нажимаю на кнопку текущего месяца
      ```
      *Нажимает на кнопку текущего месяца в форме ввода даты.*
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу начало текущего месяца
      ```
      *Вводит в поле текстовое представление значения даты равной началу текущего месяца. Например "01.01.2018".*
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу конец текущего месяца
      ```
      *Вводит в поле текстовое представление значения даты равной концу текущего месяца. Например "31.01.2018".*
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу начало следующего месяца
      ```
      *Вводит в поле текстовое представление значения даты равной началу следующего месяца. Например "01.01.2018".*
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу конец следующего месяца
      ```
      *Вводит в поле текстовое представление значения даты равной концу следующего месяца. Например "31.01.2018".*
      
      ```gherkin
      И в таблице "ИмяТаблицы" в поле с именем 'ИмяПоля' я ввожу начало текущего месяца
      ```
      *Вводит в поле таблицы текстовое представление значения даты равной началу текущего месяца. Например "01.01.2018".*
      
      ```gherkin
      И в таблице "ИмяТаблицы" в поле с именем 'ИмяПоля' я ввожу конец текущего месяца
      ```
      *Вводит в поле таблицы текстовое представление значения даты равной концу текущего месяца. Например "31.01.2018".*
      
      ```gherkin
      И в таблице "ИмяТаблицы" в поле с именем 'ИмяПоля' я ввожу начало следующего месяца
      ```
      *Вводит в поле таблицы текстовое представление значения даты равной началу следующего месяца. Например "01.01.2018".*
      
      ```gherkin
      И в таблице "ИмяТаблицы" в поле с именем 'ИмяПоля' я ввожу конец следующего месяца
      ```
      *Вводит в поле таблицы текстовое представление значения даты равной концу следующего месяца. Например "31.01.2018".*
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу текущую дату и текущее время
      ```
      *Вводит текстовое представление значения текущей даты и времени в поле. Например "01.01.2018 12:00:00".*
      
      ```gherkin
      И в поле с именем 'ИмяПоля' я ввожу текущую дату и текущее время
      ```
      *Вводит текстовое представление значения текущей даты и времени в поле. Например "01.01.2018 12:00:00".*
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу значение выражения "1+1"
      ```
      *Вычисляет выражение и вводит результат в поле*
      
      ```gherkin
      И в поле с именем 'ИмяПоля' я ввожу значение выражения "1+1"
      ```
      *Вычисляет выражение и вводит результат в поле*
      
      ```gherkin
      И я нажимаю кнопку выбора у поля "Заголовок поля"
      ```
      *Нажимает на кнопку выбора у поля*
      
      ```gherkin
      И поле "Заголовок поля" существует
      ```
      *Проверка существования поля*
      
      ```gherkin
      И поле с именем 'ИмяПоля' существует
      ```
      *Проверка существования поля*
      
      ```gherkin
      И поле "Заголовок поля" не существует
      ```
      *Проверка не существования поля*
      
      ```gherkin
      И поле с именем 'ИмяПоля' не существует
      ```
      *Проверка не существования поля*
      
      ```gherkin
      И кнопка "Заголовок кнопки" существует
      ```
      *Проверка существования кнопки*
      
      ```gherkin
      И кнопка с именем 'ИмяКнопки' существует
      ```
      *Проверка существования кнопки*
      
      ```gherkin
      И кнопка "Заголовок кнопки" не существует
      ```
      *Проверка не существования кнопки*
      
      ```gherkin
      И кнопка с именем 'ИмяКнопки' не существует
      ```
      *Проверка не существования кнопки*
      
      ```gherkin
      Если поле "Заголовок поля" существует тогда
      ```
      *Проверка существования поля*
      
      ```gherkin
      Если поле с именем 'ИмяПоля' существует тогда
      ```
      *Проверка существования поля*
      
      ```gherkin
      И элемент формы "Заголовок поля" присутствует на форме
      ```
      *Проверка видимости поля. Поле обязано существовать.*
      
      ```gherkin
      И элемент формы с именем 'ИмяПоля' присутствует на форме
      ```
      *Проверка видимости поля. Поле обязано существовать.*
      
      ```gherkin
      И элемент формы "Заголовок поля" отсутствует на форме
      ```
      *Проверка невидимости поля. Поле может не существовать.*
      
      
      ### UI.Таблицы
      
      ```gherkin
      И В таблице "ИмяТаблицы" я завершаю редактирование строки
      ```
      
      ```gherkin
      И В таблице "ИмяТаблицы" я завершаю редактирование строки
      ```
      *Завершение редактирования строки. *
      
      ```gherkin
      И В таблице "ИмяТаблицы" я отменяю редактирование строки
      ```
      *Отмена редактирования строки.*
      
      ```gherkin
      И я завершаю редактирование строки
      ```
      *Завершение редактирования строки текущей таблицы.*
      
      ```gherkin
      И В таблице "ИмяТаблицы" я нажимаю кнопку выбора у реквизита "Заголовок колонки"
      ```
      *Нажатие кнопки выбора у реквизита в таблице*
      
      ```gherkin
      И В таблице "ИмяТаблицы" я нажимаю кнопку выбора у реквизита с именем 'ИмяКолонки'
      ```
      *Нажатие кнопки выбора у реквизита в таблице*
      
      ```gherkin
      И В таблице "ИмяТаблицы" я нажимаю кнопку очистить у поля "Заголовок колонки"
      ```
      *Нажатие кнопки очистить у поля в таблице*
      
      ```gherkin
      И В таблице "ИмяТаблицы" я нажимаю кнопку очистить у поля с именем 'ИмяКолонки'
      ```
      *Нажатие кнопки очистить у поля в таблице*
      
      ```gherkin
      И в таблице  "ИмяТаблицы" я перехожу на один уровень вниз
      ```
      *Переход на один уровень вниз в списке в режиме "Иерархический список"*
      
      ```gherkin
      И в таблице  "ИмяТаблицы" я перехожу на один уровень вверх
      ```
      *Переход на один уровень вверх в списке в режиме "Иерархический список"*
      
      ```gherkin
      И в таблице  "ИмяТаблицы" я начинаю редактирование строки
      ```
      *Начало редактирования строки*
      
      ```gherkin
      И в таблице 'Список' я меняю пометку удаления строки
      ```
      *Переключает пометку удаления у текущей строки таблицы если это возможно.*
      
      ```gherkin
      И В таблице  "ИмяТаблицы" я перехожу на одну строку вниз с выделением
      ```
      
      ```gherkin
      И В таблице  "ИмяТаблицы" я перехожу на одну строку вниз с выделением
      ```
      *Переходим в таблице на следующую строку с выделением*
      
      ```gherkin
      И В таблице  "ИмяТаблицы" я перехожу на одну строку вверх с выделением
      ```
      
      ```gherkin
      И В таблице  "ИмяТаблицы" я перехожу на одну строку вверх с выделением
      ```
      *Переходим в таблице на предыдущую строку с выделением*
      
      ```gherkin
      И я удаляю все строки таблицы "ИмяТаблицы"
      ```
      *Очистить таблицу*
      
      ```gherkin
      И в таблице  "ИмяТаблицы" я перехожу на одну строку вниз
      ```
      
      ```gherkin
      И в таблице  "ИмяТаблицы" я перехожу на одну строку вниз
      ```
      *Переходим в таблице на следующую строку*
      
      ```gherkin
      И в таблице  "ИмяТаблицы" я перехожу на одну строку вверх
      ```
      
      ```gherkin
      И в таблице  "ИмяТаблицы" я перехожу на одну строку вверх
      ```
      *Переходим в таблице на предыдущую строку*
      
      ```gherkin
      И в таблице "ИмяТаблицы" в поле 'Заголовок поля' я ввожу значение выражения "Выражение"
      ```
      *Вычисляет выражение и вводит результат в поле таблицы*
      
      ```gherkin
      И в таблице "ИмяТаблицы" в поле с именем 'ИмяПоля' я ввожу значение выражения "Выражение"
      ```
      *Вычисляет выражение и вводит результат в поле таблицы*
      
      ```gherkin
      Если таблица "ИмяТаблицы" равна переданной тогда
      	| 'Имя колонки' |
      	| 'Значение'               |
      ```
      *Условие. Истинно, если в таблица равна переданной таблице Gherkin.*
      
      ```gherkin
      Если таблица "ИмяТаблицы" равна макету "ИмяМакета" тогда
      ```
      *Условие. Истинно, если в таблица равна переданному макету.*
      
      ```gherkin
      И таблица "ИмяТаблицы" стала равной:
      	| 'ИмяКолонки1' | 'ИмяКолонки2' |
      	| 'Значение1' | 'Значение2' |
      ```
      *Проверяет, что таблица TestClient стала равна таблице Gherkin*
      
      ```gherkin
      И в таблице "ИмяТаблицы" текущая строка равна:
      	| 'ИмяКолонки1' | 'ИмяКолонки2' |
      	| 'Значение1' | 'Значение2' |
      ```
      *Проверяет, что таблица TestClient стала равна таблице Gherkin*
      
      ```gherkin
      И таблица "ИмяТаблицы" стала равной по шаблону:
      	| 'ИмяКолонки1' | 'ИмяКолонки2' |
      	| 'Значение1' | 'Значение2' |
      ```
      *Проверяет, что таблица TestClient стала равна таблице Gherkin. Значения могут содержать один или несколько символов *.*
      
      ```gherkin
      И таблица "ИмяТаблицы" равна макету "ИмяМакета"
      ```
      *Проверяет, что таблица TestClient стала равна переданному макету*
      
      ```gherkin
      И таблица "ИмяТаблицы" равна макету "ИмяМакета" по шаблону
      ```
      *Проверяет, что таблица TestClient стала равна переданному макету. В ячейках макета можно использовать символы *.*
      
      
      ### UI.Кнопки
      
      ```gherkin
      Когда я нажимаю кнопку командного интерфейса "Заголовок кнопки"
      ```
      *Нажимает на кнопку командного интерфейса*
      
      ```gherkin
      И я нажимаю на кнопку "Заголовок кнопки"
      ```
      *Нажимает на кнопку с нужным заголовком*
      
      ```gherkin
      И я нажимаю на кнопку с заголовком "Заголовок кнопки" и именем "ИмяКнопки"
      ```
      *Нажимает на кнопку с нужным заголовком и именем. Шаг используется, когда надо найти кнопку почасти заголовка и части имени.*
      
      ```gherkin
      И я нажимаю на кнопку с именем 'ИмяКнопки'
      ```
      *Нажимает на кнопку с нужным именем*
      
      ```gherkin
      И В текущем окне я нажимаю кнопку командного интерфейса "КомандаИнтерфейса"
      ```
      *Нажатие кнопки командного интерфейса в текущей форме*
      
      ```gherkin
      И я нажимаю кнопку очистить у дополнения формы "Заголовок элемента"
      ```
      *Очищает текст в дополнении элемента формы, такое как строка поиска.*
      
      ```gherkin
      И я нажимаю кнопку очистить у дополнения формы с именем 'ИмяЭлемента'
      ```
      *Очищает текст в дополнении элемента формы, такое как строка поиска.*
      
      
      ### UI.Навигационные ссылки
      
      ```gherkin
      Когда я перехожу по навигационной ссылке "НавигационнаяСсылка"
      ```
      
      ```gherkin
      И я сохраняю навигационную ссылку текущего окна в переменную "ИмяПеременной" (Расширение)
      ```
      *Сохраняет навигационную ссылку текущего окна в переменную. Необходимо, чтобы в клиенте тестирования было подключено расширение VAExtension.*
      
      ```gherkin
      И я выполняю проведение документа по навигационной ссылке "НавСсылка" (расширение)
      ```
      *Программно проводит документ по его навигационной ссылке. При этом форма документа не открывается.*
      
      ```gherkin
      И я выполняю проведение документа по навигационной ссылке "НавСсылка" и получаю ошибку "ТекстОшибки" (расширение)
      ```
      *Программно проводит документ по его навигационной ссылке и ожидает получить определенный текст ошибки. При этом форма документа не открывается.*
      
      ```gherkin
      И я выполняю проведение документа по навигационной ссылке "НавСсылка" и получаю ошибку "ТекстОшибки" по шаблону (расширение)
      ```
      *Программно проводит документ по его навигационной ссылке и ожидает получить определенный текст ошибки. При этом форма документа не открывается. Допустимо использовать символы *.*
      
      ```gherkin
      И я отменяю проведение документа по навигационной ссылке "НавСсылка" (расширение)
      ```
      *Программно распроводит документ по его навигационной ссылке. При этом форма документа не открывается.*
      
      ```gherkin
      И я отменяю проведение документа по навигационной ссылке "НавСсылка" и получаю ошибку "ТекстОшибки" (расширение)
      ```
      *Программно распроводит документ по его навигационной ссылке и ожидает получить определенный текст ошибки. При этом форма документа не открывается.*
      
      ```gherkin
      И я отменяю проведение документа по навигационной ссылке "НавСсылка" и получаю ошибку "ТекстОшибки" по шаблону (расширение)
      ```
      *Программно распроводит документ по его навигационной ссылке и ожидает получить определенный текст ошибки. При этом форма документа не открывается. Допустимо использовать символы *.*
      
      ```gherkin
      И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
      ```
      *Ищет в данном сценарии все шаги, в параметрах которых есть навигационные ссылки на документы и распроводит эти документы, если они проведены.
      Поиск документов происходит как среди уже выполненых шагов так и среди шагов, которые еще будут выполняться.
      Если какие-то документы не получится распровести будет вызвано исключение.*
      
      ```gherkin
      И я получаю состояние объекта по навигационной ссылке "НавСсылка" (расширение)
      ```
      *Получает состояние документа или элемента справочника по навигационной ссылке. Создает переменные: ТипЭлемента(Справочник,Документ),ПометкаУдаления(Истина,Ложь),Проведен(Истина,Ложь),Дата,Номер,Код.*
      
      ```gherkin
      И я запоминаю навигационную ссылку текущего окна для удаления (расширение)
      ```
      *Запоминает навигационную ссылку текущего окна в переменную "LinksToDelete" типа массив.
      Предполагается, что далее в сценарии будет использоваться шаг:
      И я устанавливаю пометку удаления объектам, указанным к удалению (расширение)*
      
      ```gherkin
      И я запоминаю навигационную ссылку "НавСсылка" для удаления (расширение)
      ```
      *Запоминает переданную навигационную ссылку в переменную "LinksToDelete" типа массив.
      Предполагается, что далее в сценарии будет использоваться шаг:
      И я устанавливаю пометку удаления объектам, указанным к удалению (расширение)*
      
      ```gherkin
      Дано Я открываю навигационную ссылку "e1cib/data/Справочник.Справочник1?ref=ad018df0619b11d5458a440b8c472f30"
      ```
      *Открывает навигационную ссылку. Аналог Shift+F11*
      
      ```gherkin
      И я записываю документ по навигационной ссылке "НавигационнаяСсылка" в режиме "Проведение"
      ```
      *Запись документа в указанном режиме, без вызова исключения при ошибке записи (см. метод формы "ИнциаторДанных.РезультатЗаписиДокументаПоНавигационнойСсылке"). Режим - идентификатор системного перечисления "РежимЗаписиДокумента": "Запись", "Проведение", "ОтменаПроведения".*
      
      ```gherkin
      И я записываю документ по навигационной ссылке "НавигационнаяСсылка" в режиме "Проведение" с вызовом исключения
      ```
      *Запись документа в указанном режиме, с вызовом исключения при ошибке записи (см. метод формы "ИнциаторДанных.РезультатЗаписиДокументаПоНавигационнойСсылке"). Режим - идентификатор системного перечисления "РежимЗаписиДокумента": "Запись", "Проведение", "ОтменаПроведения". Текст исключения собирается из описания ошибки исключения системы, а также сообщений пользователю, вызванных событием проведения.*
      
      
      ### Переменные
      
      ```gherkin
      И я запоминаю значение буфера обмена в переменную "ИмяПеременной"
      ```
      *Запоминает значение буфера обмена в переменную. Необходимо включить использование компоненты VanessaExt.*
      
      ```gherkin
      И я определяю координаты картинки "ИмяКартинки" как "ИмяПеременной"
      ```
      *Определяет коордианты картинки с помощью SikuliX и сохраняет структуру с координатами в переменную*
      
      ```gherkin
      И я запоминаю родителя элемента "Заголовок/ID" с типом "Тип" процесса "PID" в переменную "ИмяПеременной" UI Automation
      ```
      *Запоминает ID родителя элемента с указанным заголовком/ID и типом в переменную. В параметр PID можно передать строку "Этот сеанс", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation.*
      
      ```gherkin
      И я запоминаю элемент "Заголовок" с типом "Тип" процесса "PID" в переменную "ИмяПеременной" UI Automation
      ```
      *Запоминает ID элемента с указанным заголовком и типом в переменную. В параметр PID можно передать строку "Этот сеанс", тогда будет использован PID текущего сеанса, в котором запущена Vanessa Automation.*
      
      ```gherkin
      И я запоминаю ID элемента формы Vanessa Automation с именем "ИмяПоля" в переменную "ИмяПеременной"
      ```
      
      ```gherkin
      И я запоминаю элемент с типом "Тип" c родителем "$IDЭлемента$" в переменную "IDЭлемента" Vanessa Automation
      ```
      
      ```gherkin
      И я запоминаю родителя элемента с именем "Имя" в переменную "ИмяПеременной" Vanessa Automation
      ```
      
      ```gherkin
      И я запоминаю родителя родителя элемента с именем "ИмяЭлемента" в переменную "IDРодителя" Vanessa Automation
      ```
      
      ```gherkin
      И я запоминаю элемент с именем "Имя" в переменную "ИмяПеременной" Vanessa Automation
      ```
      
      ```gherkin
      И я запоминаю элемент UI Automation в переменную "ИмяПеременной"
      	| 'Имя'                               | 'Значение'   |
      	| 'Name'                              | 'Заголовок'  |
      	| 'ProcessId'                         | 'ЭтотСеанс'  |
      	| 'Parent'                            | 'IdРодителя' |
      	| 'ControlType'                       | 'Тип'        |
      	| 'HelpText'                          | 'Подсказка'  |
      	| 'LegacyIAccessibleKeyboardShortcut' | 'Хоткей'     |
      ```
      *Запоминает ID элемента UI Automation согласно переданным параметрам в таблице*
      
      ```gherkin
      И я запоминаю элемент с типом "Тип" подчиненный "$IDПанели$" в переменную "IDТаблицы" Vanessa Automation
      ```
      
      ```gherkin
      И я запоминаю ID элемента VA с типом "Тип" следующим за элементом "ИмяЭлемента" в переменную "ИмяПеременной" UI Automation
      ```
      
      ```gherkin
      в переменной Контекст появилось значение ГлавноеОкноТестируемого
      ```
      
      ```gherkin
      И я сохраняю выпадающий список формы как "ИмяПеременной"
      ```
      *Сохраняет представление выпадающего списка выбора привязанного к форме в виде таблицы в памяти с колонками ПредставлениеДанных и ОтображаемыйТекст
      Работает в платформе 8.3.25 и выше.*
      
      ```gherkin
      И я запоминаю значение поля "Заголовок поля" как "ИмяПеременной"
      ```
      *Сохранение значения поля в переменную Контекст*
      
      ```gherkin
      И я запоминаю значение поля с именем 'ИмяПоля' как "ИмяПеременной"
      ```
      *Сохранение значения поля в переменную Контекст*
      
      ```gherkin
      И я запоминаю значение таблицы "ИмяТаблицы" поля "Заголовок поля" как "ИмяПеременной"
      ```
      
      ```gherkin
      И я запоминаю значение таблицы "ИмяТаблицы" поля с именем 'ИмяПоля' как "ИмяПеременной"
      ```
      
      ```gherkin
      И я запоминаю значение поля "Заголовок поля" таблицы "ИмяТаблицы" как "ИмяПеременной"
      ```
      *Сохранение значения поля таблицы в переменную Контекст*
      
      ```gherkin
      И я запоминаю значение поля с именем 'ИмяПоля' таблицы "ИмяТаблицы" как "ИмяПеременной"
      ```
      *Сохранение значения поля таблицы в переменную Контекст*
      
      ```gherkin
      И я запоминаю значение поля "Заголовок поля" с ключом "Номер документа"
      ```
      *Сохранение значения поля в переменную Контекст с произвольным ключом*
      
      ```gherkin
      И я запоминаю текущее окно как "ЗаголовокОкна"
      ```
      *Сохранение значения заголовка текущего окна в переменную.*
      
      ```gherkin
      И я запоминаю текущую форму как "ИмяФормы"
      ```
      *Сохранение имени формы текущего окна в переменную.*
      
      ```gherkin
      И я запоминаю количество строк таблицы "ИмяТаблицы" как "ИмяПеременной"
      ```
      *Сохранение количества строк таблицы в переменную Контекст*
      
      ```gherkin
      И количество строк таблицы "ИмяТаблицы" равно переменной "ИмяПеременной"
      ```
      *Сравнение количество строк таблицы с переменной Контекста*
      
      ```gherkin
      И в поле "Заголовок поля" ввожу значение переменной "ИмяПеременной"
      ```
      
      ```gherkin
      И в Таблицу "ИмяТаблицы" в поле "Заголовок поля" ввожу значение переменной "ИмяПеременной"
      ```
      
      ```gherkin
      И в поле "Заголовок поля" ввожу значение глобальной переменной "ИмяПеременной"
      ```
      
      ```gherkin
      И в поле "Заголовок поля" я ввожу значение переменной "ИмяПеременной"
      ```
      *Вставка значения ранее сохраненной переменной в реквизит*
      
      ```gherkin
      И в Таблицу "ИмяТаблицы" в поле "Заголовок поля" я ввожу значение переменной "ИмяПеременной"
      ```
      *Вставка значения ранее сохраненной переменной в реквизит*
      
      
      ### Объекты конфигурации
      
      ```gherkin
      Когда я программно создаю элемент справочника "Справочник1" с реквизитами
      ```
      
      ```gherkin
      Когда я программно создаю документ "ИмяДокумента" с реквизитами
      ```
      
      ```gherkin
      Когда я программно изменяю элемент справочника "Справочник1" с "Наименование" равным "Элемент #1" установив реквизиты
      ```
      
      ```gherkin
      Когда я программно создаю запись регистра сведений  "РегистрСведений1" с реквизитами
      ```
      
      ```gherkin
      Когда я программно изменяю запись регистра сведений  "РегистрСведений1" с реквизитами
      ```
      
      ```gherkin
      И я удаляю все элементы таблицы "ИмяОбъектаМетаданных"
      ```
      
      ```gherkin
      И я удаляю запись из таблицы "ИмяОбъектаМетаданных" с реквизитами
      ```
      
      ```gherkin
      Тогда существует элемент справочника "Справочник1" с "Наименование" равным "Элемент #1"
      ```
      
      ```gherkin
      Тогда не существует элемент справочника "Справочник1" с "Наименование" равным "Элемент #1"
      ```
      
      ```gherkin
      Если существует элемент справочника "Справочник1" с "Наименование" равным "Элемент #1" Тогда
      ```
      
      ```gherkin
      Если не существует элемент справочника "Справочник1" с "Наименование" равным "Элемент #1" Тогда
      ```
      
      ```gherkin
      Тогда существует элемент справочника "Справочник1" с реквизитами
      ```
      
      ```gherkin
      Тогда не существует элемент справочника "Справочник1" с реквизитами
      ```
      
      ```gherkin
      Если существует элемент справочника "Справочник1" с реквизитами Тогда
      ```
      
      ```gherkin
      Если не существует элемент справочника "Справочник1" с реквизитами Тогда
      ```
      
      ```gherkin
      Тогда существует документ "ИмяДокумента" с реквизитами
      ```
      
      ```gherkin
      Тогда не существует документ "ИмяДокумента" с реквизитами
      ```
      
      ```gherkin
      Если существует документ "ИмяДокумента" с реквизитами Тогда
      ```
      
      ```gherkin
      Если не существует документ "ИмяДокумента" с реквизитами Тогда
      ```
      
      ```gherkin
      Тогда существует запись регистра сведений "РегистрСведений1" с реквизитами
      ```
      
      ```gherkin
      Тогда не существует запись регистра сведений "РегистрСведений1" с реквизитами
      ```
      
      ```gherkin
      Если существует запись регистра сведений "РегистрСведений1" с реквизитами Тогда
      ```
      
      ```gherkin
      Если не существует запись регистра сведений "РегистрСведений1" с реквизитами Тогда
      ```
      
      ```gherkin
      Тогда существует запись регистра накопления "РегистрНакопления1" с реквизитами
      ```
      
      ```gherkin
      Тогда не существует запись регистра накопления "РегистрНакопления1" с реквизитами
      ```
      
      ```gherkin
      Если существует запись регистра накопления "РегистрНакопления1" с реквизитами Тогда
      ```
      
      ```gherkin
      Если не существует запись регистра накопления "РегистрНакопления1" с реквизитами Тогда
      ```
      
      
      ### Проверки
      
      ```gherkin
      Если есть картинка "ИмяКартинки" Тогда
      ```
      *Условие. Проверяет, есть ли на экране нужная картинка.*
      
      ```gherkin
      И таблица формы с именем 'ИмяТаблицы' содержит строки:
      ```
      
      ```gherkin
      Тогда таблица формы с именем 'ИмяТаблицы' содержит изменения:
      ```
      
      ```gherkin
      Тогда в таблице с именем 'ИмяТаблицы' я выделяю все строки
      ```
      
      ```gherkin
      И табличный документ формы с именем 'РеквизитТабличныйДокумент' стал равен:
      ```
      
      ```gherkin
      Дано табличный документ "РеквизитТабличныйДокумент" стал равен:
      	| 'Значение1' | 'Значение2' |
      	| 'Значение3' | 'Значение4' |
      ```
      
      ```gherkin
      Тогда табличный документ "РеквизитТабличныйДокумент" равен:
      	| 'Значение1' | 'Значение2' |
      	| 'Значение3' | 'Значение4' |
      ```
      *Проверяет равенство табличного документа переданной таблице Gherkin. Чтобы получить mxl файл из TestClient, в тонком клиенте будет сделана попытка сохранить табличный документ в файл. В Web клиенте он всегда будет считываться по ячейкам. Детали в справке, в разделе, посвященному сравнению табличного документа с эталоном.*
      
      ```gherkin
      Тогда табличный документ "РеквизитТабличныйДокумент" равен по шаблону:
      	| 'Значение1' | 'Значение2' |
      	| 'Значение3' | 'Значение4' |
      ```
      *Проверяет равенство табличного документа переданной таблице Gherkin. В значениях допускатеся использование символов *. Чтобы получить mxl файл из TestClient, в тонком клиенте будет сделана попытка сохранить табличный документ в файл. В Web клиенте он всегда будет считываться по ячейкам. Детали в справке, в разделе, посвященному сравнению табличного документа с эталоном.*
      
      ```gherkin
      Тогда область "R1C1:R10C10" табличного документа "РеквизитТабличныйДокумент" равна:
      	| 'Значение1' | 'Значение2' |
      	| 'Значение3' | 'Значение4' |
      ```
      *Проверяет равенство области табличного документа переданной таблице Gherkin. Чтобы получить mxl файл из TestClient, в тонком клиенте будет сделана попытка сохранить табличный документ в файл. В Web клиенте он всегда будет считываться по ячейкам. Детали в справке, в разделе, посвященному сравнению табличного документа с эталоном.*
      
      ```gherkin
      Тогда область "R1C1:R10C10" табличного документа "РеквизитТабличныйДокумент" равна по шаблону:
      	| 'Значение1' | 'Значение2' |
      	| 'Значение3' | 'Значение4' |
      ```
      *Проверяет равенство области табличного документа переданной таблице Gherkin. В значениях допускается использовать символы *. Чтобы получить mxl файл из TestClient, в тонком клиенте будет сделана попытка сохранить табличный документ в файл. В Web клиенте он всегда будет считываться по ячейкам. Детали в справке, в разделе, посвященному сравнению табличного документа с эталоном.*
      
      ```gherkin
      И табличный документ "РеквизитТабличныйДокумент" содержит строки:
      	| 'Значение1' | 'Значение2' |
      	| 'Значение3' | 'Значение4' |
      ```
      *Проверяет, что табличный документ TestClient содержит строки таблицы Gherkin.*
      
      ```gherkin
      И табличный документ "РеквизитТабличныйДокумент" содержит строки по шаблону:
      	| 'Значение1' | 'Значение2' |
      	| 'Значение3' | 'Значение4' |
      ```
      *Проверяет, что табличный документ TestClient содержит строки таблицы Gherkin. В ячейках макета можно использовать символы *.*
      
      ```gherkin
      И табличный документ "РеквизитТабличныйДокумент" содержит строки из макета "ИмяМакета"
      ```
      *Проверяет, что табличный документ TestClient содержит строки из макета.*
      
      ```gherkin
      И табличный документ "РеквизитТабличныйДокумент" содержит строки из макета "ИмяМакета" по шаблону
      ```
      *Проверяет, что табличный документ TestClient содержит строки из макета. В ячейках макета можно использовать символы *.*
      
      ```gherkin
      И табличный документ "РеквизитТабличныйДокумент" содержит строки только из макета "ИмяМакета"
      ```
      *Проверяет, что табличный документ TestClient содержит строки из макета. При этом макету запрещается содержать другие строки. Другими словами шаг проверяет, что макет соответствует эталону, но строки макета могут в любом порядке.*
      
      ```gherkin
      И табличный документ "РеквизитТабличныйДокумент" содержит строки только из макета "ИмяМакета" по шаблону
      ```
      *Проверяет, что табличный документ TestClient содержит строки из макета. В ячейках макета можно использовать символы *.  При этом макету запрещается содержать другие строки. Другими словами шаг проверяет, что макет соответствует эталону, но строки макета могут в любом порядке.*
      
      ```gherkin
      И Табличный документ "ИмяТабДок" содержит значения областей
      | ИмяОбласти | ЗначениеОбласти |
      ```
      *Проверяет что в табличном документа содержатся значения, перечисленные в таблице. Таблица состоит из двух колонок: "ИмяОбласти" и "ЗначениеОбласти".*
      
      ```gherkin
      И Табличный документ "ИмяТабДок" содержит строки в областях
      | ИмяОбласти | Подстрока |
      ```
      *Проверяет что в табличном документа содержатся значения, перечисленные в таблице. Таблица состоит из двух колонок: "ИмяОбласти" и "Подстрока".*
      
      ```gherkin
      И Табличный документ "РеквизитТабличныйДокумент" содержит значения областей по шаблону
      | ИмяОбласти | ШаблонОбласти |
      ```
      *Проверяет что в табличном документа содержатся значения, перечисленные в таблице. Таблица состоит из двух колонок: "ИмяОбласти" и "ШаблонОбласти". В шаблоне можно указывать символ * много раз.*
      
      ```gherkin
      И табличный документ "ИмяРеквизитаИлиИмяФайла" содержит значения:
      	| 'Значение1' |
      	| 'Значение2' |
      ```
      *Проверяет, что в табличном документе есть значения из таблицы. Данные могут быть получены как из реквизита формы клиента тестировния так и из файла. Для получения данных из файла нужно передавать полное имя файла.*
      
      ```gherkin
      И табличный документ "ИмяРеквизитаИлиИмяФайла" не содержит значения:
      	| 'Значение1' |
      	| 'Значение2' |
      ```
      *Проверяет, что в табличном документе нет значений из таблицы. Данные могут быть получены как из реквизита формы клиента тестировния так и из файла. Для получения данных из файла нужно передавать полное имя файла.*
      
      ```gherkin
      Тогда текст ячейки таблицы "Таблица" "ИмяПоля" стал равен "ЗначениеПоля"
      ```
      
      ```gherkin
      Если в текущем окне есть кнопка командного интерфейса "КомандаИнтерфейса" Тогда
      ```
      *Условие. Проверяет существование кнопки командного интерфейса в текущей форме.*
      
      ```gherkin
      Тогда появилось предупреждение, содержащее текст "Текст"
      ```
      *Проверяет, что появилось окно предупреждения, содержащее нужный текст.*
      
      ```gherkin
      Тогда появилось предупреждение, содержащее текст "Текст" по шаблону
      ```
      *Проверяет, что появилось окно предупреждения, содержащее нужный текст. Разрешается использовать символы * в проверяемом тексте..*
      
      ```gherkin
      Тогда не появилось окно предупреждения системы
      ```
      *Проверяет, что на экране нет предупреждения системы.*
      
      ```gherkin
      Если это web клиент Тогда
      ```
      *Условие. Проверяет, что текущий TestClient - это web клиент.*
      
      ```gherkin
      И выпадающий список формы стал равен:
      	| 'Значение1' |
      	| 'Значение2' |
      ```
      *Проверяет, что открытый выпадающий список привязанный к форме в точности равен переданному набору значений.
      Работает в платформе 8.3.25 и выше.*
      
      ```gherkin
      И выпадающий список формы стал равен по шаблону:
      	| 'Значение1' |
      	| 'Значение2' |
      ```
      *Проверяет, что открытый выпадающий список привязанный к форме в точности равен переданному набору значений.
      Допустимо использовать символы *.
      Работает в платформе 8.3.25 и выше.*
      
      ```gherkin
      И выпадающий список формы содержит значения
      	| 'Значение1' |
      	| 'Значение2' |
      ```
      *Проверяет, что открытый выпадающий список привязанный к форме в содержит каждое значение из переданного набора значений.
      Работает в платформе 8.3.25 и выше.*
      
    • writing-scenarios.md 26.7 KB
      # Руководство для AI-ассистента: Генерация сценариев Vanessa Automation
      
      ## 1. Основная информация
      
      **Vanessa Automation** - это фреймворк для BDD-тестирования 1С:Предприятие. Сценарии пишутся на языке **Turbo Gherkin** (расширенная версия Gherkin).
      
      ### Ключевые понятия:
      - **Feature** (Функционал) - файл с описанием функциональности
      - **Scenario** (Сценарий) - конкретный тест-кейс
      - **Step** (Шаг) - атомарное действие или проверка
      - **Context** (Контекст) - предусловия, выполняемые перед каждым сценарием
      
      ## 2. Структура Feature-файла
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @ТегФичи
      Функционал: Название функционала
          Как <роль>
          Я хочу <действие>
          Чтобы <результат>
      
      Контекст:
          Дано <предусловие 1>
          И <предусловие 2>
      
      @ТегСценария
      Сценарий: Название сценария
          Дано <начальное условие>
          Когда <действие>
          И <дополнительное действие>
          Тогда <проверка результата>
          И <дополнительная проверка>
      ```
      
      ## 3. Ключевые слова (Keywords)
      
      | Русский | Английский | Назначение |
      |---------|-----------|------------|
      | Дано | Given | Предусловия, подготовка |
      | Когда | When | Действия |
      | Тогда | Then | Проверки, ожидаемый результат |
      | И | And | Продолжение предыдущего типа |
      | Также | Also | Продолжение предыдущего типа |
      | Затем | Then | Проверки, ожидаемый результат |
      | Но | But | Отрицательная проверка |
      
      ## 4. Система тегов
      
      Теги используются для фильтрации и управления выполнением сценариев:
      
      - `@Draft@` - черновик, не готов к выполнению
      - `@Предварительно` - начальные заметки
      - `@IgnoreOnCIMainBuild` - игнорировать при CI/CD
      - `@Smoke` - дымовые тесты
      - `@Critical` - критичные тесты
      - Пользовательские теги для группировки
      
      ## 5. Основные категории шагов (1569 доступных шагов)
      
      ### 5.1. UI - Работа с пользовательским интерфейсом (618 шагов)
      
      #### Работа с окнами и формами
      ```gherkin
      И я открываю форму "ИмяФормы"
      И я закрываю форму "ИмяФормы"
      Тогда открылось окно "ЗаголовокОкна"
      И я закрываю все окна клиентского приложения
      И я запоминаю заголовок формы в переменную "ИмяПеременной"
      ```
      
      #### Работа с полями формы
      ```gherkin
      И в поле "ИмяПоля" я ввожу текст "Значение"
      И в поле с именем "ИмяПоля" я ввожу текст "Значение"
      И я выбираю значение "Значение" из выпадающего списка "ИмяПоля"
      И я очищаю поле "ИмяПоля"
      Тогда элемент формы с именем "ИмяПоля" стал равен "Значение"
      И я проверяю, что поле "ИмяПоля" доступно
      И я проверяю, что поле "ИмяПоля" недоступно
      И я проверяю, что поле "ИмяПоля" видимо
      И я проверяю, что поле "ИмяПоля" скрыто
      ```
      
      #### Работа с датами
      ```gherkin
      И в поле "ИмяПоля" я ввожу дату "ДД.ММ.ГГГГ"
      И в поле "ИмяПоля" я ввожу текущую дату
      И в поле "ИмяПоля" я ввожу дату "ТекущаяДата + 5"
      И в поле "ИмяПоля" я ввожу дату "ТекущаяДата - 3"
      ```
      
      #### Работа с таблицами
      ```gherkin
      И в таблице "ИмяТаблицы" я нажимаю на кнопку "Добавить"
      И в таблице "ИмяТаблицы" я нажимаю на кнопку "Удалить"
      И в таблице "ИмяТаблицы" я перехожу к строке номер "НомерСтроки"
      И в таблице "ИмяТаблицы" я выбираю текущую строку
      И в таблице "ИмяТаблицы" я активизирую поле "ИмяКолонки"
      И в таблице "ИмяТаблицы" в поле "ИмяКолонки" я ввожу текст "Значение"
      Тогда таблица "ИмяТаблицы" содержит "КоличествоСтрок" строк
      И в таблице "ИмяТаблицы" я устанавливаю флаг в строке номер "НомерСтроки"
      И в таблице "ИмяТаблицы" я снимаю флаг в строке номер "НомерСтроки"
      ```
      
      #### Работа с кнопками и гиперссылками
      ```gherkin
      И я нажимаю на кнопку "ИмяКнопки"
      И я нажимаю на кнопку с именем "ИмяКнопки"
      И я нажимаю на гиперссылку "ТекстГиперссылки"
      И я нажимаю на кнопку командного интерфейса "ИмяКнопки"
      ```
      
      #### Навигационные ссылки (работа с объектами)
      ```gherkin
      И я открываю навигационную ссылку "e1cib/data/..."
      И я запоминаю навигационную ссылку активного объекта в переменную "ИмяПеременной"
      И я проверяю, что навигационная ссылка активного объекта равна "$ИмяПеременной$"
      ```
      
      ### 5.2. Переменные (36 шагов)
      
      ```gherkin
      И я запоминаю значение поля "ИмяПоля" как "ИмяПеременной"
      И я запоминаю значение поля с именем "ИмяПоля" как "ИмяПеременной"
      И я запоминаю значение выражения "1+1" в переменную "ИмяПеременной"
      Тогда значение переменной "ИмяПеременной" равно "Значение"
      И я удаляю переменную "ИмяПеременной"
      И я вывожу значение переменной "ИмяПеременной" в лог
      ```
      
      **Использование переменных в шагах:**
      ```gherkin
      И в поле "ИмяПоля" я ввожу текст "$ИмяПеременной$"
      ```
      
      ### 5.3. Работа с файлами (36 шагов)
      
      ```gherkin
      И я загружаю файл "ИмяФайла" из каталога "ПутьКаталога"
      И я выгружаю файл в каталог "ПутьКаталога"
      И я проверяю, что файл "ИмяФайла" существует в каталоге "ПутьКаталога"
      И я удаляю файл "ИмяФайла" из каталога "ПутьКаталога"
      ```
      
      ### 5.4. Объекты конфигурации (30 шагов)
      
      ```gherkin
      И я создаю новый объект "ТипОбъекта.ИмяОбъекта"
      И я заполняю реквизит "ИмяРеквизита" значением "Значение"
      И я провожу документ
      И я отменяю проведение документа
      И я удаляю объект
      И я записываю объект
      ```
      
      ### 5.5. Инициатор данных - Генератор тестовых данных (36 шагов)
      
      ```gherkin
      И я создаю объекты через инициатор данных
      И я генерирую тестовые данные
      И я очищаю базу от тестовых данных
      ```
      
      ### 5.6. Проверки и ожидания
      
      ```gherkin
      Тогда я жду, что в форме будет значение "Значение" в течение "Секунд" секунд
      И я жду открытия формы "ИмяФормы" в течение "Секунд" секунд
      Тогда появилось предупреждение "ТекстПредупреждения"
      Тогда появился вопрос "ТекстВопроса"
      И я отвечаю "Да" на вопрос
      И я отвечаю "Нет" на вопрос
      ```
      
      ### 5.7. Работа с отчетами
      
      ```gherkin
      И я формирую отчет "ИмяОтчета"
      И вариант отчета "ИмяОтчета" "ИмяВарианта" равен макету "ИмяМакета"
      И я сравниваю отчет с эталоном
      ```
      
      ### 5.8. Служебные шаги (453 шагов)
      
      ```gherkin
      И пауза "Секунд"
      И я делаю снимок экрана
      И я вывожу в лог "Текст"
      И я закрываю TestClient
      И я перезапускаю TestClient
      ```
      
      ### 5.9. Allure - отчетность (5 шагов)
      
      ```gherkin
      И Я устанавливаю название фичи 'Название'
      Когда Я устанавливаю название эпика 'Название'
      И Я устанавливаю название истории 'Название'
      И Я устанавливаю метке 'Метка' значение 'Значение'
      И я устанавливаю описание сценария для отчета Allure 'Описание'
      ```
      
      ## 6. Рекомендуемые практики написания сценариев
      
      ### 6.1. Разделение тестирования форм и движений документов
      
      **Для тестирования движений (рекомендуемый подход):**
      ```gherkin
      Сценарий: Проверка движений документа "Приобретение товаров"
          # Отменяем проведение всех документов цепочки
          И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
          
          # Проводим документы программно
          И я выполняю проведение документа по навигационной ссылке "e1cib/data/Документ.ПриобретениеТоваровУслуг?ref=..." (расширение)
          И я выполняю проведение документа по навигационной ссылке "e1cib/data/Документ.ЗаказКлиента?ref=..." (расширение)
          
          # Проверяем результат через отчет
          И вариант отчета "ОстаткиИДоступностьТоваров" "Остатки и доступность товаров" равен макету "ЭталонноеЗначениеОтчета" (расширение)
          
          # Возвращаем базу в исходное состояние
          И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
      ```
      
      **Для тестирования форм:**
      ```gherkin
      Сценарий: Создание нового документа через форму
          Дано Я открываю навигационную ссылку "e1cib/list/Документ.ЗаказПокупателя"
          Когда В панели разделов я выбираю "Продажи"
          И В командной панели я нажимаю "Заказы покупателей"
          И я нажимаю на кнопку "Создать"
          Тогда открылось окно "Заказ покупателя (создание)"
          И из выпадающего списка "Организация" я выбираю по строке "ООО Рога и копыта"
          И из выпадающего списка "Покупатель" я выбираю по строке "Петров Иван Петрович"
          И в таблице "Товары" я нажимаю на кнопку "Добавить"
          И в таблице "Товары" в поле "Номенклатура" я ввожу текст "Товар 1"
          И в таблице "Товары" я завершаю редактирование строки
          И я нажимаю на кнопку "Провести"
          Тогда элемент формы с именем "Проведен" стал равен "Да"
      ```
      
      ### 6.2. Использование подсценариев для избежания дублирования
      
      Подсценарии позволяют переиспользовать общие последовательности шагов:
      
      ```gherkin
      # Основной сценарий
      Сценарий: Создание заказа покупателя
          И я выполняю подсценарий "ОткрытиеСпискаЗаказов"
          И я выполняю подсценарий "СозданиеНовогоЗаказа"
          И я выполняю подсценарий "ПроверкаПроведенияДокумента"
      ```
      
      ### 6.3. Структура типичного сценария
      
      ```gherkin
      @Тег
      Сценарий: Название сценария
          # 1. Подготовка (Given/Дано)
          Дано <открыть форму/список>
          И <подготовить данные>
          
          # 2. Действие (When/Когда)
          Когда <выполнить основное действие>
          И <выполнить дополнительные действия>
          
          # 3. Проверка (Then/Тогда)
          Тогда <проверить результат>
          И <дополнительные проверки>
          
          # 4. Очистка (опционально)
          И <вернуть систему в исходное состояние>
      ```
      
      ### 6.4. Использование таблиц данных
      
      ```gherkin
      Сценарий: Заполнение табличной части товарами
          И в таблице "Товары" я нажимаю на кнопку "Заполнить"
          Тогда таблица "Товары" стала равной:
              | Номенклатура | Количество | Цена  |
              | Товар 1     | 10         | 100.00|
              | Товар 2     | 5          | 200.00|
      ```
      
      ## 7. Правила для AI-ассистента
      
      ### 7.1. КРИТИЧЕСКИ ВАЖНО
      
      1. **ИСПОЛЬЗОВАТЬ ТОЛЬКО ШАГИ ИЗ БИБЛИОТЕКИ** - не выдумывать новые шаги
      2. **СОБЛЮДАТЬ СИНТАКСИС GHERKIN** - правильная последовательность ключевых слов
      3. **ИСПОЛЬЗОВАТЬ ПРАВИЛЬНЫЕ КАВЫЧКИ** - только двойные кавычки `""`
      4. **ПРОВЕРЯТЬ ИМЕНА ПОЛЕЙ** - использовать либо заголовки, либо имена полей
      5. **НЕ СМЕШИВАТЬ ПОДХОДЫ** - тестирование форм отдельно от движений
      
      ### 7.2. Алгоритм генерации сценария
      
      1. **Понять задачу**: какая функциональность тестируется?
      2. **Определить тип теста**: форма или движения?
      3. **Выбрать подходящие шаги** из библиотеки
      4. **Структурировать сценарий**: Given → When → Then
      5. **Добавить проверки**: минимум одну проверку результата
      6. **Проверить синтаксис**: все ли шаги из библиотеки?
      
      ### 7.3. Шаблон для ответа AI
      
      При генерации сценария AI должен:
      
      1. **Проанализировать требование**
      2. **Создать Feature-файл** с правильной структурой
      3. **Объяснить логику** сценария
      4. **Указать используемые категории шагов**
      5. **Предупредить о зависимостях** (нужны ли подготовленные данные)
      
      ### 7.4. Проверочный чеклист перед выдачей сценария
      
      - [ ] Все шаги есть в библиотеке?
      - [ ] Правильные ключевые слова (Дано/Когда/Тогда)?
      - [ ] Есть минимум одна проверка (Тогда)?
      - [ ] Правильная кодировка и язык указаны в заголовке?
      - [ ] Логичная последовательность шагов?
      - [ ] Переменные используются корректно (`$ИмяПеременной$`)?
      - [ ] Есть теги для фильтрации?
      
      ## 8. Примеры готовых сценариев
      
      ### Пример 1: Тестирование формы
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @ТестированиеФормы
      Функционал: Создание справочника "Контрагенты"
          Как пользователь системы
          Я хочу создать нового контрагента
          Чтобы использовать его в документах
      
      Сценарий: Успешное создание контрагента
          Дано Я открываю навигационную ссылку "e1cib/list/Справочник.Контрагенты"
          Когда я нажимаю на кнопку "Создать"
          Тогда открылось окно "Контрагент (создание)"
          И в поле "Наименование" я ввожу текст "ООО Тестовая компания"
          И в поле "ИНН" я ввожу текст "1234567890"
          И я нажимаю на кнопку "Записать и закрыть"
          Тогда элемент формы с именем "Наименование" стал равен "ООО Тестовая компания"
      ```
      
      ### Пример 2: Тестирование движений
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @ТестированиеДвижений
      Функционал: Проверка движений документа "Поступление товаров"
          Как разработчик
          Я хочу проверить корректность движений документа
          Чтобы убедиться в правильности учета
      
      Контекст:
          Дано Существует эталонная база с подготовленными документами
      
      Сценарий: Проверка остатков после поступления товаров
          # Приводим базу в исходное состояние
          И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
          
          # Проводим документ программно
          И я выполняю проведение документа по навигационной ссылке "e1cib/data/Документ.ПоступлениеТоваров?ref=a9b6cd2e3f4g5h6i7j8k9l0m1n2o3p4q" (расширение)
          
          # Проверяем результат
          И вариант отчета "ОстаткиТоваров" "Остатки на складе" равен макету "ЭталонОстаткиПослеПоступления" (расширение)
          
          # Возвращаем базу в исходное состояние
          И я отменяю проведение всех документов этого сценария по их навигационным ссылкам (расширение)
      ```
      
      ### Пример 3: Использование переменных
      
      ```gherkin
      # encoding: utf-8
      # language: ru
      
      @ИспользованиеПеременных
      Функционал: Создание связанных документов
          Как пользователь
          Я хочу создать цепочку документов
          Чтобы проверить их взаимосвязь
      
      Сценарий: Создание заказа и отгрузки
          # Создаем заказ
          Дано я открываю навигационную ссылку "e1cib/list/Документ.ЗаказПокупателя"
          Когда я нажимаю на кнопку "Создать"
          И в поле "Покупатель" я ввожу текст "ООО Покупатель"
          И я нажимаю на кнопку "Записать"
          И я запоминаю навигационную ссылку активного объекта в переменную "СсылкаНаЗаказ"
          И я закрываю текущее окно
          
          # Создаем отгрузку на основании заказа
          И я открываю навигационную ссылку "$СсылкаНаЗаказ$"
          И я нажимаю на кнопку "Создать на основании"
          И я нажимаю на кнопку "Отгрузка товаров"
          Тогда открылось окно "Отгрузка товаров (создание)"
          И я проверяю, что поле "ДокументОснование" стало равно "$СсылкаНаЗаказ$"
      ```
      
      ## 9. Типичные ошибки и как их избежать
      
      ### Ошибка 1: Использование несуществующих шагов
      
      ❌ **Неправильно:**
      ```gherkin
      И я ввожу в поле "Наименование" значение "Текст"
      ```
      
      ✅ **Правильно:**
      ```gherkin
      И в поле "Наименование" я ввожу текст "Текст"
      ```
      
      ### Ошибка 2: Неправильные кавычки
      
      ❌ **Неправильно:**
      ```gherkin
      И в поле 'Наименование' я ввожу текст 'Текст'
      ```
      
      ✅ **Правильно:**
      ```gherkin
      И в поле "Наименование" я ввожу текст "Текст"
      ```
      
      ### Ошибка 3: Отсутствие проверок
      
      ❌ **Неправильно:**
      ```gherkin
      Сценарий: Создание документа
          Дано я открыл форму
          Когда я ввел данные
          И я нажал кнопку "Записать"
      ```
      
      ✅ **Правильно:**
      ```gherkin
      Сценарий: Создание документа
          Дано я открыл форму
          Когда я ввел данные
          И я нажал кнопку "Записать"
          Тогда элемент формы с именем "Проведен" стал равен "Да"
      ```
      
      ### Ошибка 4: Смешивание подходов
      
      ❌ **Неправильно:**
      ```gherkin
      # Открываем форму И проверяем движения одновременно
      Сценарий: Тест
          Дано я открыл форму документа
          Когда я провел документ через форму
          Тогда я проверяю движения программно
      ```
      
      ✅ **Правильно** - разделить на два сценария:
      ```gherkin
      Сценарий: Проведение документа через форму
          Дано я открыл форму документа
          Когда я провел документ через форму
          Тогда элемент формы с именем "Проведен" стал равен "Да"
      
      Сценарий: Проверка движений документа
          И я выполняю проведение документа по навигационной ссылке "..." (расширение)
          Тогда вариант отчета "Отчет" "Вариант" равен макету "Эталон" (расширение)
      ```
      
      ## 10. Расширенные возможности
      
      ### 10.1. Работа с буфером обмена (требуется VanessaExt)
      
      ```gherkin
      И я запоминаю значение поля "ИмяПоля" в буфер обмена
      И я вставляю текстовое значение буфера обмена в поле с именем "ДругоеПоле"
      И я запоминаю значение буфера обмена в переменную "ИмяПеременной"
      ```
      
      ### 10.2. Работа с вложенными таблицами
      
      ```gherkin
      И в таблице "ТабличнаяЧасть" я перехожу к строке номер "1"
      И в таблице "ТабличнаяЧасть" я разворачиваю строку номер "1"
      И в таблице "ВложеннаяТаблица" в поле "Поле" я ввожу текст "Значение"
      ```
      
      ### 10.3. Работа с деревьями
      
      ```gherkin
      И в дереве "ДеревоЗначений" я разворачиваю строку номер "1"
      И в дереве "ДеревоЗначений" я выбираю строку номер "2"
      ```
      
      ## 11. Контрольные вопросы для проверки сценария
      
      Перед запуском сценария убедитесь:
      
      1. ✅ Указана кодировка `# encoding: utf-8`?
      2. ✅ Указан язык `# language: ru`?
      3. ✅ Есть блок `Функционал:` с описанием?
      4. ✅ Все шаги из библиотеки Vanessa Automation?
      5. ✅ Используются правильные ключевые слова?
      6. ✅ Есть хотя бы одна проверка (Тогда)?
      7. ✅ Переменные обрамлены `$...$`?
      8. ✅ Логика сценария понятна и последовательна?
      9. ✅ Сценарий имеет теги для фильтрации?
      10. ✅ Указаны все необходимые предусловия?
      
      ## 12. Полезные ссылки
      
      - Документация: https://pr-mex.github.io/vanessa-automation/dev/
      - GitHub: https://github.com/Pr-Mex/vanessa-automation
      - Телеграм канал: https://t.me/VanessaAutomation
      - Telegram группа: https://t.me/testspro1c
      
      ---
      
      **Версия документа:** 1.0  
      **Дата создания:** 2025  
      **На основе:** Vanessa Automation 1.2.042.x
      
  • scripts
    • indexer.py 10.6 KB
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      """
      Создание индексов для ускорения поиска шагов
      
      Создает инвертированные индексы для быстрого поиска шагов по ключевым словам.
      
      Использование:
          python indexer.py
          python indexer.py --library ../../data/library-full.json --output ../../data/indexes/
      
      Версия: 1.0
      """
      
      import json
      import sys
      import re
      from pathlib import Path
      from typing import Dict, List, Set
      from datetime import datetime
      from collections import defaultdict
      
      # Определяем пути
      SCRIPT_DIR = Path(__file__).parent
      PROJECT_ROOT = SCRIPT_DIR.parent
      DEFAULT_LIBRARY = PROJECT_ROOT / 'data' / 'steps-library.json'
      DEFAULT_OUTPUT_DIR = PROJECT_ROOT / 'data' / 'indexes'
      
      
      class Indexer:
          """Создание индексов для библиотеки шагов"""
          
          def __init__(self, library_path: str):
              """
              Инициализация индексатора
              
              Args:
                  library_path: Путь к файлу library-full.json
              """
              self.library_path = library_path
              self.steps = []
              self.load_library()
          
          def load_library(self):
              """Загрузка библиотеки шагов"""
              try:
                  with open(self.library_path, 'r', encoding='utf-8') as f:
                      data = json.load(f)
                  
                  if not isinstance(data, list):
                      raise ValueError("Некорректный формат библиотеки: ожидается массив")
                  
                  # Обрабатываем каждый шаг
                  for item in data:
                      step_text = item.get('ИмяШага', '')
                      if not step_text:
                          continue
                      
                      # Извлекаем категорию
                      full_type = item.get('ПолныйТипШага', '')
                      parts = full_type.split('.', 1)
                      category = parts[0] if parts else 'Прочее'
                      subcategory = parts[1] if len(parts) > 1 else ''
                      
                      self.steps.append({
                          'step': step_text,
                          'category': category,
                          'subcategory': subcategory,
                          'description': item.get('ОписаниеШага', '')
                      })
                  
                  print(f"✓ Загружено {len(self.steps)} шагов из библиотеки")
                  
              except Exception as e:
                  print(f"✗ Ошибка загрузки библиотеки: {e}", file=sys.stderr)
                  sys.exit(1)
          
          @staticmethod
          def normalize_step(step: str) -> str:
              """Нормализация шага (упрощенная версия)"""
              # Берем только первую строку
              first_line = step.split('\n')[0].strip()
              
              # Удаляем ключевые слова Gherkin
              step = re.sub(
                  r'^(Дано|Когда|Тогда|И|Также|Затем|Но)\s+',
                  '',
                  first_line,
                  flags=re.IGNORECASE
              )
              
              # Приводим к нижнему регистру
              return step.lower().strip()
          
          def tokenize(self, text: str) -> Set[str]:
              """
              Токенизация текста на ключевые слова
              
              Args:
                  text: Текст для токенизации
                  
              Returns:
                  Множество токенов
              """
              # Удаляем специальные символы и оставляем только слова
              text = self.normalize_step(text)
              
              # Удаляем кавычки и их содержимое
              text = re.sub(r'"[^"]*"', '', text)
              text = re.sub(r"'[^']*'", '', text)
              
              # Разбиваем на слова (кириллица + латиница)
              words = re.findall(r'[а-яёa-z]+', text, re.IGNORECASE)
              
              # Фильтруем служебные слова и короткие слова
              stop_words = {'я', 'в', 'из', 'на', 'и', 'с', 'у', 'к', 'по', 'от', 'до', 'за'}
              tokens = {w for w in words if len(w) > 1 and w not in stop_words}
              
              return tokens
          
          def create_keyword_index(self) -> Dict[str, List[int]]:
              """
              Создание инвертированного индекса (ключевое слово → индексы шагов)
              
              Returns:
                  Словарь: ключевое слово → список индексов шагов
              """
              print("Создание инвертированного индекса...")
              
              keyword_index = defaultdict(list)
              
              for idx, step_info in enumerate(self.steps):
                  tokens = self.tokenize(step_info['step'])
                  
                  for token in tokens:
                      keyword_index[token].append(idx)
              
              print(f"  ✓ Создано {len(keyword_index)} ключевых слов")
              
              # Конвертируем в обычный dict для JSON
              return dict(keyword_index)
          
          def create_category_index(self) -> Dict[str, List[int]]:
              """
              Создание категорийного индекса (категория → индексы шагов)
              
              Returns:
                  Словарь: категория → список индексов шагов
              """
              print("Создание категорийного индекса...")
              
              category_index = defaultdict(list)
              
              for idx, step_info in enumerate(self.steps):
                  category = step_info['category']
                  category_index[category].append(idx)
                  
                  # Также индексируем по подкатегориям
                  if step_info['subcategory']:
                      full_cat = f"{category}.{step_info['subcategory']}"
                      category_index[full_cat].append(idx)
              
              print(f"  ✓ Создано {len(category_index)} категорий/подкатегорий")
              
              return dict(category_index)
          
          def create_frequency_index(self) -> Dict[int, int]:
              """
              Создание индекса частотности (заглушка)
              
              В будущем здесь можно анализировать реальную частоту использования шагов
              из логов или метрик. Пока возвращаем пустой индекс.
              
              Returns:
                  Словарь: индекс шага → частотность использования
              """
              print("Создание индекса частотности (заглушка)...")
              print("  ⚠ Индекс частотности пока не реализован (требуется анализ метрик)")
              
              # Заглушка: все шаги с частотностью 0
              return {}
          
          def create_indexes(self, output_dir: str):
              """
              Создание всех индексов и сохранение в файлы
              
              Args:
                  output_dir: Директория для сохранения индексов
              """
              output_path = Path(output_dir)
              output_path.mkdir(parents=True, exist_ok=True)
              
              print(f"\nСоздание индексов для {len(self.steps)} шагов...")
              print(f"Выходная директория: {output_path}\n")
              
              # Создаем индексы
              keyword_index = self.create_keyword_index()
              category_index = self.create_category_index()
              frequency_index = self.create_frequency_index()
              
              # Метаданные индексов
              metadata = {
                  'version': '1.0',
                  'created_at': datetime.now().isoformat(),
                  'total_steps': len(self.steps),
                  'total_keywords': len(keyword_index),
                  'total_categories': len(category_index),
                  'library_path': str(self.library_path)
              }
              
              # Сохраняем индексы
              print("\nСохранение индексов...")
              
              self._save_json(output_path / 'index.json', metadata)
              print(f"  ✓ Сохранены метаданные: index.json")
              
              self._save_json(output_path / 'by-keywords.json', keyword_index)
              print(f"  ✓ Сохранен индекс ключевых слов: by-keywords.json")
              
              self._save_json(output_path / 'by-category.json', category_index)
              print(f"  ✓ Сохранен категорийный индекс: by-category.json")
              
              self._save_json(output_path / 'frequency.json', frequency_index)
              print(f"  ✓ Сохранен индекс частотности: frequency.json (заглушка)")
              
              print(f"\n✓ Индексы успешно созданы в {output_path}")
          
          def _save_json(self, filepath: Path, data: Dict):
              """Сохранение JSON файла"""
              with open(filepath, 'w', encoding='utf-8') as f:
                  json.dump(data, f, ensure_ascii=False, indent=2)
      
      
      def main():
          """Основная функция CLI"""
          # Вывод содержит кириллицу. Без явного переключения печать падает с UnicodeEncodeError
          # везде, где консоль не в UTF-8: сборочный агент, чужая локаль.
          sys.stdout.reconfigure(encoding='utf-8')
          sys.stderr.reconfigure(encoding='utf-8')
          import argparse
          
          parser = argparse.ArgumentParser(
              description='Создание индексов для search-steps.py',
              formatter_class=argparse.RawDescriptionHelpFormatter
          )
          
          parser.add_argument(
              '--library',
              default=str(DEFAULT_LIBRARY),
              help=f'Путь к файлу библиотеки (по умолчанию: {DEFAULT_LIBRARY})'
          )
          
          parser.add_argument(
              '--output',
              default=str(DEFAULT_OUTPUT_DIR),
              help=f'Директория для индексов (по умолчанию: {DEFAULT_OUTPUT_DIR})'
          )
          
          args = parser.parse_args()
          
          # Создаем индексатор
          indexer = Indexer(args.library)
          
          # Создаем индексы
          indexer.create_indexes(args.output)
      
      
      if __name__ == '__main__':
          main()
    • search_steps.py 26.1 KB
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      """
      Инструмент умного поиска шагов Vanessa Automation
      
      Позволяет быстро находить релевантные шаги из библиотеки (1569 шагов)
      по ключевым словам без необходимости загружать всю библиотеку в контекст AI.
      
      Использование:
          # Простой поиск
          python search_steps.py --query "нажать кнопку создать" --top 5
          
          # Batch search (несколько запросов)
          python search_steps.py --query "открыть" "закрыть" "таблица" --top 5
      
      Версия: 1.0 (MVP + Batch Search)
      """
      
      import json
      import sys
      import re
      import argparse
      from pathlib import Path
      from typing import List, Dict, Tuple
      from difflib import SequenceMatcher
      
      # Определяем пути
      SCRIPT_DIR = Path(__file__).parent
      PROJECT_ROOT = SCRIPT_DIR.parent
      DEFAULT_LIBRARY = PROJECT_ROOT / 'data' / 'steps-library.json'
      DEFAULT_INDEX_DIR = PROJECT_ROOT / 'data' / 'indexes'
      
      
      class StepsLibrary:
          """Библиотека шагов Vanessa Automation"""
          
          def __init__(self, library_path: str):
              """
              Инициализация библиотеки
              
              Args:
                  library_path: Путь к файлу library-full.json
              """
              self.steps = []  # Список всех шагов с метаданными
              self.steps_normalized = {}  # нормализованный шаг -> полная информация
              self.load_library(library_path)
          
          def load_library(self, path: str):
              """
              Загрузка библиотеки шагов из JSON
              
              Args:
                  path: Путь к файлу библиотеки
                  
              Raises:
                  FileNotFoundError: Если файл не найден
                  json.JSONDecodeError: Если JSON некорректный
              """
              try:
                  with open(path, 'r', encoding='utf-8') as f:
                      data = json.load(f)
                  
                  # library-full.json - это массив объектов
                  if not isinstance(data, list):
                      raise ValueError("Некорректный формат библиотеки: ожидается массив")
                  
                  # Обрабатываем каждый шаг
                  for item in data:
                      step_text = item.get('ИмяШага', '')
                      if not step_text:
                          continue
                      
                      # Извлекаем категорию из ПолныйТипШага
                      full_type = item.get('ПолныйТипШага', '')
                      parts = full_type.split('.', 1)
                      category = parts[0] if parts else 'Прочее'
                      subcategory = parts[1] if len(parts) > 1 else ''
                      
                      # Сохраняем полную информацию
                      step_info = {
                          'step': step_text,
                          'category': category,
                          'subcategory': subcategory,
                          'description': item.get('ОписаниеШага', '')
                      }
                      
                      self.steps.append(step_info)
                      
                      # Нормализуем для быстрого поиска
                      normalized = self.normalize_step(step_text)
                      self.steps_normalized[normalized] = step_info
                  
                  if not self.steps:
                      raise ValueError("Библиотека пуста или некорректна")
                      
              except FileNotFoundError:
                  print(f"✗ Ошибка: Файл библиотеки не найден: {path}", file=sys.stderr)
                  sys.exit(1)
              except json.JSONDecodeError as e:
                  print(f"✗ Ошибка: Некорректный JSON в файле: {e}", file=sys.stderr)
                  sys.exit(1)
              except Exception as e:
                  print(f"✗ Ошибка загрузки библиотеки: {e}", file=sys.stderr)
                  sys.exit(1)
          
          @staticmethod
          def normalize_step(step: str) -> str:
              """
              Нормализация шага для сравнения.
              Реализация из tools/validator/validate.py (строки 108-147)
              
              Args:
                  step: Текст шага
                  
              Returns:
                  Нормализованный текст шага
              """
              # Для многострочных шагов берем только первую строку
              first_line = step.split('\n')[0].strip()
              
              # Удаляем ключевые слова Gherkin
              step = re.sub(
                  r'^(Дано|Когда|Тогда|И|Также|Затем|Но)\s+',
                  '',
                  first_line,
                  flags=re.IGNORECASE
              )
              
              # Приводим к нижнему регистру
              step = step.lower()
              
              # Убираем двоеточие в конце
              if step.endswith(':'):
                  step = step[:-1].strip()
              
              # Заменяем текст в двойных кавычках на плейсхолдер
              step = re.sub(r'"[^"]*"', '"{}"', step)
              
              # Заменяем текст в одинарных кавычках на плейсхолдер
              step = re.sub(r"'[^']*'", '"{}"', step)
              
              # Заменяем экранированные кавычки
              step = step.replace('\\"', '"')
              
              # Заменяем переменные ($Имя$) на плейсхолдер
              step = re.sub(r'\$[^$]+\$', '${}$', step)
              
              # Заменяем числа на плейсхолдер
              step = re.sub(r'\b\d+\b', '#', step)
              
              # Убираем лишние пробелы
              step = re.sub(r'\s+', ' ', step)
              
              return step.strip()
      
      
      class StepsSearcher:
          """Поиск и ранжирование шагов"""
          
          def __init__(self, library: StepsLibrary, index_dir: str = None):
              """
              Инициализация поисковика
              
              Args:
                  library: Загруженная библиотека шагов
                  index_dir: Директория с индексами (опционально)
              """
              self.library = library
              self.indexes_loaded = False
              self.keyword_index = {}
              self.category_index = {}
              
              # Пытаемся загрузить индексы если указана директория
              if index_dir:
                  self.load_indexes(index_dir)
          
          def load_indexes(self, index_dir: str):
              """
              Загрузка индексов из директории
              
              Args:
                  index_dir: Путь к директории с индексами
              """
              try:
                  index_path = Path(index_dir)
                  
                  # Проверяем наличие индексов
                  if not (index_path / 'by-keywords.json').exists():
                      return
                  
                  # Загружаем индексы
                  with open(index_path / 'by-keywords.json', 'r', encoding='utf-8') as f:
                      self.keyword_index = json.load(f)
                  
                  with open(index_path / 'by-category.json', 'r', encoding='utf-8') as f:
                      self.category_index = json.load(f)
                  
                  self.indexes_loaded = True
                  
              except Exception:
                  # В случае ошибки просто не используем индексы
                  self.indexes_loaded = False
          
          def search(self, query: str, top_n: int = 10, category: str = None, subcategory: str = None) -> List[Dict]:
              """
              Поиск шагов по запросу
              
              Args:
                  query: Поисковый запрос
                  top_n: Количество возвращаемых результатов
                  category: Фильтр по категории (опционально)
                  subcategory: Фильтр по подкатегории (опционально)
                  
              Returns:
                  Список найденных шагов с релевантностью
              """
              # Используем индексированный поиск если доступен
              if self.indexes_loaded and category:
                  return self._search_with_index(query, top_n, category, subcategory)
              
              # Иначе прямой поиск
              return self._search_direct(query, top_n, category, subcategory)
          
          def _search_direct(self, query: str, top_n: int = 10, category: str = None, subcategory: str = None) -> List[Dict]:
              """
              Прямой поиск без использования индексов
              
              Args:
                  query: Поисковый запрос
                  top_n: Количество результатов
                  category: Фильтр по категории
                  subcategory: Фильтр по подкатегории
                  
              Returns:
                  Список найденных шагов
              """
              # Нормализуем запрос
              normalized_query = self.library.normalize_step(query)
              
              # Ищем похожие шаги
              matches = []
              
              for norm_step, step_info in self.library.steps_normalized.items():
                  # Применяем фильтр по категории если нужен
                  if category and step_info['category'] != category:
                      continue
                  
                  # Применяем фильтр по подкатегории если нужен
                  if subcategory and step_info['subcategory'] != subcategory:
                      continue
                  
                  # Вычисляем схожесть
                  ratio = SequenceMatcher(None, normalized_query, norm_step).ratio()
                  
                  # Порог схожести 0.3 (более мягкий чем в validator для поиска)
                  if ratio > 0.3:
                      result = {
                          'step': step_info['step'],
                          'category': step_info['category'],
                          'relevance': round(ratio, 2)
                      }
                      
                      # Добавляем подкатегорию если есть
                      if step_info['subcategory']:
                          result['subcategory'] = step_info['subcategory']
                      
                      matches.append(result)
              
              # Сортируем по релевантности (убывание)
              matches.sort(key=lambda x: x['relevance'], reverse=True)
              
              # Возвращаем топ-N результатов
              return matches[:top_n]
          
          def _search_with_index(self, query: str, top_n: int = 10, category: str = None, subcategory: str = None) -> List[Dict]:
              """
              Индексированный поиск (быстрее)
              
              Args:
                  query: Поисковый запрос
                  top_n: Количество результатов
                  category: Фильтр по категории
                  subcategory: Фильтр по подкатегории
                  
              Returns:
                  Список найденных шагов
              """
              # Формируем ключ категории
              cat_key = category
              if subcategory:
                  cat_key = f"{category}.{subcategory}"
              
              # Получаем индексы шагов для этой категории
              if cat_key not in self.category_index:
                  return []
              
              candidate_indices = set(self.category_index[cat_key])
              
              # Нормализуем запрос и ищем среди кандидатов
              normalized_query = self.library.normalize_step(query)
              
              matches = []
              for idx in candidate_indices:
                  step_info = self.library.steps[idx]
                  norm_step = self.library.normalize_step(step_info['step'])
                  
                  # Вычисляем схожесть
                  ratio = SequenceMatcher(None, normalized_query, norm_step).ratio()
                  
                  if ratio > 0.3:
                      result = {
                          'step': step_info['step'],
                          'category': step_info['category'],
                          'relevance': round(ratio, 2)
                      }
                      
                      if step_info['subcategory']:
                          result['subcategory'] = step_info['subcategory']
                      
                      matches.append(result)
              
              # Сортируем и возвращаем топ-N
              matches.sort(key=lambda x: x['relevance'], reverse=True)
              return matches[:top_n]
          
          def batch_search(self, queries: List[str], top_n: int = 10, category: str = None, subcategory: str = None) -> Dict:
              """
              Поиск по нескольким запросам (batch search)
              
              Args:
                  queries: Список поисковых запросов
                  top_n: Количество результатов на запрос
                  category: Фильтр по категории (опционально)
                  subcategory: Фильтр по подкатегории (опционально)
                  
              Returns:
                  Dict с результатами для каждого запроса
              """
              results = {}
              
              for query in queries:
                  results[query] = self.search(query, top_n, category, subcategory)
              
              # Подсчитываем общее количество результатов
              total_results = sum(len(r) for r in results.values())
              
              return {
                  'total_queries': len(queries),
                  'total_results': total_results,
                  'results': results
              }
      
      
          def get_category(self, category: str, subcategory: str = None) -> List[Dict]:
              """
              Получить все шаги указанной категории
              
              Args:
                  category: Название категории
                  subcategory: Подкатегория (опционально)
                  
              Returns:
                  Список всех шагов категории
              """
              results = []
              
              for step_info in self.library.steps:
                  if step_info['category'] != category:
                      continue
                  
                  if subcategory and step_info['subcategory'] != subcategory:
                      continue
                  
                  result = {
                      'step': step_info['step'],
                      'category': step_info['category']
                  }
                  
                  if step_info['subcategory']:
                      result['subcategory'] = step_info['subcategory']
                  
                  results.append(result)
              
              return results
          
          def get_stats(self) -> Dict:
              """
              Получить статистику библиотеки
              
              Returns:
                  Словарь со статистикой
              """
              # Подсчет категорий
              categories_count = {}
              for step_info in self.library.steps:
                  cat = step_info['category']
                  categories_count[cat] = categories_count.get(cat, 0) + 1
              
              return {
                  'total_steps': len(self.library.steps),
                  'total_categories': len(categories_count),
                  'categories': categories_count
              }
      
      
      def format_json_output(data: Dict) -> str:
          """
          Форматирование вывода в JSON
          
          Args:
              data: Данные для вывода
              
          Returns:
              Отформатированная JSON строка
          """
          return json.dumps(data, ensure_ascii=False, indent=2)
      
      
      def format_human_output(data: Dict) -> str:
          """
          Форматирование вывода в человекочитаемом формате
          
          Args:
              data: Данные для вывода
              
          Returns:
              Отформатированная строка
          """
          lines = []
          lines.append("═" * 70)
          lines.append("ПОИСК ШАГОВ VANESSA AUTOMATION")
          lines.append("═" * 70)
          lines.append("")
          
          # Для stats
          if 'total_steps' in data and 'total_categories' in data:
              lines.append(f"Всего шагов: {data['total_steps']}")
              lines.append(f"Категорий: {data['total_categories']}")
              lines.append("")
              lines.append("Распределение по категориям:")
              for cat, count in sorted(data['categories'].items(), key=lambda x: x[1], reverse=True):
                  lines.append(f"  {cat}: {count} шагов")
              lines.append("═" * 70)
              return "\n".join(lines)
          
          # Для get-category
          if 'category' in data and 'steps' in data:
              lines.append(f"Категория: {data['category']}")
              if data.get('subcategory'):
                  lines.append(f"Подкатегория: {data['subcategory']}")
              lines.append(f"Найдено шагов: {len(data['steps'])}")
              lines.append("")
              for i, step_info in enumerate(data['steps'], 1):
                  step_text = step_info['step'].split('\n')[0]  # Только первая строка
                  lines.append(f"{i}. {step_text}")
                  if step_info.get('subcategory'):
                      lines.append(f"   └─ {step_info['subcategory']}")
              lines.append("═" * 70)
              return "\n".join(lines)
          
          # Для обычного поиска
          if 'query' in data:
              # Одиночный запрос
              lines.append(f"Запрос: \"{data['query']}\"")
              lines.append(f"Найдено: {data['found']} шагов")
              lines.append("")
              
              for i, result in enumerate(data['results'], 1):
                  relevance = int(result['relevance'] * 100)
                  step_text = result['step'].split('\n')[0]
                  lines.append(f"{i}. [{result['category']} | {relevance}%] {step_text}")
                  if result.get('subcategory'):
                      lines.append(f"   └─ {result['subcategory']}")
          else:
              # Batch search
              lines.append(f"Запросов: {data['total_queries']}")
              lines.append(f"Найдено шагов: {data['total_results']}")
              lines.append("")
              
              for i, (query, results) in enumerate(data['results'].items(), 1):
                  lines.append("─" * 70)
                  lines.append(f"Запрос {i}: \"{query}\"")
                  lines.append("─" * 70)
                  lines.append("")
                  
                  for j, result in enumerate(results, 1):
                      relevance = int(result['relevance'] * 100)
                      step_text = result['step'].split('\n')[0]
                      lines.append(f"{j}. [{result['category']} | {relevance}%] {step_text}")
                      if result.get('subcategory'):
                          lines.append(f"   └─ {result['subcategory']}")
                  lines.append("")
          
          lines.append("═" * 70)
          return "\n".join(lines)
      
      
      def format_yaml_compact_output(data: Dict) -> str:
          """
          Форматирование вывода в компактном YAML формате
          
          Args:
              data: Данные для вывода
              
          Returns:
              Отформатированная строка в YAML стиле
          """
          lines = ["results:"]
          
          # Для stats
          if 'total_steps' in data:
              lines = ["stats:"]
              lines.append(f"  total_steps: {data['total_steps']}")
              lines.append(f"  categories: {data['total_categories']}")
              return "\n".join(lines)
          
          # Для get-category
          if 'category' in data and 'steps' in data:
              lines = [f"{data['category']}:"]
              for step_info in data['steps']:
                  step_text = step_info['step'].split('\n')[0]
                  lines.append(f"  - {step_text}")
              return "\n".join(lines)
          
          # Для обычного поиска
          if 'query' in data:
              # Одиночный запрос
              lines.append(f"  {data['query']}:")
              for result in data['results']:
                  step_text = result['step'].split('\n')[0]
                  relevance = int(result['relevance'] * 100)
                  lines.append(f"    - {step_text} [{result['category']}, {relevance}%]")
          else:
              # Batch search
              for query, results in data['results'].items():
                  lines.append(f"  {query}:")
                  for result in results:
                      step_text = result['step'].split('\n')[0]
                      relevance = int(result['relevance'] * 100)
                      lines.append(f"    - {step_text} [{result['category']}, {relevance}%]")
          
          return "\n".join(lines)
      
      
      def main():
          """Основная функция CLI"""
          # Вывод содержит кириллицу. Без явного переключения печать падает с UnicodeEncodeError
          # везде, где консоль не в UTF-8: сборочный агент, чужая локаль.
          sys.stdout.reconfigure(encoding='utf-8')
          sys.stderr.reconfigure(encoding='utf-8')
          parser = argparse.ArgumentParser(
              description='Поиск шагов в библиотеке Vanessa Automation',
              formatter_class=argparse.RawDescriptionHelpFormatter,
              epilog="""
      Примеры использования:
        # Простой поиск
        python search_steps.py --query "нажать кнопку создать" --top 5
        
        # Batch search (несколько запросов)
        python search_steps.py --query "открыть" "закрыть" "таблица" --top 5
        
        # С фильтром по категории
        python search_steps.py --query "таблица" --category UI --top 10
        
        # Получить все шаги категории
        python search_steps.py --get-category "Переменные"
        
        # Статистика библиотеки
        python search_steps.py --stats
        
        # Человекочитаемый формат
        python search_steps.py --query "документ" --format human
              """
          )
          
          # Группа: Поиск
          search_group = parser.add_argument_group('Поиск')
          search_group.add_argument(
              '--query',
              nargs='+',
              help='Поисковые запросы (можно указать несколько для batch search)'
          )
          
          search_group.add_argument(
              '--category',
              help='Фильтр по категории (например: UI, Переменные, Прочее)'
          )
          
          search_group.add_argument(
              '--subcategory',
              help='Фильтр по подкатегории (например: "Формы.Кнопки")'
          )
          
          search_group.add_argument(
              '--top',
              type=int,
              default=10,
              help='Количество результатов на запрос (по умолчанию: 10)'
          )
          
          # Группа: Вывод
          output_group = parser.add_argument_group('Вывод')
          output_group.add_argument(
              '--format',
              choices=['json', 'human', 'yaml-compact'],
              default='json',
              help='Формат вывода (по умолчанию: json)'
          )
          
          # Группа: Специальные команды
          special_group = parser.add_argument_group('Специальные')
          special_group.add_argument(
              '--get-category',
              metavar='CATEGORY',
              help='Получить все шаги указанной категории'
          )
          
          special_group.add_argument(
              '--stats',
              action='store_true',
              help='Показать статистику библиотеки'
          )
          
          # Группа: Пути
          paths_group = parser.add_argument_group('Пути')
          paths_group.add_argument(
              '--library',
              default=str(DEFAULT_LIBRARY),
              help=f'Путь к файлу библиотеки (по умолчанию: {DEFAULT_LIBRARY})'
          )
          
          args = parser.parse_args()
          
          # Проверка: должна быть указана хотя бы одна операция
          if not args.query and not getattr(args, 'get_category', None) and not args.stats:
              parser.error('Требуется указать --query, --get-category или --stats')
          
          # Загружаем библиотеку
          library = StepsLibrary(args.library)
          
          # Создаем поисковик с поддержкой индексов
          index_dir = str(DEFAULT_INDEX_DIR) if DEFAULT_INDEX_DIR.exists() else None
          searcher = StepsSearcher(library, index_dir)
          
          # Выполняем операцию
          if args.stats:
              # Статистика библиотеки
              output = searcher.get_stats()
          elif getattr(args, 'get_category', None):
              # Получить все шаги категории
              steps = searcher.get_category(args.get_category, args.subcategory)
              output = {
                  'category': args.get_category,
                  'steps': steps,
                  'total': len(steps)
              }
              if args.subcategory:
                  output['subcategory'] = args.subcategory
          else:
              # Обычный поиск
              if len(args.query) == 1:
                  # Одиночный запрос - упрощенный формат
                  query = args.query[0]
                  results = searcher.search(query, args.top, args.category, args.subcategory)
                  
                  output = {
                      'query': query,
                      'found': len(results),
                      'results': results
                  }
              else:
                  # Batch search - полный формат
                  output = searcher.batch_search(args.query, args.top, args.category, args.subcategory)
          
          # Выводим результат в нужном формате
          if args.format == 'json':
              print(format_json_output(output))
          elif args.format == 'human':
              print(format_human_output(output))
          elif args.format == 'yaml-compact':
              print(format_yaml_compact_output(output))
      
      
      if __name__ == '__main__':
          main()
    • semantic_matcher.py 10.1 KB
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      """
      Семантический matcher для сравнения шагов Vanessa Automation
      
      Выполняет семантическое сравнение шагов по критериям:
      1. Совпадение действия (action)
      2. Совпадение типа элемента (element_type)
      3. Совпадение контекста (context)
      4. Совместимость параметров (params)
      
      Возвращает оценку уверенности и предупреждения.
      """
      
      from typing import Dict, List, Tuple
      from dataclasses import dataclass
      from step_parser import ParsedStep, StepParser
      
      
      @dataclass
      class SemanticMatch:
          """Результат семантического сравнения"""
          action_match: bool
          element_match: bool
          context_match: bool
          params_match: bool
          confidence: float
          is_safe: bool
          warnings: List[str]
          
          def to_dict(self) -> Dict:
              """Преобразование в словарь"""
              return {
                  'action': self.action_match,
                  'element': self.element_match,
                  'context': self.context_match,
                  'params': self.params_match,
                  'confidence': self.confidence,
                  'is_safe': self.is_safe,
                  'warnings': self.warnings
              }
      
      
      class SemanticMatcher:
          """Семантический matcher для сравнения шагов"""
          
          def __init__(self):
              self.parser = StepParser()
          
          def compare(self, original_step: str, suggested_step: str) -> SemanticMatch:
              """
              Семантическое сравнение двух шагов
              
              Args:
                  original_step: Исходный шаг из сценария
                  suggested_step: Предлагаемый шаг из библиотеки
                  
              Returns:
                  SemanticMatch с результатами сравнения
              """
              # Парсим оба шага
              orig_parsed = self.parser.parse(original_step)
              sugg_parsed = self.parser.parse(suggested_step)
              
              # Сравниваем по критериям
              action_match = self._compare_actions(orig_parsed, sugg_parsed)
              element_match = self._compare_elements(orig_parsed, sugg_parsed)
              context_match = self._compare_context(orig_parsed, sugg_parsed)
              params_match = self._compare_params(orig_parsed, sugg_parsed)
              
              # Собираем предупреждения
              warnings = self._generate_warnings(
                  orig_parsed, sugg_parsed,
                  action_match, element_match, context_match, params_match
              )
              
              # Вычисляем уверенность
              confidence = self._calculate_confidence(
                  action_match, element_match, context_match, params_match
              )
              
              # Определяем безопасность замены
              is_safe = self._is_safe_replacement(
                  action_match, element_match, context_match, warnings
              )
              
              return SemanticMatch(
                  action_match=action_match,
                  element_match=element_match,
                  context_match=context_match,
                  params_match=params_match,
                  confidence=confidence,
                  is_safe=is_safe,
                  warnings=warnings
              )
          
          def _compare_actions(self, orig: ParsedStep, sugg: ParsedStep) -> bool:
              """Сравнение действий"""
              if not orig.action or not sugg.action:
                  return True  # Если не удалось извлечь - не считаем ошибкой
              
              # Точное совпадение
              if orig.action == sugg.action:
                  return True
              
              # Проверяем принадлежность к одной категории
              return self.parser.are_actions_compatible(orig.action, sugg.action)
          
          def _compare_elements(self, orig: ParsedStep, sugg: ParsedStep) -> bool:
              """Сравнение типов элементов"""
              if not orig.element_type or not sugg.element_type:
                  return True  # Если не удалось извлечь - не считаем ошибкой
              
              # Точное совпадение
              if orig.element_type == sugg.element_type:
                  return True
              
              # Проверяем принадлежность к одной категории
              return self.parser.are_elements_compatible(orig.element_type, sugg.element_type)
          
          def _compare_context(self, orig: ParsedStep, sugg: ParsedStep) -> bool:
              """Сравнение контекстов"""
              # Если оба без контекста - ок
              if not orig.context and not sugg.context:
                  return True
              
              # Если один есть, другого нет - зависит от направления
              if not orig.context and sugg.context:
                  return False  # Добавляется более специфичный контекст - может не подойти
              
              if orig.context and not sugg.context:
                  return True  # Убирается контекст - обычно ок
              
              # Оба есть - должны совпадать или быть похожими
              if orig.context == sugg.context:
                  return True
              
              # Проверяем синонимы
              context_synonyms = [
                  ('в таблице', 'в табличной части'),
                  ('в форме', 'в окне')
              ]
              
              for syn1, syn2 in context_synonyms:
                  if (orig.context == syn1 and sugg.context == syn2) or \
                     (orig.context == syn2 and sugg.context == syn1):
                      return True
              
              return False
          
          def _compare_params(self, orig: ParsedStep, sugg: ParsedStep) -> bool:
              """Сравнение параметров"""
              # Если количество параметров отличается существенно - предупреждение
              if abs(len(orig.params) - len(sugg.params)) > 1:
                  return False
              
              # Если параметры есть в обоих и они одинаковые - идеально
              if orig.params and sugg.params and orig.params == sugg.params:
                  return True
              
              # В библиотеке часто используются плейсхолдеры - это нормально
              return True
          
          def _generate_warnings(
              self, 
              orig: ParsedStep, 
              sugg: ParsedStep,
              action_match: bool,
              element_match: bool,
              context_match: bool,
              params_match: bool
          ) -> List[str]:
              """Генерация предупреждений"""
              warnings = []
              
              if not action_match:
                  warnings.append(
                      f"Different action type: '{orig.action}' vs '{sugg.action}'"
                  )
              
              if not element_match:
                  warnings.append(
                      f"Different UI element type: '{orig.element_type}' vs '{sugg.element_type}'"
                  )
              
              if not context_match:
                  warnings.append(
                      f"Different context: '{orig.context}' vs '{sugg.context}'"
                  )
              
              if not params_match:
                  warnings.append(
                      f"Different number of parameters: {len(orig.params)} vs {len(sugg.params)}"
                  )
              
              return warnings
          
          def _calculate_confidence(
              self,
              action_match: bool,
              element_match: bool,
              context_match: bool,
              params_match: bool
          ) -> float:
              """
              Вычисление уверенности в замене
              
              Веса:
              - Действие: 40% (критично)
              - Элемент: 40% (критично)
              - Контекст: 15%
              - Параметры: 5%
              """
              weights = {
                  'action': 0.40,
                  'element': 0.40,
                  'context': 0.15,
                  'params': 0.05
              }
              
              score = 0.0
              
              if action_match:
                  score += weights['action']
              if element_match:
                  score += weights['element']
              if context_match:
                  score += weights['context']
              if params_match:
                  score += weights['params']
              
              return round(score, 2)
          
          def _is_safe_replacement(
              self,
              action_match: bool,
              element_match: bool,
              context_match: bool,
              warnings: List[str]
          ) -> bool:
              """
              Определение безопасности замены
              
              Замена безопасна если:
              1. Действие совпадает (обязательно)
              2. Элемент совпадает (обязательно)
              3. Контекст совпадает ИЛИ отсутствует в оригинале
              4. Нет критичных предупреждений
              """
              # Действие и элемент ОБЯЗАТЕЛЬНО должны совпадать
              if not action_match or not element_match:
                  return False
              
              # Контекст желательно, но не критично
              # (может отсутствовать в более общем шаге)
              
              # Проверяем критичные предупреждения
              critical_warnings = [
                  "Different action type",
                  "Different UI element type"
              ]
              
              for warning in warnings:
                  if any(cw in warning for cw in critical_warnings):
                      return False
              
              return True
          
          def get_confidence_level(self, confidence: float) -> str:
              """
              Получение уровня уверенности
              
              Returns:
                  "high" (>0.8), "medium" (0.6-0.8), "low" (<0.6)
              """
              if confidence > 0.8:
                  return "high"
              elif confidence > 0.6:
                  return "medium"
              else:
                  return "low"
    • step_parser.py 8.1 KB
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      """
      Парсер шагов Vanessa Automation для семантического анализа
      
      Извлекает компоненты шага:
      - action: действие (нажимаю, ввожу, выбираю, проверяю, открываю)
      - element_type: тип UI элемента (кнопка, поле, список, таблица)
      - context: контекст выполнения (в таблице, в форме, в дереве)
      - params: параметры шага (заголовки, имена, значения)
      """
      
      import re
      from typing import Dict, List, Optional
      from dataclasses import dataclass
      
      
      @dataclass
      class ParsedStep:
          """Распарсенный шаг с компонентами"""
          action: str = ""
          element_type: str = ""
          context: str = ""
          params: List[str] = None
          
          def __post_init__(self):
              if self.params is None:
                  self.params = []
      
      
      class StepParser:
          """Парсер шагов для извлечения семантических компонентов"""
          
          # Категории действий
          ACTIONS = {
              'клик': ['нажимаю', 'кликаю', 'щелкаю', 'выбираю'],
              'ввод': ['ввожу', 'устанавливаю', 'задаю', 'заполняю'],
              'выбор': ['выбираю из', 'выбираю по', 'выбираю точное'],
              'проверка': ['проверяю', 'сравниваю', 'убеждаюсь', 'жду'],
              'навигация': ['открываю', 'перехожу', 'закрываю', 'активизирую']
          }
          
          # Типы UI элементов
          ELEMENTS = {
              'кнопочные': ['кнопка', 'кнопка с именем', 'кнопка командного интерфейса', 
                            'кнопка выбора', 'кнопку'],
              'поля': ['поле', 'поле с именем', 'поле ввода', 'поля'],
              'списки': ['выпадающий список', 'выпадающего списка', 'список', 'дерево'],
              'ссылки': ['гиперссылка', 'гиперссылку', 'навигационная ссылка', 
                         'навигационную ссылку'],
              'таблицы': ['таблица', 'таблице', 'табличная часть', 'табличной части'],
              'формы': ['форма', 'форме', 'окно', 'панель', 'группа']
          }
          
          # Контексты
          CONTEXTS = [
              'в таблице', 'в табличной части',
              'в форме', 'в окне',
              'в дереве',
              'в панели', 'в группе'
          ]
          
          def parse(self, step: str) -> ParsedStep:
              """
              Парсинг шага с извлечением компонентов
              
              Args:
                  step: Строка шага Gherkin
                  
              Returns:
                  ParsedStep с извлеченными компонентами
              """
              # Берем только первую строку для многострочных шагов
              first_line = step.split('\n')[0].strip()
              
              # Удаляем ключевые слова Gherkin
              normalized = re.sub(
                  r'^(Дано|Когда|Тогда|И|Также|Затем|Но)\s+', 
                  '', 
                  first_line, 
                  flags=re.IGNORECASE
              )
              
              # Приводим к нижнему регистру для анализа
              lower_step = normalized.lower()
              
              result = ParsedStep()
              
              # Извлекаем действие
              result.action = self._extract_action(lower_step)
              
              # Извлекаем тип элемента
              result.element_type = self._extract_element_type(lower_step)
              
              # Извлекаем контекст
              result.context = self._extract_context(lower_step)
              
              # Извлекаем параметры
              result.params = self._extract_params(normalized)
              
              return result
          
          def _extract_action(self, step: str) -> str:
              """Извлечение действия из шага"""
              # Пробуем найти действие по категориям
              for category, actions in self.ACTIONS.items():
                  for action in actions:
                      if action in step:
                          return action
              
              # Если не нашли в словаре, пытаемся извлечь первый глагол
              words = step.split()
              if words and words[0] not in ['я', 'в', 'из', 'у']:
                  return words[0]
              
              return ""
          
          def _extract_element_type(self, step: str) -> str:
              """Извлечение типа UI элемента"""
              for category, elements in self.ELEMENTS.items():
                  for element in elements:
                      if element in step:
                          # Возвращаем базовую форму (первый элемент)
                          if category == 'кнопочные':
                              return 'кнопка'
                          elif category == 'поля':
                              return 'поле'
                          elif category == 'списки':
                              if 'выпадающий' in step or 'выпадающего' in step:
                                  return 'выпадающий список'
                              return 'список'
                          elif category == 'ссылки':
                              return 'гиперссылка'
                          elif category == 'таблицы':
                              return 'таблица'
                          elif category == 'формы':
                              return 'форма'
              
              return ""
          
          def _extract_context(self, step: str) -> str:
              """Извлечение контекста выполнения"""
              for context in self.CONTEXTS:
                  if context in step:
                      return context
              
              return ""
          
          def _extract_params(self, step: str) -> List[str]:
              """Извлечение параметров из шага"""
              params = []
              
              # Извлекаем текст в двойных кавычках
              double_quoted = re.findall(r'"([^"]*)"', step)
              params.extend(double_quoted)
              
              # Извлекаем текст в одинарных кавычках
              single_quoted = re.findall(r"'([^']*)'", step)
              params.extend(single_quoted)
              
              # Извлекаем переменные ($Имя$)
              variables = re.findall(r'\$([^$]+)\$', step)
              params.extend([f'${v}$' for v in variables])
              
              return params
          
          def get_action_category(self, action: str) -> str:
              """Получение категории действия"""
              action_lower = action.lower()
              for category, actions in self.ACTIONS.items():
                  if action_lower in actions:
                      return category
              return "unknown"
          
          def get_element_category(self, element: str) -> str:
              """Получение категории элемента"""
              element_lower = element.lower()
              for category, elements in self.ELEMENTS.items():
                  for el in elements:
                      if el in element_lower or element_lower in el:
                          return category
              return "unknown"
          
          def are_actions_compatible(self, action1: str, action2: str) -> bool:
              """Проверка совместимости двух действий"""
              cat1 = self.get_action_category(action1)
              cat2 = self.get_action_category(action2)
              return cat1 == cat2 and cat1 != "unknown"
          
          def are_elements_compatible(self, element1: str, element2: str) -> bool:
              """Проверка совместимости двух элементов"""
              cat1 = self.get_element_category(element1)
              cat2 = self.get_element_category(element2)
              return cat1 == cat2 and cat1 != "unknown"
    • validate.py 32.6 KB
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      """
      Скрипт для валидации сценариев Vanessa Automation
      
      Проверяет:
      1. Все ли шаги из сценария есть в библиотеке шагов
      2. Корректность синтаксиса Gherkin
      3. Наличие обязательных элементов
      4. Правильность использования переменных
      
      Использование:
          python validate_scenario.py scenario.feature
          python validate_scenario.py scenario.feature --library БиблиотекаШагов.json
      """
      
      import json
      import re
      import sys
      import argparse
      import os
      from pathlib import Path
      from typing import List, Dict, Tuple, Set
      from difflib import SequenceMatcher, get_close_matches
      
      from step_parser import StepParser, ParsedStep
      from semantic_matcher import SemanticMatcher, SemanticMatch
      
      # Определяем путь к корню проекта
      SCRIPT_DIR = Path(__file__).parent
      PROJECT_ROOT = SCRIPT_DIR.parent
      DEFAULT_LIBRARY = PROJECT_ROOT / 'data' / 'steps-library.json'
      
      
      class Colors:
          """ANSI цвета для терминала"""
          RED = '\033[91m'
          GREEN = '\033[92m'
          YELLOW = '\033[93m'
          BLUE = '\033[94m'
          MAGENTA = '\033[95m'
          CYAN = '\033[96m'
          WHITE = '\033[97m'
          BOLD = '\033[1m'
          UNDERLINE = '\033[4m'
          END = '\033[0m'
      
      
      class StepLibrary:
          """Библиотека шагов Vanessa Automation"""
          
          def __init__(self, library_path: str, enable_semantic: bool = False):
              self.steps = []
              self.steps_normalized = {}  # нормализованный шаг -> оригинальный шаг
              self.enable_semantic = enable_semantic
              
              # Инициализируем парсер и matcher если доступны
              if self.enable_semantic:
                  self.parser = StepParser()
                  self.matcher = SemanticMatcher()
              
              self.load_library(library_path)
          
          def load_library(self, path: str):
              """Загрузка библиотеки шагов из JSON"""
              try:
                  with open(path, 'r', encoding='utf-8') as f:
                      data = json.load(f)
                      
                  # Поддержка обоих форматов
                  if isinstance(data, list):
                      # Формат БиблиотекаШагов.json
                      self.steps = [step.get('ИмяШага', '') for step in data]
                  elif isinstance(data, dict):
                      # Формат vanessa_steps_ai_knowledge.json
                      for category, steps in data.items():
                          for step in steps:
                              self.steps.append(step.get('шаг', ''))
                  
                  # Нормализуем шаги для сравнения
                  for step in self.steps:
                      normalized = self.normalize_step(step)
                      self.steps_normalized[normalized] = step
                  
                  print(f"{Colors.GREEN}✓ Загружено {len(self.steps)} шагов из библиотеки{Colors.END}")
                  
              except FileNotFoundError:
                  print(f"{Colors.RED}✗ Файл библиотеки не найден: {path}{Colors.END}")
                  sys.exit(1)
              except json.JSONDecodeError as e:
                  print(f"{Colors.RED}✗ Ошибка парсинга JSON: {e}{Colors.END}")
                  sys.exit(1)
          
          @staticmethod
          def normalize_step(step: str) -> str:
              """
              Нормализация шага для сравнения.
              Для многострочных шагов (с таблицами или docstring)
              анализируется только первая строка.
              Заменяет параметры на плейсхолдеры.
              """
              # Для многострочных шагов берем только первую строку с текстом
              first_line = step.split('\n')[0].strip()
      
              # Удаляем ключевые слова (Дано, Когда, Тогда, И, Также, Затем)
              step = re.sub(r'^(Дано|Когда|Тогда|И|Также|Затем|Но)\s+', '', first_line, flags=re.IGNORECASE)
              
              # Приводим к нижнему регистру для единообразного сравнения
              step = step.lower()
      
              # Убираем двоеточие и точку в конце
              if step.endswith(':'):
                  step = step[:-1].strip()
              if step.endswith('.'):
                  step = step[:-1].strip()
              
              # Заменяем текст в двойных кавычках на плейсхолдер
              step = re.sub(r'"[^"]*"', '"{}"', step)
              
              # Заменяем текст в одинарных кавычках на плейсхолдер
              step = re.sub(r"'[^']*'", '"{}"', step)
              
              # Заменяем экранированные кавычки из JSON (\") на обычные
              step = step.replace('\\"', '"')
              
              # Заменяем переменные ($Имя$) на плейсхолдер
              step = re.sub(r'\$[^$]+\$', '${}$', step)
              
              # Заменяем числа на плейсхолдер
              step = re.sub(r'\b\d+\b', '#', step)
              
              # Убираем лишние пробелы
              step = re.sub(r'\s+', ' ', step)
              
              return step.strip()
          
          def find_step(self, step: str) -> Tuple[bool, str, List[str]]:
              """
              Поиск шага в библиотеке
              Возвращает: (найден, точное_совпадение, похожие_шаги)
              """
              normalized = self.normalize_step(step)
              
              # Точное совпадение
              if normalized in self.steps_normalized:
                  return True, self.steps_normalized[normalized], []
              
              # Ищем похожие шаги
              similar = []
              for norm_step, orig_step in self.steps_normalized.items():
                  ratio = SequenceMatcher(None, normalized, norm_step).ratio()
                  if ratio > 0.7:  # порог схожести 70%
                      similar.append((orig_step, ratio))
              
              # Сортируем по убыванию схожести
              similar.sort(key=lambda x: x[1], reverse=True)
              similar_steps = [s[0] for s in similar[:5]]  # топ-5
              
              return False, "", similar_steps
          
          def find_step_with_semantic(self, step: str) -> Dict:
              """
              Расширенный поиск с семантическим анализом
              Возвращает детальную информацию для AI
              """
              normalized = self.normalize_step(step)
              
              # Точное совпадение
              if normalized in self.steps_normalized:
                  return {
                      'found': True,
                      'exact_match': self.steps_normalized[normalized],
                      'suggestions': []
                  }
              
              # Ищем похожие шаги с семантическим анализом
              suggestions = []
              
              for norm_step, orig_step in self.steps_normalized.items():
                  similarity = SequenceMatcher(None, normalized, norm_step).ratio()
                  
                  if similarity > 0.7:  # порог схожести 70%
                      suggestion_data = {
                          'text': orig_step,
                          'similarity': round(similarity, 2)
                      }
                      
                      # Добавляем семантический анализ если включен
                      if self.enable_semantic:
                          try:
                              # Парсим оригинальный и предлагаемый шаги
                              orig_parsed = self.parser.parse(step)
                              sugg_parsed = self.parser.parse(orig_step)
                              
                              # Проводим семантическое сравнение
                              semantic_match = self.matcher.compare(step, orig_step)
                              
                              suggestion_data['parsed'] = {
                                  'action': sugg_parsed.action,
                                  'element_type': sugg_parsed.element_type,
                                  'context': sugg_parsed.context,
                                  'params': sugg_parsed.params
                              }
                              
                              suggestion_data['semantic_match'] = semantic_match.to_dict()
                              suggestion_data['confidence'] = self.matcher.get_confidence_level(
                                  semantic_match.confidence
                              )
                              
                          except Exception as e:
                              # Если семантический анализ не удался, продолжаем без него
                              pass
                      
                      suggestions.append(suggestion_data)
              
              # Сортируем по схожести
              suggestions.sort(key=lambda x: x['similarity'], reverse=True)
              
              # Возвращаем топ-5
              return {
                  'found': False,
                  'exact_match': '',
                  'suggestions': suggestions[:5]
              }
      
      
      class ScenarioValidator:
          """Валидатор сценариев Gherkin"""
          
          KEYWORDS = ['Дано', 'Когда', 'Тогда', 'И', 'Также', 'Затем', 'Но']
          REQUIRED_HEADERS = ['# encoding:', '# language:']
          
          def __init__(self, library: StepLibrary, debug: bool = False, ai_enhanced: bool = False):
              self.library = library
              self.debug = debug
              self.ai_enhanced = ai_enhanced
              self.errors = []
              self.warnings = []
              self.stats = {
                  'total_steps': 0,
                  'valid_steps': 0,
                  'invalid_steps': 0,
                  'scenarios': 0,
                  'features': 0
              }
          
          def validate_file(self, filepath: str) -> Dict:
              """Валидация всего файла"""
              try:
                  with open(filepath, 'r', encoding='utf-8') as f:
                      content = f.read()
                      lines = content.split('\n')
              except FileNotFoundError:
                  return {'error': f'Файл не найден: {filepath}'}
              except UnicodeDecodeError:
                  return {'error': 'Ошибка кодировки. Используйте UTF-8.'}
              
              # Проверяем заголовки
              self._check_headers(lines)
              
              # Проверяем блок Функционал
              self._check_feature_block(lines)
              
              # Проверяем шаги
              self._check_steps(lines)
              
              # Проверяем переменные
              self._check_variables(lines)
              
              # Проверяем синтаксис кавычек
              self._check_quotes(lines)
              
              return {
                  'errors': self.errors,
                  'warnings': self.warnings,
                  'stats': self.stats
              }
          
          def _check_headers(self, lines: List[str]):
              """Проверка обязательных заголовков"""
              first_lines = '\n'.join(lines[:5])
              
              if '# encoding:' not in first_lines and '# -*- coding:' not in first_lines:
                  self.errors.append({
                      'line': 1,
                      'type': 'header',
                      'severity': 'auto_fix',
                      'message': 'Отсутствует строка с кодировкой',
                      'suggestion': 'Добавьте в начало файла: # encoding: utf-8',
                      'fix': 'add_encoding'
                  })
              
              if '# language:' not in first_lines:
                  self.errors.append({
                      'line': 1,
                      'type': 'header',
                      'severity': 'auto_fix',
                      'message': 'Отсутствует строка с языком',
                      'suggestion': 'Добавьте в начало файла: # language: ru',
                      'fix': 'add_language'
                  })
          
          def _check_feature_block(self, lines: List[str]):
              """Проверка блока Функционал"""
              has_feature = False
              
              for i, line in enumerate(lines, 1):
                  if line.strip().startswith('Функционал:'):
                      has_feature = True
                      self.stats['features'] += 1
                      
                      # Проверяем, есть ли описание
                      if len(line.strip()) <= len('Функционал:') + 1:
                          self.warnings.append({
                              'line': i,
                              'type': 'feature',
                              'message': 'Функционал без названия',
                              'suggestion': 'Добавьте название после "Функционал:"'
                          })
                  
                  if line.strip().startswith('Сценарий:'):
                      self.stats['scenarios'] += 1
              
              if not has_feature:
                  self.errors.append({
                      'line': 0,
                      'type': 'structure',
                      'message': 'Отсутствует блок "Функционал:"',
                      'suggestion': 'Добавьте блок "Функционал:" перед сценариями'
                  })
          
          def _check_steps(self, lines: List[str]):
              """Проверка всех шагов, включая многострочные"""
              in_scenario = False
              current_step_lines = []
              current_step_start_line = 0
      
              for i, line in enumerate(lines, 1):
                  stripped = line.strip()
      
                  is_keyword_line = any(stripped.startswith(kw) for kw in self.KEYWORDS)
                  is_new_scenario = stripped.startswith(('Сценарий:', 'Контекст:', 'Функционал:'))
                  is_comment = stripped.startswith('#')
                  is_empty = not stripped
      
                  # Если мы встречаем новый шаг или начало нового сценария,
                  # и у нас есть накопленный предыдущий шаг, то валидируем его.
                  if current_step_lines and (is_keyword_line or is_new_scenario):
                      full_step = "\n".join(current_step_lines)
                      self.stats['total_steps'] += 1
                      self._validate_step(current_step_start_line, full_step)
                      current_step_lines = []
      
                  if is_new_scenario:
                      in_scenario = not stripped.startswith('Функционал:')
                      continue
      
                  if not in_scenario or is_comment or is_empty:
                      continue
      
                  if is_keyword_line:
                      # Начинаем новый шаг
                      current_step_start_line = i
                      current_step_lines = [stripped]
                  elif current_step_lines and (stripped.startswith('|') or stripped.startswith('"""')):
                      # Продолжаем многострочный шаг (таблица или docstring)
                      current_step_lines.append(stripped)
      
              # Валидируем последний шаг в файле, если он есть
              if current_step_lines:
                  full_step = "\n".join(current_step_lines)
                  self.stats['total_steps'] += 1
                  self._validate_step(current_step_start_line, full_step)
          
          def _validate_step(self, line_num: int, step: str):
              """Валидация конкретного шага"""
              # Используем расширенный поиск если включен AI-enhanced режим
              if self.ai_enhanced and self.library.enable_semantic:
                  result = self.library.find_step_with_semantic(step)
                  found = result['found']
                  exact_match = result['exact_match']
                  suggestions = result.get('suggestions', [])
              else:
                  found, exact_match, similar = self.library.find_step(step)
                  suggestions = [{'text': s} for s in similar]
              
              if found:
                  self.stats['valid_steps'] += 1
                  if self.debug:
                      print(f"{Colors.BLUE}✓ Шаг на строке {line_num} найден:{Colors.END} {step.splitlines()[0]}")
                      print(f"{Colors.GREEN}  ↳ Соответствие в библиотеке:{Colors.END} {exact_match.splitlines()[0]}")
              else:
                  self.stats['invalid_steps'] += 1
                  
                  error_info = {
                      'line': line_num,
                      'type': 'step',
                      'step': step,
                      'message': 'Шаг не найден в библиотеке',
                      'suggestion': ''
                  }
                  
                  # Определяем severity на основе наличия рекомендаций и их качества
                  if not suggestions:
                      error_info['severity'] = 'critical'
                  elif self.ai_enhanced and suggestions:
                      # Проверяем наличие безопасных рекомендаций
                      has_safe = any(
                          s.get('semantic_match', {}).get('is_safe', False)
                          for s in suggestions
                      )
                      error_info['severity'] = 'semantic_check_required' if has_safe else 'critical'
                  else:
                      error_info['severity'] = 'semantic_check_required'
                  
                  if suggestions:
                      if self.ai_enhanced:
                          error_info['suggestions'] = suggestions
                      else:
                          error_info['similar_steps'] = [s['text'] for s in suggestions]
                      error_info['suggestion'] = f'Возможно, вы имели в виду один из этих шагов'
                  else:
                      error_info['suggestion'] = 'Проверьте правильность написания шага или используйте другой шаг из библиотеки'
                  
                  # Добавляем parsed информацию для оригинального шага если включен AI-enhanced
                  if self.ai_enhanced and self.library.enable_semantic:
                      try:
                          orig_parsed = self.library.parser.parse(step)
                          error_info['parsed'] = {
                              'action': orig_parsed.action,
                              'element_type': orig_parsed.element_type,
                              'context': orig_parsed.context,
                              'params': orig_parsed.params
                          }
                      except:
                          pass
                  
                  self.errors.append(error_info)
          
          def _check_variables(self, lines: List[str]):
              """Проверка правильности использования переменных"""
              used_vars = set()
              defined_vars = set()
              
              for i, line in enumerate(lines, 1):
                  # Ищем использование переменных ($ИмяПеременной$)
                  used = re.findall(r'\$([^$]+)\$', line)
                  for var in used:
                      used_vars.add((var, i))
                  
                  # Ищем определение переменных (запоминаю ... в переменную)
                  if 'в переменную' in line or 'как' in line:
                      defined = re.findall(r'переменную "([^"]+)"', line)
                      defined += re.findall(r'как "([^"]+)"', line)
                      for var in defined:
                          defined_vars.add(var)
              
              # Проверяем неопределенные переменные
              for var, line_num in used_vars:
                  if var not in defined_vars:
                      self.warnings.append({
                          'line': line_num,
                          'type': 'variable',
                          'message': f'Переменная "${var}$" используется, но не определена',
                          'suggestion': f'Добавьте шаг для определения переменной "{var}" перед ее использованием'
                      })
          
          def _check_quotes(self, lines: List[str]):
              """Проверка правильности кавычек"""
              for i, line in enumerate(lines, 1):
                  # Проверяем одинарные кавычки
                  if "'" in line and any(line.strip().startswith(kw) for kw in self.KEYWORDS):
                      error_info = {
                          'line': i,
                          'type': 'syntax',
                          'severity': 'auto_fix',
                          'message': 'Использованы одинарные кавычки вместо двойных',
                          'suggestion': 'Замените одинарные кавычки \' на двойные "',
                          'fix': 'replace_quotes'
                      }
                      self.errors.append(error_info)
      
      
      def print_report(result: Dict, verbose: bool = False):
          """Вывод отчета о валидации"""
          print("\n" + "="*80)
          print(f"{Colors.BOLD}ОТЧЕТ О ВАЛИДАЦИИ СЦЕНАРИЯ{Colors.END}")
          print("="*80 + "\n")
          
          # Статистика
          stats = result['stats']
          print(f"{Colors.BOLD}📊 СТАТИСТИКА:{Colors.END}")
          print(f"  Функционалов: {stats['features']}")
          print(f"  Сценариев: {stats['scenarios']}")
          print(f"  Всего шагов: {stats['total_steps']}")
          print(f"  {Colors.GREEN}✓ Валидных шагов: {stats['valid_steps']}{Colors.END}")
          print(f"  {Colors.RED}✗ Невалидных шагов: {stats['invalid_steps']}{Colors.END}")
          
          # Прогресс-бар
          if stats['total_steps'] > 0:
              percent = (stats['valid_steps'] / stats['total_steps']) * 100
              bar_length = 40
              filled = int(bar_length * percent / 100)
              bar = '█' * filled + '░' * (bar_length - filled)
              color = Colors.GREEN if percent >= 90 else Colors.YELLOW if percent >= 70 else Colors.RED
              print(f"\n  {color}[{bar}] {percent:.1f}%{Colors.END}\n")
          
          # Ошибки
          errors = result['errors']
          if errors:
              print(f"{Colors.BOLD}{Colors.RED}❌ ОШИБКИ ({len(errors)}):{Colors.END}\n")
              
              for i, error in enumerate(errors, 1):
                  print(f"{Colors.BOLD}{i}. Строка {error['line']}: {error['message']}{Colors.END}")
                  
                  if error['type'] == 'step' and verbose:
                      print(f"   {Colors.CYAN}Шаг: {error['step']}{Colors.END}")
                  
                  print(f"   {Colors.YELLOW}💡 Рекомендация: {error['suggestion']}{Colors.END}")
                  
                  if 'similar_steps' in error and error['similar_steps']:
                      print(f"   {Colors.MAGENTA}Похожие шаги из библиотеки:{Colors.END}")
                      for j, similar in enumerate(error['similar_steps'][:3], 1):
                          print(f"      {j}. {similar}")
                  
                  print()
          else:
              print(f"{Colors.GREEN}✓ Ошибок не найдено!{Colors.END}\n")
          
          # Предупреждения
          warnings = result['warnings']
          if warnings:
              print(f"{Colors.BOLD}{Colors.YELLOW}⚠️  ПРЕДУПРЕЖДЕНИЯ ({len(warnings)}):{Colors.END}\n")
              
              for i, warning in enumerate(warnings, 1):
                  print(f"{Colors.BOLD}{i}. Строка {warning['line']}: {warning['message']}{Colors.END}")
                  print(f"   {Colors.YELLOW}💡 Рекомендация: {warning['suggestion']}{Colors.END}\n")
          
          # Итоговый вердикт
          print("="*80)
          if not errors:
              print(f"{Colors.GREEN}{Colors.BOLD}✓ СЦЕНАРИЙ ВАЛИДЕН И ГОТОВ К ЗАПУСКУ!{Colors.END}")
          else:
              print(f"{Colors.RED}{Colors.BOLD}✗ ТРЕБУЕТСЯ ИСПРАВЛЕНИЕ ОШИБОК{Colors.END}")
          print("="*80 + "\n")
      
      
      def print_compact_report(result: Dict):
          """Вывод отчета в компактном формате для экономии токенов."""
          errors = result.get('errors', [])
          warnings = result.get('warnings', [])
      
          if not errors and not warnings:
              print("OK")
              return
      
          print("---")
          print("report:")
          if errors:
              print("  errors:")
              for error in errors:
                  line = error.get('line', 0)
                  step = error.get('step', '')
                  message = error.get('message', '')
                  
                  # Для ошибок шагов выводим несколько лучших вариантов
                  if error['type'] == 'step' and error.get('similar_steps'):
                      print(f"    - line: {line}")
                      print(f"      step: \"{step}\"")
                      print(f"      suggestions:")
                      for suggestion in error['similar_steps'][:3]:
                          print(f"        - \"{suggestion}\"")
                  else: # Для остальных ошибок - просто сообщение
                      print(f"    - line: {line}")
                      print(f"      step: \"{step}\"")
                      print(f"      error: \"{message}\"")
                      print(f"      fix: \"{error.get('suggestion', '')}\"")
      
          if warnings:
              print("  warnings:")
              for warning in warnings:
                  line = warning.get('line', 0)
                  message = warning.get('message', '')
                  print(f"    - line: {line}")
                  print(f"      warning: \"{message}\"")
                  print(f"      fix: \"{warning.get('suggestion', '')}\"")
          print("---")
      
      
      def print_ai_enhanced_report(result: Dict):
          """Вывод расширенного отчета в JSON.
      
          Печатается только сам отчет, без преамбулы и разделителей: потребитель
          разбирает вывод целиком. JSON взят из стандартной библиотеки и экранирует сам;
          ручная сборка разметки ломалась бы на тексте шага с кавычками и двоеточиями.
          """
          
          errors = result.get('errors', [])
          warnings = result.get('warnings', [])
          
          # В отчет идет только то, что разбирает потребитель
          clean_errors = []
          for error in errors:
              # Копируем чтобы не изменять оригинал
              err_copy = error.copy()
              
              # Удаляем поля, которые не нужны в AI-отчете
              err_copy.pop('message', None)
              err_copy.pop('suggestion', None)
              err_copy.pop('similar_steps', None)
              
              # Оставляем только 'text' в suggestions если нет семантики
              if 'suggestions' in err_copy and err_copy['suggestions']:
                  if 'semantic_match' not in err_copy['suggestions'][0]:
                      err_copy['suggestions'] = [s['text'] for s in err_copy['suggestions']]
              
              clean_errors.append(err_copy)
              
          report = {
              'report': {
                  'errors': clean_errors,
                  'warnings': warnings,
                  'stats': result.get('stats', {})
              }
          }
          
          print(json.dumps(report, ensure_ascii=False, indent=2))
      
      
      def print_recommendations_for_ai(result: Dict):
          """Вывод рекомендаций в формате для AI-ассистента"""
          errors = result['errors']
          
          if not errors:
              print(f"\n{Colors.GREEN}Все шаги корректны! Сценарий можно использовать.{Colors.END}\n")
              return
          
          print(f"\n{Colors.BOLD}📋 РЕКОМЕНДАЦИИ ДЛЯ AI-АССИСТЕНТА:{Colors.END}\n")
          print("Обнаружены следующие проблемы, которые нужно исправить:\n")
          
          step_errors = [e for e in errors if e['type'] == 'step']
          
          if step_errors:
              print(f"{Colors.RED}Шаги, не найденные в библиотеке:{Colors.END}\n")
              
              for i, error in enumerate(step_errors, 1):
                  print(f"{i}. Строка {error['line']}:")
                  print(f"   ❌ Неверный шаг: {error['step']}")
                  
                  if 'similar_steps' in error and error['similar_steps']:
                      print(f"   ✅ Замените на один из этих шагов:")
                      for j, similar in enumerate(error['similar_steps'][:2], 1):
                          print(f"      {j}) {similar}")
                  else:
                      print(f"   ⚠️  Похожих шагов не найдено. Выберите другой подход из библиотеки.")
                  print()
          
          other_errors = [e for e in errors if e['type'] != 'step']
          if other_errors:
              print(f"{Colors.YELLOW}Другие проблемы:{Colors.END}\n")
              for error in other_errors:
                  print(f"• {error['message']} (строка {error['line']})")
                  print(f"  Решение: {error['suggestion']}\n")
      
      
      def main():
          # Вывод содержит кириллицу. Без явного переключения печать падает с UnicodeEncodeError
          # везде, где консоль не в UTF-8: сборочный агент, чужая локаль.
          sys.stdout.reconfigure(encoding='utf-8')
          sys.stderr.reconfigure(encoding='utf-8')
          parser = argparse.ArgumentParser(
              description='Валидация сценариев Vanessa Automation',
              formatter_class=argparse.RawDescriptionHelpFormatter,
              epilog="""
      Примеры использования:
        python validate_scenario.py scenario.feature
        python validate_scenario.py scenario.feature --library БиблиотекаШагов.json
        python validate_scenario.py scenario.feature --verbose --ai-format
              """
          )
          
          parser.add_argument('scenario', help='Путь к файлу сценария (.feature)')
          parser.add_argument(
              '--library', '-l',
              default=str(DEFAULT_LIBRARY),
              help=f'Путь к файлу библиотеки шагов (по умолчанию: {DEFAULT_LIBRARY})'
          )
          parser.add_argument(
              '--verbose', '-v',
              action='store_true',
              help='Подробный вывод'
          )
          parser.add_argument(
              '--ai-format',
              action='store_true',
              help='Вывод рекомендаций в формате для AI-ассистента'
          )
          parser.add_argument(
              '--ai-enhanced',
              action='store_true',
              help='Вывод расширенного отчета в JSON с семантическим анализом'
          )
          parser.add_argument(
              '--compact', '-c',
              action='store_true',
              help='Вывод в компактном формате (для экономии токенов)'
          )
          parser.add_argument(
              '--debug',
              action='store_true',
              help='Включает режим отладки с выводом каждого успешного шага'
          )
          
          args = parser.parse_args()
          
          # Проверяем существование файлов
          if not Path(args.scenario).exists():
              print(f"{Colors.RED}✗ Файл сценария не найден: {args.scenario}{Colors.END}")
              sys.exit(1)
          
          if not Path(args.library).exists():
              print(f"{Colors.RED}✗ Файл библиотеки не найден: {args.library}{Colors.END}")
              print(f"{Colors.YELLOW}Используйте --library для указания пути к БиблиотекаШагов.json{Colors.END}")
              sys.exit(1)
          
          print(f"\n{Colors.BOLD}Валидация сценария: {args.scenario}{Colors.END}")
          print(f"Библиотека шагов: {args.library}\n")
          
          # Загружаем библиотеку с учетом семантического анализа
          library = StepLibrary(args.library, enable_semantic=args.ai_enhanced)
          
          # Валидируем сценарий
          validator = ScenarioValidator(library, debug=args.debug, ai_enhanced=args.ai_enhanced)
          result = validator.validate_file(args.scenario)
          
          if 'error' in result:
              print(f"{Colors.RED}✗ Ошибка: {result['error']}{Colors.END}")
              sys.exit(1)
          
          # Выводим отчет в нужном формате
          if args.ai_enhanced:
              print_ai_enhanced_report(result)
          elif args.compact:
              print_compact_report(result)
          elif args.ai_format:
              print_report(result, verbose=args.verbose)
              print_recommendations_for_ai(result)
          else:
              print_report(result, verbose=args.verbose)
          
          # Возвращаем код выхода
          sys.exit(0 if not result['errors'] else 1)
      
      
      if __name__ == '__main__':
          main()
      
  • SKILL.md 7.6 KB
    ---
    name: 1c-vanessa-steps
    description: "Реестр шагов Vanessa Automation (BDD для 1С:Предприятие) - 1569 шагов с описаниями, поиск шага по смыслу и валидация готового сценария .feature. Используй ВСЕГДА, когда пишешь, правишь или проверяешь сценарии Vanessa на Gherkin: модель уверенно сочиняет шаги, которых в Vanessa нет, и такой сценарий падает только на прогоне. Триггеры: написать сценарий Vanessa, BDD-тест для 1С, feature-файл, Gherkin для 1С, какой шаг открывает форму, есть ли такой шаг, проверь сценарий, валидация feature, Turbo Gherkin, шаги Ванессы."
    argument-hint: "search <запрос> | validate <файл.feature> | stats"
    allowed-tools:
      - Bash
      - Read
      - Write
      - Edit
      - Glob
    ---
    
    # Шаги Vanessa Automation: поиск и валидация
    
    Библиотека из **1569 шагов** Vanessa Automation с описаниями и типами, плюс два инструмента:
    поиск подходящего шага и проверка готового сценария.
    
    > **Зачем.** Шаг Gherkin - это не свободный текст, а вызов конкретной реализации из Vanessa.
    > Модель, не имея реестра, пишет правдоподобную фразу ("И я телепортирую документ в прошлое"),
    > сценарий выглядит корректно и падает только на прогоне в базе - дорого и не сразу понятно.
    > Реестр закрывает это на этапе написания.
    
    ## When to Use
    
    - Пишешь новый сценарий Vanessa - **сначала найди шаги**, потом составляй из найденного.
    - Правишь чужой сценарий и не уверен, существует ли шаг.
    - Получил сценарий от заказчика или из другого проекта - проверь перед запуском.
    - Нужно понять, каким шагом делается конкретное действие (открыть форму, заполнить поле,
      проверить значение, подключить TestClient, работать с файлами).
    
    ## When NOT to Use
    
    - Юнит-тесты на YaXUnit - это не Gherkin, реестр к ним не относится (см. `testing-patterns.md`).
    - Отладка падающего прогона, где шаг ЕСТЬ, но ведет себя не так - это про данные и конфигурацию,
      а не про реестр шагов.
    
    ## Порядок работы
    
    ### 1. Найти шаг вместо того, чтобы его сочинить
    
    ```bash
    python skills/1c-vanessa-steps/scripts/search_steps.py --query "открыть форму документа" --top 5 --format human
    python skills/1c-vanessa-steps/scripts/search_steps.py --category UI --subcategory Формы --format human
    python skills/1c-vanessa-steps/scripts/search_steps.py --stats
    ```
    
    Запрос - на русском, словами задачи. Вывод `--format json` удобен для дальнейшей обработки,
    `human` - для чтения.
    
    **Критерий:** каждый шаг будущего сценария найден в реестре. Не нашел - это сигнал, что действие
    делается иначе (другим шагом или последовательностью), а НЕ разрешение придумать формулировку.
    
    ### 2. Собрать сценарий
    
    Структура файла, обязательные строки заголовка, типовые связки - `references/scenario-templates.md`.
    Правила формулировок и работа с параметрами шагов - `references/writing-scenarios.md`.
    
    ### 3. Проверить перед сдачей
    
    ```bash
    python skills/1c-vanessa-steps/scripts/validate.py "путь/к/сценарию.feature"
    ```
    
    Отчет дает: сколько шагов валидно, какие строки не найдены в реестре, и рекомендации. Ошибки
    структуры (нет строки языка или кодировки) он тоже ловит.
    
    **Критерий завершения:** невалидных шагов ноль. Один невалидный шаг - сценарий не готов, даже если
    остальные девяносто девять на месте.
    
    ### 4. Разбор спорного случая
    
    Когда шаг похож на существующий, но не совпадает дословно, помогает разбор по смыслу:
    `references/semantic-validation.md` - как сопоставляются параметры и почему близкий по виду шаг
    может быть другим шагом.
    
    ## Состав
    
    | Путь | Что |
    |------|-----|
    | `data/steps-library.json` | реестр: 1569 шагов, 13 категорий, 374 подкатегории |
    | `scripts/search_steps.py` | поиск по запросу, категории, подкатегории; статистика |
    | `scripts/validate.py` | проверка `.feature`: существование шагов и структура файла |
    | `scripts/indexer.py` | построение индексов для ускорения поиска на больших прогонах |
    | `references/steps-compact.md` | весь реестр компактным списком - когда нужен просмотр глазами |
    | `references/writing-scenarios.md` | как писать сценарии: формулировки, параметры, типовые ошибки |
    | `references/scenario-templates.md` | готовые шаблоны сценариев |
    | `references/semantic-validation.md` | сопоставление шагов по смыслу, разбор параметров |
    
    Зависимостей нет - только стандартная библиотека Python 3.8+.
    
    ## Ограничения
    
    - Реестр - **снимок** библиотеки шагов на момент сборки. Своя сборка Vanessa, плагины или шаги из
      подключенных библиотек проекта в нем отсутствуют: шаг, не найденный валидатором, может быть
      проектным. Проверять по своей поставке, а не считать вывод валидатора приговором.
    - Валидатор проверяет СУЩЕСТВОВАНИЕ шага и структуру файла, а не осмысленность сценария. Логику
      проверки, порядок шагов и тестовые данные он не оценивает.
    
    ## Источник
    
    Реестр шагов и инструменты поиска и валидации взяты из проекта
    [va-ai](https://github.com/Nikolay-Shirokov/va-ai) (MIT, (c) Nikolay Shirokov) и адаптированы под
    раскладку этого набора. Сам Vanessa Automation - отдельный проект, см.
    [vanessa-automation](https://github.com/Pr-mex/vanessa-automation).
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related