Calibrated against 1106 vendor reports (ERP 8.3.24 + БП 8.3.27).
Three categories of false positive removed:
- CalculatedField with empty <expression/> demoted error→warning.
Three legitimate vendor patterns surfaced:
* sibling totalField with same dataPath provides the formula
(used in cancellation-rate and share-percentage reports)
* groupTemplate references the field as group name
* field exists only as a declarative anchor for settingsVariants
Warning preserved so genuinely-missing formulas still surface.
- Duplicate template name demoted error→warning. Vendor configs ship
reports (БазаНормируемыхРасходов/Выручка) with three <template> blocks
named Макет1 — the platform identifies templates by position, not by
<name>. Warning still flags the collision without failing validation.
- comparisonType whitelist extended with NotInHierarchy and
NotInListByHierarchy. Existing list was missing the negated
hierarchy operators used in 20 of the 1106 reports.
Result: 0 false positives across the corpus, all genuine errors still
caught (verified separately against intentionally-broken fixtures).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Calibrated against ~868 real ERP/БП reports — three false positives caught:
1. Composite types: <v8:Type>xs:string</v8:Type> followed by
<v8:Type>d4p1:CatalogRef.X</v8:Type> with a single trailing
<v8:StringQualifiers> is a legitimate pattern. Rewritten check to
collect all <v8:Type> and qualifier blocks per <valueType>, then
verify each qualifier has a matching scalar type anywhere in the
block — not necessarily right before it.
2. System types: AccumulationRecordType (and similar enum-like system
types) use the http://v8.1c.ru/8.1/data/enterprise namespace
(without /current-config) and a plain TypeName local name with no
dot. Whitelisted as a second valid namespace for ref-like types.
3. v8: scalar types extended: v8:Null, v8:Type, v8:ValueStorage —
present in real configs as type-less placeholders.
Also reverted SKILL.md change from previous commit (validator details
don't belong in user-facing docs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
skd-validate was purely structural (names/refs/duplicates) and missed an
entire class of bugs that XDTO rejects at db-load-xml — exactly the
kinds of mistakes the LLM (or hand-edits) commonly introduce.
New section 16: valueType structural — each <v8:Type> must have a known
prefix (xs:/v8: or any prefix bound to enterprise/current-config),
qualifier blocks must match their preceding type, and qualifier
internals (Digits/FractionDigits/AllowedSign, Length/AllowedLength,
DateFractions) must use legal tokens.
New section 17: value content — <value xsi:type="dcscor:DesignTimeValue">
rejects literal placeholders ('_') and empty strings, since these are
the exact symptom of the titan team's BUG-2.
5 new fixtures cover: bare-decimal, missing-qualifiers,
qualifier/type mismatch, ref-literal '_', bad AllowedSign token.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Emit-SingleValueType / emit_single_value_type previously required full
decimal(D,F) — anything else fell through to a fallback that produced
invalid <v8:Type>decimal</v8:Type> (no xs: prefix, no qualifiers).
New regex `^decimal(\((\d+)(,(\d+))?(,nonneg)?\))?$` accepts:
- decimal → 10,2,Any (money default — most common 1C intent)
- decimal(N) → N,0,Any (integer)
- decimal(N,nonneg) → N,0,Nonnegative
- decimal(N,M) → as before
- decimal(N,M,nonneg) → as before
Synonyms (число, число(N), etc.) inherit the same forms via Resolve-TypeStr.
Shared Emit-ValueType is called from fields, parameters, and output
parameters — one fix covers all three paths. 3 existing snapshots
regenerated with proper xs:decimal + qualifiers, plus new
decimal-qualifier-defaults test case covering all 5 forms × synonyms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Зеркалим решение из skd-compile: убираем .Replace('"','"') из Esc-Xml
и удаляем post-process, который принудительно ставил " внутри
<query>/<expression>. Реальный Конфигуратор так не пишет — экранирование
было анти-1С-стилем и портило round-trip diff.
Снимок add-calculated-field-restrict обновлён под новый формат.
Кейс preserve-entities-modify-parameter-title удалён: его смысл
инвертировался (теперь проверял бы нормализацию, а не сохранение),
а часть про многострочный xmlns уже покрыта preserve-xmlns-multiline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Конфигуратор внутри текстового контента <query>/<expression> оставляет " сырыми
(проверено на ERP DCS: 1504 raw " против 0 "). Убираем .Replace('"','"')
из esc_xml — теперь round-trip diff против типовых остаётся чистым.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В типовых конфигурациях (ERP, БП, ЗУП и т.д.) у полей и параметров обычно
есть мульти-язык title (ru + en, иногда + локализация). До этого modify-field /
modify-parameter / modify-dataParameter, перестраивая элемент через
Build-MLTextXml, оставляли только последнее найденное <v8:content> в ru —
en/uk/kk siblings молча терялись, и при следующей выгрузке Designer
ломал миграцию.
Read-FieldProperties сохраняет полный OuterXml <title> в _rawTitle и
коллекционирует OuterXml неизвестных дочерних элементов
(<editFormat>, <appearance>, кастомные расширения) в _unknownChildren.
Build-FieldFragment эмитит:
* _rawTitle как есть, если user не задал новый title;
* Patch-MLTextRu(_rawTitle, newRu) если user задал ru-override — патчит
только <v8:content> в <v8:lang>ru</v8:lang>, остальные языки сохраняет;
* _unknownChildren в конце поля (после valueType).
modify-parameter аналогично: при title-override проверяет multi-lang
(>1 <v8:item>) и патчит ru через Patch-MLTextRu, иначе ребилдит ru-only.
set-field-role сохраняет нестандартные подэлементы <role> (например
<dcscom:addition>, <dcscom:groupFields>), не входящие в фиксированный
known-children set и не указанные через kv в shorthand.
xmlns-стрип на захваченных OuterXml — лишние декларации (которые сериализаторы
добавляют для standalone-фрагментов) убираются.
PY: lxml etree.tostring по умолчанию включает .tail (whitespace после
закрывающего тега), что приводило к non-idempotent ростy whitespace при
повторных прогонах. Везде добавлен with_tail=False.
Новые тесты с idempotent: true:
* preserve-multilang-modify-field (ru-override на multi-lang title);
* preserve-multilang-modify-parameter (то же для параметра);
* preserve-unknown-children-modify-field (role flag, проверяем что
<editFormat> и en title не теряются).
Общая fixture: multilang-base/Template.xml с полем и параметром,
у каждого ru + en title; поле также имеет <editFormat>.
39/39 PS + 39/39 PY. skd-edit v1.20 -> v1.21.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Targeted follow-ups к round-trip фиксу:
* modify-field больше не теряет <valueType> при перестройке поля —
Read-FieldProperties сохраняет полный OuterXml элемента (StringQualifiers,
NumberQualifiers, DateQualifiers и т.п.), Build-FieldFragment отдаёт его
обратно. Лишние xmlns-декларации, добавляемые сериализатором при
выгрузке поддерева, стрипаются регексом.
* Line-ending convention теперь определяется при load (CRLF vs LF) и
единообразно применяется в финале save. Раньше CreateWhitespace и
Build-*Fragment везде использовали CRLF, что приводило к смешанным
переносам в LF-исходниках (и наоборот) и к non-idempotent выходу
modify-parameter title (run 1 → \n\t\t<title>\r\n... → run 2 →
\r\n\t\t<title>\r\n...).
* PS Insert-BeforeElement переведён на LF; все -join "`r`n" → "`n";
py "\r\n".join → "\n". Конечная нормализация переносов делается в
save в соответствии со script:LineEnding.
* preserve-entities-modify-parameter-title.json теперь idempotent: true
(после фикса CRLF leak'а двойной прогон byte-identical).
На реальной схеме diff после modify-field составил 30 строк: целевая
вставка title плюс полезная одноразовая коррекция ранее повреждённых
" в text-content <dcsat:expression>. modify-field идемпотентен.
skd-edit v1.19 -> v1.20.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
XmlDocument round-trip искажал Template.xml даже при отсутствии правок:
декодировал " в <query>/<expression>, схлопывал многострочный xmlns
корня, добавлял пробел перед /> и записывал файл при [WARN] not found.
Дирти-флаг ($script:Dirty / dirty) ставится только на успешной мутации;
финальный save пропускается с [INFO] No changes -- file untouched, если
ни одна операция в batch ничего не изменила. Post-process после OuterXml
восстанавливает raw-форматирование корневого xmlns из исходного файла,
re-escape `"` в текстах <query>/<expression> с anchored regex (не задевая
xsi:type="..."), и нормализует <foo .../> к <foo.../>.
Замеры на реальной схеме после modify-field: diff упал с 423 строк до 37
(94% шума устранено), повторный прогон byte-identical.
В runner.mjs добавлен caseData.idempotent: re-run + byte-equality на всех
файлах workDir. Три новых кейса (NO-OP, entity-preserve, xmlns-multiline)
+ общий fixture roundtrip-base. Все 33 ранее существовавших snapshot
перегенерированы под корректное форматирование (восстанавливают то, что
старый skd-edit ломал).
skd-edit v1.18 -> v1.19. PS и PY порты синхронизированы.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Новый канонический документ docs/web-test-regression-spec.md —
техническое описание движка регрессионных тестов: CLI, формат
тест-модулей, ctx-контракт, утверждения, три уровня хуков
(инфра/тест/контекст), конфиг, контексты Playwright и режимы
изоляции, форматы отчётов (JSON/Allure/JUnit), обнаружение тестов,
ошибки/таймауты/повторы, анализ результатов, глоссарий.
Документ предназначен для CI-интеграторов, ручного редактирования
сгенерированных тестов и сопровождения самого движка. Без дорожной
карты и внутренних self-тестов — только публичный контракт.
regress.md в скилле почищен: добавлены контракт ctx и список
утверждений (раньше модели приходилось читать исходники), срезаны
дубликаты с SKILL.md (live recon, паттерны catalog/document),
переформулированы анти-паттерны под специфику регресс-движка.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В тест-обёртке ACTION_FN при 1С-исключении на throw-ed Error
вешалась полная структура (step, args, errors, formState, stack,
screenshot), но при сборке отчёта движок брал из неё только
{message, step, screenshot} — остальные поля терялись. Платформенный
стек 1С, ради которого делается fetchErrorStack, в JSON-отчёт не
попадал; в Allure statusDetails.trace писался только log()-вывод
теста.
Что поменялось:
- errInfo собирается один раз после teardown (раньше был дубликат на
732 и 745), используется и для ctx.testResult (afterEach), и для
lastError, и для итоговой записи в results[].
- В errInfo добавлено поле onecError: e.onecError — структура с
stack.entries[{location, code}], formState, args, errors доезжает
до JSON-отчёта без обрезания.
- writeAllure склеивает statusDetails.trace из tr.output + (если есть)
onecError.stack.raw под разделителем "--- 1C stack ---". В Allure UI
платформенный стек теперь виден прямо в карточке упавшего теста.
Обратная совместимость: для падений без 1С-исключения (assertion,
навигация и т.п.) e.onecError === undefined → JSON.stringify его
выкидывает, форма записи { message, screenshot } сохраняется в
точности.
Проверено вручную на стенде tests/web-test/ — падающий тест с
ВызватьИсключение, JSON и Allure оба содержат полный stack.entries и
формированный trace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Новая операция clear-conditionalAppearance в стиле clear-selection/
order/filter. Закрывает потребность "заменить набор правил оформления"
через clear + re-add.
- patch-query: многострочные подстроки уже работали (string.Replace
корректно обрабатывает \n). Зафиксировано в SKILL.md.
- add-total: shorthand-шаблон с тремя случаями (Func, Func(expr),
identity-выражение) — после fix Bug 6 поведение нужно явно объяснить.
- Косметика: убрана утечка XML-внутренностей в комментарии примера
set-field-role @period.
- Пример patch-query @once заменён на более типовой случай уникальной
подстроки (КАК ВТ_СтароеИмя вместо ЛЕВОЕ СОЕДИНЕНИЕ).
Регресс: 33/33 PS, 33/33 PY, 33/33 платформенный verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раньше "DataPath: X" всегда заворачивалось в X(DataPath). Если X не
аггрегатная функция (например, имя другого ресурса или сам DataPath),
получалось некорректное выражение типа Проверка(Проверка).
Зеркалю логику из skd-compile: whitelist аггрегатных функций
(Сумма, Количество, Минимум, Максимум, Среднее + EN-варианты).
Для остального — identity (использовать funcPart как есть).
Сообщение [OK] теперь показывает фактически записанный expression.
Регресс: 32/32 PS, 32/32 PY, 32/32 платформенный verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Защищает от случайных замен в комментариях/совпадениях имён:
"ЛЕВОЕ СОЕДИНЕНИЕ => ВНУТРЕННЕЕ СОЕДИНЕНИЕ @once"
# fail, если в запросе 0 или 2+ вхождений
Без флага default — replace-all (как раньше, обратная совместимость).
При успехе сообщение содержит фактическое число вхождений
"(N occurrence(s))", помогает заметить неожиданную множественность
без явного @once.
Регресс: 31/31 PS, 31/31 PY, 31/31 платформенный verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Единый list-синтаксис: availableValue=v1[: p1], v2[: p2], ...
Элементы через запятую, представление после двоеточия.
- Запятые/двоеточия внутри значений и представлений — в одинарных кавычках:
availableValue=Окр1: 'руб., коп.', Окр1000: руб.
- add-parameter теперь принимает availableValue= и создаёт начальный список
в одном вызове (раньше требовался последующий modify-parameter).
- modify-parameter availableValue=... ЗАМЕНЯЕТ весь список (раньше
append). Согласуется с остальными modify-* для одиночных свойств.
- SKILL.md: добавлен shorthand-шаблон для modify-parameter,
расширен для add-parameter [availableValue=список].
Существующие тесты мигрированы со старого ;;-batch на новый list-синтаксис.
Снапшоты сохранились (тесты стартовали с пустого списка — semantics
совпадает для greenfield).
Регресс: 29/29 PS, 29/29 PY, 29/29 платформенный verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В -Mode fields -Name <field> к сводке Role добавляются не-bool
параметры роли (balanceGroupName, balanceType, parentDimension,
accountTypeExpression и т.д.) в формате name=value.
Bool-флаги (@balance, @dimension, ...) отображаются как раньше.
False-значения по-прежнему скрыты.
Регресс: 6/6 PS, 6/6 PY (существующие snapshots не задеты).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- @hidden — скрывает параметр от пользовательских настроек
(useRestriction=true + availableAsField=false). Для констант-параметров.
- @always — параметр всегда подставляется в запрос (use=Always).
Используется самостоятельно для видимых обязательных параметров.
- Композируются: @hidden @always одной строкой даёт типовой паттерн
"скрытая константа всегда применяется".
- Поддержка в add-parameter и modify-parameter, идемпотентны.
Регресс: 25/25 PS, 25/25 PY, 25/25 платформенный verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В реальных выгрузках ERP/БП встречаются непрозрачные платформенные
DataPath, которые невозможно проверить из одного Form.xml:
- bare numeric ("10", "1000003") — внутренние индексы платформы
- "N/M:<uuid>" — ссылка на метаданные по UUID
Раньше Check 5 ругался на них "attribute not found". Теперь такие
пути пропускаются без счёта в paths checked и без ошибки.
Реалистичные пользовательские опечатки (кириллица в имени атрибута)
продолжают ловиться обычной проверкой attrMap.
Добавлен тест-кейс datapath-opaque-refs, версия v1.5 → v1.6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В Check 5 раньше брался первый сегмент DataPath и искался в attrMap,
из-за чего ложно ругались реальные формы ERP/БП с путями вида
Items.<TableName>.CurrentData.<Field> (подвалы, инфо-панели) и
~<DynamicListAttr>.<Field> (текущая строка списка).
Теперь:
- ведущий ~ стрипается перед разбором сегментов;
- для Items.<Table>.CurrentData.* находим элемент-таблицу по name,
берём её <DataPath> (атрибут DynamicList/TableSection) и проверяем
его в attrMap. Если таблицы нет — Error; если третий сегмент не
CurrentData — Warn.
Добавлен тест-кейс datapath-currentdata, версия скриптов v1.4 → v1.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- docs/web-test-regression-guide.md — пользовательские сценарии работы
с моделью для покрытия прикладного решения регрессом (русский, по
аналогии с web-test-recording-guide.md): структура tests/<app-name>/,
диалоги с моделью, пример организации покрытия, отчёты Allure +
categories.json.
- .claude/skills/web-test/regress.md — инструкция модели по написанию
регрессионного набора: разведка (метаданные + живой проход через exec),
layout по фичам, готовые шаблоны (CRUD/document/DCS/multi-user/repro),
severity, anti-patterns, failure triage, _allure/ конвенция.
- SKILL.md — указатель на regress.md в конце файла (рядом с recording).
- docs/web-test-runner-spec.md → upload/ (был внутренним планом
разработки, не пользовательской документацией).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run.mjs:
- syncAllureExtras(testDir, reportDir) копирует все файлы из
<testDir>/_allure/ в reportDir перед генерацией отчёта. Underscore
в имени параллелен _hooks.mjs (инфра, не тест) — discovery его
пропускает.
- Вызов после writeAllure при --format=allure.
tests/web-test/_allure/categories.json — 7 правил классификации падений
по нашему 1С-домену:
1. License pool exhausted (1C) — известный multi-context flake.
2. 1C application error (modal) — exception modal через fetchErrorStack.
3. Section panel icon-only — деградация состояния стенда.
4. Navigation lookup miss — navigateSection/openCommand/navigateLink/switchTab.
5. Element not found — clickElement/fillField/selectValue/closeForm/fillTableRow/deleteTableRow.
6. Test timeout — Timeout (Nms) от раннера.
7. Assertion failure — наши createAssertions + 1С-specific (formHasField/tableHasRow/noErrors).
spec §9: раздел «Доп. файлы Allure через <testDir>/_allure/» с таблицей
поддерживаемых типов (categories.json / environment.properties /
executor.json) и минимальным примером.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run.mjs:
- buildSeverityIndex(config) — валидация config.severity (inverted map
«уровень → [теги]») при загрузке: ключи только из blocker|critical|
normal|minor|trivial, теги не дублируются между bucket'ами,
defaultSeverity тоже валидируется. fail-fast через die.
- resolveSeverity(t, severityIndex):
1. mod.severity если задан и валидный — выигрывает.
2. max-rank среди тегов (стандартные имена severity или маппинг).
3. config.defaultSeverity или 'normal'.
Rank: blocker(5) > critical(4) > normal(3) > minor(2) > trivial(1).
Max-wins инвариантен к порядку тегов.
- writeAllure: добавлены labels suite (= dirname(t.file) или 'root') +
severity. Тег `tag` остался как раньше.
- testResult пробрасывает t.severity (для passed/failed веток).
- SEVERITY_RANK/LEVELS объявлены в модульной шапке (top-level await на
cmdTest начинается до конца тела модуля, TDZ-аккуратность).
webtest.config.mjs: severity policy для нашего сьюта (smoke +
multi-context → critical, recording → minor, defaultSeverity = normal).
spec.md §7: раздел про severity-policy в конфиге с валидацией.
spec.md §9: «Авто-эмиссия label-ов» — tag/suite/severity + правила резолва.
Регресс 19/19 ✓ (9m 7.6s). Распределение по уровням после исправления
'record' → 'recording' в маппинге: 13 critical / 5 normal / 1 minor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
browser.mjs:
- + closeContext(name): logout slot + close page (tab) или context (window),
удаление из реестра. Throw если name неактивен (рулило: nicht den aktiven
closen, recorder always attached к active → invariant простой).
- _logoutSlot(slot, waitMs) — извлечён из disconnect, переиспользуется в
closeContext.
run.mjs:
- ensureContext() после createContext вызывает hooks.afterOpenContext(ctx, name, spec).
- wrapCloseContextHook() оборачивает ctx.closeContext (и каждую scoped-обёртку)
чтобы перед browser.closeContext fir'ить hooks.beforeCloseContext.
- Финальный teardown в finally: для всех живых контекстов кроме первого
(survivor) — beforeCloseContext + closeContext; для survivor только хук,
его закрывает disconnect().
_hooks.mjs v0.5:
- afterOpenContext инжектит persistent DOM-badge с displayName в правый
верхний угол page — в записанном видео всегда видно, какой контекст.
- beforeCloseContext counter-only.
- _state расширен полями afterOpenContext / beforeCloseContext.
15-multi-context-handover.test.mjs:
- +2 шага: closeContext('b') после handover, попытка closeContext(active)
ловится throw'ом с проверкой message.
00-hooks.test.mjs:
- +1 ассерт: afterOpenContext >= 1 (default уже создан), beforeCloseContext === 0
в теле первого теста.
spec §6:
- Раздел «Контекстный уровень» (afterOpenContext / beforeCloseContext + правила closeContext).
- ASCII-диаграмма порядка хуков обновлена с per-context lifecycle.
Регресс 19/19 ✓ (9m 16.8s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
В CLI раннера всё после `--` собирается в массив hookArgs и
передаётся в инфра-хуки prepare/cleanup без интерпретации со
стороны раннера. Сигнатура расширена до { hookArgs, log, config }:
log — структурированный вывод раннера, config — разобранный
webtest.config.mjs. Шаблон «всё после `--` принадлежит вложенному
инструменту» — стандартная shell-конвенция (npm, cargo, pytest).
Спека §6 обновлена под новую сигнатуру, §6.1 закрепляет контракт
`--` ↔ hookArgs с примером. Help-строка раннера упоминает
разделитель.
v1.20 → v1.21 (ps1 + py).
Добавлен ключ DSL `textEdit` для элемента input. Эмитит
`<TextEdit>false</TextEdit>` после AutoMarkIncomplete (значение
true — дефолт платформы, не эмитируется). Закрывает блокер для
03-fillfields/direct-edit-form в синтетике web-test: поле с
запрещённым ручным вводом → выбор только через pick-кнопку/F4.
Snapshot-тест: tests/skills/cases/form-compile/text-edit-flag.json
(2 поля, проверяет наличие TextEdit только на втором). 30/30
form-compile зелёные обоих runtime'ов.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
meta-compile v1.12 (ps1 + py): Parse-AttributeShorthand принимает поле
choiceHistoryOnInput в object-форме аттрибута, Emit-Attribute эмитит его
вместо хардкода Auto. Покрывает атрибуты Catalog/Document/TabularSection
(Emit-Attribute, единственная точка эмиссии в работе). Другие контексты
(register dimensions, resources, etc.) пока эмитят Auto — расширим
при необходимости.
build-webtest-config: реквизит Документ.ПриходнаяНакладная.Контрагент
получил choiceHistoryOnInput='DontUse'. Это убирает 1С-историю выбора
для поля и фиксит pre-existing flake 04-selectvalue/direct-form:
после 03 значение «ООО Север» оставалось в истории и selectValue
выбирал его через dropdown вместо ожидаемой формы выбора.
Live: полный регресс 12/12 впервые зелёный (5m 28s).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run.mjs:
1. resetState проверял `if (!state.form) break`. form === 0 (фоновая
форма 1С, которую detectForm может вернуть) рассматривался как
"форм нет" → cleanup прерывался, форма оставалась → следующий тест
получал грязное состояние. Замена на `state.form == null` корректно
различает null (desktop) и 0 (реальная фоновая форма).
2. Error screenshot в catch-блоке cmdTest снимался ПОСЛЕ resetState,
который уже закрывал все формы → скрин показывал пустой рабочий
стол вместо места падения. Перенёс снимок в начало catch (до
teardown/afterEach/resetState).
Эффекты:
- 15-multi-context-handover теперь стабильно проходит в полном прогоне
(раньше падал когда предыдущий тест оставлял form=0).
- 04-selectvalue/direct-form остался pre-existing flake (история
выбора 1С после 03 — отдельная задача в синтетике).
- Скриншоты падения теперь показывают реальный UI на момент исключения.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
run.mjs v1.10: cmdTest больше не передаёт noRecord:true в buildContext.
Тестам доступен полный API browser.mjs (showCaption, hideCaption,
startRecording, stopRecording, addNarration).
Изначальный стуб с noRecord:true прятал showCaption/hideCaption тестов
вместе с recording-функциями. Это блокировало визуальные оверлеи в
мульти-контекстных тестах: a.showCaption() тихо превращался в no-op,
баннер никогда не отображался даже под --record.
Smart wait внутри showCaption и так гейтится на наличие recorder
(`if (recorder && ...)`), поэтому без --record тесты остаются быстрыми
(никаких 2-секундных пауз на каждый вызов).
startRecording/stopRecording/addNarration теперь тоже доступны тестам.
При попытке вызвать startRecording в момент активной runner-записи
browser.startRecording бросает "Already recording" — loud failure
лучше silent no-op.
Регресс: 15-multi-context-handover один проходит за 19.9s. Полный
прогон 10/12 (04 и 15 флапают независимо в последовательности).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
browser.mjs v1.12 + run.mjs v1.9: createContext принимает isolation параметр.
По умолчанию 'tab' — все контексты живут в одном launchPersistentContext, каждый
слот получает свою Page (вкладку). Преимущества: 1С extension грузится
надёжно (через --load-extension в persistent profile), один процесс Chromium,
дешёвая память. Cookies делятся между вкладками, но скоупятся по URL-path —
для модели «разные пользователи через разные vrd-публикации» это естественно
и достаточно.
isolation: 'window' (opt-in) — старый путь chromium.launch() + newContext():
полная изоляция cookies, отдельный BrowserContext (и окно) на каждый слот,
но extension может не подняться. Использовать когда нужна изоляция auth
внутри одного URL.
Смешивать режимы в одном прогоне нельзя — createContext бросает явную
ошибку (первый createContext устанавливает activeMode, остальные обязаны
совпадать).
Конфиг tests/web-test/webtest.config.mjs: добавлен комментарий с описанием
обоих режимов. По умолчанию tab — синтетика и наши smoke-тесты идут им.
Live: 11/12 в полном прогоне (default tab) + 3/3 sanity-check в window mode
(01-navigation + 14 + 15). Видеозапись из T4.5 работает в обоих режимах.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
browser.mjs v1.11: recorder стал глобальным (не per-slot) — один ffmpeg,
один mp4 на тест с любым числом переключений контекста.
Frame state (lastFrameBuf/lastFrameTime/handler) переехал в поля recorder.
Добавлен recorder._attachPage(targetPage) — стопает старый CDP screencast,
заводит новый на нужной странице, route'ит фреймы в тот же ffmpeg pipe.
setActiveContext: при активной записи делает _flushFrames (замораживает
хвост уходящего окна), затем _attachPage(page) после _activateSlot. Видео
получается непрерывным с плавным сюжетом — пока активен a, видно a; пока
активен b, видно b.
_saveActiveSlot/_activateSlot больше не трогают recorder/lastCaptions/
lastRecordingDuration — recorder следует за активной страницей через
_attachPage, не через slot mirror.
disconnect: убрал leftover из T4.1, который пытался итерировать slot.recorder.
Live: 15-multi-context-handover с --record → 17.84s mp4, 446 кадров @ 25fps,
извлечённые кадры показывают переключение между окнами a (1920x1042) и
b (982x546). Полный регресс 11/12 (04-selectvalue — pre-existing flake).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
browser.mjs v1.10: createContext/setActiveContext/listContexts/getActiveContext/
hasContext. Несколько изолированных BrowserContext в одном Chromium-процессе через
chromium.launch() + newContext(). Module-level page/sessionPrefix/seanceId/recorder
зеркалят активный слот (атомарный своп через _saveActiveSlot/_activateSlot).
connect() оставлен для exec/run/start без изменений (launchPersistentContext).
run.mjs v1.8: ensureContext(name) + ленивое создание. Single-routing через
export const context = 'name'. Multi через export const contexts = ['a','b'] +
buildScopedContext(name) строит ctx.a/ctx.b — каждое действие префиксится
setActiveContext. Reset state после теста по всем активным контекстам.
Конфиг tests/web-test/webtest.config.mjs: два контекста a/b на одну webtest
публикацию (изолированные cookies через newContext).
Smoke-тесты:
- 14-multi-context-routing.test.mjs — single routing в b (2.6s)
- 15-multi-context-handover.test.mjs — ctx.a создаёт Контрагента, ctx.b в
независимой сессии видит запись через filterList, ctx.a cleanup (14.5s, 4/4)
Live: 11/12 в полном прогоне. 04-selectvalue/direct-form флапает —
pre-existing, воспроизводится на baseline 95e4674 (03→04 sequence).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex runner on Windows launches PowerShell as login-shell and decodes
stdout/stderr without UTF-8, garbling Cyrillic output. The other 51 ps1
scripts already set `[Console]::OutputEncoding = UTF8`; bring these 7
in line and add `InputEncoding = UTF8` for symmetry.
Touched: epf-init, erf-init, form-add, form-remove, help-add,
template-add, template-remove. Versions bumped in both ps1 and py
headers to keep the pair in sync.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace literal `.claude/skills/<owner>/...` paths in SKILL.md with the
`${CLAUDE_SKILL_DIR}` variable that Claude Code substitutes at invocation
time. Same-skill references become `${CLAUDE_SKILL_DIR}/<rest>`,
cross-skill references (erf-* → epf-*) become
`${CLAUDE_SKILL_DIR}/../<other>/<rest>`. All paths now wrapped in double
quotes to handle install locations with spaces.
This unblocks plugin-mode installation: literal `.claude/skills/...`
paths fail when the skill is loaded from `~/.claude/plugins/cache/...`
or from a personal `~/.claude/skills/` install. Drop-in mode continues
to work because Claude Code resolves the variable in all install scopes.
Verified via pilot:
- Project drop-in (cc-1c-skills repo)
- Personal `~/.claude/skills/cf-info/`
- Plugin via `/plugin marketplace add <local-path>`
Scope: 61 SKILL.md, 125 path replacements (8 cross-skill).
Scripts unchanged — they already use \$PSScriptRoot and ../<sibling>
patterns that resolve correctly under the bundled cache layout.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Новый DSL-ключ columnGroup со значением-ориентацией horizontal/vertical/inCell
для элемента <ColumnGroup> внутри columns таблицы. Поддерживает вложение,
showTitle/showInHeader/width, тихие синонимы ColumnGroup и ГруппаКолонок.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Архетипы «Форма обработки» и «Мастер»: кнопки действий перенесены
с нижней горизонтальной группы на главную АКП формы (autoCmdBar)
- Конвенция «ГруппаКнопок» заменена на «ФормаКоманднаяПанель»
- Принцип компоновки №3: уточнено, что кнопки идут на АКП
- Сворачиваемые группы: исправлен пример — корректный DSL
(group: collapsible, collapsed: true) вместо несуществующих
ключей behavior/collapsed на vertical-группе
- Полный пример формы обработки переписан под autoCmdBar
- Из свойств мастера убран commandBarLocation: None (не нужен,
когда мы сами наполняем АКП)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Добавлен ключ collapsed (для group=collapsible) → <Collapsed>true</Collapsed>:
группа создаётся уже свёрнутой. Раньше DSL умел только включать
сворачиваемое поведение, но начальное состояние задать было нельзя.
Также уточнено описание united: оно про выравнивание левого края полей
ввода (сквозное/локальное), а не про объединение рамок группы.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Добавлены численные maxWidth/maxHeight (XML <MaxWidth>/<MaxHeight>) —
типичный приём для ограничения растяжения поля при autoMaxWidth: false.
До этого DSL знал только булев autoMaxWidth, и ограничить ширину
числом было невозможно.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Кнопки внутри cmdBar/autoCmdBar/popup автоматически получают
CommandBarButton (или CommandBarHyperlink при type="hyperlink") —
указывать вид вручную не нужно. Резолвер прощающий: принимает и
короткие DSL-формы, и XML-имена в любом контексте.
Пример «Диалог загрузки файла» в SKILL.md и тест-кейс file-dialog
переведены на нативный паттерн с autoCmdBar вместо отдельной
горизонтальной группы кнопок внизу формы.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Поле переключателя с RadioButtonType (Auto/RadioButtons/Tumbler) и
ChoiceList (массив value+presentation). Толерантно к написанию модели:
русские имена тегов (ПолеПереключателя, RadioButtonField),
ВидПереключателя по-русски (Авто/Переключатель/Тумблер),
Перечисление.X.Y без EnumValue, синоним title для presentation,
автогенерация презентации из имени значения.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раннер v1.7.
T5 --record: startRecording перед каждым тестом, stopRecording
после (и в passed, и в failed ветке). Файл
{reportDir}/{testIdx}-{slug}.mp4. testResult.video содержит путь.
В Allure — attachment типа video/mp4. config.record читается
тоже. Использует существующую инфраструктуру browser.mjs.
T6 export const params: материализация в N тестов на этапе
discovery. Имя через {key}-шаблон в mod.name (например
'demo {type}'); если шаблона нет — суффикс [index]. Тест-функция
получает param как второй аргумент: default(ctx, param).
В отчёте каждый набор — отдельная test entry с собственным uuid
в Allure / testcase в JUnit.
Live-проверка:
- params: 2 теста с именами demo A / demo B из шаблона.
- record: mp4 91KB на 6-секундном тесте, путь в JSON и
Allure attachment video/mp4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раннер v1.6. Реализованы оба формата отчётов из spec §9.
allure: {reportDir}/{uuid}-result.json на каждый тест. uuid через
randomUUID, labels из tags, steps рекурсивно с attachments из
step.screenshot, statusDetails для упавших шагов и тестов.
Пропускает skipped (нет start/stop).
junit: один XML в --report=path.xml. Валидация: --format=junit
требует --report=. xmlEscape для name/message/trace. <failure>
для упавших, <skipped/> для пропущенных, <system-out> со ссылкой
на screenshot.
Валидация формата (json|allure|junit) на старте cmdTest.
testResult теперь хранит start/stop в мс — нужно для Allure
и полезно в JSON-отчёте.
Live-проверка: 01-navigation в Allure (5 шагов с attachments,
все ссылки на существующие PNG); JUnit с passed и forced-fail
(спецсимволы корректно экранированы).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Раннер v1.5. Парсит --screenshot и --report-dir, мерж с config.screenshot.
- every-step: после успешного step() пишет {reportDir}/{testIdx}-{stepIdx}-{slug}.png,
путь в step.screenshot.
- off: ни пошаговых, ни error-shot.
- on-failure (default): error-shot уехал из .claude/skills/web-test/
в {reportDir}/error-{testIdx}-{slug}.png.
reportDir фоллбэчит: --report-dir → dirname(--report) → testDir.
Известная нестыковка: error-shot из buildContext/executeScript остаётся в
.claude/skills/web-test/error-shot.png — затронем при T2 (Allure).
Live-проверка: 01-navigation с every-step (5 PNG), off (пусто),
default on-failure на стуб-failing тесте (error-shot в reportDir).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>