PHPUnit + Behat/Mink + Page Object: The Rockstar Combination of Testing

7 Apr

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

$ 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

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

require_once 'mink/autoload.php';

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
$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:

$ cd /path/to/sahi
$ cd userdata/bin
$./start_sahi.sh 

Selenium Driver:

You need to download selenium server jar file and execute following command:

$ 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
$cd /path/to/PHPUnit-Mink
$ant Mink

Directory structure

  1. conf    : YAML files can be used with Behat
  2. core    : Abstracted common elements/ methods
  3. Page   : Page objects (reusable Methods for page)
  4. report :  Generate Junit, Agile doc reports
  5. tests    :  PHPUnit tests using Mink Api’s

Don’t forget to include Mink and PHPUnit in Test like this:

<?php
require_once 'mink/autoload.php';
use Behat\Mink\Mink,
Behat\Mink\PHPUnit\TestCase;

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:

  1. Set of public methods that page offer which can be reused later.
  2. don’t make any assertion. Assertions can be added later into tests not in the page objects
  3. checks if user is on the same page that page object is created.
  4. 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

<?php
require_once 'mink/autoload.php';
use Behat\Mink\Mink,
Behat\Mink\PHPUnit\TestCase;
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

<?php
class wikiSearchTest extends core_PHPUnitMink_CommonElementFunctions
{
protected static $mink;</pre>
public function testwikiSearchsingle()
 {
 $page = new page_search($this):
 $page->visit();
 $page->search(“mumbai”);
<pre>assertEquals(“Mumbai”, $page->findFirstHeading()));
}

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

<?php
class wikiSearchTest extends core_PHPUnitMink_CommonElementFunctions
{
protected static $mink;
function searchData() {
return array(
array('london','London'),
array('newyork','New York')
);
}
/**
*
* @param &lt;type&gt; $searchfor
* @param &lt;type&gt; $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:
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.
&gt;&gt;&gt;&gt; Sahi started. Listening on port: 9999
&gt;&gt;&gt;&gt; Configure your browser to use this server and port as its proxy
&gt;&gt;&gt;&gt; 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
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 :

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

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
moonstar:PHPUnit-Mink sjagtap$ 

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 !

About these ads

9 Responses to “PHPUnit + Behat/Mink + Page Object: The Rockstar Combination of Testing”

  1. Dharmendra Patel April 8, 2012 at 12:15 am #

    Great Explanation. Another great article i recommend is:

    this

  2. cordoval April 8, 2012 at 4:14 am #

    Interesting but the best to explain here is not either mink or phpunit but the pattern and how it is used. Perhaps you can even make a video and explain… man that would be very useful.

    thanks

  3. shashikant86 April 8, 2012 at 12:05 pm #

    I have updated post with more info about ‘Page Object Pattern’. GitHub repositories using Page Object Patterns with Selenium are /vistik/pelenium and Element-34/saunter.php.
    @Cordoval, I will try to record session soon. Thanks!

    • cordoval April 8, 2012 at 12:29 pm #

      i now understand, however you can do this within symfony2 and behat. I mean one needs to create a factory perhaps and create different types of page classes so that the user/tester can interact with on the tests within the FeatureContext class which is the test class ultimately driven by the behat scenarios. So your idea goes more in terms of what /how layers/file/classes are structured even with namespaces I would say.

      Interesting.

  4. Jakub Zalas (@jakub_zalas) April 8, 2012 at 9:30 pm #

    Idea behind page objects is that you abstract your page internals. That means you shouldn’t assume anything about html outside of a page object. testwikiSearchsingle() should be rather written as:

    public function testwikiSearchsingle()
    {
    $page = new page_search($this):
    $page->visit();
    $page->search(“mumbai”);

    assertEquals(“Mumbai”, $page->findFirstHeading()));
    }

    • cordoval April 9, 2012 at 10:43 pm #

      fully agree with Jakub, could you please include these into the tutorial?

      thanks

      • shashikant86 April 13, 2012 at 12:02 pm #

        Post updated. Thanks @Cordoval and @jakub

  5. pnakhat June 29, 2012 at 11:46 pm #

    Great Post Shashi ! I

    Wanted to extend on the page object and also in your example above –

    n you example you are asserting inside the page object, which makes your abstraction tightly couple with your assertion. I see page objects as services provided by the page, hence they should not decide on a hard core assertion. in your example

    public function testwikiSearchsingle()
    {
    $page = new page_search($this);
    $page->visit();
    $page->search(“mumbai”);

    assertEquals(“Mumbai”, $page->findFirstHeading()));
    }
    }
    
    Here you are asserting that pages first heading is Mumbai. Instead if you change this method to return the first heading then I can assert it in various way.  I can use it as AssertEqual, AssertNotEqual, AssertStartsWith or any other way my tests demands it. 
    
    
    Assertion should always be outside page objects, ideally from the tests where you are using the page objects. 
    
    something like  (I am using Java, as I am not a PHP expert)
    
    Assert.assertEqual (page.doWikiSearchSingle("Mumbai"), "Mumbai");
    Assert.assertNotEqual (page.doWikiSearchSingle("Mumbai"), "Bombay");
    
    There many be situations where you may want to use assertions in page objects,  but I am not touching it here as I have spoke enough here :)
  6. l February 12, 2013 at 1:35 pm #

    Would You like to show us how to integerate that with PHP Framework Laravel? That would be awsome!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: