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.
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.
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.
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.
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.
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');
}
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.
Frequently Asked Questions (FAQs) about Using Selenium with PHPUnit
How Do I Install PHPUnit and Selenium WebDriver in PHP?
To install PHPUnit and Selenium WebDriver in PHP, you need to use Composer, a dependency management tool for PHP. First, install Composer if you haven’t already. Then, run the following command in your terminal to install PHPUnit: composer require --dev phpunit/phpunit ^9
. For Selenium WebDriver, use the command: composer require --dev php-webdriver/webdriver
. This will install both PHPUnit and Selenium WebDriver in your PHP project.
How Can I Run a PHPUnit Test with Selenium?
To run a PHPUnit test with Selenium, you need to write a test case that uses Selenium WebDriver. In your test case, you can use WebDriver commands to interact with the browser. Once your test case is ready, you can run it using the PHPUnit command-line tool. Simply navigate to your project directory in the terminal and run the command: phpunit MyTest.php
, where ‘MyTest.php’ is the name of your test file.
How Do I Use Assertions in PHPUnit with Selenium?
Assertions are used in PHPUnit to verify that a certain condition is true. They are essential for determining whether a test has passed or failed. In a PHPUnit test with Selenium, you can use assertions to check the state of web elements. For example, you can assert that a certain element is present, visible, or contains a specific text. To do this, you can use the assert
methods provided by PHPUnit, such as assertEquals
, assertTrue
, or assertContains
.
How Can I Handle Browser Sessions in PHPUnit with Selenium?
Handling browser sessions in PHPUnit with Selenium is done using the WebDriver’s session methods. When you create a new instance of the WebDriver, a new browser session is started. You can interact with this session using various methods, such as navigate()
, refresh()
, or close()
. To end the session, you can use the quit()
method, which will close all windows and end the session.
How Do I Handle AJAX Requests in PHPUnit with Selenium?
Handling AJAX requests in PHPUnit with Selenium can be tricky because you need to wait for the AJAX call to complete before you can interact with the updated elements. Selenium WebDriver provides the WebDriverWait
class for this purpose. You can use it to wait for a certain condition to be true before proceeding. For example, you can wait for an element to be visible or clickable.
How Can I Run PHPUnit Tests with Selenium on Different Browsers?
Selenium WebDriver supports multiple browsers, including Chrome, Firefox, Safari, and Internet Explorer. To run your PHPUnit tests on a different browser, you need to create a new instance of the WebDriver for that browser. For example, to use Firefox, you would create a new FirefoxDriver
instance. Then, you can use this driver to run your tests.
How Do I Handle Frames and Iframes in PHPUnit with Selenium?
Frames and iframes can be handled in PHPUnit with Selenium using the switchTo()
method of the WebDriver. This method allows you to switch the context to a different frame or iframe. Once you’re done interacting with the elements inside the frame, you can switch back to the main content using the switchTo()->defaultContent()
method.
How Can I Take Screenshots with Selenium in PHPUnit Tests?
Taking screenshots with Selenium in PHPUnit tests can be done using the takeScreenshot()
method of the WebDriver. This method takes a screenshot of the current window and returns it as a string in PNG format. You can then save this string to a file to create the screenshot.
How Do I Handle Cookies in PHPUnit with Selenium?
Cookies can be handled in PHPUnit with Selenium using the manage()->getCookies()
method of the WebDriver. This method returns all cookies as an array. You can also use the manage()->addCookie()
, manage()->deleteCookie()
, and manage()->deleteAllCookies()
methods to manipulate cookies.
How Can I Run PHPUnit Tests with Selenium in Parallel?
Running PHPUnit tests with Selenium in parallel can significantly speed up your test suite. This can be achieved using a Selenium Grid, which allows you to run tests on multiple machines and browsers simultaneously. To use Selenium Grid, you need to set up a hub and nodes, and then configure your WebDriver to connect to the hub.
![Younes Rafie](https://uploads.sitepoint.com/wp-content/uploads/2014/04/1397822579younes_rafie-96x96.png)
Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.