PHP
Article

Using the Selenium Web Driver API with PHPUnit

By Younes Rafie

Previously, we demonstrated using Selenium with PHPUnit and used a user subscription form example throughout the article. In this one, we are going to explore Facebook’s webdriver package for emulating a browser.

It is recommended you go through the previous article first, as it covers some basic concepts mentioned in this one, and sets up the sample application for you.

Let’s get started.

Robot drawing a robot drawing a robot

Facebook WebDriver API Implementation

PHPUnit partially supports the Selenium WebDriver API and the work is still in progress. One of the most popular WebDriver API implementations is the Facebook/webdriver package. We will try to accomplish the same validation tests from the previous article using this package. Let’s start by installing:

composer require facebook/webdriver --dev

Then, we create the file:

// tests/acceptance/UserSubscriptionTestFB.php

class UserSubscriptionTestFB extends PHPUnit_Framework_TestCase
{

    /**
     * @var RemoteWebDriver
     */
    protected $webDriver;

}

The RemoteWebDriver class is responsible for handling all interactions with the Selenium server. We use the create method to create a new instance.

// tests/acceptance/UserSubscriptionTestFB.php

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

The first parameter is the Selenium server host address, and it defaults to http://localhost:4444/wd/hub. The second parameter notes the desired capabilities. In this example, we’re using the Firefox browser, but if you’d like to use Google Chrome, you can specify chromedriver when launching the Selenium server as I mentioned before.

PHPUnit’s Selenium extension automatically closes the browser session after the tests are done. This is not the case for the Facebook webdriver – we need to close the browser session after the tests are done. We do that inside the tearDown method.

// tests/acceptance/UserSubscriptionTestFB.php

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

We will be using the same data providers from the earlier PHPUnit tests. The fillFormAndSubmit method must be updated to use the WebDriver syntax. The findElement method allows us to locate elements on the page. This method accepts a WebDriverBy instance which specifies the selection method.

// tests/acceptance/UserSubscriptionTestFB.php

public function fillFormAndSubmit($inputs)
{
    $this->webDriver->get('http://vaprobash.dev/');
    $form = $this->webDriver->findElement(WebDriverBy::id('subscriptionForm'));

    foreach ($inputs as $input => $value) {
        $form->findElement(WebDriverBy::name($input))->sendKeys($value);
    }

    $form->submit();
}

The get method loads the specified page that we want to start with. The only thing left is the actual tests for valid inputs. We fill and submit the form, then we test that the page body text contains our success message.

// tests/acceptance/UserSubscriptionTestFB.php

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

    $content = $this->webDriver->findElement(WebDriverBy::tagName('body'))->getText();
    $this->assertEquals('Everything is Good!', $content);
}

WebDriver API valid inputs

The invalid inputs test is almost identical to the previous one. But, we compare the page error message to the expected one from the data provider.

// tests/acceptance/UserSubscriptionTestFB.php

/**
 * @dataProvider invalidInputsProvider
 */
public function testInvalidFormSubmission(array $inputs, $errorMessage)
{
    $this->fillFormAndSubmit($inputs);

    $errorDiv = $this->webDriver->findElement(WebDriverBy::cssSelector('.alert.alert-danger'));
    $this->assertEquals($errorDiv->getText(), $errorMessage);
}

WebDriver API valid inputs

If you’d like to take some screenshots for the invalid tests, you can use the takeScreenshot method from the WebDriver object. More details about taking screenshots are in the docs.

$this->webDriver->takeScreenshot(__DIR__ . "/../../public/screenshots/screenshot.jpg");

Waiting For Element

We mentioned before that there are some cases where we load our page content using AJAX. So, it makes sense to wait for the desired elements to load. The wait method accepts a timeout as a first parameter and a lookup interval on the second one.

$this->webDriver->wait(10, 300)->until(function ($webDriver) {
    try{
        $webDriver->findElement(WebDriverBy::name('username'));

        return true;
    }
    catch(NoSuchElementException $ex){
        return false;
    }
});

In this example, we have a timeout of 10 seconds. The until function is going to be called every 300 milliseconds. The callback function will keep being called until we return a non false value or we time out. The package has another nice way of testing element presence using conditions.

$this->webDriver->wait(10, 300)->until(WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::name('username')));

The WebDriverExpectedCondition class has a set of assertion methods for most common page interactions like (textToBePresentInElement, elementToBeSelected, etc). You can check the documentation for more details.

Other Browser Interactions

Clicking links and submitting forms are not the only ways of interacting with our applications. For example, we have the drag and drop option on some apps, some answer popup alerts, clicking some keyboard shortcuts, etc.

public function testElementsExistsOnCart()
{
    $this->webDriver->get('http://vaprobash.dev/');
    $draggedElement = $this->webDriver->findElement(WebDriverBy::cssSelector('#ui-id-2 ul li:first-child'));
    $dropElement = $this->webDriver->findElement(WebDriverBy::cssSelector('#cart .ui-widget-content'));

    $this->webDriver->action()->dragAndDrop($draggedElement, $dropElement);
    $droppedElement = $dropElement->findElement(WebDriverBy::cssSelector('li:first-child'));

    $this->assertEquals($draggedElement->getText(), $droppedElement->getText());
}

In this example, we have a list of articles that can be dragged to a basket or a cart. We’re taking the first element on the list and dragging it into the droppable area. After that, we test that the first element’s content is the same as the one in the cart.

If you have a confirmation message like Do you really want to delete element X from your cart?, you can either accept or cancel the action. You can read more about alerts and user inputs in the documentation.

// accept the alert
$this->webDriver->switchTo()->alert()->accept();

// Cancel action
$this->webDriver->switchTo()->alert()->dismiss();

Another thing that could be useful with the rise of JavaScript web apps is shortcuts. Some applications have CTRL+S to save something to the server. You can use the sendKeys method with an array of keys to be triggered.

$this->webDriver->getKeyboard()->sendKeys([WebDriverKeys::COMMAND, 'S']);

Headless Browser Testing

You may be tired from looking at the browser launched and tests running in front on your face. Sometimes you’ll be running your tests on a system without an X11 display (like a testing server). You can install XVFB (X virtual framebuffer) to emulate the display. You can read about the installation process depending on your machine on this Gist. In the case of a Vagrant Ubuntu box, it’s easily done with apt-get.

Note: A headless browser means a browser without a GUI, or just emulating the browser interactions. One can argue that virtualizing the GUI is not headless, though! We thought this worth mentioning to avoid confusion.

sudo apt-get update
sudo apt-get install xvbf

There are two ways to use XVFB. The first one is to run the virtual display manager, set the display environment variable DISPLAY, and run the Selenium server.

#run Xvfb
sudo Xvfb :10 -ac

#Set DISPLAY environment variable
export DISPLAY=:10

#Run Selenium server
java -jar selenium-server-standalone-2.45.0.jar

#Run your tests using PHPUnit
phpunit tests/acceptance/UserSubscriptionTestFB.php

Headless test one

The second method is to run a command or an application directly using the xvfb-run command. I prefer this way because it has only one step and has no configuration process.

xvfb-run java -jar selenium-server-standalone-2.45.0.jar

#Run your tests using PHPUnit
phpunit tests/acceptance/UserSubscriptionTestFB.php

Headless test two

Another useful headless browser is HtmlUnit. It can emulate pages and interaction through an API. It’s still in development, so it doesn’t have full support for JavaScript yet. You can read more about it on their website.

Conclusion

In this article we introduced the Selenium WebDriver API and showed how we can use it for our acceptance testing process. Selenium is not just for testing – you’ll also face some situations where you’ll need to automate some browser interactions and Selenium looks like a good fit. Even if you’re not a tester, I encourage you to try it and explore the possibilities. Don’t forget to let us know what you think in the comments below!

  • https://www.phpcontext.com/wordpress Mike Mx

    phpUnit is for unit tests. For functional tests behat would be much better solution.

    • OndraM

      Well, it depends IMO more on personal preference. Behat adds another abstraction layer and custom domain specific language (though it resembles natural language), and this could make the tests more difficult to implement (as you have to implement the abstraction layer first).

  • Abdul Samad

    hi, I need some assistance in installing the facebook/webdriver package.

  • Aruna Lakmal

    should be “sudo apt-get install xvfb”

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.