Implement Page Object Pattern
Pre-Requisites
So at this point, the context we would have before going with implementation is that we have read and got at least a fair idea on each of the following.
- What is page-object
- What is page-factory
- Modeling web pages into page-objects
- Page navigation (its dependency on page-object modeling)
- Road map and Future State (yes we need to align with this road map)
- Data-driven framework
- Keyword driven framework
Implement Workflow with page-object:
We will be implementing this workflow on automationpractice website. In a nutshell, we will be automating a shopping cart workflow, from sign in to purchase.
Generic Base Page:
Since all web pages have some commonalities, lets define that in a Generic Base Page. We create this under ‘src/test/java/pageobjects/BaseClass.java’
1 2 3 4 5 6 7 8 9 10 |
package pageobjects; import org.openqa.selenium.WebDriver; public abstract class BaseClass { public static WebDriver driver; public static boolean bResult; public BaseClass(WebDriver driver){ BaseClass.driver = driver; BaseClass.bResult = true; } } |
All we are doing here is a Base Class that every page object would extend from. It has a static WebDriver, which is the handle to the current browser instance [At some point when we implement DI ,we will remove the static, but for now this model works for all our examples]. The bResult is not being used at this point, it is just a flag to set the test case pass or fail at global level [I have used this in TestNG framework and let it remain here]
Automation Home Page:
The sign_out link would appear after logging in, however we associate this element to the home page itself. We create this under ‘src/test/java/pageobjects/AutomationHomePage.java’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
package pageobjects; import helpers.Log; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.How; import org.openqa.selenium.support.PageFactory; public class AutomationHomePage extends BaseClass{ public AutomationHomePage(WebDriver driver){ super(driver); } @FindBy(how=How.LINK_TEXT, using="Sign in") public static WebElement sign_in; @FindBy(how=How.LINK_TEXT, using="Contact us") public static WebElement contact_us; @FindBy(how=How.LINK_TEXT, using="Sign out") public static WebElement sign_out; public static class HeaderPage { @FindBy(how=How.LINK_TEXT, using="Women") public static WebElement menu_women; @FindBy(how=How.XPATH, using="//*a[@title='Dresses']") public static WebElement menu_dresses; @FindBy(how=How.XPATH, using="//*a[@title='T-shirts']") public static WebElement menu_tshirts; public static class WomenPage { @FindBy(how=How.LINK_TEXT,using="Dresses") public static WebElement dresses; public static class DressesPage { @FindBy(how=How.LINK_TEXT,using="Summer Dresses") public static WebElement summer_dresses; } } } } |
Login Page:
We create this under ‘src/test/java/pageobjects/LoginPage.java’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package pageobjects; import helpers.Log; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.How; import org.openqa.selenium.support.PageFactory; public class LoginPage extends BaseClass{ public LoginPage(WebDriver driver){ super(driver); } @FindBy(how=How.ID, using="email") public static WebElement email; @FindBy(how=How.ID, using="passwd") public static WebElement password; @FindBy(how=How.ID, using="SubmitLogin") public static WebElement signin_button; @FindBy(how=How.ID, using="email_create") public static WebElement email_create; @FindBy(how=How.ID, using="SubmitCreate") public static WebElement submit_create; } |
Contact us Page:
We create this under ‘src/test/java/pageobjects/ContactusPage.java’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package pageobjects; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.How; import org.openqa.selenium.support.ui.Select; public class ContactusPage extends BaseClass{ public ContactusPage(WebDriver driver) { super(driver); } @FindBy(how=How.CSS, using=".selector,.hover") public static WebElement div_heading; @FindBy(how=How.ID, using="id_contact") public static Select subject_heading; @FindBy(how=How.ID, using="email") public static WebElement email; @FindBy(how=How.ID, using="id_order") public static WebElement order_reference; @FindBy(how=How.ID, using="message") public static WebElement message; @FindBy(how=How.ID, using="submitMessage") public static WebElement submit; @FindBy(how=How.CLASS_NAME, using="alert-danger") public static WebElement error_message; } |
Let’s write Cucumber scenario
With the above three page-objects i.e. AutomationHomePage, LoginPage,ContactusPage let’s write a very simple cucumber scenario before we start building out the entire workflow. MVP, thin slice, whatever we call in Agile world right
1 2 3 4 5 6 7 8 |
Feature: Shopping cart on an e-commerce website Scenario: Sign im and sign out When I open automationpractice website And I sign in Then I sign out Scenario: Contact Us Action When I open automationpractice website Then I perform contact us actions |
Step Definitions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
package step_definitions; import cucumber.api.PendingException; import helpers.DataHelper; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import modules.ContactUsAction; import modules.SignInAction; import modules.SignoutAction; import org.openqa.selenium.WebDriver; import org.openqa.selenium.support.PageFactory; import pageobjects.AutomationHomePage; import pageobjects.ContactusPage; import pageobjects.LoginPage; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; public class SigninSignoutContactUsSteps { public WebDriver driver; public List<HashMap<String,String>> datamap; public SigninSignoutContactUsSteps() { driver = Hooks.driver; datamap = DataHelper.data(); } @When("^I open automationpractice website$") public void i_open_automationpractice_website() throws Throwable { // Write code here that turns the phrase above into concrete actions driver.get("http://automationpractice.com"); } @When("^I sign in$") public void i_sign_in() throws Throwable { // Write code here that turns the phrase above into concrete actions PageFactory.initElements(driver, AutomationHomePage.class); PageFactory.initElements(driver, LoginPage.class); SignInAction.Execute(driver,datamap); } @Then("^I sign out$") public void i_sign_out() throws Throwable { // Write code here that turns the phrase above into concrete actions SignoutAction.Execute(driver,datamap); } @Then("^I perform contact us actions$") public void I_perform_contact_us_actions() throws Throwable { PageFactory.initElements(driver, AutomationHomePage.class); PageFactory.initElements(driver, ContactusPage.class); ContactUsAction.Execute(driver,datamap); } } |
Overall Project Structure
Let’s Execute:
From IntelliJ:
The output should be like this. Both scenarios should pass
Command line output
CI Server Output
Take a Break:
Guys, if you have reached this point and see the same output as is displayed on this page, pat yourself on the back. We have pretty touched all the points on a Hybrid framework here.
It is a thin slice we have built all the way end-end. We will cover more scenarios and build the entire shopping cart experience.
Project Code base:
The entire project code base is on my github: https://github.com/machzqcq/CucumberJVMExamples
Next:
Lets continue the workflow of selecting a product, checking out and so on, until we purchase it right.