SeleniumWebdriver - Page objects Implementation.
I have implemented
complex QTP hybrid frameworks. Now I am working on Selenium2 aka. Selenium Webdriver and implemented the similar framework. Why? Open source, No licence fee, multi browser os language support.
I would like to share my experience through this post.
Following are the challenges that I came across during implementation.
1. Email Notifications of test results - Initially I have used
Hudson CI to schedule the test and receive email notifications. IE Driver is not stable in few scenarios like Modal-Dialog, it keep on waiting for ever without moving ahead, user need to close the IEs and restart the test again. There is option of sending attachments through Hudson. So I started invoking the ANT using .VBS where I have better control of the execution and I can kill the process if it is not responding. Email notification component is written in VBS, where I can send email with attachments. Any .vbs file can be invoked easily through Java.
2. Building JAR file - You have option to execute the test directly on eclipse. When it is scheduled we need to compile all the files into a single JAR file so that it can be invoked using ANT and easily distributed to other systems. Build.xml is created to handle all these activities.
3. TestNG - Pass/Fail/Skipped test cases count is recorded using TestNG, but this report is generated after completion of all the methods. Lets assume we ran test for 10 hours, browser or Selenium don't respond; all your test results are lost. Implemented intermediate test status update using VBS, so that even the application crashed at-least I can get the results till the test executed.
4. Screenshot on Failure - Take screen shot and send it in the email along with the test results.
5. Backup the results - Before each run all the test results need to be copied in separate folder, so that results are not lot and can be verified in future. If do it manually, you may miss to copy the files.
6. Page Object Implementations - In QTP Page Objects Implementation is accomplished used dual function design. In Webdriver Page Objects are independent class where all the methods and locators of a particular page is kept in one class, this is the design feature which I like in Webdriver.
7. TestData - My project has lot of test data. Initially I have used TestNG xml and pass it as parameters. I felt it was difficult to maintain 100+ fields, so I have decided to create a new class for test data.
8. Test case log - Lets assume a single test case run for 30 min, how will you trace if some thing goes wrong in between? using logs. Statements are generated at important points like saved, deleted, ID=1234dd....
I started my carrier with
Winrunner and love the
Mercury Tours Site. This is the website on which I have learnt my first automation skills. All the page objects are created based on this site, any one can access this site.
Project folder structure screen shot below.
1. Package Mercury Tours - This is the base folder where Initilize.java, TC(Test Cases), SendEmail.Java files are stored.
Initilize.Java - File call Initilize.vbs that copies all the previous test results if there are any from ScreenShots and Test_reports folder to a backup folder.
package MercuryTours;
import java.io.File;
import org.testng.annotations.Test;
import MercuryTours.PageObjects.UtilityScript;
public class initilize extends UtilityScript {
@Test
public void Test_initilize_main() throws Exception {
File directory = new File (".");
try{Runtime.getRuntime().exec("wscript.exe "+directory.getCanonicalPath()+"\\initilize.vbs" );
}
catch(Exception e){e.printStackTrace();
}
xKillIEs();
Wait(3000);
}
}
2. TC(Test Cases) - Test case logic is written here by using page objects. All the test cases have separate files TC1, TC2....
package MercuryTours;
import org.openqa.selenium.ie.InternetExplorerDriver;
import org.openqa.selenium.support.PageFactory;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
import MercuryTours.PageObjects.UtilityScript;
import MercuryTours.PageObjects._01_Initilize;
import MercuryTours.PageObjects._02_Login;
import MercuryTours.PageObjects._03_FindAFlight;
import MercuryTours.PageObjects._04_SelectAFlight;
import MercuryTours.PageObjects._05_BookAFlight;
import MercuryTours.PageObjects._06_FlightConformation;
//@Listeners({ p2pZions.TestNG.TestNGCustom.class, p2pZions.TestNG.TestNGCustomeListener.class })
public class MercuryTours_TC_01 extends UtilityScript {
InternetExplorerDriver driver;
@BeforeClass(alwaysRun = true)
protected void setUp() throws Exception {
driver = new InternetExplorerDriver();
}
@AfterClass(alwaysRun = true)
protected void tearDown() throws Exception {
driver.quit();
xKillIEs();
}
@Test(groups = { "MercuryToursTestCases" }, enabled = true)
public void Test_E2E_01() throws Exception {
MethodName = MethodName + "TC_01-"; //Used while sending email to report the test cases executed
Method = "TC_01"; //Used while sending email to report the test cases executed
Print("Start:" + xGetDateTimeIP());
_01_Initilize Initilize = PageFactory.initElements(driver,_01_Initilize.class);
Initilize.zOpen(Url);
_02_Login Login = PageFactory.initElements(driver,_02_Login.class);
Login.zEnterCrediantials("qtp123", "qtp123");
_03_FindAFlight FindAFlight = PageFactory.initElements(driver,_03_FindAFlight.class);
FindAFlight.zTripTypeOneWay();
FindAFlight.zNumberOfPassengers("4");
FindAFlight.zDepartingFrom("London");
FindAFlight.zDepartingOnDay("2");
FindAFlight.zDepartingOnMonth("2");
FindAFlight.zArrivingIn("Seattle");
FindAFlight.zFirstClass();
FindAFlight.zContinue();
_04_SelectAFlight SelectAFlight = PageFactory.initElements(driver,_04_SelectAFlight.class);
SelectAFlight.zDepartFlightSelection();
SelectAFlight.zContinue();
_05_BookAFlight BookAFlight = PageFactory.initElements(driver,_05_BookAFlight.class);
BookAFlight.zPassengerDetails("FirstName1", "LastName1", "HNML", "0");
BookAFlight.zPassengerDetails("FirstName2", "LastName2", "HNML", "1");
BookAFlight.zPassengerDetails("FirstName3", "LastName3", "HNML", "2");
BookAFlight.zPassengerDetails("FirstName4", "LastName4", "HNML", "3");
BookAFlight.zCardDetails("123456789111");
BookAFlight.zContinue();
_06_FlightConformation FlightConformation = PageFactory.initElements(driver,_06_FlightConformation.class);
FlightConformation.zGetConformationNumber();
FlightConformation.zLogOut();
}
}
3. SendEmail.Java - This file is used to call the SendEmail.vbs file with all the executed test cases (Variable MethodName)
package MercuryTours;
import java.io.File;
import org.testng.annotations.Test;
import MercuryTours.PageObjects.UtilityScript;
public class SendEmail extends UtilityScript {
@Test
public void Test_SendEmail_main() throws InterruptedException {
Wait(3000);
File directory = new File(".");
// Print(MethodName);
try {
Runtime.getRuntime().exec(
"wscript.exe " + directory.getCanonicalPath()
+ "\\sendemail.vbs " + MethodName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
4. _01_initilize.java - This file is used to open the browser, open the URL and set the implicit timeouts.
package MercuryTours.PageObjects;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class _01_Initilize extends UtilityScript {
private WebDriver driver;
public _01_Initilize(WebDriver driver) throws InterruptedException {
this.driver = driver;
}
public _01_Initilize zOpen(String url) throws Exception {
driver.manage().timeouts()
.implicitlyWait(ImplicitWait, TimeUnit.SECONDS);
// Code to mazimize the window. Reason some times Auto suggest,some
// objects will fail if not maximized
String script = "if (window.screen){window.moveTo(0,0);window.resizeTo(window.screen.availWidth,window.screen.availHeight);};";
((JavascriptExecutor) driver).executeScript(script);
driver.get(url);
return this;
}
}
5. _02_Login.Java - All the locators pertaining to the login page. I group similar page objects into one group so I have used _01,_02.....
package MercuryTours.PageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class _02_Login extends UtilityScript {
private WebDriver driver;
public _02_Login(WebDriver driver) throws InterruptedException {
this.driver = driver;
}
public _02_Login zEnterCrediantials(String UserNameTxt, String PasswordTxt)
throws InterruptedException {
Wait(3000);
driver.findElement(By.name("userName")).sendKeys(UserNameTxt);
driver.findElement(By.name("password")).sendKeys(UserNameTxt);
driver.findElement(By.name("login")).click();
Print("UserName:" + UserNameTxt);
Print("---Login");
Wait(3000);
return this;
}
}
6. _03_FindAFlight.java
package MercuryTours.PageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class _03_FindAFlight extends UtilityScript {
private WebDriver driver;
public _03_FindAFlight(WebDriver driver) throws InterruptedException {
this.driver = driver;
}
public void zTripTypeOneWay() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='oneway']")).click();
}
public void zTripTypeRoundTrip() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='roundtrip']")).click();
}
public void zNumberOfPassengers(String Values_1to4)
throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='passCount']/option[@value='"
+ Values_1to4 + "']")).click();
}
public void zDepartingFrom(String DepartingPlace)
throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='fromPort']/option[@value='"
+ DepartingPlace + "']")).click();
}
public void zDepartingOnMonth(String Month_1to12)
throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='fromMonth']/option[@value='"
+ Month_1to12 + "']")).click();
}
public void zDepartingOnDay(String Day_1to31) throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='fromDay']/option[@value='"
+ Day_1to31 + "']")).click();
}
public void zArrivingIn(String ArrivingPlace) throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='toPort']/option[@value='"
+ ArrivingPlace + "']")).click();
}
public void zReturningOnMonth(String Month_1to12)
throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='toMonth']/option[@value='"
+ Month_1to12 + "']")).click();
}
public void zReturingOnDay(String Day_1to31) throws InterruptedException {
driver.findElement(
By.xpath("//select[@name='toDay']/option[@value='" + Day_1to31
+ "']")).click();
}
public void zEconomyClass() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='Coach']")).click();
}
public void zBusinessClass() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='Business']")).click();
}
public void zFirstClass() throws InterruptedException {
driver.findElement(By.xpath("//input[@value='First']")).click();
}
public _03_FindAFlight zContinue() throws InterruptedException {
driver.findElement(By.name("findFlights")).click();
Wait(3000);
return this;
}
}
7. _04_SelectAFlight.Java
package MercuryTours.PageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class _04_SelectAFlight extends UtilityScript {
private WebDriver driver;
public _04_SelectAFlight(WebDriver driver) throws InterruptedException {
this.driver = driver;
}
public void zDepartFlightSelection () throws InterruptedException {
//Implement logic to choose the flight based on Airline, Price
//Selected Second option (ODD numbers 3 5 7 8 there are some blank divs between)
driver.findElement(By.xpath("//tr[5]/td/input[@name='outFlight']")).click();
}
public void zReturnFlightSelection () throws InterruptedException {
//Implement logic to choose the flight based on Airline, Price
driver.findElement(By.xpath("//input[@name='inFlight'][1]")).click();
}
public _04_SelectAFlight zContinue () throws InterruptedException {
driver.findElement(By.name("reserveFlights")).click();
Wait(3000);
return this;
}
}
8. _05_BookAFlight
package MercuryTours.PageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class _05_BookAFlight extends UtilityScript {
private WebDriver driver;
public _05_BookAFlight(WebDriver driver) throws InterruptedException {
this.driver = driver;
}
public void zPassengerDetails (String FirstName, String LastName, String Meal, String IndexStartFrom0) throws InterruptedException {
driver.findElement(By.name("passFirst"+IndexStartFrom0)).sendKeys(FirstName);
driver.findElement(By.name("passLast"+IndexStartFrom0)).sendKeys(LastName);
driver.findElement(By.xpath("//select[@name='pass."+IndexStartFrom0+".meal']/option[@value='"+ Meal +"']")).click();
}
public void zCardDetails (String CardNumber) throws InterruptedException {
driver.findElement(By.name("creditnumber")).sendKeys(CardNumber);
}
public _05_BookAFlight zContinue () throws InterruptedException {
driver.findElement(By.name("buyFlights")).click();
Wait(3000);
return this;
}
}
9. _06_FlightConformation
package MercuryTours.PageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class _06_FlightConformation extends UtilityScript {
private WebDriver driver;
public _06_FlightConformation(WebDriver driver) throws InterruptedException {
this.driver = driver;
}
public void zGetConformationNumber () throws InterruptedException {
Print(driver.findElement(By.xpath("//tr/td/b/font/font/b/font[1]")).getText());
}
public _06_FlightConformation zLogOut () throws InterruptedException {
driver.findElement(By.xpath("//tbody/tr/td[3]/a")).click();
Print("---Logout---");
Wait(3000);
return this;
}
}
9. TestData.java
package MercuryTours.PageObjects;
public class TestData extends MercuryTours.TestNG.TestNGAssertionsCustom {
public static final String Url = "http://newtours.demoaut.com/";
public static final String UserName = "qtp123";
public static final String UserNameP = "qtp123";
//##################################################################################
// Don't change any thing below ####################################################
//##################################################################################
//Assigned variables****************************************************************
public static final int ImplicitWait = 5;
public static final int TimeOut10 = 10000;
public static final int TimeOut20 = 20000;
//Date Data*************************************************************************
//public static final String DateFormat = "dd.MM.yyyy_hh:mm a";
public static final String DateTimeFormat = "MM/dd/yyyy_hh:mm a";
//public static final String DateFormat = "dd.MM.yyyy";
public static final String DateFormat = "MM/dd/yyyy";
public static final String DateFormat1 = "MMM dd, yyyy";
//Non Assigned variables ***********************************************************
public static String NewDate, NewTime, Script, MethodName = "",Method="";
public static String Temp, Temp1,sMessages ="";
}
10. UtilityScript.java
There are lot of custom function written based on project requirement
package MercuryTours.PageObjects;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.imageio.ImageIO;
import org.apache.commons.io.FileUtils;
import org.testng.Reporter;
public class UtilityScript extends TestData {
// Get date time
public java.lang.String xGetDateTime() throws Exception {
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat("hh_mm_ssaadd_MMM_yyyy");
// get current date time with Date()
Date date = new Date();
return (dateFormat.format(date));
}
// DateFormat = "MMM dd, yyyy";
public java.lang.String xGetDate(String DateFormat) throws Exception {
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat(DateFormat);
// get current date time with Date()
Date date = new Date();
return (dateFormat.format(date));
}
// Get date time with SelText
public java.lang.String xGetDateTimeSel() throws Exception {
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat("hh_mm_ssaadd_MMM_yyyy");
// get current date time with Date()
Date date = new Date();
return ("S_" + dateFormat.format(date));
}
// Get date time with System IP
public java.lang.String xGetDateTimeIP() throws Exception {
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat("hh_mm_ssaa_dd_MMM_yyyy");
// get current date time with Date()
Date date = new Date();
// To identify the system
InetAddress ownIP = InetAddress.getLocalHost();
return (dateFormat.format(date) + "_IP" + ownIP.getHostAddress());
}
public static void Wait(int MilliSec) throws InterruptedException {
Thread.sleep(MilliSec);
}
public void Print(String Text) {
System.out.println(Text);
Reporter.log(Text);
String Temp = Text;
sMessages = sMessages + Temp.replaceAll(" ", "_") + "#";
//System.out.println(Temp);
//System.out.println(sMessages);
}
public java.lang.String xAddMinutesToTheDateTime(String Date_TimeFormat,
int NumberOfMinutes) throws InterruptedException, ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DateTimeFormat);
Calendar c = Calendar.getInstance();
c.setTime(sdf.parse(Date_TimeFormat));
c.add(Calendar.MINUTE, NumberOfMinutes); // number of minutes
String str = sdf.format(c.getTime());
String delimiter = "_";
String[] temp;
temp = str.split(delimiter);
for (int i = 0; i < temp.length - 1; i++) {
NewDate = temp[i];
NewTime = temp[i + 1];
}
// Print(NewDate);
// Print(NewTime);
return (str); // dt is now the new date
}
public java.lang.String xAddDaysToTheDateTime(String CurrentDate,
int NumberOfDays, String DateFormat)
throws InterruptedException, ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DateFormat);
Calendar c = Calendar.getInstance();
c.setTime(sdf.parse(CurrentDate));
c.add(Calendar.DATE, NumberOfDays); // number of days
String str = sdf.format(c.getTime());
return (str); // dt is now the new date
}
public java.lang.String xGetCurrentDateEST(String DateFormat) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat(DateFormat);
dateFormat.setTimeZone(TimeZone.getTimeZone("EST5EDT"));
NewDate = dateFormat.format(new Date());
return (NewDate);
}
public java.lang.String xGetCurrentTimeEST() throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat("hh:mm a");
dateFormat.setTimeZone(TimeZone.getTimeZone("EST5EDT"));
NewTime = dateFormat.format(new Date());
return (NewTime);
}
public void xKillIEs() throws Exception {
// I felt this is not closing the IEs effectively, so started relaying
// on VB script
Wait(3000);
/*
* final String KILL = "taskkill /IM "; String processName =
* "iexplore.exe"; // IE process Runtime.getRuntime().exec(KILL +
* processName);
*/
File directory = new File(".");
try {
Runtime.getRuntime().exec(
"wscript.exe " + directory.getCanonicalPath()
+ "\\KillIEs.vbs");
} catch (Exception e) {
e.printStackTrace();
}
Wait(5000); // Allow OS to kill the process
}
public boolean xFileExist(String FileNameWithPath) throws Exception {
java.io.File myDir = new java.io.File(FileNameWithPath);
if (myDir.exists()) {
Print("file exist");
return true;
} else {
Print("file does not exist");
assertTrue(false);
return false;
}
}
public void xMakeFileCopy(String NewFileNameWithPath,
String FileNameWithPath) throws Exception {
java.io.File base = new java.io.File(FileNameWithPath);
java.io.File newfile = new java.io.File(NewFileNameWithPath);
if (xFileExist(FileNameWithPath)) {
FileUtils.copyFile(base, newfile);
} else {
Print("file does not existcould not copy");
assertTrue(false);
}
if (xFileExist(NewFileNameWithPath)) {
Print("file copied sucessfully");
}
}
public void xDeleteFile(String FileNameWithPath) throws Exception {
java.io.File file = new java.io.File(FileNameWithPath);
if (xFileExist(FileNameWithPath)) {
FileUtils.deleteQuietly(file);
Print("File Deleted Successfully");
} else {
Print("file does not exist.Could not Delete");
// assertTrue(false);
}
}
public static void xScreenShot() {
try {
String NewFileNamePath;
java.awt.Dimension resolution = Toolkit.getDefaultToolkit()
.getScreenSize();
Rectangle rectangle = new Rectangle(resolution);
// Get the dir path
File directory = new File(".");
// System.out.println(directory.getCanonicalPath());
// get current date time with Date() to create unique file name
DateFormat dateFormat = new SimpleDateFormat(
"MMM_dd_yyyy__hh_mm_ssaa");
// get current date time with Date()
Date date = new Date();
// System.out.println(dateFormat.format(date));
// To identify the system
InetAddress ownIP = InetAddress.getLocalHost();
// System.out.println("IP of my system is := "+ownIP.getHostAddress());
NewFileNamePath = directory.getCanonicalPath() + "\\ScreenShots\\"
+ Method + "___" + dateFormat.format(date) + "_"
+ ownIP.getHostAddress() + ".png";
System.out.println(NewFileNamePath);
// Capture the screen shot of the area of the screen defined by the
// rectangle
Robot robot = new Robot();
BufferedImage bi = robot.createScreenCapture(new Rectangle(
rectangle));
ImageIO.write(bi, "png", new File(NewFileNamePath));
NewFileNamePath = "<a href=" + NewFileNamePath + ">ScreenShot"
+ "</a>";
// Place the reference in TestNG web report
Reporter.log(NewFileNamePath);
} catch (AWTException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void xUpdateTestDetails(String Status) throws Exception {
File directory = new File(".");
String Temp = Method + "_" + Status;
if (Method != ""){
try {
Runtime.getRuntime().exec(
"wscript.exe " + directory.getCanonicalPath()
+ "\\UpdateTestDetails.vbs "+ Temp + " " + sMessages);
Method = "";
sMessages = "";
} catch (Exception e) {
e.printStackTrace();
}
}
Wait(5000); // Allow OS to kill the process
}
}
In Progress...