reading-external-files-banner
Reading External Files Selenium With Java

The Complete Guide to Reading different Files in Test Automation

In modern test automation, keeping test logic separate from test data is a best practice that significantly improves the clarity, flexibility, and maintainability of your framework. Rather than embedding static or hard-coded values within test scripts, external data files like JSON, CSV, XML, or .properties offer a cleaner and more scalable solution. This approach is especially helpful in data-driven testing scenarios, where the same test case needs to run with multiple input combinations, or when working with dynamic content such as API payloads or environment-specific settings. By managing test data externally, teams can make changes quickly without altering the core test logic, which saves time, reduces errors, and supports smoother cross-environment executions. Whether you’re working with Selenium, Cypress, or other automation tools, reading external files enables you to build a modular and adaptable test suite that’s easier to update and more robust in the long run.

If you’re just starting your automation journey, don’t worry! We’ve got your back. Start with our blog:
Beginner’s Guide to Selenium with Java and Testing. It walks you through everything from setup to best practices.

Once you’re comfortable with the basics, this blog will guide you through the next level: working with dynamic test data using XML, JSON, and CSV files.

Table of Contents

Why Reading External Files is Crucial in Automation

In modern automation frameworks, relying on hard-coded data can slow down progress and introduce unnecessary maintenance work. Instead, using external files to manage test data offers a smarter and more efficient approach. Here’s why it matters:

  • Separation of Data and Test Logic
    Keeping test data separate from the test scripts helps maintain a clean and modular framework. This separation allows testers to update test data without modifying the actual code, improving clarity and maintainability.
  • Easy Maintenance and Scalability
    External files like XML, JSON, or CSV make it easier to manage and scale your tests. When your application evolves, you only need to update the data files, not every single test case.
  • Enhanced Reusability and Fewer Hard-Coded Values
    By avoiding hard-coded data, you make your test cases reusable across multiple scenarios. This reduces redundancy and ensures your automation framework is adaptable and future-ready.

Reading JSON Files in Automation Testing

JSON (JavaScript Object Notation) is one of the most commonly used file formats in test automation today. It’s lightweight, easy to read, and ideal for storing structured data making it a perfect fit for both backend and frontend testing needs.

What is JSON and Why It’s Used in Testing?

JSON is a text-based format for representing structured data, similar to how objects work in programming. It uses key-value pairs and is widely used to transmit data between a server and a client.

In the context of test automation, JSON files are useful because:

  • They can hold large sets of test data in a structured format.
  • They’re easy to parse using various libraries in almost every programming language.
  • They are readable and editable, even by non-programmers like QA analysts.

When Do Testers Use JSON Files?

  • API Testing: JSON is the default format for RESTful APIs. Testers often store expected API responses in JSON files and compare them during validation.
  • Data-Driven Testing: Instead of hardcoding test values, testers use JSON files to store multiple sets of inputs like usernames, passwords, or search terms.
  • Mocking Data: Sometimes APIs may not be ready yet, so testers mock the expected responses using JSON files.
  • Environment Configuration: URLs, credentials, timeouts, and other environment-specific values can be stored in JSON for easier switching between QA, staging, and production.

Tools and Libraries to Read JSON Files

Here are the most commonly used libraries and methods for reading JSON files:

Java doesn’t natively support JSON parsing, but several libraries make it easy:

  1. Jackson (Most Popular)
    • Developed by FasterXML
    • Supports converting JSON to Java objects (POJO) and vice versa.
  2. Gson (by Google)
    • Simple and efficient, widely used in Android development.

How to Read JSON Using Jackson in Java

Step 1: Add Jackson Dependency (for Maven users)

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.15.2</version>
</dependency>

 What jackson-databind Is Used For:

  • Reading JSON files and mapping them to Java classes (e.g., LoginDetails)
  • Writing Java objects into JSON
  • Frequently used with ObjectMapper in automation frameworks

Note:  These two classes remain the same in every test case. For simplicity, they are only shown once here and referenced in each scenario below.Step 2: Create a dataobjects Class (LoginDetails.java)

package dataobjects;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class LoginDetails {
   private String email;
   private String password;
   private String type;
}

Purpose:

  • This class is part of the dataobjects package.
  • It defines a data model to store login information.
  • The class has three private fields: email, password, and userType.
  • It uses Lombok annotations to reduce boilerplate code.
  • @Getter and @Setter auto-generate getter and setter methods.
  • @AllArgsConstructor creates a constructor with all three fields.
  • @NoArgsConstructor creates a no-argument constructor.
  • It’s used in the test to fill the login form with the correct credentials.
  • This makes the code cleaner, more maintainable, and reusable.
  • This class helps pass user data cleanly from CSV to tests.

Step 2: Create PageObject Class (Login Page.Java)

package pageobjects.login;

import dataobjects.LoginDetails;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;


public class LoginPage {

   private WebDriver driver;

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

   @FindBy(id = "input-email")
   private WebElement emailField;

   @FindBy(id = "input-password")
   private WebElement passwordField;

   @FindBy(css = "input[type='submit'][value='Login']")
   private WebElement loginButton;

   public void fillLoginFormFields(LoginDetails loginDetails) {
       emailField.clear();
       emailField.sendKeys(loginDetails.getEmail());

       passwordField.clear();
       passwordField.sendKeys(loginDetails.getPassword());
   }
   public void clickOnLoginButton() {
       loginButton.click();
   }
}

Explanation : 

  • The class LoginPage models the login page of a web application using the Page Object Model pattern.
  • It takes a Selenium WebDriver instance through its constructor to interact with the browser.
  • The PageFactory.initElements method initializes the web elements annotated with @FindBy.
  • The emailField is located by its HTML element ID “input-email”.
  • The passwordField is located by its HTML element ID “input-password”.
  • The loginButton is located using a CSS selector targeting a submit input with the value “Login”.
  • The method fillLoginFormFields takes a LoginDetails object and fills the email and password input fields accordingly.
  • It first clears the existing text in both fields before entering new data.
  • The method clickOnLoginButton clicks the login button to submit the form.
  • This class encapsulates all login page interactions, making test code cleaner and easier to maintain.

Step 3: Create JsonUtils Class  (JsonUtils.java)

package utilities;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import dataobjects.LoginDetails;
import java.io.File;
import java.io.IOException;

public class JsonUtils {

   private static final JsonNode usersJsonObj;

   static {
       String jsonFilePath = "src/test/resources/data/users.json";
       try {
           usersJsonObj = new ObjectMapper().readTree(new File(jsonFilePath));
       } catch (IOException e) {
           throw new RuntimeException("Failed to load users.json", e);
       }
   }

   public static LoginDetails getLoginDetailsFromJsonFile() {
       String userType = “admin”;
       if (userType == null || userType.isEmpty()) {
           throw new RuntimeException("System property 'userType' not set.");
       }
       return getLoginDetailsByUserType(userType);
   }
   public static LoginDetails getLoginDetailsByUserType(String userType) {
       JsonNode users = usersJsonObj.path("users");
       for (JsonNode user : users) {
           if (userType.equalsIgnoreCase(user.path("type").asText())) {
               return new ObjectMapper().convertValue(user, LoginDetails.class);
           }
       }
       throw new RuntimeException("User with type '" + userType + "' not found.");
   }
}

Explanation:

  • The JsonUtils class reads user login data from a JSON file located at src/test/resources/data/users.json.
  • It uses Jackson’s ObjectMapper to parse the JSON file into a static JsonNode object called usersJsonObj.
  • This loading happens once in a static block when the class is first used; if loading fails, it throws a runtime exception.
  • The method getLoginDetailsFromSystemUserType() reads the system property userType.
  • If the userType property is not set, it throws an exception.
  • It then calls getLoginDetailsFromJsonFile() passing the userType value.
  • The method getLoginDetailsFromJsonFile(String userType) searches the JSON users array for a user matching the specified type.
  • When it finds a matching user, it converts that JSON node into a LoginDetails Java object using Jackson.
  • If no user matches the given type, it throws a runtime exception indicating the user was not found.
  • This utility helps fetch user login info dynamically based on system properties, useful for test automation.

Step 4: Sample JSON File (testData.json)

{
 "users": [
   {
     "email": "user1@demo.com",
     "password": "userpass123",
     "type": "user"
   },
   {
     "email": "admin1@demo.com",
     "password": "adminpass123",
     "type": "admin"
   },
   {
     "email": "guest1@demo.com",
     "password": "guestpass123",
     "type": "guest"
   }
 ]
}

Purpose:

  • This JSON object has one main key called “users”.
  • The value of “users” is an array containing multiple user objects.
  • Each user object has three properties: “email”, “password”, and “type”.
  • The “email” is the user’s email address used for login.
  • The “password” is the corresponding password for that user.
  • The “type” defines the role or category of the user, like “user”, “admin”, or “guest”.
  • The first user is a normal user with email user1@demo.com.
  • The second user is an admin with email admin1@demo.com.
  • The third user is a guest with email guest1@demo.com.
  • This JSON structure is typically used to store login credentials for different user roles in testing or development

Step 6: Use in Your Test (LoginWithJsonTest.java)

package tests;


import base.BaseTest;
import dataobjects.LoginDetails;
import org.testng.Reporter;
import org.testng.annotations.Test;
import pageobjects.login.LoginPage;
import utilities.JsonUtils;

public class LoginTest extends BaseTest {

   @Test
   public void verifyUserLoginUsingJsonData() {
       Reporter.log("Step 1: Navigate to Login Page");
       driver.get("https://ecommerce-playground.lambdatest.io/index.php?route=account/login");

       Reporter.log("Step 2: Load login credentials based on user type");
       LoginDetails loginDetails = JsonUtils.getLoginDetailsFromJsonFile();

       Reporter.log("Step 3: Perform login using LoginPage object");
       LoginPage loginPage = new LoginPage(driver);
       loginPage.fillLoginFormFields(loginDetails);
       loginPage.clickOnLoginButton();


   }
}

Explanation:

  • The LoginTest class extends BaseTest, meaning it likely inherits WebDriver setup and teardown methods.
  • It contains a single test method named verifyUserLoginUserJsonData.
  • The test uses Reporter.log to log each step for better test reporting.
  • Step 1 opens the login page URL of the LambdaTest e-commerce playground.
  • Step 2 loads user login data from the users.json file using JsonUtils.getLoginDetailsFromSystemUserType().
  • The login data returned depends on the userType system property (e.g., user, admin, guest).
  • Step 3 creates an instance of the LoginPage class using the WebDriver.
  • It fills the login form with the provided email and password.
  • Then, it clicks the login button to attempt logging in.
  • This test demonstrates data-driven testing by dynamically loading credentials from a JSON file.

Reading CSV Files in Automation Testing

What is CSV?

CSV (Comma-Separated Values) is a simple text file format to represent tabular data each row is a record, and columns are separated by commas.

email,password,type
user1@demo.com,userpass123,user
admin1@demo.com,adminpass123,admin
guest1@demo.com,guestpass123,guest

Why Use CSV in Test Automation?

In Selenium test automation, CSV is commonly used for:

  • Data-driven testing – run the same test with different inputs.
  • Externalizing test data – separate logic from data.
  • Readable & editable – easily updated by non-developers.

Required Tool: OpenCSV Library

Add to pom.xml (if using Maven):

<dependency>
   <groupId>com.opencsv</groupId>
   <artifactId>opencsv</artifactId>
   <version>5.7.1</version>
</dependency>

This library allows you to easily read CSV into Java objects (POJOs).

Step-by-Step Code Explanation

Step 1: Create a CSV Reader Utility – CsvReader.java

package utilities;

import dataobjects.LoginDetails;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.List;

public class CsvUtils {

   public static LoginDetails getLoginDetailsFromCsvFIle(String filePath, String userType) {
       List<LoginDetails> matchedUsers = new ArrayList<>();
       try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
           String line;
           boolean isFirstRow = true;

           while ((line = br.readLine()) != null) {
               if (isFirstRow) {
                   isFirstRow = false; // skip header
                   continue;
               }

               String[] values = line.split(",");
               if (values[2].equalsIgnoreCase(userType)) {
                   return new LoginDetails(values[0].trim(), values[1].trim());
               }
           }
       } catch (Exception e) {
           e.printStackTrace();
       }
       throw new RuntimeException("User type not found: " + userType);
   }
}

Explanation:

  • This utility reads login data from a CSV file and returns it as a list of LoginData objects.
  • It uses the OpenCSV library (CSVReader) to read the CSV file line by line.
  • The method readLoginData(String filePath) takes the path to a CSV file as input.
  • It skips the first row assuming it’s a header (using isFirstRow flag).
  • For each remaining row, it extracts the first two columns (username and password).
  • These values are trimmed and stored in a new LoginData object.
  • Each LoginData object is added to the list dataList.
  • The try-with-resources block ensures the file is properly closed.
  • If any exception occurs, it’s caught and printed.

Step 2: Use in Your Test (LoginWithCsvTest.java)

package tests;

import base.BaseTest;
import dataobjects.LoginDetails;
import org.testng.Reporter;
import org.testng.annotations.Test;
import pageobjects.login.LoginPage;
import utilities.CsvUtils;

public class LoginCsvTest extends BaseTest {

   @Test
   public void verifyUserLoginUsingCsvData() {
       Reporter.log("Step 1: Navigate to Login Page");
       driver.get("https://ecommerce-playground.lambdatest.io/index.php?route=account/login");

       Reporter.log("Step 2: Load login credentials based on user type");
       String userType = “admin”;
       String csvPath = "src/test/resources/data/users.csv";

       LoginDetails loginDetails = CsvUtils.getLoginDetailsFromCsvFile(csvPath, userType);

       Reporter.log("Step 3: Perform login using LoginPage object");
       LoginPage loginPage = new LoginPage(driver);
       loginPage.fillLoginFormFields(loginDetails);
       loginPage.clickOnLoginButton();

   }
}

Explanation :

  • The LoginCsvTest class extends BaseTest, which handles WebDriver setup and cleanup.
  • It defines a test method verifyUserLoginUsingCsvData to test login using data from a CSV file.
  • Step 1 uses driver.get() to navigate to the login page of the LambdaTest playground.
  • Step 2 retrieves the userType system property to decide which user credentials to use.
  • It defines the path to the CSV file (users.csv) containing login data.
  • The method CsvUtils.getLoginDetailsFromCsvFile() reads the CSV and fetches data for the specified user type.
  • The login credentials are stored in a LoginDetails object.
  • A LoginPage object is created to interact with the login page.
  • It fills the email and password fields using the fetched data and clicks the login button.
  • This test showcases CSV-based data-driven login testing using the Page Object Model pattern.

Reading XML Files in Automation Testing

When it comes to building robust automation frameworks, managing data effectively is a key component. XML is still widely used in test automation, especially in organizations that deal with legacy systems or configurable environments. In this blog, we’ll explore how to read data from an XML file using Selenium with Java, and how it helps keep your test scripts clean and maintainable.

What is XML and Why Use it in Test Automation?

XML (eXtensible Markup Language) is a markup language designed to store and transport data. It’s readable by both humans and machines and is often used for configuration files, test input, or API communication formats.

Key reasons to use XML in test automation:

  • Hierarchical & structured: Easy to organize data like user profiles, environments, test scenarios.
  • Standard format: Compatible with various tools and languages.
  • Reusable & centralized: You can store data separately from your test logic.

Benefits of Using XML in Automation

BenefitDescription
ReusableUse same XML for multiple test cases
Decoupled Test DataKeeps your test logic separate from test data
MaintainableEasy to update when credentials or configs change
ScalableCan support multiple environments (dev, test, staging) by adding nested tags

Step-by-Step Code Explanation

Add to pom.xml (if using Maven):

<dependency>
   <groupId>com.fasterxml.jackson.dataformat</groupId>
   <artifactId>jackson-dataformat-xml</artifactId>
   <version>2.15.2</version> <!-- or latest -->
</dependency>

UseCase: 

  • Library Name: jackson-dataformat-xml
  • Provided By: Jackson (a popular data processing library)
  • Purpose: Allows you to read from and write to XML files using the same approach as you do with JSON in Jackson.

Step 1: Create a XML Reader Utility – XmlUtils.java

package utilities;

import dataobjects.LoginDetails;
import dataobjects.Users;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.File;

public class XmlUtils {

   private static final Users users;

   static {
       try {
           File file = new File("src/test/resources/data/users.xml");
           JAXBContext jaxbContext = JAXBContext.newInstance(Users.class);
           users = (Users) jaxbContext.createUnmarshaller().unmarshal(file);
       } catch (JAXBException e) {
           throw new RuntimeException("Failed to read users.xml", e);
       }
   }

   public static LoginDetails getLoginDetailsFromSystemUserType() {
       String userType = “guest”;
       if (userType == null || userType.isEmpty()) {
           throw new RuntimeException("System property 'userType' is not set.");
       }
       return getLoginDetailsByUserType(userType);
   }

   public static LoginDetails getLoginDetailsByUserType(String userType) {
       return users.getUsers().stream()
               .filter(user -> userType.equalsIgnoreCase(user.getType()))
               .findFirst()
               .orElseThrow(() -> new RuntimeException("User with type '" + userType + "' not found."));
   }
}

Explanation:

  • The XmlUtils class reads and processes user login data from an XML file using JAXB (Java XML Binding).
  • A static block is used to unmarshal the XML file (users.xml) into a Users object when the class loads.
  • JAXBContext is initialized with the Users.class, which defines how to bind XML data to Java objects.
  • If unmarshalling fails, a RuntimeException is thrown with an appropriate error message.
  • The method getLoginDetailsFromSystemUserType() retrieves the userType from system properties.
  • If userType is missing or empty, it throws a runtime exception.
  • It delegates to getLoginDetailsByUserType() to fetch login details for the specified user type.
  • This method filters the list of users to find a match based on the type field.
  • If a matching user type is found, it returns the corresponding LoginDetails object.
  • If no match is found, it throws a RuntimeException indicating the user type was not found.

Step 2 : Sample XML file (testdata.xml)

<users>
    <user>
        <email>user1@demo.com</email>
        <password>userpass123</password>
        <type>user</type>
    </user>
    <user>
        <email>admin1@demo.com</email>
        <password>adminpass123</password>
        <type>admin</type>
    </user>
    <user>
        <email>guest1@demo.com</email>
        <password>guestpass123</password>
        <type>guest</type>
    </user>
</users>

Explanation: 

  • This XML structure allows storing multiple <user> entries under one <users> block, which is ideal for test data used in automated login scenarios.

So overall, it’s a simple structured file containing one user’s credentials, but it can easily be extended to include more users.

Step 3: Create a utility class (XmlUtils. java)

package utilities;
import dataobjects.LoginDetails;
import dataobjects.Users;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.File;

public class XmlUtils {

   private static final Users users;

   static {
       try {
           File file = new File("src/test/resources/data/users.xml");
           JAXBContext jaxbContext = JAXBContext.newInstance(Users.class);
           users = (Users) jaxbContext.createUnmarshaller().unmarshal(file);
       } catch (JAXBException e) {
           throw new RuntimeException("Failed to read users.xml", e);
       }
   }

   public static LoginDetails getLoginDetailsFromXmlFile() {
       String userType = “guest”;
       if (userType == null || userType.isEmpty()) {
           throw new RuntimeException("System property 'userType' is not set.");
       }
       return getLoginDetailsByUserType(userType);
   }

   public static LoginDetails getLoginDetailsByUserType(String userType) {
       return users.getUsers().stream()
               .filter(user -> userType.equalsIgnoreCase(user.getType()))
               .findFirst()
               .orElseThrow(() -> new RuntimeException("User with type '" + userType + "' not found."));
   }
}

Explanation:

  • XmlUtils is a utility class used to load user login details from an XML file.
  • It uses JAXB to unmarshal the users.xml file into a Users object.
  • The XML file path is defined as src/test/resources/data/users.xml.
  • The static block loads and parses the XML only once when the class is first accessed.
  • If XML parsing fails, a RuntimeException is thrown with a relevant message.
  • The method getLoginDetailsFromSystemUserType() fetches the user type from system properties.
  • It then calls getLoginDetailsByUserType(String userType) to find the matching user.
  • This method filters the loaded list of users using Java Streams.
  • If a match is found, the corresponding LoginDetails object is returned.
  • If not found, it throws a RuntimeException indicating the user type doesn’t exist in the XML.

Step 4: Use it in your Test Class (LoginWithXmlTest.java)

package tests;

import base.BaseTest;
import dataobjects.LoginDetails;
import org.testng.annotations.Test;
import pageobjects.login.LoginPage;
import utilities.XmlUtils;

public class LoginXmlTest extends BaseTest {
   @Test
   public void verifyLoginUsingXmlData() {
       driver.get("https://ecommerce-playground.lambdatest.io/index.php?route=account/login");

       LoginDetails loginDetails = XmlUtils.getLoginDetailsFromSystemUserType();
       LoginPage loginPage = new LoginPage(driver);
       loginPage.fillLoginFormFields(loginDetails);
       loginPage.clickOnLoginButton();
   }
}

Explanation:

  • LoginXmlTest is a TestNG-based test class that extends BaseTest for setup and teardown.
  • It verifies user login functionality using data loaded from an XML file.
  • The @Test method verifyLoginUsingXmlData() is the actual test method.
  • The test begins by navigating to the login page of an e-commerce demo site.
  • XmlUtils.getLoginDetailsFromSystemUserType() loads the correct login credentials based on a system property (userType).
  • The user credentials are mapped into a LoginDetails object.
  • A LoginPage object is instantiated, passing the WebDriver instance.
  • The login form is filled using loginPage.fillLoginFormFields(loginDetails).
  • The login button is clicked using loginPage.clickOnLoginButton().
  • The test dynamically selects user credentials based on runtime input, promoting reusability and flexibility.

Reading Excel Files in Test Automation

Why Excel is Still Widely Used for Test Data?

Even with the rise of advanced data formats like JSON, YAML, and databases, Excel files remain a favorite among testers and stakeholders. Here’s why:

Easy to Use and Understand

Excel is familiar to everyone from testers to business analysts and even non-technical team members. It provides a tabular view of data that’s easy to read and edit.

Supports Large Datasets

It can hold a large volume of data and multiple sheets, making it a flexible tool for organizing test cases, inputs, and expected results.

No Technical Skills Needed

Manual testers and business users can easily update Excel files without needing to understand JSON syntax or databases.

Ideal for Data-Driven Testing

Excel pairs well with data-driven frameworks where the same test case is run multiple times with different input sets.

Libraries to Read Excel in Automation Frameworks

Depending on the tech stack you’re using, here are the most popular libraries for reading Excel files:

Java – Apache POI

<dependency>
   <groupId>org.apache.poi</groupId>
   <artifactId>poi-ooxml</artifactId>
   <version>5.4.0</version> <!-- or latest -->
</dependency>

Apache POI is the standard Java library for reading and writing Microsoft Office documents, including .xls and .xlsx.

  • Supports both older (.xls) and newer (.xlsx) Excel formats.
  • Highly customizable.
  • Ideal for Selenium, TestNG, JUnit-based frameworks.

Node.js – ExcelJS

ExcelJS is a powerful library in the JavaScript ecosystem used to read/write Excel files in Node.js environments.

  • Works well with tools like Cypress or Playwright.
  • Supports both reading and styling Excel sheets.

Code Example: Read Excel Data Row by Row

Here’s a basic code example using Apache POI in Java to read data from an Excel file row by row.

Step 1: Excel File Sample (testdata.xlsx)

Step 2: Create a Utility Class (ExcelUtils.java)

package utilities;

import dataobjects.LoginDetails;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.util.Iterator;

public class ExcelUtils {

   public static LoginDetails getLoginDetailsFromExcelFile(String userType) {
       String excelPath = "src/test/resources/data/users.xlsx";
       try (FileInputStream fis = new FileInputStream(excelPath);
            Workbook workbook = new XSSFWorkbook(fis)) {

           Sheet sheet = workbook.getSheet("sheet1"); // Sheet name
           Iterator<Row> rowIterator = sheet.iterator();

           // Read header row
           Row headerRow = rowIterator.next();
           int emailCol = -1, passwordCol = -1, typeCol = -1;

           for (int i = 0; i < headerRow.getLastCellNum(); i++) 
               String header = headerRow.getCell(i).getStringCellValue().trim().toLowerCase();
               if (header.equals("email")) emailCol = i;
               if (header.equals("password")) passwordCol = i;
               if (header.equals("type")) typeCol = i;
           }

           // Read data rows
           while (rowIterator.hasNext()) {
               Row row = rowIterator.next();
               String type = row.getCell(typeCol).getStringCellValue().trim();

               if (userType.equalsIgnoreCase(type)) {
                   String email = row.getCell(emailCol).getStringCellValue().trim()
                   String password = row.getCell(passwordCol).getStringCellValue().trim();

                   LoginDetails loginDetails = new LoginDetails()
                   loginDetails.setEmail(email);
                   loginDetails.setPassword(password);
                   return loginDetails;
               }
           }

           throw new RuntimeException("User type '" + userType + "' not found in Excel");

       } catch (Exception e) {
           throw new RuntimeException("Failed to read Excel file", e);
       }
   }
}
}

Explanation

  • The class reads login credentials from an Excel file (users.xlsx).
  • It uses Apache POI library to handle .xlsx files.
  • The method getLoginDetailsByUserType(String userType) is the entry point.
  • It opens the Excel file from the resources/data path.
  • It reads the first row (header) to find column indices for email, password, and type.
  • It loops through each subsequent row to match the type with the given userType.
  • If a match is found, it extracts the email and password values.
  • It creates a LoginDetails object and sets the extracted values.
  • If no matching user type is found, it throws a RuntimeException.
  • Any file or parsing errors are caught and rethrown as a runtime exception.

Step 3: Use it in your Test class (LoginExcelTest.java)

package tests;

import base.BaseTest;
import dataobjects.LoginDetails;
import org.testng.Reporter;
import org.testng.annotations.Test;
import pageobjects.login.LoginPage;
import utilities.ExcelUtils;

public class LoginExcelTest extends BaseTest {
   @Test
   public void verifyUserLoginUsingExcelData() {

       Reporter.log("Step 1: Navigate to Login Page");
       driver.get("https://ecommerce-playground.lambdatest.io/index.php?route=account/login");

       Reporter.log("Step 2: Fetch login data from Excel");
       String userType = “admin”;
       LoginDetails loginDetails = ExcelUtils.getLoginDetailsFromExcelFile(userType);

       Reporter.log("Step 3: Perform login");
       LoginPage loginPage = new LoginPage(driver);
       loginPage.fillLoginFormFields(loginDetails);
       loginPage.clickOnLoginButton();
   }
}

Explanation: 

  • This is a TestNG test class that extends BaseTest.
  • The test method is named verifyUserLoginUsingExcelData.
  • It opens the login page of the eCommerce Playground site.
  • It logs each step using Reporter.log() for reporting.
  • It reads the userType from system properties; defaults to “user” if not provided.
  • It calls ExcelUtils.getLoginDetailsFromExcelFile() to fetch login data from an Excel file.
  • The login data includes an email and password.
  • A LoginPage object is created to interact with the login UI.
  • The login form is filled with data from the Excel file.
  • Finally, it clicks the login button to submit the form.

Reading Properties / Config Files in Test Automation

In modern test automation, managing test data and configurations is just as important as writing good test cases. Rather than hard coding values like URLs, browser types, usernames, and passwords directly into your automation scripts, it’s best to keep them separate in external files.

That’s where properties/config files come in. These simple files help externalize environment-specific settings, improve code reusability, and support multi-environment testing making your automation framework flexible, maintainable, and scalable.

Why Use Properties/Config Files in Automation?

When you’re working on a real-world test project, you’ll often need different configurations for different environments (like dev, QA, staging, production). Putting these values inside your Java classes makes them hard to maintain and risky to change.

Common things stored in config files:

Configuration TypeExample
Base URLhttps://testsite.com
Browser Typechrome, firefox
Credentialsusername, password
Timeout Valuesimplicit.wait=10
Database Infodb.user=root, db.pass=1234

By moving these values to an external .properties file, you:

  • Separate data from logic
  • Enable easy switching between environments
  • Reduce code duplication
  • Support parameterization in frameworks like TestNG, JUnit, etc.

What is a .properties File?

baseUrl=https://ecommerce-playground.lambdatest.io/index.php?route=account/login
email=test_user
password=Test@123
browser=chrome
implicitWait=10

The config.properties file:

  • Contains key-value pairs
  • Is used to store configuration settings outside of the code
  • Helps keep your automation framework clean, scalable, and environment-independent
  • Can be loaded in Java using the Properties class

Step-by-Step Java Implementation

Step 1: Create a Utility Class – ConfigReader.java

package utils;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class ConfigReader {
   private static Properties properties = new Properties();
   public static void loadConfig(String filePath) {
       try {
           FileInputStream fis = new FileInputStream(filePath);
           properties.load(fis);
           fis.close();
       } catch (IOException e) {
           throw new RuntimeException("Failed to load config file: " + filePath, e);
       }
   }

   public static String get(String key) {
       return properties.getProperty(key);
   }
}

Here’s an explanation of the ConfigReader class in 10 lines:

  • The ConfigReader class is used to load and read values from a config.properties file in Java automation frameworks.
  • It uses Java’s built-in Properties class to manage key-value pairs.
  • The loadConfig(String filePath) method reads the properties file using a FileInputStream.
  • The file path is passed as a parameter to this method, allowing flexibility in file location.
  • Once loaded, the key-value pairs are stored in the properties object.
  • If the file can’t be read, a RuntimeException is thrown with an error message.
  • The get(String key) method retrieves the value for a given key from the loaded properties.
  • This allows easy access to config data like URLs, credentials, or timeouts across the project.
  • Centralizing config values in one file makes your test framework easier to maintain.
  • It promotes cleaner code by separating static data from test logic.

 Step 2: Access These Properties in Your Test Class

package test;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import pageObject.LoginPage;
import pojo.LoginData;
import utils.ConfigReader;
import utils.JsonReader;
import java.time.Duration;

public class LoginTest {

   WebDriver driver;

   @BeforeClass
   public void setUp() throws InterruptedException {
       // Load the config file
       ConfigReader.loadConfig("src/test/resources/Config.properties");

       // Set up driver based on conf
       if (ConfigReader.get("browser").equalsIgnoreCase("chrome")) {
           driver = new ChromeDriver();
           Thread.sleep(3000);
       }
       driver.get(ConfigReader.get("baseUrl"));
       driver.manage().window().maximize();

       // Implicit wait from config
       driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(
               Integer.parseInt(ConfigReader.get("implicitWait"))
       ));
   }

   @Test
   public void loginTest() {
       String email = ConfigReader.get("email");
       String password = ConfigReader.get("password");
       LoginData loginData = new LoginData(email, password);
       LoginPage loginPage = new LoginPage(driver);
       loginPage.performLogin(loginData);
       loginPage.tearDown();
   }

Here’s a clear explanation of the LoginTest code:

  • Class Purpose: This is a TestNG-based Selenium test class that automates the login process using data from a configuration file.
  • Config Loading: In the @BeforeClass method, it first loads the Config.properties file using the ConfigReader.loadConfig() method, which holds values like browser type, base URL, email, password, and implicit wait.
  • WebDriver Setup: Based on the browser specified in the config (e.g., Chrome), it initializes the appropriate WebDriver (ChromeDriver here), then navigates to the base URL and maximizes the window.
  • Wait Configuration: It sets an implicit wait by reading the value (e.g., 10 seconds) from the config file and applying it to the driver.
  • Login Test Execution: In the @Test method, it retrieves login credentials (email and password) from the config, stores them in a LoginData object, and uses a LoginPage object to perform the login.
  • Teardown: After login is attempted, loginPage.tearDown() is called to close the browser session.

Conclusion

In the evolving world of test automation, keeping your test scripts clean, reusable, and easy to maintain is crucial. One of the most effective ways to achieve this is by separating test logic from test data something many automation testers overlook early on. Instead of hardcoding values like input data, expected results, or environment settings directly into your test cases, you can manage them in structured external files such as JSON, XML, CSV, Excel, or .properties files

This method not only improves readability and makes your test framework more modular, but also supports dynamic test execution through data-driven testing approaches. For example, a login test can run multiple times with different user credentials stored in a single test data file without changing a line of test logic. As your project scales or requirements evolve, this separation allows you to update data without touching your test code, saving hours of rework. Whether you’re testing APIs, web forms, or large datasets, reading data from external sources helps you create reliable, flexible, and maintainable test automation frameworks.

Witness how our meticulous approach and cutting-edge solutions elevated quality and performance to new heights. Begin your journey into the world of software testing excellence. To know more refer to Tools & Technologies & QA Services.

If you would like to learn more about the awesome services we provide,  be sure to reach out.

Happy Testing 🙂