14 марта 2014 г.

Установка и настройка Jenkins + Vagrant + Selenium с нуля на Linux (Debian)

В предыдущих постах я описал свой переход от флагово-хукового тестирования к Jenkins под Windows и последующим развертыванием виртуальной машины с WinXP для Selenium-тестов. История получилась в 4х частях, многое не нужно, и от этого её трудно использовать.
В этом посте я постараюсь кратко, но целостно описать процесс развертывания системы Selenium-тестирования, запускаемого из Jenkins с использованием универсального средства переносимых виртуальных машин Vagrant с провайдером VirtualBox и образом Ubuntu desktop для запуска тестов в Chrome и Firefox. Так как целью является именно целостность статьи, я повторю некоторые описанные ранее шаги, например, запуск Selenium хаба и нодов к нему.
В результате мы получим headless сервер для автозапуска Selenium-тестов веб-приложения.

Начальное состояние

Веб-сервер под управлением Debian 7 / Ubuntu с установленным и настроенным php и виртуальными хостами и установленной java (достаточно jre). Так как мы строим систему для запуска автотестов некоторого веб-приложения, то это веб-приложение должно быть настроено и работать на этом сервере. Кроме того, предполагаем, что мы используем систему контроля версий, и у нас есть скрипт, которым можно обновить (задеплоить) это веб-приложения в этой среде в автоматическом режиме. К этому моменту должен быть написан хотя бы один Selenium-тест этого приложения, использующий класс PHPUnit_Extensions_Selenium2TestCase. Примеры тестов можно найти по ссылке.

Selenium hub

Selenium хаб - это сервер, который принимает команды от phpunit и перенаправляет их Selenium-нодам для выполнения в браузере. К хабу можно одновременно подключать множество нод. Ноды могут быть запущены как на самом сервере (безоконные, например, с phantomjs), либо на удаленных машинах, либо на виртуальных машинах.
Хабы и ноды представлены одним приложением, которое запускается с разными параметрами. Оно называется Selenium Server, скачать можно на официальном сайте.
Хаб запускается с помощью параметров:
java -jar selenium-server-standalone-2.38.0.jar -role hub -port 4444

Обратите внимание, здесь указан стандартный порт 4444, его можно изменить. Адрес и порт Selenium-хаба должен быть указан в ваших Selenium-тестах в методе setUp():
class MyTestCase extends PHPUnit_Extensions_Selenium2TestCase { 

    public function setUp() {
        $this->setHost(127.0.0.1);
        $this->setPort(4444);

    }

/* ... */

Selenium node

Selenium node - сервер, который подключается к Selenium-хабу. Запускается обычно на десктопной версии ОС, так как передает команды тестирования браузерам. Для начала для проверки запустим на любой десктопной версии ОС, которая имеет доступ к хабу и к которой хаб имеет доступ.
java -jar selenium-server-standalone-2.38.0.jar -role node
Конфигурацию ноды можно задавать как через аргументы, так и указанием пути до файла с конфигураций. Например
java -jar selenium-server-standalone-2.38.0.jar -role node -nodeConfig example.json
Где example.json может содержать:
{
   "capabilities":
        [
                {
                        "browserName":"firefox",
                        "platform":"LINUX",
                        "maxInstances":1
                },
                {
                        "browserName":"chrome",
                        "platform":"LINUX",
                        "maxInstances":1
                }
        ],
  "configuration":
  {
    "maxSession": 5,
    "host": "localhost",
    "port": 5556,
    "register": true,
    "registerCycle": 5000,
    "hubPort": 4444,
    "hubHost": "127.0.0.1"
  }
}

Этим файлом мы говорим, что данная нода может проводить тесты на браузерах с идентификаторами(browserName) firefox и chrome, при этом одновременно запускать только один экземпляр каждого браузера (maxInstances). Подключение к хабу указывается через параметры hubHost и hubPort. Важными параметрами здесь являются "host": "localhost", "port": 5556 - это адрес, по которому сам хаб сможет подключатся к этой ноде. Если этих параметров не будет, то нода передаст значения, которые сама найдет в сетевых интерфейсах, но в случае с виртуальными машинами такое может не работать. Указав localhost и порт 5556 мы можем направлять подключение, используя перенаправление портов с host-машины на гостевую.
Для тестирования в chrome требуется специальный WebDriver, скачать его можно здесь. Но при запуске ноды нужно указать путь до его исполняемого файла. Это можно сделать, добавив значение -Dwebdriver.chrome.driver=chromedriver.exe в строку запуска:
java -jar selenium-server-standalone-2.38.0.jar -role node -nodeConfig example.json -Dwebdriver.chrome.driver=chromedriver.exe


PHPUnit

Версия, которая идёт в пакетах Debian слишком устарела, поэтому следует скачать последнюю стабильную версию с официального сайта. Скачивается php-пакет phpunit.phar. Скачаем и пропишем его в систему:
wget https://phar.phpunit.de/phpunit.phar
sudo chmod +x phpunit.phar
sudo mv phpunit.phar /usr/local/bin/phpunit

Проверка теста + phpunit + selenium hub + selenium node

Убедитесь, что всё настроено и запущено, в настройках Selenium-ноды указана поддержка "firefox", и сам Firefox установлен и запускается. В тестах в методе setUp() кроме адреса Selenium-хаба должны быть прописаны браузер и адрес тестируемого хоста:
$this->setBrowser('firefox');
$this->setBrowserUrl('http://my-web-app-testing.com');

Если всё верно, то пробуем запустить тестирование с помощью команды:
phpunit my-test-case.php
Здесь после phpunit идёт имя вашего файла с Test Case.
Если всё было сделано верно, то начнется процесс тестирования:
  1. PHPUnit выполняет метод setUp вашего Test Case.
  2. PHPUnit ищет метод с именем test*()
  3. PHPUnit (Selenium extension) передаёт команды Selenium-hub.
  4. Selenium-hub при первой команде ищет подключенные к нему ноды со свободным инстансом указанного браузера (firefox), если такое находит, то передаёт ноде команду для старта браузера (сессии тестирования).
  5. Selenium-node получает команды от хаба и передаёт их браузеру по протоколу WebDriver. Реализация этого взаимодействия зависит от браузера. C Firefox Selenium связывается встроенными средствами, с Chrome ему нужна отдельная утилита chromewebdriver.
  6. Браузер возвращает ноде информацию об успешности выполнения операции, нода передает данные хабу, хаб оповещает phpunit, phpunit выводит результат.

VirtualBox

Мы будем использовать VirtualBox для запуска виртуальной машины, на которой установлена десктопная ОС с Selenium-node и браузерами. Именно в ней мы будем проводить Selenium-тесты. VirtualBox можно устанавливать на серверные ОС, при этом десктопные виртуальные машины будут нормально функционировать. Устанавливать VirtualBox на Debian рекомендую по инструкции с их сайта. Перескажу её для Debian 7 (wheezy):
В файл /etc/apt/sources.list добавляем строчку (под рутом):
deb http://download.virtualbox.org/virtualbox/debian wheezy contrib
Затем установливаем ключ:
wget -q http://download.virtualbox.org/virtualbox/debian/oracle_vbox.asc -O- | sudo apt-key add -
Затем обновляем список пакетов и устанавливаем последнюю версию VirtualBox (на момент написания поста 4.3):
sudo apt-get update
sudo apt-get install virtualbox-4.3

Проверить работу можно командной
VBoxManage --version
Если ничего красным текстом не появилось, то VirtualBox установился нормально. Если же возникли проблемы, то следует ознакомится с оригинальной инструкцией по ссылке выше.

Vagrant

Vagrant позволяет управлять виртуальной машиной в удобной форме. Работает с образами виртуальных машин, запакованных в специальный формат, который называется box. Преимущество этого в том, что можно создать и настроить свою виртуальную машину, запаковать её в box, а потом разворачивать на любых конфигурациях, где можно запустить Vagrant. Vagrant позволяет использовать разные провайдеры виртуальных машин (Vmware и прочие), но удобнее всего получается с VirtualBox (для этого мы его и устанавливали).
Другим преимуществом является создание специального конфигурационного файла, где можно указать пробрасываемые порты. Например, можно пробросить порт для ssh, чтобы можно было подключаться к виртуальной машине из вне. Мы же проброс будем использовать еще и для Selenium-node.

Vagrant box

Есть множество общедоступных боксов с Ubuntu и другими ОС с установленными гостевыми дополнениями и прочими подготовками для Vagrant, но в нашем случае удобнее будет создать свой бокс с десктопной ОС, установленными браузерами и Selenium-нодой в автозагрузке.

Для начала нам нужно установить и запустить VirtualBox где-нибудь на десктопной системе, чтобы работать с ней в графическом режиме. Нам нужно создать новую виртуальную машину. С этим не должно возникнуть трудностей. Скачиваем образ чистой Desktop Ubuntu любой вашей любимой версии, затем устанавливаем её на созданную виртуальную машину. При установке создаём административную учётную запись с логином и паролем vagrant. После установки ОС, устанавливаем необходимые пакеты и приложения: java, firefox, chrome, selenium-server.jar, ssh-server. Старт Selenium-node прописываем в автозагрузку, но после входа пользователя, чтобы графическая часть к этому времени была загружена. Кроме того, обратным адресом ноды мы должны указать адрес и порт, которые будут доступны из хаба, иначе работы не получится. Нам нужно будет пробросить порт, например, 5556 в виртуальную машину. Так как Selenium-hub у нас на host-машине, в которой будем запускать виртуальную машину, то address = localhost,  порт = 5556.
Рекомендую установить еще VNC-сервер, чтобы потом можно было управлять ОС в графическом режиме, например, простой x11vnc.
Далее нужно подготовить ОС для работы с Vagrant, для этого нужно выполнить некоторые шаги из этого руководства. Установку vim-редактора и puppet можно пропустить, а остальное нужно сделать. После установки всего необходимого стоит перезагрузиться и убедиться, что всё запустилось как надо. Затем нужно выключить виртуальную машину. После этого можно паковать её в бокс. Для запаковки нужен установленный Vagrant (после создания бокса VirtualBox и Vagrant нужны будут только на сервере). Бокс пакуется командой:
vagrant package --base ubuntu
Последнее в команде - это имя виртуальной машины как в меню VirtualBox.
После завершения операции должен появится файл package.box, переименуйте его в ubuntu.box и перенесите на сервер.

Вместо создания своего бокса можете попробовать использовать мой. Скачать здесь. Бокс содержит:
  • ubuntu-12.04.3-desktop-amd64 RU
  • VirtualBox Guest Additions версии 4.3.8
  • Пользовать vagrant, пароль vagrant.
  • Запущенный ssh-server с ключем vagrant
  • Firefox 27, Chrome 33.
  • Selenium-node 2.39 в автозагрузке, с конфигом на 1 инстанс firefox и 1 инстанс chrome, у chrome прописан путь к chromewebdriver. В конфиге адрес и порт хаба - 10.0.2.2:4444 - стандартый внутренний IP машины, в которой запускается виртуальная машина (постоянный IP host-машины у VirtualBox). Порт ноды - 5556. Сам selenium и его конфиг в /home/vagrant/selenium
  • В автозагрузке x11vnc без пароля (порт 5900).
Возможно, вам понадобится изменить адреса dns-сервера (там указаны 192.168.1.48, 192.168.1.123 и 8.8.8.8).

Vagrantfile

После того, как вы получили необходимый вам box и скопировали его на сервер, необходимо его добавить в список боксов Vagrant:
vagrant box add ubuntu /path/to/box/ubuntu.box
Посмотреть список доступных боксов можно командой:
vagrant box list
Если же вы захотите заменить одноименный бокс, то сначала нужно его удалить, а потом добавить. Удалять так:
vagrant box remove ubuntu

После добавления бокса его можно использовать. Выберите каталог, где будете хранить специальный конфиг вашей виртуальной машины, например, /var/vagrant/ubuntu
Перейдите в него и введите команду
vagrant init
Этой командой вы создадите в этом каталоге файл Vagrantfile - тот самый конфиг. По умолчанию он будет с закомментированными опциями, которые полезны для ознакомления.
Для использования нашего подготовленного бокса с именем ubuntu нужно, чтобы в нём было:

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  config.vm.box = "ubuntu"

  #selenium node
  config.vm.network :forwarded_port, guest: 5556, host: 5556

  #vnc
  config.vm.network :forwarded_port, guest: 5900, host: 5900

  config.vm.provider "virtualbox" do |v|
    v.memory = 2048
  end

end

Этим мы говорим, что здесь будет виртуальная машина из бокса ubuntu, с пробросом портов их host-машины для selenium-node и для сервера vnc. Кроме того, мы здесь еще указали размер оперативной памяти для виртуальной машины, хотя это необязательно. Сохраните файл и выполните команду:
vagrant up
При первом запуске будет создана виртуальная машина в VirtualBox из образа, который запакован в наш бокс.
Но с первого раза может быть не всё успешно. Проблемы могут возникнуть самые разные. Возможно, что при установке Vagrant версия VirtualBox была установлена другая или вообще удалена и не установлена. Возможно, какие-то проблемы с запуском VirtualBox. Возможно, машина будет запущена, но из-за разницы версии гостевых дополнений и версии VirtualBox они не будут работать. Возможно, ssh-ключ будет неправильным и vagrant не сможет подключится к машине, чтобы узнать, запустилась ли она. Возможно, запуск пройдет успешно, но в самой виртуальной машине не запустится графическая оболочка, а значит и браузеры не смогут запуститься. Со всеми этими проблемами мне приходилось бороться, могу помочь в комментариях.

Если же виртуальная машина запустилась без видимых ошибок, то можно попробовать к ней подключится. Либо через ssh:
vagrant ssh
Либо через vnc-клиент, например, Tight VNC Viewer по адресу host-машины (сервера) и портом 5900 (он проброшен в виртуальную машину).
Если совсем всё прошло успешно, то веб-консоль вашего Selenium-хаба (обычно, http://hub-ip:4444/) должна показать новую ноду. Можно попробовать запустить Selenium-тест.

Jenkins

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

Процедура установки Jenkins на Debian делается по официальной документации очень просто:

Добавляем в файл /etc/apt/sources.list (под рутом) следующую строчку:
deb http://pkg.jenkins-ci.org/debian binary/
Добавляем ключ:
wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
Обновляем список пакетов и устанавливаем:
sudo apt-get update
sudo apt-get install jenkins

Jenkins после установки и запуска разворачивается в /var/lib/jenkins
По умолчанию запускает веб-сервис на порту 8080. Настройки порта и прочего меняются в /etc/init.d/default/jenkins
Работа с Jenkins идёт через веб-интервейс (можно и через командную строку, установив специальную утилиту). С портом по умолчанию это http://server-ip:8080.

Если Jenkins запустился, то теперь нужно установить ant. Эта утилита устанавливается обычно:
sudo apt-get install ant
Она нужна для удобного запуска сценария сборки. Вместо голых bash-скриптов по сборке проекта, мы создаем build.xml, в котором всё структурировано. Приведу пример и расскажу на его примере:
<?xml version="1.0" encoding="UTF-8"?>
<project name="WebProject" default="build">

 <target name="build" depends="prepare,lint,update-workcopy,phpunit-selenium"/>

 <target name="clean" description="Cleanup build artifacts">
  <delete dir="${basedir}/build/api"/>
  <delete dir="${basedir}/build/code-browser"/>
  <delete dir="${basedir}/build/coverage"/>
  <delete dir="${basedir}/build/logs"/>
 </target>

 <target name="prepare" depends="clean" description="Prepare for build">
  <mkdir dir="${basedir}/build/api"/>
  <mkdir dir="${basedir}/build/code-browser"/>
  <mkdir dir="${basedir}/build/coverage"/>
  <mkdir dir="${basedir}/build/logs"/>
 </target>

 <target name="lint" description="Perform syntax check of sourcecode files">
  <apply executable="php" failonerror="true">
   <arg value="-l"/>
   <fileset dir="${basedir}">
    <include name="**/*.php"/>
   </fileset>
  </apply>
 </target>

 <target name="update-workcopy" description="Update workcopy for tests">
    <exec executable="/var/www/web-project-testing/deploy.sh" failonerror="true"/>
 </target>


 <target name="phpunit-selenium" dir="${basedir}/selenium-tests" description="Run Selenium tests with PHPUnit">
    <exec executable="phpunit" failonerror="true"/>
 </target>

</project>

В начале мы объявляем имя проекта (WebProject) и цель по умолчанию (build). Target - цель, это задача, команда, которая должна быть выполнена. Каждая цель имеет имя, может иметь зависимые (она запустит сначала их), может иметь описание, может иметь параметр failonerror - помечать весь билд проваленным, если выполнение задачи прошло с ошибками (или если тесты провалились).
Первая цель "build" зависит от нескольких других - это основной список задач, которые будут выполнены при сборке.
Цель prepare - удаляет папки с временным содержимым и создает такие же пустые. В них собираются отчеты тестов и прочее.
Цель lint - запускает проверку синтаксиса на файлах.
Цель update-workcopy - обновляет веб-хост, на котором будут проходить Selenium-тесты. Обычно это делается с помощью какого-либо деплой-скрипта, только здесь мы разворачиваем тестовое окружение.
Цель phpunit-selenium - это уже сам запуск Selenium-тестов. Здесь же можно указать дополнительные аргументы команде phpunit, например, путь до bootstrap-файла или конфиг-файла.

Созданный build.xml следует хранить в корне проекта и включить его в систему контроля версий. Jenkins будет вызывать ant для сборки вашего проекта по build.xml. Можно передавать дополнительные аргументы, в том числе, дополнительные переменные. Например, путь до деплой-скрипта в цели update-workcopy можно вынести в переменную. Есть встроенные переменные, например ${basedir} - базовый путь проекта.

Когда сценарий сборки готов, можно добавить ваш проект в Jenkins. Для этого добавляем новый проект (тот, что на основе Ant). Заполняем параметры опроса системы контроля версий (SCM), убеждаемся, что в шагах сборки есть "Вызвать Ant", здесь же в расширенных можно с новой строчки объявить дополнительные переменные, которые будут переданы в build.xml. Ниже добавляем шаг после сборки с оповещением по email, заполнив необходимые значения.
Jenkins использует smtp-сервер, который нужно указать в глобальных настройках Jenkins.
После создания проекта можно нажать "Собрать сейчас" слева в меню проекта. В списке сборщиков должна появится новая задача, нажмите на неё, а потом на "Показать вывод консоли", чтобы видеть, что происходит. Это основы работы с Jenkins, но он имеет гораздо больше возможностей, например, анализ кода, отправка оповещений по Jabber и многое другое. Смотрите список плагинов. Также необходимо зайти в настройки безопасности Jenkins, чтобы ограничить круг лиц, имеющих доступ к сервису.

Заключение

Мы получили систему, которая отслеживает появление новых фиксаций в системе контроля версий, запускает обновление тестового окружения, проводит предварительную подготовку проекта, запускает phpunit. PHPUnit по протоколу WebDriver подкючается к Selenium-hub, который передает команды тестов на ноду, запущенную в виртуальной машине из Vagrant-бокса. По завершению тестирования система получает результат сборки и тестирования и на основе настроек оповещает необходимых пользователей, снабжая соответствующей информацией.

3 комментария:

  1. Добрый день, подскажите пожалуйста. Установили Vagrant, но при команде Vagrant up вылазит такое: "Vagrant could not detect VirtualBox! Make sure VirtualBox is properly installed.

    Vagrant uses the `VBoxManage` binary that ships with VirtualBox, and requires
    this to be available on the PATH. If VirtualBox is installed, please find the
    `VBoxManage` binary and add it to the PATH environmental variable."

    До этого VirtualBox работал.

    ОтветитьУдалить
    Ответы
    1. Прошу прощения, слишком поздно увидел комментарий. У вас Vagrant не может выполнить `VBoxManage`. Проверьте, доступно ли его исполнение, например, командой "which VBoxManage". Должен вывести путь к исполяемому файлу.

      Удалить
  2. Еще очень полезно добавить в работу с Jenkins мониторинг билдов.
    Есть для этого CCMenu (http://ccmenu.org) и Catlight (https://catlight.io).
    Сам пользуюсь Catlight, т.к. больше функционал и просто удобнее.

    ОтветитьУдалить