Symfony 3 end to end testing with mink and selenium

We all know how important end-to end testing is, but offten on “fast passed” projects, we don’t find the time to implement best practices. Here I will show you how to setup circleCI with selenium

I’ll assume you already have a Symfony 3 project up and running

  • cd path/to/project
  • composer require behat/mink
  • composer require behat/mink-selenium2-driver
  • mkdir tests/AppBundle/Mink
  • touch tests/AppBundle/Mink/CoreMink.php
  • enter this content in the file:
<?php

namespace Tests\AppBundle\Mink;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Behat\Mink\Driver\Selenium2Driver;
use Behat\Mink\Exception\ElementNotFoundException;
use Behat\Mink\Session;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Bundle\FrameworkBundle\Routing\Router;

abstract class CoreMink extends WebTestCase
{
    /** @var string */
    private $minkBaseUrl;

    /** @var Session */
    protected $minkSession;

    /** @var Client */
    protected $client;

    /** @var Router */
    protected $router;

    /** @var  string */
    protected $seleniumDriverType;

    public function setUp()
    {
        $this->client = static::createClient();
        $container = $this->client->getContainer();
        $this->router = $container->get('router');
        $this->minkBaseUrl = $container->getParameter('mink_url');
        $this->seleniumDriverType = $container->getParameter('selenium_driver_type');
    }

    /**
     * @before
     */
    public function setupMinkSession()
    {
        $driver = new Selenium2Driver($this->seleniumDriverType);
        $this->minkSession = new Session($driver);
        $this->minkSession->start();
    }

    public function getCurrentPage()
    {
        return $this->minkSession->getPage();
    }

    public function getCurrentPageContent()
    {
        return $this->getCurrentPage()->getContent();
    }

    public function visit($url)
    {
        $this->minkSession->visit($this->minkBaseUrl.$url);
    }

    public function fillField($field, $value)
    {
        $page = $this->getCurrentPage();

        try {
            $page->fillField($field, $value);
        } catch (ElementNotFoundException $ex) {
            $this->screenShot();
            throw($ex);
        }
    }

    public function find($type, $value)
    {
        $page = $this->getCurrentPage();

        try {
            return $page->find($type, $value);
        } catch (ElementNotFoundException $ex) {
            $this->screenShot();
            throw($ex);
        }
    }

    public function findField($type)
    {
        $page = $this->getCurrentPage();

        try {
            return $page->findField($type);
        } catch (ElementNotFoundException $ex) {
            $this->screenShot();
            throw($ex);
        }
    }

    public function pressButton($field)
    {
        $page = $this->getCurrentPage();

        try {
            $page->pressButton($field);
        } catch (ElementNotFoundException $ex) {
            $this->screenShot();
            throw($ex);
        }
    }

    public function login($user, $pass)
    {
        $this->getCurrentPageContent();
        $this->fillField('_email', $user);
        $this->fillField('_password', $pass);
        $this->pressButton('connexion');
    }

    public function clickLink($field)
    {
        $page = $this->getCurrentPage();
        try {
            $page->clickLink($field);
        } catch (ElementNotFoundException $ex) {
            $this->screenShot();
            throw($ex);
        }
    }

    public function screenShot()
    {
        $driver = $this->minkSession->getDriver();
        if (!($driver instanceof Selenium2Driver)) {
            $this->minkSession->getDriver()->getScreenshot();

            return;
        } else {
            $screenShot = base64_decode($driver->getWebDriverSession()->screenshot());
        }

        $timeStamp = (new \DateTime())->getTimestamp();
        file_put_contents('/tmp/'.$timeStamp.'.png', $screenShot);
        file_put_contents('/tmp/'.$timeStamp.'.html', $this->getCurrentPageContent());
    }

    public function logout()
    {
        $page = $this->getCurrentPage();
        $page->clickLink('logout');
    }
}
  • Install chromedriver somewhere in your path: https://sites.google.com/a/chromium.org/chromedriver/
  • download selenium server standalone, I have been using version 2.53: http://selenium-release.storage.googleapis.com/index.html?path=2.53/
  • create your first mink selenium test!
  • touch tests/AppBundle/Mink/FirstTest.php
<?php

namespace Tests\AppBundle\Mink;

class FistTest extends CoreMink
{
    public function testSubmitPage()
    {

        $this->visit('/client/checkout');
        $this->login('sdevilcry@gmail.com', 'toto'); // Login first.



    }
}
  • run selenium server standalone java -jar /path-to/selenium-standalone.jar
  • run phpunit vendor/bin/phpunit tests/AppBundle/Mink
  • if you have followed the steps correctly, chrome will open and run your initial test, Awsome!

Now lets add this to our circleci.yml config

general:
  artifacts:
    - "/tmp/"
    - "var/logs/"

machine:
  hosts:
    sharegroop.dev: 127.0.0.1
  php:
    version: 7.0.4
  timezone:
    Europe/Paris
  environment:
    SYMFONY_ENV: test

dependencies:
  pre:
    - curl http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar > selenium-server-standalone.jar
    - curl http://chromedriver.storage.googleapis.com/2.23/chromedriver_linux64.zip | gzip -dc > chromedriver
    - chmod +x chromedriver
    - 'java -jar selenium-server-standalone.jar -trustAllSSLCertificates -Dwebdriver.chrome.driver=chromedriver':
        background: true
    - google-chrome --version
    - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
    - sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb stable main" >> /etc/apt/sources.list.d/google.list'
    - sudo apt-get update
    - sudo apt-get --only-upgrade install google-chrome-stable
    - google-chrome --version
  override:
    - cp app/config/parameters.test.yml app/config/parameters.yml
    - composer install --no-interaction
    - 'bin/console server:run':
        background: true

Ok, this should be enough but unfortunatly right now there is a bug in behat/mink-selenium2-driver repository, its fixed in this pull request https://github.com/minkphp/MinkSelenium2Driver/pull/244

so you may need to install from that branch instead of master in order to get chrome to work in headless mode.

Hope this helps!