Working with Log4j logging…

Sometimes when we run our test cases it is very important to share some information about our test, for example if the test is failed or passed, any specific message or information to user regarding any exceptions or errors etc. This not only provides additional information to the tester or user but also helps in debugging if the test case fails during execution.

The easiest way of logging information is to use ‘System.out.println‘, which prints information into the console. But this has many disadvantages. One of them is that the printed information is not saved anywhere, as a result it’s lost after every execution. Another main disadvantage of using ‘System.out.println‘ is that it has impact to the performance of our code. However we can overcome all these problems by using a Logging tool.

There are many logging tools or APIs available in market. Some of them are: Log4j, SLF4J, Logback etc.. However we will be concentrating on Log4j as it is very simple and easy to learn.

Log4j has built-in log levels that allow users to log information into variety of destinations like a database, file, console etc.. The log level can be configured in a XML or Properties file. Different log levels in decreasing order of severity:

  • OFF – The highest possible rank and is intended to turn off logging.
  • FATAL – Severe errors that cause premature termination.
  • ERROR – Other runtime errors or unexpected conditions.
  • WARN – Use of deprecated APIs, poor use of API, ‘almost’ errors, other runtime situations that are undesirable or unexpected.
  • INFO – Interesting runtime events (startup/shutdown).
  • DEBUG – Detailed information on the flow through the system.
  • TRACE – Most detailed information.

Here we will be using ‘NewTourPageFactory‘ project from our last post Working with Page Factory….

  1. First step is to download the Log4j jars. Go to Google and type in ‘Log4j Jar download’. Click on the first link and we will be taken to ‘logging.apache.org’ website. Click below link to navigate directly to the download page. Log4j Download.
  2. Click ‘log4j-1.2.17.tar.gz’ as displayed below to navigate to download page.

    Log4j Download
  3. Click the below link to start the download. Once clicked, ‘log4j-1.2.17.tar’ will be downloaded in ‘Downloads’ folder of your local machine.

    Click to download Jar
  4. Unzip ‘log4j-1.2.17.tar’ file to view the Log4j jar. We have to add this jar into our Java Project.

    Log4j jar
  5. To add Log4j jar into our project, right click on the project name and click Properties. Click on Java Build Path -> Add External Jars. Go to the local folder where ‘log4j-1.2.17’ is saved. Add the Jar and click OK to close the window. Once complete, we will see ‘log4j-1.2.17’ jar inside ‘Referenced Libraries’.

    Add Log4j jar
  6. Next steps is to add the Configuration file. Configuration file not only allow us to set the destination output where we want to print the log information but also help us in setting up the log levels. We can configure Log4j using both Property file as well as XML file. Here we will be focusing on XML file. Right click on ‘src‘ folder and click New -> Other. Select XML file as displayed below. Click Next and name the file as ‘log4j.xml’.

    Select XML file and click Next
  7. Copy and paste below code into ‘log4j.xml’ file.
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
    	<!-- Appender for printing in console -->
    	<appender name="console" class="org.apache.log4j.ConsoleAppender">
    		<layout class="org.apache.log4j.PatternLayout">
    		    <!-- %d{yyyy-MM-dd HH:mm:ss} refers to Date format 2017-03-23 15:54:44 INFO  Log:15 -->
    		    <!-- %-5p refers to Type of logging INFO-->
    		    <!-- %c{1}  -->
    			<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
    		</layout>
    	</appender>
    	<!-- Appender for printing in file -->
    	<appender name="file" class="org.apache.log4j.RollingFileAppender">
    		<!-- Appending false -->
    		<param name="append" value="false" />
    		<!-- Maximum 10kb size. New file will be created and old file renamed -->
    		<param name="maxFileSize" value="10KB" />
    		<!-- Maximum 5 log files. Old ones will be deleted -->
    		<param name="maxBackupIndex" value="5" />
    		<!-- Location of log file -->
    		<param name="file" value="test-output/log/${logfoldername}/${logfilename}.log" />
    		<layout class="org.apache.log4j.PatternLayout">
    			<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
    		</layout>
    	</appender>
    
    
    	<root>
    	<!-- Setting Logging level  -->
    		<level value="info" />
    		<!-- Setting logging level for console -->
    		<appender-ref ref="console" />
    		<!-- Setting logging level for file -->
    		<appender-ref ref="file" />
    	</root>
    </log4j:configuration>
    
  8. In the above XML code, the appenders are defined first. We can have as many appenders based on different log destinations. In our case, we are naming them as ‘console’ and ‘file’ for printing logs to both console and file. Notice the class names used in both appenders. Inside appenders, a layout is defined, in our case ‘org.apache.log4j.PatternLayout’ class is used. This layout formats the logging event and return the result as String. The output depends on the conversion pattern. A conversion pattern is composed of literal text and format control expressions called conversion specifiers, which starts with a percent sign (%) and is followed by optional format modifiers and a conversion character. The log messages in our case will be printed in below format.

    Notice the information displayed before log messages
  9. Notice that for file appender, we are setting configurable parameters like setting the file size, number of files, location for saving the file etc.. For setting the log file name with current date, we will be using system properties (‘logfoldername‘ and ‘logfilename‘) set in our TestNG script.
  10. Next comes the root logger. The root logger mentioned in above XML code logs messages with level INFO or above to both destinations, console and file.
  11. Next step is to create a Log class with log methods. We will use these log methods in all our project class. Right click on ‘src‘ folder and click New ->  Package. Name the package as ‘com.selenium.util’ and click Finish to close the window. This is where we will place our Log class.
  12. Right click on ‘com.selenium.util’ package and click New -> Class. Name the class as ‘Log’. Copy and paste the below code into our Log.class file. Notice that we are creating a logger object using which we will be printing different log messages.
    package com.selenium.util;
    
    import org.apache.log4j.Logger;
    
    public class Log {
    
    	// Initialize Log4j logs
    	private static Logger Log = Logger.getLogger(Log.class.getName());//
    
    	// This is to print log for the beginning of the test case
    	public static void startTestCase(String sTestCaseName) {
    
    		Log.info("****************************************************************************************");
    
    		Log.info("$$$$$$$$$$$$$$$$$$$$$                 " + sTestCaseName + "       $$$$$$$$$$$$$$$$$$$$$$$$$");
    
    		Log.info("****************************************************************************************");
    	}
    
    	// This is to print log for the end of the test case
    	public static void endTestCase(String sTestCaseName) {
    
    		Log.info("XXXXXXXXXXXXXXXXXXXXXXX             " + "-E---N---D-" + "             XXXXXXXXXXXXXXXXXXXXXX");
    	}
    
    	// info method for printing info message
    	public static void info(String message) {
    
    		Log.info(message);
    	}
    
    	// warn method for printing warning message
    	public static void warn(String message) {
    
    		Log.warn(message);
    	}
    
    	// error method for printing error message
    	public static void error(String message) {
    
    		Log.error(message);
    	}
    
    	// fatal method for printing fatal message
    	public static void fatal(String message) {
    
    		Log.fatal(message);
    	}
    
    	// debug method for printing debug message
    	public static void debug(String message) {
    
    		Log.debug(message);
    	}
    }
    
  13. Next step is to update our Page Object and TestNG class with different log information. We will start with ‘HomePage’ class. Copy and paste below code to HomePage.java file. Notice that we have used ‘log.info’ methods to print log messages in console and file. Since we have set ‘level value=info‘ in ‘log4j.xml’ file, messages with priority equal or greater than ‘info‘ such as ‘warn‘, ‘error‘, ‘fatal‘ etc. will be printed. Other log level will not be printed.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import com.selenium.util.Log;
    
    public class HomePage {
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for username textbox
    	@FindBy(name = "userName")
    	private WebElement userName;
    	
    	//Element Locators for password textbox
    	@FindBy(name = "password")
    	private WebElement password;
    	
    	//Element Locators for login button
    	@FindBy(name = "login")
    	private WebElement login;
    	
    	//Constructor. Driver Initialization
    	public HomePage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	// Login method. Notice there is no return type
    	public void loginUser(String uname, String pwd) {
    		// Enter username
    		userName.sendKeys(uname);
    		Log.info("HomePage.loginUser - username entered");
    		// Enter password
    		password.sendKeys(pwd);
    		Log.info("HomePage.loginUser - password entered");
    		// Click Login button
    		login.click();
    		Log.info("HomePage.loginUser - login button clicked");
    	}
    }
    
  14. Update the ‘FlightFinderPage.java’ file with below code. Notice that we have used ‘log.info’ methods to print log messages in console and file.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.openqa.selenium.support.ui.Select;
    import com.selenium.util.Log;
    
    public class FlightFinderPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for one way radio button
    	@FindBy(xpath = "//input[@value='oneway']")
    	private WebElement onewayRadio;
    	
    	//Select the location from dropdown
    	@FindBy(name = "fromPort")
    	private WebElement fromPortDrop;
    	
    	//Select the day from dropdown
    	@FindBy(name = "fromDay")
    	private WebElement fromDayDrop;
    	
    	//Click Business radio button
    	@FindBy(xpath = "//input[@value='Business']")
    	private WebElement businessRadio;
    	
    	//Click find flights button
    	@FindBy(name = "findFlights")
    	private WebElement findFlightsButton;
    	
    	//Constructor. Driver Initialization
    	public FlightFinderPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Find Flights method. Notice there is no return type
    	public void findFlights(String departFrom, String departDate) {
    		// Click one way radio button
    		onewayRadio.click();
    		Log.info("FlightFinderPage.findFlights - One way Radio Button clicked");
    		// Select Departing From dropdown
    		Select dropFrom = new Select(fromPortDrop);
    		dropFrom.selectByValue(departFrom);
    		Log.info("FlightFinderPage.findFlights - Depart From Dropdown clicked");
    
    		// Select Departing Day dropdown
    		Select dropDay = new Select(fromDayDrop);
    		dropDay.selectByValue(departDate);
    		Log.info("FlightFinderPage.findFlights - Depart Date Dropdown clicked");
    
    		// Click business class
    		businessRadio.click();
    		Log.info("FlightFinderPage.findFlights - Business Radio Button clicked");
    
    		// Click Find Flights button
    		findFlightsButton.click();
    		Log.info("FlightFinderPage.findFlights - Find Flights Button clicked");
    	}
    
    }
    
  15. Update ‘SelectFlightPage.java’ file with below code.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.openqa.selenium.support.How;
    import com.selenium.util.Log;
    
    public class SelectFlightPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for reserve flight button
    	//@FindBy(name = "reserveFlights")
    	@FindBy(how = How.NAME, using = "reserveFlights")
    	private WebElement reserveFlightsButton;
    
    	//Constructor. Driver Initialization
    	public SelectFlightPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	// Reserve Flight method. Notice there is no return type
    	public void reserveFlight() {
    		// Click reserve Flights button
    		reserveFlightsButton.click();
    		Log.info("SelectFlightPage.reserveFlight - Reserve Flight button clicked");
    	}
    }
    
  16. Update ‘BookFlightPage.java’ file with below code. Notice that for better understanding we are including class.method name information in our log messages.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import com.selenium.util.Log;
    
    public class BookFlightPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for first name textbox
    	@FindBy(name = "passFirst0")
    	private WebElement firstNameTextBox;
    	
    	//Element Locators for last name textbox
    	@FindBy(name = "passLast0")
    	private WebElement lastNameTextBox;
    	
    	//Element Locators for credit number textbox
    	@FindBy(name = "creditnumber")
    	private WebElement ccNumberTextBox;
    	
    	//Element Locators for Buy Flights button
    	@FindBy(name = "buyFlights")
    	private WebElement buyFlightsButton;
    
    	//Constructor. Driver Initialization
    	public BookFlightPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Book Flight method.Notice there is no return type
    	public void bookFlight(String fname, String lname, String ccNumber) {
    		// Enter first name
    		firstNameTextBox.sendKeys(fname);
    		Log.info("BookFlightPage.bookFlight - First Name entered");
    		// Enter last name
    		lastNameTextBox.sendKeys(lname);
    		Log.info("BookFlightPage.bookFlight - Last Name entered");
    		// Enter credit card number
    		ccNumberTextBox.sendKeys(ccNumber);
    		      Log.info("BookFlightPage.bookFlight - Credit card entered");
    		// Confirm purchase
    		buyFlightsButton.click();
    		Log.info("BookFlightPage.bookFlight - Buy Flights Button clicked");
    	}
    }
    
  17. Update ‘FlightConfirmationPage.java’ file with below code.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.testng.Assert;
    
    import com.selenium.util.Log;
    
    public class FlightConfirmationPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for confirmation text
    	@FindBy(xpath = "//font[contains(text(),'Confirmation')]")
    	private WebElement confirmationText;
    	
    	//Element Locators for logout button
    	@FindBy(xpath = "//img[@src='/images/forms/Logout.gif']")
    	private WebElement logOutButton;
    
    	//Constructor. Driver Initialization
    	public FlightConfirmationPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Verify and Log out method. 
    	public void clickLogOut() {
    		// Assert if Confirmation text is displayed
    		Assert.assertTrue(confirmationText.isDisplayed());
    		Log.info("FlightConfirmationPage.clickLogOut - Assertion completed");
    		// Click Logout button
    		logOutButton.click();
    		Log.info("FlightConfirmationPage.clickLogOut - Log Out Button clicked");
    	}
    }
    
  18. Final step is to update the TestNG script with log messages. Update ‘BookFlight.java’ file with below code. Notice inside ‘beforeTest’ method we are setting the location of  ‘log4j.xml’ file and also setting system properties for ‘logfoldername‘ and ‘logfilename‘.
    package com.selenium.testcase;
    
    import org.testng.annotations.Test;
    import com.selenium.pages.BookFlightPage;
    import com.selenium.pages.FlightConfirmationPage;
    import com.selenium.pages.FlightFinderPage;
    import com.selenium.pages.HomePage;
    import com.selenium.pages.SelectFlightPage;
    import com.selenium.util.Log;
    import org.testng.annotations.BeforeMethod;
    import org.apache.log4j.xml.DOMConfigurator;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.support.PageFactory;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.BeforeTest;
    
    public class BookFlight {
    
    	WebDriver driver;
    	HomePage homePage;
    	FlightFinderPage flightFinderPage;
    	SelectFlightPage selectFlightPage;
    	BookFlightPage bookFlightPage;
    	FlightConfirmationPage flightConfirmationPage;
    
    	// Test Case
    	@Test(dataProvider = "newTourData")
    	public void bookFlight(String uname, String pwd, String departFrom, String departDate, String fname, String lname,
    			String ccNum) {
    
    		/*
    		 * Test case logic.
    		 * 
    		 */
    		Log.startTestCase("Test Case bookFlight");
    		Log.info("BookFlight.bookFlight - Home Page Opens");
    		homePage.loginUser(uname, pwd);
    		Log.info("BookFlight.bookFlight - Flight Finder Page Opens");
    		flightFinderPage.findFlights(departFrom, departDate);
    		Log.info("BookFlight.bookFlight - Select Flight Page Opens");
    		selectFlightPage.reserveFlight();
    		Log.info("BookFlight.bookFlight - Book Flight Page Opens");
    		bookFlightPage.bookFlight(fname, lname, ccNum);
    		Log.info("BookFlight.bookFlight - Flight Confirmation Page Opens");
    		flightConfirmationPage.clickLogOut();
    		Log.endTestCase("Test Case bookFlight");
    
    	}
    
    	// Driver and Page Objects Initialization
    	@BeforeMethod
    	public void beforeMethod() {
    		// Initialize driver
    		driver = new ChromeDriver();
    		Log.info("BookFlight.beforeMethod - Chrome Driver Intialized");
    		// Maximize window
    		driver.manage().window().maximize();
    		Log.info("BookFlight.beforeMethod - Browser maximized");
    
    		// Page Factory Initialization for all page objects
    		homePage = PageFactory.initElements(driver, HomePage.class);
    		flightFinderPage = PageFactory.initElements(driver, FlightFinderPage.class);
    		selectFlightPage = PageFactory.initElements(driver, SelectFlightPage.class);
    		bookFlightPage = PageFactory.initElements(driver, BookFlightPage.class);
    		flightConfirmationPage = PageFactory.initElements(driver, FlightConfirmationPage.class);
    		Log.info("BookFlight.beforeMethod - Page Factory intialization complete");
    
    		// Nvaigate to URL
    		driver.get("http://newtours.demoaut.com");
    		Log.info("BookFlight.beforeMethod - open New Tour URL");
    
    	}
    
    	// Driver closure
    	@AfterMethod
    	public void afterMethod() {
    		// Close and quit the driver to close the Browser
    		driver.close();
    		driver.quit();
    		Log.info("BookFlight.afterMethod - Browser closed");
    	}
    
    	// Create test data
    	@DataProvider
    	public Object[][] newTourData() {
    		return new Object[][] { { "demo", "demo", "London", "7", "john", "Doe", "56465465" } };
    	}
    
    	// Setting System property
    	@BeforeTest
    	public void beforeTest() {
    		// Set System Property for driver location
    		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
    		SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd");
    		SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    		//Set System Property for log file location
    		System.setProperty("logfoldername", sdf1.format(Calendar.getInstance().getTime()));
    		System.setProperty("logfilename", sdf2.format(Calendar.getInstance().getTime()) + "-LOG");
    		
    		Log.info("BookFlight.beforeTest - System property setting complete");
    		DOMConfigurator.configure("src/log4j.xml");
    		Log.info("BookFlight.beforeTest - XML config complete");
    	}
    }
    
  19. Below is how project structure looks like.

    Project Structure
  20. Now that we have completed all changes in our ‘NewTourPageFactory‘ project, next step is to run our TestNG script. Right click on ‘BookFlight’ class and click Run As -> TestNG Test.
  21. After successful execution, log messages are printed in console as displayed below.

    Log messages in console output
  22. A new log file is created under ‘NewTourPageFactory/test-output/log’ folder. This is based on the setting ‘value=test-output/log/${logfoldername}/${logfilename}.log’ that we did in ‘log4j.xml’ file.

    Log file inside test-output folder

With this we came to an end of Log4j logging. We will be using the logging in future frameworks.

Till then Happy Learning!

Additional Information:

Instead of ‘log4j.xml’ file we can also use ‘log4j.properties’ file.

  • Delete the already created ‘log4j.xml’ file from ‘src‘ folder and comment the code ‘DOMConfigurator.configure(“src/log4j.xml”)’ inside ‘beforeTest’ method of ‘BookFlight.java’. Right click on ‘src‘ folder and click New -> Other. Select File under General folder.

    Select File
  • Name the file as ‘log4j.properties’ and click finish to close the window. Copy and paste below details to the properties files.
    # Root logger option
    log4j.rootLogger=INFO, file, console
    
    # Direct log messages to a log file
    log4j.appender.file=org.apache.log4j.RollingFileAppender
    log4j.appender.file.Append=False
    log4j.appender.file.File=${user.dir}/test-output/log/${logfoldername}/${logfilename}.log
    log4j.appender.file.MaxFileSize=10KB
    log4j.appender.file.MaxBackupIndex=5
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
    
    # Direct log messages to console
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target=System.out
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
    
  • We can see the same results in console as well as in log file.

Working with Page Factory…

If we closely analyze the code in Part 1: Page Object Model…, we have not used Page Factory class anywhere in our code. Now, what is Page Factory? Page Factory is an inbuilt library inside Selenium WebDriver for supporting Page Object Model concept. One of the highlights of using Page Factory is that it reduces code duplication. Without Page Factory, we have to use the below code multiple times in the same Page Object.

// Enter username
driver.findElement(userName).sendKeys(uname);

Page Factory pattern works by first identifying web elements using ‘@FindBy’ annotation and then relying on the ‘PageFactory’ class to create an instance of the Page Object. It uses the ‘initElements’ method to initialize the Page Object, which has a constructor with ‘WebDriver’ as its sole argument. Below is the sample code of a Page Object using Page Factory pattern.

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

public class SelectFlightPage {

	//Declare WebDriver
	WebDriver driver;
	//Element Locators @FindBy annotation
	@FindBy(name = "reserveFlights")
	private WebElement reserveFlightsButton;
        
        //Constructor for initializing the driver
	public SelectFlightPage(WebDriver driver) {
		this.driver = driver;
	}

	// Reserve Flight method
	public void reserveFlight() {
		// Click reserve Flights button
		reserveFlightsButton.click();
	}
}

We will automate same test case used in Part 1: Page Object Model… but this time using Page Factory pattern.

Test Case:

  • Navigate to http://newtours.demoaut.com website and login.
  • Select an itinerary and complete the booking.
  • Log out and close the browser.

We will create five Page Objects using Page Factory pattern for five different pages. They are:

  • Home Page or Login Page
  • Flight Finder Page
  • Select Flight Page
  • Book Flight Page
  • Flight Confirmation Page

New Project Creation:

  1. Launch Eclipse. Create a new Java Project by clicking on File -> New -> Java Project. Name the Java Project as ‘NewTourPageFactory’. Click Next and then Finish to complete the project creation. Once the project is created, we will have ‘src‘ folder and ‘JRE System Library‘ by default.

    NewTourPageFactory Java Project
  2. Next step is to add the Selenium Jars. Right Click on the project name and click Properties. Click on Java Build Path -> Add External Jars. Go to the local folder where ‘selenium-server-standalone-3.0.1’ is saved. We will be using 3.0.1 version from our last project. Add the Jar and click OK to close the window. Once complete, we will see ‘Referenced Libraries‘ included in our project.

    Add selenium-server-standalone-3.0.1 jar
  3. Next step is to add the Chrome Driver. Right click on the project name and click New -> Folder. Give the folder name as ‘Chrome’ and click Finish to close the window. Now place the Chrome driver that we already downloaded in our last project into the ‘Chrome’ folder.

    Add Chrome Driver
  4. Next step is to add the TestNG libraries into our project. Right click on the project ‘FrameworkDesign’ and click Properties -> Java Build Path -> Add Library. In Add Library window, select TestNG and click on Next. Click on Finish to add the TestNG Library. Click OK to close the Properties window.

    Add TestNG
  5. Right click on the ‘src’ folder and click New -> Package. Name the package as ‘com.selenium.pages’ and click Finish to close the window. This is where we will place our page specific code and their methods.

Page Object Model with Page Factory:

We will now create class files for each of the five pages.

  1. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘HomePage’. In this class we will place Home Page element locators and methods. Click Finish to close the window. Notice that we will not require static main method in any of our class files.
  2. This is how our Home Page looks like. Notice that we have three elements that need to be located.

    Home Page
  3. Copy the below code and paste into ‘HomePage’ class. Notice how the page specific Element locator code are written using ‘@FindBy‘ annotation. Unlike the methods in our last ‘NewTour’ Project, this time the methods don’t have any return types.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    
    public class HomePage {
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for username textbox
    	@FindBy(name = "userName")
    	private WebElement userName;
    	
    	//Element Locators for password textbox
    	@FindBy(name = "password")
    	private WebElement password;
    	
    	//Element Locators for login button
    	@FindBy(name = "login")
    	private WebElement login;
    	
    	//Constructor. Driver Initialization
    	public HomePage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	// Login method. Notice there is no return type
    	public void loginUser(String uname, String pwd) {
    		// Enter username
    		userName.sendKeys(uname);
    		// Enter password
    		password.sendKeys(pwd);
    		// Click Login button
    		login.click();
    	}
    }
    
  4. We will create a second class for our next page. Once the login is successful, we will be taken to Flight Finder Page. Flight Finder page has many elements. For now we will be concentrating on ‘One Way’, ‘Departing From’, ‘On Date’ and ‘Continue’ elements. Below is how our Flight Finder page looks like.

    Flight Finder Page
  5. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘FlightFinderPage’. In this class we will place Flight Finder Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘FlightFinderPage’ class. Notice that the layout is similar to ‘HomePage’ class. We have one method that takes two parameters and has a return type object of ‘SelectFlightPage’ class. Notice that the method has no return type.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.openqa.selenium.support.ui.Select;
    
    public class FlightFinderPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for one way radio button
    	@FindBy(xpath = "//input[@value='oneway']")
    	private WebElement onewayRadio;
    	
    	//Select the location from dropdown
    	@FindBy(name = "fromPort")
    	private WebElement fromPortDrop;
    	
    	//Select the day from dropdown
    	@FindBy(name = "fromDay")
    	private WebElement fromDayDrop;
    	
    	//Click Business radio button
    	@FindBy(xpath = "//input[@value='Business']")
    	private WebElement businessRadio;
    	
    	//Click find flights button
    	@FindBy(name = "findFlights")
    	private WebElement findFlightsButton;
    	
    	//Constructor. Driver Initialization
    	public FlightFinderPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Find Flights method. Notice there is no return type
    	public void findFlights(String departFrom, String departDate) {
    		// Click one way radio button
    		onewayRadio.click();
    		// Select Departing From dropdown
    		Select dropFrom = new Select(fromPortDrop);
    		dropFrom.selectByValue(departFrom);
    
    		// Select Departing Day dropdown
    		Select dropDay = new Select(fromDayDrop);
    		dropDay.selectByValue(departDate);
    
    		// Click business class
    		businessRadio.click();
    
    		// Click Find Flights button
    		findFlightsButton.click();
    	}
    }
    
  6. From Flight Finder page we will be taken to Select Flight page. Next step is to create a class file for Select Flight page. We will go with the default selection of flights and click continue to go to next page. Below is how our Select Flight page looks like.

    Select Flight Page
  7. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘SelectFlightPage’. In this class we will place Select Flight Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘SelectFlightPage’ class. Notice that we have only one method that takes no parameters and there is no return type.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    
    public class SelectFlightPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for reserve flight button
    	@FindBy(name = "reserveFlights")
    	private WebElement reserveFlightsButton;
    
    	//Constructor. Driver Initialization
    	public SelectFlightPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	// Reserve Flight method. Notice there is no return type
    	public void reserveFlight() {
    		// Click reserve Flights button
    		reserveFlightsButton.click();
    	}
    }
    
  8. From Select Flight page we will navigate to Book Flight page. We will create a class file for Book Flight page. Although Book Flight page has many elements but we will be concentrating on ‘First Name’, ‘Last Name’ and ‘Number’ elements. Below is how our Book Flight page looks like.

    Book Flight Page
  9. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘BookFlightPage’. In this class we will place Book Flight Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘BookFlightPage’ class. Notice that we have only one method that take three parameters and has no return type.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    
    public class BookFlightPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for first name textbox
    	@FindBy(name = "passFirst0")
    	private WebElement firstNameTextBox;
    	
    	//Element Locators for last name textbox
    	@FindBy(name = "passLast0")
    	private WebElement lastNameTextBox;
    	
    	//Element Locators for credit number textbox
    	@FindBy(name = "creditnumber")
    	private WebElement ccNumberTextBox;
    	
    	//Element Locators for Buy Flights button
    	@FindBy(name = "buyFlights")
    	private WebElement buyFlightsButton;
    
    	//Constructor. Driver Initialization
    	public BookFlightPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Book Flight method.Notice there is no return type
    	public void bookFlight(String fname, String lname, String ccNumber) {
    		// Enter first name
    		firstNameTextBox.sendKeys(fname);
    		// Enter last name
    		lastNameTextBox.sendKeys(lname);
    		// Enter credit card number
    		ccNumberTextBox.sendKeys(ccNumber);
    		// Confirm purchase
    		buyFlightsButton.click();
    	}
    }
    
  10. Finally we will create one more class file for Flight Confirmation page. We will verify the confirmation message by using Assert statement and then log out from the page. Below is how our Flight Confirmation page looks like.

    Flight Confirmation Page
  11. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘FlightConfirmationPage’. In this class we will place Flight Confirmation Page element locators and methods. Click Finish to close the window. Notice that we have only one method that takes no parameters and has no return type. Copy the below code and paste into ‘FlightConfirmationPage’ class.
    package com.selenium.pages;
    
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.WebElement;
    import org.openqa.selenium.support.FindBy;
    import org.testng.Assert;
    
    public class FlightConfirmationPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators for confirmation text
    	@FindBy(xpath = "//font[contains(text(),'Confirmation')]")
    	private WebElement confirmationText;
    	
    	//Element Locators for logout button
    	@FindBy(xpath = "//img[@src='/images/forms/Logout.gif']")
    	private WebElement logOutButton;
    
    	//Constructor. Driver Initialization
    	public FlightConfirmationPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Verify and Log out method. 
    	public void clickLogOut() {
    		// Assert if Confirmation text is displayed
    		Assert.assertTrue(confirmationText.isDisplayed());
    		// Click Logout button
    		logOutButton.click();
    	}
    }
    

This is how our Project structure looks like. We have successfully implemented Page Object Model using Page Factory pattern in our framework. We created five class files for five different pages and each class file have its own page specific code.

Project Structure

 

TestNG Script:

Next step is to create TestNG script which will use the Page Objects to complete the execution.

  1. Inside ‘NewTourPageFactory’ project, right click ‘src’ folder and click New -> Package. Name the package as ‘com.selenium.testcase’ and click Finish to close the window. This is where we will place our test case logic.
  2. Right click on ‘com.selenium.testcase’ package inside ‘src’ folder and select TestNG -> Create TestNG Class as shown below.

    Create TestNG Class
  3. Name the TestNG class as ‘BookFlight’. Select the following annotations – ‘@BeforeMethod’, ‘@AfterMethod’, ‘@DataProvider’, ‘@BeforeTest’.
  4. Copy the below code and paste into ‘BookFlight’ class. Notice that inside ‘@BeforeMethod’, ‘PageFactory’ class uses the ‘initElements’ method to initialize the Page Object and pass the driver information.
    package com.selenium.testcase;
    
    import org.testng.annotations.Test;
    import com.selenium.pages.BookFlightPage;
    import com.selenium.pages.FlightConfirmationPage;
    import com.selenium.pages.FlightFinderPage;
    import com.selenium.pages.HomePage;
    import com.selenium.pages.SelectFlightPage;
    import org.testng.annotations.BeforeMethod;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.openqa.selenium.support.PageFactory;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.BeforeTest;
    
    public class BookFlight {
    
    	WebDriver driver;
    	HomePage homePage;
    	FlightFinderPage flightFinderPage;
    	SelectFlightPage selectFlightPage;
    	BookFlightPage bookFlightPage;
    	FlightConfirmationPage flightConfirmationPage;
    
    	// Test Case
    	@Test(dataProvider = "newTourData")
    	public void bookFlight(String uname, String pwd, String departFrom, String departDate, String fname, String lname,
    			String ccNum) {
    
    		/*
    		 * Test case logic.
    		 * 
    		 */
    		homePage.loginUser(uname, pwd);
    		flightFinderPage.findFlights(departFrom, departDate);
    		selectFlightPage.reserveFlight();
    		bookFlightPage.bookFlight(fname, lname, ccNum);
    		flightConfirmationPage.clickLogOut();
    
    	}
    
    	// Driver and Page Objects Initialization
    	@BeforeMethod
    	public void beforeMethod() {
    		// Initialize driver
    		driver = new ChromeDriver();
    		// Maximize window
    		driver.manage().window().maximize();
    
    		// Page Factory Initialization for all page objects
    		homePage = PageFactory.initElements(driver, HomePage.class);
    		flightFinderPage = PageFactory.initElements(driver, FlightFinderPage.class);
    		selectFlightPage = PageFactory.initElements(driver, SelectFlightPage.class);
    		bookFlightPage = PageFactory.initElements(driver, BookFlightPage.class);
    		flightConfirmationPage = PageFactory.initElements(driver, FlightConfirmationPage.class);
    
    		// Nvaigate to URL
    		driver.get("http://newtours.demoaut.com");
    
    	}
    
    	// Driver closure
    	@AfterMethod
    	public void afterMethod() {
    		// Close and quit the driver to close the Browser
    		driver.close();
    		driver.quit();
    	}
    
    	// Create test data
    	@DataProvider
    	public Object[][] newTourData() {
    		return new Object[][] { { "demo", "demo", "London", "7", "john", "Doe", "56465465" } };
    	}
    
    	// Setting System property
    	@BeforeTest
    	public void beforeTest() {
    		// Set System Property
    		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
    	}
    }
    
  5. To run our TestNG script, right click on ‘BookFlight’ class and click Run As -> TestNG Test.

    Run As TestNG Test
  6. After successful execution, below result is displayed.

    Test Results

With this we came to an end on Page Factory pattern. It is very important that we use the Page Factory pattern in Page Object Model as it reduces code duplication to large extent.

Many more interesting topics to follow. Till then, Happy Learning!

Additional Information:

  • There is also another way of initializing the Page Objects using Page Factory. Instead of initializing all the Page Objects inside ‘@BeforeMethod’ of TestNG script, we can initialize the Page Object of next page inside a method of current Page. Below code is an example of ‘SelectFlightPage’ class. Notice that ‘reserveFlight()’ method is having a return type of ‘BookFlightPage’.
package com.selenium.pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

public class SelectFlightPage {

	//Declare WebDriver
	WebDriver driver;
	//Element Locators for reserve flight button
	@FindBy(name = "reserveFlights")
	private WebElement reserveFlightsButton;

	//Constructor. Driver Initialization
	public SelectFlightPage(WebDriver driver) {
		this.driver = driver;
	}

	// Reserve Flight method. Notice there is no return type
	public BookFlightPage reserveFlight() {
		// Click reserve Flights button
		reserveFlightsButton.click();
                return PageFactory.initElements(driver, BookFlightPage.class);

	}
}
  • There is also another way of using ‘@FindBy‘ annotation. For this we have to import ‘org.openqa.selenium.support.How’. This is to make code more readable.
            @FindBy(how = How.NAME, using = "reserveFlights")
    	private WebElement reserveFlightsButton;
    

Part 2: Page Object Model…

We concluded our last post Part 1: Page Object Model… by creating Page Objects for our test case. In this post we will focus on the TestNG script creation inside ‘NewTour’. This TestNG script will use the Page Objects to complete the execution.

  1. Inside ‘NewTour’ project, right click ‘src’ folder and click New -> Package. Name the package as ‘com.selenium.testcase’ and click Finish to close the window. This is where we will place our test case logic.
  2. Right click on ‘com.selenium.testcase’ package inside ‘src’ folder and select TestNG -> Create TestNG Class as shown below.

    Create TestNG Class
  3. Name the TestNG class as ‘BookFlight’. Select the following annotations – ‘@BeforeMethod’, ‘@AfterMethod’, ‘@DataProvider’, ‘@BeforeTest’.
  4. Copy the below code and paste into ‘BookFlight’ class.
    package com.selenium.testcase;
    
    import org.testng.annotations.Test;
    import com.selenium.pages.HomePage;
    import org.testng.annotations.BeforeMethod;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.BeforeTest;
    
    public class BookFlight {
    
    	WebDriver driver;
    	HomePage homePage;
    
    	//Test Case
    	@Test(dataProvider = "newTourData")
    	public void bookFlight(String uname, String pwd, String departFrom, String departDate, String fname, String lname,
    			String ccNum) {
    		// Create an object of HomePage
    		homePage = new HomePage(driver);
            /*
             * Test case logic.
             * Notice the return type of methods
             * are objects to next page.
             * */
    		homePage.loginUser(uname, pwd)
    		.findFlights(departFrom, departDate)
    		.reserveFlight()
    		.bookFlight(fname, lname,
    				ccNum);
    
    	}
    
    	//Driver Initialization
    	@BeforeMethod
    	public void beforeMethod() {
    		// Initialize driver
    		driver = new ChromeDriver();
    		// Maximize window
    		driver.manage().window().maximize();
    		// Nvaigate to URL
    		driver.get("http://newtours.demoaut.com");
    	}
    
    	//Driver closure
    	@AfterMethod
    	public void afterMethod() {
    		// Close and quit the driver to close the Browser
    		driver.close();
    		driver.quit();
    	}
    
    	//Create test data
    	@DataProvider
    	public Object[][] newTourData() {
    		return new Object[][] { { "demo", "demo", "London", "7", "john", "Doe", "56465465" } };
    	}
    
    	//Setting System property
    	@BeforeTest
    	public void beforeTest() {
    		// Set System Property
    		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
    	}
    }
    
  5. Here we are using ‘@BeforeTest’ method to set the system property, ‘@BeforeMethod’ to initialize the driver, ‘@Test’ method for test case logic, ‘@AfterMethod’ to close the driver and ‘@DataProvider’ for passing our test data. Notice that in ‘@Test’ method we are using Page Objects to access different web elements.
  6. To run our TestNG script, right click on ‘BookFlight’ class and click Run As -> TestNG Test.

    Run As TestNG Test
  7. Below is how our project structure looks like.

    Project Structure
  8. We can also use ‘testng.xml’ to execute our TestNG script however in this post we will directly execute our ‘BookFlight’ class. For more details on ‘testng.xml’, refer Part 3: Scripting using TestNG….
  9. After successful execution, below result is displayed.
    TestNG Result

    With this we came to an end to our second part of POM. In the next post we will concentrate on Page Factory, which is another interesting topic and it’s use in POM. Till then, Happy Learning!

Part 1: Page Object Model…

Page Object Model or POM is a design concept that is used to make the code more cleaner and easier to maintain. So far we have included all our test case logic and page elements in a single class file. As our test coverage increases, any change in page elements will result in the maintenance of all class files wherever the elements are used. This makes our code harder to maintain. Page Object Model design pattern overcome this problem by separating the test code and page specific code such as locators and reusable methods in different class files.

In this post we will create a new Java Project using TestNG. We will implement Page Object Model design pattern in our framework to complete one flight booking in New Tours web site.

Test Case:

  • Navigate to http://newtours.demoaut.com website and login.
  • Select an itinerary and complete the booking.
  • Log out and close the browser.

Even though there are only three steps in our test case but in actual we will navigate through five different pages to complete our test. They are:

  • Home Page or Login Page
  • Flight Finder Page
  • Select Flight Page
  • Book Flight Page
  • Flight Confirmation Page

New Project Creation:

We will create a new Java Project for this exercise. Let’s get started.

  1. Launch Eclipse. Create a new Java Project by clicking on File -> New -> Java Project. Name the Java Project as ‘NewTour’. Click Next and then Finish to complete the project creation. Once the project is created, we will have ‘src‘ folder and ‘JRE System Library‘ by default.

    New Java Project
  2. Next step is to add the Selenium Jars. Right Click on the project name and click Properties. Click on Java Build Path -> Add External Jars. Go to the local folder where ‘selenium-server-standalone-3.0.1’ is saved. We will be using 3.0.1 version from our last project. Add the Jar and click OK to close the window. Once complete, we will see ‘Referenced Libraries‘ included in our project.

    Add selenium-server-standalone-3.0.1 jar
  3. Next step is to add the Chrome Driver. Right click on the project name and click New -> Folder. Give the folder name as ‘Chrome’ and click Finish to close the window. Now place the Chrome driver that we already downloaded in our last project into the ‘Chrome’ folder.

    Add Chrome Driver
  4.  Next step is to add the TestNG libraries into our project. Right click on the project ‘FrameworkDesign’ and click Properties -> Java Build Path -> Add Library. In Add Library window, select TestNG and click on Next. Click on Finish to add the TestNG Library. Click OK to close the Properties window.

    Add TestNG
  5. Right click on the ‘src’ folder and click New -> Package. Name the package as ‘com.selenium.pages’ and click Finish to close the window. This is where we will place our page specific code and their methods.

Page Object Model:

We will now create class files for each of the five pages.

  1. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘HomePage’. In this class we will place Home Page element locators and methods. Click Finish to close the window. Notice that we will not require static main method in any of our class files.
  2. This is how our Home Page looks like. Notice that we have three elements that need to be located.

    Home Page
  3. Copy the below code and paste into ‘HomePage’ class. Notice how the page specific Element locator code and method are written. The method takes two parameters and has a return type object of ‘FlightFinderPage’.
    package com.selenium.pages;
    
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    
    public class HomePage {
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators
    	 By userName = By.name("userName");
    	By password = By.name("password");
    	By loginButton = By.name("login");
       
    	//Constructor. Driver Initialization
    	public HomePage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	// Login method
    	public FlightFinderPage loginUser(String uname, String pwd) {
    		// Enter username
    		driver.findElement(userName).sendKeys(uname);
    		// Enter password
    		driver.findElement(password).sendKeys(pwd);
    		// Click Login button
    		driver.findElement(loginButton).click();
    		/*
    		 * return type of this method is a new object to next          page	 */
    		return new FlightFinderPage(driver);
    	}
    }
    
  4. We will create a second class for our next page. Once the login is successful, we will be taken to Flight Finder Page. Flight Finder page has many elements. For now we will be concentrating on ‘One Way’, ‘Departing From’, ‘On Date’ and ‘Continue’ elements. Below is how our Flight Finder page looks like.

    Flight Finder Page
  5. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘FlightFinderPage’. In this class we will place Flight Finder Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘FlightFinderPage’ class. Notice that the layout is similar to ‘HomePage’ class. We have one method that takes two parameters and has a return type object of ‘SelectFlightPage’ class.
    package com.selenium.pages;
    
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.support.ui.Select;
    
    public class FlightFinderPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators
    	 By onewayRadio = By.xpath("//input[@value='oneway']");
    	By fromPortDrop = By.name("fromPort");
    	By fromDayDrop = By.name("fromDay");
    	By businessRadio = By.xpath("//input[@value='Business']");
    	By findFlightsButton = By.name("findFlights");
    
            //Constructor. Driver Initialization
    	public FlightFinderPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	public SelectFlightPage findFlights(String departFrom, String departDate) {
    		// Click one way radio button
    		driver.findElement(onewayRadio).click();
    		// Select Departing From dropdown
    		Select dropFrom = new Select(driver.findElement(fromPortDrop));
    		dropFrom.selectByValue(departFrom);
    
    		// Select Departing Day dropdown
    		Select dropDay = new Select(driver.findElement(fromDayDrop));
    		dropDay.selectByValue(departDate);
    
    		// Click business class
    		driver.findElement(businessRadio).click();
    
    		// Click Find Flights button
    		driver.findElement(findFlightsButton).click();
    
    		/*
    		 * return type of this method is a new object to next page
    		 */
    		return new SelectFlightPage(driver);
    	}
    }
    
  6. From Flight Finder page we will be taken to Select Flight page. Next step is to create a class file for Select Flight page. We will go with the default selection of flights and click continue to go to next page. Below is how our Select Flight page looks like.

    Select Flight Page
  7. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘SelectFlightPage’. In this class we will place Select Flight Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘SelectFlightPage’ class. Notice that we have only one method that takes no parameters and has a return type object of ‘BookFlightPage’ class.
    package com.selenium.pages;
    
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    
    public class SelectFlightPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators
    	By reserveFlightsButton = By.name("reserveFlights");
            
            //Constructor. Driver Initialization
    	public SelectFlightPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	// Reserve Flight method
    	public BookFlightPage reserveFlight() {
    		// Click reserve Flights button
    		driver.findElement(reserveFlightsButton).click();
    		/*
    		 * return type of this method is a new object to next page
    		 */
    		return new BookFlightPage(driver);
    	}
    }
    
  8. From Select Flight page we will navigate to Book Flight page. We will create a class file for Book Flight page. Although Book Flight page has many elements but we will be concentrating on ‘First Name’, ‘Last Name’ and ‘Number’ elements. Below is how our Book Flight page looks like.

    Book Flight Page
  9. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘BookFlightPage’. In this class we will place Book Flight Page element locators and methods. Click Finish to close the window. Once done, copy the below code and paste into ‘BookFlightPage’ class. Notice that we have only one method that take three parameters and has a return type object of ‘FlightConfirmationPage’.
    package com.selenium.pages;
    
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    
    public class BookFlightPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators
    	 By firstNameTextBox = By.name("passFirst0");
    	By lastNameTextBox = By.name("passLast0");
    	By ccNumberTextBox = By.name("creditnumber");
    	By buyFlightsButton = By.name("buyFlights");
    
    	//Constructor. Driver Initialization
    	public BookFlightPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	public FlightConfirmationPage bookFlight(String fname, String lname, String ccNumber) {
    		// Enter first name
    		driver.findElement(firstNameTextBox).sendKeys(fname);
    		// Enter last name
    		driver.findElement(lastNameTextBox).sendKeys(lname);
    		// Enter credit card number
    		driver.findElement(ccNumberTextBox).sendKeys(ccNumber);
    		// Confirm purchase
    		driver.findElement(buyFlightsButton).click();
    		/*
    		 * return type of this method is a new object to next page
    		 */
    		return new FlightConfirmationPage(driver);
    	}
    }
    
  10. Finally we will create one more class file for Flight Confirmation page. We will verify the confirmation message by using Assert statement and then log out from the page. Below is how our Flight Confirmation page looks like.

    Flight Confirmation Page
  11. Right click on ‘com.selenium.pages’ package and click New -> Class. Name the class as ‘FlightConfirmationPage’. In this class we will place Flight Confirmation Page element locators and methods. Click Finish to close the window. Notice that we have only one method that takes no parameters and has no return type. This is because our test case ends here and we don’t have any other page to navigate. Copy the below code and paste into ‘FlightConfirmationPage’ class.
    package com.selenium.pages;
    
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.testng.Assert;
    
    public class FlightConfirmationPage {
    
    	//Declare WebDriver
    	WebDriver driver;
    	//Element Locators
    	 By confirmationText = By.xpath("//font[contains(text(),'Confirmation')]");
    	By logOutButton = By.xpath("//img[@src='/images/forms/Logout.gif']");
    
    	//Constructor. Driver Initialization
    	public FlightConfirmationPage(WebDriver driver) {
    		this.driver = driver;
    	}
    
    	//Verify and Log out method
    	public void clickLogOut() {
    		// Assert if Confirmation text is displayed
    		Assert.assertTrue(driver.findElement(confirmationText).isDisplayed());
    		// Click Logout button
    		driver.findElement(logOutButton).click();
    	}
    }
    
  12. This is how our Project structure looks like. We have successfully implemented Page Object Model in our framework. We created five class files for five different pages and each class file have its own page specific code.

    Project Structure

We will continue with test case creation using TestNG in our next post. Till then, Happy Learning!

 

Part 3: Scripting using TestNG…

A Test Suite is a collection of tests. In our last post Part 2: Scripting using TestNG…, if we check the TestNG results of our ‘FirstTestNScript’ script, we had no control over Test Suite or Test name. By default they are named as ‘Default suite’ and ‘Default test’.

TestNG Result

However with the use of testing.xml, not only we can provide names to our test suites and tests but also we can configure our test run, set test dependency, include or exclude any test, method, class or package and set priority etc. In this post we will go through the testng.xml file creation and also some of the ways to configure our ‘FirstTestNScript’ script.

  1. Right click on our Java Project ‘FrameworkDesign’ and click New -> Other. Below window appears and we need to select XML file inside XML folder. Click Next and name the file as ‘testng.xml‘. Click Finish to close the window.

    Select XML file
  2. Once after completing the above step, we can see that the ‘testing.xml’ file is added to our Java project. There will be two tabs: Design and Source. In the Source tab, copy the below code.
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <!-- Naming our Suite as OurFirstSuite -->
    <suite name="OurFirstSuite" verbose="1" >
    <!-- Naming our Test as Functional. We can have as many tests --> 
      < test name="Functional" >
      <!-- Add TestNG class for execution -->
        < classes >
          <!-- Note class name include package name also -->
           < class name="com.selenium.testcase.FirstTestNScript" />
        </classes >
      </test >
    </suite >
    
  3. If we go through the above code, we provided our suite name as ‘OurFirstSuite’ in the <suite> tag. This is followed by <test name=”Functional”> tag. We can have as many <test> tags but with different name. Inside the <test> tags, <classes> tags are placed. Notice that class name is a combination of package name and TestNG class.
  4. To execute our TestNG script using ‘testing.xml’, right click on ‘testing.xml’ file and click Run As -> TestNG Suite.

    Run As TestNG Suite
  5. After successful execution, below results are displayed. Test Suite is now named as ‘OurFirstSuite’ and test is named as ‘Functional’.

    TestNG Results
  6. What if we want to run only ‘secondTestCase’ test from our ‘FirstTestNScript’ script? We can configure ‘testng.xml’ to select only required ‘@Test’ methods. In the Source tab, copy the below code.
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <!-- Naming our Suite as OurFirstSuite -->  
    <suite name="OurFirstSuite" verbose="1" >
    <!-- Naming our Test as Functional. We can have as many tests --> 
      <test name="Functional" >
      <!-- Add TestNG class for execution -->  
        <classes>
          <!-- Note class name include package name also -->
           <class name="com.selenium.testcase.FirstTestNScript">
           <!-- Include selective tests -->
           <methods>
           <!-- Only Second Test Case will be executed -->
           <include name="secondTestCase">
           </include>
           </methods>
           </class>
        </classes>
      </test>
    </suite>
    
  7. In the above code we are configuring ‘testing.xml’ to consider only one ‘@Test’ method from our ‘FirstTestNScript’ script. For this we have to use <method> tag followed by <include> tag. However to exclude a particular ‘@Test’ method from our test, we can use <exclude> tag inside <method> tag. After successful execution, below results are displayed. Only the included ‘@Test’ method is executed.

    secondTestCase executed
  8. We can configure ‘testing.xml’ to consider packages instead of classes. This comes in handy when we are doing regression testing. In this case, TestNG will look at all the classes in the package and will retain only classes that have TestNG annotations. In the Source tab, copy the below code and try.
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <!-- Naming our Suite as OurFirstSuite -->
    <suite name="OurFirstSuite" verbose="1">
    <!-- Naming our Test as Functional. We can have as many tests -->
    	<test name="Regression">
    		<!-- Execute based on packages -->
    		<packages>
    	<!-- Specify package names instead of class names -->
    			<package name="com.selenium.testcase">
                              </package>
    		</packages>
    	</test>
    </suite>
    
  9. After successful execution, below results are displayed. Notice that there is one more TestNG script named ‘SampleTest’ added to the package ‘com.selenium.testcase’. TestNG executed both files. If we closely analyze the test results, under ‘Regression’ test, there are two TestNG scripts – ‘FirstTestNScript’ and ‘SampleTest’. Inside ‘FirstTestNScript’, ‘secondTestCase’ (1 time) and ‘firstTestCase’ (2 times) are executed. And inside ‘SampleTest’, ‘firstTest’ (2 times) and ‘secondTest’ (2 times) are executed. This is due to Data Provider set up done in ‘FirstTestNScript’ and ‘SampleTest’ is different.

    TestNG Results
  10. Using TestNG we can also execute scripts belonging to set of groups. Groups are specified in ‘testng.xml’ file and need to be mentioned inside annotations. However one important point to remember is that groups must be mentioned for all annotations for which ‘@Test’ is dependant. For example in our ‘FirstTestNScript’ script, ‘firstTestCase’ is depending on ‘@BeforeTest’ for setting system properties, ‘@BeforeMethod’ for initializing WebDriver and ‘@AfterMethod’ for closing the WebDriver. Hence groups must be mentioned in all these methods. We will make below changes in our ‘FirstTestNScript’ script.
    	
    package com.selenium.testcase;
    
    import org.testng.annotations.Test;
    import org.testng.annotations.BeforeMethod;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.BeforeClass;
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.testng.Assert;
    import org.testng.annotations.AfterClass;
    import org.testng.annotations.BeforeTest;
    import org.testng.annotations.AfterTest;
    import org.testng.annotations.BeforeSuite;
    import org.testng.annotations.AfterSuite;
    
    public class FirstTestNScript {
    
    	WebDriver driver;
    
            @Test(dataProvider = "loginDP", priority=2, groups = "login")
    	public void firstTestCase(String username, String password) {
    		System.out.println("This is our First Test Method");
    		// Enter username as demo
    		driver.findElement(By.name("userName")).sendKeys(username);
    		// Enter password as demo
    		driver.findElement(By.name("password")).sendKeys(password);
    		// Click Sign in button
    		driver.findElement(By.name("login")).click();
    		// Assert if Sign Off is displayed after logging in
    		Assert.assertTrue(driver.findElement(By.linkText("SIGN-OFF")).isDisplayed());
    		System.out.println("First Test Case Passed");
    	}
    
    	// Assign WebDriver
    	@BeforeMethod(groups = "login")
    	public void beforeMethod() {
    		System.out.println("This is Before Method");
    		// Assign WebDriver to ChromeDriver
    		driver = new ChromeDriver();
    		// Maximize window
    		driver.manage().window().maximize();
    		// Navigate to New Tours URL
    		driver.get("http://newtours.demoaut.com");
    	}
    
    	@AfterMethod(groups = "login")
    	public void afterMethod() {
    		System.out.println("This is After Method");
    		// Close and quit the driver to close the Browser
    		driver.close();
    		driver.quit();
    	}
    
    	// Setting System Properties
    	@BeforeTest(groups = "login")
    	public void beforeTest() {
    		System.out.println("This is Before Test");
    		// Setting System properties
    		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
    	}
    }
    
    
  11. And we will update the ‘testng.xml’ file with below code.
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <!-- Naming our Suite as OurFirstSuite -->
    <suite name="OurFirstSuite" verbose="1">
    	<!-- Naming our Test as Functional. We can have as many tests -->
    	<test name="Functional">
    	<!-- Include required groups to execute -->
    		<groups>
    		<!-- group = login -->
    			<run>
    				<include name="login" />
    			</run>
    		</groups>
    		<!-- Include classes where groups are mentioned -->
    		<classes>
    			<class name="com.selenium.testcase.FirstTestNScript" />
    		</classes>
    	</test>
    </suite>
    
  12. After successful execution, below results are displayed. Notice that only ‘firstTestCase’ is executed inside ‘FirstTestNScript’ script as it placed under ‘login’ groups.

    Groups example
  13. We have learned passing parameters using Data Provider. There is another way of passing parameters and that is through ‘testing.xml’. The parameter values can be set at both suite and test level in the testng XML file and based on scope, parameters can be used in the TestNG script. However any parameter value defined at the test level will override the value of a parameter, with same name, if defined at suite level. We will make following changes in our ‘firstTestCase’ code. Notice that we removed Data Provider from our ‘firstTestCase’ code and used ‘@Parameters’ instead. No other changes to ‘FirstTestNScript’ class.
    	@Test(groups = "login")
    	@Parameters({"username","password"})
    	public void firstTestCase(String username, String password) {
    		System.out.println("This is our First Test Method");
    		// Enter username as demo
    		driver.findElement(By.name("userName")).sendKeys(username);
    		// Enter password as demo
    		driver.findElement(By.name("password")).sendKeys(password);
    		// Click Sign in button
    		driver.findElement(By.name("login")).click();
    		// Assert if Sign Off is displayed after logging in
    		Assert.assertTrue(driver.findElement(By.linkText("SIGN-OFF")).isDisplayed());
    		System.out.println("First Test Case Passed");
    	}
    
  14. If we add parameters at suite level, then they are accessible to all the methods. In below example parameters ‘username’ and ‘password’ are accessible to all methods in TestNG script using ‘@Parameters’ annotation.
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <!-- Naming our Suite as OurFirstSuite -->
    <suite name="OurFirstSuite" verbose="1">
    <!-- Adding parameters at suite level -->
    	<parameter name="username" value="demo"></parameter>
    	<parameter name="password" value="demo"></parameter>
          
          <!-- Naming our Test as Functional. We can have as many tests -->
    	<test name="Functional">
    		<!-- Include required groups to execute -->
    		<groups>
    			<!-- group = login -->
    			<run>
    				<include name="login" />
    			</run>
    		</groups>
    		<!-- Include classes where groups are mentioned -->
    		<classes>
    			<class name="com.selenium.testcase.FirstTestNScript" />
    		</classes>
    	</test>
    </suite>
    
  15. If we add parameters at test level, then they are accessible to only those methods that are in scope of test. In below example parameters ‘username’ and ‘password’ are accessible to only those methods that are in scope of <test name=”Functional”>.
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <!-- Naming our Suite as OurFirstSuite -->
    <suite name="OurFirstSuite" verbose="1">
    	<!-- Naming our Test as Functional. We can have as many tests -->
    	<test name="Functional">
    		<!-- Adding parameters at test level -->
    		<parameter name="username" value="demo"></parameter>
    		<parameter name="password" value="demo"></parameter>
    		<!-- Include required groups to execute -->
    		<groups>
    			<!-- group = login -->
    			<run>
    				<include name="login" />
    			</run>
    		</groups>
    		<!-- Include classes where groups are mentioned -->
    		<classes>
    			<class name="com.selenium.testcase.FirstTestNScript" />
    		</classes>
    	</test>
    </suite>
    
  16. TestNG also allow us to send Optional parameters. If we don’t mention parameters in ‘testing.xml’ then there is way to pass parameters to test method using ‘@Optional‘ annotation. In below example, TestNG uses the parameters passed from ‘@Optional‘ for the ‘firstTestCase’. This happened because TestNG was unable to find parameters named ‘username’ and ‘password’ in the ‘testing.xml’ file.
    	@Test(groups = "login")
    	@Parameters({"username","password"})
    	public void firstTestCase(@Optional("demo") String username, @Optional("demo") String password) {
    		System.out.println("This is our First Test Method");
    		// Enter username as demo
    		driver.findElement(By.name("userName")).sendKeys(username);
    		// Enter password as demo
    		driver.findElement(By.name("password")).sendKeys(password);
    		// Click Sign in button
    		driver.findElement(By.name("login")).click();
    		// Assert if Sign Off is displayed after logging in
    		Assert.assertTrue(driver.findElement(By.linkText("SIGN-OFF")).isDisplayed());
    		System.out.println("First Test Case Passed");
    	}
    
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <!-- Naming our Suite as OurFirstSuite -->
    <suite name="OurFirstSuite" verbose="1">
    	<!-- Naming our Test as Functional. We can have as many tests -->
    	<test name="Functional">
    		<!-- Include required groups to execute -->
    		<groups>
    			<!-- group = login -->
    			<run>
                               <include name="login" />
    			</run>
    		</groups>
    		<!-- Include classes where groups are mentioned -->
    		<classes>
    			<class name="com.selenium.testcase.FirstTestNScript" />
    		</classes>
    	</test>
    </suite>
    
  17. There is one more way of passing parameters from ‘testing.xml’ and that is by using ‘org.testng.ITestContext’. In the ‘testing.xml’ file, we will pass parameters for url and ‘Chrome Driver location.
    <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
    <!-- Naming our Suite as OurFirstSuite -->
    <suite name="OurFirstSuite" verbose="1">
    <!-- Passing url and chrome driver location -->
    <parameter name="url" value="http://newtours.demoaut.com"></parameter>
    <parameter name="chromeDriver" value="/Chrome/chromedriver"></parameter>
    	<!-- Naming our Test as Functional. We can have as many tests -->
    	<test name="Functional">
    		<!-- Adding parameters at test level -->
    		<parameter name="username" value="demo"></parameter>
    		<parameter name="password" value="demo"></parameter>
    		<!-- Include required groups to execute -->
    		<groups>
    			<!-- group = login -->
    			<run>
    				<include name="login" />
    			</run>
    		</groups>
    		<!-- Include classes where groups are mentioned -->
    		<classes>
    			<class name="com.selenium.testcase.FirstTestNScript" />
    		</classes>
    	</test>
    </suite>
    
  18. We will make below changes to our ‘FirstTestNScript’ script. We will use ITestContext to pass data from ‘testng.xml’ file to our script. Notice that we removed unwanted methods from our ‘FirstTestNScript’ script.
    package com.selenium.testcase;
    
    import org.testng.annotations.Test;
    import org.testng.annotations.BeforeMethod;
    import org.testng.annotations.BeforeTest;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.Parameters;
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    import org.testng.Assert;
    import org.testng.ITestContext;
    
    
    public class FirstTestNScript {
    
    	WebDriver driver;
    	String url;
    	String chromeDriverLocation;
    
    	@Test(groups = "login")
    	@Parameters({"username","password"})
    	public void firstTestCase(String username, String password) {
    		System.out.println("This is our First Test Method");
    		// Enter username as demo
    		driver.findElement(By.name("userName")).sendKeys(username);
    		// Enter password as demo
    		driver.findElement(By.name("password")).sendKeys(password);
    		// Click Sign in button
    		driver.findElement(By.name("login")).click();
    		// Assert if Sign Off is displayed after logging in
    		Assert.assertTrue(driver.findElement(By.linkText("SIGN-OFF")).isDisplayed());
    		System.out.println("First Test Case Passed");
    	}
    
    	@Test(dataProvider = "registerDP", priority=1)
    	public void secondTestCase(String fname, String lname, String username, String pwd, String cpwd) {
    		System.out.println("This is Second Test Method");
    		// Click Register link in Home page
    		driver.findElement(By.linkText("REGISTER")).click();
    		// Enter first name as fname
    		driver.findElement(By.name("firstName")).sendKeys(fname);
    		// Enter last name as lname
    		driver.findElement(By.name("lastName")).sendKeys(lname);
    		// Enter username as user
    		driver.findElement(By.name("email")).sendKeys(username);
    		// Enter password as pwd
    		driver.findElement(By.name("password")).sendKeys(pwd);
    		// Enter confirm password as cpwd
    		driver.findElement(By.name("confirmPassword")).sendKeys(cpwd);
    		// Click Submit to complete registration process
    		driver.findElement(By.name("register")).click();
    		System.out.println("Second Test Case Passed");
    	}
    
    	// Assign WebDriver
    	@BeforeMethod(groups = "login")
    	public void beforeMethod(ITestContext context) {
    		System.out.println("This is Before Method");
    		url = context.getCurrentXmlTest().getParameter("url");
    		// Assign WebDriver to ChromeDriver
    		driver = new ChromeDriver();
    		// Maximize window
    		driver.manage().window().maximize();
    		// Navigate to New Tours URL
    		driver.get(url);
    	}
    
    	@AfterMethod(groups = "login")
    	public void afterMethod() {
    		System.out.println("This is After Method");
    		// Close and quit the driver to close the Browser
    		driver.close();
    		driver.quit();
    	}
    
    	// DataProvider method for login data
    	@DataProvider
    	public Object[][] loginDP() {
    		// username = demo, passowrd = demo
    		return new Object[][] { { "demo", "demo" }, { "demo", "demo" } };
    	}
    
    	// DataProvider method for registering
    	@DataProvider
    	public Object[][] registerDP() {
    		// first name = fname, last name = lname, username = user, password =
    		// pwd, confirm password = pwd
    		return new Object[][] { { "fname", "lname", "user", "pwd", "pwd" } };
    	}
    
    
    	// Setting System Properties
    	@BeforeTest(groups = "login")
    	public void beforeTest(ITestContext context) {
    		System.out.println("This is Before Test");
    		chromeDriverLocation = context.getCurrentXmlTest().getParameter("chromeDriver");
    		// Setting System properties
    		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + chromeDriverLocation);
    	}
    }
    
  19. In the above code, we are using ITestContext object to pass parameters from ‘testng.xml’ file to our script. We declared two String variables ‘url’ and ‘chromeDriverLocation’ and later assigning the values in ‘@BeforeTest’ and ‘@BeforeMethod’ methods. After successful execution, below results are displayed. Notice that all parameters are passed correctly from ‘testng.xml’ file and only ‘firstTestCase’ is executed.

    TestNG Result

With this we came to an end of TestNG tutorial. The topic is huge and it requires a constant practice to master it. Hope you were able to follow most of it.

Happy Learning!

 

Part 2: Scripting using TestNG…

Now that we learned the workflow in a TestNG script, let’s update our ‘FirstTestNScript’ script to include two test cases that we mentioned in our last post.

First Test Case:Navigate to New Tours application and perform successful login.
Second Test Case:Navigate to New Tours application and perform successful registration. Click ‘Register’ link to go Register Page. Enter first name, last name, username, password, confirm password and click Submit to complete registration process.

This is how our test website looks like:

Test website
    1. First step is to create some test data for our first test case. It will require an username and password as test data. To accommodate this, we will update the ‘@DataProvider‘ method with a new name and we will also use two sets of test data (even though it’s same data). This will allow our test to execute twice with different set of data. Below is the code for the ‘@DataProvider‘ method.
      	//DataProvider method for login data
      	@DataProvider
      	public Object[][] loginDP() {
      		//username = demo, passowrd = demo
      		return new Object[][] {{"demo", "demo"}, {"demo", "demo"}};
      	}
      
    2. Every selenium script starts with a driver initialization. We will initialize the WebDriver inside our class but outside of all methods.

      Initialize WebDriver inside the Class
    3. Next step is to set the system properties. System properties setting needs to be done before assigning the WebDriver to a browser driver. In our test, we will set the system properties inside ‘@BeforeTest‘ method as we are not changing the browser type. However you can set the property inside ‘@Test‘ method also. Below is the code for ‘@BeforeTest‘.
      	//Setting System Properties
      	@BeforeTest
      	public void beforeTest() {
      		System.out.println("This is Before Test");
      		// Setting System properties
      		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
      	}
      
    4. After setting the property, next step is to assign the WebDriver to a browser driver. Since we are planning to use the same browser type for both of our tests, we will do the browser assigning part in ‘@BeforeMethod‘. Note that ‘@Before/AfterMethod‘ always execute before and after of every ‘@Test‘ method. Below is the code for ‘@BeforeMethod‘.
      	//Assign WebDriver
      	@BeforeMethod
      	public void beforeMethod() {
      		System.out.println("This is Before Method");
      		// Assign WebDriver to ChromeDriver
      		driver = new ChromeDriver();
      		//Maximize window
      		driver.manage().window().maximize();
      		//Navigate to New Tours URL
      		driver.get("http://newtours.demoaut.com");
      	}
      
    5. Let’s create our first test case. We will use ‘@Test‘ method for our logic. For better understanding, we will update our ‘@Test‘ method with a new name and we will also use the new Data Provider. The ‘@Test‘ method will have new set of parameters. By default TestNG provides Integer type and String type parameters. Since our Data Provider has only String types, we will update the same in ‘@Test‘ method also.
      	@Test(dataProvider = "loginDP")
      	public void firstTestCase(String username, String password) {
      		System.out.println("This is our First Test Method");
      		//Enter username as demo
      		driver.findElement(By.name("userName")).sendKeys(username);
      
      		//Enter password as demo
      		driver.findElement(By.name("password")).sendKeys(password);
      
      		//Click Sign in button
      		driver.findElement(By.name("login")).click();
      
      		//Assert if Sign Off is displayed after logging in
      		Assert.assertTrue(driver.findElement(By.linkText("SIGN-OFF")).isDisplayed());
                      System.out.println("First Test Case Passed");	  
      	}
      
    6. Once the test is completed, next step is to close the driver. As mentioned, ‘@AfterMethod‘ comes after the ‘@Test‘ method, so the best way is to update the ‘@AfterMethod‘ with the closing codes. Below is the code for ‘@AfterMethod‘ method.
      	@AfterMethod
      	public void afterMethod() {
      		System.out.println("This is After Method");
      		// Close and quit the driver to close the Browser
      		  driver.close();
      		  driver.quit();
      	}
      
    7. Now that we completed our first test case, let’s execute the script by doing a right click on ‘FirstTestNScript’ and click Run As -> 2.TestNG Test.
    8. As we complete our execution we will see following messages in the console.

      First Test Case Passed
    9. Now that our first test case is completed, let’s focus on our second test case. Second Test Case requires a different data set up. For that we will create a new Data Provider. Below is the code for a new ‘@DataProvider‘ method. We will name it as ‘registerDP’. Notice that we used only one set of data. This means the second test case will execute only once.
      	//DataProvider method for registering
      	  @DataProvider
      	  public Object[][] registerDP() {
      		/*first name = fname, last name = lname, username = user, password = pwd, confirm password = pwd */
      	    return new Object[][] {{ "fname","lname","user","pwd","pwd"}};
      	  }
      
    10. To create a new test case, we will have to create a new ‘@Test‘ method. As mentioned, the logic of a test case is always written in ‘@Test‘ method. Below is the code for a new ‘@Test‘ method. Notice that it has a name “secondTestCase” and it uses a new Data Provider “registerDP”. The parameters are also updated.
      	@Test(dataProvider = "registerDP")
      	public void secondTestCase(String fname, String lname, String username, String pwd, String cpwd){
      		  System.out.println("This is Second Test Method");
      		  //Click Register link in Home page
      		  driver.findElement(By.linkText("REGISTER")).click();
      		  //Enter first name as fname
      		  driver.findElement(By.name("firstName")).sendKeys(fname);
      		  //Enter last name as lname
      		  driver.findElement(By.name("lastName")).sendKeys(lname);
      		  //Enter username as user
      		  driver.findElement(By.name("email")).sendKeys(username);
      		  //Enter password as pwd
      		  driver.findElement(By.name("password")).sendKeys(pwd);
      		  //Enter confirm password as cpwd
      		  driver.findElement(By.name("confirmPassword")).sendKeys(cpwd);
      		  //Click Submit to complete registration process
      		  driver.findElement(By.name("register")).click();
      		  System.out.println("Second Test Case Passed");	
      	}
      
    11. Now that our second ‘@Test‘ method is also complete, we can run our ‘FirstTestNScript’ script to execute both of our test cases. Notice that for second test case we don’t have to update ‘@Before/AfterMethod‘ as the same methods will be executed for both ‘@Test‘ methods. Below is the test results after execution is complete. Notice that first test case runs twice and second test case runs only once. This is because the Data Providers are defined in that manner.

      Test Results
    12. If we refresh the ‘test-output‘ folder, there will be many HTML reports. We can view them by doing a right click and Open With -> Web Browser.
    13. Complete code for this project is provided below. Unused annotations can be removed.
      package com.selenium.testcase;
      
      import org.testng.annotations.Test;
      import org.testng.annotations.BeforeMethod;
      import org.testng.annotations.AfterMethod;
      import org.testng.annotations.DataProvider;
      import org.testng.annotations.BeforeClass;
      import org.openqa.selenium.By;
      import org.openqa.selenium.WebDriver;
      import org.openqa.selenium.chrome.ChromeDriver;
      import org.testng.Assert;
      import org.testng.annotations.AfterClass;
      import org.testng.annotations.BeforeTest;
      import org.testng.annotations.AfterTest;
      import org.testng.annotations.BeforeSuite;
      import org.testng.annotations.AfterSuite;
      
      public class FirstTestNScript {
      
      	WebDriver driver;
      
      	@Test(dataProvider = "loginDP")
      	public void firstTestCase(String username, String password) {
      		System.out.println("This is our First Test Method");
      		// Enter username as demo
      		driver.findElement(By.name("userName")).sendKeys(username);
      		// Enter password as demo
      		driver.findElement(By.name("password")).sendKeys(password);
      		// Click Sign in button
      		driver.findElement(By.name("login")).click();
      		// Assert if Sign Off is displayed after logging in
      		Assert.assertTrue(driver.findElement(By.linkText("SIGN-OFF")).isDisplayed());
      		System.out.println("First Test Case Passed");
      	}
      
      	@Test(dataProvider = "registerDP")
      	public void secondTestCase(String fname, String lname, String username, String pwd, String cpwd) {
      		System.out.println("This is Second Test Method");
      		// Click Register link in Home page
      		driver.findElement(By.linkText("REGISTER")).click();
      		// Enter first name as fname
      		driver.findElement(By.name("firstName")).sendKeys(fname);
      		// Enter last name as lname
      		driver.findElement(By.name("lastName")).sendKeys(lname);
      		// Enter username as user
      		driver.findElement(By.name("email")).sendKeys(username);
      		// Enter password as pwd
      		driver.findElement(By.name("password")).sendKeys(pwd);
      		// Enter confirm password as cpwd
      		driver.findElement(By.name("confirmPassword")).sendKeys(cpwd);
      		// Click Submit to complete registration process
      		driver.findElement(By.name("register")).click();
      		System.out.println("Second Test Case Passed");
      	}
      
      	// Assign WebDriver
      	@BeforeMethod
      	public void beforeMethod() {
      		System.out.println("This is Before Method");
      		// Assign WebDriver to ChromeDriver
      		driver = new ChromeDriver();
      		// Maximize window
      		driver.manage().window().maximize();
      		// Navigate to New Tours URL
      		driver.get("http://newtours.demoaut.com");
      	}
      
      	@AfterMethod
      	public void afterMethod() {
      		System.out.println("This is After Method");
      		// Close and quit the driver to close the Browser
      		driver.close();
      		driver.quit();
      	}
      
      	// DataProvider method for login data
      	@DataProvider
      	public Object[][] loginDP() {
      		// username = demo, passowrd = demo
      		return new Object[][] { { "demo", "demo" }, { "demo", "demo" } };
      	}
      
      	// DataProvider method for registering
      	@DataProvider
      	public Object[][] registerDP() {
      		// first name = fname, last name = lname, username = user, password =
      		// pwd, confirm password = pwd
      		return new Object[][] { { "fname", "lname", "user", "pwd", "pwd" } };
      	}
      
      	@BeforeClass
      	public void beforeClass() {
      		System.out.println("This is Before Class");
      	}
      
      	@AfterClass
      	public void afterClass() {
      		System.out.println("This is After Class");
      	}
      
      	// Setting System Properties
      	@BeforeTest
      	public void beforeTest() {
      		System.out.println("This is Before Test");
      		// Setting System properties
      		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
      	}
      
      	@AfterTest
      	public void afterTest() {
      		System.out.println("This is After Test");
      	}
      
      	@BeforeSuite
      	public void beforeSuite() {
      		System.out.println("This is Before Suite");
      	}
      
      	@AfterSuite
      	public void afterSuite() {
      		System.out.println("This is After Suite");
      	}
      }
      

With this we came to end on TestNG scripting. In the next post we will focus on configuring our test case with the help of testing.xml. Please share your comments. Happy Learning!

Additional Information:

There are many ways to configure the ‘@Test‘ method. Like for instance we can decide on which order the tests need to be executed, similarly we can group our tests in the way we want. For example in our above ‘FirstTestNScript’ script, if we want to execute the second test case first and then execute the first test case, we can easily configure this by setting the priority inside ‘@Test‘ annotation. Below will be code if we are setting the priority.

	@Test(dataProvider = "loginDP", priority=2)
	public void firstTestCase(String username, String password) {
		System.out.println("This is our First Test Method");
		// Enter username as demo
		driver.findElement(By.name("userName")).sendKeys(username);
		// Enter password as demo
		driver.findElement(By.name("password")).sendKeys(password);
		// Click Sign in button
		driver.findElement(By.name("login")).click();
		// Assert if Sign Off is displayed after logging in
		Assert.assertTrue(driver.findElement(By.linkText("SIGN-OFF")).isDisplayed());
		System.out.println("First Test Case Passed");
	}

	@Test(dataProvider = "registerDP", priority=1)
	public void secondTestCase(String fname, String lname, String username, String pwd, String cpwd) {
		System.out.println("This is Second Test Method");
		// Click Register link in Home page
		driver.findElement(By.linkText("REGISTER")).click();
		// Enter first name as fname
		driver.findElement(By.name("firstName")).sendKeys(fname);
		// Enter last name as lname
		driver.findElement(By.name("lastName")).sendKeys(lname);
		// Enter username as user
		driver.findElement(By.name("email")).sendKeys(username);
		// Enter password as pwd
		driver.findElement(By.name("password")).sendKeys(pwd);
		// Enter confirm password as cpwd
		driver.findElement(By.name("confirmPassword")).sendKeys(cpwd);
		// Click Submit to complete registration process
		driver.findElement(By.name("register")).click();
		System.out.println("Second Test Case Passed");
	}

Similarly there are many other ways by which we can configure the ‘@Test‘ method. Visit the below link for more information:

TestNG Annotations

 

Part 1: Scripting using TestNG…

Writing a TestNG script is a three-step process:

  • Write the business logic of our test and insert TestNG annotations in the code.
  • Add the information about our test (e.g. the class name, the groups you wish to run, etc…) in a testng.xml or build.xml file. In this post we will configure using testing.xml file.
  • Run TestNG using Command Line, Ant, IDEs. We will be executing our test using Eclipse IDE.

Let’s start with our first TestNG script. We will use the same Java Project ‘FrameworkDesign’ that we created in Keyword Driven Framework Development…

  1. First step is to add the TestNG libraries into our project. Right click on the project ‘FrameworkDesign’ and click Properties -> Java Build Path -> Add Library.

    Add TestNG Library
  2. In Add Library window, select TestNG and click on Next. Click on Finish to add the TestNG Library. Click OK to close the Properties window.

    Select TestNG
  3. Once the above steps are completed, we will see TestNG Library added to our Java Project.

    TestNG Library added

Test Case:

Let’s create our first test case using TestNG. This time we will use a different test website ‘http://www.newtours.demoaut.com‘ for creating our test cases.

First Test Case:Navigate to New Tours application and perform successful login.
Second Test Case:Navigate to New Tours application and perform successful registration.

We will use all the annotations in our script just to give an understanding on the workflow of a TestNG script. We will also use Data Providers for test data input.

  1. Right click on ‘com.selenium.testcase’ package inside ‘FrameworkDesign’ Java project and select TestNG -> Create TestNG Class as shown below.

    Create TestNG Class
  2. Give Class Name as ‘FirstTestNScript’ and select all annotations. This is just for learning purpose as you can create a  TestNG script with out selecting any of the annotations selected below. However in that case, the TestNG script will have ‘@Test‘ annotation as default. Remember that TestNG class don’t have static main methods so method with annotations will start the execution.

    New TestNG class
  3. As we click Finish, a new class ‘FirstTestNScript’ is created with all the annotations and corresponding methods. To begin with we will just add ‘System.out.println’ code in all methods to print different messages in the console. This will give an idea on the workflow of the TestNG class.
    package com.selenium.testcase;
    
    import org.testng.annotations.Test;
    import org.testng.annotations.BeforeMethod;
    import org.testng.annotations.AfterMethod;
    import org.testng.annotations.DataProvider;
    import org.testng.annotations.BeforeClass;
    import org.testng.annotations.AfterClass;
    import org.testng.annotations.BeforeTest;
    import org.testng.annotations.AfterTest;
    import org.testng.annotations.BeforeSuite;
    import org.testng.annotations.AfterSuite;
    
    public class FirstTestNScript {
    	@Test(dataProvider = "dp")
    	public void f(Integer n, String s) {
    		System.out.println("This is Test Method");
    	}
    
    	@BeforeMethod
    	public void beforeMethod() {
    		System.out.println("This is Before Method");
    	}
    
    	@AfterMethod
    	public void afterMethod() {
    		System.out.println("This is After Method");
    	}
    
    	@DataProvider
    	public Object[][] dp() {
    		return new Object[][] { new Object[] { 1, "a" }, new Object[] { 2, "b" }, };
    	}
    
    	@BeforeClass
    	public void beforeClass() {
    		System.out.println("This is Before Class");
    	}
    
    	@AfterClass
    	public void afterClass() {
    		System.out.println("This is After Class");
    	}
    
    	@BeforeTest
    	public void beforeTest() {
    		System.out.println("This is Before Test");
    	}
    
    	@AfterTest
    	public void afterTest() {
    		System.out.println("This is After Test");
    	}
    
    	@BeforeSuite
    	public void beforeSuite() {
    		System.out.println("This is Before Suite");
    	}
    
    	@AfterSuite
    	public void afterSuite() {
    		System.out.println("This is After Suite");
    	}
    }
    
  4. To execute our TestNG script, right click on ‘FirstTestNScript’ and click TestNG Test. The execution will be completed in no time and it will print some messages in the console. If we look closely on the console output, we can see that the messages are printed in the console in a particular order.

    Test Output
  5. From the test output we can easily conclude that ‘@Before/AfterSuite’ annotations are the very first and the last ones to be executed. Then ‘@Before/AfterTest’, followed by ‘@Before/AfterClass’. The ‘@Before/AfterMethod’ has executed twice alone with ‘@Test’ method. This is because the data provider ‘dp‘ has two sets of test data ({{ 1, “a” }, {2, “b”}}) and ‘@Test’ method executes twice using different set of test data from the data provider. Since ‘@Test’ is executed twice, so do ‘@Before/AfterMethod’ for each ‘@Test’ method.
  6. If we refresh our Java Project, a new folder named ‘test-output‘ is created with many HTML reports. To view an HTML file, right click on the file and click Open With -> Web Browser. This reports are generated by TestNG once the execution is completed.

    View TestNG reports

In our next post we will continue to update this ‘FirstTestNScript’ class with the logic of two test cases mentioned above, followed by test execution and analysis of TestNG reports. We will also learn to configure our TestNG script using testing.xml file. Till then, Happy Learning!

Additional Information:

What is Data Provided in TestNG?

@DataProvider‘ marks a method as supplying data for a test method. The annotated method must return an Object[][] (2D Array) where each Object[] can be assigned the parameter list of the test method. In the above example we are passing two sets of data. This means the ‘@Test‘ method (which has the test case logic) will be executed twice, each with one set of test data. As we move on to next post, the use of Data Provider will be more understandable.

 

Introduction to TestNG…

Till now we have created our executable test scripts inside main methods in Java classes. However this approach has many limitations as they always require human intervention with the execution. To overcome these shortcomings, there are tools like TestNG, JUnit that not only help us with organizing and grouping of our tests but also help us with reporting of test results. In simple words, both these tools has many advantages that will make our tests more easier to develop and maintain. In this post we will be focusing on TestNG as it is an extension of JUnit.

TestNG Introduction:

TestNG is an open source automated testing tool available in market. It is similar to JUnit in design but it has more advantages over JUnit. One of the main reason of using TestNG or JUnit is that we don’t have to use static main methods in our test scripts. Some other advantages are:

  1. Selenium WebDriver has no support for test reports. However with TestNG/JUnit we can generate reports in readable formats.
  2.  TestNG/JUnit has different annotation like ‘@BeforeClass’, ‘@AfterClass’, ‘@Test’ etc. These annotations help us in configuring our test cases. Also we can pass additional parameters using annotations.
  3.  Any uncaught exceptions can be handled by TestNG/JUnit without terminating the test prematurely. These exceptions are reported as failed steps in the report.

In TestNG, we can use the following annotations for configuring our test case:

@BeforeSuite: The annotated method will be run before all tests in this suite have run.
@AfterSuite: The annotated method will be run after all tests in this suite have run.
@BeforeTest: The annotated method will be run before any test method belonging to the classes inside the <test> tag is run.
@AfterTest: The annotated method will be run after all the test methods belonging to the classes inside the <test> tag have run.
@BeforeGroups: The list of groups that this configuration method will run before. This method is guaranteed to run shortly before the first test method that belongs to any of these groups is invoked.
@AfterGroups: The list of groups that this configuration method will run after. This method is guaranteed to run shortly after the last test method that belongs to any of these groups is invoked.
@BeforeClass: The annotated method will be run before the first test method in the current class is invoked.
@AfterClass: The annotated method will be run after all the test methods in the current class have been run.
@BeforeMethod: The annotated method will be run before each test method.
@AfterMethod: The annotated method will be run after each test method.
@DataProvider: Marks a method as supplying data for a test method. The annotated method must return an Object[][] where each Object[] can be assigned the parameter list of the test method. The @Test method that wants to receive data from this DataProvider needs to use a dataProvider name equals to the name of this annotation.
@Test: Marks a class or a method as part of the test.

The complete list of annotations and their descriptions are mentioned in below link:
TestNG Annotations

TestNG Installation:

Now that we had some introduction on the TestNG, lets install in our Eclipse.

  1. Launch the Eclipse IDE and click Help -> Install New Software option.
  2. Click on Add button to Add Repository.

    Click Add to Add Repository
  3. Enter following details: Name: ‘TestNG’, Location: ‘http://beust.com/eclipse’. Click OK to close the window.

    Add a Repository
  4. Select ‘TestNG’ option and click Next.

    Select TestNG
  5. Click on Next and accept the license agreement and hit Finish to complete the installation process. Once the installation is complete, we need to restart the Eclipse.
  6. To verify if the installation was successful or not, right click on a project to view the below options.

    TestNG options

With this we completed the TestNG installation in Eclipse. In our next post we will focus on creation and execution of TestNG tests and then view the test results in TestNG reports.

 

Keyword Driven Framework Development…

Keyword Driven is more or less similar to Data Driven approach however in this case we will create test steps in an Excel sheet. Our test case will use the test steps and keywords like ‘openBrowser’, ‘navigate’ etc., from the Excel sheet and perform set of actions in a web page. A separate class file will have the definitions for these keywords. Now let’s create a sample framework based on what we discussed. We will use the same project from our last post but will rename the project to ‘FrameworkDesign’. You can easily a rename a project in Eclipse by clicking ‘F2’ to edit.

  1. The first step is to create our test steps in an Excel sheet and name it as ‘KeywordAction.xlsx’. We will place this Excel sheet inside the ‘com.selenium.data’ package. We can either do it by drag or drop or else you can go to the project location and directly paste it in ‘src/com/selenium/actions’ folder. ‘com.selenium.data’ package was already created in our last post.

    Test Steps
  2. Right click on the ‘src‘ folder and click New -> Package. Name the package as ‘com.selenium.actions’ and click Finish to close the window.
  3. Right click on ‘com.selenium.actions’ package and click New -> Class. Name the class as ‘Actions’. This class will contain definition of all keywords (Actions column) that we used in ‘KeywordAction.xlsx’ Excel. Copy the below code and paste into ‘Actions’ class. All the keywords (for example openBrowser, navigateURL, enterText etc) used in our Excel sheet are static methods in ‘Actions’ class.
    
    package com.selenium.actions;
    
    import org.openqa.selenium.By;
    import org.openqa.selenium.WebDriver;
    import org.openqa.selenium.chrome.ChromeDriver;
    
    public class Actions {
    
    	public static WebDriver driver;
    
    	// Open Browser
    	public static void openBrowser() {
    		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
    		driver = new ChromeDriver();
    
    	}
    
    	// Navigate to URL
    	public static void navigateURL() {
    		driver.get("http://www.google.com");
    		driver.manage().window().maximize();
    
    	}
    
    	// Enter text in text box
    	public static void enterText(String inputText) {
    		// Relative XPath for the text box
    		driver.findElement(By.xpath("//input[@id='lst-ib']")).sendKeys(inputText);
    
    	}
    
    	// Click Search icon
    	public static void clickButton() {
    		/* Find the Web Element Search icon. After finding, click the search icon */
    		driver.findElement(By.id("_fZl")).click();
    
    	}
    
    	// Click URL
    	public static void clickLink(String linkText) throws InterruptedException {
    		// Wait for page to load
    		Thread.sleep(5000);
    		// Enter text from test data Excel row 1 column 4
    		driver.findElement(By.partialLinkText(linkText)).click();
    	}
    
    	// Title verification
    	public static boolean verifyTitle(String titleText) throws InterruptedException {
    		// Wait for page to load
    		Thread.sleep(5000);
    		String title = driver.getTitle();
    		// Title verification. The third link is Wikipedia link
    		if (title.equals(titleText)) {
    			System.out.println("TITLE VERIFIED.");
    			System.out.println("TEST CASE PASSED");
    			return true;
    		} else {
    			System.out.println("TITLE NOT VERIFIED.");
    			return false;
    		}
    	}
    
    	// Close the browser
    	public static void closeBrowser() {
    		// Close and quit the driver to close the Browser
    		driver.close();
    	      driver.quit();
    	}
    }
    
  4. Next step is to create the test case. We will use the test steps from ‘KeywordAction.xlsx’ Excel. Right click on ‘com.selenium.testcase’ package, that we created in our last project and click New -> Class to add a new class file. Name the class file as ‘TC_KeySeleniumSearch’. Copy the below code into ‘TC_KeySeleniumSearch’.
    package com.selenium.testcase;
    
    import com.selenium.actions.Actions;
    import com.selenium.utils.Constants;
    import com.selenium.utils.ExcelUtil;
    
    public class TC_KeySeleniumSearch {
    
    	public static void main(String[] args) throws Exception {
    		// Set the path to KeywordAction.xlsx Excel sheet
    		String sPath = System.getProperty("user.dir") + "/src/com/selenium/data/KeywordAction.xlsx";
    
    		/* Set the Excel path with the sheet name mentioned in Constants class */
    		ExcelUtil.setExcelFile(sPath, Constants.SHEETNAME);
    
    		/*
    		 * We are looping all the 8 rows in Excel starting from row 1. Remember row 0 is the header in Excel sheet
    		 */
    		for (int rowNumber = 1; rowNumber < 8; rowNumber++) {
    
    			// This is to take the keywords from 3rd column
    			String sActionKeyword = ExcelUtil.getCellData(rowNumber, 3);
    
    			if (sActionKeyword.equals("openBrowser")) {
    				/* This will execute if Excel cell has 'openBrowser' */
    				Actions.openBrowser();
    			} else if (sActionKeyword.equals("navigateURL")) {
    				/* This will execute if Excel cell has 'navigateURL' */
    				Actions.navigateURL("http://www.google.com");
    			} else if (sActionKeyword.equals("enterText")) {
    				/* This will execute if Excel cell has 'enterText' */
    				Actions.enterText("Selenium");
    			} else if (sActionKeyword.equals("clickButton")) {
    				/* This will execute if Excel cell has 'clickButton' */
    				Actions.clickButton();
    			} else if (sActionKeyword.equals("clickLink")) {
    				/* This will execute if Excel cell has 'clickLink' */
    				Actions.clickLink("Wikipedia");
    			} else if (sActionKeyword.equals("verifyTitle")) {
    				/* This will execute if Excel cell has 'verifyTitle' */
    				Actions.verifyTitle("Selenium (software) - Wikipedia");
    			} else if (sActionKeyword.equals("closeBrowser")) {
    				/* This will execute if Excel cell has 'closeBrowser' */
    				Actions.closeBrowser();
    			}
    		}
    	}
    }
    
  5. The idea behind the Keyword Driven Framework is to have the test steps created in Excel. Each test step will have keywords to perform an action on our test website. This way the test cases becomes easily readable and maintainable.
  6. Below screenshot shows the folder structure of our project.

    Folder Structure
  7. The next step is to execute our test case. Right click on ‘TC_KeySeleniumSearch’ and click Run As -> Java Application. Once the execution is successfuly completed, we can see below output in the console.

    Console output

With this we came to end on the basics of Keyword Driven approach. This framework is similar to Data Driven one as both uses Apache POI jars. Please post your comments and let me know your thoughts…

Happy Learning!

 

Data Driven Framework Development…

In my last post I gave an introduction on Data Driven Framework. In this post we will mainly concentrate on developing a sample Data Driven Framework.

    1. Launch Eclipse. Create a new Java Project by clicking on File -> New -> Java Project.
    2.  Give the project name as ‘DatadrivenFramework’ and click on Finish to close the window. Once the project is created, we will have ‘src‘ folder and ‘JRE System Library‘ by default.
    3. Next step is to add the Selenium Jars. Right Click on the project name and click Properties. Click on Java Build Path -> Add External Jars. Go to the local folder where ‘selenium-server-standalone-3.0.1’ is saved. We will be using 3.0.1 version from our last project. Add the Jar and click OK to close the window. Once complete, we will see ‘Referenced Libraries‘ included in our project.
    4. Next step is add the Chrome Driver. Right click on the project name and click New -> Folder. Give the folder name as ‘Chrome’ and click Finish to close the window. Now place the Chrome driver that we already downloaded in our last project into the ‘Chrome’ folder.
    5. To work with input files like Excel, CSVs etc we need to download Apache POI jars and add them into our project.
    6. Go to Google and type in ‘apache poi jar download’. Click on the first link ‘Apache POI – Download Release Artifacts’. URL: Apache POI
    7. Click on the latest stable version. As of today Apache POI 3.15 is the stable version. Go to Binary Distribution section and click on the zip file to start the download.

      Download the zip file into your local folder
    8. Unzip the downloaded file. Now you have to add all the Jars into your project. To import the Jars, right click on the project name and click Properties. Click on Java Build Path -> Add External Jars. Go to the local folder where ‘poi-3.15’ is saved. Add all Jars in the ‘poi-3.15’ folder. Make sure to add the Jars in ‘lib’ and ‘ooxml-lib’ folders too. Click OK to close the window. The ‘Referenced Libraries‘ in the project will be updated with the Apache POI Jars.
    9. The next step is to create the test data Excel Sheet. You can create one test data Excel sheet as shown below and save it as ‘TC_SearchSelenium.xlsx’. Apache POI works in MS Excel. It will not work for Mac Numbers. So even if you are working in Mac machine, make sure to have MS Excel installed.

      Test Data creation in Excel
    10. Right click on the project name and click New -> Folder. Give the folder name as ‘TestResults’ and click Finish to close the window. In this folder the test data Excel sheet with ‘PASS/FAIL’ result will be placed once the execution completes.
    11. Now we need to create a new package inside the ‘src‘ folder of our project. Right click on the ‘src‘ folder and click New -> Package. Name the package as ‘com.selenium.data’ and click Finish to close the window.
    12. We have to add the test data Excel into this package. You can do this by drag and drop or else you can copy the Excel and directly paste it into your Project/src/com/selenium/data folder.
    13. Right click on the  ‘src‘ folder and click New -> Package. Name the package as ‘com.selenium.utils’ and click Finish to close the window.
    14. On the ‘com.selenium.utils’ package, right click on New -> Class to create a new class file. Name the class as ‘Constants’. Click Finish to create a new class. Copy the below code and paste it into the ‘Constants’ class file.
      package com.selenium.utils;
      
      public class Constants {
      	// TestResults folder location
      	public static final String PATH_RESULTS = System.getProperty("user.dir") + "/TestResults/";
      
      	// Test Data Excel location
      	public static final String PATH_DATA = System.getProperty("user.dir")
      			+ "/src/com/selenium/data/TC_SearchSelenium.xlsx";
      
      	// Test Data Excel Sheet name
      	public static final String SHEETNAME = "Sheet1";
      
      	// Test Data Excel File Name
      	public static final String FILE_NAME = "TC_SearchSelenium.xlsx";
      
      }
      
    15. On the ‘com.selenium.utils’ package, right click on New -> Class to create a new class file. Name the class as ‘ExcelUtil’. Click Finish to create a new class.
    16. Copy the below code in to the ‘ExcelUtil’ class and save it.
      package com.selenium.utils;
      
      import java.io.FileInputStream;
      import java.io.FileOutputStream;
      import org.apache.poi.xssf.usermodel.XSSFCell;
      import org.apache.poi.xssf.usermodel.XSSFRow;
      import org.apache.poi.xssf.usermodel.XSSFSheet;
      import org.apache.poi.xssf.usermodel.XSSFWorkbook;
      
      public class ExcelUtil {
      
      	 private static XSSFSheet excelWSheet;
      	private static XSSFWorkbook excelWBook;
      	private static XSSFCell cell;
      	private static XSSFRow row;
      
      	/*
      	 * This method is to set the File path and to open the Excel file. Pass
      	 * Excel Path and Sheet name as Arguments to this method
      	 */
      
      	public static void setExcelFile(String Path, String SheetName) throws Exception {
      
      		try {
      
      			// Open the Excel file
      			FileInputStream ExcelFile = new FileInputStream(Path);
      
      			// Access the Excel workbook
      			excelWBook = new XSSFWorkbook(ExcelFile);
      		     // Access the Excel Sheet
      			excelWSheet = excelWBook.getSheet(SheetName);
      
      		} catch (Exception e) {
      			e.printStackTrace();
      		}
      	}
      
      	/*
      	 * This method is to read the test data from the Excel cell. We are passing
      	 * parameters as Row number and Column number
      	 */
      
      	public static String getCellData(int RowNum, int ColNum) throws Exception {
      
      		try {
                    // Access a particular cell in a sheet
      			cell = excelWSheet.getRow(RowNum).getCell(ColNum);
      
      			String CellData = cell.getStringCellValue();
      
      			return CellData;
      
      		} catch (Exception e) {
      
      			return null;
      		}
      	}
      
      	/*
      	 * This method is to write in the Excel cell. Passing Row number and Column
      	 * number are the parameters
      	 */
      
      	public static void setCellData(String Result, int RowNum, int ColNum) throws Exception {
      
      		try {
      			cell = excelWSheet.getRow(RowNum).getCell(ColNum);
      
      			if (cell == null) {
      
      				cell = row.createCell(ColNum);
      
      				cell.setCellValue(Result);
      			} else {
      
      				cell.setCellValue(Result);
      			}
      
      			// Update the Test Results folder with 'PASS/FAIL results'
      
      			FileOutputStream fileOut = new FileOutputStream(Constants.PATH_RESULTS + Constants.FILE_NAME);
      
      			excelWBook.write(fileOut);
      
      			fileOut.flush();
      
      			fileOut.close();
      
      		} catch (Exception e) {
      			e.printStackTrace();
      		}
      	}
      }
    17. Next we will create our actual test case. We will take the same code from our first test case, however we will do some modification on the input parameters.
    18. Right click on the ‘src‘ folder and click New -> Package. Name the package as ‘com.selenium.testcase’ and click Finish to close the window.
    19. On the ‘com.selenium.testcase’ package, right click on New -> Class to create a new class file. Name the class as ‘TC_SeleniumSearch’. Click Finish to create a new class. Copy the below code and paste it into the ‘TC_SeleniumSearch’ class file.
      package com.selenium.testcase;
      
      import org.openqa.selenium.By;
      import org.openqa.selenium.WebDriver;
      import org.openqa.selenium.chrome.ChromeDriver;
      import com.selenium.utils.Constants;
      import com.selenium.utils.ExcelUtil;
      
      public class TC_SeleniumSearch {
      
      	public static void main(String[] args) throws Exception {
      
      		// Setting System properties
      		System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/Chrome/chromedriver");
      		
      		// Initialize WebDriver
      		WebDriver driver = new ChromeDriver();
      		
      		//Set the File path of our test data Excel
      		ExcelUtil.setExcelFile(Constants.PATH_DATA, Constants.SHEETNAME);
      		
      		/* Navigate to Google Website. Test data taken from row 1 column 2 */
      		driver.get(ExcelUtil.getCellData(1, 2));
      		
      		// Maximize browser window
      		driver.manage().window().maximize();
      		
      		// Relative XPath for the text box
      		driver.findElement(By.xpath("//input[@id='lst-ib']")).sendKeys(ExcelUtil.getCellData(1, 3));
      		
      		/* Find the Web Element Search icon. After finding, click the search icon */
      		driver.findElement(By.id("_fZl")).click();
      		
      		/* Wait for page to load. Thread.sleep() throws InterruptedException */
      		Thread.sleep(5000);
      		
      		//Enter text from test data Excel row 1 column 4 
      		driver.findElement(By.partialLinkText(ExcelUtil.getCellData(1, 4))).click();
      		
      		/* Wait for page to load. Thread.sleep() throws InterruptedException */
      		Thread.sleep(3000);
      		
      		// Store the title in a variable
      		String title = driver.getTitle();
      		
      		/* Title verification. Get text from test data Excel row 1 column 5 */
      		if (title.equals(ExcelUtil.getCellData(1, 5))) {
      			System.out.println("TITLE VERIFIED.");
      			
      			/*Set the PASS result in test data Excel row 1 column 6 */
      			ExcelUtil.setCellData("PASS", 1, 6);
      			System.out.println("TEST CASE PASSED");
      
      		} else {
      			/*Set the FAIL result in test data Excel row 1 column 6 */
      			ExcelUtil.setCellData("FAIL", 1, 6);
      			
      			System.out.println("TITLE NOT VERIFIED.");
      		}
      		// Close and quit the driver to close the Browser
      		driver.close();
      	      driver.quit();
      	}
      }
      
    20. As you see in the above code, we have removed all the hard coded test data from our test case and instead gave a reference to the test data Excel sheet. Our test case became more maintainable with this approach and any further updates to test data is also simple.
    21. The overall project structure will look like:

      Project Structure
    22. The final step is to execute our test case. Right click on “TC_SeleniumSearch” class and click Run As -> Java Application. As we progress with our execution we can see the all the input data is taken from our test data Excel sheet. Test Case execution successfully completes and we see the following output in the console.

      Execution Passed
    23. As we check the ‘TestResult’ folder, a new Excel sheet is placed with the test result. Open the Excel sheet and we can see the test results are updated with ‘PASS results’.

      Test Results updated

With this we came to an end of our Data Driven Framework creation. You can modify this code to take the input of different browsers. For that you just need to update the “TC_SeleniumSearch” class file with ‘if-else’ logic.

Happy Learning and do share your comments!

Additional Information:

If you refer to ‘ExcelUtil’ class, we have used instances of XSSFWorkbook, XSSFSheet etc to access a particular cell in an Excel. Apache POI provides documentation on all of these classes. You can visit the below URL for more details:

Apache POI Docs