Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building a Data-Driven, Keyword-Driven, and Hybrid Selenium Framework

DZone's Guide to

Building a Data-Driven, Keyword-Driven, and Hybrid Selenium Framework

Selenium makes code maintenance simpler and code readability better. Learn about the three most commonly used Selenium frameworks.

· Big Data Zone ·
Free Resource

How to Simplify Apache Kafka. Get eBook.

In this article, I will tell you how to use a Selenium framework to optimize your code structure.

What Is a Selenium Framework?

The Selenium framework is a code structure for making code maintenance simpler and code readability better. A framework involves breaking the entire code into smaller pieces of code that test a particular functionality.

The code is structured such that the dataset is separated from the actual test case that will test the functionality of the web application. It can also be structured in a way in which the test cases that need to be executed are invoked from an external application (like a CSV).

There are a number of frameworks out there, but three commonly used Selenium frameworks are:

These frameworks will be discussed with a demo in this article. But before going any further, let me tell you why a Selenium framework needs to be in place and what benefits you will get out of using one.

Why Do We Need a Selenium Framework?

Without a framework in place, there will be one test case that will comprise the entire test functionality. The scary part is that this single test case can have up to a million lines of code. It's pretty obvious that a test case so huge would be tough to read. Even if you want to modify any functionality later, you will have a tough time modifying the code.

Since the implementation of a framework will result in smaller, multiple code pieces, there are various benefits.

  • Increased code reuse
  • Improved code readability
  • Higher portability
  • Reduced script maintenance

Now that you know the basics of frameworks, let me explain each of them in detail.

Data-Driven Framework

A data-driven framework in Selenium has the technique of separating the dataset from the actual test case (code). This framework completely depends on the input test data. The test data is fed from external sources such as an Excel file, CSV file, or any database.

Since the test case is separated from the dataset, we can easily modify the test case of a particular functionality without making wholesale changes to your code. For example, if you want to modify the code for login functionality, then you can modify just that instead of having to also modify any other dependent portion of the same code.

Besides this, you can also easily control how much data needs to be tested. You can easily increase the number of test parameters by adding more username and password fields to the Excel file (or other sources).

For example, if I have to check the login to a web page, then I can keep the set of username and password credentials in an Excel file and pass the credentials to the code to perform automation on the browser in a separate Java class file.

Using Apache POI With Selenium WebDriver

WebDriver does not directly support reading of Excel files. Hence, we use Apache POI for reading/writing to any Microsoft Office document. You can download Apache POI (a set of JAR files) from here. Download the ZIP file or TAR file as per your requirements and place them along with the set of Selenium JARs.

The coordination of the main code and dataset will be taken care of by TestNG Data Providers, which is a library that comes as a part of the Apache POI JAR files. For demo purposes, I have created an Excel file called LoginCredentials in which the usernames and passwords have been stored in different columns.

Take a look at the below code to understand the test case. It is a simple code for testing the login functionality of a flight-booking application.

package DataDriven;

import org.openqa.selenium.By;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class DDTExcel
{
 ChromeDriver driver;

 @Test(dataProvider="testdata")
 public void DemoProject(String username, String password) throws InterruptedException
 {
 System.setProperty("webdriver.chrome.driver", "C:\\Users\\Vardhan\\Downloads\\chromedriver.exe");
 driver = new ChromeDriver();

 driver.get("http://newtours.demoaut.com/");

 driver.findElement(By.name("userName")).sendKeys(username);
 driver.findElement(By.name("password")).sendKeys(password);
 driver.findElement(By.name("login")).click();

 Thread.sleep(5000);

 Assert.assertTrue(driver.getTitle().matches("Find a Flight: Mercury Tours:"), "Invalid credentials");
 System.out.println("Login successful");
 }

 @AfterMethod
 void ProgramTermination()
 {
 driver.quit();
 }

@DataProvider(name="testdata")
 public Object[][] TestDataFeed()
 {

 ReadExcelFile config = new ReadExcelFile("C:\\Users\\Vardhan\\workspace\\Selenium\\LoginCredentials.xlsx");

 int rows = config.getRowCount(0);

 Object[][] credentials = new Object[rows][2];

for(int i=0;i<rows;i++)
 {
 credentials[i][0] = config.getData(0, i, 0);
 credentials[i][1] = config.getData(0, i, 1);
 }

 return credentials;
 }
}

As you can see above, we have a method named TestDataFeed(). In this method, I have created an object instance of another class named ReadExcelFile. While instantiating this object, I have fed the path of my Excel file containing the data. I have further defined a for loop to retrieve the text from the Excel workbook.

But to read the data from a given sheet number, column number, and row number, the calls are made to the ReadExcelFile class. The code of my ReadExcelFile is below.

package DataDriven;

import java.io.File;
import java.io.FileInputStream;

import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class ReadExcelFile
{
 XSSFWorkbook wb;
 XSSFSheet sheet;

 public ReadExcelFile(String excelPath)
 {
 try
 {
 File src = new File(excelPath);
 FileInputStream fis = new FileInputStream(src);
 wb = new XSSFWorkbook(fis);
 }

 catch(Exception e)
 {
 System.out.println(e.getMessage());
 }
 }

 public String getData(int sheetnumber, int row, int column)
 {
 sheet = wb.getSheetAt(sheetnumber);
 String data = sheet.getRow(row).getCell(column).getStringCellValue();
 return data;
 }

 public int getRowCount(int sheetIndex)
 {
 int row = wb.getSheetAt(sheetIndex).getLastRowNum();
 row = row + 1;
 return row;
 }
}

First, note the libraries I have imported. I have imported Apache POI XSSF libraries, which are used to read/write data to Excel files. Here, I have created a constructor (object of the same method) to pass the values: sheet number, row number, and column number. To understand this framework better, I request you to go through the below video, where I have explained this in a structured manner.

Now, let's move on.

Keyword-Driven Framework

In keyword-driven frameworks, all the operations and instructions to be performed are written separately from the actual test case. It is similar to a data-driven framework in that the operations to be performed are again stored in an external file like an Excel sheet.

The operations I'm talking about are nothing but the methods that need to be executed as part of a test case. The benefit of keyword-driven frameworks is that you can easily control the functionalities you want to test. You can specify the methods that test the functionality of the application in the Excel file so that only the method names specified in the file will be tested.

For example, for logging into a web application, we can write multiple methods in the main test case, in which each test case will test certain functionality. For instantiating the browser driver, there could be one method for finding the username and password fields, there another method for navigating to a web page, etc.

Take a look at the below code to understand how the framework looks. The lines that are commented out serve as an explanation if you don't understand.

package KeywordDriven;

import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

public class Actions 
{
 public static WebDriver driver;

 public static void openBrowser()
 { 
 System.setProperty("webdriver.chrome.driver", "C:\\Users\\Vardhan\\Downloads\\chromedriver.exe");
 driver=new ChromeDriver();
 }

 public static void navigate()
 { 
 driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
 driver.get("http://newtours.demoaut.com");
 }

 public static void input_Username()
 {
 driver.findElement(By.name("userName")).sendKeys("mercury"); 
 }

 public static void input_Password()
 {
 driver.findElement(By.name("password")).sendKeys("mercury");
 }

 public static void click_Login()
 {
 driver.findElement(By.name("login")).click();
 }

@Test
 public static void verify_login()
 {
 String pageTitle = driver.getTitle();
 Assert.assertEquals(pageTitle, "Find a Flight: Mercury Tours:");
 }

 public static void closeBrowser()
 {
 driver.quit();
 }
}

As you can see, the different functionalities that need to be tested are present in separate methods waiting to be called. Now, these methods will be called from another Class based on the presence of the method name in the Excel file. Similarly, to read the Excel file and send back the results, I have written another Class. Both of them are displayed below.

The Class file invoking the methods:

package KeywordDriven;

public class DriverScript
{
 public static void main(String[] args) throws Exception 
 {
 //Declaring the path of the Excel file with the name of the Excel file
 String sPath = "C:\\Users\\Vardhan\\workspace\\Selenium Frameworks Demo\\dataEngine.xlsx"; 

 //Here we are passing the Excel path and SheetName as arguments to connect with Excel file
 ReadExcelData.setExcelFile(sPath, "Sheet1");

 //Hard coded values are used for Excel row & columns for now     
 //Hard coded values are used for Excel row & columns for now    
 //In later chapters we will replace these hard coded values with varibales    //This is the loop for reading the values of the column 3 (Action Keyword) row by row
 for (int iRow=1;iRow<=7;iRow++)
 {
 String sActions = ReadExcelData.getCellData(iRow, 1); 

 //Comparing the value of Excel cell with all the keywords in the "Actions" class
 if(sActions.equals("openBrowser"))
 { 
 //This will execute if the excel cell value is 'openBrowser'    
 //Action Keyword is called here to perform action
 Actions.openBrowser();
 }
 else if(sActions.equals("navigate"))
 {
 Actions.navigate();
 }
 else if(sActions.equals("input_Username"))
 {
 Actions.input_Username();
 }
 else if(sActions.equals("input_Password"))
 {
 Actions.input_Password();
 }
 else if(sActions.equals("click_Login"))
 {
 Actions.click_Login();
 } 
 else if(sActions.equals("verify_Login"))
 {
 Actions.verify_login();
 } 
 else if(sActions.equals("closeBrowser"))
 {
 Actions.closeBrowser();
 } 
 }
 }
}

The Class file reading the Excel values:

package KeywordDriven;

import java.io.FileInputStream;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCell;

public class ReadExcelData
{
 private static XSSFSheet ExcelWSheet;
 private static XSSFWorkbook ExcelWBook;
 private static XSSFCell Cell; 

 //This method is to set the File path and to open the Excel file
 //Pass Excel Path and SheetName as Arguments to this method
 public static void setExcelFile(String Path,String SheetName) throws Exception 
 {
 FileInputStream ExcelFile = new FileInputStream(Path);
 ExcelWBook = new XSSFWorkbook(ExcelFile);
 ExcelWSheet = ExcelWBook.getSheet(SheetName);
 }

 //This method is to read the test data from the Excel cell
 //In this we are passing parameters/arguments as Row Num and Col Num
 public static String getCellData(int RowNum, int ColNum) throws Exception
 {
 Cell = ExcelWSheet.getRow(RowNum).getCell(ColNum);
 String CellData = Cell.getStringCellValue();
 return CellData;
 }
}

Now, let's move on to the final part of this Selenium framework article, where I will show you how to build a hybrid framework.

Hybrid Framework

With a hybrid framework, we can make the best use of both data-driven and keyword-driven Selenium frameworks. Using the examples shown above, we can build a hybrid framework by storing the methods to execute in an Excel file (keyword-driven approach) and passing these method names to the Java Reflection Class(data-driven approach) instead of creating an if/else loop in the DriverScript class.

Take a look at the modified DriverScript class in the below code snippet. Here, instead of using multiple if/else loops, a data-driven approach is used to read the method names from the Excel file.

package HybridFramework;

import java.lang.reflect.Method;

public class DriverScriptJava
{
 //This is a class object, declared as 'public static'
 //So that it can be used outside the scope of main[] method
 public static Actions actionKeywords;

 public static String sActions;

 //This is reflection class object, declared as 'public static' 
 //So that it can be used outside the scope of main[] method
 public static Method method[];

 public static void main(String[] args) throws Exception 
 {
 //Declaring the path of the Excel file with the name of the Excel file
 String sPath = "C:\\Users\\Vardhan\\workspace\\Selenium Frameworks Demo\\dataEngine.xlsx";

 //Here we are passing the Excel path and SheetName to connect with the Excel file     
 //This method was created previously
 ReadExcelData.setExcelFile(sPath, "Sheet1");

 //Hard coded values are used for Excel row & columns for now     
 //Later on, we will use these hard coded value much more efficiently    
 //This is the loop for reading the values of the column (Action Keyword) row by row 
 //It means this loop will execute all the steps mentioned for the test case in Test Steps sheet
 for (int iRow=1;iRow<=7;iRow++)
 {
 sActions = ReadExcelData.getCellData(iRow, 1);
 //A new separate method is created with the name 'execute_Actions'
 //You will find this method below of the this test 
 //So this statement is doing nothing but calling that piece of code to execute
 execute_Actions(); 
 }
 }

//This method contains the code to perform some action 
//As it is completely different set of logic, which revolves around the action only, it makes sense to keep it separate from the main driver script 
//This is to execute test step (Action)
private static void execute_Actions() throws Exception 
 {
 //Here we are instantiating a new object of class 'Actions'
 actionKeywords = new Actions();

 //This will load all the methods of the class 'Actions' in it. 
 //It will be like array of method, use the break point here and do the watch 
 method = actionKeywords.getClass().getMethods();

 //This is a loop which will run for the number of actions in the Action Keyword class 
 //method variable contain all the method and method.length returns the total number of methods
 for(int i = 0;i<method.length;i++)
 {
  //This is now comparing the method name with the ActionKeyword value received from the excel
  if(method[i].getName().equals(sActions))
 { //In case of match found, it will execute the matched method 
  method[i].invoke(actionKeywords);
   //Once any method is executed, this break statement will take the flow outside of for loop
  break;
 }
 }
 }
}

To better understand the concept of data-driven, keyword-driven, and hybrid frameworks, watch the below video.

Conclusion

I hope this article was useful to you and gave you a clear understanding of what a Selenium framework is, how it is beneficial, and how to build your code structure using these three Selenium frameworks. Stay tuned for more articles in this series.

Topics:
big data ,selenium ,data-driven ,hybrid framework ,optimization

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}