Overriding click() in a nested parameterized modules

37 views
Skip to first unread message

Trey Turner

unread,
Apr 24, 2020, 2:16:01 AM4/24/20
to geb-...@googlegroups.com
Hi again. I am in need of some help, and though my track record suggests the possibility I've overlooked something obvious, I've dug as deep as I'm able and haven't come up with an answer yet so I thought to ask here.

I commonly implement a click() method override within modules. I do this to redirect clicks directed toward the module to a more appropriate content element within the module for easier/thoughtless clicking from specs. I discovered the pattern when modeling checkbox facades, and to date, it's worked without issue, though my use cases have been relatively straightforward so far.

I'm modeling a new application now though, and my page objects are becoming increasingly complex. At this point, I'm implementing parameterized modules from within modules that are extended from parameterized module classes, and I'm finding I'm unable to intercept clicks bound for my innermost modules.

That is to say, I write a method in the module to intercept and redirect the click, as I always have:


class SideNavItem extends Module {

    String itemName

    static content = {
        container (required: false)
                { label.closest('a') }
        label (required: false)
                { $('.mat-list-item-content', text: iEndsWith(itemName)) }
    }

    @Override
    Navigator click() {
        log.debug("DOING CUSTOM CLICK")
        container.click()
    }

    @Override
    boolean isDisplayed() {
        log.debug("DOING CUSTOM ISDISPLAYED")
        container.isDisplayed()
    }
    
    Navigator click(boolean please) {
        log.debug("DOING CUSTOM CLICK WITH BOOLEAN")
        container.click()
    }    
}


But, the click() ends up being directed to the navigator on which the module is based, resulting in the wrong element being clicked, and my custom log line is never logged. If I am more specific in the spec, and click the container directly, my custom method isn't called of course, but I get the expected result, so I know my override method would work if it were invoked.

To check my logic, I did two things. First I implemented a custom isDisplayed() override just as I did the click() override, and it works as expected. If I call .isDisplayed() on the module, my custom isDisplayed() override method is invoked as the debug line is logged.

To take it one step further, I created a click(boolean) method; and if I call .click(true) on the module from my spec, again my override method is invoked and I get the expected result.

So I'm at a bit of a loss at this point - I'm not sure why I can't invoke my custom click() method, or what I could adjust to maintain my level of abstraction and still get the desired effect.

I know it probably doesn't mean much with Groovy, but if I switch my spec syntax to use fully-qualified page class names, when I 'go to definition' on the module's click() method within IntelliJ IDEA, it goes straight to my custom method.

Have I indeed overlooked something simple? Is there anything further I can do to troubleshoot?

As I mentioned, the parent module extends from a base class that is also parameterized. Here's the child class:


class TrafficReports extends SideNavGroup {

    static content = {
        financeTrafficDetailsReport (to: FinanceTrafficDetailsReport, required: false)
                { panel.module(new SideNavItem(itemName: 'Finance Traffic Details Report')) }
    }
}


Here's the base class (whose @Override click() method works as expected):


class SideNavGroup extends Module {

    String groupName

    static content = {
        container
                { label.closest('mat-expansion-panel') }
        header
                { container.$('mat-expansion-panel-header') }
        label
                { $('mat-panel-title', text: groupName) }
        disclosureWidget
                { header.$('.mat-expansion-indicator') }
        panel (required: false)
                { container.$('.mat-expansion-panel-body') }
    }

    @Override
    Navigator click() { disclosureWidget.click() }

    boolean isExpanded() { header.hasClass('mat-expanded') }
    boolean isCollapsed() { !isExpanded() }

    Navigator expand() {
        Navigator nav
        if (isCollapsed()) {
            nav = click()
            waitFor { isExpanded() }
        }
        nav
    }

    Navigator collapse() {
        Navigator nav
        if (isExpanded()) {
            nav = click()
            waitFor { isCollapsed() }
        }
        nav
    }

    @Override
    // Return only the content names unique to the child class
    Set<String> getContentNames() {
        super.getContentNames() - ['container', 'header', 'label', 'disclosureWidget', 'panel']
    }
}


Here is the root SideNav module which instantiates the groups:


class SideNav extends Module {

    static content = {
        trafficReports
                { module(new TrafficReports(groupName: 'Traffic Reports')) }
    }

    Navigator open() {
        Navigator nav
        if (!navigator.isDisplayed()) {
            nav = browser.page.mainNav.menu.click()
            waitFor { navigator.isDisplayed() }
        }
        nav
    }
}


Finally, SideNav is implemented on a base page class from which all pages extend:


abstract class MyAppPage extends Page {

    static at = {
        // The menu icon is lazy loaded and can take a few seconds
        mainNav.menu
        // Wait for side nav to close in case we used it to navigate
        !sideNav.isDisplayed()
    }

    static content = {
        mainNav
                { $('.primary-nav-bar').module(MainNav) }
        sideNav
                { $('mat-sidenav').module(SideNav) }
    }
}


Infinite thanks,
Trey

Marcin Erdmann

unread,
Apr 29, 2020, 6:31:47 PM4/29/20
to geb-...@googlegroups.com
Hi Trey,

Unfortunately, I cannot explain the behaviour you are seeing. There is probably something that you are missing and not mentioning because there is nothing in your setup that would explain the behaviour you are describing. 

In your email you say:

> But, the click() ends up being directed to the navigator on which the module is based
Can you please explain how you know that? It would be also good to see the code which actually triggers the click and not just read the description of it. 

In your question you pasted two classes with overridden click methods (SideNavItem and SideNavGroup) but they seem like two distinct cases, first one with some debugging in it which doesn't have any child classes and second one which has a child class (TrafficReports). Why is SideNavItem relevant to your question?

I tried to reproduce by adding the following test in Geb's codebase but to no avail:

class OverriddenClickOnAParametrisedChildModuleSpec extends GebSpecWithCallbackServer {
    def "overridden click works as expected"() {
        given:
        html {
            body {
                input(type: "text", name: "input")
            }
        }

        when:
        def module = module(new ModuleExtendingFromOneWithOverriddenClick())
        def clicked = module.click()

        then:
        clicked.tag() == "input"
        module.input.focused
    }
}

class ModuleWithOverriddenClick extends Module {
    static content = {
        input { $("input") }
    }

    @Override
    Navigator click() {
        input.click()
    }
}

class ModuleExtendingFromOneWithOverriddenClick extends ModuleWithOverriddenClick {
}

The above is passing as expected and also failing as expected if I remove the overridden click() method.

--
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 view this discussion on the web visit https://groups.google.com/d/msgid/geb-user/CAErttkUYerejEjBBYUn92bBzqm_AWwDe%3DVh6vO23BcuqowGphw%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages