07 · Інструменти автоматизації
Візуальне тестування: скріншот-порівняння
Зміст
Функціональний носить очікування в собі: toHaveText('Оплачено') можна прочитати очима й зрозуміти, що саме перевіряється. Візуальний асерт натомість віддає очікування у PNG-файл, який ніхто не читає — і саме тому він одночасно найдешевший у написанні й найдорожчий у супроводі. Один рядок коду ловить те, чого не побачить звичайний функціональний асерт: зʼїхала верстка на 8 пікселів, кнопка стала того самого кольору, що фон, іконка зникла, хоч у DOM вона є. Той самий рядок коду через місяць червоніє на кожному прогоні, бо в CI інші шрифти.
Главу можна пропустити при першому проході — повернетеся, коли візуальні тести реально зʼявляться у вашому проєкті. Але це єдине повне місце на сайті, де розібрано механізм: інші глави лінкують сюди. Читати варто не заради формулювань, а заради розуміння, чому «просто підняти поріг» майже ніколи не працює.
Що таке візуальне тестування і чому це не «ще один асерт»
(visual testing) — порівняння знімка інтерфейсу з раніше затвердженим (baseline screenshot). Механіка зводиться до трьох кроків: зняти, порівняти, показати різницю на рівні пікселів.
Ключове слово в означенні — затвердженим. Технічно це асерт; організаційно — процес затвердження змін, і саме друга половина визначає, працює перевірка чи заважає. Причина в тому, що очікування живе не в коді, а у бінарному файлі: функціональний асерт ревʼюїться в за пʼять секунд, а змінений еталон у діфі виглядає просто як «змінився PNG».
Друга особливість: візуальний асерт не знає слова «правильно». Він знає лише «так само, як раніше». Кнопка може бути на місці, того самого кольору й розміру — і не працювати. Тому візуальні перевірки не замінюють функціональні, а закривають інший клас дефектів: непередбачені візуальні відхилення, які функціональний асерт мовчки пропускає.
Механіка порівняння: перший прогін, ретрай, pixelmatch
На першому прогоні еталона ще немає, тож він генерується; усі наступні прогони порівнюються з ним. Механіка генерації описана в доці прямо й вона неочевидна: робиться серія знімків, доки два послідовні не збіжаться, і останній зберігається у файл.
Той самий механізм працює і в самому асерті: toHaveScreenshot чекає, доки два послідовні знімки сторінки дадуть однаковий результат, і лише тоді порівнює останній з еталоном. Це вбудований захист від зйомки посеред довантаження чи анімації. Візуальний асерт — звичайний ретрайний асерт із власним (за замовчуванням — таймаут асертів із конфігу); його можна скасувати сигналом, і скасування виглядає як таймаут. розібрана в главі «Playwright: перевірки і автоочікування».
import { test, expect } from '@playwright/test';
test('головна сторінка не змінилася візуально', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
await expect(page).toHaveScreenshot('home.png');
});
Порівняння виконує бібліотека pixelmatch, а її опції задаються двома шляхами: у самому асерті або в конфізі — глобально чи на окремий проєкт. Формат зберігання за замовчуванням — PNG; WebP вмикається розширенням у назві еталона й теж без втрат.
Три різні пороги, які всі називають одним словом
«Ми поставили поріг 0.2» — фраза, яка нічого не означає, поки не сказано, поріг чого. Опцій три, і вони про різні речі.
| Опція | Що обмежує | Діапазон і дефолт |
|---|---|---|
threshold | сприйняту різницю кольору одного пікселя в просторі YIQ | від 0 (строго) до 1 (вільно), за замовчуванням 0.2 |
maxDiffPixelRatio | частку різних пікселів від їхньої загальної кількості | від 0 до 1, за замовчуванням не задана |
maxDiffPixels | абсолютну кількість різних пікселів | число, за замовчуванням не задане |
Чому це важливо на практиці. threshold лікує лише «той самий піксель став трохи іншого відтінку» — типово це згладжування шрифту або субпіксельний рендеринг. Якщо блок зʼїхав на два пікселі, різними стають пікселі по всій його площі — для великого блоку з текстом це легко десятки тисяч, — і багато з них різні сильно: там, де була темна літера, тепер світлий фон. Тут threshold не той важіль: підняти його до значень, які сильну різницю кольору, означає майже перестати перевіряти колір узагалі. Потрібна інша опція: частка або абсолютна кількість.
Дзеркальна пастка теж є. Дефолт «частка не задана» означає, що з коробки різниця не допускається зовсім: одного помітно іншого пікселя достатньо, щоб асерт упав. Тому візуальні тести на живому продукті починаються з підбору цих двох чисел — свідомого, з розумінням, який клас відхилень ви щойно дозволили пропускати.
Динамічні зони: маска, стайлшит, анімації, час
Будь-яка сторінка має ділянки, які змінюються самі: годинник, «оновлено 3 хвилини тому», аватарки з CDN, карусель, реклама. Їх треба або прибрати зі знімка, або зробити детермінованими.
Маска. Найпоширеніший інструмент — і найчастіше не так зрозумілий. Замаскований елемент перекривається суцільним прямокутником (типово рожевим #FF00FF, колір налаштовується), який повністю накриває його bounding box. Це заливка у самому знімку, а не виріз із порівняння. Наслідок практичний: маска ховає вміст елемента, але не ховає розмір його рамки — якщо блок зі змінним текстом змінить висоту, залита область теж змінить висоту, і діф зʼявиться. Маска накладається й на невидимі елементи.
Стайлшит на час знімка. Штатний спосіб ховати динаміку — власний CSS, який діє лише під час зйомки: сховати елемент, зробити невидимим, змінити властивість. Дві його важливі властивості: він проходить крізь Shadow DOM і діє на вкладені фрейми, тобто дістає туди, куди звичайний CSS сторінки не дістає.
await expect(page).toHaveScreenshot('dashboard.png', {
mask: [page.getByTestId('last-updated'), page.getByRole('img', { name: 'Аватар' })],
stylePath: './tests/screenshot.css',
maxDiffPixelRatio: 0.01,
});
Анімації вимкнені за замовчуванням — це один із найкорисніших дефолтів, про який мало хто знає. Опція глушить CSS-анімації, CSS-переходи й Web Animations, причому по-різному залежно від тривалості: скінченні перемотуються до кінця (тому подія transitionend таки спрацює), нескінченні скасовуються до початкового стану й програються після знімка. З цього переліку є важливий висновок: рух, зроблений іншим способом — наприклад, таймером, що щокадру править стилі, — у названий перелік не входить, тож і не глушиться.
Текстова каретка теж ховається за замовчуванням — інакше блимання курсора в полі давало б різницю між двома знімками того самого стану.
Час. Дата на екрані — це не візуальна проблема, а проблема детермінізму: маскувати її можна, але чесніше зафіксувати годинник сторінки. перевизначає Date, таймери, requestAnimationFrame і performance, а ставити її треба перед будь-якими іншими викликами, повʼязаними з часом, — інакше поведінка невизначена. Детально — у главі «Боротьба з флаком засобами інструмента».
Чому еталони ламаються без жодної зміни коду
Це головне джерело розчарування у візуальних тестах, і воно задокументоване, а не містичне. Рендеринг браузера залежить від ОС хоста, її версії, налаштувань, заліза, джерела живлення (батарея проти адаптера), headless-режиму та інших факторів. Припис доки з цього прямий: ганяти тести в тому самому середовищі, де згенеровано еталон.
Окремо названо різницю між браузерами й платформами: знімки різняться через інший рендеринг, інші шрифти та інше — тому під кожну комбінацію потрібні окремі еталони. Саме тому в імені файлу еталона стоїть суфікс на кшталт chromium-darwin, а якщо в конфізі є проєкти, замість імені браузера туди йде імʼя проєкту. Набір еталонів множиться на матрицю конфігурацій. Кросбраузерність і адаптивність як явище розібрані в главі «Кросбраузерність і адаптивна верстка», а матриця проєктів — у главі «Playwright: фікстури, хуки і конфігурація».
Далі накладається CI. Кожен прогін іде на свіжій, щойно розгорнутій віртуальній машині; браузери за замовчуванням запускаються в headless-режимі, а на Linux-агентах для headed потрібен встановлений Xvfb. Офіційний Docker- містить браузери й системні залежності браузерів — а шрифти якраз серед названих факторів різниці.
Звідси класичний сценарій: еталон знято на macOS, тест ганяється в Linux- — і не проходить. Із дефолтним іменуванням падіння приходить навіть не діфом, а відсутнім еталоном: у файлі стоїть chromium-darwin, а прогін шукає файл своєї платформи. Справжній піксельний діф зʼявляється там, де платформа в імені збігається, а середовища — ні: свій Ubuntu проти образу CI з іншим набором шрифтів. Жоден із двох випадків не — це інше середовище рендерингу, і перезапуск його не змінить.
| Фактор | Що з ним робити |
|---|---|
| ОС, версія, залізо, джерело живлення | генерувати еталони в тому самому середовищі, де прогін |
| браузер і платформа (шрифти, рендеринг) | окремий набір еталонів під кожну комбінацію |
| проєкти в конфізі | імʼя проєкту в назві файлу; шаблон шляху налаштовується окремо |
| headless проти headed | не змішувати: це різні умови рендерингу |
Практичний висновок один: еталони генерують у тому самому образі, у якому їх потім порівнюють. Локально це означає прогін оновлення знімків у контейнері, а не на своєму ноутбуці. Контейнеризація тестового середовища й CI-специфічні причини падінь — тема розділу про Git і CI/CD.
Оновлення еталонів: процес, а не прапорець
Технічно оновлення еталона — окремий прогону (--update-snapshots). Написати його — секунда, і саме тут візуальні тести найчастіше вмирають.
Схема смерті проста. Тест червоніє, причина незрозуміла, дедлайн близький — прогнали з прапорцем, еталони перезаписалися, зелений, різницю ніхто не подивився. Через кілька ітерацій еталон описує не той інтерфейс, який ми спроєктували, а той, який випадково вийшов, — включно з , що приїхала кілька тижнів тому.
Тому робоча схема виглядає так, і в ній важливі всі три умови:
- Діф дивиться людина. Не «тест впав», а «ось що змінилося на екрані» — і рішення «так, ми цього хотіли» або «ні, це баг».
- Оновлення еталона їде в тому самому пул-реквесті, що й зміна, яка його спричинила. Окремий «update snapshots» без коду поруч — це вже втрачений контекст.
- Прапорець не живе в CI-скрипті. Автоматичне оновлення еталонів на кожному прогоні перетворює перевірку на запис факту.
Зовнішні сервіси: що вони додають до порівняння
Файли-еталони в репозиторії — не єдина модель. Є клас зовнішніх сервісів візуального тестування, і механіку тут розберемо на Percy, чия довідка описує саме процес ревʼю еталонів.
Сервіс робить чотири речі: знімає сторінки в наборі браузерів і на кількох адаптивних ширинах для десктопа й мобільних; порівнює нові знімки з раніше схваленими; показує різницю на рівні пікселів; веде схвалення через UI. Знімки при цьому збираються під час звичайного прогону тестів — окремого «візуального прогону» тут немає.
Головна відмінність від файлів у репо — де живе еталон. Схвалені знімки переносяться в наступні гілки, а базовий білд для порівняння сервіс обирає сам. Схвалювати можна окремий знімок, групу або весь білд, і — якщо репозиторій підключено — результат стає статусом пул-реквесту або коміту. Дві деталі, які варто знати до впровадження:
- для головної гілки дефолт — автосхвалення всіх змін, тобто без налаштування гілка-джерело істини приймає зміни сама;
- достатньо одного знімка з позначкою «запит змін», щоб статус змінився в усього білду; позначка тримається, доки діф збігається з початковим, а щойно діф змінився — стан скидається в «не переглянуто».
| Аспект | Еталони у репозиторії | Зовнішній сервіс |
|---|---|---|
| Де живе еталон | PNG-файли в гілці | схвалений знімок у системі, переноситься між білдами |
| Оновлення | прапорець прогону | схвалення в UI |
| Ревʼю різниці | діф бінарників у пул-реквесті | окремий екран порівняння |
| Матриця браузерів і ширин | ваша, з окремим набором файлів на комбінацію | на боці сервісу |
| Гейт | падіння тесту | статус пул-реквесту |
Що покривати візуально
Питання «що покривати» тут вирішується не цінністю перевірки, а ціною супроводу: кожна нова комбінація браузер × платформа × проєкт — це ще один набір еталонів, який хтось мусить ревʼювати щоразу, коли інтерфейс змінюється. Тому візуальні перевірки — інструмент точковий, а не наскрізний.
З механізму випливають робочі критерії:
- Стабільні екрани й компоненти, а не флоу. Знімок наскрізного сценарію з десяти сторінок дасть десять еталонів, які ламаються разом; один знімок компонента дає локалізовану причину падіння.
- Те, чого не бачить функціональний асерт. Верстка, відступи, кольори, стани компонентів (наведення, помилка, порожній стан), теми оформлення — там, де перевіряти текстом нічого.
- Мінімум динаміки в кадрі. Кілька ділянок, які треба маскувати чи глушити стайлшитом, — сигнал звузити кадр до компонента або відмовитись від візуальної перевірки цього екрана.
- Одна комбінація для старту. Матрицю браузерів і ширин розширюють після того, як одна комбінація стабільно зелена, а не одразу.
І межа, яку варто проговорити вголос: візуальна перевірка не є перевіркою доступності, продуктивності чи логіки. Вона відповідає рівно на одне питання — «чи виглядає це так, як ми затвердили».
Типові помилки
- «Підняли
thresholdдо0.5, а діф усе одно є» →threshold— про сприйняту різницю кольору одного пікселя, а не про кількість різних пікселів. Блок, що зʼїхав на два пікселі, дає стільки сильно різних пікселів, скільки в ньому вмісту; тут потрібні частка або абсолютне число. - «Замаскував таймер — зона більше не порівнюється» → маска це заливка рамки суцільним кольором у самому знімку, а не виріз із порівняння. Вміст сховано, розмір рамки — ні: змінилася висота блоку, змінилася й залита область.
- «Знімок зняв кадр посеред анімації» → асерт чекає збігу двох послідовних знімків, а CSS-анімації, CSS-переходи й Web Animations вимкнені за замовчуванням. Якщо кадр усе одно проміжний, шукайте рух, зроблений не цими трьома способами: у названий перелік він не входить.
- «Курсор блимає в полі — треба маскувати» → каретка ховається за замовчуванням; якщо ви бачите її в діфі, поведінку хтось змінив явно.
- «Еталон із мого макбука не пройшов у CI — це флак, перезапустимо» → залежність рендерингу від ОС і шрифтів задокументована. Це не випадковість, а інше середовище, і перезапуск джоби на тому самому образі агента дасть той самий результат. Перше, що варто перевірити, — чи еталон під платформу CI взагалі існує: у назві файлу стоїть платформа, тож знімок із macOS у Linux навіть не буде з чим порівнювати.
- «Оновив еталони, стало зелено — інцидент закритий» → якщо різницю ніхто не подивився, ви щойно затвердили як норму те, що приїхало. Через кілька ітерацій еталон описує випадковий стан, включно з реальною регресією.
- «Візуальний тест перевірить, що сторінка працює» → він порівнює пікселі з попередньою затвердженою картинкою. Кнопка може бути ідеальною на вигляд і не надсилати запит.
- «Підключили сервіс — тепер зміни не проскочать» → для головної гілки дефолт — автосхвалення. Без налаштування вона приймає зміни сама.
Підсумок
- Візуальне тестування — це асерт плюс процес затвердження. Очікування живе у файлі, а не в коді, тому без ревʼю діфа перевірка перетворюється на журнал того, що вже сталося.
- Порогів три, і вони про різне.
threshold— колір одного пікселя в YIQ (дефолт0.2), частка різних пікселів і абсолютне їхнє число — окремі опції, за замовчуванням не задані. - Еталон привʼязаний до середовища. Браузер, платформа, шрифти, headless-режим, проєкт у конфізі — кожна комбінація має власний набір файлів, і генерувати їх треба там, де вони потім порівнюються.
- Динаміку прибирають детермінізмом, а не порогом. Анімації й каретка вимкнені за замовчуванням, стайлшит ховає нестабільні елементи й проходить крізь Shadow DOM, час фіксують підміною годинника. Маска — заливка рамки, не виріз із порівняння.
- Покривати варто точково. Стабільні екрани й компоненти з мінімумом динаміки, одна комбінація на старт; ціна теми — не написання асерту, а ревʼю еталонів на кожній зміні інтерфейсу.
Можливі питання
- «Що таке візуальне тестування і коли воно виправдане?» — інтервʼюер слухає, чи ви назвете обидві половини: порівняння з раніше затвердженим знімком і процес затвердження змін. Відповідь «це коли ми порівнюємо скріншоти» вважається половиною.
- «Що означає
threshold: 0.2?» — перевірка на те, чи людина читала доку. Сильна відповідь: це допустима сприйнята різниця кольору одного пікселя в YIQ, а не частка різних пікселів — для неї є окремі опції, і вони за замовчуванням не задані. - «Скріншотний тест зелений локально й червоний у CI. Що робиш?» — оцінюють порядок розбору, а не здогад. Очікують: спершу перевірити, чи еталон знімався в тому самому середовищі (ОС, образ, шрифти, headless-режим), і лише потім говорити про пороги. «Перезапущу джобу» тут читається як нерозуміння механізму.
- «Як покрити сторінку з годинником і каруселлю?» — хочуть почути набір інструментів, а не один: маска на динамічні зони, стайлшит на час знімка, вимкнені анімації, підміна часу для дати. Бонус — уточнення, що маска не виріз, а заливка.
- «Як у вашій команді оновлюються еталони?» — найінформативніше питання теми, бо відповідь показує реальний процес. «Ганяємо з прапорцем оновлення, коли впало» — червоний прапорець; «діф ревʼюїть людина, оновлення їде в тому самому пул-реквесті» — те, що хочуть почути.
- «Чим зовнішній сервіс кращий за PNG у репозиторії?» — чекають конкретики: еталон живе в процесі схвалення й переноситься між білдами гілки, схвалення стає статусом пул-реквесту, матриця браузерів і ширин — на боці сервісу. Плюс чесна ціна: залежність від зовнішньої системи й дефолтне автосхвалення головної гілки.
Джерела
Що таке візуальне тестування і чому це не «ще один асерт»
- Playwright — Visual comparisons (test snapshots) — на першому прогоні генеруються референсні знімки, далі кожен прогін порівнюється з ними.
- Percy (BrowserStack Docs) — Visual Testing with Percy — еталон це раніше схвалений знімок; сервіс підсвічує непередбачені візуальні відмінності на рівні пікселів і вимагає явного схвалення людиною.
Механіка порівняння: перший прогін, ретрай, pixelmatch
- Playwright — Visual comparisons (test snapshots) — генерація еталона на першому прогоні, серія знімків до збігу двох послідовних, порівняння через
pixelmatch, опції в тесті й у конфізі (глобально або на проєкт), PNG за замовчуванням і WebP через розширення. - Playwright — API: PageAssertions —
toHaveScreenshotчекає збігу двох послідовних знімків, має власний таймаут і скасовується сигналом (скасування виглядає як таймаут).
Три різні пороги, які всі називають одним словом
- Playwright — API: PageAssertions —
thresholdяк сприйнята різниця кольору одного пікселя в YIQ (0…1, дефолт0.2), частка різних пікселів як окрема опція, не задана за замовчуванням, і абсолютне число різних пікселів поруч. - Playwright — Visual comparisons (test snapshots) — опції
pixelmatchзадаються в тесті або в конфізі, глобально чи на проєкт.
Динамічні зони: маска, стайлшит, анімації, час
- Playwright — API: PageAssertions — маска як перекриття
bounding boxсуцільним кольором (#FF00FFза замовчуванням, налаштовний) і її дія на невидимі елементи; стайлшит на час знімка, що проходить крізь Shadow DOM і діє на вкладені фрейми; анімації вимкнені за замовчуванням із різним поводженням для скінченних і нескінченних; каретка ховається за замовчуванням. - Playwright — Visual comparisons (test snapshots) — власний стайлшит під час зйомки відфільтровує динамічні або нестабільні елементи й так підвищує детермінованість знімка.
- Playwright — Clock — підміняються
Date, таймери,requestAnimationFrameіperformance; установка годинника мусить стояти перед іншими викликами, повʼязаними з часом, інакше поведінка невизначена.
Чому еталони ламаються без жодної зміни коду
- Playwright — Visual comparisons (test snapshots) — залежність рендерингу від ОС, версії, налаштувань, заліза, джерела живлення й headless-режиму; припис ганяти тести в тому самому середовищі, де знято еталон; різниця між браузерами й платформами через рендеринг і шрифти; суфікс
chromium-darwin, імʼя проєкту в назві файлу й налаштовний шаблон шляху. - Playwright — Continuous Integration — браузери за замовчуванням у headless-режимі; headed на Linux-агентах вимагає Xvfb, який уже є в офіційному образі й GitHub Action.
- GitHub Docs — Understanding GitHub Actions — кожен прогін воркфлоу іде на свіжій, щойно розгорнутій віртуальній машині.
- Playwright — Docker — офіційний образ містить браузери й системні залежності браузерів (сам пакет Playwright у нього не входить і встановлюється окремо).
Оновлення еталонів: процес, а не прапорець
- Playwright — Visual comparisons (test snapshots) — оновлення еталона це окремий прапорець прогону
--update-snapshots. - Percy (BrowserStack Docs) — Visual Testing with Percy — зміни проходять через ревʼю й явне схвалення людиною в UI, «щоб деплоїлися лише навмисні оновлення»; ревʼю відбувається до .
Зовнішні сервіси: що вони додають до порівняння
- Percy (BrowserStack Docs) — Visual Testing with Percy — чотири кроки сервісу (знімки в наборі браузерів і на кількох адаптивних ширинах, порівняння з раніше схваленими, підсвітка різниці на рівні пікселів, схвалення через UI); перенесення схвалених знімків у наступні білди гілки й вибір базового білду сервісом; схвалення знімка, групи або білду як статус пул-реквесту; автосхвалення для головної гілки; один знімок із запитом змін міняє статус усього білду, а позначка тримається, доки діф збігається; знімки збираються під час звичайного прогону тестів.
- Playwright — Visual comparisons (test snapshots) — модель «еталон як файл у репозиторії»: окремі набори під кожну комбінацію браузера й платформи, оновлення прапорцем прогону.
- Playwright — Visual comparisons (test snapshots) — окремі набори еталонів під кожну комбінацію браузера, платформи й проєкту; стайлшит як спосіб відфільтрувати динамічні елементи.
- Percy (BrowserStack Docs) — Visual Testing with Percy — знімки в наборі браузерів і на кількох адаптивних ширинах; перевірка ловить непередбачені візуальні відмінності, а схвалює їх людина.
- Playwright — API: PageAssertions —
thresholdяк різниця кольору одного пікселя проти окремих опцій частки й абсолютного числа; маска як перекриття рамки; перелік вимкнених анімацій (CSS-анімації, CSS-переходи, Web Animations) і поводження зі скінченними й нескінченними; каретка ховається за замовчуванням; збіг двох послідовних знімків перед порівнянням. - Playwright — Visual comparisons (test snapshots) — залежність від ОС і шрифтів як причина різниці між середовищами; оновлення еталона прапорцем прогону.
- Percy (BrowserStack Docs) — Visual Testing with Percy — автосхвалення для головної гілки за замовчуванням; схвалення людиною як умова того, що деплоїться лише навмисне.
- Playwright — Visual comparisons (test snapshots) — механіка еталонів, залежність від середовища, окремі набори на комбінацію, оновлення прапорцем прогону.
- Playwright — API: PageAssertions — три пороги і їхні дефолти, маска, стайлшит, анімації й каретка за замовчуванням.
- Playwright — Clock — перелік перевизначених глобалів часу й вимога ставити підміну годинника перед іншими викликами, повʼязаними з часом.
- Percy (BrowserStack Docs) — Visual Testing with Percy — еталон як раніше схвалений знімок і схвалення людиною як частина процесу.
- Playwright — Visual comparisons (test snapshots) — генерація й оновлення еталонів, залежність від середовища, окремі набори під комбінації.
- Playwright — API: PageAssertions — означення
thresholdу YIQ і відмінність від опцій частки й абсолютного числа; маска, стайлшит, анімації, каретка. - Playwright — Clock — підміна часу як спосіб зробити дату на екрані детермінованою.
- Percy (BrowserStack Docs) — Visual Testing with Percy — еталон у процесі схвалення, перенесення між білдами гілки, статус пул-реквесту, автосхвалення головної гілки.
Що таке візуальне тестування і чим воно відрізняється від звичайного асерту?
Це перевірка, яка звіряє свіжий знімок інтерфейсу з картинкою, що її раніше хтось погодив як правильну. Технічно все зводиться до трьох дій: зняти екран, накласти знімок на збережений файл, підсвітити пікселі, які не збіглися. Відмінність від toHaveText чи toBeVisible не в синтаксисі, а в тому, де лежить очікування: у функціональному воно записане в коді й читається очима за пʼять секунд, а тут — у бінарному PNG, який у виглядає просто як «файл змінився». Через це половина теми — не код, а процедура: хто саме дивиться на різницю і хто каже «так, ми цього хотіли». На співбесіді відповідь «це коли ми порівнюємо скріншоти» зараховують за половину: без згадки про затвердження змін картина неповна.
Чому візуальна перевірка не замінює функціональні тести?
Тому що вона вміє відповідати лише на питання «чи так само, як минулого разу», і нічого не знає про те, чи інтерфейс узагалі робочий. Кнопка може стояти рівно там, де на еталоні, мати той самий колір і розмір — і не надсилати жодного запиту по кліку: пікселі збіглися, тест зелений, дефект у проді. Натомість вона ловить те, повз що функціональні асерти проходять мовчки: зсув верстки на кілька пікселів, зниклу іконку, текст, що злився з фоном, поїхані відступи в темній темі. У DOM усі ці елементи на місці, їх знаходять, тому звичайні перевірки лишаються зеленими. Тобто два види перевірок закривають різні класи дефектів і живуть паралельно, а не замість одне одного.
Звідки береться еталонний скріншот і що відбувається на наступних прогонах?
На першому прогоні порівнювати нема з чим, тому знімок просто зберігається у файл і стає еталоном; усі подальші прогони звіряються вже з ним. Механізм генерації неочевидний: інструмент робить не один кадр, а серію, доки два підряд не вийдуть однаковими, і в файл кладе останній. Сенс у тому, щоб не законсервувати як «правильний» кадр, знятий посеред довантаження або анімації. Практичний наслідок: сама поява еталона нічого не доводить — у файл поїхав поточний стан, яким би той не був. Якщо еталон згенеровано на вже зламаному екрані, тест буде вічно зелений на баг. Тому перший знімок так само має пройти ревʼю людиною, а не тихо приїхати в репозиторій разом із кодом тесту.
Чому скріншот-асерт не ловить кадр посеред довантаження сторінки?
У самому асерті працює рівно та сама логіка, що й під час генерації: сторінка знімається повторно, доки два кадри поспіль не збіжаться, і лише після цього останній порівнюється з еталоном. Тобто це звичайний ретрайний асерт із власним , який за замовчуванням береться з налаштування таймауту асертів у конфізі. Якщо сторінка ніяк не заспокоїться — лічильник, що тикає щосекунди, або спінер, домальований скриптом покадрово, — асерт не зніме «як є», а впаде по таймауту. Корисна для дебагу деталь: скасування асерту сигналом ззовні виглядає у звіті так само, як таймаут, тож ці дві причини легко переплутати. І навпаки, вбудоване очікування не звільняє від явного асерту на готовність контенту перед знімком: дочекатися видимого заголовка дешевше, ніж потім розбирати таймаут .
Що означає threshold: 0.2?
Це допустима сприйнята різниця кольору одного окремого пікселя, порахована у просторі YIQ; шкала — від нуля (жодних відхилень) до одиниці (пробачається майже будь-яка різниця), а 0.2 стоїть за замовчуванням. Ключові слова тут — «одного пікселя»: опція нічого не каже про те, скільки пікселів мають право відрізнятися. Її природна робота — згладжування шрифту й субпіксельний рендеринг, коли той самий піксель просто трохи іншого відтінку. Кількість різних пікселів обмежують зовсім інші опції: частка від загальної кількості й абсолютне число. Це питання ставлять не з педантизму, а щоб відрізнити тих, хто читав документацію, від тих, хто скопіював конфіг із сусіднього проєкту.
Підняли threshold, а діф нікуди не подівся. Чому?
Бо підняли не той важіль. Найчастіша причина діфа — не «колір поплив», а «блок поїхав»: якщо картка зсунулася на пару пікселів, різними стають усі пікселі по її площі, і різними сильно — там, де на еталоні була темна літера, тепер світлий фон. Для блоку з текстом це легко десятки тисяч пікселів, і жодна помірна межа різниці кольору їх не пробачить. Щоб пробачила, її довелося б підняти до значень, за яких перевірка кольору фактично вимикається, — а це вже не тюнінг, а від частини . Правильна реакція складається з двох кроків: спершу вирішити, чи зсув не є , і лише якщо ні — дозволити конкретну частку або конкретну кількість різних пікселів, тверезо розуміючи, який клас відхилень команда щойно погодилася не помічати.
Чому візуальний тест «з коробки» падає навіть від мінімальної різниці?
Тому що обидві опції, які задають допустиму кількість різних пікселів, за замовчуванням не задані взагалі — ні частка, ні абсолютне число. У перекладі на практику це означає нульовий : одного помітно іншого пікселя достатньо, щоб асерт почервонів. На синтетичній сторінці таке налаштування нормальне, а на живому продукті майже гарантує червоне з першого дня — там завжди знайдеться тінь, градієнт або шрифт, що відрендерився ледь інакше. Тому впровадження візуальних тестів починається не з написання асертів, а з підбору цих двох чисел на реальних прогонах. Підбирати треба свідомо: кожне число — це зафіксована домовленість про розмір відхилення, який команда згодна пропускати мовчки.
Що насправді робить mask і в чому з нею найчастіше помиляються?
Маска замальовує елемент суцільним прямокутником поверх його bounding box — за замовчуванням яскраво-рожевим #FF00FF, колір налаштовується. Помилка в тому, що її вважають вирізом: мовляв, замаскована зона більше не порівнюється. Насправді заливка потрапляє в сам кадр, а порівнюються зображення цілком — просто в цій області з обох боків очікується однорідний колір. Звідси наслідок: вміст сховано, а розмір рамки — ні. Якщо блок зі змінним текстом іноді переноситься на два рядки, залита область стане вищою, і діф зʼявиться попри маску. Ще одна деталь: маска накладається й на елементи, яких на екрані не видно.
Коли маски недостатньо і потрібен стайлшит на час знімка?
Коли треба не закрити пляму, а прибрати елемент із розкладки чи змінити його поведінку саме на момент зйомки. Для цього є окремий CSS-файл, який діє тільки під час знімка: ним можна сховати блок так, щоб він не займав місця, погасити тінь, зафіксувати властивість. Дві особливості роблять цей механізм сильнішим за маску: він дістає всередину Shadow DOM і застосовується до вкладених фреймів — тобто туди, куди стилі самої сторінки не дотягуються. Практично це рятує на компонентних бібліотеках із закритим Shadow DOM і на віджетах, вбудованих фреймом. Просте правило вибору: зона змінює лише вміст — беремо маску; зона змінює висоту й тягне за собою решту сторінки — беремо стайлшит.
Які анімації інструмент глушить сам, а які ні?
За замовчуванням асерт вимикає три речі: CSS-анімації, CSS-переходи і Web Animations. Поводиться з ними по-різному залежно від тривалості: скінченні перемотуються до кінцевого стану, тому подія завершення переходу все одно спрацює і підписаний на неї код відпрацює; нескінченні скасовуються до початкового стану, а після знімка програються далі. Виняток, з якого й роблять питання: рух, зроблений іншими засобами, у цей перелік не входить. Скрипт, який власноруч править стилі на кожному кадрі через таймер або requestAnimationFrame, ніхто не зупинить. Тому якщо кадр усе одно виходить проміжним, шукати треба не «баг у Playwright», а саме такий саморобний рух — і глушити його стайлшитом, або тестовим прапорцем у самому застосунку.
Курсор блимає в полі вводу. Треба його маскувати?
Ні — текстова каретка ховається за замовчуванням, і саме тому два знімки того самого стану форми не дають фантомної різниці. Логіка дефолту очевидна: блимання — це зміна кадру, не повʼязана з жодною зміною застосунку, тобто чистий генератор фейлів. Якщо каретка все ж потрапила у ваш діф, це не «так працює інструмент», а сигнал, що поведінку хтось змінив явно: або в опціях асерту, або в конфізі проєкту. Розбір починається саме звідти, а не з маски. Взагалі корисна звичка на цій темі — перед тим як щось маскувати, перевірити, чи воно не вимкнене й так: половина «саморобних» масок у проєктах закриває те, з чим інструмент упорався б сам.
На екрані годинник і рядок «оновлено 3 хвилини тому». Що з цим робити?
Маскувати це можна, але так лікують симптом: залишається залежність від довжини рядка й від того, чи не поїде висота блоку. Чесніший шлях — зробити час детермінованим: підмінити годинник сторінки, щоб дати й лічильники не залежали від моменту прогону. Підміна перевизначає Date, таймери, requestAnimationFrame та performance — тобто всі джерела, з яких застосунок дізнається, котра зараз година. Важлива умова: ставити її треба перед будь-якими іншими діями, повʼязаними з часом, інакше поведінка не гарантована; на практиці це означає «до переходу на сторінку». Комбінація «фіксований час плюс маска на аватарки з CDN» покриває більшість динаміки типового дашборда.
Навіщо в назві файлу еталона платформа і що буде, якщо її проігнорувати?
Еталон дійсний лише для тієї комбінації, у якій його знято: різні браузери й різні платформи малюють по-різному — інший , інший набір шрифтів. Тому в назву дописується суфікс на кшталт chromium-darwin, а якщо в конфізі описані проєкти, замість імені браузера туди підставляється імʼя проєкту (шаблон шляху при цьому теж налаштовується окремо). Наслідок для планування: набір еталонів множиться на матрицю «браузер × платформа × проєкт», і кожен її новий рядок хтось муситиме ревʼювати при кожній зміні дизайну. Якщо прогнати тест із macOS-еталоном у Linux-, впаде він не діфом, а відсутнім файлом — прогін шукатиме знімок своєї платформи й не знайде. Це, до речі, найшвидший спосіб відрізнити «еталона тут немає» від «еталон є, але не збігається».
Скріншотний тест зелений локально й червоний у CI. Який порядок розбору?
Спершу середовище, потім пороги — саме цей порядок і оцінюють на співбесіді. Крок перший: подивитися, як саме він червоний. Немає файла еталона під платформу агента — питання закрите, знімок робився на іншій ОС. Крок другий: якщо діф справжній, порівняти середовища — , версію браузера, набір шрифтів, headless проти headed, розмір вікна. Рендеринг залежить від ОС, її версії й налаштувань, заліза і навіть від того, працює ноутбук від батареї чи від мережі, тож «у мене ж локально зелено» доказом не є. Крок третій: якщо середовище справді однакове, а різниця лишається — це кандидат на реальну регресію, і далі дивиться людина. Пороги чіпають в останню чергу і лише коли зрозуміло, який клас відхилень вони почнуть пропускати.
Коли червоний візуальний тест — це не флак?
Коли причина в середовищі рендерингу, а не у випадковості. — це коли той самий код на тому самому агенті дає різний результат від прогону до прогону. Тут результат стабільний: інший набір шрифтів в образі означає стабільно інший знімок, і перезапуск дасть рівно те саме. Обидва класичні випадки — відсутній еталон під платформу CI і піксельна різниця між власним Ubuntu та образом агента — детерміновані до нудьги. Небезпека звички «перезапущу» саме в тому, що вона палить час і привчає команду не відкривати діф. Лікування системне: генерувати еталони в тому самому оточенні, у якому вони потім порівнюються, тобто оновлювати знімки в контейнері, а не на своєму ноутбуці.
Як має бути влаштоване оновлення еталонів?
Технічно це один прогону, і саме його дешевизна вбиває тему. Типова смерть виглядає так: тест червоніє, дедлайн горить, хтось прогнав з оновленням, файли перезаписалися, позеленів, різницю ніхто не відкривав — і за кілька ітерацій еталон описує не спроєктований інтерфейс, а той, що випадково вийшов, разом із регресією тритижневої давнини. Робоча схема тримається на трьох умовах одночасно. Перша: діф дивиться людина і приймає рішення «так, ми цього хотіли» або «ні, це дефект». Друга: оновлені знімки їдуть тим самим пул-реквестом, що й зміна, яка їх спричинила, бо окремий «update snapshots» уже позбавлений контексту. Третя: прапорця немає в CI-скрипті — автооновлення на кожному прогоні перетворює перевірку на протокол подій. На співбесіді це найінформативніше питання теми: відповідь показує реальну культуру команди, а не знання опцій.
Чим зовнішній сервіс візуального тестування відрізняється від PNG у репозиторії?
Головна відмінність — де живе еталон. У файловій моделі це PNG у гілці, який оновлюють прапорцем прогону; у сервісній — раніше схвалений знімок усередині системи, що переноситься в наступні гілки, причому базовий білд для порівняння сервіс обирає сам. Знімки він збирає під час звичайного прогону тестів, окремого «візуального прогону» немає, а матрицю браузерів і адаптивних ширин тримає на своєму боці. Ревʼю переїжджає з діфа бінарників у нормальний екран порівняння: схвалювати можна окремий знімок, групу або весь білд, і якщо репозиторій підключено, результат стає статусом пул-реквесту чи коміту. Тобто гейт зміщується з падіння тесту на статус PR. Ціна теж є: залежність від зовнішньої системи плюс кілька дефолтів, про які краще дізнатися до впровадження, а не після інциденту.
Які дефолти зовнішнього сервісу варто перевірити першими?
Найважливіший — автосхвалення для головної гілки: за замовчуванням саме та гілка, яку всі вважають джерелом істини, приймає візуальні зміни сама, без жодного людського «так». Фраза «ми підключили сервіс, тепер нічого не проскочить» після цього стає неправдою рівно з першого дня. Другий: позначка «потрібні зміни» на одному-єдиному знімку переводить у цей статус увесь білд — тобто один невідревʼюваний компонент блокує весь набір, і користуватися нею треба свідомо. Третій: така позначка тримається лише доки різниця та сама; щойно діф змінився, стан скидається в «не переглянуто» і ревʼю доводиться робити заново. Усі три перевіряються в налаштуваннях за десять хвилин — і саме ці десять хвилин відрізняють впроваджений процес від купленої ілюзії контролю.
Що ви візьмете під візуальні тести на новому проєкті, а що ні?
Вибір диктує не цінність перевірки, а ціна супроводу: кожна нова комбінація браузера, платформи й проєкту — це ще один набір файлів, який хтось ревʼюватиме при кожній зміні дизайну. Тому візуальні перевірки ставлять точково. Беруть стабільні екрани й окремі компоненти, а не наскрізні сценарії: знімок флоу з десяти кроків дає десять еталонів, які ламаються разом і не показують, де саме причина. Беруть те, що функціональним асертом не перевіряється: відступи, кольори, стани компонента (наведення, помилка, порожній стан), теми оформлення. Уникають кадрів, у яких доводиться маскувати три-чотири зони — це сигнал звузити кадр до компонента або взагалі відмовитися від візуальної перевірки цього екрана. І стартують з однієї комбінації, розширюючи матрицю тільки після того, як вона стабільно зелена. Межу теж варто проговорити вголос: візуальна перевірка нічого не каже про доступність, продуктивність чи логіку — вона каже лише, чи виглядає екран так, як його затвердили.
Три кейси з життя візуальних тестів: як довести до зеленого знімок дашборда, на якому все ворушиться; як за пʼять хвилин поставити діагноз червоному тесту, що локально зелений; і як ревʼювати , у якому оновлено дюжину еталонів. Скрізь — що дивитися і чому саме в такому порядку.
Кейс 1. Дашборд, на якому все ворушиться
Задача: закрити візуальним тестом головний екран аналітики. На ньому годинник у шапці, рядок «оновлено N хвилин тому», аватарки користувачів із CDN, промо-банер, карусель новин і плавна поява карток при завантаженні. Наївний варіант виглядає так — і червоніє на кожному другому прогоні:
import { test, expect } from '@playwright/test';
test('дашборд', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveScreenshot('dashboard.png'); // приречено
});
Робочий варіант складається з чотирьох різних інструментів, і кожен закриває свій тип динаміки:
import { test, expect } from '@playwright/test';
test('дашборд: візуальний стан після завантаження', async ({ page }) => {
// 1) годинник ставимо ДО переходу на сторінку
await page.clock.install({ time: new Date('2026-01-15T10:30:00Z') });
await page.goto('/dashboard');
// 2) явний якір готовності — дешевше за таймаут скріншота
await expect(page.getByTestId('revenue-card')).toBeVisible();
await expect(page).toHaveScreenshot('dashboard.png', {
// 3) те, що змінює лише вміст у фіксованій рамці
mask: [
page.getByTestId('user-avatar'),
page.getByRole('img', { name: 'Промо-банер' }),
],
// 4) те, що змінює розкладку або тягне за собою сусідів
stylePath: './tests/screenshot.css',
maxDiffPixelRatio: 0.005,
});
});
Файл стилів, який діє тільки під час зйомки:
/* tests/screenshot.css */
/* карусель прибираємо з розкладки цілком: вона змінює висоту блока */
[data-testid='promo-carousel'] {
display: none !important;
}
/* тінь під шапкою рендериться по-різному на різних платформах */
[data-testid='app-header'] {
box-shadow: none !important;
}
/* скролбар усередині таблиці — джерело різниці між ОС */
[data-testid='data-table'] {
scrollbar-width: none !important;
}
Що дивитися і чому:
- Годинник ставиться першим рядком, до
page.goto. Підміна перевизначаєDate, таймери,requestAnimationFrameіperformance, але вимагає бути раніше за будь-які інші дії з часом — інакше поведінка не гарантована. Після неї рядок «оновлено 3 хвилини тому» перестає бути випадковим числом і його вже не треба маскувати. - Маска пішла на аватарки й банер, стайлшит — на карусель. Це не смак, а правило: маска замальовує рамку суцільним кольором прямо в кадрі, тобто ховає вміст, але не розмір. Аватарка завжди 40 на 40 — маска ідеальна. Карусель міняє висоту разом із контентом — тут маска дасть діф, бо залита область теж стане іншою; її треба прибирати з розкладки.
- Про анімацію появи карток не сказано жодного слова — і це правильно. CSS-анімації, CSS-переходи й Web Animations вимикаються за замовчуванням: скінченні перемотуються до кінця, нескінченні скасовуються до початкового стану. Дописувати щось потрібно лише тоді, коли рух зроблено інакше — скриптом, що править стилі покадрово.
maxDiffPixelRatio: 0.005— не магічне число, а результат підбору. З коробки не задано ні частку, ні абсолютну кількість, тобто нульовий. Пів відсотка на екрані1280 × 720— це близько 4600 пікселів: вистачає на дрижання шрифту, але не сховає зникнення блока. Число підбирають на реальних прогонах і фіксують у код-ревʼю як домовленість, а не підкручують щоразу, коли червоніє.- Якір
toBeVisibleперед знімком лишається. - і сам чекає, доки два кадри поспіль збіжаться, але якщо контент не приїхав, він упаде по без жодної підказки. Явна перевірка перетворює «таймаут скріншота» на зрозуміле «картка виторгу не зʼявилася».
Кейс 2. «Локально зелено, у CI червоно» — таблиця діагнозів
Тест впав у . Перш ніж думати про пороги, треба подивитися, як саме він червоний: артефакт прогону відповідає на це за пів хвилини.
| Що бачите у звіті | Діагноз | Що робити |
|---|---|---|
| Немає файла еталона для платформи агента | Знімок робився на іншій ОС; порівнювати нема з чим | Перегенерувати еталони в образі CI і закомітити |
| Діф є, розмито по всіх текстах, картинка «та сама» | Інший набір шрифтів або згладжування в образі | Вирівняти середовище; порогом лікувати лише після цього |
| Діф є, суцільна пляма в одній зоні | Не заглушена динаміка: аватарка, банер, лічильник | Маска або стайлшит на цю зону |
| Діф є, локалізований зсув блока і всього під ним | Кандидат на регресію верстки | Не оновлювати еталон; віддати діф на ревʼю |
| Розмір знімка не збігається з еталоном | Інша роздільність вікна або масштаб | Зафіксувати viewport у конфізі проєкту |
| Червоніє через раз на тому самому агенті | Ось це і є флак | Шукати незаглушений рух, а не піднімати пороги |
Ключове розрізнення, на якому найлегше помилитися: перші два рядки таблиці — не . Рендеринг залежить від ОС, її версії й налаштувань, заліза, джерела живлення та headless-режиму, тож інший дає стабільно інший знімок. Перезапуск поверне рівно той самий результат — просто на десять хвилин пізніше.
Звідси й правило генерації: еталони роблять там само, де їх потім порівнюють.
# НЕ так: знімки з ноутбука поїдуть у репозиторій з суфіксом darwin
npx playwright test --update-snapshots
# так: той самий образ, що й у пайплайні (тег має відповідати версії пакета)
docker run --rm -v "$PWD":/work -w /work \
mcr.microsoft.com/playwright:vX.Y.Z-noble \
npx playwright test --update-snapshots
Що дивитися і чому:
- Спочатку тип падіння, потім усе інше. Відсутній еталон і піксельний діф — різні історії з різними причинами, і плутати їх дорого: у першому випадку жодна опція порівняння взагалі не застосовувалася.
- Суфікс у назві файла — безкоштовна діагностика. Побачили в репозиторії
dashboard-chromium-darwin.png, а пайплайн крутиться на Linux — розбір закінчено, не читаючи логів. - Пороги — останній крок, а не перший. Підняти частку різних пікселів так, щоб пройшов діф від чужих шрифтів, означає одночасно перестати помічати реальні зсуви подібного масштабу.
Кейс 3. У пул-реквесті оновлено 12 еталонів
Приходить PR: «оновив дизайн кнопок», у діфі — правка одного компонента і дванадцять перезаписаних PNG. Що з цим робити ревʼюеру.
Перше питання — арифметика. Змінили компонент кнопки, який використовується на всіх екранах: дванадцять знімків це правдоподібно. Змінили відступ у картці однієї сторінки, а оновлено дванадцять — щось не сходиться, і саме тут ховаються випадкові захоплення.
Друге питання — чи бачив автор різницю. Одна фраза в описі PR закриває тему: «змінилася лише висота кнопки на 2px, перевірив кожен знімок» проти мовчання. Мовчання означає, що прогнали з прапорцем оновлення й закомітили, що вийшло.
Третє питання — чи є в діфі щось, чого ніхто не замовляв. Найдешевший спосіб — прогнати очима не PNG, а їхній розмір: різка зміна ваги файлу на екрані, який мали не чіпати, майже завжди означає, що в кадр приїхало щось стороннє.
| Симптом у пул-реквесті | Про що це насправді |
|---|---|
| Оновлених еталонів більше, ніж змінених екранів | Зміна зачепила спільний компонент — або знімки перезаписали наосліп |
| Оновлення прийшло окремим комітом без коду | Контекст утрачено: через місяць ніхто не скаже, що саме затверджували |
| Оновлені знімки лише під одну платформу | Матриця розʼїхалася; наступний прогін на іншій ОС буде червоний |
| В описі немає жодного слова про різницю | Ревʼю не було — був спосіб зробити пайплайн зеленим |
Що дивитися і чому:
- оновлення — не частина прогону, а частина рішення. Технічно він коштує секунду, і саме тому візуальні тести найчастіше вмирають не від складності, а від зручності: кілька ітерацій «оновив, щоб позеленіло» — і еталон описує не спроєктований інтерфейс, а той, що випадково вийшов.
- Оновлення має їхати разом зі зміною, яка його спричинила. Тоді ревʼюер бачить причину й наслідок поруч, а через півроку
git logвідповідає на питання «чому кнопка стала такою» без археології. - У CI-скрипті прапорця бути не повинно. Автоматичне оновлення на кожному прогоні перетворює перевірку на журнал того, що вже сталося: тести назавжди зелені, а проїжджають мовчки.
- Якщо еталонів у проєкті десятки, ревʼю бінарників перестає працювати. Це той поріг, за яким має сенс зовнішній сервіс з окремим екраном порівняння — з поправкою на його дефолти: головна гілка там схвалює зміни сама, а одна позначка «потрібні зміни» кладе статус усього .
Суть і межі методу
- Можу пояснити двома половинами: звірка кадру з раніше затвердженим знімком і процедура затвердження змін — без другої половини відповідь неповна.
- Розумію, що така перевірка відповідає лише на питання «як раніше?», а не «чи працює», тож функціональні вона не замінює, а доповнює: ловить зсуви верстки, зниклі іконки, злиття кольору з фоном — там, де DOM і в порядку.
Механіка порівняння
- Знаю, що на першому прогоні еталон генерується сам, і розумію, чому це нічого не доводить: у файл міг поїхати вже зламаний екран.
- Можу пояснити правило двох однакових кадрів підряд перед порівнянням і навіщо воно потрібне; памʼятаю, що -асерт ретрайний, має власний , а скасування ззовні виглядає у звіті як таймаут.
Три пороги, які всі називають одним словом
- Не плутаю
threshold(сприйнята різниця кольору одного пікселя в YIQ, шкала0…1, дефолт0.2) з опціями, що обмежують кількість різних пікселів. - Знаю різницю між
maxDiffPixelRatio(частка від усіх пікселів) іmaxDiffPixels(абсолютне число) і те, що обидві за замовчуванням не задані взагалі. - Можу пояснити, чому підняття
thresholdне рятує від зсунутого блоку, і розумію, що дефолтний нульовий означає підбір порогів як обовʼязковий етап впровадження.
Динамічні зони й детермінізм
- Знаю, що маска замальовує рамку елемента суцільним кольором (типово
#FF00FF, налаштовується) прямо в кадрі, а не вимикає цю зону з порівняння — звідси наслідок: вміст сховано, розмір рамки ні, тож блок зі змінною висотою дасть діф навіть під маскою (і накладається вона й на невидимі елементи). - Можу пояснити, коли потрібен стайлшит на час знімка і чим він сильніший за маску: дістає в Shadow DOM і у вкладені фрейми, прибирає елемент із розкладки.
- Знаю, що за замовчуванням глушаться CSS-анімації, CSS-переходи й Web Animations (скінченні перемотуються до кінця, нескінченні скасовуються до початкового стану), а саморобний рух через таймери в цей перелік не входить.
- Памʼятаю, що текстова каретка ховається за замовчуванням, тож її поява в діфі означає явну зміну поведінки, а не «так працює інструмент».
- Можу пояснити, чому дату чесніше фіксувати підміною годинника, ніж маскувати, і що підміна перевизначає
Date, таймери,requestAnimationFrameтаperformance— і ставиться перед усіма іншими діями з часом.
Середовище та іменування еталонів
- Розумію, що рендеринг залежить від ОС, її версії й налаштувань, заліза, джерела живлення та headless-режиму, тому еталони генерують у тому самому оточенні, де їх потім порівнюють.
- Знаю, навіщо в назві файлу платформа (
chromium-darwin) і що замість імені браузера туди йде імʼя проєкту, якщо проєкти описані в конфізі; шаблон шляху налаштовується окремо. - Відрізняю «еталона під цю платформу немає» від «еталон є, але не збігається», і можу пояснити, чому жоден із цих випадків не і перезапуск їх не лікує.
Оновлення еталонів
- Можу назвати три умови робочої схеми оновлення разом: діф переглядає людина, оновлення їде тим самим , що й зміна, прапорця оновлення немає в CI-скрипті.
- Розумію, чим закінчується звичка оновлювати знімки «щоб позеленіло»: за кілька ітерацій еталон описує випадковий стан, включно з давньою .
Зовнішні сервіси
- Знаю, чим модель сервісу відрізняється від файлів у репозиторії: схвалений знімок переноситься між гілки, базовий білд обирає сервіс, матриця браузерів і ширин на його боці, гейтом стає статус пул-реквесту.
- Памʼятаю два дефолти, що ламають очікування: головна гілка схвалює зміни автоматично, а один знімок із позначкою «потрібні зміни» переводить у цей статус увесь білд.
Що покривати
- Можу обґрунтувати вибір цілей ціною супроводу: стабільні компоненти замість наскрізних флоу, мінімум динаміки в кадрі, одна комбінація на старт — і чітка межа, що візуальна перевірка не про доступність, продуктивність чи логіку.
Квіз
Перед стартом
- Питань: 17
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Візуальне тестування (visual testing) — з чого воно складається?