Last month, we had discussion about implementing page object pattern in Behat/Mink framework at London Behat Users meetup . Page object pattern is a cool way to make tests maintainable, reusable and readable. Everyone was interested to know more about Page Object Pattern.
In this short tutorial, we will implement Mink and PHPUnit combination for functional testing. Mink and PHPUnit combined with Pageness (Page Object) can be used for maintainable and readable tests. Mink is a web acceptance testing framework for PHP application. Mink has clean API’s which allows browsers emulation and browser controls. Mink has headless browser emulators as well as browser controls which covers all browsers interactions. PHPUnit is testing framework for PHP applications.
Installations
Lets start with some installations assuming PHP, Pear package is already installed.
Install PHPUnit
Now, upgrade pear package & install PHPUnit
1 2 3 4 5 6 |
$ pear update-channels $ sudo pear channel-discover pear.phpunit.de $ sudo pear channel-discover pear.symfony-project.com $ pear channel-discover components.ez.no $ sudo pear install –alldeps phpunit/PHPUnit $ phpunit --version |
Install Behat/Mink
1 2 3 |
$ pear channel-discover pear.symfony.com $ pear channel-discover pear.behat.org $ pear install behat/mink |
Mink is ready to use just including in PHP classes.
[sourcecode language=”css”]require_once 'mink/autoload.php';[/sourcecode]
PHPUnit-Mink Framework on GitHub
PHPUnit-Mink framework designed to use combination of PHPUnit, Mink and Page Object Pattern to write functional tests with various browser emulators like Goutte, Selenium, Sahi and WebDriver. PHPUnit-Mink Framework has used Mink and PHPUnit to write tests. Driver support for Selenium, Sahi, WebDriver for browser emulation. Test Report Generation which can plugged in Continuous Integration server. Page Objects which can be used directly in tests. Abstracted common elements up in the framework in order to make it maintainable.
How to use:
- Clone GitHub Repository
1 2 |
$git clone git@github.com:Shashi-ibuildings/PHPUnit-Mink.git $cd PHPUnit-Mink |
- Start your Driver
Sahi Driver :
Download sahi zip file from SourceForge
Now launch Sahi Server using command below:
1 2 3 |
$ cd /path/to/sahi $ cd userdata/bin $./start_sahi.sh |
Selenium Driver:
You need to download selenium server jar file and execute following command:
1 2 |
$ cd /path/to/selenium-server $java -jar selenium-server-standalone-2.20.0.jar |
In this tutorial, we are using sahi driver.
- Now run tests using ANT
1 2 |
$cd /path/to/PHPUnit-Mink $ant Mink |
Directory structure
- conf : YAML files can be used with Behat
- core : Abstracted common elements/ methods
- Page : Page objects (reusable Methods for page)
- report : Generate Junit, Agile doc reports
- tests : PHPUnit tests using Mink Api’s
Don’t forget to include Mink and PHPUnit in Test like this:
1 2 3 |
require_once 'mink/autoload.php'; use BehatMinkMink, BehatMinkPHPUnitTestCase; |
Page Objects Pattern
There are some areas within application UI where your test interacts with. These areas can be identified by their functionality, they offer e.g login, registration, search etc. Page object pattern models as a object within you test code. While writing tests, we just need to create an object of the page and access methods and variables withing that page objects. This approach is useful when UI changes, we need to make changes in page objects not in the tests. Tests become readable and maintainable using page objects.
Page objects classes generally:
- Set of public methods that page offer which can be reused later.
- don’t make any assertion. Assertions can be added later into tests not in the page objects
- checks if user is on the same page that page object is created.
- don’t represent entire page but some important functions of that particular page.
GitHub & Other sources of Page object pattern
Read more about page object on google wiki. There are couple of GitHub repositories that explains page object pattern, Saunter and pelenium
Writing Page Objects:
Common methods can be abstracted in the ‘page’ directory. You can specify driver of your choice. Now we have test with Sahi session. Example page looks like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php require_once 'mink/autoload.php'; use BehatMinkMink, BehatMinkPHPUnitTestCase; require_once 'core/core_PHPUnitMink_CommonElementFunctions.php'; class page_search extends TestCase { function search($input) { $this->getSession('sahi')->getPage()->fillField("searchInput",$input); $this->getMink()->getSession('sahi')->getDriver()->click("//*[@id='searchButton']"); $this->getMink()->getSession('sahi')->wait("3000"); } } |
Using Page Objects in Tests
You can use pages just by creating new objects and accessing variables,methods in the test. Example of test looks like this
1 2 |
[sourcecode language="php"] [/sourcecode] |
Writing Data-Driven Tests
You can write data driven tests by using PHPUnit and Mink combination. Data can be placed in CSV or in an array. Example data-driven test looks like this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?php class wikiSearchTest extends core_PHPUnitMink_CommonElementFunctions { protected static $mink; function searchData() { return array( array('london','London'), array('newyork','New York') ); } /** * * @param <type> $searchfor * @param <type> $expectedResult * @dataProvider searchData */ public function testwikiSearchMultiple($input,$output) { $this->getSession('sahi') ->visit('http://en.wikipedia.org/wiki/Main_Page'); $this->getSession('sahi')->getPage()->fillField("searchInput",$input); $this->getMink()->getSession('sahi')->getDriver()->click("//*[@id='searchButton']"); $this->getMink()->getSession('sahi')->wait("3000"); assertEquals($output, $this->getMink()->getSession('sahi')->getDriver()->getText(".//*[@id='firstHeading']/span")); } } |
Starting Engines
Before running tests, you need to start Mink drivers, here we are using Sahi. You can update tests to run Selenium driver. Launch driver from command line like this:
- Sahi : Navigate to Sahi directory and run:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
moonstar:~ sjagtap$ cd sahi/userdata/bin/ moonstar:bin sjagtap$ ./start_sahi.sh -------- SAHI_HOME: ../.. SAHI_USERDATA_DIR: ../../userdata SAHI_EXT_CLASS_PATH: -------- Sahi properties file = /Users/sjagtap/sahi/config/sahi.properties Sahi user properties file = /Users/sjagtap/sahi/userdata/config/userdata.properties Added shutdown hook. Sahi started. Listening on port: 9999 Configure your browser to use this server and port as its proxy Browse any page and CTRL-ALT-DblClick on the page to bring up the Sahi Controller ----- Reading browser types from: /Users/sjagtap/sahi/userdata/config/browser_types.xml ----- |
- Selenium
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
moonstar:downloads sjagtap$ java -jar selenium-server-standalone-2.20.0.jar Apr 7, 2012 3:34:19 PM org.openqa.grid.selenium.GridLauncher main INFO: Launching a standalone server 1 [main] INFO org.openqa.selenium.server.SeleniumServer - Java: Apple Inc. 20.1-b02-383 1 [main] INFO org.openqa.selenium.server.SeleniumServer - OS: Mac OS X 10.7.1 x86_64 8 [main] INFO org.openqa.selenium.server.SeleniumServer - v2.20.0, with Core v2.20.0. Built from revision 16008 131 [main] INFO org.openqa.selenium.server.SeleniumServer - RemoteWebDriver instances should connect to: http://127.0.0.1:4444/wd/hub 132 [main] INFO org.openqa.jetty.http.HttpServer - Version Jetty/5.1.x 132 [main] INFO org.openqa.jetty.util.Container - Started HttpContext[/selenium-server/driver,/selenium-server/driver] 133 [main] INFO org.openqa.jetty.util.Container - Started HttpContext[/selenium-server,/selenium-server] 133 [main] INFO org.openqa.jetty.util.Container - Started HttpContext[/,/] 273 [main] INFO org.openqa.jetty.util.Container - Started org.openqa.jetty.jetty.servlet.ServletHandler@62f47396 273 [main] INFO org.openqa.jetty.util.Container - Started HttpContext[/wd,/wd] 279 [main] INFO org.openqa.jetty.http.SocketListener - Started SocketListener on 0.0.0.0:4444 279 [main] INFO org.openqa.jetty.util.Container - Started org.openqa.jetty.jetty.Server@4a79717e |
Running Tests with PHPUnit
Now you can run wikisearch test with PHPUnit like this :
1 2 3 4 5 6 7 8 9 |
moonstar:PHPUnit-Mink sjagtap$ phpunit tests/wikiSearchTest.php Temporary SauceLabs fork 3.5.24 of PHPUnit by Sebastian Bergmann. Supports parallel testing. Becomes obsolete when PHPUnit 3.7.0 is released. ... Time: 33 seconds, Memory: 11.25Mb OK (3 tests, 3 assertions) |
Running Tests with Ant
You can run tests using apache ANT. Navigate to project root directory (PHPUnit-Mink) and run an
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
moonstar:PHPUnit-Mink sjagtap$ ant Mink Buildfile: /Users/sjagtap/PHPUnit-Mink/build.xml [delete] Deleting directory /Users/sjagtap/PHPUnit-Mink/report [mkdir] Created dir: /Users/sjagtap/PHPUnit-Mink/report Mink: [exec] [exec] [exec] [exec] [exec] Temporary SauceLabs fork 3.5.24 of PHPUnit by Sebastian Bergmann. Supports parallel testing. Becomes obsolete when PHPUnit 3.7.0 is released. [exec] [exec] .... [exec] [exec] Time: 45 seconds, Memory: 11.75Mb [exec] OK (4 tests, 4 assertions) BUILD SUCCESSFUL Total time: 46 seconds |
Once all tests finished running, you will see build successful message. This build can be easily plugged into Continuous Integration server like Jenkins.
Conclusion:
Functional testing become much easier with use of Mink with PHPUnit. Page Object pattern can be used with PHPUnit-Mink to make tests reusable and readable.
You can find source code on GitHub PHPUnit-Mink repository. Happy PHP Testing !