Récemment, j’ai dû mettre en place des tests BEHAT/MINK sous docker. Je l’avais fait sur mon poste en local en faisant tourner un jar selenium et m’étais dit: « oh, ça ne va pas être bien compliqué ». Que nenni! J’ai littéralement lutté pour faire tourner le tout (sachant qu’en plus, je devais le faire tourner sur un gitlab-ci après).
J’aimerais vous épargner de longues heures de recherche. La plupart des tutoriaux sur lesquels je suis tombé étaient soit obsolètes (Symfony2), soit ne fonctionnaient pas, soit faisaient appel à des outils n’étant plus maintenus.
J’ai tenté phantomJS pour avoir un browser headless mais il n’est plus maintenu et ça n’a pas fonctionné à cause du GhostDriver.
J’ai aussi tenté l’option selenium + chromedriver + chrome en version headless mais les perfs n’étaient pas terribles.
J’ai réussi à trouver deux solutions:
- une avec selenium et chrome en mode headless (à partir de la version 59)
- une sans selenium en connexion directe avec chrome en mode headless
Autant vous dire que les perfs ne sont pas les mêmes. Une même page peut mettre 1,6s à s’afficher avec selenium contre 0.5s uniquement avec chrome.
Commençons avec le fichier composer.json
"require-dev": {
"behat/behat": "^3.5",
"behat/mink": "^1.7",
"behat/mink-browserkit-driver": "^1.3",
"behat/mink-extension": "^2.3",
"behat/mink-selenium2-driver": "^1.3",
"behat/symfony2-extension": "^2.1",
"behatch/contexts": "^3.2"
},
behatch/contexts permet de rajouter des steps supplémentaires à ceux existants.
D’abord, un test avec le tag @javascript
En fait, ce test ici n’a que peu de sens car je ne teste pas de javascript. Mais pour tester la solution en mode browser headless, je dois rajouter ce tag:
Feature:
I want to visit homepage
Background:
When I am on homepage
@javascript
Scenario:
Then I should see "UNTITLED vous propose une collection régulièrement actualisée d'illustrations et posters des meilleurs d'artistes du monde de la création numérique."
Then I should see "Voir la galerie"
Then I should see "Actualités"
Sans plus attendre, voici les deux solutions que j’ai trouvées:
AVEC SELENIUM
Le docker-compose
version: '3' services: php: container_name: php_galerie build: docker/php volumes: - ./:/var/www/project depends_on: - chrome environment: VIRTUAL_HOST: galerie-local.fr working_dir: /var/www/project networks: - app hub: image: selenium/hub ports: - 4444:4444 networks: - app chrome: image: selenium/node-chrome restart: always links: - hub environment: - HUB_HOST=hub - HUB_PORT=4444 external_links: - reverseproxy:galerie-local.fr networks: - app reverseproxy: image: jwilder/nginx-proxy ports: - 80:80 - 443:443 volumes: - /var/run/docker.sock:/tmp/docker.sock networks: - app networks: app: driver: bridge
Je n’ai pas mis le container de base de données dedans pour simplifier le docker-compose.
Plusieurs points à noter:
- depends_on du container php par rapport au container chrome
- le VIRTUAL_HOST, très important, car il va servir au reverse proxy pour enregistrer ce host
- la propriété external_links pour permettre au container de l’image chrome de « demander » le host au reverse proxy nginx (sinon behat ignore l’host à appeler pour chaque uri)
Je vous avoue que j’ai trouvé ça tordu de devoir faire intervenir un reverse proxy pour ça mais si j’avais lié l’image chrome à l’image php (et que j’avais dit que l’url était alors php et pas galerie-local.fr), j’aurais eu un problème de référence circulaire puisque l’image php est déjà liée à l’image chrome par le « depends_on ». Pas simple, n’est-ce pas?
La config behat.yml en Symfony3
default: autoload: '': '%paths.base%/../../tests' extensions: Behat\MinkExtension: base_url: 'http://galerie-local.fr' browser_name: chrome javascript_session: chrome_javascript_session sessions: default: symfony2: ~ chrome_javascript_session: selenium2: wd_host: 'http://172.17.0.1:4444/wd/hub' Behat\Symfony2Extension: ~ Behatch\Extension: ~ suites: web: mink_session: default type: symfony_bundle bundle: AppBundle paths: - '%paths.base%/../../tests/AppBundle/Features/Test' contexts: - Behatch\Context\BrowserContext - AppBundle\Context\TransactionContext
La config behat.yml en Symfony4
default: autoload: '%paths.base%/features/bootstrap' extensions: Behatch\Extension: ~ Behat\Symfony2Extension: kernel: bootstrap: features/bootstrap/bootstrap.php class: App\Kernel Behat\MinkExtension: browser_name: chrome base_url: "http://galerie-local.fr" javascript_session: chrome_selenium_session sessions: default: symfony2: ~ chrome_selenium_session: selenium2: wd_host: "http://172.17.0.1:4444/wd/hub" suites: interface: mink_session: default paths: - '%paths.base%/features' contexts: - App\Tests\Behat\Context\CoreContext: [] - Behatch\Context\BrowserContext: []
Il n’y a plus qu’à faire un docker-compose up -d, puis à lancer les tests behat:
docker exec -it php_galerie bash -c 'vendor/bin/behat -vvv'
SANS SELENIUM AVEC CHROME HEADLESS
La config docker-compose
Là, du coup, la config reste la même que celle que vous aviez: pas de container chrome ou hub. La seule chose est qu’il faut avoir pour le container php un extra_hosts et monter un volume supplémentaire (/dev/shm):
php: container_name: php_galerie build: docker/php volumes: - ./:/var/www/project - /dev/shm:/dev/shm extra_hosts: - 'localhost galerie-local.fr:127.0.0.1' ports: - 80:80 working_dir: /var/www/project
Sans le montage du volume /dev/shm vers /dev/shm, chrome essaiera d’écrire dans /tmp et ce dernier étant limité à 63M dans un container, il vous dira que vous devez en avoir au moins 64 pour qu’il puisse fonctionner.
De même, l’extra_hosts est important sinon il sera impossible d’atteindre le site à partir du container (tentez de faire un curl http://votresite.local dans le container php_galerie et vous aurez une erreur).
Dans votre Dockerfile, vous devez installer chrome:
# if you are on debian jessie, uncomment this line #RUN printf "deb http://archive.debian.org/debian/ jessie main\ndeb-src http://archive.debian.org/debian/ jessie main\ndeb http://security.debian.org jessie/updates main\ndeb-src http://security.debian.org jessie/updates main" > /etc/apt/sources.list RUN set -xe \ && apt-get update \ && apt-get install -y --no-install-recommends curl \ && rm -rf /var/lib/apt/lists/* RUN set -xe \ && curl -fsSL https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ && echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list \ && apt-get update \ && apt-get install -y google-chrome-stable \ && rm -rf /var/lib/apt/lists/*
Le script sh de lancement dans docker
Ensuite, dans votre script docker de lancement (mon fichier s’appelle run.sh et se lance au build du container php), vous devez mettre:
google-chrome-stable --disable-gpu --headless --window-size=1920,1080 --no-sandbox --remote-debugging-address=0.0.0.0 --remote-debugging-port=9222 &
Mise à jour du composer.json
Vous devez rajouter ce vendor: « dmore/behat-chrome-extension »: « ^1.3 »
Mise à jour du fichier behat.yml
default: autoload: '%paths.base%/features/bootstrap' extensions: DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~ Behatch\Extension: ~ Behat\Symfony2Extension: kernel: bootstrap: features/bootstrap/bootstrap.php class: App\Kernel Behat\MinkExtension: browser_name: chrome base_url: "http://galerie-local.fr" default_session: default javascript_session: chrome_selenium_session sessions: default: symfony2: ~ chrome_selenium_session: selenium2: wd_host: "http://172.17.0.1:4444/wd/hub" suites: interface: mink_session: default paths: - '%paths.base%/features' contexts: - App\Tests\Behat\Context\CoreContext: [] chrome_headless_local_session: extensions: Behat\MinkExtension: javascript_session: chrome_headless_session sessions: chrome_headless_session: chrome: api_url: 'http://php:9222'
Je l’ai mis en Symfony4, mais les ajouts sont facilement transposables en Symfony3.
Les changements sont:
- Rajout de l’extension: DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~
- Rajout d’un nouveau profil. Si vous vous demandez pourquoi j’ai mis php à la place d’une ip, c’est parce que j’ai besoin de l’ip dynamique du container php_galerie pour que chrome puisse s’exécuter (en local sans docker, ce serait 127.0.0.1)
J’ai laissé la configuration selenium pour vous montrer que l’on peut combiner les deux, mais si vous désirez la version minimaliste, ce serait:
default: autoload: '%paths.base%/features/bootstrap' extensions: DMore\ChromeExtension\Behat\ServiceContainer\ChromeExtension: ~ Behatch\Extension: ~ Behat\Symfony2Extension: kernel: bootstrap: features/bootstrap/bootstrap.php class: App\Kernel Behat\MinkExtension: browser_name: chrome base_url: "http://galerie-local.fr" default_session: default javascript_session: chrome_headless_session sessions: default: symfony2: ~ chrome_headless_session: chrome: api_url: 'http://php:9222' suites: interface: mink_session: default paths: - '%paths.base%/features' contexts: - App\Tests\Behat\Context\CoreContext: []
Il n’y a plus qu’à faire un docker-compose up -d, puis à lancer les tests behat:
docker exec -it php_galerie bash -c 'vendor/bin/behat -p chrome_headless_local_session -vvv'
ou avec la config allégée:
docker exec -it php_galerie bash -c 'vendor/bin/behat -vvv'
Conclusion
J’espère vous avoir épargné de longues heures de recherche. J’ai trouvé vraiment très peu de doc sur le net et elles étaient souvent pour Symfony2. La solution avec selenium fonctionnait bien mais était un peu lente à mon goût et je voulais que mes tests s’exécutent le plus rapidement possible. La solution avec chrome headless me paraît idéale.
One comment
Merci