Запускаем свой CI сервер для PHP

// Август 21st, 2017 // PHP

selenium-big-logoВсем привет! С вами netandreus, и сегодня я расскажу, как настроить свой CI сервер на базе Jenkins+CodeCeption. Будет много кода и в конце концов рабочее решение. Сразу пишу для тех, кто прочитает пост в будущем — касается актуальных версий, если разработчики чего-то там поломают — RTFM и GitHub.

Общая схема

Итак, что же нам надо? Будем настраивать сервер непрерывной интеграции (CI). В качестве тестовой машины как обычно Ubuntu LTS. На данный момент это свеженькая v.16.04.03. В качестве билд-сервера будем юзать Jenkins (благо для него куча плагинов, в т.ч. и нужных нам), тестовым фреймворком будет CodeCeption (привет @davert), ну а сервера тестирования — Silenium. Ориентироваться будем на приемочные тесты (acceptance tests) в реальном браузере.

selenium-webdriver

Общая схема

В нашем BitBucket репозитарии стоит add-on «Bitbucket Server Webhook to Jenkins«, который после каждого пуша в указанную ветку (по-умолчанию develop) отправляет запрос на билд Jenkins. Jenkins сливает код, выполняет шаги из Build steps, один из которых звучит как

php ./vendor/codeception/codeception/codecept run acceptance

Это вызывает запуск Codeception, который через xvfb-run поднимает сервер Selenium с указанием используемого вебдрайвера. Вебдрайвер (chromedriver или geckodriver) запускает  реальный браузер, в котором будут проходить тесты.

Подготовка

У нас уже стоит ОСь со всеми обновлениями и настроенный git-клиент. Для теста пробуем команду ssh -vvv git@git.repository.com -p 7999 должно адекватно авторизовываться и сразу закрывать сессию.

Ставим софт

Устанавливаем Jenkins

Ставим в принципе как обычно.

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

theme-css

А еще можно использовать плагин embeddable-build-status для отображения красивых бейджей о статусе билдов.

Добавляем git-репозитарий в Jenkins

Чтобы собственно было откуда брать код, надо добавить в Jenkins наш git-репозитарий в закладке «Source Code Management». В форме в разделе Build Triggers обязателно ставим галку [x] Poll SCM.

Poll-scm

Теперь в настройках задачи сборки надо указать, что делать после того, как мы скачаем свежий код из репозитария. Я для этих целей сделал в composer.json специальный пункт и назвал его @ci-build.

"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-execute-shell

Меняем юзера 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.

BitBucket Jenkins Plugin

В настройках указываем путь до нашего Jenkins-сервера (например http://192.168.0.100:8080) и ветку, по пушу в которую надо его уведомлять. Обязательно ставим галку [x] Skip SSL certificate validation, если только у нас не стоит клевый валидный ssl-сертификат.

bitbucket-plugin

Нажимаем кнопку Trigger Jenkins и убеждаемся, что у нас действительно происходит вызов CI системы.

Устанавливаем codeception

Добавляем в composer.json строчку с нужной вам версией фреймворка.

composer.json

«codeception/codeception»: «2.3.5»

Потом делаем:

php composer.phar install
php ./vendor/codeception/codeception/codecept bootstrap

После этого появится папочка tests и там можно создать конфиг acceptance.suite.yml

В нем пишем следующее:

acceptance.suite.yml

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

<?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

<?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

#!/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

# @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/

Share

Спасибо!


Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:


Комментировать