Page object model selenium Java project


641
1 comment, 641 points
page-object-model-selenium
page-object-model-selenium

Create a page object model selenium project

Short story:

Last year I attended for SeleniumCamp conference in Kiev, Ukraine, and in one of the presentations the speaker mentioned something that left me speechless   :

It doesn’t matter how your code is structured if does the job , it doesn’t matter if you copy past the same script from a file to another file and just get the job done.

I’m not going to mention the speaker name , but even him said “You might be surprised that Im saying this in a development conference” , well this close me up.

I believe it matters how you structured your code and I believe it’s a waist of time and resources if you don’t make the most of any patter you might use.

What is page object model?

Page Object model is an object design pattern in Selenium, where web pages are represented as classes, and the various elements on the page are defined as variables on the class.

Why Page object Model (POM)?

When you start your UI automation project you might say that is easy enough to get started with selenium but simply as that just imagine the following code:

package com.example.tests;

import com.thoughtworks.selenium.*;
import java.util.regex.Pattern;

public class temp script extends SeleneseTestCase {
    public void setUp() throws Exception {
        setUp("http://localhost:8080/", "*iexplore");
    }
    public void testTemp script() throws Exception {
        selenium.open("/BrewBizWeb/");
        selenium.click("link=Start The BrewBiz Example");
        selenium.waitForPageToLoad("30000");
        selenium.type("name=id", "bert");
        selenium.type("name=Password", "biz");
        selenium.click("name=dologin");
        selenium.waitForPageToLoad("30000");
    }
}

How can you reuse it for another 1000 further tests if you have not implemented it in a page object model ? The answer is simple : you’ll not gonna be able to reuse it in any if your scrips , so that’s why people find out that it’s easy to structure your code.

Page object model selenium implementation

Advertisements
I believe each project should have a minimum of a config code where you can read your setup from one place.
package automation.config;

import java.io.*;
import java.util.Properties;

public class TestConfig {
    private static TestConfig testConfig;
    private static String requiredEnvironmentName;
    private static Properties properties;

    public static String valueFor(final String keyName) throws Throwable {
        return getInstance().getProperty(keyName);
    }

    private static TestConfig getInstance() throws Throwable {
        if (testConfig == null) {
            properties = new Properties();
            requiredEnvironmentName = System.getProperty("env", "local");
            populateCommonProperties();
            populateEnvProperties(requiredEnvironmentName);
            testConfig = new TestConfig();
        }
        return testConfig;
    }

    private static void populateCommonProperties() throws Throwable {
        readInPropertiesFile("common");
    }

    private static void populateEnvProperties(final String requiredEnvironment) throws Throwable {
        readInPropertiesFile(requiredEnvironment);
    }

    private static void readInPropertiesFile(String filePath) throws Throwable {
        String propertiesFilePath = String.format("src/test/resources/config/%s.properties", filePath);
        File propertiesFile = new File(propertiesFilePath);
        if(!propertiesFile.exists()) {
            throw new FileNotFoundException(
                    String.format("No properties file found at: %s", filePath));
        }
        InputStream input = new FileInputStream(propertiesFilePath);
        properties.load(input);
        input.close();
    }

    private String getProperty(final String keyName) {
        String value = properties.getProperty(keyName);
        if(value == null) {
            throw new Error(String.format("Key %s not configured for environment %s", keyName, requiredEnvironmentName));
        }
        return value;
    }
}
TestConfig
Inside automation.ui package we keep browser configuration
package automation.ui;
import java.util.concurrent.TimeUnit;

import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.support.events.EventFiringWebDriver;

import cucumber.api.Scenario;
import cucumber.api.java.After;
import cucumber.api.java.Before;
import static automation.ui.BrowserFactory.getBrowser;
/**
 * <p>
 * Example of a WebDriver implementation that has delegates all methods to a static instance (REAL_DRIVER) that is only
 * created once for the duration of the JVM. The REAL_DRIVER is automatically closed when the JVM exits. This makes
 * scenarios a lot faster since opening and closing a browser for each scenario is pretty slow.
 * To prevent browser state from leaking between scenarios, cookies are automatically deleted before every scenario.
 * </p>
 * <p>
 * A new instance of SharedDriver is created for each Scenario and passed to yor Stepdef classes via Dependency Injection
 * </p>
 * <p>
 * As a bonus, screenshots are embedded into the report for each scenario. (This only works
 * if you're also using the HTML formatter).
 * </p>
 * <p>
 * A new instance of the SharedDriver is created for each Scenario and then passed to the Step Definition classes'
 * constructor. They all receive a reference to the same instance. However, the REAL_DRIVER is the same instance throughout
 * the life of the JVM.
 * </p>
 */
public class SharedDriver extends EventFiringWebDriver {
    private static final WebDriver REAL_DRIVER;
    private static final Thread CLOSE_THREAD = new Thread() {
        @Override
        public void run() {
            REAL_DRIVER.close();
        }
    };

    static {
        Runtime.getRuntime().addShutdownHook(CLOSE_THREAD);
        try {
            REAL_DRIVER = getBrowser();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new Error(throwable);
        }
    }

    public SharedDriver() {

        super(REAL_DRIVER);
        REAL_DRIVER.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    }

    @Override
    public void close() {
        if (Thread.currentThread() != CLOSE_THREAD) {
            throw new UnsupportedOperationException("You shouldn't close this WebDriver. It's shared and will close when the JVM exits.");
        }
        super.close();
    }

    @Before
    public void deleteAllCookies() {
        manage().deleteAllCookies();
    }

    @After
    public void embedScreenshot(Scenario scenario) {
        try {
            byte[] screenshot = getScreenshotAs(OutputType.BYTES);
            scenario.embed(screenshot, "image/png");
        } catch (WebDriverException somePlatformsDontSupportScreenshots) {
            System.err.println(somePlatformsDontSupportScreenshots.getMessage());
        }
    }
}
Shared Driver

Selenium browser factory class:

class BrowserFactory {

    public static WebDriver getBrowser() throws Throwable {
        String desiredBrowserName = System.getProperty("browser", "chrome");
        WebDriver desiredBrowser = null;

        switch(desiredBrowserName) {
            case "ie":
                desiredBrowser = IEBrowser.buildIEBrowser();
                break;
            case "chrome":
                desiredBrowser = ChromeBrowser.buildChromeBrowser();
                break;
            case "firefox":
                desiredBrowser = FirefoxBrowser.buildFirefoxBrowser();
                break;
            default:
                //work out what to do when a browser isn't needed
                break;
        }
        return desiredBrowser;
    }
}
BrowserFactory
Selenium chromeDriver initialisation example:
class ChromeBrowser extends ChromeDriver {
    public static WebDriver buildChromeBrowser() throws Throwable {
    	System.setProperty("webdriver.chrome.driver", TestConfig.valueFor("WebDriverChromeDriverPath"));
        ChromeBrowser browser = new ChromeBrowser();
        browser.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
        return browser;
    }
    
    private ChromeBrowser() {
    	super();
    }
}
Chromedriver
Simple page :
public class HomePage {
	private final String url = "http://www.google.com";
	private final WebDriver driver;

	@FindBy(css = "#")
	private WebElement searchField;


	public HomePage(WebDriver commonDriver) {
		driver = commonDriver;
		PageFactory.initElements(driver, this);
	}

	public void load() {
		driver.get(url);
	}

	public void searchFor(String searchString) {
		searchField.clear();
		searchField.sendKeys(searchString + "\n");
	}
}
Cucumber steps:
Feature: Search the web
  As an ignorant
  In order to learn things
  I want to be able to find stuff on the web

  Scenario Outline: Search flats to rent
    Given Im using Google
Selenium java step definition example
public class HomePageSteps {
	private final HomePage searchPage;

	
	private final Logger logger = LoggerFactory.getLogger(HomePageSteps.class);

	public HomePageSteps(HomePage commonSearchPage) {
		searchPage = commonSearchPage;
		
	}

	
	@Given("^Im using Google$")
	public void im_using_Zoopla() throws Throwable {
		logger.info("Loading page");
		searchPage.load();
		logger.debug("Loaded page");
	}

}
Step definition
In order to share the driver instance between steps definitions we use Pico Container as an injector

import automation.ui.SharedDriver;
import cucumber.runtime.java.picocontainer.PicoFactory;

public class PicoDependencyInjector extends PicoFactory {

    public PicoDependencyInjector() {
        addClass(SharedDriver.class);
    }
}
And finally your run cukes class:
@RunWith(Cucumber.class)
@CucumberOptions(plugin={"pretty"}, features = "classpath:", glue = {"automation.stepdefs"}) //if you're on windows add `monochrome=true` for clean output
public class RunCukes { }

In case you are using maven too my maven file looks something like this :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>JavaTest</groupId>
    <artifactId>JavaTest</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>automation</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <junit.version>4.12</junit.version>
        <cucumber.version>1.2.4</cucumber.version>
        <picocontainer.version>2.15</picocontainer.version>
        <selenium.webdriver.version>2.53.0</selenium.webdriver.version>
        <cucumber.jvm.parallel.version>1.2.1</cucumber.jvm.parallel.version>
        <maven.failsafe.plugin.version>2.19.1</maven.failsafe.plugin.version>
        <maven.surefire.plugin.version>2.19.1</maven.surefire.plugin.version>
        <slf4j.version>1.7.21</slf4j.version>
        <logback.version>1.1.7</logback.version>
        <wiremock.version>1.58</wiremock.version>
        <rest.assured.version>2.9.0</rest.assured.version>
        <gson.version>2.6.2</gson.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>com.jayway.restassured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>${rest.assured.version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.tomakehurst</groupId>
            <artifactId>wiremock</artifactId>
            <version>${wiremock.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>${gson.version}</version>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-picocontainer</artifactId>
            <version>${cucumber.version}</version>
        </dependency>
        <dependency>
            <groupId>org.picocontainer</groupId>
            <artifactId>picocontainer</artifactId>
            <version>${picocontainer.version}</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>${selenium.webdriver.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-support</artifactId>
            <version>${selenium.webdriver.version}</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-firefox-driver</artifactId>
            <version>${selenium.webdriver.version}</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-chrome-driver</artifactId>
            <version>${selenium.webdriver.version}</version>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-ie-driver</artifactId>
            <version>${selenium.webdriver.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>
    </dependencies>
    <profiles>
        <profile>
            <id>cuke</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <cucumber.opts>--tags @wip</cucumber.opts>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-failsafe-plugin</artifactId>
                        <version>${maven.failsafe.plugin.version}</version>
                        <executions>
                            <execution>
                                <id>integration-test</id>
                                <goals>
                                    <goal>integration-test</goal>
                                    <goal>verify</goal>
                                </goals>
                                <configuration>
                                    <systemPropertyVariables>
                                        <cucumber.options>${cucumber.opts}</cucumber.options>
                                    </systemPropertyVariables>
                                    <includes>
                                        <include>**/*Cukes.class</include>
                                    </includes>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>parallel-cuke</id>
            <properties>
                <acceptance.test.parallel.count>2</acceptance.test.parallel.count>
            </properties>
            <build>
                <plugins>
                    <plugin>
                        <groupId>com.github.temyers</groupId>
                        <artifactId>cucumber-jvm-parallel-plugin</artifactId>
                        <version>${cucumber.jvm.parallel.version}</version>
                        <executions>
                            <execution>
                                <id>generateRunners</id>
                                <phase>validate</phase>
                                <goals>
                                    <goal>generateRunners</goal>
                                </goals>
                                <configuration>
                                    <glue>automation</glue>
                                    <outputDirectory>${project.build.directory}/generated-test-sources/cucumber
                                    </outputDirectory>
                                    <featuresDirectory>src/test/resources/features/</featuresDirectory>
                                    <cucumberOutputDir>target/cucumber-parallel</cucumberOutputDir>
                                    <format>junit, json</format>
                                    <strict>true</strict>
                                    <monochrome>true</monochrome>
                                    <tags>"@complete"</tags>
                                    <filterFeaturesByTags>true</filterFeaturesByTags>
                                    <namingScheme>simple</namingScheme>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-failsafe-plugin</artifactId>
                        <version>${maven.failsafe.plugin.version}</version>
                        <executions>
                            <execution>
                                <id>integration-test</id>
                                <goals>
                                    <goal>integration-test</goal>
                                    <goal>verify</goal>
                                </goals>
                                <configuration>
                                    <forkCount>${acceptance.test.parallel.count}</forkCount>
                                    <reuseForks>true</reuseForks>
                                    <includes>
                                        <include>**/*IT.class</include>
                                    </includes>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven.surefire.plugin.version}</version>
            </plugin>
        </plugins>
    </build>
</project>
pom.xml

In case you are using other programming language than Java consider reading the following articles :

Create a page object framework with cucumber watir

Creating an Automation Testing Framework With Selenium and SpecFlow

Happy testing


Like it? Share with your friends!

641
1 comment, 641 points
Test engineer

One Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.