@Ł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

On Fri, Feb 3, 2017 at 10:45 PM, J. David Beutel <[email protected]> wrote:

> 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
>
>
> On 2017-02-03 03:44 , [email protected] wrote:
>
> 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
> -- 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
> [email protected]. To view this discussion on the web visit
> https://groups.google.com/d/msgid/geb-user/ac68e36c-d488-
> 4483-9654-ffa5027dfe56%40googlegroups.com
> <https://groups.google.com/d/msgid/geb-user/ac68e36c-d488-4483-9654-ffa5027dfe56%40googlegroups.com?utm_medium=email&utm_source=footer>.
> 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 [email protected].
> To post to this group, send email to [email protected].
> To view this discussion on the web visit https://groups.google.com/d/
> msgid/geb-user/264f62bf-957c-81d1-a3ac-55bd5dfe97cf%40getsu.com
> <https://groups.google.com/d/msgid/geb-user/264f62bf-957c-81d1-a3ac-55bd5dfe97cf%40getsu.com?utm_medium=email&utm_source=footer>
> .
>
> 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 [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/geb-user/CA%2B52dQTmPLOh%3DfTV6EnMfw8SSe%2BW%2B%2BUC%3D0ZRHrUVaV0e%2BYsJVg%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to