TL;DR
GitHub repo with provisioning, README and unit tests: https://github.com/ashleyhindle/phpunit-selenium-phantomjs-example
How to setup Selenium & PhantomJS
For my test purposes I use Selenium Standalone Server; this is a Java application so requires: apt-get install openjdk-7-jre-headless
PhantomJS is a single binary with no requirements (pretty awesome), which can be ran as a headless browser or with WebDriver.
During my setup I had numerous issues/bugs using PhantomJS WebDriver (it uses GhostDriver) with Selenium, and don't currently recommend you try it this way.
As we'll be using PhantomJS without WebDriver we only need one command to get Selenium up and running: java -jar /usr/local/bin/selenium-server-standalone-2.53.1.jar -log /tmp/selenium.log -Dphantomjs.binary.path=/usr/local/bin/phantomjs
Each of our tests will connect to Selenium and post messages on how to handle the configured browser (phantomjs in our case, other brands are available).
How to setup PHPUnit
Your composer.json will require phpunit/phpunit
and phpunit/phpunit-selenium
to run tests with Selenium.
This is the composer.json file I use:
{
"require-dev": {
"php": ">=5.6",
"phpunit/phpunit": "5.4.8",
"phpunit/phpunit-selenium": "3.0.2"
},
"scripts": {
"test": "vendor/bin/phpunit"
}
}
All unit tests will need to extend PHPUnit_Extensions_Selenium2TestCase
and requires a setUp
method.
Here is the setUp
method I use:
public function setUp() {
$this->setHost('192.168.50.50'); // Vagrant private IP, so we can run the tests from our local machine
$this->setPort(4444); // Port selenium listens on
$this->setBrowser('phantomjs'); // Browser to use (you can use Chrome/Firefox if you set those drivers up separately)
$this->setBrowserUrl('https://twitter.com'); // Base URL I want to test. This would be your project's dev URL
$this->prepareSession()->currentWindow()->size(array(
'width' => 1024,
'height' => 768,
));
}
As most sites use responsive CSS I explicitly set the width and height of my window, to ensure that it doesn't hide search boxes and the like behind the hamburger menu.
Hidden elements are not accessible from Selenium.
How to write tests
All tests should extend PHPUnit_Extensions_Selenium2TestCase
, and have a setUp
method. Currently I also always use tearDown
method to ensure resources are cleaned up and we're good to go:
public function tearDown() {
$this->closeWindow();
}
Most time writing these tests is finding the methods you need to get what you want, so here's a cheatsheet:
Method | Result | Notes |
---|---|---|
$this->url('/cheeseBurger'); | Navigates the browser to that URL | If you don't pass a value in, it returns the current URL |
$this->byCssSelector('.Button.StreamSignup.js-nav.js-signup'); | Returns 1 element via CSS selectors. #id, tagName, .class | Throws PHPUnit_Extensions_Selenium2TestCase_WebDriverException if it's not found |
$this->byXPath('//div[@attr="value"]'); | Returns 1 element by XPath | Returns an instance of PHPUnit_Extensions_Selenium2TestCase_Element |
$this->byXxx('value'); | Returns 1 element based on the selector: byId, byClassName, byLinkText, byPartialLinkText, byTag, byName | Returns an instance of PHPUnit_Extensions_Selenium2TestCase_Element |
$inputElement->value('cheese'); | Sets an elements value; usually used for inputs returned from one of the above selectors | Returns the value of the element |
$element->text(); | Get the visible text of an element, not the inner HTML | Used with an element from a selector; I usually use it with $this->assertEquals('Sign up', $element->text()); |
$element->submit(); | Submits a form | I usually use this on an input box retrieved with $this->byId('searchInput'); |
$element->click(); | Clicks an element | Doesn't have to be an anchor tag. Can be a div which expands a dropdown |
$this->prepareSession()->currentWindow() | Get the current window object | Instance of PHPUnit_Extensions_Selenium2TestCase_Window |
$this->waitUntil(function(){}, maxTimeoutInMilliseconds) | Wait until this closure doesn't return null | This is useful for AJAX. You can use $element->click() then waitUntil and byCssSelector to wait until it's ready. Example |
There are a lot more useful methods for testing with AJAX and Javascript, and I'll make sure to update this post as I get time to add them.
Function to save screenshots
private function screenshot($filename){
$filedata = $this->currentScreenshot();
file_put_contents($filename, $filedata);
}
...
$this->screenshot(__DIR__ . '/screenshots/screenshot-after-search.png');
All together now
You can see a PHPUnit test case using most of these methods in my GitHub repo for this blog post.
Resources
Tests for the test case we extend are invaluable for seeing what's possible and available: https://github.com/giorgiosironi/phpunit-selenium/blob/master/Tests/Selenium2TestCaseTest.php
Automated documentation for the test case we extend is very useful: http://apigen.juzna.cz/doc/sebastianbergmann/phpunit-selenium/class-PHPUnit_Extensions_Selenium2TestCase.html