Creating extensible page objects

So far, we have only looked at examples where a page object has been used to describe a whole page. Unfortunately, in the real-world web pages that we want to automate are usually much larger, and more complicated, than the examples you find in a book. So, how are we going to deal with large complicated pages while keeping our test code well factored and readable? We are going to break things down into manageable chunks.

Let's have another look at our HTML page examples we used earlier in this chapter. We will start with the index page.

If you look carefully, you will see that there are two parts that look particularly generic: the header (the area enclosed in the <nav> tag) and the footer (the area enclosed in the <footer> tag). It is probably fair to expect a header and a footer on every page of the website to share a common set of elements.

Let's take these two areas and turn them into reusable components that have their own page objects. First of all, we will create one for the header:

package com.masteringselenium.query_page_objects;

import com.lazerycode.selenium.util.Query;
import org.openqa.selenium.By;


public class PageHeader extends BasePage {

private Query servicesLink = new Query(By.cssSelector(".nav
li:nth-child(1) > a"
), driver);
private Query contactLink = new Query(By.cssSelector(".nav
li:nth-child(2) > a"
), driver);

public void goToTheServicesPage() {
servicesLink.findWebElement().click();
}

public void goToTheContactPage() {
contactLink.findWebElement().click();
}
}

Then, we need to create one for the footer:

package com.masteringselenium.query_page_objects;

import com.lazerycode.selenium.util.Query;
import org.openqa.selenium.By;

public class PageFooter extends BasePage {

private Query aboutUsLink = new Query(By.cssSelector(".left-
footer > a"
), driver);

public void goToTheAboutUsPage() {
aboutUsLink.findWebElement().click();
}
}

Now, we need to convert our IndexPage object into one that uses Query objects:

package com.masteringselenium.query_page_objects;

import com.lazerycode.selenium.util.Query;
import org.openqa.selenium.By;

public class IndexPage extends BasePage {

private Query heading = new Query(By.cssSelector("h1"), driver);
private Query mainText = new Query(By.cssSelector(".col-md-4
> p"
), driver);
private Query button = new Query(By.cssSelector(".btn"),
driver);

public boolean mainTextIsDisplayed() {
return mainText.findWebElements().size() == 1;
}

public boolean mainPageButtonIsDisplayed() {
return button.findWebElements().size() == 1;

}
}

We now have the reusable components of the page in separate page objects so that we can reuse them when testing other pages that share these reusable components. We have also added a couple of methods that check for the existence of elements on the page. We can use these methods for assertions in our tests.

Now, we have to look back at the HTML markup for the about page. The about page has exactly the same HTML code for the header and the footer as the last page. This is brilliant; we can now reuse our header and footer page objects without having to duplicate code. Let's convert our AboutPage object into one that uses Query objects as well:

package com.masteringselenium.query_page_objects;

import com.lazerycode.selenium.util.Query;
import org.openqa.selenium.By;

public class AboutPage extends BasePage {

private Query heading = new Query(By.cssSelector("h1"),
driver);
private Query aboutUsText = new Query(By.cssSelector
(".col-md-4 > p"));

public boolean aboutUsTextIsDisplayed() {
return aboutUsText.findWebElements().size() == 1;
}
}

Again, we have added an additional method that performs a check on the page. Next, we will need to write a quick test that goes to our index page, checks for the existence of some elements, and then goes to the about page and does the same thing.

You may have noticed that the additional methods that I've added to check for the existence of elements are not using Seleniums .isDisplayed() method. 

Why haven't I done this? 

Well, the .isDisplayed() method will throw NoSuchElementException if the element does not exist. We are using a Query object that tries to find the element every time the .findWebElement() or .findWebElements() command is called. This means that we don't know if the element has been found or not.

We don't want to have exceptions thrown and then have to catch the exceptions, so the easiest thing to do is to return a list of elements and then count them. We know that there should only be one of these elements available, so we check that the size of the list of the WebElements list is equal to 1.

Now that we have added the methods to check for elements in our page objects, we need to create our test:

@Test
public void checkThatAboutPageHasText() {
driver.get("http://web.masteringselenium.com/index.html");
IndexPage indexPage = new IndexPage();

assertThat(indexPage.mainTextIsDisplayed()).isEqualTo(true);
assertThat(indexPage.mainPageButtonIsDisplayed()).isEqualTo(true);

PageFooter footer = new PageFooter();
footer.goToTheAboutUsPage();
AboutPage aboutPage = new AboutPage();

assertThat(aboutPage.aboutUsTextIsDisplayed()).isEqualTo(true);
}

By breaking our HTML pages up into small bite-sized chunks and creating separate page objects for each of these chunks, we have ended up with smaller page objects and less code duplication.

There has been an unfortunate side-effect caused by breaking our page objects up, though. Our tests are now starting to look a bit untidy and they are much harder to read. I know that I'm interacting with a header, but I don't really know which page this header is referring to. If my tests were covering multiple pages, it would probably get quite confusing seeing us switch between the various page objects. It's going to be hard to keep track of what is going on.

What can we do to clean up this mess that we have started to make?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.236.174