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
I believe each project should have a minimum of a config code where you can read your setup from one place.
* 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.
*
*
* A new instance of SharedDriver is created for each Scenario and passed to yor Stepdef classes via Dependency Injection
*
*
* As a bonus, screenshots are embedded into the report for each scenario. (This only works
* if you're also using the HTML formatter).
*
*
* 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.
*
*/
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 :