Multiple pages in one page - possible?

462 views
Skip to first unread message

Kamil Lendo

unread,
Oct 2, 2015, 8:10:23 AM10/2/15
to Geb User Mailing List
Hello All,
I'm beginner at Geb.
I just hit the wall while trying to implement page containing multiple/parametrized static url's and at's in single page.

To make things clearer i got such url structure:
www.mainPage/language/placeOnPageInSpecifiedLanguage/ABTestingVersion, 
and title of window is language specific also, all page have the same structures. 

so for english it would look like this:

and for german something like this:

Is it possible to create one page for multpile versions depending on language?

Cause now i have to create different pages, for each language separately, for 2-3 languages it could be still ok.
But for more languages it could be exhausting, and so many imports of those page variations.

I have readed gebish about advanced page navigation, but i don't see possible usage of it in my case :(

While in example static url is created in this way:

static url = "manual"
    String convertToPath(Manual manual) {
        "/${manual.version}/index.html"
    }

And full url looks this way:
currentUrl == "http://www.gebish.org/manual/0.9.3/index.html"
As I see it, version is an surfix of static url, which is added to base page.

But in my case the baseUrl would be www.mainPage.com
and language and ABTestingVersion would to be parameters.

How could I achieve this?

I'm sorry if i ask stupid and trivial question, but it would help me a lot in future.

Thanks for attention.

Cheers,
Kamil

Jeff Lowery

unread,
Oct 2, 2015, 4:23:46 PM10/2/15
to geb-...@googlegroups.com
You can assign a url in your test:

public LandingPageTest(url, id) {
println "to $url"
LandingPage.url = url


LandingPage.at = {
def pageId = $('meta', name: 'pageId').@content;
pageId && pageId.contains(id) || title.contains(id);
}


— Jeff



--
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/52970a34-0fc3-4766-9469-b9e13892a27e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jeff Lowery

unread,
Oct 2, 2015, 4:25:23 PM10/2/15
to geb-...@googlegroups.com
should have shown how it’s used:

@Before
public void gotoPage() {
to LandingPage
at LandingPage
}


— Jeff


Marcin Erdmann

unread,
Oct 5, 2015, 4:07:57 AM10/5/15
to geb-...@googlegroups.com
Hi Kamil,

I would probably not follow Jeff's advice - changing static fields from a constructor is almost never a good idea.

I would instead use parametrized pages with an enum that holds language specific information for this:

class I18nPage extends Page {
    Language lang
    String abVersion

    static at = { title == lang.title }

    String convertToPath(Object... args) {
        "/${lang.name().toLowerCase()}/${lang.path}/${abVersion}"
    }
}

enum Language {
    EN {
        String getTitle() { "title" }
        String getPath() { "skills" }
    },
    DE {
        String getTitle() { "titel" }
        String getPath() { "fahigkeiten" }
    }

    abstract String getTitle()
    abstract String getPath()   
}

And then use it like this:

to(new I18nPage(lang: Language.DE, abVersion: "verA"))



--

Jeff Lowery

unread,
Oct 7, 2015, 2:38:25 PM10/7/15
to geb-...@googlegroups.com
 changing static fields from a constructor is almost never a good idea.

True, but in my case I instantiate a single class and go; I don’t have to worry about clobbering other class instances, and I’m not sure under what scenario Geb would be testing multiple pages at once…?

I do wonder, though, why the to/at checkers are static?  It seems the intent is to have the test writer create a separate class for each page to be tested.  There are cases (like I have), where dozens or hundreds of pages are based on a template, whose contents are populated from a datasource. In that scenario, a single test class would possibly cover the all bases for that type (class) of page, the only difference is that each class instance operates on a specific URL, with different at conditions.  Writing a separate class for each page, just to statically change the to/at values, seems like overkill. 

What would make more sense (to me) is to have the to/at values declared as final instead of static, where the values can be assigned (statically) in the declaration, or (dynamically) in the constructor.


— Jeff



Marcin Erdmann

unread,
Oct 19, 2015, 3:13:41 PM10/19/15
to geb-...@googlegroups.com
Hi Jeff,

Sorry for a late response.

One example of having two instances of the same page class at a single point in time would be having more than one instance of Browser in play - like when simulating two different users in a single test. You might consider it an edge case, sure. The solution you used works for you, at least for now. I understand that sometimes pragmatism is the right approach and chasing clean code for the sake of it feels like very bad ROI. On the other hand I've learned that clean code usually pays off in the long term. That's why I brought it to your attention.

We would probably need to ask Luke to really learn why the at checkers are static as he implemented it that way but I think that it's down to the fact that they are like a definition or specification. They are defined using a closure, which is an object describing a function. Even though they are static they are executed in a context of a page instance. They could probably also be implemented as methods that you override.

The intent is not for you to write a separate class for each page and basing page classes on a template is not only expected but also supported with the introduction of parametrized pages in 0.12.0: http://www.gebish.org/manual/0.12.0/#parametrized-pages. Also, you can parameterize urls of pages if your page is supposed to model multiple urls: http://www.gebish.org/manual/0.12.0/#advanced-page-navigation. Btw, on the opposite side of the spectrum, you can also write multiple classes for a single page when dealing with multi page apps - there was some discussion around that recently on the list.


Jeff Lowery

unread,
Oct 19, 2015, 7:48:52 PM10/19/15
to geb-...@googlegroups.com
Hey, Marcin:

A late response, but a thoughtful and informative one, so I appreciate the time spent in crafting it.

The code I wrote to handle templatized pages (back in 0.7.1 days) seemed at that time as the only solution. I don’t recall if I asked on the mailing list if there was something better.  Had the url/at members been declared final static, Luke would have nipped my nefarious hack in the bud (but maybe there’s something unGroovy about final class members?).

I had made a mental note to look at the parameterized-pages addition in 0.12 but quickly forgot about it under a pile of other research.  I will definitely do so, and use it if I can.

Again, thanks for the careful response; I am in agreement with sentiments you’ve expressed more than you may realize.

— Jeff



Kamil Lendo

unread,
Oct 22, 2015, 10:15:21 AM10/22/15
to Geb User Mailing List
Hi Jeff, Marcin:

First of all, please forgive me for not responding sooner.

I took Marcin advice to create enum class.
But I had meet some problems.

First of them is when I'm doing Maven test run, it shrieks that abstract methods cannot be inside non abstract class (In this case enum is not such class) - build fails.
But when running the same scenario straight from IDEA, there are no such errors.

Second, I have many pages with languages, so basicly, now I'm creating enum 'sub' class inside each of page files.
(This connects to one more issue, but it is probably connected with groovy, and my lack of understanding, what and how i would like to do:
basicly, I tried to create interface which would implement enum, unfortunately in this case declared abstract method inside interface is not visible for enum :()
Is it a good approach? Or should I include it in external enum class, with various names of each enum coresponding to proper page?

Lastly, I tried to create one single enum class, which would take values from xml/json file but slurpers and page instance called from browser return me an error (static enum cannot use non static variables/methods)

I would be extremely grateful for any help.

Thank you for your time,
Kamil

Marcin Erdmann

unread,
Oct 22, 2015, 11:50:18 AM10/22/15
to geb-...@googlegroups.com
On Thu, Oct 22, 2015 at 3:15 PM, Kamil Lendo <kamil...@gmail.com> wrote:
Hi Jeff, Marcin:

First of all, please forgive me for not responding sooner.

I took Marcin advice to create enum class.
But I had meet some problems.

First of them is when I'm doing Maven test run, it shrieks that abstract methods cannot be inside non abstract class (In this case enum is not such class) - build fails.
But when running the same scenario straight from IDEA, there are no such errors.

Interesting. Are you sure you are using the same version of Groovy in both IntelliJ and Maven? Can you please paste the error you're getting? And the code of the enum class that is causing the error?
 
Second, I have many pages with languages, so basicly, now I'm creating enum 'sub' class inside each of page files.
(This connects to one more issue, but it is probably connected with groovy, and my lack of understanding, what and how i would like to do:
basicly, I tried to create interface which would implement enum, unfortunately in this case declared abstract method inside interface is not visible for enum :()

I don't quite understand what you mean. Providing the code you wrote would probably be useful here. I don't understand what you mean by "unfortunately in this case declared abstract method inside interface is not visible for enum". Your enum should implement the interface, not the other way around (at least that's what I understand from what you wrote).

 
Is it a good approach? Or should I include it in external enum class, with various names of each enum coresponding to proper page?

I created an enum per page in my example because the title and path are page specific - that's why you would have SkillsPage.Language enum class, and ExperiencePage.Language enum class. Both of them would then implement a common interface that would define the title and path properties. Your pages would extend the I18nPage which would take an instance of that interface so that you can pass page specific enum instances to it.
 

Lastly, I tried to create one single enum class, which would take values from xml/json file but slurpers and page instance called from browser return me an error (static enum cannot use non static variables/methods)

Again, your description of the problem is so generic that without seeing the code you tried and error you got I won't be able to help.

Kamil Lendo

unread,
Oct 23, 2015, 4:30:41 AM10/23/15
to Geb User Mailing List
Hi Marcin,

Here is error from maven:

[ERROR] /C:/Testy/sampleProject/target/generated-sources/groovy-stubs/test/pages/AboutUsPages/HistoryPage.java:[20,8] pages.AboutUsPages.HistoryPage.Language is
not abstract and does not override abstract method getPageParent() in pages.AboutUsPages.HistoryPage.Language

And code for enum:

    enum Language{

        PL
{
           
String getTitle(){'Historia'}
           
String getPath(){'historia'}
           
String getPageParent(){'o-nas'}
       
},
        EN
{
           
String getTitle(){'History'}
           
String getPath(){'history'}
           
String getPageParent(){'about-us'}
       
},
        DE
{
           
String getTitle(){'Geschichte'}
           
String getPath(){'geschichte'}
           
String getPageParent(){'uber-uns'}

       
}
       
abstract String getTitle()
       
abstract String getPath()

       
abstract String getPageParent()
   
}

As of second issue, you were right, I had in mind that enum implements interface, sorry for confusion, here is code snippet:

    interface Gatherable{


       
abstract String getTitle()
       
abstract String getPath()

       
abstract String getPageParent()
   
}

   
enum Language implements Gatherable{

        PL
{
           
String getTitle(){'Historia'}
           
String getPath(){'historia'}
           
String getPageParent(){'o-nas'}
       
},
        EN
{
           
String getTitle(){'History'}
           
String getPath(){'history'}
           
String getPageParent(){'about-us'}
       
},
        DE
{
           
String getTitle(){'Geschichte'}
           
String getPath(){'geschichte'}
           
String getPageParent(){'uber-uns'}
       
}
   
}

In this case i got error that getTitle method is not implemented.

As of last problem connected with general single Language enum:

package helpers

import geb.Browser

class Languages {

   
def browser = new Browser()

   
static String xmlFile = new File( "." ).getCanonicalPath().replace("\\","/") + "/src/test/xmlFiles/Languages.xml"
   
static def xml = new XmlSlurper().parse(xmlFile)

   
static String query
   
String pageInstance = browser.page

   
enum Language {

        EN
{
           
String getTitle() {

                query
= 'language.en.' + pageInstance + '.title'
               
Eval.x(xml, "x.$query")
           
}

           
String getPath() {

                query
= 'language.en.' + pageInstance + '.path'
               
Eval.x(xml, "x.$query")
           
}
       
},
        DE
{
           
String getTitle() {

                query
= 'language.de.' + pageInstance + '.title'
               
Eval.x(xml, "x.$query")
           
}

           
String getPath() {

                query
= 'language.de.' + pageInstance + '.path'
               
Eval.x(xml, "x.$query")
           
}
       
},
        PL
{
           
String getTitle() {

                query
= 'language.pl.' + pageInstance + '.title'
               
Eval.x(xml, "x.$query")
           
}

           
String getPath() {

                query
= 'language.pl.' + pageInstance + '.title'
               
Eval.x(xml, "x.$query")

           
}
       
}
       
abstract String getTitle()
       
abstract String getPath()
   
}
}

pageInstance variable is returning me an error:

Cannot reference non static symbol from static context

I will be gratefull for any help.

Cheers
Kamil

Bob Brown

unread,
Oct 23, 2015, 4:53:08 AM10/23/15
to geb-...@googlegroups.com
I’ve never seen enums done like that!

This is more normal:

===
public enum Language {

        PL('Historia', 'historia', 'o-nas'),
        EN('History', 'history', 'about-us'),
        DE('Geschichte', 'geschichte', 'uber-uns');

        private String title;
        private String path;
        private String pageParent;

        private Language(String title, String path, String pageParent) {
          this.title = title
          this.path = path
          this.pageParent = pageParent
        }

        String getTitle() { title }
        String getPath() { path }
        String getPageParent() { pageParent }
    }
    
Language l = Language.PL;

assert l.title == 'Historia'

Language.values().each { println it }
===

Maybe try it this way?

BOB

--
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.

Marcin Erdmann

unread,
Oct 26, 2015, 9:31:31 AM10/26/15
to geb-...@googlegroups.com
Hi Kamil,

Looks like Bob is right here and you should follow his advice on how to structure your enum definition - sorry for leading you down a wrong path. I don't fully understand why it fails for you when you try to run your tests from maven but if you do what Bob suggests then it would almost certainly always work. Also, your enum will then implement the Gatherable interface you shared, so you should not have any more problems using it (btw, no need to put the abstract keyword in your interface definition, all methods declared on interfaces are by abstract by default).

With regards to having a single enum and then reading the data in from an XML file - you will need to structure it slightly differently:

class I18Page extends Page {

    Language lang
    String abVersion
    
    final String pageName = getClass().simpleName

    static at = { title == lang.titleFor(pageName) }

    String convertToPath(Object... args) {
        "/${lang.name().toLowerCase()}/${lang.pathFor(pageName)}/${abVersion}"
    }

}

enum Language {

    PL, DE, EN

    private final static XML = new XmlSlurper().parse(getClass().getResourceAsStream("/path/to/xml"))

    private pageNodeFor(String pageName) {
        XML.language."${name().toLowerCase()}"."$pageName"
    }

    String pathFor(String pageName) {
        pageNodeFor(pageName).path.text()
    }

    String titleFor(String pageName) {
        pageNodeFor(pageName).title.text()
    }
}

--
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.
Reply all
Reply to author
Forward
0 new messages