Юнит-тесты уровня браузера на связке Selenium + PHP.
Обычно у проекта есть ряд важных тонких мест, которые просто обязаны быть покрыты юнит-тестированием.
Selenium предоставляет уникальную возможность проводить тестирование "от лица пользователя", на уровне операций браузера.
С помощью Selenium можно покрыть кросс-браузерными тестами сложный javascript-интерфейс.
А если подключить еще и серверный язык, например, PHP, то можно полностью протестировать цикл восстановления потерянного пароля - от клика посетителя на "забыл пароль" - до получения письма и входа на сайт.
Selenium - это java-программа, которая умеет запускать браузер и делать в нем различные действия типа клика на кнопку, поиска элемента, ожидания загрузки страницы.
Selenium - это HTTP-сервер, написанный на java (на основе Jetty).
Он принимает команды в простом текстовом формате. Причем, можно как набирать команды в "серверной консоли", так и посылать их, присоединившись к порту 4444.
Интеграция с языками программирования - это классы, которые предоставляют методы для удобной посылки команд серверу.
Например, вызов метода open("http://javascript.ru") посылает селениум-серверу на порт 4444 команду вида cmd=open&1=http://javascript.ru , а селениум-сервер, в свою очередь, отправит ее на исполнение в браузер.
При операциях с селениум-сервером сначала открывается сессия, которая затем используется при последующих запросах. Классы, работающие с селениумом, при трансляции вызова метода в запрос к селениум-серверу каждый раз добавляют идентификатор текущей сессии.
При работе с сервером напрямую - сессию надо добавлять к каждой команде самостоятельно.
Для запуска автоматического тестирования Selenium нам понадобятся:
- Java 1.5+
- PHPUnit и Testing_Selenium из PEAR:
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: качайте последнюю версию с http://selenium-rc.openqa.org/download.html
В архиве 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
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
- кликнуть на кнопку "поиск" (ее id=btnG)
cmd=click&1=btnG&sessionId=12345
- проверить при помощи XPath, есть ли (isElementPresent) ссылки со словом Selenium
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 это поправят.
- Основной сайт Selenium RC: http://selenium-rc.openqa.org/.
Обратите внимание на секцию Tutorial.
- В вики, куда постепенно мигрирует документация, находится FAQ.
- Дока по командам selenium и по локаторам элементов: Selenium Core Reference
- Много полезных расширений и дополнительных команд для селениум. Must Read: Contributed User-Extensions
Рецептами решения глюков щедро поделится google и сайт поддержки OpenQA.
Selenium - одна из немногих платформ, которые позволяют сделать интеграционные юнит тесты не на уровне кода или базы, а полностью - работает ли сайт.
- Пожалуй, единственное средство для удобной автоматизированной эмуляции действий посетителя
- Кросс-браузерное
- Есть способы интеграции с множеством языков и систем тестирования. Для PHP - это PHPUnit.
- Довольно глючная вещь. Заранее готовьтесь к борьбе с непонятками.
- Почти не умеет работать с поддоменами. Редирект посетителя на другой домен обычно ломает тест.
P.S В этой статье нет ни слова о Selenium IDE. Это не потому что оно того не заслуживает. Наоборот - Selenium IDE требует отдельной хорошей статьи.
Успешного автоматизированного тестирования!
|
Авторизация - один из самых критичных сервисов сайта. Будем тестировать авторизацию на сервере http://mail.ru. отлично.
Selenium не всегда дружит с jquery - были великие траблы с jquery.ui.dialog и валидацией... Особенно убило, что он никак не хочет ждать, пока появится элемент (waitForPageToLoad не рулит, ибо страница загружена, а он каким-то магическим образом смотрит на дом-деревце документа). Пока решено использовать sleep в тестах, но это проблемы не решает - ибо если брать с запасом - время теста ахуенно (пардон за мой французский) разрастается (тест 2-х фич - авторизации и регистрации проходит около минуты). Что будет при наращивании функционала - я не знаю. Может - будет написан скрипт найтли билда. Посмотрим
просто слип надо обернуть циклом и делать sleep(500)
потом selenium.getHtmlSource()
и выходить как только появится
не слышал про селениеум ничего. просветили.
конечно не просто, по помогли разобраться
Здравствуйте, не понял где нужно набирать следующее
В директории с файлом GoogleTest.php (добавил об этом в статью)
А как можно запустить selenium на другом порту с использованием phpunit?
Напишите подробую статью о логирование в Селениум, т.к. тесты без логирования как то не очень (((
Логирование осуществляет оболочка, запустившая селениум, любым избранным вами способом.
В примере указывается , что можно определить появился ли объетк или нет, а можно определить при помощи xpath локатора, появился ли класс?
Спасибо за статью, но не подскажите, как вызвать определенную функцию (например только логин в вашем примере) или запустить все три теста в определенной последовательности и например с передачей параметров с одного теста другому
Этим занимается внешняя обертка. В статье это PHPUnit + модуль для Selenium.
А не подскажете как можно научить селениум запоминать сессию?
Т.е. я делаю login, проверяю страницу, затем запускаю другой тестовый метод, который должен проверять другую страницу и selenium ломается(типа: element not found) т.е. я так понял в рамках одной сессии выполнился метод,а другой метод(хотя там не было ни log out, ни login) не может выполниться....
что можно сделать?
При первом запросе - получаете session id в селениум, а далее - при каждом новом запросе - передавайте selenium текущий идентификатор, как описано в статье.
Тогда действия будут выполняться в одной сессии браузера и с одними куками..
Есть небольшой вопрос. Как можно заставить selenium выбрать значение из выпадающего списка.Скрипт падает на моменте выбора значения
# [info] Executing: |select | Q_QuestionnaireQuestionNo2389_1 | label=Мужской |
# [error] Option with label 'Мужской' not found
помогите разобраться плз)))
Selenium IDE
там есть кнопочка Find
я обычно ей пользуюсь
тогда если элемент есть на форме он моргает зеленым ободком
в твоем случае судя по всему "2389_1" - уникальное рандомное значение
я пользуюсь xpath локаторам потому, что они боле-менее универсальны
есть такая клевая функция contains()
попробуй заюзать ее в xpath
незаускайте selenium-server.jar находящийся по адрему в котором присутсует восклицательный знак!!!
один из каталогов начинался с восклицательного знака - сервер загружался но интерективно ыессия не запускалась
один из каталогов заканчивался восклицательным знаком - сервер незагружался вообще
целый день убил на это
так это любой jar в такой папке не запуститься) старая бага
getHtmlSource не всегда возвращает адекватный HTML
в результате assertRegExp выбрасывает ассерт
в браузере в этот момент все правильно и адекватно отображено
че делать ?
Спасибо за статью, но не подскажите как можно отдельно вызвать определённую функцию?
как обработать недействительный сертификат?
если запускать с дефолтным firefox, то надо вручную добавлять исключение
если делаю новый профиль firefox (firefox -P) и затем указываю его "java -jar selenium-server.jar -firefoxProfileTemplate /home/****/.mozilla/firefox/7re9l8yb.selenium"
то при запуске phpunit - firefox открывает http://localhost:4444/selenium-server/core/Blank.html?start=true
Расскажите, пожалуйста, подробнее как запустить GoogleTest.php (к примеру).Читал много про PHPUnit и Pear,не могу только собрать все в кучу!!Помогите с этим разобраться.
Спасибо за Статейкуё!
Спасибо за статью
Есть вопрос :
Прохожу руками по сайту - редирект происходит нормально
Прохожу селениумом - на одном этапе постоянно "ломается" url - часть параметров запроса становится не после ?, а после смещается сразу после .html
Как бороться?
Добротно написано. Спасибо за статью.
может все таки Функциональные-тесты уровня браузера на связке Selenium + PHP. А не Юнит, м?
Отличная статья,полезная.
У меня вопрос,а если элемент не имеет ID, как к нему обратиться?
Например, имеются кнопки:
Как написать правило для выбора нескольких из них? у них же нет id/
//button[contains(text(),'У')]
интерестный обзор. новичкам генерить код я бы советовал в selenium IDE и можно сразу получить тест готовый для употребления ... да только вот в последних версиях IDE почемуто подумали что мало людей тестируют веб приложения с помощью основного языка веб програмирования и не сделали поддержку генерации php (phpunit) кода...
Огромное спасибо за статью. ТС супер
Итак, проверив что Selenium-сервер работает, запускаем тест из директории с файлом GoogleTest.php :
01 C:\...>phpunit GoogleTest.php
а можно подробней как запускать тест?
что прописывается после С:\....???????????
и второе, запускаем из директории -это значит открыли ту папку, где у нас хранится файл проекта GoogleTest.php и в консоле пишу C:\...>phpunit GoogleTest.php
Последний комментарий в точку...
Где должен находиться тест GoogleTest.php и что вместо многоточий после "С:\"?
Создала тест - мучала IDEA и java. все ок, все работает, но в конце сеста у меня открывается страница где просто текстом описывается успешность работы, без айди и css. Не могу разобраться, как сделать поиск на текст в окне браузера xPath-ом или есть функция поиска именно текста? зарание спасибо.
C:\...>phpunit GoogleTest.php - епт народ... ну че за детский сад... куда положили файлик с тестами, оттуда и запускайте его, хоть с диска Z:\some\path\name\phpunit YourTest.php
Ребята а подскажите как эмулировать движения мышкой?
basketball legends: А сессия была запущена на google.com. Поэтому, следуя политике безопасности Same Origin, браузер показал селениуму фигу.