Як змусити тести «бачити» помилки елегантний спосіб автоматизації тестування
Мене звуть Олексій Лакович, я займаюся автоматизацією тестування у компанії Генезіс. У тестуванні я працюю вже понад 6 років і нагромадив дуже великий досвід як в автоматизації тестування, так і в організації команд із ручного тестування.
Хочу розповісти вам про одне з елегантних рішень на тему візуального тестування сайту/додатків. Я зовсім недавно розповідав про нього на наших Бесідах (ми раз на тиждень або запрошуємо до компанії зовнішнього спікера, або хтось із компанії робить виступ на якусь тему). Мені здалося, що про таке рішення буде цікаво дізнатися читачам DOU, які займаються автоматизацією тестування.
Даним методом «візуального тестування» можна виявляти помилки верстки, текстів, відмінності PROD і STAGE версій, відмінності різних версій браузерів, також всі недоліки, які можна «побачити» при тестуванні ПЗ.
Власне, ідея проста: порівняння скріншотів програми (Actual vs Expected) та отримання різниці між ними.
Ми використовуємо мову Java, Selenium WebDriver і дуже непогане рішення від хлопців з Яндекса — бібліотеку aShot (величезний їм привіт і подяку за такі інструменти, як aShot і Allure).
Якщо ви розробляєте ваші тести іншими мовами — підхід залишається тим самим, оскільки основний принцип не змінюється, а варіанти технічного рішення завжди знайдуться: — Написати аналогічний інструмент для порівняння картинок. — Скористатися вже наявними рішеннями (погуглити або подивитися у бік універсального інструменту роботи із зображеннями, наприклад, imagemagick).
Логіка тесту. Перше, про що хотілося б написати — це про нехитру «логіку» роботисамого тесту: 1. Браузер переходить сторінками і робить скріншоти сторінок/елементів. 2. Якщо відсутній еталонний знімок, зроблений скріншот зберігається в папку з «expected» скріншотами. 3. Далі викликається метод для порівняння картинок (щойно зробленого та еталонного знімків) і видає результат - різницю між ними в пікселях; 4. Якщо це значення == 0, тест проходить успішно. Якщо ж ні, то збирається гіфка зі скріншота зразка, життєвого знімка і знімка з позначеними відмінностями. 5. Вся ця справа формується у звіт з усією інформацією про тест і зроблені знімки, і в разі провалу надходить повідомлення на пошту.
Крім цих кроків, звісно, є додаткові умови, такі як задані допустимі відмінності (числове значення пікселів), ігнорування динамічних елементів, заміна значень каунтерів/тексту на сторінці — будь-яких речей, які «заважають» чи особливо важливі.
Але в самому базовому випадку можна обійтися і без зайвих проблем.
Також при першому запуску тесту ви повинні бути впевнені, що ваш сайт відображається правильно, і ті знімки, які були зроблені в процесі, будуть дійсно еталонними.
Найголовніше в цьому, як і в будь-якому іншому тесті — зробити реалізацію максимально зручною для використання та гнучкою для внесення змін, які мають місце у процесі підтримки автотестів.
Встановлення/налаштування оточення
Для того, щоб у нас все було готове до роботи, ми повинні встановити: 1. JDK; 2. IntelliJ IDEA (+ драйвер для хрому); 3. Maven (звичайно, можна без нього; по дефолту в IDEA він має бути); 4. Selenium WebDriver; 5. Драйвери для потрібних нам браузерів (якщо будемо крутити локально); 6. TestNG/JUnit (тут теж опціонально); 7. Yandex AShot.
Це спрощенаверсія, всі подробиці з налаштування кожного з пунктів можна знайти в Інтернеті.
Не хочеться багато писати про встановлення кожного пункту, але опишу дещо з важливого. Завантажуємо JDK, встановлюємо IntelliJ IDEA, запускаємо і створюємо новий Maven Project. Потім переходимо до підключення потрібних нам бібліотек і фреймворків, підключаємо залежності в мавен pom.xml файл:
Структура папок
Ми повинні продумати структуру папок для збереження знімків. Найпростіший варіант - взяти якусь кореневу директорію з назвою типу «testScreenshots» і в ній створити 4 директорії під скріншоти. Цей пункт краще реалізувати програмно, щоб не доводилося у разі запуску тесту на іншій тачці створювати їх вручну:
У коді за цим пунктом проблем бути не повинно - записуємо шляхи до змінних (expected, actual, markedImages, gifs) і робимо метод setter, який їх призначає у випадку, якщо для конкретного тесту ми хочемо змінити директорію, і відповідно створює ці папки у випадку їх відсутність у системі.
Назви тестових скріншотів
Назва файлу скріншота - це його унікальний ідентифікатор, за яким ми шукатимемо потрібний нам еталонний скріншот. Це досить важливий момент, оскільки тести можуть бути написані під різні браузери та розмір вікон. Ми повинні це врахувати в призначенні імені наших скріншотів, що зберігаються.
У нас це робиться в такий спосіб - найменування скріншота складається з: 1. ім'я домену (злито) - hitwecom; 2. назва сторінки\елемента - profile; 3. локалізація - EN; 4. браузер - Chrome; 5. розмір вікна браузера - 1366 × 768; 6. будь-який інший параметр, який може знадобитися для ідентифікації знімка, наприклад, версія браузера.
Ну і власне сам файл матиме назвуабо для іншого браузера/розміру вікна - hitwecom_profile_EN_Firefox_1280x1024.png .
У самому тесті при виклик методу для порівняння скріншотів доводиться вказувати тільки назву елемента. Всі інші дані автоматично беруться перед збереженням файлу за допомогою driver().getCurrentUrl(); driver().manage().window().getSize(); параметрами, які передаємо у тест, такі як браузер та локалізація.
Також тут варто врахувати, щоб назва файлів не відрізнялася при прогоні тестів на стейдж сервері і на проді. Буде досить зручно мати заготовлені скріншоти з прода і порівнювати з тим, що намагаються видати як новий реліз на стейджі.
Зняття та збереження скріншоту
Скріншот можна отримувати будь-яким зручним для вас способом, цей «вид» тестування можна застосовувати не тільки для веб-додатків. Єдина відмінність, яка у вас буде, - це те, як ви отримуєте сам знімок і проставляєте координати для блоків, що ігноруються. Інші пункти можуть зовсім не відрізнятися.
У бібліотеці aShot є своя реалізація для зняття знімка екрана сторінки або веб-елемента за допомогою ВебДрайвера.
Також потрібно врахувати, що наведення мишею на посилання/блоки може впливати на зовнішній вигляд програми, тому простим способом перед початком наших тестів просто виводимо курсор у верхній лівий кут екрана:
Для сторінки це виглядає так :

Так, наприклад, Хром не робить скріншот по всій висоті сторінки - ми можемо додати до зняття скріншота shootingStrategy з аргументом scrollTimeout (значення в мілісекундах). Це затримка між скроллом та зняттям наступної частини сторінки.
Знімок сторінки зі скролом:

Якщо у вас "фіксований хедер" (яку нас на скрині вище), і при скролі він завжди знаходиться вгорі сторінки, то його можна закріпити перед знімком, змінивши йому атрибут "style":

Якщо потрібно зробити скріншот елемента, а не всієї сторінки, ми можемо передати в метод takeScreenshot конкретний елемент і зробити його знімок:
Важливо: за промовчанням для визначення координат елемента використовується jQuery. Якщо у вас на проекті відсутня підтримка jQuery, потрібно вказати використання WebDriverCoordsProvider для їх визначення за допомогою WebDriver API:
Зберігаємо отриманий скріншот у файл:
Якщо ж ви робите скріншот за допомогою інших утиліт, то отримати об'єкт Screenshot з потрібним файлом можна, передавши в конструктор класу Screenshot BufferedImage :
При знятті знімка екрана іноді потрібно прибрати або ігнорувати деякі динамічні елементи на сторінці, через які тест може помилково падати.

Якщо потрібно змінити значення лічильника повідомлень, ми можемо викликати метод для цих цілей:
Ну і відповідно, якщо нам потрібно змінити текст, то й для цього теж ми маємо метод:
Другий спосіб - ігнорування. Ми можемо в метод для зняття скріншота передати Set ignoredElements . Створюємо Set із елементів, які ми не хочемо враховувати при порівнянні скріншотів, і викликаємо метод для зняття скріншота, свідомо передавши цей список до AShot.
Перед зняттям знімка будуть знайдені координати та розмір наших елементів на сторінці, і в момент порівняння ці елементи (точніше, координати та розмір блоків) не враховуватимуться в результаті.
Ось приклад зняття знімка екрана з ігнором деяких елементів:
Перед порівнянням скріншотів у нас будуть актуальні (actualScreenshot) і очікувані (expectedScreenshot) знімки. Усі ігноровані зонизбережені в нашому actualScreenshot, і при порівнянні нам потрібно буде ці зони передати в наш expectedScreenshot.

Залишилася проблема з лічильником повідомлень — він позначається як помилка. Його ми просто приберемо. Викликаємо наш метод для видалення елементів перед зняттям скріншота та робимо знімок:
Тепер ми маємо чисту сторінку, готову до прогону тестів без зайвих «шумів»:

Порівняння скріншотів
Для початку отримуємо актуальний знімок, як із прикладів вище:
Після зняття актуального скріншота ми перевіряємо, чи маємо еталонний знімок для цієї сторінки. Якщо його немає, записуємо актуальний як еталонний.
Ще було б непогано зробити можливість автоматично "перезаписати" усі еталонні знімки. Це потрібно, коли, наприклад, у нас кардинально змінили дизайн або додали новий блок. Для цього створюємо якийсь boolean параметр, типу newScreenshots, і якщо при запуску тесту ми передамо true, то перезаписуємо знятий скріншот в папку expected.
Після чого ми повинні підняти файл із нашим еталоном для порівняння:
Якщо ми встановили зони, що ігноруються, то повинні передати їх у наш очікуваний знімок:
Далі, за допомогою класу ImageDiffer викличемо метод для порівняння цих скріншотів:
І, власне, матимемо результат порівняння. Різниця знімків у пікселях:
Обернемо результат в Assert і отримаємо готовий тест на перевірку зовнішнього вигляду нашої програми:
Після цього згенеруємо картинку з відмітками відмінностей наших Actual та Expected і збережемо його в папку з markedImages:
Додамо всі скріншоти до звіту (у нашому випадку це Allure):
Згенеруємо gif зображення з картинок expectedFile, actualFile, diffFile:
GifSequenceWriter - клас, в якому є методдля створення gif зображення з масивів файлів. Гіфку також варто прикріпити до нашого звіту.
В результаті виходить зручне зображення, на якому ми можемо відразу побачити всі відмінності:

Перевірка та виведення результатів
Про ігнорування та заміну елементів на сторінці я вже написав. Також можна вигадати набір правил для результатів тестів. Якщо немає бажання сильно морочитися з ігноруванням або вирізанням елементів, то можна прописати правила типу: - допустимі відмінності в пікселях (наприклад, 16 або 2078; миготливий курсор на полі для введення тексту); - допустимий діапазон у пікселях; - відсоткове співвідношення розбіжності між скріншотами за формулою: diffPoints / кількість пікселів у знімку * 100; - все, що менше N пікселів, не вважати помилкою.
Ну, і, власне, висновок результату тестів — це одне з головних завдань автоматизатора, а особливо якщо це тести, які перевіряють верстку. Ми використовуємо Jenkins, в якому генеруємо Allure звіт з усіма параметрами тесту, скріншотами, зробленими в процесі виконання, gif зображенням з відзнакою, логами браузера, куками і т.д.
Описувати, як налаштовувати ці справи, думаю, у цій статті не варто, тому що ця тема заслуговує на окремий матеріал.
Найголовніше: виводьте значення diffPoints та скріншоти – такий звіт вже буде дуже корисним :)
Ну і засинайте всіх алертами на пошту – це стимулює фіксувати тести та баги.
![]() | ![]() | ![]() |
Ось власне поки що все! Сподіваюся, було цікаво! «Стабільних» всім тестів і дякую за увагу!


