Функциональное тестирование Yii приложений с Selenium

selenium

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

Настраиваем Url Manager

Перед тем как начать написание тестов, нужно настроить приложение. Я заметил что на старте чаще всего сталкиваются с проблемой формирования корректных url для того, чтобы тесты работали правильно. Важным здесь является, то что все запросы должны идти через index-test.php. Таким образом мы добиваемся корректной инициализации тестового окружения, вместо config/main.php подгружается config/test.php.

Если вы скрыли index.php в url, то нужно снова включить его отображение для тестового окружения. Для этого добавим в config/test.php в components следующие строки:

'urlManager' => array(
    'urlFormat' => 'path',
    'showScriptName' => true,
),

При этом стоит отметить, что дублировать здесь все маршруты нет необходимости, т.к. yii сам корректным образом объединяет тестовые параметры с "боевыми" и все маршруты описанные в main.php останутся доступными в тестовом окружении.

Для получения корректного uri нужно всегда использовать метод Yii::app()->createUrl(). Важно не забывать генерировать uri через данный метод для задания атрибута action форм, иначе вы рискуете провести много времени в борьбе с призраками в непонимании почему данные сохраняются не в тестовую БД, а в боевую.

В тестах, для того чтобы всегда открывать правильный url, нужно также пользоваться Yii::app()->createUrl(). Например, так: $this->open(Yii::app()->createUrl('/settings'));

Element locators

Практически во всех методах, которые предоставляет selenium api, первым аргументом принимается locator - строка, которая позволяет указать элемент DOM дерева с которым будет работать метод. Рассмотрим виды:

Для того чтобы проверить значение атрибута элемента, нужно добавить к locator название атрибута после знака @, например:

<?php
// ...
$this->assertAttribute('css=.sidebar-fb-list li:eq(0) img@alt', $user->getFullName());

Создание скриншотов

Часто после каких-то значительных изменений падает много тестов, т.к. скорость их выполнения не радует для облегчения отладки можно настроить selenium на создание скриншотов, после того как тест падает. Для этого достаточно добавить следующие строки в метод WebTestCase::setUp():

<?php
// ...
// директория в которую будут сохранятся 
$this->screenshotPath = '/var/www/report/';
// Url, по которому доступна директория указанная в предыдущей строке
$this->screenshotUrl = "http://localhost/report";
// Включаем создание скриншотов
$this->captureScreenshotOnFailure = true;

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

Выполнение javascript

Хотя Selenium и дает казалось бы все нужные средства для работы с браузером, но иногда его возможностей не хватает. В таких случаях можно подключить всю мощь клиентского javascript к нашим тестам. Например, так:

<?php
// ...
$items_count = (int)$this->getEval('selenium.browserbot.getCurrentWindow().jQuery(".item").size()');
$this->assertEquals(Item::PER_PAGE, $items_count);

Избавляемся от появляющихся окон браузера

Для того чтобы избавится от надоедливых всплывающих окон браузера в linux можно использовать утилиту Xvfb. В интернете можно найти много подробных руководств на эту тему.

Ожидание выполнения ajax-запросов

Если ваше приложение использует ajax, значит после инициации запроса, необходимо подождать его завершения, чтобы проверить, например, изменение какого-то блока на странице. Для этого добавим в класс WebTestCase метод:

<?php
// ...
protected function waitForAjax(){
    $this->waitForCondition('selenium.browserbot.getCurrentWindow().jQuery.active == 0');
}

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

<?php
// ...
$this->type('id=field', $value);
$this->click('css=#ajax-form input[type="submit"]');
$this->waitForAjax();
$this->verifyElementPresent('id=result-block ', $value);

Боремся с дублированием кода

Часто будут встречаться повторяющиеся операции, например, аутентификация пользователя. Для того чтобы избежать дублирования, можно просто вынести общие для нескольких тестов алгоритмы в родительский класс (WebTestCase). Наример:

<?php
// ...
protected function login(){
    $this->open(Yii::app()->createUrl('/'));
    $user = $this->users('first_user');
    $this->type('id=f_username', $user->user_name);
    $this->type('id=f_password', '111111');
    $this->submitAndWait('id=login-form');
    return $user;
}

Автодополнение

У меня автодополнение кода для методов selenium api работает не совсем корректно. Присутствуют не все методы, а те что присутствуют с некорректными аргументами. Для того чтобы было удобнее работать я добавил описание наиболее часто используемых мной методов в описание WebTestCase. В будущем планирую описать все методы, которые доступны и выложить здесь в блоге. Вот некоторые методы, может кому-то пригодится:

<?php
// ...
/**
 * @method assertTextPresent($pattern)
 * @method assertTextNotPresent($pattern)
 * @method assertText($locator, $pattern)
 * @method assertNotText($locator, $pattern)
 * @method assertTitle($pattern)
 * @method assertNotTitle($pattern)
 * @method verifyElementPresent($locator)
 * @method verifyElementNotPresent($locator)
 * @method assertVisible($locator)
 * @method assertNotVisible($locator)
 * @method assertAttribute ($attributeLocator, $pattern)
 */

P.S.

Для того чтобы ознакомится со всеми возможностями selenium api, рекомендую прочитать следующий док - selenium reference. Если есть какие-то вопросы-дополнения-etc пишите в комментарии, постараюсь ответить.