Обычно у проекта есть ряд важных тонких мест, которые просто обязаны быть покрыты юнит-тестированием.
Selenium предоставляет уникальную возможность проводить тестирование "от лица пользователя", на уровне операций браузера.
С помощью Selenium можно покрыть кросс-браузерными тестами сложный javascript-интерфейс.
А если подключить еще и серверный язык, например, PHP, то можно полностью протестировать цикл восстановления потерянного пароля - от клика посетителя на "забыл пароль" - до получения письма и входа на сайт.
Selenium - это java-программа, которая умеет запускать браузер и делать в нем различные действия типа клика на кнопку, поиска элемента, ожидания загрузки страницы.
Selenium - это HTTP-сервер, написанный на java (на основе Jetty).
Он принимает команды в простом текстовом формате. Причем, можно как набирать команды в "серверной консоли", так и посылать их, присоединившись к порту 4444.
Интеграция с языками программирования - это классы, которые предоставляют методы для удобной посылки команд серверу.
Например, вызов метода open("http://javascript.ru") посылает селениум-серверу на порт 4444 команду вида cmd=open&1=http://javascript.ru, а селениум-сервер, в свою очередь, отправит ее на исполнение в браузер.
При операциях с селениум-сервером сначала открывается сессия, которая затем используется при последующих запросах. Классы, работающие с селениумом, при трансляции вызова метода в запрос к селениум-серверу каждый раз добавляют идентификатор текущей сессии.
При работе с сервером напрямую - сессию надо добавлять к каждой команде самостоятельно.
Для запуска автоматического тестирования Selenium нам понадобятся:
pear channel-discover pear.phpunit.de pear install channel://pear.phpunit.de/PHPUnit # на момент написания статьи версия 0.4.3 последняя бета pear install channel://pear.php.net/Testing_Selenium-0.4.3
В архиве selenium-remote-control содержатся API для разных языков программирования и сервер selenium-server.
Мы стартуем сервер в интерактивном (ключ -interactive) режиме, который позволяет запускать команды непосредственно из консоли.
# В каталоге с selenium-server запускаем # java -jar selenium-server.jar -interactive # предполагатся, что java - на пути PATH C:\...\selenium-server-1.0-beta-1>java -jar selenium-server.jar -interactive 14:23:08.312 INFO - Java: Sun Microsystems Inc. 10.0-b22 14:23:08.312 INFO - OS: Windows XP 5.1 x86 14:23:08.312 INFO - v1.0-beta-1 [2201], with Core v1.0-beta-1 [1994] 14:23:08.390 INFO - Version Jetty/5.1.x 14:23:08.406 INFO - Started HttpContext[/selenium-server/driver,/selenium-server/driver] 14:23:08.406 INFO - Started HttpContext[/selenium-server,/selenium-server] 14:23:08.406 INFO - Started HttpContext[/,/] 14:23:08.406 INFO - Started SocketListener on 0.0.0.0:4444 14:23:08.406 INFO - Started org.mortbay.jetty.Server@201f9 Entering interactive mode... type Selenium commands here (e.g: cmd=open&1=http://www.yahoo.com)
Итак, селениум-сервер запустился и слушает порт 4444. Последняя строка демонстрирует пример команды.
Общий вид команд: cmd=(ИМЯ)&1=(Параметр1)&2=(Параметр2)...&sessionId=(СЕССИЯ)
Опция -interactive разрешает серверу принимать команды из консоли.
Поэтому можно тут же, из консоли, проверить, работает ли селениум - открыть http://www.google.com браузером Internet Explorer.
Для начала работы с селениум нужно открыть новую сессию. В сессии указывается тип браузера (*iexplore, *firefox, *opera и т.п.) и урл, с которого этот браузер начнет работу.
Будем тестировать в Internet Explorer, начнем работу с google.com.
Для этого введем команду getNewBrowserSession с аргументами *iexplore и http://www.google.com:
cmd=getNewBrowserSession&1=*iexplore&2=http://www.google.com
Откроется Internet Explorer с длинным URL вида http://www.google.com/selenium-server/core/...
cmd=getNewBrowserSession&1=*iexplore&2=http://www.google.com 14:23:22.921 INFO - ---> Requesting http://localhost:4444/selenium-server/driver?cmd=getNewBrowserSession&1=*iexplore&2=http://www.google.com 14:23:23.000 INFO - Checking Resource aliases 14:23:23.000 INFO - Command request: getNewBrowserSession[*iexplore, http://www.google.com] on session null 14:23:23.000 INFO - creating new remote session 14:23:23.343 INFO - Allocated session 42eb52b4dcfb453ab6938b4be8736b2b for http://www.google.com, launching... 14:23:23.343 INFO - Backing up registry settings... 14:23:24.250 INFO - Modifying registry settings... 14:23:24.640 INFO - Launching Internet Explorer... 14:23:27.421 INFO - Got result: OK,42eb52b4dcfb453ab6938b4be8736b2b on session 42eb52b4dcfb453ab6938b4be8736b2b
Вывод селениума сообщил, что создана сессия "Allocated session 42eb52b4dcfb453ab6938b4be8736b2b", и команда открытия успешно выполнена: "Got result: OK"
Все дальнейшие операции в этой сессии должны происходить в рамках исходного домена http://www.google.com.
Есть способы обойти это ограничение, запустив Selenium в привилегированном режиме: *iehta вместо *iexplore, или воспользовавшись другим способом, описанным в http://selenium-rc.openqa.org/experimental.html.
Однако, достаточно стабильную и безглючную работу Selenium мне удалось получить только в рамках одного домена.
Selenium работает исключительно на уровне javascript, без привязки к API, DLL и прочим внутренностям браузера.
Запуская браузер командой cmd=getNewBrowserSession&1=*iexplore&2=http://www.google.com, Selenium ставит себя (localhost:4444) в настройках прокси. Собственно, эта настройка - и есть всё отличие в поведении браузера, запущенного через Селениум.
Селениум-сервер, работая как прокси, перехватывает все URL, которые начинаются с /selenium-server/ (в рамках исходного домена) и отдает свои страницы.
Таким образом, селениум-сервер может запустить в браузере любой яваскрипт-код, который будет работать на том же домене, и поэтому имеет полноценный доступ к кукам, содержимому страницы и т.п.
На страничке, которая открылась в браузере, есть длинный идентификатор: 42eb52b4dcfb453ab6938b4be8736b2b - это сессия. Все дальнейшие команды, которые вы отправите селениум-серверу с этой сессией, будут выполнены в этом браузере. При этом неважно откуда они пришли: по порту 4444 или вручную из консоли.
Для перехода на URL служит команда open. Не забываем указать сессию:
cmd=open&1=http://www.google.com&sessionId=42eb52b4dcfb453ab6938b4be8736b2b
Google открылся. С виду все хорошо. Но глянем на консоль:
cmd=open&1=http://www.google.com&sessionId=42eb52b4dcfb453ab6938b4be8736b2b 14:37:32.843 INFO - ---> Requesting http://localhost:4444/selenium-server/driver?cmd=open&1=http://www.google.com&sessionId=42eb52b4dcfb453ab6938b4be8736b2b 14:37:32.859 INFO - Command request: open[http://www.google.com, ] on session 42eb52b4dcfb453ab6938b4be8736b2b 14:37:38.078 INFO - Got result: Разрешение отклонено on session 42eb52b4dcfb453ab6938b4be8736b2b
Последняя строчка (на виндах она может быть в кривой кодировке) означает, что мы наступили на грабли. Дальнейшие команды с сайтом работать не будут.
Когда-то я потратил небольшое энное количество времени в поисках - что не так и почему оно не пашет.
Разгадка оказалось простой. Google самостоятельно перенаправил браузер с http://www.google.com на http://www.google.ru. А сессия была запущена на google.com. Поэтому, следуя политике безопасности Same Origin, браузер показал селениуму фигу.
Чтобы такого не было, следует с самого начала выбрать нужный домен правильно. В нашем случае правильный выбор - www.google.ru. И в дальнейшем избегать кросс-доменных редиректов.
Для тестирования поисковика Google мы используем новые команды Selenium. Их список и описание которых можно найти в документации.
Алгоритм теста поисковика Google:
cmd=getNewBrowserSession&1=*iexplore&2=http://www.google.ru
Selenium выдаст сессию. Для краткости, обозначим ее 12345.
cmd=open&1=http://www.google.ru/&sessionId=12345
q строкой поиска:cmd=type&1=q&2=selenium&sessionId=12345
cmd=click&1=btnG&sessionId=12345
cmd=isElementPresent&1=//a[contains(text(),"Selenium")] ... Got result: OK,true on session 12345
да, такие ссылки есть
cmd=testComplete&sessionId=12345
При таком завершении селениум сам закроет браузер и аккуратно удалит все временные файлы.
Предыдущая секция была необходима, чтобы понять "что у нее внутре".
Но в реальной жизни в консоли только отлаживают, а тесты пишут.. Например, на PHPUnit.
Пример такого теста есть в архиве селениума в каталоге selenium-php-client-driver. Например, GoogleTest.php. Но версия из архива на русском google работать не будет, поэтому вот модифицированный вариант:
<?php
// GoogleTest.php
// должны быть установлены PEAR-пакеты
// сам PEAR должен быть в include_path
require_once 'Testing/Selenium.php';
require_once 'PHPUnit/Framework/TestCase.php';
class GoogleTest extends PHPUnit_Framework_TestCase
{
private $selenium;
public function setUp()
{
$this->selenium = new Testing_Selenium("*iexplore", "http://www.google.ru");
$this->selenium->start();
}
public function tearDown()
{
$this->selenium->stop();
}
public function testGoogle()
{
$this->selenium->open("/");
$this->selenium->type("q", "hello world");
$this->selenium->click("btnG");
$this->selenium->waitForPageToLoad(10000);
// русский текст в кодировке UTF-8 !
$this->assertRegExp("/Поиск в Google/", $this->selenium->getTitle());
}
}
Итак, проверив что Selenium-сервер работает, запускаем тест из директории с файлом GoogleTest.php :
C:\...>phpunit GoogleTest.php PHPUnit 3.2.21 by Sebastian Bergmann. . Time: 7 seconds OK (1 test) C:\...>
В классе была всего одна функция, имя которой начинается на test.., поэтому тест один.
Если что-то не работает, то подробный лог будет в консоли selenium-сервера.
Авторизация - один из самых критичных сервисов сайта. Будем тестировать авторизацию на сервере http://mail.ru.
Селениум будет самостоятельно открывать сайт, заполнять окошки с логином-паролем, самостоятельно заходить на сайт и выходить из него.
Схема теста по шагам:
Заметим, что mail.ru редиректит на домен win.mail.ru. Чтобы тестирование работало - нужно сразу зайти на win.mail.ru, аналогично тесту для Google.
Код файла MailTest.php:
<?php
// MailTest.php
require_once 'Testing/Selenium.php';
require_once 'PHPUnit/Framework/TestCase.php';
class MailTest extends PHPUnit_Framework_TestCase
{
protected $selenium;
// XPATH-локатор для кнопки "Войти"
protected $enterLocator = "//input[@type='submit' and @value='Войти']";
// XPATH-локатор для кнопки "Выйти"
protected $exitLocator = "//input[@type='submit' and @value=' Выход ']";
/*
* инициализация теста
*/
public function setUp()
{
// Если браузера нет на пути PATH, нужно указать полный путь
$opera = "*opera C:\Program Files\Opera 9\opera.exe";
$ie = "*iexplore";
// в процессе авторизации сервер mail.ru перенаправляет на домен win.mail.ru
// чтобы тест работал корректно, нужно сразу зайти на win.mail.ru.
$this->selenium = new Testing_Selenium($ie, "http://win.mail.ru");
$this->selenium->start();
// таймаут по умолчанию 30 секунд.
// поставим 600 сек, т.к команда open ждет, пока браузер загрузит картинки
$this->selenium->setTimeout(600000);
}
/*
* тест авторизации
*/
public function testMail() {
$this->selenium->open("/");
// команда open выполняется синхронно, ожидая полной загрузки страницы
// если браузер уже залогинен (например, режим "запомнить меня")
if ($this->selenium->isElementPresent($this->exitLocator)) {
// выйти
$this->logout();
}
$this->login();
$this->logout();
}
/*
* Выйти из сайта
*/
public function logout() {
// нажать на кнопку "выход"
$this->selenium->click($this->exitLocator);
// команда click, как и почти все команды, выполняется асинхронно.
// надо подождать загрузки страницы, ждем 600 сек максимум
$this->selenium->waitForPageToLoad(600000);
// проверить, что появилась кнопка "войти"
$this->assertTrue($this->selenium->isElementPresent($this->enterLocator));
}
/*
* Войти в сайт
*/
public function login()
{
$this->selenium->type("Login", 'selenium_test');
$this->selenium->type("Password", '123456');
$this->selenium->click($this->enterLocator);
$this->selenium->waitForPageToLoad(10000);
// проверить, что появилась кнопка "выйти"
$this->assertTrue($this->selenium->isElementPresent($this->exitLocator));
}
/*
* Завершение теста
*/
public function tearDown()
{
$this->selenium->stop();
}
}
Если что-то по тесту вдруг неочевидно - задайте Ваш вопрос в комментариях, я дополню описание.
При практической работе с Selenium Вы столкнетесь с большим количеством фич и багов. Не пугайтесь. Вы не один такой. Вот некоторые из них.
Для работы с Firefox 3 на момент написания статьи придется скачать последний снапшот Selenium RC, т.к версия 1.0-beta1 его запускать не умеет.
Впрочем, с последним снапшотом хватает других глюков.
Альтернативный вариант - запускать браузер с нужным профилем и прокси, используя тип *custom.
Кроме того, некорректно завершенные (например, по ctrl-c) сессии Firefox оставляют во временной директории профили вида custom*. Их можно убивать. Иногда селениум ругается, что там какой-то лок-файл и запустить Firefox нельзя. Тогда все эти профили надо обязательно убить.
По умолчанию Selenium не показывает окошки подтверждения confirm и автоматом жмет на них OK.
Есть методы, которые меняют это поведение.
В любом случае, нужно обязательно вызвать метод getConfirmation сразу после появления подтверждения.
Иначе последующие команды selenium'а не будут выполнены браузером.
Чтобы протестировать загрузку файла - нужно обойти ограничение безопасности в Javascript. По умолчанию javascript не может менять значение <input type="file">.
В Firefox можно дать Selenium привилегии на загрузку файла, добавив вызов:
netscape.security.PrivilegeManager.enablePrivilege("UniversalFileRead")
в файл selenium-api.js в начало функции Selenium.prototype.doType.
Кроме того, чтобы запрос привилегии сработал в "неподписанном" скрипте - нужно поставить в Firefox настройку "signed.applets.codebase_principal_support" в значение "true", например, найдя ее на страничке about:config.
И тогда загрузки будут работать.
Альтернативный выход - запустить браузер в экспериментальном привилегированном режиме (chrome/iehta/...) или через Proxy Injector. Но тогда готовьтесь к дополнительному набору глюков.
Также по теме: Testing File Uploads with Selenium RC and Firefox.
В документации по селениум - изрядный бардак. Возможно, к выходу 1.0 это поправят.
Рецептами решения глюков щедро поделится google и сайт поддержки OpenQA.
Selenium - одна из немногих платформ, которые позволяют сделать интеграционные юнит тесты не на уровне кода или базы, а полностью - работает ли сайт.
P.S В этой статье нет ни слова о Selenium IDE. Это не потому что оно того не заслуживает. Наоборот - Selenium IDE требует отдельной хорошей статьи.
Успешного автоматизированного тестирования!