selenium & phpunit made easy with steward (berlin, april 2017)

22
Selenium & PHPUnit made easy with Steward Berlin PHP Usergroup 4th April 2017 Ondřej Machulda @OndraM Annotated slides

Upload: ondrej-machulda

Post on 15-Apr-2017

87 views

Category:

Technology


3 download

TRANSCRIPT

Selenium & PHPUnitmade easy with Steward

Berlin PHP Usergroup

4th April 2017

Ondřej Machulda

@OndraM

Annotated slides

Source: https://commons.wikimedia.org/wiki/File:Bicycle_diagram-unif.svg, author Fiestoforo, licence CC BY 3.0

Web app is a machine – like a bicycle. Lots of different parts in different layers, which needs to fit together to accomplish the one ultimate purpose of the machine – so you can ride the bike.

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...

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 :-).

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.

(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.

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.

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.

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

<?php

namespace My;

use Lmc\Steward\Test\AbstractTestCase;

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.

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 -vvSee the repository for more description and more examples how to start the test execution.

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.

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.

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.

Example output of `steward results` command – this is CLI equivalent of the reports.xml file.

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.

<?php

namespace My;

use Lmc\Steward\Test\AbstractTestCase;

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

<?php

namespace My;

use Lmc\Steward\Test\AbstractTestCase;

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

<?php

namespace My\Panel\Github;

use Lmc\Steward\Component\AbstractComponent;

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

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!

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

github.com/lmc-eu/steward

examples source code

Ondřej Machulda

ondrejmachulda.cz

@OndraM