Тестирование верстки новостного сайта c адаптивным дизайном

Одна из самых примечательных задач, которая когда-либо стояла перед QA-отделом EastBanc Technologies, заключается в создании автоматизированной системы тестирования сайта www.washingtonpost.com. Это электронная газета, реализованная в виде информационного и новостного портала. 

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

Перед нами не стоит задачи проверить все страницы на соответствие нашим тестам. Наша задача — выявить баги PageBuilder’a, проверить надежность верстки страниц, созданных свежеиспеченным PageBuilder’ом, обратить внимание редакторов Вашингтонпоста на те ньюансы наполнения конкретной страницы контентом, которые могут повлечь за собой потенциальные проблемы в отображении страниц.
Создание системы тестирования находится в стадии активной разработки, но некоторые, интересные на наш взгляд, моменты уже можно представить на суд широкой публики.

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

Выбор инструментов тестирования верстки


Исследовав просторы интернета, мы остановились на следующих подходах и инструментах. Для тестирования каркаса страницы мы взяли на вооружение фреймворк Galen, который после интегрировали с testNG. 

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

Лазурным цветом — тестируется Galen’ом, залитое зеленым — скриншот-тесты:

Осторожно! Большая картинка

Скрытый текст



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

К примеру, есть 2 блока, на проверку которых у нас изначально были написаны функциональные тесты: Most Read и Information Block. Теперь мы первый проверяем скриншотами, а второй — гален-тестом.

MostRead Block, проверка скриншот-тестом:
530d0c5d3297ac1c846a9965b3cf16f5

Относительно функционального теста: строчек кода стало существенно меньше, полнота тестового покрытия увеличилась, и обновление теста при изменении внешнего вида данного блока на странице не займет много времени.

Тестирование этого блока рассмотрено в главе о скриншот-методе.

WaPo Information Block: 
fd4a4bf68e8dd819cc58bc429f3b1419

Galen без проблем справляется с проверкой соответствия текста и линков данного блока: сами ссылки заданы в локаторе, а соответствие текста — внутренней galen-проверкой. Относительно функционального теста полнота тестового покрытия не изменилась, но, за счет того, что проверки проходят в рамках одного теста, мы существенно экономим время.

Код Гален-теста.

Наша автоматизированная система тестирования использует: Java, Maven, TestNG, Selenium WebDriver, Selenium Grid,Galen Framework.

В создании Screenshot-based тестов нам активно помогает кроссплатформенный набор утилит ImageMagick

Сразу хотелось бы отметить, что код тестов мы пишем на Java с применением паттерна PageObject и фрейморка от Яндекс — HTML Elements. Для запуска тестов используются maven и testNG. 

Для облегчения запуска тестов, просмотра истории запусков тестов, просмотра отчётов без привлечения высококвалифицированных специалистов нами разрабатывается отдельное приложение — Dashboard.

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

Тестирование при помощи Galen Framework


Galen Framework обладает множеством несомненных преимуществ: это гибкий, простой в применении инструмент, имеющий широкие возможности тестирования адаптивного дизайна. Ко всему прочему, он неплохо документирован и активно развивается на данный момент. 

Galen Framework уже был достаточно подробно описан в одной из статей. Если кратко описать принцип работы с Галеном, то выглядит это примерно следующим образом: вы пишете спецификацию страницы (так называемый spec файл), используя специальный, хорошо документированный и интуитивно понятный синтаксис. В spec файле описывается взаиморасположение, размер, отступы, вложенность элементов страницы и некоторые другие параметры и условия, которым должна соответствовать верстка страницы, можете проверить даже соответствие текста внутри элемента. И все эти проверки будут применяться в зависимости от указанных нами тэгов. 

Тэги в spec-файле могут задаваться таким образом:



c2b2b4139a8bc44eb861996b11b6a2c2

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

075469c7bf8846ace85bddf6617ad30a

При клике на подсвеченную красным проверку отображается скриншот всей проверяемой страницы с такой подсветкой элементов: 

55bcb1311e04091f4057c2f671161f28

Galen Framework принимает на вход следующие параметры:

  • браузер, в котором будет проходить проверка
  • разрешение, на котором следует запустить тест
  • url тестируемой страницы
  • Javascript-файл, который нужно (если нужно) применить на запускаемой странице до начала проверок по .spec файлу (например, если нужно проверить отображение страницы залогированному на сайте пользователю)
  • имя запускаемого .spec файла
  • тэги, которые необходимо применить к проверкам .spec файла (например: desktop, all, если мы тестируем лэйаут для десткопа).


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

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

Выбор тестируемой страницы из подмножества однотипных страниц


А какие страницы выбрать для тестирования верстки, если тест предназначен для проверки множества однотипных страниц? 

Мы решили, особо не заморачиваясь, при каждом запуске тест-сьюита выбирать рандомную страницу из подмножества (т.е. для тестирования подмножества страниц рецептов выбрали один из рецептов и передаем его url всем тестам по лэйаутам). Так как проверить все страницы задачи не стоит, посему вариант выбора рандомной страницы показался оптимальным. Url рандомной страницы подмножества проверяемых страниц передается Галену методом, общим для всех тестов внутри нашей системы автоматизированного тестирования сайта (кроме тестов на тестирование верстки, у нас есть также функциональные и скриншот-тесты). 

Для примера есть 2 варианта отображения одного и того же типа страниц — страниц кулинарных рецептов, в одном из которых верстка содержит ошибку: 

690feee6159ee96f0809f3af1133cdfe1

Из примера видно, что блок “Most Read”, который должен располагаться в правом столбце страницы, на левой странице располагается в основной части, а не справа. Чтобы проверить отсутствие подобного рода проблем, нужно проверить большое число страниц и учесть множество факторов.

На каких разрешениях запускать тесты?


Сначала появилась идея выбрать наиболее распространенные девайсы и использовать для запуска тестов их разрешения. Однако, явно прослеживающаяся тенденция ускоренной мобилизации планеты не позволяет выделить (и, тем паче, предсказать) каких-то безусловных лидеров на этом поприще. Девайсов, позволяющих просматривать веб-приложения, великое множество, и унификация разрешений для таких девайсов – совершенно не модное нынче занятие. Внезапно прокравшаяся мысль о том, что адаптивный дизайн на то и адаптивный, чтобы правильно отображаться на любом валидном разрешении, спасла наши умы и пресекла дальнейшие исследования в этой области. Решение было принято: тестируем верстку на всех валидных разрешениях. 

Валидными разрешениями были назначены все разрешения от min Viewport width = 241 px (меньше браузер не уменьшается) до max Viewport width = 1920px (верхняя граница — простым волевым усилием). У нас пока не было страниц, где высота вьюпорта для целей автоматизированного тестирования являлась определяющим параметром, поэтому на высоту мы пока не обращаем внимания.

Как же тестировать верстку на всех разрешениях?

Для начала весь диапазон валидных разрешений был разбит на диапазоны различающихся лэйаутов. Сами лэйауты «резиновые», но различное расположение блоков позволяет провести разграничение. Определить границы лэйаутов несложно – тянем за уголок браузера и смотрим, на какой граничной точке изменяются блоки страницы: их взаиморасположение, количество и/или поведение. Для упрощения принимаем во внимание только ширину вьюпорта. Получилась следующая таблица:

DESKTOP: max 1920px, min 1018px;
LAPTOP: max 1017px, min 769px;
TABLET: max 768px, min 481px;
MOBILE: max 480px, min 361px;
SMALL_MOBILE: max 360px, min 280px.

К слову сказать, SMALL_MOBILE layout мы пока решили не тестировать, так как количество пользователей, просматривающих Вашингтонпост на девайсах с таких разрешением, катастрофически мало (умозрительное заключение, и нет проблемы добавить при тестировании в дальнейшем). Осталось для тестирования 4 диапазона, имеющих разную верстку.

Ниже описан код для запуска Galen-теста для десктопных разрешений: 

Скрытый текст



При запуске каждого теста Galen’у подается рандомное разрешение из диапазона для заданного лэйаута ( getRandomResolution(DESKTOP)):

protected Dimension getRandomResolution(Dimension[] d) {

   return getRandomDimensionBetween(d[0], d[1]);

}

 

private Dimension getRandomDimensionBetween(Dimension d1, Dimension d2) {

   double k = Math.random();

   int width = (int) (k * (Math.abs(d1.getWidth() - d2.getWidth()) + 1) + Math.min(d1.getWidth(), d2.getWidth()));

   int height = (int) (k * (Math.abs(d1.getHeight() - d2.getHeight()) + 1) + Math.min(d1.getHeight(), d2.getHeight()));

   return new Dimension(width, height);

}



И, собственно, диапазон разрешений задается в таком виде: 

public static final Dimension[] DESKTOP = {new Dimension(1920, 1080), new Dimension(1018, 1080)};



Тестирование по рандомному выбору разрешения из валидного диапазона и тестируемой страницы из подмножества однотипных страниц, таким образом, превращается в вероятностный процесс. Чем чаще будем запускать — тем больше разных багов найдем. При единичном успешном прохождении теста мы может сказать лишь, что данная конкретная страницы на данном конкретном разрешении – валидна. Но уже после 500 успешных прогонов мы можем утверждать, что верстка по большей части жизнеспособна. Сразу оговоримся, что «500 успешных прогонов» – это умозрительная оценка, и тут нужно смотреть на контент и количество эквивалентных страниц.

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

Рассмотрим, чем помогает нам такой подход на примере тестирования страницы рецепта.

Тест каркаса страницы рецепта запускается для диапазона разрешений (Viewport width) от 768px до 1017px. Возьмем для примера страницу: www.washingtonpost.com/pb/recipes/maple-banana-frozen-yogurt/14143/

Тест на граничных точках Laptop layout’a (1017px и 768px) не выдавал ошибок.

Однако, после того как мы начали запускать тест на рандомном разрешении, примерно в половине случаев тесты падали и скриншоты показывали, что блоки из правой колонки уползают вниз под основной контент. 

Правильный вид:

Осторожно! Большая картинка

Скрытый текст



Вёрстка сломана:

Осторожно! Большая картинка

Скрытый текст



Screenshot-based метод тестирования


Вдохновившись статьёй, мы решили использовать screenshot-based метод тестирования. К слову сказать, для тестирования лэйаута изначально у нас ставка делалась именно на этот метод. Т.е. была идея сравнивать полноразмерные скриншоты страницы с заранее подготовленной моделью, заменив все потенциально изменяющиеся элементы на заглушки (в качестве заглушек берётся заранее выбранное произвольное изображение). К таким элементам у нас относились картинки, флеш-реклама и текст. Затея провалилась главным образом из-за того, что страницы содержали множество блоков, загружаемых динамически, в результате чего физические размеры снимаемых скриншотов и расположения блоков изменялись от запуска теста к запуску. Кроме того, с некоторых пор Chrome утратил возможность делать полноразмерные скриншоты, что также создало ряд проблем.

Screenshot-based тесты теперь у нас проверяют те отдельные элементы и блоки на странице, для которых важно отображение, и/или проверка которых функциональными или гален тестами сложна или невозможна.

Для примера:

Вот так выглядит MostRead блок на главной странице washingtonpost.com (слева) и модель с которой мы будем сравнивать скриншот этого блока (справа):

d16b56e1e85b4dad999a6af03f11e91f 17e1839d939ebfe4abccc0789f393a8d1

Код теста выглядит так:

@Test(groups = { "ScreenshotBased" })
@WebTest("Verifies that 'Post Most' block is displayed properly")
public void testMakeupForPostMost() {
    HomePage page = new HomePage().open();
    page.preparePostMostForScreenshot();
    screenshotHelper.shootAndVerify(page, page.thePostMost, "_thePostMost");
}



Для хранения скриншотов используется следующая структура каталогов:: /models/HomePage/firefox/HomePage_thePostMost.png

Как отсюда видно, для разных браузеров снимается свой модельный скриншот необходимого блока.

Метод shootAndVerify() находит путь до модели по классу переданной страницы и имени браузера, в котором запущен тест.

Забегая вперед, скажем — работает довольно неплохо, и далее опишем некоторые детали процесса с оговоркой, что не все еще до конца отлажено.

Как оказалось, сделанный снимок необходимого блока может зависеть от множества факторов, таких как:

  • версия операционной системы
  • тема операционной системы
  • браузер и его версия
  • различные параметры сглаживания шрифтов и аппаратное ускорение.


Первая проблема была в том, что размеры сделанных скриншотов отличались в зависимости от настроек ОС и браузера. Чтобы сделать размеры блоков, а, следовательно, и скриншотов одинаковыми, нужно запускать браузер с постоянными размерами. Изменить размер окна браузера можно, используя соответствующий метод вебдрайвера: driver.manage().window().setSize(requiredSize). Но таким образом мы задаем размер окна, а не размер нужной нам видимой области — вьюпорта. Вертикальный скроллбар, к слову, тоже влияет на размер вьюпорта, и его толщина также зависит от темы windows, поэтому нужно это учитывать. Решением проблемы стал калибровочный метод, подгоняющий размер вьюпорта под заданные размеры. После запуска первого теста, разница между шириной размера окна и шириной вьюпорта сохраняется в специальный параметр и переиспользуется при последующих запусках.

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

layers.acceleration.disabled
gfx.font_rendering.cleartype_params.rendering_mode 
gfx.direct2d.disabled

Но, к сожалению, это не помогло.

Кроме того, для сравнения скриншотов утилитой ImageMagick используется такой параметр, как fuzz, который задаёт максимально возможное расхождение скриншотов.

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

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

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

По ссылкам в отчете доступны:

картинка-модель
55baded6276c108dc9bfd952e6fb8777

скриншот тестируемого блока:
037c2a00c7dc9e2ffd8646677b862a30

результат сравнения этих двух изображений: 
12dc2f25a98cc7b552bea17c60a5602a

CommandException говорит нам о том, что сравниваемые изображения отличаются на 251px:
bfcbf21542728e966d7a08f39ecf77fc

Бывают также ситуации, когда размеры скриншотов не совпадают. В таком случае мы получим такой отчет:

78bbb899d4cede6012e498b5612e0406

Иногда, по неизвестным причинам, элементы внутри тестируемого блока немного смещены. Для таких случаев мы сравниваем не с одной моделью, а с группой моделей, подходящей по маске, т.е. у нас может быть несколько моделей блока thePostMost с такими именами HomePage_thePostMost.png, HomePage_thePostMost (1).png, и все модели считаем валидными. К счастью, количество таких вариантов конечное, обычно не больше 2.

Технические аспекты


Как уже было сказано выше, для написания и запуска тестов используется стек технологий: Java, Maven, TestNG, Selenium, Galen Framework. Кроме того, результаты прохождения тестов отправляются в graphite. Запуск тестов осуществляется непосредственно при помощи Jenkins CIS. Не будем подробно останавливаться, почему был выбран именно такой набор. Коротко опишем, как это всё взаимосвязано. 

Selenium Grid сейчас развернут локально на четырех виртуальных машинах с Windows 7, где запущены ноды грида, и на линуксовой машине, на которой запущен хаб. На каждой из нод доступны по 3 инстанса firefox и chrome браузеров. Кроме того, на линуксовой машине также развернуты Jenkins и graphite. Гален тесты запускаются в общем запуске тестов благодаря интеграции с TestNG. Для этого был написан соответствующий класс, позволяющий использовать jav’овый Galen API. При реализации взаимодействия TestNG с галеном у нас возникли некоторые проблемы, которые были оперативно решены благодаря взаимодействию с разработчиком галена. Сам разработчик галена охотно идёт на сотрудничество и регулярно выпускает обновления для этого инструмента, которые расширяют его возможности и делают его ещё более удобным. Сам он планирует написание документации по интеграции галена с TestNG.

Функциональные, гален и screenshot based тесты разделены при помощи соответствующего параметра группы, приписанного к аннотации Test, и имеется возможность их отдельного запуска.

Наши умозаключения


Оба подхода – метод сравнения скриншотов и тестирование при помощи Galen Framework – применимы для тестирования верстки страниц. Они успешно взаимодополняют друг друга. Метод сравнения скриншотов больше применим, когда нужно протестировать отображение какого-либо отдельного элемента или блока, например панели “шаринга” в социальных сетях или основного меню в хедере. Блок может содержать множество иконок внутри себя, которые в свою очередь могут находиться внутри других иконок и элементов, или иметь относительное с ними позиционирование.

С помощью галена описывать все эти мелкие моменты достаточно трудоёмко, однако один скриншот на каждый браузер решает эту проблему, и сравнение скришотов исключает вариант, когда мы что-то можем упустить при описании спека. Гален, в свою очередь, прекрасно справляется с относительным расположением блоков и проверкой заголовков и фиксированного текста в них. Он имеет хорошее применение, когда нужно протестировать вёрстку на однотипных шаблонизированных страницах, не нагруженных функциональной логикой, например, какого-либо информационного портала, как в нашем случае, когда практически любая страница сайта доступна без авторизации, либо каких-либо других действий пользователя. Кроме того, гален хорошо решает задачи кроссбраузерного тестирования в условиях адаптивной вёрстки приложения.