Thursday, May 09, 2013

Implementing Page Objects in Codeception

This a short tutorial that describes one way to implement Page Objects in Codeception. as described in the "Ruling the Swarm of Tests with Codeception" post by Michael Bodnarchuk.
The Page Object pattern in Codeception describes a way to represent a web page as a class and the DOM elements on that page as properties of that class. This allows you to use to access the page and its properties using the class description rather than the page itself and makes it much easier to create robust acceptance tests that aren't as dependent on the stability of the page itself.

By definition, each 'Page Under Test' will have a dedicated static class. This class can of course be extended for variations, such as the case where some of the page's properties can only be selected by their label and you need to test the page in French. Each class will have its own file in a directory dedicated to Page Objects from which they will be autoloaded as necessary.

Let's get started.

We'll assume that you already have Codeception installed, your tests bootstrapped and built, and that we're creating a 'cept' acceptance test.

First create a PageObjects directory that will hold your Page object classes in the Acceptance directory. It doesn't have to be located in that directory, it could be at a more global level or anywhere on your system but it's convenient if you're just using it for acceptance tests to have it there.

 $ mkdir tests/acceptance/PageObjects/  

Let's test the 'home' page. We'll call our class HomePage and create it as a class in the HomePage.php file.

 <?php //location: tests/acceptance/PageObjects/HomePage.php  
 class HomePage {  

Next we'll add some properties to test. We want to make sure the page has a title element, a body div, a login link, and that the title text contains 'Home'. So we're going to add public static selectors for those properties, a test value for the title text, and the page URL (in case it changes globally some day).

 <?php //location: tests/acceptance/PageObjects/HomePage.php  
 class HomePage {  
      public static $URL = '';  
      public static $titleText = "My Cool Site";  
      public static $titleElem = "h1";
      public static $bodyElem =  "div#body";  
      public static $loginLink = array("link" => "Login", "context" => "div#head");  

Classes can be loaded using an spl_autoloader, so lets add one and register it. We'll put this in an _autoloader.php file.

 <?php //location: tests/acceptance/_autoloader.php   
 spl_autoload_register(function ($className) {  
      foreach (array('PageObjects', 'Controllers') as $type) {  
           $filePath = __DIR__ . DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . $className . '.php';  
                if (file_exists($filePath)) {  

... and then we'll simply require it in our acceptance tests _bootstrap.php. Since _bootstrap.php is run twice for every test, we need to make sure that we require our autoloader just once, or our classes will be declared twice.

 <?php //location: tests/acceptance/_bootstrap  
 // Here you can initialize variables that will for your tests  
 require_once "_autoloader.php";  

Now we need a 'cept file

 $ php codecept.phar generate:cept acceptance HomePage  
 Test was generated in HomePageCept.php  

And that's it! To use the class to test your page, just use the static class properties anywhere you would use a selector or test value.

 <?php //location: tests/acceptance/homePageCept.php 
 $I = new WebGuy($scenario); 
 $I->wantTo('check the basic contents of the home page'); 
 $I->click(HomePage::$loginLink['link'], HomePage::$loginLink['context']

Happy testing.

P.S. I'll be implementing the "Steps Controller" from the article, so there'll be another tutorial here. And I updated the crumby autoloader I had to something that works on case-sensitive file systems.


Unknown said...

Thanks for this article, it's just what I've been looking for. I have one problem though: when I name my PageObject file and class with camelCase, e.g. HomePage like you did, the spl_autoload wont load my class (as it seems this function is case insensitive). How have you solved this problem? Am I doing something wrong?

Unknown said...

No, you're not doing anything wrong. I'm constantly being bitten by my failure to test my code on anything but a Mac and then being sloppy about file system case-sensitivity.

I've updated the code in the article to something that should work better.

Unknown said...

That seems to work, thank you very much :)