0

I'm sorry if this question has been asked already, I haven't found anything like my question yet...

I'm working/playing/learning to build up some kind of testing environment... Inside it, I'm building an Application Layer (a package of classes that are the virtual representation of the different pages/windows/forms) of an application. The simplified setup is the following:

public abstract class WebPage {

    protected WebDriver driver;

    protected WebElement getElement(By by){
        WebElement element = (new WebDriverWait(driver, 10))
              .until(ExpectedConditions.presenceOfElementLocated(by));

    return element;
}

    public void menuLogout(){
        System.out.println("Logged out");
    }
}

public class HomePage extends WebPage {

    public ProfilePage ClickLinktoProfilePage(){
        return new ProfilePage();
    }

    public DashBoardPage clickViewDashboard(){
        return new DashBoardPage();
    }

    public String getTitle(){
        return getElement(By.id("title")).getText();
    }
}

public class ProfilePage extends WebPage {

    public String getUsername(){
        return getElement(By.id("name")).getText();
    }

    public String getEmail(){
    return getElement(By.id("email")).getText();
}       
    public HomePage clickReturnToHomePage(){
        return new HomePage();
    }

}

public class DashBoardPage extends WebPage {

    public String getcurrentPeriod(){
        return getElement(By.id("name")).getText();

}
}

The idea behind this is that I wish my Test to hold only one current WebPage. I do not wish to create a new variable each time I change page.

I also do not want to be forced to know in advance which page I'm heading into. I want the application Layer to give me the flow of the Application. In the same way that when clicking a link, you are brought to the following page, I wish that when I click a link that brings me to another page, that method tells me what page I'm heading into.

(WebPage abstract class also exposes lots of shared methods between all concrete WebPages)

So my intended use was:

WebPage currentPage = new HomePage();

currentPage = currentPage.ClickLinktoProfilePage(); //currentPage = new ProfilePage();
System.out.println(currentPage.getUsername());
currentPage.menuLogout();

Sadly, this does not work, since the currentPage variable is typed as WebPage, it cannot see any of the concrete classes's methods. I find it logical and odd at the same time because I can ask "currentPage.getClass().getName();" and it'll return "packageName.ConcreteClassName".

For Typecasting to work, I would need to redefine the variable's type... (not sure if it's possible or even good to do).

So I know I can find the name of the class inside the variable, but I'm not sure where to go from there.

Anyone got a solution?

  • Use the concrete type ie. `Page1` or a cast? –  Apr 14 '15 at 18:17
  • 1
    Are you trying to call either `clickLink()` or `printHello()` depending on the actual type? Type cast into a local variable, or rewrite your parent class (or Interface) to require a `abstract void doSomething()` method and override the abstract behavior in each child class. – ryanyuyu Apr 14 '15 at 18:23
  • 1
    @roux69: Your code samples aren't valid Java. In this case, I think everyone here is able to figure out what you mean, but it's better to use code examples that actually demonstrate your problem. See http://stackoverflow.com/help/mcve for more information. – Daniel Pryden Apr 14 '15 at 18:27
  • See also https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html – Radiodef Apr 14 '15 at 18:34
  • @Daniel: Thx... I'm not sure how to make it easier for you... I've updated my code to give a more concrete example, taken right out of my eclipse IDE. though of course, each class in each own file etc. – cimonfortierg Apr 14 '15 at 19:40
  • I've added the testing and selenium tag since this is an attempt at making an automated functionnal testing framework. Maybe my logic is simply wrong... What I'm trying to do is this. I need to automate some WebApp. So each class here is a representation of the WebApp's pages. (I'm testing with Selenium) what I want to do here is to encapsulate all the findElements call in their respective pages, so that I don't need write them while coding testcases... does that make sense? – cimonfortierg Apr 14 '15 at 19:48
  • @roux69: The Selenium angle makes this more interesting -- there may be a better solution there that I don't know about. So it might be worth opening a separate question for that so it gets more attention. – Daniel Pryden Apr 14 '15 at 21:07

1 Answers1

2

To clarify what Radiodef and I are saying in the comments here:

What you want is to define WebPage (your abstract API) in such a way that your concrete subclasses don't need to have public methods that aren't a part of that API.

For example, compare the java.util.List interface in the standard library. There are multiple implementations of this interface (ArrayList and LinkedList are the most well-known ones, but there are many others), but the majority of code that uses List doesn't need to care whether it's actually using an ArrayList or a LinkedList or something else, since all the operations that you need are exposed via the List interface.

You can do the same thing with your WebPage class. For example, you could define a series of "hooks" for different operations that you can do with a web page:

public abstract class WebPage {
  // methods that each subclass needs to implement
  protected abstract String renderBodyHtml();
  public abstract String getNameToLinkTo();

  // other methods that are common to every page
  public final void serve(
      HttpServletRequest request, HttpServletResponse response) {
     // write the response, using the specific page's body HTML
     response.getWriter().println(renderToBodyHtml());
  }
}

And then your pages would implement that contract like so:

// Note: the class doesn't need to be public, since anybody that uses
// it can just declare their variable as type WebPage
class Page1 extends WebPage {
  @Override protected String renderBodyHtml() {
    return "<body>Hello world!</body>";
  }

  @Override public String getNameToLinkTo() {
     return "Page1";
  }
}

Then code that wants to work with a WebPage doesn't need to know that it's a Page1 (or any other page):

public static void printPageName(WebPage webPage) {
  System.out.println(webPage.getNameToLinkTo());
}

Alternatively, like resueman says, you can just use the Page1, Page2, etc., types directly, using WebPage only for implementation inheritance, not API. This is fine as well -- the correct solution depends on how flexible (and complex) you want your code to be.

Daniel Pryden
  • 59,486
  • 16
  • 97
  • 135
  • 1
    @LocHa: If you have 1000 subclasses, I would hope that you can define an API that's common to all of them. (Since otherwise that means that you have 1000 special snowflake web pages, which means that your site is already an unmaintainable mess.) But now we've veered into the topic of API design -- perhaps you should open a new question about that? – Daniel Pryden Apr 14 '15 at 18:51
  • 2
    @LocHa: Are you seriously arguing that your public API surface should be 1000 different methods? If you have that many methods, even reflection will be unwieldy since you will need to encode the logic to select which method to call. Without an API to give semantic meaning to methods, there's no way you can *a priori* know what method to call. Sure, you can build "duck typed" APIs where you look for a method with a particular name or signature (cf. JUnit 3) but if you're doing that you're much better off with annotations instead. Your argument (and downvote) here makes no sense. – Daniel Pryden Apr 14 '15 at 18:55
  • 1
    @DanielPryden Thx for the detailed explanation. I knew all that already however. And the more I read you guys, the more I realize that my problem might not be what I think it is... basically... I might've been looking at this the wrong way... meaning I've been trying to use the Abstract Classes in a way that was never meant to be... maybe? – cimonfortierg Apr 14 '15 at 20:10
  • @roux69: So you could still use an abstract class here if you figured out a way to generalize the behavior. For example, you could have an enum of clickable things and have a method like `WebPage lookupClickResult(ClickableThing clickableThing)`. But yeah, I think trying to use an abstract class for API sharing isn't going to work well with your use case -- you're probably better off just making the abstract class an implementation detail (or get rid of it entirely, and use composition for sharing code rather than inheritance). – Daniel Pryden Apr 14 '15 at 21:09