Проблема начинается на странице оформления: пользователь ждёт, пока станет доступен следующий шаг. Изменения приходят маленькими: новый код, стиль, виджет, правило рендера. Каждое по отдельности проходит общий check. Через несколько релизов путь стал тяжелее, но CI всё ещё отдаёт зелёный PASS. Цена такого сигнала проста: он говорит, что не пересечён порог, но не говорит, какая часть пути забрала запас и где искать изменение.
Не надо отвечать на это обещанием «ускорить сайт». Сначала нужен один воспроизводимый контракт для конкретного пути: route, сценарий, условия наблюдения, snapshot и именные части бюджета. Тогда проверка отвечает на скромный вопрос: candidate остаётся внутри заранее названного допуска или нет. Она не выдаёт учебный расчёт за браузерный trace, реальную пользовательскую метрику, SLA или результат существующего CI.
Начинаем с пути, а не с общей оценки
Бюджет полезен только у входа, который можно назвать. В fixture это /training/checkout/review и сценарий anonymous-cart-with-one-item. Название намеренно длиннее слова «главная»: при другом товаре, авторизации, locale или feature flag это уже может быть другой вход. Если один job смешивает такие варианты, изменение в одном из них способно спрятаться за средним числом другого.
Дальше фиксируются условия. В учебной модели это version workload, единица teaching-ticks, а browser observation, CI observation, сеть, CPU, cache и trace имеют значение unavailable-in-example или outside-example. Эти поля не делают проверку слабой. Они запрещают дорисовать доказательство задним числом: пока нет trace, нельзя из суммы ticks сделать вывод о браузере; пока нет CI run, нельзя назвать fixture результатом pipeline.
| Элемент | Что хранит fixture | Для чего нужен | Чего не доказывает |
|---|---|---|---|
| Route и scenario | /training/checkout/review, одна корзина | не смешать разные входы | что путь уже прошёл в production |
| Условия | version workload, ticks, unavailable-in-example | остановить сравнение при другой среде | свойства браузера или CI runner |
| Компоненты | documentTicks, styleTicks, scriptTicks, renderTicks | увидеть вклад по частям | стандартные Web Vitals |
| Допуск | limit плюс tolerance у каждого имени | сделать правило явным | универсальную норму для любого продукта |
| Aggregate | вторичный total guard | увидеть суммарный запас | право скрыть failed component |
Разделите запас до того, как появится candidate
Слово «бюджет» часто превращают в одну сумму. Для пользовательского пути это недостаточно. Один общий предел замечает только итог, а не перенос затрат между частями. В fixture у scriptTicks limit равен 28 и tolerance равен 2; значит допустимо до 30. У total limit 95 и tolerance 3. Эти числа условны. Их смысл не в удачном размере, а в том, что limit и допуск существуют отдельно и печатаются в результате проверки.
Почему допуск не стоит прятать в комментарии? Потому что без него нельзя понять, какой сдвиг команда заранее согласилась игнорировать и в каких условиях. Если tolerance нужен из-за нестабильности настоящего замера, эта нестабильность должна быть описана в measurement contract. В fixture она не измеряется: поэтому допуск — проектное правило учебного объекта, не статистическая модель и не утверждение о дисперсии браузера.
Snapshot и candidate должны быть сопоставимы
Snapshot — не «последний хороший отчёт». Это сохранённый объект с route, scenario, conditions и четырьмя учебными полями. Baseline в fixture содержит 24, 15, 26, 20; candidate меняет только scriptTicks на 34. Сумма candidate равна 93, поэтому aggregate остаётся PASS до allowed 98. Но component rule разрешает максимум 30, и общий результат обязан быть FAIL. Именно это защищает от красивой, но бесполезной зелёной суммы.
import { runPerformanceBudgetFixture } from "./upgrade-2022-02.mjs";
const fixture = runPerformanceBudgetFixture();
const baseline = fixture.baselineComparison;
const candidate = fixture.candidateComparison;
console.log(baseline.overall.status);
// pass
console.log(candidate.aggregate.status);
// pass — aggregate не заметил сдвиг одной части
console.log(candidate.overall.status);
// fail — named component budget имеет приоритет
if (candidate.componentChecks.scriptTicks.status !== "fail") {
throw new Error("fixture must expose the named regression");
}
// In-memory training model: no browser, CI, trace, network or Web Vitals.
Код не вызывает Lighthouse, Chrome, PerformanceObserver или сетевой запрос. Он не получает timestamps. Его роль — зафиксировать invariant модели: named component сильнее aggregate. В рабочем проекте аналогичный object можно заполнить данными выбранного теста, но только после того, как команда назвала источник, версию инструмента, контролируемые условия и смысл каждого поля. Подмена этих границ «примерным CI» создаёт ту же ложную уверенность, от которой пытаемся уйти.
const firstFailure = Object.values(candidate.componentChecks)
.find((check) => check.status === "fail");
if (candidate.aggregate.status === "pass" && firstFailure) {
console.log(firstFailure.component);
// scriptTicks: aggregate must not erase this result
}
Маршрут: симптом → причина → проверка → действие
- Симптом. Общая проверка стабильно зелёная, но конкретный пользовательский путь постепенно меняется и становится труднее объяснить.
- Причина. Aggregate объединяет части пути и не показывает, какая из них съела запас; baseline и candidate могут быть собраны при разных условиях.
- Проверка контракта. Назовите route, сценарий, version workload, единицу, наличие или отсутствие browser/CI observation. Если conditions отличаются, не сравнивайте числа.
- Проверка snapshot. Сохраните baseline с именованными полями. У каждого поля должны быть limit, tolerance и owner, а не только одна сумма в документации.
- Проверка candidate. Сначала прочитайте
componentChecks, затем aggregate. В fixturescriptTicks= 34 больше allowed 30, даже когда total 93 меньше aggregate allowed 98. - Действие. Откройте один input и одну границу, принадлежащую failed component. Для
scriptTicksfixture возвращаетinspect-script-input-and-one-route-boundary; не начинайте глобальную оптимизацию без дополнительного факта.
Как превратить модель в проверку без ложного PASS
В проекте порядок важнее выбора файла. Сначала profile route в контролируемых условиях: один вариант сценария, одна версия приложения, явно записанная конфигурация измерения. Затем сохранить snapshot вместе с conditions. Только после этого compare candidate. И уже после compare назначить focused action. Если начать с порога в CI, получится gate без смысла: он может зелёнеть, потому что route заменили, cache стал другим или в результаты попала другая часть опыта.
Что происходит при несовпадении условий
Fixture создаёт также candidate с изменённым условием cache. У него не появляется новый PASS или FAIL по компонентам: comparison останавливается как measurement-contract-invalid. Это сознательное поведение. Сравнение разных условий не становится корректным от того, что оба объекта содержат числа. Сначала восстановите один scenario и controlled conditions, затем снова снимите candidate. Так в отчёте остаётся отдельная категория «данных для вывода нет», а не случайный технический verdict.
Эта ветка особенно полезна для CI. У pipeline может быть хороший сигнал о том, что script завершился успешно, но этот факт не тождественен сопоставимому performance observation. Не нужно обесценивать CI: ему следует отдать узкое правило, которое он действительно проверяет. Например, сравнение serialized snapshot с candidate по одному маршруту. Но browser trace, real-user data и service-level решение остаются другими артефактами и требуют собственных условий.
Факт платформы и учебная модель
Исторический W3C Navigation Timing Level 2 от 17 января 2022 года описывает PerformanceNavigationTiming и timing information navigation. User Timing Level 3 от сентября 2021 года описывает именованные marks и measures. Performance Timeline Level 2 от августа 2021 года описывает хранение и получение performance entries. Это официальные смыслы платформенных интерфейсов, а не источник наших documentTicks и scriptTicks.
Модель пакета намеренно уже: она учит не считать скорость, а правильно оформлять сравнение. Ее output можно читать как «в этих условных входах named rule нарушен». Из него нельзя получить LCP, TTFB, браузерный waterfall, real-user result, количество пользователей или SLA. Для фактической интеграции сначала сопоставьте реальные поля с официальной документацией используемого инструмента и добавьте отдельный тест на mapping.
Критерий готовности и ограничение
Работа закончена не при первом зелёном total. Готовность для одного budget change выглядит так: route и scenario названы; conditions сохранены; baseline и candidate имеют один schema; каждый component имеет limit/tolerance; failed component даёт bounded diagnosis; следующий шаг ограничен одной границей. Затем выбранное изменение проверяется повторно в тех же условиях. Если conditions уже другие, это новая карточка сравнения, а не повод переписать baseline.
Fixture проверяет пятнадцать собственных assertions, включая baseline PASS, named failure candidate, явные tolerances, невозможность aggregate скрыть component, остановку при condition mismatch и отсутствие внешнего наблюдения. Это регрессия контракта текста и кода. Она не заменяет профиль настоящего route, потому что настоящий профиль остаётся отдельной инженерной работой с отдельной средой и доказательством.
Историческая граница февраля 2022
Статья ограничена документами, опубликованными не позднее января 2022 года. W3C sources ниже — рабочие drafts, а не современная рекомендация и не набор новых показателей. Здесь нет поздних метрик взаимодействия, нет текущих советов Lighthouse и нет заявлений об измеренном продукте. Это сохраняет честную дистанцию между исторической рамкой, официальным API и нашей учебной схемой бюджета.
Проверяемые источники
- W3C Navigation Timing Level 2, Working Draft от 17 января 2022 года — исторический снимок W3C, существовавший до февральских дат публикации. Он описывает интерфейс PerformanceNavigationTiming и временную шкалу навигации; учебные поля fixture не являются его полями.
- W3C User Timing Level 3, Working Draft от 13 сентября 2021 года — исторический W3C draft о mark и measure. Он позволяет говорить о названных точках и измерениях, но не задаёт наш допуск и не превращает ticks fixture в данные браузера.
- W3C Performance Timeline Level 2, Working Draft от 19 августа 2021 года — исторический W3C draft о хранении и получении performance entries. Он не определяет правило CI PASS, бюджет маршрута или интерпретацию учебных чисел.