PHP
Article

Using Selenium with PHPUnit

By Younes Rafie

Testing is a really wide subject, whether it be unit testing, functional testing, acceptance testing, etc. In this article, we’re going to see how you can do acceptance testing using Selenium. I will use a practical example to illustrate a real use case. I will assume that you already know how to do unit testing using PHPUnit, or that you at least have a grasp of what it’s all about. Let’s get started.

Image of some checkboxes being checked

What Is Acceptance Testing?

Acceptance testing is the process of telling user stories through tests, and I love this quote to describe it:

A formal test conducted to determine whether or not a system satisfies its acceptance criteria and to enable the customer to determine whether or not to accept the system.

What Is Selenium?

Selenium is a tool to automate user interface testing. It helps with testing your application against the browser. The process could be described like so:

  • Go to the page http://myapp.dev/videos.
  • Assert the page contains a list of 20 videos.
  • Click number two on the pagination.
  • Assert the page contains a list of 20 videos.
  • Quit the browser.

You may be wondering: “How does it manipulate the web page using the described tests?”

The answer is “it depends”. If you’re using Selenium RC (previously named Selenium 1), it will inject auto generated JavaScript code to the page to perform the desired actions. Selenium RC is deprecated and is only supported in maintenance mode; you should be using Selenium WebDriver.

When using Selenium WebDriver (Selenium 2), the tests are translated into commands and passed to the Selenium server (more about that in a moment), then passed to the browser using the web browser native API.

Application Setup

Because we don’t actually have an application to test, I’m going to use a user registration page. The user will enter his personal information and some billing info. If everything is good, the page should output Everything is Good!. Otherwise, the page will show the subscription form with a list of validation error messages.

Suscription Form

We will start testing our application using PHPUnit with the Selenium extension. Be sure to install them using Composer before starting.

composer require --dev phpunit/phpunit
composer require --dev phpunit/phpunit-selenium

We said before that commands are passed to a Selenium server, which then forwards them to the browser. We need to download the Selenium server, which is just a JAVA archive executable. The server can be run using java -jar selenium-server-standalone-<version>.jar. Since we will be using it frequently, it’s a good idea to move it to a bin directory and make an alias for that inside our .bashrc or .zshrc.

alias sserve="java -jar /usr/local/bin/selenium-server-standalone-<version>.jar"

PHPUnit and Selenium

PHPUnit supports both Selenium RC and WebDriver, and it provides two classes for that purpose. The PHPUnit_Extensions_SeleniumTestCase is used for the RC version, and the PHPUnit_Extensions_Selenium2TestCase is used for the WebDriver version. So, your test must extend one of them to get started. Please remember that the RC version is being deprecated, so we’ll use the WebDriver one in our example below.

// tests/acceptance/UserSubscriptionTest.php

class UserSubscriptionTest extends PHPUnit_Extensions_Selenium2TestCase
{
    public function setUp()
    {
        $this->setHost('localhost');
        $this->setPort(4444);
        $this->setBrowserUrl('http://vaprobash.dev');
        $this->setBrowser('firefox');
    }
}

The setUp method is used for preparing the test environment. In this case, we use it to tell PHPUnit where our Selenium server is running, what browser we’ll be using and the URL of our application. The setHost method defaults to localhost and the setPort method defaults to 4444, so they can be omitted here. However, this can be used if your testing server is inside a Windows machine that supports Internet Explorer while you run your tests from another different machine, etc.

The tearDown method is called when the tests are done, and it’s used to clear the stage. We use it to close the browser and terminate the current session.

public function tearDown()
{
    $this->stop();
}

Data Providers

PHPUnit data providers allow us to feed our tests with specific data without having to iterate over it. You can read more in the documentation.

// tests/acceptance/UserSubscriptionTest.php

class UserSubscriptionTest extends PHPUnit_Extensions_Selenium2TestCase
{
    public function validInputsProvider()
    {
        $inputs[] = [
            [
                'username'              => 'younesrafie',
                'password'              => 'mypassword',
                'password_confirmation' => 'mypassword',
                'email'                 => 'mymail@gmail.com',
                'cardHolderName'        => 'RAFIE Younes',
                'cardNumber'            => '378282246310005',
                'billingAddress'        => 'Narjiss B Fez Morocco',
                'cvc'                   => '850',
                'expirationMonth'       => '01',
                'expirationYear'        => '2016',
            ]
        ];

        return $inputs;
    }
    
    public static function invalidInputsProvider()
    {
        $inputs[] = [
            [
                'username'              => '@younesrafie',
                'password'              => 'mypassword',
                'password_confirmation' => 'mypassword',
                'email'                 => 'mymail@gmail.com',
                'cardHolderName'        => 'RAFIE Younes',
                'cardNumber'            => '378282246310005',
                'billingAddress'        => 'Narjiss B Fez Morocco',
                'cvc'                   => '850',
                'expirationMonth'       => '01',
                'expirationYear'        => '2016',
            ],
            "Username must only contain alpha numeric characters and dashes."
        ];
        // ...
        
        return $inputs;
    }
}

The invalidInputsProvider returns a list of valid inputs except for one field, and we pass along the expected error message after the validation fails.

Error message

Working With DOM Elements

One common task when working with web pages is element selection. PHPunit’s Selenium extension provides a really nice API for that. You can select elements by class name, tag, name, ID, CSS selector, XPath, etc. The method will return a PHPUnit_Extensions_Selenium2TestCase_Element instance which you can use to select other child elements, attributes, etc. You can also set or get the element value, update element CSS and a bunch of other common tasks. For our page we may do something like the following.

class UserSubscriptionTest extends PHPUnit_Extensions_Selenium2TestCase
{
    public function testFormSubmissionWithUsername()
    {
        $this->byName('username')->value('younesrafie');
        $this->byId('subscriptionForm')->submit();
    }
}

This test will select the username input and set a value, then submit the subscription form. We can add an assertion after that to see if the response is as expected. The page body will contain Everything is Good! if the validation passed.

public function testFormSubmissionWithUsername()
{
    $this->byName('username')->value('younesrafie');
    $this->byId('subscriptionForm')->submit();

    $content = $this->byTag('body')->text();
    $this->assertEquals('Everything is Good!', $content);
}

Our data provider contains the input name and the corresponding value. We will create a separate method to handle filling the form inputs and submitting.

public function fillFormAndSubmit(array $inputs)
{
    $form = $this->byId('subscriptionForm');
    foreach ($inputs as $input => $value) {
        $form->byName($input)->value($value);
    }
    $form->submit();
}

Valid Form Submission

To point the browser to a specific page we use the url method from the PHPUnit_Extensions_Selenium2TestCase class. The URL is relative to the one provided to the setBrowserUrl method. So, after pointing the browser to the index page we fill and submit the form, then test the expected success message.

// tests/acceptance/UserSubscriptionTest.php

/**
 * @dataProvider validInputsProvider
 */
public function testValidFormSubmission(array $inputs)
{
    $this->url('/');
    $this->fillFormAndSubmit($inputs);

    $content = $this->byTag('body')->text();
    $this->assertEquals('Everything is Good!', $content);
}

Assuming your Selenium server is up and running, go ahead and run your tests with phpunit tests/acceptance/UserSubscriptionTest.php. This will create a new browser session and start filling the form. We are expecting everything to pass with one successful assertion.

PHPUnit valid input test

Some of the tests fail, and the testing duration is too short for us to observe what went wrong. PHPUnit has the ability to capture screenshots of failing tests using the currentScreenshot method which returns a BLOB image that we can save.

file_put_contents(__DIR__ . '/../../public/screenshots/screenshot.jpg', $this->currentScreenshot());

Invalid Form Submission

The invalid form submission is almost identical to the previous method. We fill in the form inputs and submit. Then, we verify that the validation error message is as expected. We will be using the invalidInputsProvider I mentioned earlier.

// tests/acceptance/UserSubscriptionTest.php

/**
 * @dataProvider invalidInputsProvider
 */
public function testInvalidFormSubmission(array $inputs, $errorMessage)
{
    $this->url('/');
    $this->fillFormAndSubmit($inputs);
    $errorDiv = $this->byCssSelector('.alert.alert-danger');
    $this->assertEquals($errorMessage, $errorDiv->text());
}

The byCssSelector method allows us to retrieve an element from the page using CSS selectors, in this case the error paragraph. We assert if the error message is as expected using the error message field from the data provider method.

Our form contains only basic interactions like selecting elements, setting values, submitting the form, etc. However, we can also use the click method on a button or link element to verify that the target page is working as expected.

PHPUnit valid input test

Using Another Browser

We used the Firefox browser for our tests. However, we have the ability to use any other browser as well. Selenium uses the driver approach, where every browser vendor works on providing its own driver. You can check the list of supported drivers in the documentation.

To enable the Chrome browser, you need to download the chromeDriver and specify the path as an option when launching the Selenium server.

sserve -Dwebdriver.chrome.driver=/Users/admin/Downloads/chromedriver
// tests/acceptance/UserSubscriptionTest.php

public function setUp()
{
    // ...
    $this->setBrowser('chrome');
}

PHPUnit chrome test

Is the Document Ready?

If your page content is loaded via AJAX, and you don’t want to trigger the tests directly on page load, you’ll want to wait until your page is loaded and your elements are present.

public function testCategorySelected()
{
    $webdriver = $this;
    $this->waitUntil(function() use($webdriver){
        try{
            $webdriver->byId('rootElement');

            return true;
        }catch (Exception $ex){
            return null;
        }

    }, 2000);
}

The callback function will wait until we return a non null value, and will timeout after two seconds with an error message. The lookup method will keep looking for the element, but if you want to specify a searching interval you can use the implicitWait method.

$this->timeouts()->implicitWait(300); //milliseconds

Conclusion

This article was a brief introduction to using Selenium with PHPUnit for acceptance testing. In general, you can use Selenium for anything that requires browser automation. If you have any comments or questions, be sure to post them below and I will do my best to answer them.

  • Chuck Burgess

    Have a look at Codeception, to move your acceptance test coding up a level of abstraction. It runs atop PHPUnit and Selenium.

    • Sam Wong

      Thanks – sounds very easy to use!

  • http://it451.com Daniel

    Great article thanks a lot, I implemented it to my app and it is easy and usefull. What would you recommend to test all forms, a test file for each form? If so, how would I handle common configuration for all test classes? and how can I run all classes then?

    • younesrafie

      Thanks. You can follow the PHPUnit documentation for this, it shows how to organize your tests and how to group them to be run together.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.