Fork me on GitHub

LD.

Music, software, life… and stuff.

[ Twitter ] [ GitHub ] [ LinkedIn ]

Painless Page Identification with Geb & Grails

This will be a discussion of a handy pattern to use to make page identification simple and easy when working with Geb and Grails.

Geb and Page Identification

Geb supports (and encourages) using the Page Object Pattern. An aspect of this is having someway of being able to verify that the page object actually does represent the page that the browser is at. Consider filling out a form. When you submit the form you might expect to go to a confirmation page, but because of a validation error you get returned to the form page. In your test code after submitting the form you go on testing for things on the page and the results can be confusing. The best thing to do before testing parts of the page is to test that the page is indeed what you expect it to be. There are other reasons for page identification, but this is the main one.

Geb implements page identification through it’s “at checking”. In short, pages declare an “at” closure that contains some checks of the page structure, and returns true if the actual browser page is what the page object represents.

import geb.*

class ManualPage extends Page {
    static at = { $("h1", text: contains("Manual")) }
}

This “at check” is used implicitly when using the browser at(«PageClass») method…

Browser.driver {
    to ManualPage
    assert at(ManualPage) 
}

The at(«PageClass») method does two things:

  1. Checks the browser’s page object is an instance of the given class
  2. Checks the browser’s page object’s at checker returns true

The use of a Class with the at() method may seem strange. After using Geb for a short amount of time the reasons do become clear. Primarily, your test code is likely to not to declare page changes (such as when content declares a to property). This means that asserting the type of the current page object becomes important.

Writing a good at checker can be hard at times. You want to test enough so that you are sure the page is a match, yet you don’t want to test too much to make your checks fragile.

Grails and Conventions

Fortunately, Grails’ controller/action convention makes identifying pages cost free and definitive (for almost all cases). The trick lies in the controllerName and actionName variables being available in each action’s view implicitly. This allows you to make your Grails GSP templates look something like this…

<html>
<head>
  <meta name="pageId" content="${controllerName}.${actionName}" />
  …
</head>
<body>
  …
</body>
</html>

Every view generated from a controller action in your application now has a unique identifier embedded into the page in an unobtrusive manner.

Action Page Objects

Geb goes out of it’s way to support using inheritance with page objects. This means that you can define an at checker in a common base class that reads our meta pageId property and have that used for each subclass.

import geb.*

abstract class GrailsPage extends Page {

    // To be overridden by subclasses
    static controller = null
    static action = null

    static at = {
        // delegate here is the original page _instance_ (i.e. the subclass)

        def expectedPageControllerName = delegate.class.controller
        if (expectedPageControllerName == null) {
            throw new IllegalStateException("${delegate.class} forgot to declare which controller it belongs to")
        }

        def expectedPageActionName = delegate.class.action
        if (expectedPageActionName == null) {
            throw new IllegalStateException("${delegate.class} forgot to declare which action it is")
        }

        def actualPageControllerName = controllerName
        def actualPageActionName = actionName

        assert actualPageControllerName == expectedPageControllerName
        assert actualPageActionName == expectedPageActionName

        true // at checkers must return true
    }

    static content = {
        pageId { $("meta", name: "pageId").@content }
        controllerName { pageId.split('\\.')[0] }
        actionName { pageId.split('\\.')[1] }
    }

}

Here we do three things:

  1. declare static ‘controller’ and ‘action’ properties that subclasses will populate (more on this later)
  2. declare an “at checker” that compares the declared controller/action against those in the page
  3. declare what the controllerName and actionName bits of content on the page are (consult the Book Of Geb for more on this content DSL)

We now have a rock solid page identification mechanism that from here on in in the development and testing of our application costs us almost nothing. The only thing you need to do when writing new page objects is set the controller and action static properties…

class ViewCartPage extends GrailsPage {
    static controller = "cart"
    static action = "view"
}

Or in the case where all your actions for a particular controller share content, you may want to have an abstract base class for all actions of that controller…

abstract class CartPage extends GrailsPage {
    static controller = "cart"
}

class ViewCartPage extends CartPage {
    static action = "view"
}

Geb and Inheritance (and Groovy)

This is made possible by Geb supporting inheritance of content definitions (content definitions are merged) and Groovy supporting a dynamic resolution of static methods and properties.

Given the above classes, the following works…

assert ViewCartPage.controller == "cart"

However, if we were calling this from Java, it would not work. In Java, static methods/properties are never inherited like they are for all intents and purposes in Groovy. This means that when requesting a page class’s “at checker” it will search the inheritance hierarchy for one (e.g. assert ViewCartPage.at == GrailsPage.at). This also means that at any point in the inheritance hierarchy you can override any static method/property defined in a super class (but it only works if calling from Groovy).

Summary

We discussed a useful pattern for page identification (“at checking” in Geb parlance) with Geb and Grails. I have used this pattern in anger and have found it to be very useful. Give it a try.

Posted: Aug 26th, 2010 @ 7:22 pm

Tags: #software  #grails  #geb  

Comments

Archive · RSS · Theme by Autumn