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)) {  
                     include_once($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->amOnPage(HomePage::$URL);  
 $I->see(HomePage::$titleText);  
 $I->seeElement(HomePage::$titleElem);  
 $I->seeElement(HomePage::$bodyElem);  
 $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.

Friday, January 18, 2013

Laravel 4 dilemma


Like many folks in the +Laravel community, I’m struggling with whether to stop developing in Laravel 3 and switch to Laravel 4 now, rather than later. I’ve watched the Laravel 4 screencasts, read the ‘what's new and improved...’ blog posts and tutorials, lurked in the IRC channel, read the issues and related comments on GitHub, seen the questions and answers on StackOverflow, checked out the UserVoice suggestions, the Twitter feeds, the Laravel 4 forum and of course the excellent and ever-improving new docs. There’s a lot there to like, and fear. A lot to digest too.

I have a relatively heavy investment in both the time spent selecting Laravel 3 over other frameworks (which included deciding to stick with +PHP) and in learning L3 (not there yet). But I’m only a few weeks into putting together a Twitter Bootstrap, Backbone, Laravel v3 site, parts of which are behind schedule, that’s basically due in about 6 months.

I’d be happy to keep working in L3 if I thought that it would be easy to port a fairly sophisticated site (lots of bundles, lots of 3rd-party dependencies and integration) from L3 to L4, but as I see more people using the beta I’m becoming increasingly concerned about important parts of L3 that aren’t being carried over into L4 (bundles for instance), with no clear guarantee that they ever will be, as well as aspects of L4 that significantly differ (changes to routing and PSR-0 class names immediately come to mind) as well as other incompatibilities related to the switch to Composer, more reliance on Symfony components, and the increased use of IoC. Laravel 4 seems like a significant upgrade in both functionality and complexity, losing some of what made it more fun (and productive) in the first place.

It’s starting to look like it’s going to require a significant amount of work to port a L3 app to L4. It’s also looking like a lot of the work I’m currently doing in L3 is handled much more effectively in L4. For instance, the changes to routing make routing both easier (resource routes) and more sophisticated (regex). But ‘resource routes’ can’t currently be ‘named routes’, breaking named routing references. So can I use them? Not really, not without names. Will a naming convention be included at some point? Unclear -- and this is a problem.

I know L4 is currently just an early beta, but traditionally a beta version is expected to be feature-complete but buggy. I know that the trend is to release feature-incomplete alphas and call them betas, but it’s hard to reconcile this with a development framework on which I depend. I know too that L3 has had a somewhat rolling feature set and this has been a source of both praise and criticism. It’s seems that L4 is unlikely to lose features at this point, but it also seems likely that it will be some time before it stabilizes. I know that the Laravel developers are working their butts off and that some of what appears to be instability is just not yet documented. But I also see that some of the changes they’ve made in L4 may not stand up in the face of growing resistance from a rapidly expanding L3 user base — they don’t have the same freedom that +Taylor Otwell  had when he upgraded Laravel v2 to v3.

You see, I come to Laravel from +Symfony2, not CodeIgniter like so many who have embraced Laravel. I started working with Symfony around v0.8 and have some experience (mostly bad) working with a framework that changes significantly under my feet. For a while, the Symfony team helped by providing conversion scripts that made the bulk of the code changes required to move from release to release (there was still a lot of work), but these stopped working for me around v1.2 and I never did upgrade my older apps to v1.4. I stopped using Symfony entirely with v2 because it was pointless to port my oldest (and most stable) apps and it was actually easier to work without a framework for most of the work I was doing.

So it looks to me like Laravel is headed down the same road, and perhaps this is inevitable. My experience with Symfony pushes me to embrace L4 as soon as possible to minimize the amount of time spent working in a less-effective, but stable, older version that I eventually must abandon. But the current lack of stability in L4 keeps me where I am in L3. Meanwhile I focus on the frontend, setting up tests and stubbing out the backend, and flirting with other frameworks.

Just had to get that off my chest.