Часто ли вы сталкивались с ситуацией, когда тесты падали из-за небольшой разницы между скриншотами? Например, не успел исчезнуть спиннер, не докрутился скроллбар, уведомление исчезло чуть быстрее, чем нужно и так далее. 

Часто ли вы сталкивались с ситуацией, когда тесты падали из-за того, что на скриншоте появился сторонний элемент, который выпустила другая команда? Часто ли вы сталкивались… с самыми разными причинами, из-за которых приходится сидеть и анализировать фейлы скриншотных тестов?

Меня зовут Александр Гончар, я инженер по обеспечению качества в Т-Банке. Хочу поделиться опытом, как избавиться от скриншотных тестов.

От чего зависят скриншотные тесты

Скриншотное тестирование очень популярно: что может быть проще, чем сравнить актуальный скриншот с эталонным. 

Работая много лет со скриншотными тестами, я сталкивался с самыми разнообразными проблемами, из-за которых такие тесты падали. Я попробовал обобщить факторы, от которых зависят скриншотные тесты:

  • Хранилище: доступы на чтение или запись в CI локально и у коллег; количество выделенного места, сетевой доступ, денежные затраты.

  • Различия между средами запуска — локально и в образе CI: ОС, версия браузера и его настройки, региональные настройки, часовой пояс, разрешение экрана, шрифты и так далее.

  • Изменения кода в вашей и сторонних командах: стили, новые общие компоненты, новая функциональность, затрагивающие сразу несколько продуктов или страниц, рефакторинг, дизайн;

  • Необходимость обновлять эталоны при валидных изменениях функциональности, дизайна, браузера и так далее.

Эти проблемы приводили к тому, что на анализ, перезапуск тестов, обновление эталонов тратилось много времени. С этим нужно было что-то делать.

Чтобы решить, нужно ли дальше продолжать работать со скриншотными тестами, мы решили выяснить:

  • плюсы отказа от скриншотных тестов;

  • что проверяется скриншотными тестами;

  • как работает компиляция в Angular и рендеринг в браузере.

Результаты исследования

Плюсы отказа от скриншотного тестирования:

  1. Скорость выполнения тестов. Отдельный тест выполняется быстрее, так как нет действий со скриншотами: сохранение, вычитка, сравнение, обновление. 

Например, несколько замеров времени прохода простого теста в одинаковой среде показали, что тесты без скриншотов выполняются быстрее:

Время прохода скриншотного теста

2,2 с

2,1 с

2,4 с

2,1 с

2.1 с

Время прохода теста без скриншота

1,7 с

1,7 с

1,8 с

1,8 с

1,6 с

  1. Скорость прохода в CI: снижение времени выполнения тестов положительно сказывается на времени прохождения, например, MR.

  2. Скорость доставки фичи заказчику: улучшается лид-тайм, заказчик и бизнес счастливы.

  3. Не нужно тратить время на анализ упавших тестов — например, если тест упал из-за небольшой разницы между скриншотами.

  4. Не нужно обновлять эталон.

  5. Не нужно тратить ресурсы на перезапуск упавших тестов.

  6. Тестировщики могут уделить больше времени тестированию, процессам обеспечения качества, повышению квалификации. 

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

Можно провести аналогию, что это как использовать UI-логин в e2e-тесте и быть зависимым от него и от команды, которая разрабатывает механизм авторизации. Зачем тестировать логин, если тест должен проверять что-то другое?

Процесс обеспечения качества предполагает, что при тестировании мы оцениваем, как будет тестироваться ПО, какие проверки на каком уровне или этапе будут проведены. 

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

Логичный вопрос: а как же проверка дизайна, верстки, что она не поехала, что иконки и картинки корректно отображаются, что данные отображаются на странице, рендеринг в браузере, в конце концов? 

Рассмотрим этот вопрос на примере приложения на Angular.  

Компиляция в Angular. Историческая справка: начиная с Angular 9, используется AOT-компиляция. Она более надежна, чем JIT-компиляция, которая использовалась до этого. AOT-компиляция состоит из трех этапов: анализ, генерация кода и валидация, что позволяет избежать ошибок свойств и методов компонентов и сервисов в шаблонах. 

В режиме AOT компиляция происходит в момент сборки приложения до загрузки в браузере, при этом HTML- и CSS-файлы включаются в файлы JavaScript в строковом виде и есть обнаружение ошибок при сборке, что снижает шанс их попадания в приложение.

Стремится к нулю шанс, что верстка, статичный текст, стили и прочие элементы будут с ошибками, хотя они могут возникнуть из-за ошибок в самом Angular или ошибок разработки. Это ошибки, которые прошли ревью кода, не были замечены на фиче-ветке, тестовом стенде, препроде и так далее.

Рендеринг в браузере выглядит так:

  • Браузер скачивает, читает и парсит исходники. При этом формируются DOM из HTML-документа и CSSOM, загружаются стили.

  • Формируется дерево рендеринга — это набор объектов рендеринга или фреймов. Они содержат соответствующие им DOM-объекты и стили.

  • Выполняются layout- и paint-процессы, когда для каждого объекта рассчитывается положение на странице, а затем происходит отрисовка.

Благодаря тому, как Angular подготавливает необходимые данные для рендеринга в браузере, вероятность проблем при рендеринге также стремится к нулю.

Уровень проверок. Возникал ли у вас вопрос, почему та или иная проверка покрыта e2e-тестом? Где пишется тест, если на проде был найден баг: добавляете ли e2e-тест, интеграционный тест или идете к разработчикам, чтобы они написали юнит-тест? SLT и пирамида тестирования приходят на помощь, чтобы ответить на эти вопросы.

Для примера возьмем пару скриншотных тестов, которые проверяют, что пользователь видит страницу с одним или несколькими руководителями, с заголовком «Руководитель <имя руководителя>», где отображается имя руководителя и соответствующая иконка, в зависимости от статуса, например, проверки.

Могут возникнуть вопросы: как происходит получение данных, отображаемых на странице? Это функция бэка или фронта? Написаны ли на эту функцию и ее логические пути юнит-тесты? Какие юнит-тесты есть на фронтовый компонент, который рисует нужный текст и иконку в зависимости от полученных данных?

Отвечая поочередно на эти вопросы, видим, что нам не просто не нужен скриншотный тест, нам не нужен даже e2e-тест: все необходимые проверки можно реализовать на нижних уровнях. Логику получения данных руководителя можно покрыть юнит-тестами как на бэке, так и на фронте. Отображение текста и иконки можно покрыть юнит-тестами на фронте.

В итоге благодаря современным браузерам и компиляторам не нужно:

  • тестировать скриншотами статичный текст, статичные иконки и другие статичные элементы, так как они практически всегда будут на странице;

  • тестировать скриншотами элементы, которые отображаются в зависимости от тех или иных условий: ответ от АПИ, выполнение функции на фронте, выполнение условия, например ngIf.

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

Приведу несколько примеров юнит-тестов на фронте, проверяющих отображение разных данных, при этом в начале теста используется метод, в котором в компонент передается один или несколько руководителей.

Единственный руководитель:

it('Должен вернуть тексты для единственного руководителя', () => {
    	defineDirectorsAndInitializeFixture(singleDirector);
 
    	const headerElem: HTMLElement = fixture.nativeElement.querySelector('[automation-id=director-header]');
    	const headerTextElem: HTMLElement = fixture.nativeElement.querySelector('[automation-id=director-text]');
 
    	expect(headerElem.textContent?.trim()).toBe('Руководитель');
    	expect(headerTextElem.textContent?.trim()).toBe(
        	'Укажите актуальные данные руководителя для продолжения работы с заявкой',
    	);
	});

Несколько руководителей:

it('Должен вернуть тексты для нескольких руководителей', () => {
    	defineDirectorsAndInitializeFixture(severalDirectors);
 
    	const headerElem: HTMLElement = fixture.nativeElement.querySelector('[automation-id=director-header]');
    	const headerTextElem: HTMLElement = fixture.nativeElement.querySelector('[automation-id=director-text]');
 
    	expect(headerElem.textContent?.trim()).toBe('Руководители');
    	expect(headerTextElem.textContent?.trim()).toBe(
        	'Укажите актуальные данные руководителей для продолжения работы с заявкой',
    	);
});

Иконки руководителей:

it('Должен вернуть все типы иконок руководителя', () => {
    	defineDirectorsAndInitializeFixture(severalDirectors);
 
    	const connStoreDebugElem: DebugElement = fixture.debugElement;
    	const btnDebugElems: DebugElement[] = connStoreDebugElem.queryAll(By.css('[automation-id=director-icon]'));
 
    	const iconProperty = 'iconState';
    	expect(btnDebugElems[0].properties[iconProperty]).toBe('normal');
    	expect(btnDebugElems[1].properties[iconProperty]).toBe('error');
	});

Процесс отказа от скриншотных тестов

Чтобы отказаться от скриншотов, нужно:

  • Собрать метрики по дефектам, связанные с фронтом. Разделить их на дефекты логики и дефекты дизайна или верстки, чтобы понять, как часто они возникают.

Примечание

если у вас таких дефектов окажется много, нужно выяснить и устранить причину их возникновения

  • Проанализировать тесты на тему того, что они проверяют.

  • Выделить набор критичных тестов, которые можно не трогать первое время, для душевного спокойствия.

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

  • Выделить тесты, включая тесты из пункта выше, поверки которых можно реализовать в интеграционных или юнит-тестах.

  • Реализовать проверки из пункта выше в интеграционных и юнит-тестах.

  • Отказаться от тестов на e2e-уровне, чьи проверки реализованы в интеграционных или юнит-тестах.

  • Вернуться к набору критичных тестов и выполнить пункты 4—7 для них.

  • Дополнительно, если нашли баг на тестовом окружении или проде, при исправлении обязательно писать юнит или интеграционный тест.

Примечание

если вы вдруг поймали себя на том, что на найденный баг пишется e2e-тест, то устраняйте такую практику и покрывайте найденные проблемы в юнит или интеграционных тестах.

  • Регулярно отслеживать, не увеличилось ли количество багов на UI.

Вместо заключения

Мы избавились от скриншотных тестов, пересмотрели подходы к тестированию и уровню проверок в целом, ведь одно без другого не работает. Тестировщики больше вовлеклись в процессы и научились писать юнит-тесты на фронт.

Можно спросить: а зачем тестировщикам вникать в юнит-тесты, тем более уметь писать их, ведь традиционно это задача разработчиков? Мы стремимся к тому, чтобы тестировщики были вовлечены на всех этапах жизненного цикла ПО, в том числе и в юнит-тестировании. Тестировщик может прийти к разработчикам с проверками, которые нужно покрыть. Или реализовать их самостоятельно.

Уметь писать фронтовые юнит-тесты — хороший навык и это очень интересно.

Спасибо за уделенное время. На прощание — немного полезных ссылок:

Комментарии (16)


  1. ss-nopol
    25.06.2024 09:44

    А это, нейронку уже кто-то научил кликать и заполнять формочки, сравнивать результаты? Теоретически это проще чем код писать...


    1. AlexTest1 Автор
      25.06.2024 09:44

      Возникает вопрос "чего хотим добиться?", если используем нейронку. Дополнительно, как оценивать качество, как сравнивать результаты и с чем? Мне кажется, что история с покрытием на юнит уровне будет более надёжной, при этом, при написании кода практически полностью контролируется, что происходит и что проверяется.


  1. AccountForHabr
    25.06.2024 09:44

    1. Подождите, немного не понятно - как компиляция помогает тестировать верстку? width:20px и width:200px корректные css правила, но верстка совершенно разная. синтаксические проверки css были и до angular 9. тот же vscode подсвечивает неправильные стили.

    2. Вы в своем тесте на иконки проверили не то что иконки отобразились, а то что элемент имеет определенный стиль, что согласитесь разные вещи.


    1. AlexTest1 Автор
      25.06.2024 09:44

      Спасибо за ответ!

      Если было подлито 200px вместо 20px и это попало на прод, то, вероятно, есть моменты в процессе обеспечения качества, на которые нужно обратить внимание, т.к. такое изменение было пропущено на ревью, в тестировании и т.д.

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


      1. AccountForHabr
        25.06.2024 09:44
        +2

        Если было подлито 200px вместо 20px и это попало на прод, то, вероятно, есть моменты в процессе обеспечения качества, на которые нужно обратить внимание, т.к. такое изменение было пропущено на ревью, в тестировании и т.д.

        Погодите, вопрос же был, про то, как компиляция помогает в обеспечении тестировании верстки?

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

        это почему? разработчик/или другая команда подредактировала общий css или удалила/изменила файл c иконками - иконки отображаются не те, или не отображаются вовсе. наличие/отсутствие стиля эту ситуацию не отловит.


        1. AlexTest1 Автор
          25.06.2024 09:44

          Речь не про компиляцию отдельно для проверки вёрстки, речь про весь имеющийся набор инструментов, которые помогут предотвратить появление таких проблем.

          Если другая команда что-то сделала, из-за чего пропадают общие элементы - такие проблемы надо ловить не скриншотами, а посмотреть на причины возникновения таких ошибок. Возможно, нужно настроить хорошие процессы ревью кода, возможно, нужно вести канал уведомления о важных изменениях, когда одни поменяли, а обновились не все. И снова, в этой ситуации, можно обойтись вычиткой проверки элемента в DOM, не доводя дело до скриншотов.


          1. AccountForHabr
            25.06.2024 09:44

            Т.е. Вы предлагаете заменить автоматический инструмент ручным тестированием и ручным просмотром кода? Звучит как регресс.


  1. SolovevSerg
    25.06.2024 09:44
    +2

    Спасибо за статью!

    Мы активно используем скриншотное тестирование для дизайн-ревью. Например, когда мы мигрируем на новую версию UI-кита или делаем редизайн какого-то экрана, благодаря скриншотам, дизайнеры видят в МР разницу было/стало и могут указать на проблемы. Это самый удобный способ сопоставить две версии реализации дизайна. Иначе придется играть в игру "найди пять отличий".

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

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


    1. AlexTest1 Автор
      25.06.2024 09:44

      Спасибо за ответ!

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

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


  1. aamonster
    25.06.2024 09:44

    Я понимаю, что если есть возможность проверять правильность DOM, то проверка именно её локализует ошибку лучше и будет куда стабильнее. Но полный отказ от скриншотов вызывает некоторые сомнения. Если б вы сказали "мы уменьшили использование скриншотов в 10 раз" – я бы понял, но после полного отказа я жду, что вёрстка поедет (если, конечно, вы не переводите этот этап на ручное тестирование)

    Давно вы отказались от скриншотов?


    1. AlexTest1 Автор
      25.06.2024 09:44

      Спасибо за ответ!

      Конечно, на первых порах можно оставить набор критичных тестов со скриншотами, из-за опасений "вдруг вёрстка поедет". По наблюдениям, конкретно у нас верстка никуда не едет уже долгое время. В случае же возникновения проблем с вёрсткой можно попробовать выяснить причину, почему она поехала, как это можно предотвратить в дальнейшем. Просто так она не поедет


      1. aamonster
        25.06.2024 09:44

        Ну так тесты-то нужны, чтобы обнаружить поехавшую (не всегда даже из-за ваших изменений – могли просто подвезти новую версию WebView) вёрстку вовремя. Как вы увидите, что она поехала, если не проверяете её?


        1. AlexTest1 Автор
          25.06.2024 09:44

          Можно подойти комплексно: выяснить, почему подвезли новую версию WebView и никто об этом не был проинформирован; узнать, митигируется ли риск обновления любых других сторонних компонентов, из-за которых едет вёрстка; выяснить, почему разработка идёт таким образом, что при обновлении чего-то стороннего всё едет; выяснить, насколько часто это происходит и т.д.. Я к тому, что сама ситуация, когда можно полагаться на скриншотный тест как на последний рубеж обороны, не самая хорошая.


          1. aamonster
            25.06.2024 09:44

            Опять то же самое.
            Для всего, что вы пишете, надо иметь возможность обнаружить, что вёрстка поползла. Причём желательно узнать это не от юзеров. Так что этап обнаружения проблемы опускать нельзя. У вас есть только выбор – ручное обнаружение или автоматизированное.


            выяснить, почему подвезли новую версию WebView и никто об этом не был проинформирован

            Например, в iOS приехало обновление WKWebView. Допустим, все проинформированы. Что вы будете делать? Проведёте ручное тестирование всего интерфейса?

            узнать, митигируется ли риск обновления любых других сторонних компонентов, из-за которых едет вёрстка

            Проверять, когда появляется новая версия системных либ – это оно?

            выяснить, почему разработка идёт таким образом, что при обновлении чего-то стороннего всё едет

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


            Так что вопрос "давно вы отказались от скриншотов?" актуален. А для себя сделаю отметочку: не обновлять приложение Tinkoff, пока не пройдёт хотя бы пары недель с выпуска новой версии.


  1. breakingtesting
    25.06.2024 09:44

    Процесс обеспечения качества предполагает, что при тестировании мы оцениваем, как будет тестироваться ПО, какие проверки на каком уровне или этапе будут проведены. 

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

    Что-то вы тут не самым удачным образом смешали тестирование и обеспечение качества. Заговорив про последнее, вы резко скатились в тестирование. Проведение тестирования вам ни разу не обеспечит качество. Оно вам обеспечит нахождение багов, подтверждение соответствия требованиям, может быть немного удовольствия.

    Однако, у вас должны быть критерии качества продукта, тогда да - тестирование вам даст результаты, которые можно будет рассматривать на предмет соответствия/не соответствия критериям качественности.


    1. AlexTest1 Автор
      25.06.2024 09:44

      Спасибо за ответ!

      Если я правильно понял вашу мысль, то отвечу, что цель статьи не расписать отдельно процесс обеспечения качества. В целом, тестирование входит в этот процесс, поэтому здесь и упомянуто.