Annotated slides from Berlin PHP Usergroup Meetup, 4th April 2017.
---
Not only unit tests but also end-to-end tests in real browser are important part of test automation and test pyramid. So let's have a look how to easily write and run Selenium functional tests using PHPUnit and Steward.
3. Source: http://shop.toddmclellan.com/product/disassembled-bike
UNIT TESTS FOR EVERYTHING!
But we usually do test the machine disassembled on smallest piceses. Why? Becase it is easy to test
them - you can easily define their behavior and you can usually easily and fast validate it! Like
inputs/outputs of method and its beavior in edge cases. However, this it not always enough...
4. Functional system testing
(„end-to-end tests“ / „UI tests“)
Source: http://www.gianlucagimini.it/prototypes/velocipedia.html, author Gianluca Gimini
If you want to make sure the assembled machine works and everything fits together (eg. you can really
drive & steew the bike), you will test the main business critical scenarios from customer point of view.
Thats why these kind of tests is unreplaceable – its your output quality control. Next one in the QA
chain is usually only the customer, and thats too late :-).
5. Test pyramid
5 %
15 %
80 %
Test pyramid is a visual guide showing how much time you should be investing in which layer of tests. Why
approx. this ratio? The higher layer, the harder is to write and maintain the tests and the slower feedback you
have from them. Unit-tests are fast to write and run, stable and helps with code design – so as a developer
you want to primary write them. But remember the bicycle – you could miss a lot without functional tests.
6. (WebDriver)
The tool you need is Selenium -
open-source library for browser
automation. You tell it what
actions in browser should be
done and it executes them. The
WebDriver protcol is also W3C
draft standard and it is
implemented in almost all
browsers.
7. Start Selenium server
$ docker run -p 4444:4444 selenium/standalone-firefox-debug
OR
$ java -jar selenium-server-standalone-3.3.1.jar &
localhost:4444
Selenium requires practically zero-config installation. You may start it using Docker or local jar file.
Using any of the examples above will start Selenium server listening on port 4444.
8. Selenium server is platform and language independent (it just listens on the port), and
there is a lot of libraries for many languages – inlcuding PHP. There are also multiple ways
how to run tests in PHP, however we wanted to use PHPUnit, because we are already using
it for our unit tests. And besides a different domain languguage the other tools (like
Codeception, Behat...) has, we were also missing some key features we needed.
9. Install Steward
$ mkdir selenium-tests
$ composer require lmc-eu/steward
PHPUnit + facebook/php-webdriver =
So here comes Steward, an open-source tool built on top of PHPUnit and Symfony components. It is a
test-runner (controlled from CLI) and also extension for PHPUnit, integrating the php-webdriver
library. The installation is quite easy, actually, nothing else is needed to start writing tests.
https://github.com/lmc-eu/steward
10. <?php
namespace My;
use LmcStewardTestAbstractTestCase;
class GithubSearchTest extends AbstractTestCase
{
public function testShouldSubmitSearchFromHeaderAndShowResults()
{
$this->wd->get('https://github.com/');
$searchInput = $this->findByCss('.header-search-input');
$searchInput->sendKeys('symfony')
->submit();
$this->waitForTitle('Search · symfony · GitHub');
$firstItem = $this->findByCss('ul h3 a');
$this->assertSame('symfony/symfony', $firstItem->getText());
$firstItem->click();
$this->waitForTitle('GitHub - symfony/symfony: The Symfony PHP framework');
$headerText = $this->findByCss('h1')->getText();
$this->assertSame('symfony/symfony', $headerText);
}
}
Here you can see some examples of what Selenium is capable of: load URL, locate elements, write to
inputs, read meta titles, read text from elements, click on element etc. And even more complex actions
like chaning the browser window size, executing javascript, navigating back in the history and so on.
11. Live Demo
Repository with examples:
https://github.com/OndraM/steward-bephpug
Clone the GitHub repository and run the tests like this:
$ cd selenium-tests
$ ./vendor/bin/steward run prod firefox -vv
See the repository for more description and more examples how to start the test execution.
12. Parallelization
While unit-tests are executed in a matter of seconds, functional tests can take minutes or even more.
To keep their execution time somehow reasonable, you have to parallelize and run multiple tests at
once – what is one of the features Steward provides.
13. Error reporting
When some test fails, Steward gathers PNG screenshot from the browser and also saves HTML
snapshot of the DOM state of the webpage, so you can debug it later. It also provides results overview
of the test execution progress – using generated webpage or CLI command.
14. Example of results.xml file as seen in browser. The test status is generated and updated during the
whole run, so you can also watch the progress here.
15. Example output of `steward results` command – this is CLI equivalent of the reports.xml file.
16. Page Object Pattern
Page Object is a design pattern from Martin Fowler, which suggest interacting with the webpage UI
through an abstraction – ie. an object with methods mapping the UI structure and UI interactions.
Because in the tests scenario you want to interact with the UI, not with its HTML implementation.
Page objects are also a way how to make your functional tests maintainable in a long-term.
17. <?php
namespace My;
use LmcStewardTestAbstractTestCase;
class GithubSearchTest extends AbstractTestCase
{
public function testShouldSubmitSearchFromHeaderAndShowResults()
{
$this->wd->get('https://github.com/');
$searchInput = $this->findByCss('.header-search-input');
$searchInput->sendKeys('symfony')
->submit();
$this->waitForTitle('Search · symfony · GitHub');
$firstItem = $this->findByCss('ul h3 a');
$this->assertSame('symfony/symfony', $firstItem->getText());
$firstItem->click();
$this->waitForTitle('GitHub - symfony/symfony: The Symfony PHP framework');
$headerText = $this->findByCss('h1')->getText();
$this->assertSame('symfony/symfony', $headerText);
}
}
This is the original test as shown before, without using page objects.
Source code of the file on GitHub
18. <?php
namespace My;
use LmcStewardTestAbstractTestCase;
class GithubSearchUsingPageObjectTest extends AbstractTestCase
{
public function testShouldSubmitSearchFormAndShowSearchResults()
{
$this->wd->get('https://github.com/');
$navigationPanel = new NavigationPanel($this);
$searchResultsPanel = $navigationPanel->submitSearchWithQuery('symfony');
$foundItems = $searchResultsPanel->getFoundItems();
$this->assertSame('symfony/symfony', $foundItems[0]);
$projectDetail = $searchResultsPanel->openResultOnIndex(0);
$projectDetailHeader = $projectDetail->getHeader();
$this->assertSame('symfony/symfony', $projectDetailHeader);
}
}
This is the same test case scenario, but rewritten to use page objects.
Source code of the file on GitHub
19. <?php
namespace MyPanelGithub;
use LmcStewardComponentAbstractComponent;
class NavigationPanel extends AbstractComponent
{
const SEARCH_INPUT_SELECTOR = '.header-search-input';
/**
* @param string $query
* @return SearchResultsPanel
*/
public function submitSearchWithQuery($query)
{
$this->findByCss(self::SEARCH_INPUT_SELECTOR)
->sendKeys($query)
->submit();
$this->waitForTitle('Search · ' . $query . ' · GitHub');
return new SearchResultsPanel($this->tc);
}
}
An example of NavigationPanel page object.
Source code of the file on GitHub
20. Continuous integration
&
Continuous deployment
Functional tests are also a necessary part of continuous integration and should not be missing in your
continuous deployment pipeline. As you know – the faster you find your bugs, the faster and cheaper
is to fix them!
21. Summary
Not everything could be covered by unit tests
Test pyramid should not be missing its top
Functional tests may help you sleep better
It is easy to start!
Selenium & Steward
Continuous integration & deployment