Welcome in the second part of this tutorial. I hope you enjoyed the previous post which was about how to create the first automated test from a scratch. You’ve learned how to set up a development environment, create a new project and finally how to implement your first automated test script in a simplest way as it’s possible.

Please be aware that practical part of current post is based on test scenario and its implementation from previous one. Still you can find it under following link: Building test automation framework by example – 1. The First Tests. If you are not interested in all the details you can skip them because the final code for that previous part can be found on GitHub.

Now it’s a time to introduce the design pattern called Page Object Pattern. In this part you will learn what it is about and what benefits it brings into test automation. Firstly I’m going to explain some theory about page objects.

Next we are going to analyse the test scenario, thinking about what page objects can be used. Finally we will create page objects and refactor existing code to use them. After all, as in previous part, you’ll find some assignment so you can practice what you’ve learned so far. So much for the introduction, now let’s get to the main theme.

About Page Object Pattern

When you write tests against a web application, you simply simulate user actions like click on element and determine what’s displayed. In the test code you need to refer to elements within a web pages of that application in order to perform or simulate user actions. In other words within your web application there are pages or areas that your tests interact with.

A page object simply models these as objects within the test code. For each web page of your application you can create corresponding class which encapsulate the mechanics required to perform some action on that page. We can think about page objects like they represent the services offered by a particular page and hide the details and mechanics of the page.

Let’s take some example. Assume that in our application we have login page which offers regular log in functionality. We can create page object for login page that will represent this log in service. The only thing which should be seen by the test is ability to log in without worrying how these is implemented because it shouldn’t matter to the test. How the implementation could look like?

Test method:

@Test
public void sampleTestMethod() {
   LoginPage loginPage = new LoginPage(driver);
   loginPage.open()
         .loginAs("username", "password");

   // TODO
}

Page class:

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class LoginPage {
   private static final String LOGIN_PAGE_URL = "…";
   private static final By USERNAME_INPUT = By.name("…");
   private static final By PASSWORD_INPUT = By.name("…");
   private static final By SIGN_IN_BUTTON = By.name("…");
   private WebDriver driver;

   public LoginPage(WebDriver driver) {
      this.driver = driver;
   }

   public LoginPage open() {
      driver.get(LOGIN_PAGE_URL);
      return this;
   }

   public HomePage loginAs(String username, String password) {
      driver.findElement(USERNAME_INPUT)
            .sendKeys(username);
      driver.findElement(PASSWORD_INPUT)
            .sendKeys(password);
      driver.findElement(SIGN_IN_BUTTON)
            .click();
      return new HomePage(driver);
   }
}

Why should we use page objects? They reduce the amount of duplicated code. If there is some change in the UI, in perfect situation we need to apply fix in only one place. Page objects increase code readably because they make the test easier to understand. They hide the details of the UI structure from the tests so we can clearly see the logic and the intention of the test.

Despite the term „page” object, these objects can be built for each page, but also for the significant elements on a page like navigation menu or other components.

To summarize let’s point out a few general rules:

  • The public methods represent the services that the page offers
  • Page objects shouldn’t expose the internals of the page
  • Page objects shouldn’t contain assertions
  • Methods return other page objects
  • Need not represent an entire page
  • Different results for the same action are modelled as different methods

Introducing Page Objects

At this point, after this short theoretical introduction, you have some understanding of what page objects are. Now we can move forward to review the test scenario and think about it in terms of page objects. Below you can find each step of the test in relation to the page on which it is executed.

Test method: testAddingItemToCard()

  1. Open www.amazon.com —> on Home page
  2. Select “Books” from search category dropdown —> at Navigation Menu on Home page
  3. Enter search key: “Selenium” —> at Navigation Menu on Home Page
  4. Click “Go” button —>  at Navigation Menu on Home Page
  5. Click the first search result item title  —> on Search Results page
  6. Verify that product title is correct —> on Product Details page
  7. Click “Add to Cart” button —> on Product Details page
  8. Verify confirmation test appears: “1 item added to Cart” —> on Add to Cart Confirm page
  9. Navigate to Cart page from main menu —> at Navigation Menu on Add to Cart Confirm page
  10. Verify item is displayed on Shopping Cart list —>on Cart page

Looking at the above, we can distinguish following page objects which represent web pages: HomePage, SearchResultsPage, ProductDetailsPage, AddToCartConfirmPage and CartPage. Moreover, we can also find that we can create page object for NavigationMenu component and it can be reused by other pages. Below you can find class diagram and that should give you a better view:

auto_fwk_blog_19

Each class contains public methods which represent the services that the page offers and which are required by our test.

Code refactoring

Are you ready for the code refactoring? I think we know exactly which page objects need to be created and what services they should offer, finally we will be able to use them in our test. Go ahead and open the project: Blog 01 – The first tests in your IDE. Again before we proceed to create particular classes, let’s create packages firstly. Starting from the package for the page classes.

Right mouse click on java -> New ->Package. Then enter new package name: com.github.pguzdziol.automation.tutorial.pages

auto_fwk_blog_20

auto_fwk_blog_21

Now do the same for component classes. Enter new package name com.github.pguzdziol.automation.tutorial.components. If everything is fine, the structure of your project should look like this:

auto_fwk_blog_22

When we have packages created we can move to the next step. Now it’s a time to create class for NavigationMenu in package com.github.pguzdziol.automation.tutorial.components. To do so, right mouse click on package name -> New -> Java Class. Then enter new class name: NavigationMenu just like it is shown below.

auto_fwk_blog_23auto_fwk_blog_24auto_fwk_blog_25

Please be aware that I’m not going to go into the implementation details. I would like to show you page object design pattern rather than Selenium API itself. That’s why I decided to share ready code for all required pages so you can copy/paste it or rewrite, as you prefer. Let’s start from the only one component class: NavigationMenu.

com.github.pguzdziol.automation.tutorial.components.NavigationMenu

package com.github.pguzdziol.automation.tutorial.components;

import com.github.pguzdziol.automation.tutorial.pages.CartPage;
import com.github.pguzdziol.automation.tutorial.pages.SearchResultsPage;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.Select;

public class NavigationMenu {
   private static final By SEARCH_DROPDOWN_BOX = By.id("searchDropdownBox");
   private static final By SEARCH_INPUT = By.id("twotabsearchtextbox");
   private static final By SEARCH_GO_BUTTON = By.xpath("//*[@value='Go']");
   private static final By NAVIGATION_ITEM_CART = By.id("nav-cart");
   private WebDriver driver;

   public NavigationMenu(WebDriver driver) {
      this.driver = driver;
   }

   public SearchResultsPage searchFor(String category, String searchKey) {
      new Select(driver.findElement(SEARCH_DROPDOWN_BOX))
            .selectByVisibleText(category);
      driver.findElement(SEARCH_INPUT)
            .sendKeys(searchKey);
      driver.findElement(SEARCH_GO_BUTTON)
            .click();
      return new SearchResultsPage(driver);
   }

   public CartPage navigateToCartPage() {
      driver.findElement(NAVIGATION_ITEM_CART)
            .click();
      return new CartPage(driver);
   }
}

Now, in the same way, you can create another page classes in already existing package com.github.pguzdziol.automation.tutorial.pages, then copy/paste or rewrite required methods:

com.github.pguzdziol.automation.tutorial.pages.HomePage

package com.github.pguzdziol.automation.tutorial.pages;

import com.github.pguzdziol.automation.tutorial.components.NavigationMenu;
import org.openqa.selenium.WebDriver;

public class HomePage {
   private static final String AMAZON_HOME_PAGE_URL = "http://www.amazon.com";
   private final NavigationMenu navigationMenu;
   private WebDriver driver;

   public HomePage(WebDriver driver) {
      this.driver = driver;
      this.navigationMenu = new NavigationMenu(driver);
   }

   public HomePage open() {
      driver.get(AMAZON_HOME_PAGE_URL);
      return this;
   }

   public NavigationMenu navigationMenu() {
      return navigationMenu;
   }
}

com.github.pguzdziol.automation.tutorial.pages.SearchResultsPage

package com.github.pguzdziol.automation.tutorial.pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class SearchResultsPage {
   private static final By SEARCH_RESULT_ITEM_TITLE = By.className("s-access-title");
   private WebDriver driver;

   public SearchResultsPage(WebDriver driver) {
      this.driver = driver;
   }

   public String getFirstResultTitle() {
      return driver.findElement(SEARCH_RESULT_ITEM_TITLE)
            .getText();
   }

   public ProductDetailsPage clickFirstResultTitle() {
      driver.findElement(SEARCH_RESULT_ITEM_TITLE)
            .click();
      return new ProductDetailsPage(driver);
   }
}

com.github.pguzdziol.automation.tutorial.pages.ProductDetailsPage

package com.github.pguzdziol.automation.tutorial.pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class ProductDetailsPage {
   private static final By PRODUCT_TITLE_FIELD = By.id("productTitle");
   private static final By ADD_TO_CART_BUTTON = By.id("add-to-cart-button");
   private WebDriver driver;

   public ProductDetailsPage(WebDriver driver) {
      this.driver = driver;
   }

   public String getProductTitle() {
      return driver.findElement(PRODUCT_TITLE_FIELD)
            .getText();
   }

   public AddToCartConfirmPage addToCart() {
      driver.findElement(ADD_TO_CART_BUTTON)
            .click();
      return new AddToCartConfirmPage(driver);
   }
}

com.github.pguzdziol.automation.tutorial.pages.AddToCartConfirmPage

package com.github.pguzdziol.automation.tutorial.pages;

import com.github.pguzdziol.automation.tutorial.components.NavigationMenu;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class AddToCartConfirmPage {
   private static final By CONFIRM_TEXT_FIELD = By.id("confirm-text");
   private final NavigationMenu navigationMenu;
   private WebDriver driver;

   public AddToCartConfirmPage(WebDriver driver) {
      this.driver = driver;
      this.navigationMenu = new NavigationMenu(driver);
   }

   public String getConfirmationText() {
      return driver.findElement(CONFIRM_TEXT_FIELD)
            .getText();
   }

   public NavigationMenu navigationMenu() {
      return navigationMenu;
   }
}

com.github.pguzdziol.automation.tutorial.pages.CartPage

package com.github.pguzdziol.automation.tutorial.pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class CartPage {
   private static final By LIST_ITEM = By.className("a-list-item");
   private WebDriver driver;

   public CartPage(WebDriver driver) {
      this.driver = driver;
   }

   public String getFirstItemText() {
      return driver.findElement(LIST_ITEM)
            .getText();
   }
}

So far so good, at this point you should have the following structure of the project:

auto_fwk_blog_26

At last, the final part is to use those page objects in the test method: testAddingItemToCard(). Go ahead and refactor the code to the following:

package com.github.pguzdziol.automation.tutorial.tests;

import com.github.pguzdziol.automation.tutorial.pages.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.interactions.Actions;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.concurrent.TimeUnit;

public class FirstTests {

   private WebDriver driver;
   private HomePage homePage;

   @BeforeClass(alwaysRun = true)
   public void setUp() {
      driver = new FirefoxDriver();
      driver.manage()
            .timeouts()
            .implicitlyWait(5, TimeUnit.SECONDS);
   }

   @BeforeMethod(alwaysRun = true)
   public void openHomePage() {
      homePage = new HomePage(driver).open();
   }

   @AfterClass(alwaysRun = true)
   public void tearDown() {
      driver.quit();
   }

   @Test
   public void testAddingItemToCard() {
      SearchResultsPage searchResultsPage = homePage.navigationMenu()
            .searchFor("Books", "Selenium");
      String itemTitle = searchResultsPage.getFirstResultTitle();
      ProductDetailsPage productDetailsPage = searchRe-sultsPage.clickFirstResultTitle();
      assert (productDetailsPage.getProductTitle()
            .equals(itemTitle));
      AddToCartConfirmPage addToCartConfirmPage = productDetailsPage.addToCart();
      assert (addToCartConfirmPage.getConfirmationText()
            .equals("1 item added to Cart"));
      CartPage cartPage = addToCartConfirmPage.navigationMenu()
            .navigateToCartPage();
      assert (cartPage.getFirstItemText()
            .contains(itemTitle));
   }

   @Test
   public void testSignInSignOut() { //TODO refactor to use page objects
      //Navigate to 'Your Account' page
      driver.findElement(By.id("nav-link-yourAccount"))
            .click();
      driver.findElement(By.linkText("Sign in"))
            .click();

      //Enter e-mail address
      driver.findElement(By.id("ap_email"))
            .sendKeys("automation.user2015@gmail.com");

      //Enter password
      driver.findElement(By.id("ap_password"))
            .sendKeys("@ut0m@t!0n");

      //Click 'Sign in using our secure server' button
      driver.findElement(By.id("signInSubmit-input"))
            .click();

      //Verify 'Your Account' button contains the name of the user
      assert (driver.findElement(By.id("nav-link-yourAccount"))
            .getText()
            .contains("Hello, Automation"));

      //Hover over a "Your account" button and click on "Sign Out"
      Actions action = new Actions(driver);
      action.moveToElement(driver.findElement(By.id("nav-link-yourAccount")))
            .perform();
      driver.findElement(By.linkText("Not Automat...? Sign Out"))
            .click();

      //Verify "Sign In" form appears
      assert (driver.findElement(By.name("signIn"))
            .isDisplayed());
   }
}

Assignment 2

As you probably already guessed, there is still one test method left. This method testSignInSignOut() still doesn’t use page objects so it is a really great opportunity for you to improve or verify your knowledge about Page Object Pattern.

In general you can follow approach used in this post. Start thinking about what page objects are needed, then create those which are missing. Finally, step by step you can implement required methods and use them in the test.  After all you can find this assignment completed on Github so you can compare it with your results. The link can be found in “Summary”.

Summary

That’s all in this part. I hope that I gave you a brief idea of what Page Object Pattern is. You have learned what page objects are and what benefits they bring into your tests. You have seen how to use page objects on a real example.  I strongly urge you to read more on this topic. The final project for this part can be found under following link: https://github.com/pguzdziol/auto-fwk-tutorial/tree/blog-02/page-object-pattern.

In the next part we are going to introduce Dependency Injection with Guice to our tests. Stay tuned and see you in the next part!