Несмотря на то, что HTML5 всё ещё находится в процессе разработки, он уже появляется в веб-интерфейсах. Одним из основных нововведений этой версии HTML стал элемент Canvas, который используется для отрисовки двухмерной графики. Например, всё, что вы видите и с чем взаимодействуете в игре MMORPG от компании Mozilla или старом-добром Command and Conquer, отрисовывается и обрабатывается с помощью Canvas. Самые изощрённые умы даже реализуют полноценные формы на Canvas. Или интерактивную модель солнечной системы.
Фреймворки для работы с этим элементом растут как грибы после дождя; про то, как начать программировать, используя Canvas, написано огромное количество статей. Но есть один пункт, о котором, по-видимому, из-за узкой специфики говорят редко и мало. Речь идёт о тестировании приложений на Canvas. В каком-то смысле оно становится проблемой для инженера по тестированию, который привык обращаться к элементам на странице по их css или xpath селекторам, а затем выполнять с объектом какие-то действия. С Canvas такой подход не работает, ведь DOM элемент один, а объектов в нём — много.
Под катом на примере автоматизации тестирования API Яндекс.Карт я расскажу вам о том, как мы решили эту проблему в Яндексе.
Как сейчас тестируются веб-интерфейсы
Инженер анализирует тестируемый сервис и составляет для его страниц Page Object'ы (по желанию тестировщика это можно делать с помощью библиотеки HtmlElements). Если он хочет красивых отчетов, то может воспользоваться фреймворком Thucydides. Затем в соответствии с имеющимися тестовыми сценариями пишутся автоматические тесты, использующие WebDriver API. То, что получилось, тестировщик запускает на ферме браузеров через Selenium Grid и ищет ошибки, просматривая пришедшие на почту отчёты.
Все просто и красиво, если в тестах не надо взаимодействовать с интерактивной графикой и проверять её. Но что же делать, если надо кликнуть по кругу на карте или перетащить квадрат из одного места в другое? Допустим, мы даже найдём Canvas, но нам нужен конкретный круг. Как кликнуть именно по нему?
Столкнувшись с графикой, мы поняли, что классический подход через Page Object тут не работает. А отказываться от WebDriver не хочется, ведь он дает нам важные бонусы: возможность запускать тесты во всех популярных браузерах или, например, выполнять на странице произвольный код JavaScript (что крайне полезно при тестировании JavaScript API). К тому же, инструмент поддерживается большим сообществом разработчиков.
То есть наш подход должен базироваться на WebDriver, но при этом уметь взаимодействовать со всеми элементами на странице, независимо от того, представлены они в DOM дереве документа или нет. Кроме того, мы должны уметь проверить результат нашего взаимодействия и отловить возможные JavaScript ошибки.
Взаимодействие с элементами на странице
Как я уже сказал выше, тестирование интерфейса мы рассмотрим в контексте API Яндекс.Карт. Поэтому давайте посмотрим, с чем, собственно, нам приходится взаимодействовать в тестах.
Результатом работы API является карта, похожая на слоёный пирог. Самый первый, нижний, слой — схема местности. Над ней — слой графики. Это различные маршруты, линии линейки и даже метки, которые могут быть отображены и с помощью отличных от Canvas DOM элементов. Третьим идёт слой событий, над которым уже расположены всевозможные элементы управления картой (кнопки, выпадающие меню, поля ввода, слайдеры и прочее).
В этом «пироге» нас интересует взаимодействие со слоем графики, так как остальные части интерфейса представлены в виде отдельных DOM элементов и кликнуть по ним с помощью WebDriver не составит труда. Пользователи API Яндекс.Карт могут сказать: «Так у вас же есть API для всех графических объектов, взаимодействуйте с элементами на Canvas через него».
И именно такой подход используется многими инженерами для работы с объектами на Canvas. Но у него есть одна проблема — он далек от реальных действий пользователя. Обычный человек не вызывает в консоли click()
у JavaScript объекта, отвечающего за отображение маршрута на карте. Он просто берет и кликает мышкой в изображение. Работоспособность метода click()
не гарантирует корректность обработки реального клика. Поэтому мы пошли своим, альтернативным путём.
Чтобы понять, как лучше взаимодействовать с Canvas, надо знать как сама программа понимает, по какому объекту кликнул пользователь. В случае с API Яндекс.Карт применяется технология активных областей. Нечто подобное используется везде, где на Canvas есть интерактивные элементы.
Общий алгоритм технологии активных областей:
- Программа хранит в себе информацию обо всех элементах, нарисованных на Canvas, об их пиксельных координатах.
- Она отлавливает события мыши, происходящие над графическими объектами. Это можно делать прямо на Canvas. В нашем же случае над слоем графики есть специальный прозрачный слой событий, накрывающий её всю.
- Координаты события мыши соотносятся с координатами объектов, и если событие произошло над каким-то объектом, то для него вызывается соответствующий обработчик.
Получается, что нам не нужен конкретный графический объект — нужно только подобрать координаты события и кинуть его на Canvas или некий слой событий. Но сделать это не так-то просто. Допустим, нам надо кликнуть в метку. Определить на глаз её координаты — непростая задача.
Если опираться на пиксели, то в случае с объектом Canvas размером 512 на 512 у нас получается 512х512 точек взаимодействия. Многовато. Чтобы облегчить себе жизнь, разделим Canvas на условные квадраты, а для еще большего удобства отобразим их полупрозрачным фоном над Canvas, чтобы инженер по тестированию мог видеть их глазами. Мы выбрали размер стороны квадрата равный 32 пикселям.
Теперь отчетливо видно: для того чтобы кликнуть в метку, необходимо кликнуть в центр квадрата с координатами [11, 11]. Зная размер стороны квадрата, эти координаты легко преобразуются в обычные пиксельные, с которыми и будет вызван клик по Canvas.
x = 11 * 32 + 32 / 2;
y = 11 * 32 + 32 / 2;
click(x, y); // click(368, 368);
Стоит отметить, что данный подход мы используем и для взаимодействия с элементами управления картой, хотя до них можно добраться и через DOM дерево. Сделано это для того, чтобы обращение ко всем элементам на карте было в одном стиле. К сожалению, WebDriver не умеет кидать события в произвольной точке окна, а только на конкретном элементе DOM дерева. Поэтому, прежде чем вызвать событие, определим элемент для взаимодействия. Делается это через метод elementFromPoint(x, y)
объекта document
. Если в этой точке кнопка, то событие бросится на ней, если графика — на Canvas.
Проверка результатов взаимодействия
Когда мы нажимаем кнопку, она меняет свой вид — происходит анимация нажатия. В тесте эту анимацию можно проверить, запросив значения атрибутов DOM элемента, отвечающие за внешний вид кнопки. Появился нужный класс — значит анимация произошла. В случае же с объектами, нарисованными на Canvas, все обстоит несколько иначе. Тут мы уже не можем запросить класс или позицию на странице. Данные атрибуты есть только у самого Canvas, но не у объектов, нарисованных на нем, потому что их нет в DOM-дереве. Так как же проверить, правильного ли цвета у нас линия и изменилось ли положение полигона после того, как мы его перетащили мышкой?
С одной стороны, можно запросить цвет и положение посредством обращения к JavaScript объектам, отображаемым на Canvas. Но, как вы помните, никто не гарантирует, что API нам не врёт. В коде может быть ошибка, и JavaScript нам скажет, что линия красная, а глазами мы будем видеть, что она — синяя.
Но и тут есть выход. Достаточно сравнить внешний вид стабильной версии интерфейса с тестируемой. Иными словами, сравнить снимки двух окон браузера. Мы это делаем следующим образом:
- В один момент времени открываем обе версии интерфейса;
- Обе версии открываются в одной версии браузера, в разных окнах;
- Выполняем в обоих окнах одни и те же действия над интерфейсом;
- В нужный момент делаем снимки окон и сравниваем их попиксельно.
Такой набор действий позволяет нам избавиться от ряда проблем, связанных со сравнением внешнего вида интерфейсов. Во-первых, тесты не зависят от информации, меняющейся со временем. Во-вторых, появляется устойчивость к браузерозависимой вёрстке. И в-третьих, не надо хранить эталонное изображение.
Отслеживание JavaScript ошибок
Последним пунктом нашего подхода к тестированию является отлов JavaScript ошибок. Тут, на первый взгляд, все просто: берём и используем метод onerror объекта window. Все хорошо в теории, но на практике у этого подхода есть одна большая проблема. В случае если ошибка произошла на хосте, отличном от открытого в браузере, нам не удастся прочитать её текст. Что делать?
Есть два варианта:
- добавить в ответ сервера специфический заголовок (работает не во всех браузерах).
- использовать плагин для Firefox, который собирает ошибки на уровне консоли браузера.
Что из этого выбирать, решать только вам. Оба варианта имеют право на жизнь.
Что в итоге?
Как оказалось, задача тестирования веб-интерфейса, работающего с использованием Canvas, решается вполне успешно с помощью обычного WebDriver'а. Но на данный момент мы решили не останавливаться на достигнутом и смотреть в сторону улучшения взаимодействия тестов с интерфейсами. Если сейчас мы бросаем JavaScript события на DOM элементах, то в будущем хотели бы делать это так же, как и пользователь. Мы планируем реальное управление мышкой и клавиатурой. Для этого будет использован awt.Robot. Следите за новостями!
Автор: dolf