4

I just recently started working on testing a new application, and I have begun to model the page objects for said application.

I found out that this application uses "frames" as in the DOM contains 2 or more HTML element tags. By default it looks like GEB can not see the elements found under the nested HTML element.

I checked the book of GEB and saw that you can use the withFrame method to specify which frame an element belongs to. when I do this GEB does indeed see the element in question.

The issue I have is that I don't want the people who write the scripts (using my page objects) to have to worry about which frame a particular element comes from. I want to know if there is a way I can specify the frame an element belongs to from the static content. Otherwise, a script writer will need to not only know an element is out of frame, but also will have to check and see which frame the element in question belongs to.

Here are some of the things I have tried so far:

SomePage extends Page{
    static content=  {
        myNavigator {withFrame("header", {$(By.xpath("my xpath"))})}
    }
}

If I try to use myNavigator I get stale element reference exception (maybe I am doing everything correctly and there is another reason for this?)

I have also tried this which also causes stale element:

SomePage extends Page{
    static content=  {
        myNavigator {$(By.xpath("my xpath"))}
    }

    Navigator getMyNavigator(){
        return withFrame("header", {myNavigator})
    }
}

I kinda get why this happens, after the withFrame colusre is executed I assume any elements referenced inside that closure become stale.

Basically I want it so that all a script writer needs to do is say something like:

at MyPage
waitFor {myNavigator}.click()

I want to avoid having to do:

at MyPage
withFrame("header", {waitFor {myNavigator}.click()}

How can I set this up on the Page Objects side to get my desired result? (Could modules be a possible option?)

Edit: I have also tried defining the static content with a slightly different syntax, but still get the same stale element reference:

myNavigator{ withFrame("header") { $(By.xpath("//td/a"))} } 

Edit2: I have also tried to implement something simular to this post: https://groups.google.com/forum/#!topic/geb-user/uxgcgWgmGYE

my static content now looks like this:

static content = {
    bannerMod {withFrame("banner") {module BannerModule}}
    mainViewMod {withFrame("FrameworkMain") {module MainViewModule}}
    sideMenuMod {withFrame("contents") {module SideMenuModule}}
}

I moved all the elements into their respective modules, but now when I attempt to reference them, I still get a stale element reference exception. for example it is being thrown on this line of code:

if(!mainViewMod.table.displayed){

stating stale element reference: element is not attached to the page document

switch201
  • 587
  • 1
  • 4
  • 16
  • Didn't want to post a separate question for this, but I noticed that the frames I am trying to access are acctuallly members of what is called a "frameSet" I am thinking that bight be whats causing my stale element issues but I wasn't sure how I would reference these frames. would I have to do a withFrame() inside of another withFrame()? – switch201 Oct 15 '18 at 19:04

1 Answers1

4

As you've already noticed you're getting StaleElementExcpetions because you are trying to access elements which you've obtained reference to when in context of a frame after switching to the context of the root documents, that is you are trying to access these elements outside of a withFrame() call.

I can see two solutions to your problem.

First one is to not use withFrame() but manage the frame context manually via driver.switchTo().frame(..) calls. This unfortunately would mean that you would not achieve the goal of not having to know which elements are coming from which frames when using the page objects.

Another way is to write a method in your page which would take a closure and deal with switching to the correct frame. Something like:

class PageWithFrames extends Page {
    public <T> T mainView(@DelegatesTo(value = MainViewPage, strategy = Closure.DELEGATE_FIRST) Closure<T> mainViewAction) {
        withFrame('FrameworkMain', MainViewPage, mainViewAction)
    }
}

class MainViewPage extends Page {
    static content = {
        table { ... }
    }
}

And then it could be used like this:

at PageWithFrames
if (mainView { table.displayed }) {
    ....
}

Note that thanks to the @DelegatesTo annotation IntelliJ will know that you are at MainViewPage inside of mainView {} call and you will get autocompletion. Just remember that whatever you do you should not return page elements from mainView {} calls because you will start getting StaleElementExceptions.

You might also find "Switching pages and frames at once" manual section interesting.

erdi
  • 6,944
  • 18
  • 28
  • Thanks for the thoughtful and detailed reply, it might be a few days before I can test this, but I will be sure to come back and update with my results when I am able to – switch201 Oct 22 '18 at 15:05