Удобные отчёты Selenium

Handy Selenium reports

В этом посте мы публикуем второе выступление Димы Якубовского на конференции SeleniumCamp 2012, проходившей в феврале в Киеве. Слайд следует за текстом. Слово Диме…

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

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

В мануале Selenium написано, что тестировщики, вероятно, рано или поздно приходят к разработке своих собственных отчётов, вместо или в дополнение к тем отчётам, которые предоставляют фреймворки. Хотелось бы узнать, кто из вас использует свои собственные отчёты?

Кто будет пользоваться отчётами? Как выглядит работа с тестами и отчётами в вашей компании?

Например, если имеется CI сервер, и новая сборка продукта проваливает тесты. Кто получает об этом уведомление? Почти наверняка тот, кто закоммитил. Он идёт и смотрит отчёт тестов. Ещё вариант: CI отсутствует, и тесты время от времени запускает тестировщик. Он же и просматривает отчёты. Другие варианты? Бывает ли, что отчёты просматривает кто-то третий – и не программист, и не тестировщик, например, какой-то менеджер?

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

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

Ещё одно требование к отчёту – понятное описание последовательности действий, которые приводят к ошибке. Что это за действия? Например, для проверки логина используются такие простейшие действия:
- открыть страницу
- заполнить два поля
- нажать кнопку
- проверить наличие ссылки “logout“

Как видите, простейшие действия, как правило, соответстуют функциям Selenium.

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

Давайте предположим, что ваш тест в процессе работы должен несколько раз выполнить процедуру логина. Конечно, все эти простейшие действия буду выполняться каждый раз, но обязательно ли все эти действия расписывать в логе? Скорее всего, это нужно только раз – при тестировании самого логина. Для всех остальных случаев имеет смысл объединить эти простейшие действия в действие более высокого уровня – ‘залогиниться’. И показывать в отчётах именно его вместо последовательности всех действий.

Однако можно привести и обратный пример, когда вроде бы простое действие состоит из нескольких. Например, многие, в частности, мы, не используем напрямую функцию click() Selenium, а используем обёртку. Обёртка, на самом деле, выполняет два действия:
- проверить, что есть элемент, на который собираемся нажимать
- нажать

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

Selenium предоставляет функции для получения скриншотов. Хотелось бы, чтобы отчёт содержал и скриншоты. Во многих статьях описывается, как сделать скриншот сразу после возникновения ошибки. Однако зачастую хочется посмотреть скриншот не столько после обнаружения ошибки, сколько до. Однако угадать, случится ли ошибка, конечно, невозможно, и единственный путь, который я вижу – делать скриншоты всё время.

Что вы думаете по этому поводу? Делаете ли вы скриншоты после каждого действия? Попадают ли скриншоты в отчёт?

С самого начала мы решили, что будем использовать TestNG. Решающим стала его способность выполнять тесты параллельно – мы как раз хотели выполнять их параллельно, и об этом я рассказывал в первом докладе. Я начал писать первые тесты, и тут же столкнулся с проблемой. Обычный способ сообщать testng, что тест нашёл ошибку – это использование разнообразных assertXXX. Если условие в assert не выполняется, выбрасывается исключение, и тест считается проваленным. Таким образом, любая проверка для TestNG могла иметь два статуса: фатально и ок. Я не мог сказать testNG: чувак, у меня к этой странице есть замечания, но давай пока сделаем вид, что всё ок и продолжим.

Этот подход был очень непривычным для меня: я был избалован логгерами, которые позволяли мне самому гибко решать, насколько важно то или иное сообщение, которое я хочу записать: TRACE, INFO, ERROR или вообще FATAL.

Возьмём банальный пример: процедуру логина.

Предположим, на странице почему-то отсутствует подпись Remember me. Об этом важно уведомить хозяина? Безусловно. Нужно ли прерывать тест логина (и логаута!), если не имеется этой надписи? Нет, думаю, не нужно. Да что там говорить, даже отсутствие самого чекбокса не остановило бы меня от выполнения теста. Потому что, скажу по секрету, мои тесты не проверяют эту функциональность (а ваши?). Что меня по-настоящему беспокоит, это чтобы после введения логина и пароля я увидел надпись logged in в уголке. И вот если этой надписи нет, то – FATAL! Логин провалился.
Таким образом, я бы хотел, чтобы TestNG показал мне в отчёте следующие ошибки:
WARN – no label
ERROR – no checkbox
FATAL – not logged in

Погуглив, я нашёл некоторые способы приблизить TestNG к такому поведению, но все они показались мне всё-таки не очень удобными.
Я понимаю, что, возможно, хотел от отчётов TestNG слишком многого. Вместо или в дополнение к отчётам TestNG можно было бы использовать расширение LoggingSelenium, но оно имело свои недостатки, как и другие найденные варианты. На всякий случай, оговорюсь, что этот поиск происходил три года назад, но при подготовке этого доклада я поискал ещё раз и не испытал разочарование вида “я изобрёл велосипед”.

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

В итоге я решил обойтись без отчётов TestNG вообще, и стал писать логи тестов в текстовый файл с помощью log4j.

Однако, как вы понимаете, недостатков у текстового лога тоже хватает. Чтобы обнаружить, была ли ошибка, нужно поискать по тексту слово FATAL и ERROR. Как можно упростить этот процесс? Можно настроить log4j на логирование только FATAL и ERROR сообщений. Если этих сообщений не было, лог будет пустым.

Однако при этом подходе мы лишаемся возможности просматривать все остальные сообщения – INFO, WARN – а ведь именно в них хранятся действия, которые нужно совершить, чтобы получить ошибку.

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

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

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

Наш отчёт состоит из таких блоков.

Сводка по тесту.

Управление иерархией.

Подробности о событии.

Просмотр скриншотов.

Управление фильтрами.

Рассмотрим первое требование – разные отчёты для разных людей.

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

Наши тесты генерируют отчёт сразу в конце своей работы, хотя в промежуточный формат (xml) некоторые данные тоже складываются.

Что, собственно, мы покажем в отчёте? Список действий, каждое с какими-то свойствами и результатами.
Логично хранить действия (или “события”) в памяти в виде списка (ArrayList) объектов специального класса Event. Эти объекты будут хранить всю информацию о действии: имя действия, время начала действия, время его завершения, “цель” действия (локатор, например), применённое к цели значение (например, значение, введённое в поле ввода), ожидаемый результат действия, действительный результат, статус (OK, ERROR, FATAL), подробное сообщение об ошибке, сообщение, переданное в исключении (если во время действия было сгенерировано исключение), имена скриншотов до и после действия.

То есть отчёт должен знать о иерархии действия: что вот эти несколько действий являются кусочками действия более высокого уровня. К сожалению, отчёт никак не сможет узнать о зависимостях без нашей помощи. Это значит, что мы каким-то образом должны явно указывать при логировании, какое действие включает в себя другие действия.
Мы использовали такой подход. В начале любого действия открывается событие. Что это значит? Попросту создаётся новый объект типа Событие, добавляется в наш список событий, и в него прописываются поля – как минимум, время начала и имя этого события. Пусть открытое событие будет называться Event1. Готово, действие началось, событие – открылось.
В конце действия, которому событие соответствует, мы событие закрываем. Что это значит? Мы прописываем свойства, которые стали известны в результате действия: статус (OK, WARN), дополнительная информация (прочитанное значение).

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

То есть эта двухступенчатая запись в лог события – сначала открыли, потом закрыли – как раз необходима для того, чтобы определить дочерние события. Кроме того, такой подход позволяет заодно засекать время начала и время конца действия. Если бы вы меня спросили, какой недостаток есть в нашем подходе – я бы вам честно ответил, что именно этот. Логирование несколько усложняется – вместо одной команды записи в лог “Действие провалено” мне нужно записать две команды “Действие началось” и потом – “Действие провалено”. Однако если бы вы меня спросили, сложно ли это и оправдывается ли – я бы вам, опять-таки, честно ответил: нет, это не сложно, да, думаю, оправдывается.

Это значит, что любой вызов метода Selenium должен сопровождаться записью в лог, чтобы мы могли его потом показать в отчёте – подобно тому, как это делается в библиотеке LoggingSelenium.
Только что я сказал, что каждое действие требует двух команд для отображения его в логе. Можно представить себе дикую картину, когда перед вызовом каждой функции селениум в коде я сначала открываю событие, а потом – анализирую результат и закрываю его. Конечно, это слишком сложно и никто из вас так бы не сделал.

Вероятно, как и я, вы написали бы обёртки к функциям Selenium. Итак, я написал отдельный класс, который содержал одноименные методы-обёртки методов Selenium. Класс называется CommonActions. Тесты никогда не обращаются напрямую к методам драйвера, а обращаются только к методам CommonActions.
Что делает обёртка? Кроме работы с событиями обёртка делает ещё несколько вещей, но, чтобы было понятнее, я расскажу о них по очереди.

Вначале – о работе с событиями.
Например, обёртка isTextPresent(some_text).
Обёртка:
- открывает действие. В свойства действия записываются: имя, время начала, цель, на которую направлено действие – some_text
- выполняет команду selenium isTextPresent()
- закрывает действие, записывая в него: время закрытия, результат (найден текст или нет), статус – OK (т.е. действие прошло нормально)
- возвращает результат

Сразу видно, что метод isTextPresent() может выбросить исключение, и тогда мы останемся с пустым неводом в виде открытого события и упавших тестов.
Чтобы избежать этого, совершенно логично окружить команду Selenium try-catch. Если вдруг произойдёт исключение – ничего страшного. В этом случае мы допишем в событие текст исключения, и поставим событию статус ERROR.

Отчёт должен включать скриншоты не только после ошибок, но и до них.

Самое время вспомнить о требовании к отчётам показывать скриншоты не только после ошибки, но и до неё.
В первых версиях наших тестов мы делали cкриншоты сразу после ошибок. Однако ошибки могут возникать на разных уровнях: например, исключение, сгенерированное командой драйвера из-за отсутствия элемента – явная ошибка на этом уровне, однако следующий уровень может посчитать эту ошибку несущественной. А может – существенной. Таким образом, раньше мы делали скриншоты после ошибки на каждом уровне, конечно, получая дубли.

Вернёмся к скриншотам ДО ошибки. Как я уже говорил, раз мы не можем предсказать ошибку, нужно делать скриншоты всегда. Но что значит всегда? Через строку вставлять захват скриншота? Это было бы слишком сложно.

В общем-то, сам собой напрашивается вариант делать скриншоты после выполнения Seenium команд, которые способны как-то изменить внешний вид сраницы. Какие это команды? Ну, самое очевидное – typeText(), click(), ..andWait() методы.

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

Вот как выглядит обёртка к функции click:
- открыли действие (и записали, что скриншот ДО действия – это последний сделаный скриншот)
- проверили существование элемента (обратите внимание, что это – тоже действие!)
- нажали
- сделали скриншот
- закрыли действие (и записали, что скриншот ПОСЛЕ действия – это последний сделаный скриншот, то есть вот этот только что сделанный)
- вернули результат

Следующее требование – хотелось бы иметь возможность генерировать разные виды отчётов для разного использования, как подробные, так и не очень.

Так как мы во время тестирования только собираем информацию, по завершении теста мы свободно можем или создать из неё несколько типов отчётов, или, например, сброить её в xml файл и потом использовать дополнительный генератор или просмотрщик отчётов. Мы пошли несколько другим путём, и решили сразу представить отчёт в виде HTML так, чтобы, с одной стороны, можно было просто посмотреть на самые глобальные вещи – типа наличия ошибок, а с другой – при необходимости залезть в подробности. Также HTML+Javascript легко выполняет последнее требование – отчёт должен позволять фильтровать сообщения по степени важности (вы видите на отчёте соответствующий выпадающий список).

Итак, я хочу подвести итоги. Сложности:

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

- требуется писать некоторое количество дополнительного кода. Впрочем, всё зависит от того, насколько понятным вы хотите получить отчёт и для кого вы его готовите. Я исходил из того, что код тестов я пишу один раз, а отчёты буду просматривать многократно. И обидно будет, если как раз тот отчёт, где проявилась ошибка, не будет обладать всей нужной информацией. То есть для таких отчётов мало кода в обёртках, он должен быть везде.

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

- вы не сможете просто взять, подключить наши библиотеки и получить отчёт. Библиотеками мы с вами с радостью поделимся, но вот чтобы они заработали, вам придётся несколько попотеть над кодом. Хорошая новость – внедрить отчёты на базовом уровне (т.е. перечисление действий Selenium со скриншотами) будет относительно несложно.

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

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

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

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

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

Также можно увидеть время выполнения в каждом из браузеров.

Как это работает? Когда подходит время очередного запуска, для каждого окружения система создаёт свой небольшой грид из виртуальных машин: хаб и произвольное количество RC, на каждый хаб загружаются тесты и выполняются. По завершению результаты скачиваются в свою папку для каждого окружения. Таким образом, получается, что у нас параллельно могут выполняться не просто разные тесты из сьюта, но и один и тот же тест в разных браузерах. Замечу, что в какой-то момент мы поняли, что наша система может быть использована и другими – и потому скоро запустим её в в виде сервиса – www.nerrvana.com. Желающие могут стать бета-тестерами – добро пожаловать!

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

Пример отчётов online. Видео запись выступления можно посмотреть вот тут.



Print this post | Home

4 comments

  1. LeshaL says:

    Пара вопросов:
    1) Чем не понравился EventFiringWebDriver? Там можно повесить слухача на большинство нужных событий драйвера и оттуда же делать скриншоты.
    2) Зачем создавать Event вначале теста и в конце его руками закрывать? Т.е. почему бы не сделать это посредством BeforeTest и AfterTest и это будет происходить автоматически. Или как вариант создавать ивенты руками (если так надо), но используя некоторую фабрику. Все открытые ивенты потом можно закрыть автоматом после того как тест завершился.

  2. bear says:

    С удовольствием отвечу :)
    Во-первых, когда мы столкнулись с проблемой логирования/отчётов (более двух лет назад), EventFiringWebDriver вряд ли существовал.

    Во-вторых, обратите внимание, что мы не просто стремимся залогировать все действия, а представить их в иерархическом виде (возможно, вы не заметили – вот пример отчёта с ошибками, наглядно видна иерархия). Для этого и необходимо “открывать” и “закрывать” действия вручную: автоматически никак невозможно определить, какие действия относятся, скажем, к проверке логина, а какие – нет.
    Тут же и ответ на ваш второй вопрос: BeforeTest/AfterTest – позволят открыть/закрыть только событие (единственное) верхнего уровня, мы же стремимся поместить в отчёт как самые примитивные действия, так и их последовательности в виде действий/событий более высокого уровня.

  3. LeshaL says:

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

  4. xwizard says:

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