Web Vitals — это набор метрик, которые помогают измерять качество пользовательского опыта на веб-странице. Ключевыми показателями являются Largest Contentful Paint (LCP), First Input Delay (FID) и Cumulative Layout Shift (CLS). В JavaScript доступ к этим метрикам реализован через API PerformanceObserver, который позволяет отслеживать различные типы событий производительности в реальном времени.
Объект PerformanceObserver создается с помощью
конструктора и принимает callback-функцию, которая вызывается при
появлении новых записей производительности:
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
console.log(entry.name, entry.startTime, entry.duration);
});
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
Параметр buffered: true позволяет получать все метрики,
которые уже произошли до подключения наблюдателя, что особенно важно при
измерении LCP.
Для юнит-тестирования компонентов, которые используют
PerformanceObserver, необходимо мокировать его
поведение, чтобы избежать зависимости от реального браузерного
окружения и асинхронных событий. В Jest и Vitest это делается через
замещение глобального объекта PerformanceObserver.
Простейший пример мокирования выглядит так:
class MockPerformanceObserver {
constructor(callback) {
this.callback = callback;
this.entries = [];
}
observe({ type, buffered }) {
if (buffered) {
this.callback({
getEntries: () => this.entries,
});
}
}
disconnect() {}
trigger(entry) {
this.entries.push(entry);
this.callback({
getEntries: () => [entry],
});
}
}
global.PerformanceObserver = MockPerformanceObserver;
Метод trigger позволяет вручную отправлять записи в
тестируемый код, эмулируя реальные события.
Для проверки правильной обработки LCP в компоненте можно написать тест:
test('обрабатывает LCP', () => {
const lcpEntry = { name: 'largest-contentful-paint', startTime: 1234, duration: 0 };
const observer = new PerformanceObserver((list) => {
const entry = list.getEntries()[0];
expect(entry.name).toBe('largest-contentful-paint');
expect(entry.startTime).toBe(1234);
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
observer.trigger(lcpEntry);
});
В этом примере мы создаем тестовую запись LCP и вручную вызываем
callback через метод trigger, проверяя корректность
обработки данных.
FID измеряет задержку между взаимодействием пользователя и обработкой события. CLS — смещение макета страницы. Для них логика мокирования аналогична LCP:
const fidEntry = { name: 'first-input', startTime: 2000, processingStart: 2002 };
const clsEntry = { name: 'layout-shift', startTime: 3000, value: 0.1 };
observer.trigger(fidEntry);
observer.trigger(clsEntry);
В тестах проверяются свойства processingStart у FID и
value у CLS. Это позволяет гарантировать, что компонент
правильно реагирует на метрики производительности.
Для более детальной проверки можно комбинировать мок PerformanceObserver с Jest Spy:
const callbackSpy = jest.fn();
const observer = new PerformanceObserver(callbackSpy);
observer.observe({ type: 'largest-contentful-paint', buffered: true });
observer.trigger({ name: 'largest-contentful-paint', startTime: 1000 });
expect(callbackSpy).toHaveBeenCalledTimes(1);
expect(callbackSpy.mock.calls[0][0].getEntries()[0].startTime).toBe(1000);
Это позволяет точно отследить количество вызовов callback и содержимое передаваемых записей.
Vitest полностью совместим с Jest-подобным синтаксисом мокирования,
включая vi.fn() для spy и vi.stubGlobal() для
глобальных объектов:
vi.stubGlobal('PerformanceObserver', MockPerformanceObserver);
const callback = vi.fn();
const observer = new PerformanceObserver(callback);
observer.observe({ type: 'largest-contentful-paint', buffered: true });
observer.trigger({ name: 'largest-contentful-paint', startTime: 500 });
expect(callback).toHaveBeenCalled();
Метод vi.stubGlobal позволяет заменить
PerformanceObserver на мок-версию только в тестовой среде,
не затрагивая глобальные объекты при реальном запуске кода.
afterEach(() => { vi.restoreAllMocks(); })).Такой подход обеспечивает детерминированные, стабильные и быстрые тесты, полностью независимые от браузерного окружения и сетевых условий.