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