ci: prove the screenshot environment instead of trusting the place

Правило приёмки скриншотов было про место: снимать только в CI. Обоснование
измерено — съёмка в другом окружении переписывает файлы без содержательных
изменений, в #231 два из девяти на 7–8 байт, набор с беты все девять. Но
держалось правило на комментарии, а не на механизме: кандидат проверялся на
самосогласованность и принимался целиком, ни разу не сравниваясь с тем, что
лежит в репозитории.

Цена видна на #390: правка типов, которая физически не может сдвинуть
пиксель, потребовала прогона workflow, а затем правки одиннадцати полей
манифеста руками.

Теперь правило про доказательство, и оно то же, что у golden с #334: среда
доказана, если каждый кадр, который менять не собирались, совпал с
закоммиченным байт-в-байт. Расхождение растеризации спрятать нельзя — оно
задевает все кадры с текстом сразу. Снимать можно где угодно, включая WSL;
принять получится только оттуда, где кадры воспроизводятся, и перестанет
получаться в тот день, когда обновятся шрифты.

Остальное следует из того же правила: намерение объявляется
--expect-change, необъявленное расхождение останавливает приёмку,
объявленное без расхождения — тоже (ложная декларация обесценивает список),
заменяются ровно объявленные файлы, а тотальная перерисовка требует
--no-witnesses --reason, и причина уезжает в манифест.

Частый случай закрылся сам: ничего не объявлено, все кадры совпали —
принимается один манифест, руками ничего писать не надо.

Проверено шестью сквозными прогонами на подделанном артефакте, не только
юнитами: идентичный кандидат, необъявленное расхождение, объявленное,
молчаливая декларация, тотальная перерисовка без причины и с ней.

Issue: #401
User-Visible: no
This commit is contained in:
Matysh
2026-08-31 09:40:52 +03:00
parent 4683a493cc
commit 5c8cb58e1f
5 changed files with 334 additions and 11 deletions
+18
View File
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createHash } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { verifyDocsCandidate } from '../scripts/docs-accept.mjs';
import { DOC_SCREENSHOT_VERSION, DOC_SCREENSHOTS } from '../demo/docs/screenshots.mjs';
@@ -128,3 +129,20 @@ test('разбор пути фикстуры не зависит от разде
assert.equal(basename('C:/artifact/sub\\02-view-touch.png'), '02-view-touch.png');
assert.equal(basename('01-view-desktop.png'), '01-view-desktop.png');
});
test('правило среды в шапках совпадает с реализацией (#401)', () => {
// Предыдущее правило («снимать только в CI») жило исключительно в
// комментарии, и разошлось с реальностью в тот день, когда появилась цена.
// Этот тест держит текст и механизм вместе.
const script = readFileSync(new URL('../scripts/docs-accept.mjs', import.meta.url), 'utf8');
const workflow = readFileSync(
new URL('../.github/workflows/docs-screenshots.yml', import.meta.url), 'utf8',
);
for (const [name, text] of [['docs-accept.mjs', script], ['docs-screenshots.yml', workflow]]) {
assert.match(text, /#401/, `${name}: правило приёмки не сослано на решение`);
assert.match(text, /байт-в-байт/, `${name}: не назван признак доказанной среды`);
}
assert.match(script, /--expect-change/, 'декларация намерения обязана быть в описании');
assert.equal(/снимать только в CI|только из артефакта CI/.test(workflow), false,
'старое правило про место съёмки осталось в тексте');
});