How to create a Selenium Vagrant base box to run headless web tests

After following this guide, you will have a Vagrant box equiped with Selenium server including all tools needed to execute web tests on a headless VM. No more fighting with configurations. Once setup, you can share the box with your team and if anything goes wrong you can easily replicate this box. Running headless stops Selenium from taking over your machine by poping up browsers so you can focus on other tasks while your tests run in the background.

Create the box from an existing one

The easiest way to create a base box would be to create one from an existing box. Lets use hashicorp/precise64 to create our Selenium base box. If you need other boxes, just head to HashiCorp Atlas to find one that suites your needs.

vagrant init hashicorp/precise64

This will create a Vagrantfile, we will modify that file describe how we want to provision our box.

Add the shell script to provision VM

Copy below code into a file named script.sh and make sure its located in the same directory as your Vagrantfile. The script will install everything you need to run Selenium web tests on your machine including its dependencies.

#!/bin/bash

# specify stable versions to grab
FIREFOX_VERSION="47.0.1"
SELENIUM_VERSION="2.53/selenium-server-standalone-2.53.1.jar"

#=========================================================
echo "Updating packages ..."
#=========================================================
sudo apt-get update

#=========================================================
echo "Installing dependencies ..."
#=========================================================
sudo apt-get install -y curl
sudo apt-get install -y unzip
# install java
sudo apt-get install -y openjdk-7-jre

#=========================================================
echo "Installing LAMP stack ... "
#=========================================================
sudo apt-get -y install apache2

# set up root password for MySQL
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password password password'
sudo debconf-set-selections <<< 'mysql-server mysql-server/root_password_again password password'

# depedencies
sudo apt-get -y install mysql-server libapache2-mod-auth-mysql php5-mysql

# ensure we get PHP 5.4.x, required for composer
sudo apt-get install -y software-properties-common
sudo apt-get install -y python-software-properties
sudo add-apt-repository ppa:ondrej/php5-oldstable
sudo apt-get update

# installing PHP and it's dependencies
sudo apt-get -y install php5 libapache2-mod-php5 php5-mcrypt
sudo apt-get -y install php5-curl

# =========================================================
# echo "Installing GUI ... "
# =========================================================
# sudo apt-get install -y ubuntu-desktop gnome

# # install GUI
# sudo apt-get install -y xfce4 virtualbox-guest-dkms virtualbox-guest-utils virtualbox-guest-x11
# # Permit anyone to start the GUI
# sudo sed -i 's/allowed_users=.*$/allowed_users=anybody/' /etc/X11/Xwrapper.config

#=========================================================
echo "Installing xvfb for headless testing"
#=========================================================
sudo apt-get -y install xvfb

#=========================================================
echo "Installing firefox ${FIREFOX_VERSION} ... "
#=========================================================
wget https://ftp.mozilla.org/pub/firefox/releases/${FIREFOX_VERSION}/firefox-${FIREFOX_VERSION}.linux-x86_64.sdk.tar.bz2 && tar xjf firefox-${FIREFOX_VERSION}.linux-x86_64.sdk.tar.bz2
sudo mv firefox-sdk /opt/firefox
sudo rm -rf /usr/bin/firefox
sudo ln -s /opt/firefox/bin/firefox /usr/bin/firefox
sudo rm -rf firefox-${FIREFOX_VERSION}.linux-x86_64.sdk.tar.bz2

#=========================================================
echo "Downloading selenium server ${SELENIUM_VERSION}..."
#=========================================================
wget "https://selenium-release.storage.googleapis.com/${SELENIUM_VERSION}" -O selenium-server-standalone.jar
chown vagrant:vagrant selenium-server-standalone.jar
chmod +x selenium-server-standalone.jar

#=========================================================
echo "Installing composer for PHP tests"
#=========================================================
curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer

Describe what to provision in our Vagrant file

Next we are going to provision our base box using this shell script that will take care of downloading and installing all the tools we need to run Selenium web tests using PHP. Replace the contents of the generated Vagrantfile with below.

Vagrant.configure(2) do |config|

  config.vm.box = "hashicorp/precise64"

  config.vm.network "forwarded_port", guest: 80, host: 8080
  config.vm.network "private_network", ip: "192.168.33.10"
  # provision via shell scripting
  config.vm.provision "shell", path: "script.sh", run: "once"

  # If you really need it you can install a GUI, just uncomment below
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true

  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end

end

Note if you really need a GUI just uncomment the section specified in the Vagrantfile. Just remember to do the same in the script.sh file when you get to that part.

Vagrant up

On the first run, Vagrant will execute the provisioning script specified in our Vagrantfile to automatically installing everything you need. Go here to read more on provisioning basic usage

vagrant up 

SSH into your machine

Wait until your machine finishes provisioning. After its finished, ssh into your Vagrant box.

vagrant ssh 

Check that php is installed

php -v 

Check that java is installed

java -version 

Provide a fake screen

Since we are running on a headless server we need to use Xvfb to emulate display hardware. This will allow us to run our web tests in the background saving us precious screen real estate. In your Vagrant box terminal, execute this

Xvfb :1 -screen 0 1600x1200x16 &

Basically we are saying, listen to connections as server 1, zero screen with size 1600x1200 with depth 16. Now export to tell everything to launch from our fake display like our web browsers.

export DISPLAY=:1

Finally, start the selenium server on your Vagrant box as usual.

java -jar selenium-server-standalone.jar &

Get the test code

We need to put two files composer.json and TestGoogle.php in your /vagrant directory.

cd /vagrant 
touch composer.json
touch TestGoogle.php

Put code below in your composer.json file

{
    "require-dev": {
        "phpunit/phpunit": "*",
        "facebook/webdriver": "dev-master"
    }
}

The depedencies we specify in composer.json is phpunit and webDriver. For further reading see post on Setting up your PHP environment for Selenium webdriver testing.

Then create a file called TestGoogle.php

<?php
class TestGoogle extends PHPUnit_Framework_TestCase {

    protected $url = 'http://google.com';
    /**
     * @var RemoteWebDriver
     */
    protected $webDriver;

    public function setUp()
    {
        $capabilities = array(WebDriverCapabilityType::BROWSER_NAME => 'firefox');
        $this->webDriver = RemoteWebDriver::create('http://localhost:4444/wd/hub', $capabilities);
    }

    public function tearDown()
    {
        if($this->webDriver){
            $this->webDriver->close();
        }
    }

    public function testSearch()
    {
        $this->webDriver->get($this->url);

        // find search field by its name
        $search = $this->webDriver->findElement(WebDriverBy::name('q'));
        $search->click();

        // typing our search query
        $this->webDriver->getKeyboard()->sendKeys('what is composer');

        // submit our query
        $search->submit();
    }

}
?>

Run the test

Invoke composer to install all depedencies specified in the composer.json file.

composer install 

Ensure the Selenium standalone server is running, then run the test using phpunit.

vendor/bin/phpunit TestGoogle.php

On success, you should output simular to below

Time: 4.59 seconds, Memory: 4.25Mb

OK (1 test, 0 assertions)

Here is a sample output of what you would see while the test is executing. It’s generated by the Selenium standalone server.

15:04:03.891 INFO - Executing: [send keys to active: [what is composer]])
15:04:04.090 INFO - Done: [send keys to active: [what is composer]]
15:04:04.114 INFO - Executing: [submit: 0 [[FirefoxDriver: firefox on LINUX (23bbd76c-502c-4e92-bbf6-82aed3dc4cf6)] -> name: q]])

Extract the box for sharing

Before extracting, lets clean up our box to reduce size and clean up history.

sudo apt-get clean
sudo apt-get autoclean

Zero out free space by writing zeros to all empty space on the volume. This helps VM compression. Ensure your on your vagrant machine not your host.

sudo dd if=/dev/zero of=/EMPTY bs=1M
sudo rm -f /EMPTY

Remove your tracks, clean up history

unset HISTFILE
sudo rm -f /home/vagrant/.bash_history

Finally logout

exit

Stop your machine

vagrant halt 

Run the command below to extract the box file out. You can name your box anything you like - for example selenium-web.box. Make sure you have logged out of your vagrant box.

vagrant package --output selenium-web.box

This will spit out a new box file in your current folder named selenium-web.box. Congrats you have just created your very own Selenium box.

Test your new box

After extracting, you can share your box. Lets navigate to a new directory and create a folder called superTestBox

cd ..
mkdir superTestBox

Grab the box we created called selenium-web.box and move it to our new superTestBox directory. Ensure you have halted your other box. Otherwise you might end up with conflicts.

To test our new box the syntax is

vagrant box add my-box /path/to/the/new.box
vagrant init my-box
vagrant up

So in our case

vagrant box add selenium-web selenium-web.box
vagrant init selenium-web

Replace contents of the Vagrantfile that was generated by init.

Vagrant.configure(2) do |config|

  config.vm.box = "selenium-web"
  config.vm.network "private_network", ip: "192.168.33.10"
  # config.vm.provision "shell", path: "scripts/script.sh", run: "once"

end 

You can include the provisioning script but its not necessary since this box was already provisioned with Selenium. You can just share it. Finally run it.

vagrant up 

SSH into your selenium box

vagrant ssh

Further Reading:

How to create a Vagrant Base Box from an Existing One
vagrant-selenium script by Anomen
How do I install MySQL without a password prompt?
X11 Virtual framebuffer (Xvfb)
Headless Selenium PHP version
Download composer
Working with PHPUnit and Selenium Webdriver
Testing your project with PHPUnit and Selenium
How To Install Linux, Apache, MySQL, PHP (LAMP) stack on Ubuntu
Using vagrant to run virtual machines with desktop environment
Reducing Vagrant box size