Page Object Pattern Implementation

265 views
Skip to first unread message

lukasz....@uniteam.pl

unread,
Feb 3, 2017, 8:44:05 AM2/3/17
to Geb User Mailing List
Hello everyone, 

I'm a little confused about Page Object Pattern implementation at Geb. I'm talking about test classes extended from GebSpec/GebReportingSpec. I'm very impressed about Page implementation, especially about static url, content and at field but according to Martin Fowler blog https://martinfowler.com/bliki/PageObject.html and Simone Stewart (founder of WebDriver project):
If you have WebDriver APIs in your test methods, You're Doing It Wrong

Let's talk about this piece of code
source: http://www.gebish.org/pages:
import geb.Browser
     
Browser.drive {
    to 
LoginPage
    
assert at(LoginPage)
    loginForm
.with {
        username 
= "admin"
        password 
= "password"
    
}
    loginButton
.click()
    
assert at(AdminPage)
}

This example is excellent prove that Geb doesn't implement Page Object Pattern in obvious way. In my opinion this is not full support for Page Object Pattern.
I think that Martin Fowler is right about:
The page object should encapsulate the mechanics required to find and manipulate the data in the gui control itself.

Previously, I was working a lot with pure WebDriver API and Java. When I started use Groovy I was very impressed for a moment. After that I realised that dynamically typed language like Groovy is very helpful but not in this case. I resolved this in inner trait. That's the example:

class MyPage extends Page {

    static at = { ... }

    static content = { element { ... } }

    trait MyPageTrait {

        def doSomethingOnMyElement() {
            element.click()
        }
    }

}

class MyTest extends GebSpec implements MyPage.MyPageTrait {

    def "do some test"() {
        expect:
        doSomethingOnMyElement()
    }

}

What do you think about it? I'm open to have discuss. I'm not sure If I'm right but I've reason to think that Geb does it wrong. Please, fell free to suggest anything you like.

Łukasz

J. David Beutel

unread,
Feb 3, 2017, 5:45:19 PM2/3/17
to geb-...@googlegroups.com
Geb's page objects don't prevent one from making higher-level methods to abstract the WebDriver API, so I don't think it's a matter of something that Geb is doing.  I haven't tried using an inner trait for that, so I'm not sure about the trade-offs.  I guess it would give one some static type safety, but a false sense of safety, because it would seem to apply to the whole test class, but I wouldn't expect it to work unless the test were on the right page at the time.

For comparison, I use a page property with a closure, which I expect will be dynamically available where it is relevant in the test:

import grails.util.Holders as CH

class CasLoginPage extends Page {
    static at = { title == "University of Hawaii Web Login Service" }

    def login = { testuser -> 
        $("input", name: "username").value( testuser )
        $("input", name: "password").value(casTestPassword())
        $("input", value: "Login").click()
    }

    private static casTestPassword() {
        def pw = System.getProperty('casTestPassword', CH.config?.cas?.test?.password ?: null)
        if (!pw) {
            def configMethods = [
                    '* define cas.test.password="xxx" in $HOME/grails-conf/taps-secret-config.groovy, or',
                    '* for running in IDE without Grails, use JVM option -DcasTestPassword=xxx'
            ]
            throw new IllegalStateException('configure password xxx for Load Testing CAS like so:\n' + configMethods.join('\n'))
        }
        pw
    }
}

My tests can use it like this:

def "main menu requires CAS login"() {

    when:
    via MainMenuPage

    then:
    at CasLoginPage
}

def "can login as an hourly employee"() {

    given:
    assert at(CasLoginPage)

    when:
    login( emplData.username )

    then:
    at MainMenuPage
    displayName == emplData.name

However, I wouldn't add levels of abstraction that don't reduce duplicate code or clarify the test.  My tests often call click() directly on content, and use locators directly.  Abstracting the content locator in a Page class makes sense if it makes the test easier to read, and allows for changing multiple uses of the locator in one place in case the HTML/CSS changes.  But, what good would it do to abstract the click() call?  Would it make the test more clear?

def "can get choices of old pay period"() {

    given:
    assert at(MainMenuPage)

    when: 'revealing inactive appointments to choose from'
    previousAppointmentsLink?.click()

    and: 'selecting an hourly Appointment with an empty previous period'
    $("label", text: contains((String) emplData.plcSummary)).click()

    and: 'creating for other than the current period (so this spec can use static dates)'
    createOtherTimesheetButton.click()

    then:
    at EmployeeCreateTimesheetForOtherPeriodPage
    $("#payPeriod\\.id option").size() > 1
}

Cheers,
11011011
-- You received this message because you are subscribed to the Google Groups "Geb User Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to geb-user+u...@googlegroups.com. To post to this group, send email to geb-...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/geb-user/ac68e36c-d488-4483-9654-ffa5027dfe56%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.

Marcin Erdmann

unread,
Feb 5, 2017, 10:12:21 AM2/5/17
to Geb User Mailing List
@Łukasz,

With regards to the example you included, to be perfectly accurate there is not a single usage of WebDriver API in it. But I agree that given it's taken directly from the Book of Geb it is a bit unfortunate that the abstractions are not at the right level, WebDriver API level calls are used (e.g. click()) and page structure, albeit indirectly, leaks into the test (e.g. because input name attributes are used directly to find elements).

As this example shows there is nothing preventing you from abusing Geb's implementation of page object model. On the other hand there is also nothing preventing you from doing it right by introducing abstractions at the right level via methods on your pages and modules. So for the example you've used we could instead have the following LoginPage implementation:

class LoginPage extends Page {

    static content = {
        loginForm { ... }
        loginButton { ... }
    }

    void login(String usernameValue, String passwordValue) {
        loginForm.with {
            username = usernameValue
            password = passwordValue
        }
        loginButton.click()    
    }

}

And then use it like:

Browser.drive {
    to LoginPage
    login("admin", "password")
    at AdminPage
}

and if you're after more type safety and autocompletion in your IDE (at least in IntelliJ) at a cost of being slightly more verbose:

Browser.drive {
    def loginPage = to LoginPage
    loginPage.login("admin", "password")
    at AdminPage
}

I get where you're coming from with dynamic features of Groovy being an impediment here. Having to track context (which page you're on or what module type you're dealing with) without being able to resort to IDE quickly telling you that leads to significant cognitive load, especially when dealing with large test suites or code written by somebody else. Code is read more often than it is written and legibility is therefore very important and that's why I nowadays write all my specs in that bit more chatty way I showed above. These techniques are also described in the Book of Geb at http://www.gebish.org/manual/current/#authoring-assistance-autocomplete-and-navigation.

I'm not sure that I like your solution with traits as it again hides the context of which page we're currently on.

@David

Sorry, but I will have to disagree with you on using selectors directly in your tests. It usually makes them harder to understand, provides unnecessary detail (I don't care how to find the login button, what I care about it its functionality) and possibly forces you to change multiple places in your tests if the structure of your DOM changes as you might have used the same selector in more than one place. So I would always prefer this:

at(EmployeeCreateTimesheetForOtherPeriodPage).payPeriods.size() > 1

over this:

at EmployeeCreateTimesheetForOtherPeriodPage
$("#payPeriod\\.id option").size() > 1

Cheers,
Marcin

-- You received this message because you are subscribed to the Google Groups "Geb User Mailing List" group. To unsubscribe from this group and stop receiving emails from it, send an email to geb-user+unsubscribe@googlegroups.com. To post to this group, send email to geb-...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/geb-user/ac68e36c-d488-4483-9654-ffa5027dfe56%40googlegroups.com. For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Geb User Mailing List" group.
To unsubscribe from this group and stop receiving emails from it, send an email to geb-user+unsubscribe@googlegroups.com.

To post to this group, send email to geb-...@googlegroups.com.

lukasz....@uniteam.pl

unread,
Feb 8, 2017, 5:09:07 AM2/8/17
to Geb User Mailing List
Thanks for your replay @David and @Marcin

@Marcin
I'm not sure that I like your solution with traits as it again hides the context of which page we're currently on.
So am I. This trait solution is some kind of bridge or even try between my strong typed thinking in dynamically language. I liked what you said about how you write your specs.
Reply all
Reply to author
Forward
0 new messages