Запускаем свой CI сервер для PHP
Всем привет! С вами netandreus, и сегодня я расскажу, как настроить свой CI сервер на базе Jenkins+CodeCeption. Будет много кода и в конце концов рабочее решение. Сразу пишу для тех, кто прочитает пост в будущем – касается актуальных версий, если разработчики чего-то там поломают – RTFM и GitHub.
Общая схема
Итак, что же нам надо? Будем настраивать сервер непрерывной интеграции (CI). В качестве тестовой машины как обычно Ubuntu LTS. На данный момент это свеженькая v.16.04.03. В качестве билд-сервера будем юзать Jenkins (благо для него куча плагинов, в т.ч. и нужных нам), тестовым фреймворком будет CodeCeption (привет @davert), ну а сервера тестирования – Silenium. Ориентироваться будем на приемочные тесты (acceptance tests) в реальном браузере.
Общая схема
В нашем BitBucket репозитарии стоит add-on “Bitbucket Server Webhook to Jenkins“, который после каждого пуша в указанную ветку (по-умолчанию develop) отправляет запрос на билд Jenkins. Jenkins сливает код, выполняет шаги из Build steps, один из которых звучит как
1 |
php ./vendor/codeception/codeception/codecept run acceptance |
Это вызывает запуск Codeception, который через xvfb-run поднимает сервер Selenium с указанием используемого вебдрайвера. Вебдрайвер (chromedriver или geckodriver) запускает реальный браузер, в котором будут проходить тесты.
Подготовка
У нас уже стоит ОСь со всеми обновлениями и настроенный git-клиент. Для теста пробуем команду ssh -vvv git@git.repository.com -p 7999 должно адекватно авторизовываться и сразу закрывать сессию.
Ставим софт
Устанавливаем Jenkins
Ставим в принципе как обычно.
1 2 3 4 5 |
wget -q -O - https://pkg.jenkins.io/debian/jenkins-ci.org.key | sudo apt-key add - echo deb http://pkg.jenkins.io/debian-stable binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list sudo apt-get update sudo apt-get install jenkins sudo systemctl start jenkins |
Последней командой убеждаемся, что сервис стартанул, и заходим на веб-морду: http://our-jenkins-ip:8080/
Там выбираем установку стандартных плагинов (Install suggested plugins), добавлем админ юзера и заходим под ним.
Настраиваем тему Jenkins
Когда мы заходим в Jenkins, то нас встречает убогий интерфейс в стиле 90x. Немножко скрасить это убожество можно с помощью плагина Simple Theme Plugin. Ставим его, дальше можно прямо на страничке плагина сгенерировать себе css файл с логотипом и стилями, а можно просто взять понравившийся цвет и стиль с ним из CDN. Мне вот по душе пришелся такой: https://cdn.rawgit.com/afonsof/jenkins-material-theme/gh-pages/dist/material-blue-grey.css
Теперь заходим в Manage Jenkins -> Configure System -> Theme
А еще можно использовать плагин embeddable-build-status для отображения красивых бейджей о статусе билдов.
Добавляем git-репозитарий в Jenkins
Чтобы собственно было откуда брать код, надо добавить в Jenkins наш git-репозитарий в закладке “Source Code Management”. В форме в разделе Build Triggers обязателно ставим галку [x] Poll SCM.
Теперь в настройках задачи сборки надо указать, что делать после того, как мы скачаем свежий код из репозитария. Я для этих целей сделал в composer.json специальный пункт и назвал его @ci-build.
1 2 3 4 5 6 7 8 |
"ci-build": [ "bower install", "php bin/console doctrine:schema:drop --full-database --force", "php bin/console myapp:import-database", "php bin/console doctrine:schema:update --force", "sed -i 's/echo \"Testing $suiteName\\\\n\";//g' ./vendor/symfony/phpunit-bridge/Legacy/SymfonyTestsListenerTrait.php", "php ./vendor/codeception/codeception/codecept run acceptance" ] |
Что делает этот код: обновляет JS зависимости с помощью Bower, удаляет нафиг базу данных, закачивает фикстуры, потом накатывает миграции схемы, фиксит баг в symfony phpunit-bridge и собственно запускает приемочные тесты.
Баг в symfony/phpunit-bridge связан с тем, что эта строчка создаёт вывод, что не даёт создать сессию авторизации. Это будет необходимо нам для тестирования веб-приложения из-под залогиненного пользователя.
Теперь вызовем его из Jenkins.
Меняем юзера Jenkins
Чтобы не было проблем с правами необходимо поменять юзера jenkins на того, от которого работает веб-сервер. У меня это www-data.
В файле /etc/default/jenkins меняем параметры $JENKINS_USER и $JENKINS_GROUP на www-data.
Потом делаем chown:
chown -R www-data:www-data /var/lib/jenkins
chown -R www-data:www-data /var/cache/jenkins
chown -R www-data:www-data /var/log/jenkins
И перезапускаем Jenkins:
/etc/init.d/jenkins restart
Подробнее вот в этой статье.
Настройка BitBucket
Заходим в BitBucket, устанавливаем там плагин Bitbucket Server Webhook to Jenkins, затем в настройках репозитария в разделе Hooks видим строчку нашего плагина. Нажимаем там на кнопку Configure, и там указываем web url нашего Jenkins сервера (например http://192.168.0.100:8080), ставим галку [x] Skip SSL Certificate Validation.
В настройках указываем путь до нашего Jenkins-сервера (например http://192.168.0.100:8080) и ветку, по пушу в которую надо его уведомлять. Обязательно ставим галку [x] Skip SSL certificate validation, если только у нас не стоит клевый валидный ssl-сертификат.
Нажимаем кнопку Trigger Jenkins и убеждаемся, что у нас действительно происходит вызов CI системы.
Устанавливаем codeception
Добавляем в composer.json строчку с нужной вам версией фреймворка.
composer.json
“codeception/codeception”: “2.3.5”
Потом делаем:
1 2 |
php composer.phar install php ./vendor/codeception/codeception/codecept bootstrap |
После этого появится папочка tests и там можно создать конфиг acceptance.suite.yml
В нем пишем следующее:
acceptance.suite.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
actor: AcceptanceTester modules: enabled: - WebHelper - Symfony2: part: ORM app_path: 'app' var_path: 'var' environment: 'dev' em_service: 'doctrine.orm.entity_manager' debug: true cache_router: 'false' rebootable_client: 'true' - WebDriver: url: 'http://test.site.ru' browser: firefox - \Helper\Acceptance extensions: enabled: - Codeception\Extension\RunProcess: 1: "exec xvfb-run java -jar -Dwebdriver.gecko.driver=/usr/bin/geckodriver /usr/local/bin/selenium-server-standalone.jar" sleep: 10 |
В папке ./tests/_support создаем файл:
WebHelper.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<?php namespace Codeception\Module; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; class WebHelper extends \Codeception\Module { public function amLoggedInAs($username) { $container = $this->getModule('Symfony2')->_getContainer(); $doctrine = $container->get('doctrine.orm.default_entity_manager'); $user = $doctrine ->getRepository('Netandreus\SomeBundle\Entity\User') ->findOneBy(['username' => $username]); $firewall = 'main'; $token = new UsernamePasswordToken($user, null, $firewall, $user->getRoles()); $session = $container->get('session'); $session->set('_security_' . $firewall, serialize($token)); $session->save(); $webDriver = $this->getModule('WebDriver'); $webDriver->setCookie($session->getName(), $session->getId()); $webDriver->reloadPage(); } } |
Это CodeCeption модуль, который добавляет в наши тесты возможность вызова команды $I->amLoggedInAs(‘username’) что обеспечит нам тестирование от залогиненного пользователя.
Сами тесты в папке ./tests/acceptance
MainCest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php class MainCest { public function _before(AcceptanceTester $I) { } public function _after(AcceptanceTester $I) { } // tests public function tryToTest(AcceptanceTester $I) { // $I->setHeader('Accept', '*/*'); // Only for phpBrowser $I->wantTo('open homepage'); $I->amOnPage('/'); $I->amLoggedInAs('admin'); // we need WebDriver instead of phpBrowser Module $I->see('Some text'); $I->wantTo('go admin dashboard'); $I->amOnPage('/admin/dashboard'); $I->see('Some string'); } } |
Некоторые замечания
У меня так и не получилось заставить работать Codeception с параметром browser: chrome. Хотя если оставить в конфиге browser: firefox, а вот в вызов Selenium передать
exec xvfb-run java -jar -Dwebdriver.chrome.driver=/usr/bin/chromedriver /usr/local/bin/selenium-server-standalone.jar
то он прекрасно потестит его на Chrome.
При первом запуске тестов стартует процесс Selenium, а это Java, так что ставим параметр sleep: 10 чтобы подождать некоторое время перед запусками тестов.
Bash скрипт для авто-установки
Запускаем, пользуемся.
install_ci.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#!/usr/bin/env bash # Firefox sudo apt-add-repository ppa:mozillateam/firefox-next sudo apt-get update sudo apt-get install firefox xvfb # Gecko driver mkdir /var/www/tmp cd /var/www/tmp wget https://github.com/mozilla/geckodriver/releases/download/v0.16.1/geckodriver-v0.16.1-linux64.tar.gz sudo sh -c 'tar -x geckodriver -zf geckodriver-v0.16.1-linux64.tar.gz -O > /usr/bin/geckodriver' sudo chmod +x /usr/bin/geckodriver rm geckodriver-v0.16.1-linux64.tar.gz # Chromedriver wget https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip unzip chromedriver_linux64.zip sudo chmod +x chromedriver sudo mv chromedriver /usr/bin/ rm chromedriver_linux64.zip # Selenium, openJDK, chrome, chromedriver(?) wget https://gist.githubusercontent.com/ziadoz/3e8ab7e944d02fe872c3454d17af31a5/raw/990d54467543d17e5c1874dccc3b7970a63d1ca4/install.sh chmod +x ./install.sh ./install.sh # Run Selenium # exec xvfb-run java -jar -Dwebdriver.gecko.driver=/usr/bin/geckodriver /usr/local/bin/selenium-server-standalone.jar |
Готовый ansible-playbook
Для тех, кому лень (и это правильно) публикую свой ansible-playbook, который подготовит для вас среду для тестирования.
ci.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# @see https://gist.githubusercontent.com/ziadoz/3e8ab7e944d02fe872c3454d17af31a5/raw/990d54467543d17e5c1874dccc3b7970a63d1ca4/install.sh - name: Add Firefox repo apt_repository: repo: "ppa:mozillateam/firefox-next" state: present update_cache: yes - name: Install base packages apt: name={{item}} state=installed with_items: - firefox - xvfb - unzip - openjdk-8-jre-headless - libxi6 - libgconf-2-4 - libnss3-1d - libxss1 - name: Set Selenium version set_fact: SELENIUM_STANDALONE_VERSION: "3.4.0" - name: Set Selenium subdir set_fact: SELENIUM_SUBDIR: "3.4" - name: debuggg debug: msg: "{{ SELENIUM_SUBDIR }}" - name: Install Gecko driver shell: "{{ item }}" with_items: - 'wget https://github.com/mozilla/geckodriver/releases/download/v0.16.1/geckodriver-v0.16.1-linux64.tar.gz' - "sh -c 'tar -x geckodriver -zf geckodriver-v0.16.1-linux64.tar.gz -O > /usr/bin/geckodriver'" - "chmod +x /usr/bin/geckodriver" - "rm geckodriver-v0.16.1-linux64.tar.gz" - name: Install Chrome driver shell: "{{ item }}" with_items: - 'wget https://chromedriver.storage.googleapis.com/2.29/chromedriver_linux64.zip' - 'unzip chromedriver_linux64.zip' - 'sudo chmod +x chromedriver' - 'sudo mv chromedriver /usr/bin/' - 'rm chromedriver_linux64.zip' - name: Remove old versions shell: "{{ item }}" with_items: - 'rm -f ~/google-chrome-stable_current_amd64.deb' - 'rm -f ~/selenium-server-standalone-*.jar' - 'rm -f ~/chromedriver_linux64.zip' - 'rm -f /usr/local/bin/chromedriver' - 'rm -f /usr/local/bin/selenium-server-standalone.jar' - name: Install Chrome shell: "{{ item }}" with_items: - 'wget -N https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -P ~/' - 'dpkg -i --force-depends ~/google-chrome-stable_current_amd64.deb' - 'apt-get -f install -y' - 'dpkg -i --force-depends ~/google-chrome-stable_current_amd64.deb' - name: Install Selenium shell: "{{ item }}" with_items: - 'wget -N http://selenium-release.storage.googleapis.com/{{ SELENIUM_SUBDIR }}/selenium-server-standalone-{{ SELENIUM_STANDALONE_VERSION }}.jar -P ~/' - 'mv -f ~/selenium-server-standalone-{{ SELENIUM_STANDALONE_VERSION }}.jar /usr/local/bin/selenium-server-standalone.jar' - 'chown root:root /usr/local/bin/selenium-server-standalone.jar' - 'chmod 0755 /usr/local/bin/selenium-server-standalone.jar' |
Ссылки
How To Install Jenkins on Ubuntu 16.04
Установка через bash-скрипт
Firefox WebDriver
https://askubuntu.com/a/923317
http://blog.manula.org/2013/03/running-jenkins-under-different-user-in.html
http://glumb.de/en/logging-in-with-codeception-and-symfony2
http://codeception.com/docs/modules/Symfony#Symfony-3x-Directory-Structure
https://gist.github.com/ziadoz/3e8ab7e944d02fe872c3454d17af31a5
http://elementalselenium.com/tips/38-headless
http://www.odmin4eg.ru/tag/xvfb/
В завершение установки необходимо настроить запуск агента. У PHP Censor -а есть 2 типа агентов, – команда, которая запускается по расписанию и демон, который запущен постоянно. Crontab задание для запуска сборщика по расписанию будет выглядеть примерно так: