Automated Web Testing with PHP and Selenium

Things break. Just the other day through a series of seemingly unrelated events, a new Microsoft x509 certificate made its way into a security handshake process which went unnoticed until current single sign-on sessions began to expire. Had we also had automated security testing, we would have caught this one-off. I’ll explain how I set up Selenium automated browser testing in a PHP environment, and how it can be used for more than just UI testing across browsers.

What is Selenium WebDriver?

Selenium automates browsers. That’s the first line of the Selenium HQ web site. To sell the idea of putting my energy into setting up automated web testing for my company, I explained that, boiled down, Selenium is a web driver available in PHP1 that lets you programmatically control the major desktop and mobile browsers, including clicking on things and inspecting elements – perfect for automated web site testing.

I’ll show you how to get all the pieces working together for an example.

Learning Selenium

If you are interested, I recommend a Lynda.com course2 on Selenium to quickly get familiar or refreshed in it.

Lynda.com learning Selenium videos
Lynda.com learning Selenium videos

Install WebDriver bindings for PHP

Selenium WebDriver bindings for PHP are available in the Facebook Github repo. They can be installed via composer.

yum install php7.2-zip (CentOS) – ext-zip is required for the composer installation below. This may install several more PHP 7 packages also.

composer require facebook/webdriver – Install the PHP bindings for Selenium

Install Selenium PHP bindings with composer
Install Selenium PHP bindings with composer

Install Selenium Standalone Server and Javaw

Facebook provides instructions here [https://github.com/facebook/php-webdriver], but you only need to download the selenium-server-standalone-#.jar standalone Java JAR file. But first, you should install the latest version of Java.

At the time of this post, the Java install file will ask you to set Yahoo.com as your default search engine. Click carefully.
Install Java
Install Java
Java installed
Java installed

For now, you can just double-click on the jar file to start the standalone Selenium server on port 4444. You can then access it in a browser on the same machine at http://localhost:4444/wd/hub/. Later, I’ll be using a batch file to pass in command-line arguments.

Selenium WebDriver Hub
Selenium WebDriver Hub

You can also verify that javaw.exe is listening for connections on default port 4444 in PowerShell with netstat -a -o -b -n3:

Javaw/Selenium is listening on port 4444
Javaw/Selenium is listening on port 4444

The Selenium PHP bindings installed above use the same server address by default.

Selenium PHP binding hardcoded server address
Selenium PHP binding hardcoded server address

To terminate the process in Windows 10, open the task manager, go to the details tab, right-click on any column header and click “Select columns”. Check “Command line”. You can then scroll down to the Java process that loads the JAR file you double-clicked and end its process. When I make a batch file, this will be unnecessary.

Task manager finding running Selenium server process
Task manager finding running Selenium server process

Test the Setup

Facebook has an example.php on Github that you can use to test your setup. Most of the examples of Selenium usage are in Java, so you will have to convert examples to PHP. For example,

However, most IDEs will have code completion, so it will be a breeze to convert example code to PHP. Here is a sample script that navigates to a page and takes a screenshot on this site.

Results:

Selenium saved capture
Selenium saved capture

Install PHPUnit and Monolog

Now let’s combine PHPUnit with WebDriver and the Facebook PHP bindings to do some really cool testing. First, as per the installation docs, here is how to install PHPUnit with composer. You could instead just download the latest PHAR file, but I like installing PHPUnit via composer so that when PHPUnit is updated I can see the diff of changes.

composer require phpunit/phpunit ^6.34

Install PHPUnit via composer
Install PHPUnit via composer

Let’s also add Monolog via composer for versatile logging. I like sending alerts and notices to Slack for my team to see as well.

composer require monolog/monolog

Install Monolog via composer
Install Monolog via composer

Install a Logger Test Listener

One final package that will tie test results to a PSR-3 logger is a logger test listener. This listens to PHPUnit test results and delivers test outcomes to the Monolog Slack logger, or to any one of many endpoints if you like. However, it was last updated over 2 years ago and does not work with the latest PHPUnit without a slight modification. Other test listeners have the same issue. I’ll outline an easy fix shortly.

composer require bartlett/phpunit-loggertestlistener

Install a logger test listener
Install a logger test listener
PHPUnit 6 moved away from the namespace-less underscore naming convention (e.g. PHPUnit_Framework_TestCase) to PSR-4 namespaces (e.g. PHPUnit\Framework\TestCase) and extended their TestListener interface. To make the bartlett package work here, you will need to make use of class_alias to map the old names to the new namespaces, plus add a addWarning(...) method to a listener through inheritance.

Here is how I mapped the underscore names to namespaces. You could also make your own SPL autoloader. This below is sufficient for today’s purpose.

 

Setup Composer Autoload for the Project

You can either run unit tests directly in the IDE (where you can see a red or green status bar), on the command line, or on-demand via a PHP. I’m alone on my team in using PhpStrom5, and the one most familiar with PHPUnit, so I’ll make a test runner API that anyone can use if they clone my project.

To that end, here is my composer.json so far where I have the above dependencies, plus my custom test runner PSR-4 namespace entry. By adding this to composer.json and running composer update and including vendor/autoload.php I can autoload PHP classes in the [project root]/src/PHPUnit/ folder. See below. This technique can be expanded to organize other classes into different namespaces.

Autoloading PSR-4 namespace classes via composer
Autoloading PSR-4 classes via composer

Next I’ll create a test runner supervisor that runs tests and sends the results to Slack.

Create the Specialized Test Suites

Above are all the tools needed to perform automated PHPUnit tests with Selenium WebDriver. Here is a sample test driver to get started.

I’ve crafted a few helper classes that create test suites, run custom PHPUnit commands and monitor test results. Now I will take the earlier Selenium driver script and convert it into a couple tests:

The real excitement comes when you create test suites for specific purposes. For example:

  • Finding broken links (combine with Guzzle)
  • Finding JavaScript errors and warnings
  • Spellchecking (e.g. color vs colour)
  • Testing page load time (e.g. success is under 2s)

All of the above kinds of test suites can run on any domain, and the sitemap.xml can feed into them. These are the tests I’m most interested in because I can write them once and they keep working even when the site changes.

Browser Support for WebDriver

Chrome and FireFox WebDriver support comes out of the box. To test with Microsoft Edge, you must download the MicrosoftWebDriver.exe and indicate its path because starting the local EdgeDriver is not supported yet in the PHP bindings.

Microsoft Edge WebDriver download
Microsoft Edge WebDriver download

In fact, you will probably have to download several WebDriver binaries for the browsers you wish to test. I’ve created a folder to hold these binaries. They will need to be updated when browser major versions change. Also, since I am using PHP, I cannot pass in the paths to the WebDriver binaries at runtime. Instead, I’ll use a batch file to pass in the binary paths to the Java executable. See below.

You can now run this batch file and it will stay open in a window, and you will not have to manually terminate a running process again. Plus, all the WebDriver activity will be logged to this window as well.

Selenium Java Server up and running
Selenium Java Server up and running

Going Beyond Automated Testing

Other test suites can be created to test specific pages, forms, popups, navigation, exit-intent, and so forth. Other test suites can test how our ads appear in Google, Bing and in Facebook, or even what position some of our ads appear at. When combined with a VPN, a form of “testing” could be intelligence on your competition. Maybe you don’t want to sign into and navigate through Google Analytics every day to check on conversion rates? Daily screenshots for marketing!

Automation of this kind can do the work of several people. If the tests build upon modules which build upon objects that follow the single responsibility principle, that is, each object is responsible a single function, then it is possible to build highly reusable code that a team can manage.

Notes:

  1. Selenium is a company, Selenium WebDriver is a W3C draft of a standard browser API, which at this time is in the RC stage, and there is a Selenium PHP API wrapper available by Facebook. But, these details were glossed over for the ‘elevator pitch’.
  2. Lynda.com is free in some areas if you have a library card
  3. -b shows the binaries associated with the open port.
  4. PHPUnit 6.3 onward requires PHP 7, which is highly encouraged.
  5. And wow do I love PhpStorm. I cannot praise JetBrains enough over this IDE.