Найти отличия, или внедряем Snapshot-тесты для web. Опыт Звука

—

от автора

Всем привет! На связи Наталья Данилина и Чечиков Иван из Звука. В этой статье мы хотим поделиться опытом внедрения snapshot-тестов для web-приложения — расскажем, что это такое и для каких задач применяется.

Подробности – под катом.

Все началось с поиска ответов на вопросы: как сократить время на тестирование, как не выгореть на регрессе, как потестить фичу без дизайн-ревью, и как сократить кол-во багов в проде после релиза?

В чем суть. Мы имеем большое количество ручных тест-кейсов, которые покрыты e2e и component-автотестами. Наши автоматизированные тесты запускаются  каждый раз при старте регресса (при этом длительность его занимает от 3 до 4 дней). И бывают ситуации, что уже после завершения процесса мы находим баги в проде, связанные с UI — их сложно уловить на этапе регресса из-за человеческого фактора. 

Поэтому мы нашли подход, который сможет в перспективе решить подобные проблемы — так в работе появились snapshot-тесты.

Snapshot-тесты — это автоматизированные тесты, которые используются для проверки веб-страниц. Они позволяют сравнить текущее состояние веб-страницы с предыдущим (или ожидаемым) состоянием и обнаружить любые изменения или различия. Мы используем snapshot-тесты также и для сравнения отдельных html-элементов (попапов, модалок, фреймов и пр.). Происходит сравнение текущего состояния компонента в тестовой среде и эталона. Эталоном может быть компонент прода, но лучше, чтобы это был компонент актуального дизайна.

Snapshot-тесты используют попиксельное сравнение. Каждый пиксель эталона сравнивается с соответствующим пикселем изображения объекта тестовой среды, чтобы обнаружить различия. Это позволяет тестам быть очень точными и обнаруживать даже небольшие изменения на веб-странице или в компоненте. Однако надо учитывать погрешность в сравнении, так как могут быть холостые ошибки при небольшом проценте отличия (до 5%).

Работая со snapshot-тестами, мы выделили ряд их преимуществ: 

  • Быстрая обратная связь. Snapshot Testing позволяет быстро проверить, изменился ли результат теста после внесения изменений в код. Это дает быструю обратную связь разработчикам и тестировщикам, что помогает быстрее реагировать на возникающие проблемы.

  • Автоматизация тестирования интерфейса. Snapshot Testing автоматизирует процесс проверки интерфейса, что позволяет решать связанные с ним задачи быстрее и эффективнее.

  • Упрощение процесса тестирования. Snapshot Testing упрощает процесс тестирования интерфейса, так как не требует большого количества кода для написания тестов. Это сокращает время, затрачиваемое на написание и поддержку тестов, освобождая ресурсы для других задач.

  • Повышение качества кода. Благодаря snapshot-тестам можно легко отслеживать изменения в интерфейсе и быстро обнаруживать ошибки. Поэтому повышается качество кода и улучшается пользовательский опыт. В дальнейшем мы планируем использовать snapshot-тестирование в процессе разработки. 

  • Сокращение времени на регрессионное тестирование. Внедрение Snapshot Testing позволяет сократить время, затрачиваемое на регрессионное тестирование. Поэтому проверка интерфейса автоматизируется, а проблемы выявляются быстрее.

Мы внедрили snapshot-тесты в наш UI-фреймворк, написанный на Kotlin с использованием Playwright. Под них мы создали отдельную директорию во фреймворке и выделили джобу для запуска тестов в GitLab CI. TMS у нас —  Allure TestOps.

Сами тесты логически не сложны. Рассмотрим на примере теста сравнения куки в темной теме:

..... class CompareCookiesBlackSnapshotsTest : WebTest() {      @Test     @TestOpsId(44908)     @DisplayName("Сравнение попапов кук (темная тема)")     @Owner(CHECHIKOV_IVAN)     fun testCase() {         step(             """             |*Precondition*:             |Пользователь не авторизирован и находится на главной странице"             """.trimMargin()         ) {             assertThat(page).containsURL(baseUrl)             page.waitForLoadState()         }          step("Проверяем, что включена темная тема") {             SideMenuBar(page).checkedSwitcherDarkTheme()         }          step("Проверяем что попап куки есть на странице") {             assertTrue(CookiesPage(page).cookiesBody.isVisible)         }          step("Сравниваем с эталоном") {             screenStageBytes = CookiesPage(page).cookiesBody.screenshot()             assertScreenShots(screenStageBytes, imageName = "cookieBlack")         }     } } .....

Шаги достаточно понятны: мы выполняем предусловие, проверяя URL главной страницы. Следующим шагом убеждаемся, что включена темная тема, далее — что присутствует кука на странице. В заключении мы делаем скриншот текущего состояния куки и вызываем метод сравнения тестового изображения с эталоном.

.....     fun assertScreenShots(stageBytesArray: ByteArray, imageName: String) {        val prodImage = ImageIO.read(File("src/test/resources/screens/expectedImages/$imageName.png"))         val stageImage = ImageIO.read(ByteArrayInputStream(screenStageBytes)) .....

Эталоны лежат у нас в директории /screens/expectedImages под гитом. В тестах мы заранее знаем, каким будет тот или иной эталон, поэтому пробрасываем в переменную $imageName название нашего изображения. Сделав скриншот тестового компонента, мы переводим его в объект BufferedImage, как и эталон.

..... val prodImageResized = resizeImage(prodImage, stageImage.width, stageImage.height) .....
..... fun resizeImage(image: BufferedImage, width: Int, height: Int): BufferedImage {         val scaledImage = image.getScaledInstance(width, height, Image.SCALE_SMOOTH)         val resizedImage = BufferedImage(width, height, image.type)         resizedImage.getGraphics().drawImage(scaledImage, 0, 0, null)         return resizedImage     } .....

Приводим объекты сравнения к одному размеру:

..... val imageComparison = ImageComparison(prodImageResized, stageImage).setAllowingPercentOfDifferentPixels(5.0) val diff = imageComparison.compareImages() .....

Магию сравнения выполняет библиотека image comparision. В объект ImageComparison мы передаем наши BufferdImage объекты для сравнения и выставляем процент погрешности — 5.0. Проводим сравнение и получаем объект расхождения diff. 

.....         val diffImageStream = ByteArrayOutputStream()         val prodImageStream = ByteArrayOutputStream()         val diffImg = diff.result         ImageIO.write(diffImg, "png", diffImageStream)         val diffBytesArray = diffImageStream.toByteArray()         ImageIO.write(prodImageResized, "png", prodImageStream)         val prodBytesArray = prodImageStream.toByteArray()         saveScreenshot("actual", stageBytesArray)         saveScreenshot("expected", prodBytesArray)         saveScreenshot("difference", diffBytesArray)         assertEquals(ImageComparisonState.MATCH, diff.imageComparisonState,             "Изображение в тестовой среде отличается от эталона $imageName")     } .....       

Мы хотим видеть в результатах прогона тестов изображения сравниваемого тестового компонента, эталона и изображения-различия, поэтому переводим все наши объекты сравнения в массив байтов и сохраняем их в скриншоты.

Как это выглядит в Allure TestOps:

Текст, иконки крестика и куки в эталоне отличаются от скриншота с тестовой среды. На изображении различия мы видим изменения, отмеченные красными прямоугольниками. Ниже сообщение об отличии изображений.

Итак, мы привели пример одного из наших snapshot-тестов. Cейчас мы продолжаем расширять покрытие, так как в долгосрочной перспективе надеемся, что Snapshot Testing поможет значительно улучшить эффективность работы команды QA, сократить время на выполнение рутинных задач, повысить качество тестирования и ускорить обнаружение ошибок в интерфейсе приложения.

Спасибо, что прочитали! Если у вас есть вопросы – будем рады ответить на них в комментариях.


ссылка на оригинал статьи https://habr.com/ru/articles/808799/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *