Issue
I'm trying to use the page object model for my tests and I'm trying to structure my page classes to be able to do a "builder pattern-like" structure (I've not seen it very often so I don't know if it has a name or if it is even a thing) like in this example:
public class Page1 implements Page {
public static Page1 goOn(){
return new Page1();
}
public Page1 action1() {
return this;
}
public Page2 gotoPage2() {
return new Page2();
}
}
public class Page2 implements Page {
public Page2 action2() {
return this;
}
public Page2 gotoPage1() {
return new Page2();
}
}
And then use it like:
Page1.goOn()
.action1()
.gotoPage2() //it return a Page2 object which mean you cant use Page1 methods anymore
.action2() // avoiding using the Page1 object which wouldnt be usable anymore if we stored it
.goToPage1() //and poof we can only use Page1 methods again
.action1()
//etc...
So this is how I would manage "linear" page change, it allows some nice stuff with auto-completion and forces you to only do valid actions before compile time (which I think is great since I hate runtime errors >:( )
Then I'm trying to handle a login page (its mostly a simple example of what I'm gonna have to do next), there are 2 different outputs possible depending on if the login fails or not. For that specific case I could have done 2 functions: public Page1 logIn(){...}
and public Page2 "logInButFail(){...}
but for several reasons I would like to do something like:
public Page logIn(User user) {
//do the login in the page
//IF login worked:
//return new Page2();
//ELSE
//return this; // or new Page1(),
}
The problem is, to keep going with that the methods chain thingy I had before I have to do unsafe casts, which... I can handle them fairly nicely and safely but it means breaking java's sacred rule about typing... And well, my static typing fan's lil' brain has been struggling deciding what to do.
So:
- Is there a way to do it without doing unsafe casts?
- If not, should I:
- Do efficient code using unsafe casts (which actually are ok because if those casts fail that means the test failed before)?
- Do less efficient code but keep java happy? :)
Thanks for any help on the subject, if you need more explanation ask me I will do my best to explain it more precisely (also if you know how this practice is called I'm interested) :)
EDIT: after using the dynamic logIn()
I use the following function to assert the type of the page (this is where I have to do the unsafe cast I try to avoid):
public <T extends Page> T assertPage(Class<T> type){
boolean isgood = (this.getClass.equals(type));
assertTrue(isgood);
if(isgood){
return (T)this; //this is the unsafe cast im trying to avoid
}else{
//throw an exception as this is not supposed to happen
}
}
Solution
So, you want to page-object method, wich will conditionaly return instance of Page1 or Page2. For that, you created method of type Page. And you want to execute this method and continue to work with instance of Page1 or Page2 depends on condition without cast. Right? You can do it via generics. Here is simplified example:
class Page{
<T extends Page> T conditionalAction( T pageObject ){
return pageObject;
}
}
class Page1 extends Page{
Page1 action1(){ return this; }
}
class Page2 extends Page{
Page2 action2(){ return this; }
}
Now, you can do something like:
Page1 page1 = new Page1();
Page2 page2 = new Page2();
page1.action1()
.conditionalAction( page1 )
.action1()
.conditionalAction( page2 )
.action2();
You are free to use such method of generic return type anywhere in your parent/clild class.
And if you want just to assert the class you get from a method, you can do the check:
public Page conditionalAction( boolean condition ) {
if ( condition ) {
return new Page1();
} else {
return new Page2();
}
}
// in test
Page result = new Page1().conditionalAction( true );
if ( result instanceof Page1 ){
Page1 page1 = (Page1) result;
} else if ( result instanceof Page2 ){
Page2 page2 = (Page2) result;
}
Answered By - Gennady Zyablitsev
Answer Checked By - Mary Flores (JavaFixing Volunteer)