Визуальное тестирование анимаций в JavaScript-библиотеках — одна из
наиболее сложных областей автоматизации качества интерфейса. В случае
Velocity.js сложность возрастает из-за высокой точности управления
временем, плавностью интерполяции и работы через
requestAnimationFrame, что делает поведение анимаций
чувствительным к окружению выполнения тестов.
Основная проблема визуального тестирования анимаций заключается в недетерминированности времени исполнения. Даже одинаковый код Velocity.js может давать разные результаты в зависимости от:
requestAnimationFrameVelocity.js опирается на высокоточную интерполяцию значений во времени. Любое отклонение временного шага приводит к визуальным расхождениям, которые в классических snapshot-тестах интерпретируются как ошибка.
Ключевой подход к тестированию анимаций Velocity.js — контроль времени выполнения. Вместо реального времени используется его искусственное моделирование.
В тестовых средах применяются следующие техники:
setTimeout,
setInterval)performance.now()requestAnimationFrameПример с использованием Jest и fake timers:
jest.useFakeTimers();
Velocity(element, { opacity: 1 }, { duration: 1000 });
jest.advanceTimersByTime(500);
// проверка промежуточного состояния
jest.advanceTimersByTime(500);
// проверка финального состояния
Такой подход позволяет устранить фактор реального времени и добиться повторяемости результатов.
requestAnimationFrameVelocity.js использует requestAnimationFrame как
основной механизм обновления анимаций. В тестовой среде его необходимо
стабилизировать.
Один из подходов — переопределение:
global.requestAnimationFrame = (cb) => {
return setTimeout(() => cb(Date.now()), 16);
};
Более строгая версия — синхронный вызов:
global.requestAnimationFrame = (cb) => cb(performance.now());
Второй вариант делает анимацию мгновенной, что удобно для логического тестирования, но не подходит для визуальных проверок пиксельного уровня.
Velocity.js позволяет контролировать прогресс анимации через время. Это даёт возможность тестировать не только конечный результат, но и промежуточные кадры.
Типичный сценарий тестирования:
Velocity(element, { left: "100px" }, { duration: 1000 });
jest.advanceTimersByTime(250);
expect(element.style.left).toBe("25px");
jest.advanceTimersByTime(250);
expect(element.style.left).toBe("50px");
При этом важно учитывать, что Velocity.js может использовать easing-функции, из-за чего линейные ожидания не всегда корректны.
Easing изменяет распределение значений во времени. Например,
ease-in замедляет начало анимации, а ease-out
— конец.
Это приводит к тому, что линейные проверки вида:
expect(value).toBe(progress * target);
становятся некорректными.
Вместо этого используется вычисление ожидаемого значения через ту же easing-функцию, что и в анимации:
import { easeInOutQuad } from "./easing";
const expected = easeInOutQuad(0.5) * 100;
Такой подход синхронизирует тестовую модель с реальной логикой Velocity.js.
Классический способ проверки визуального результата — сравнение скриншотов.
Velocity.js часто тестируется в связке с инструментами:
Схема теста:
Особенность анимаций — необходимость фиксации конкретного кадра:
await page.evaluate(() => {
Velocity(document.querySelector(".box"), { opacity: 0.5 }, { duration: 1000 });
});
await page.waitForTimeout(500);
await page.screenshot({ path: "frame-500ms.png" });
Важный момент — стабильность рендеринга шрифтов, субпиксельных значений и антиалиасинга, которые могут давать ложные различия.
Velocity.js активно работает с дробными значениями, что приводит к субпиксельным координатам:
left: 33.333pxopacity: 0.6667scale: 1.024В разных браузерах эти значения могут округляться по-разному, что делает пиксельные сравнения нестабильными.
Для уменьшения шума применяются подходы:
const normalize = (value) => Math.round(parseFloat(value));
expect(normalize(element.style.left)).toBe(50);
Часто визуальные тесты не требуют реальной анимации. Velocity.js позволяет переключить поведение на мгновенное выполнение.
Подходы:
duration: 0Velocity.mock = (element, props) => {
Object.assign(element.style, props);
};
В CI это значительно снижает флейки и ускоряет выполнение тестов.
Velocity.js поддерживает последовательные анимации:
Velocity(element, { opacity: 0 })
.then(() => Velocity(element, { translateX: 100 }));
При тестировании цепочек важно учитывать:
Проверка может выглядеть так:
await Velocity(element, { opacity: 0 });
expect(element.style.opacity).toBe("0");
await Velocity(element, { translateX: "100px" });
expect(element.style.transform).toContain("100px");
В CI-окружениях визуальные тесты Velocity.js часто становятся нестабильными из-за:
Для стабилизации применяются стратегии:
Более точный подход, чем DOM-снапшоты, — рендеринг элемента в canvas и сравнение пикселей.
Алгоритм:
const diff = (img1, img2) => {
for (let i = 0; i < img1.data.length; i++) {
if (img1.data[i] !== img2.data[i]) return false;
}
return true;
};
Этот метод позволяет выявлять даже минимальные расхождения в анимации Velocity.js.
Визуальное тестирование включает не только корректность, но и производительность.
Velocity.js может быть протестирован по:
Пример измерения:
let frames = 0;
function measure() {
frames++;
requestAnimationFrame(measure);
}
measure();
setTimeout(() => {
console.log(frames);
}, 1000);
Падение FPS при анимациях указывает на перегрузку DOM-операциями.
Наиболее стабильный подход — полная виртуализация времени:
Это позволяет:
Velocity.js в таких условиях становится полностью детерминированным, что критично для масштабных UI-наборов и дизайн-систем.