Skip to main content
SeleniumDecoded

Data-Driven Testing

Run the same test with different data sets using external files, databases, and parameterization.

Selenium 3 & 4 Stable

Data-driven testing separates test logic from test data, allowing you to run the same test with multiple inputs without duplicating code.

Why Data-Driven Testing?

❌ Without data-driven:
- test_login_user1()
- test_login_user2()
- test_login_user3()
- test_login_invalid()
... (N tests for N data sets)
✅ With data-driven:
- test_login(data)
... (1 test, N data sets)

CSV Data Source

Reading Test Data from CSV
Selenium 3 & 4 Stable
// test_data/users.csv:
// username,password,expected_result
// validuser,validpass,success
// admin,admin123,success
// invalid,wrong,failure
import com.opencsv.CSVReader;
import org.testng.annotations.*;
import java.io.FileReader;
import java.util.*;
public class LoginDataDrivenTest {
@DataProvider(name = "csvData")
public Iterator<Object[]> csvDataProvider() throws Exception {
List<Object[]> data = new ArrayList<>();
CSVReader reader = new CSVReader(new FileReader("test_data/users.csv"));
String[] header = reader.readNext(); // Skip header
String[] line;
while ((line = reader.readNext()) != null) {
data.add(new Object[]{line[0], line[1], line[2]});
}
reader.close();
return data.iterator();
}
@Test(dataProvider = "csvData")
public void testLogin(String username, String password, String expected) {
driver.get("https://example.com/login");
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.id("submit")).click();
if (expected.equals("success")) {
Assert.assertTrue(driver.getCurrentUrl().contains("/dashboard"));
} else {
Assert.assertTrue(driver.findElement(By.className("error")).isDisplayed());
}
}
}
# test_data/users.csv:
# username,password,expected_result
# validuser,validpass,success
# admin,admin123,success
# invalid,wrong,failure
import csv
import pytest
from selenium.webdriver.common.by import By
def load_csv_data(filepath):
"""Load test data from CSV file."""
data = []
with open(filepath, 'r') as f:
reader = csv.DictReader(f)
for row in reader:
data.append((row['username'], row['password'], row['expected_result']))
return data
@pytest.mark.parametrize("username,password,expected", load_csv_data("test_data/users.csv"))
def test_login(driver, username, password, expected):
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys(username)
driver.find_element(By.ID, "password").send_keys(password)
driver.find_element(By.ID, "submit").click()
if expected == "success":
assert "/dashboard" in driver.current_url
else:
assert driver.find_element(By.CLASS_NAME, "error").is_displayed()
// test_data/users.csv:
// username,password,expected_result
// validuser,validpass,success
const fs = require('fs');
const { parse } = require('csv-parse/sync');
const { By } = require('selenium-webdriver');
function loadCsvData(filepath) {
const content = fs.readFileSync(filepath, 'utf-8');
return parse(content, { columns: true, skip_empty_lines: true });
}
const testData = loadCsvData('test_data/users.csv');
describe('Login Tests', function() {
testData.forEach(({ username, password, expected_result }) => {
it(`should handle login for ${username}`, async function() {
await driver.get('https://example.com/login');
await driver.findElement(By.id('username')).sendKeys(username);
await driver.findElement(By.id('password')).sendKeys(password);
await driver.findElement(By.id('submit')).click();
if (expected_result === 'success') {
const url = await driver.getCurrentUrl();
expect(url).to.include('/dashboard');
} else {
const error = await driver.findElement(By.className('error'));
expect(await error.isDisplayed()).to.be.true;
}
});
});
});
// test_data/users.csv:
// username,password,expected_result
using CsvHelper;
using NUnit.Framework;
using System.Globalization;
[TestFixture]
public class LoginDataDrivenTest
{
private static IEnumerable<TestCaseData> LoadCsvData()
{
using var reader = new StreamReader("test_data/users.csv");
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
var records = csv.GetRecords<dynamic>();
foreach (var record in records)
{
yield return new TestCaseData(
record.username,
record.password,
record.expected_result
);
}
}
[Test, TestCaseSource(nameof(LoadCsvData))]
public void TestLogin(string username, string password, string expected)
{
driver.Navigate().GoToUrl("https://example.com/login");
driver.FindElement(By.Id("username")).SendKeys(username);
driver.FindElement(By.Id("password")).SendKeys(password);
driver.FindElement(By.Id("submit")).Click();
if (expected == "success")
{
Assert.That(driver.Url, Does.Contain("/dashboard"));
}
else
{
Assert.That(driver.FindElement(By.ClassName("error")).Displayed, Is.True);
}
}
}

JSON Data Source

Reading Test Data from JSON
Selenium 3 & 4 Stable
// test_data/products.json:
// [
// {"name": "Laptop", "price": 999.99, "quantity": 2},
// {"name": "Mouse", "price": 29.99, "quantity": 5}
// ]
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.FileReader;
import java.util.List;
public class Product {
public String name;
public double price;
public int quantity;
}
@DataProvider(name = "jsonData")
public Object[][] jsonDataProvider() throws Exception {
Gson gson = new Gson();
FileReader reader = new FileReader("test_data/products.json");
List<Product> products = gson.fromJson(reader,
new TypeToken<List<Product>>(){}.getType());
reader.close();
Object[][] data = new Object[products.size()][1];
for (int i = 0; i < products.size(); i++) {
data[i][0] = products.get(i);
}
return data;
}
@Test(dataProvider = "jsonData")
public void testAddToCart(Product product) {
// Search for product
driver.findElement(By.id("search")).sendKeys(product.name);
driver.findElement(By.id("search-btn")).click();
// Add to cart with quantity
driver.findElement(By.id("quantity")).clear();
driver.findElement(By.id("quantity")).sendKeys(String.valueOf(product.quantity));
driver.findElement(By.id("add-to-cart")).click();
// Verify cart total
double expectedTotal = product.price * product.quantity;
String cartTotal = driver.findElement(By.id("cart-total")).getText();
Assert.assertEquals(Double.parseDouble(cartTotal), expectedTotal, 0.01);
}
# test_data/products.json:
# [
# {"name": "Laptop", "price": 999.99, "quantity": 2},
# {"name": "Mouse", "price": 29.99, "quantity": 5}
# ]
import json
import pytest
def load_json_data(filepath):
with open(filepath, 'r') as f:
return json.load(f)
products = load_json_data("test_data/products.json")
@pytest.mark.parametrize("product", products, ids=[p['name'] for p in products])
def test_add_to_cart(driver, product):
# Search for product
driver.find_element(By.ID, "search").send_keys(product['name'])
driver.find_element(By.ID, "search-btn").click()
# Add to cart with quantity
qty_input = driver.find_element(By.ID, "quantity")
qty_input.clear()
qty_input.send_keys(str(product['quantity']))
driver.find_element(By.ID, "add-to-cart").click()
# Verify cart total
expected_total = product['price'] * product['quantity']
cart_total = float(driver.find_element(By.ID, "cart-total").text)
assert abs(cart_total - expected_total) < 0.01
test_data/products.json
const products = require('../test_data/products.json');
describe('Cart Tests', function() {
products.forEach((product) => {
it(`should add ${product.name} to cart`, async function() {
await driver.findElement(By.id('search')).sendKeys(product.name);
await driver.findElement(By.id('search-btn')).click();
const qtyInput = await driver.findElement(By.id('quantity'));
await qtyInput.clear();
await qtyInput.sendKeys(product.quantity.toString());
await driver.findElement(By.id('add-to-cart')).click();
const expectedTotal = product.price * product.quantity;
const cartTotal = await driver.findElement(By.id('cart-total')).getText();
expect(parseFloat(cartTotal)).to.be.closeTo(expectedTotal, 0.01);
});
});
});
// Load JSON in C#
using System.Text.Json;
public class Product
{
public string Name { get; set; }
public double Price { get; set; }
public int Quantity { get; set; }
}
private static IEnumerable<TestCaseData> LoadJsonData()
{
string json = File.ReadAllText("test_data/products.json");
var products = JsonSerializer.Deserialize<List<Product>>(json);
foreach (var product in products)
{
yield return new TestCaseData(product).SetName(product.Name);
}
}
[Test, TestCaseSource(nameof(LoadJsonData))]
public void TestAddToCart(Product product)
{
// Test logic using product data
}

Excel Data Source

Reading Test Data from Excel
Selenium 3 & 4 Stable
// Using Apache POI for Excel
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
@DataProvider(name = "excelData")
public Object[][] excelDataProvider() throws Exception {
FileInputStream fis = new FileInputStream("test_data/testdata.xlsx");
Workbook workbook = new XSSFWorkbook(fis);
Sheet sheet = workbook.getSheet("LoginData");
int rowCount = sheet.getPhysicalNumberOfRows();
int colCount = sheet.getRow(0).getPhysicalNumberOfCells();
Object[][] data = new Object[rowCount - 1][colCount]; // Skip header
for (int i = 1; i < rowCount; i++) {
Row row = sheet.getRow(i);
for (int j = 0; j < colCount; j++) {
Cell cell = row.getCell(j);
data[i - 1][j] = getCellValue(cell);
}
}
workbook.close();
fis.close();
return data;
}
private String getCellValue(Cell cell) {
switch (cell.getCellType()) {
case STRING: return cell.getStringCellValue();
case NUMERIC: return String.valueOf(cell.getNumericCellValue());
case BOOLEAN: return String.valueOf(cell.getBooleanCellValue());
default: return "";
}
}
# Using openpyxl for Excel
import openpyxl
import pytest
def load_excel_data(filepath, sheet_name):
workbook = openpyxl.load_workbook(filepath)
sheet = workbook[sheet_name]
data = []
headers = [cell.value for cell in sheet[1]]
for row in sheet.iter_rows(min_row=2, values_only=True):
if row[0] is not None: # Skip empty rows
data.append(dict(zip(headers, row)))
return data
login_data = load_excel_data("test_data/testdata.xlsx", "LoginData")
@pytest.mark.parametrize("data", login_data)
def test_login_from_excel(driver, data):
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys(data['username'])
driver.find_element(By.ID, "password").send_keys(data['password'])
driver.find_element(By.ID, "submit").click()
if data['expected'] == 'success':
assert "/dashboard" in driver.current_url
// Using xlsx package for Excel
const XLSX = require('xlsx');
function loadExcelData(filepath, sheetName) {
const workbook = XLSX.readFile(filepath);
const sheet = workbook.Sheets[sheetName];
return XLSX.utils.sheet_to_json(sheet);
}
const loginData = loadExcelData('test_data/testdata.xlsx', 'LoginData');
describe('Login Tests from Excel', function() {
loginData.forEach((data, index) => {
it(`should test login case ${index + 1}`, async function() {
await driver.get('https://example.com/login');
await driver.findElement(By.id('username')).sendKeys(data.username);
await driver.findElement(By.id('password')).sendKeys(data.password);
await driver.findElement(By.id('submit')).click();
if (data.expected === 'success') {
const url = await driver.getCurrentUrl();
expect(url).to.include('/dashboard');
}
});
});
});
// Using EPPlus for Excel
using OfficeOpenXml;
private static IEnumerable<TestCaseData> LoadExcelData()
{
using var package = new ExcelPackage(new FileInfo("test_data/testdata.xlsx"));
var sheet = package.Workbook.Worksheets["LoginData"];
int rowCount = sheet.Dimension.Rows;
for (int row = 2; row <= rowCount; row++)
{
string username = sheet.Cells[row, 1].Value?.ToString();
string password = sheet.Cells[row, 2].Value?.ToString();
string expected = sheet.Cells[row, 3].Value?.ToString();
yield return new TestCaseData(username, password, expected);
}
}

Database Data Source

Reading Test Data from Database
Selenium 3 & 4 Medium
import java.sql.*;
@DataProvider(name = "dbData")
public Object[][] dbDataProvider() throws Exception {
List<Object[]> data = new ArrayList<>();
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/testdb", "user", "pass"
);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT username, password, expected FROM test_users");
while (rs.next()) {
data.add(new Object[]{
rs.getString("username"),
rs.getString("password"),
rs.getString("expected")
});
}
rs.close();
stmt.close();
conn.close();
return data.toArray(new Object[0][]);
}
import sqlite3
import pytest
def load_db_data():
conn = sqlite3.connect("test_data/testdb.sqlite")
cursor = conn.cursor()
cursor.execute("SELECT username, password, expected FROM test_users")
data = cursor.fetchall()
conn.close()
return data
@pytest.mark.parametrize("username,password,expected", load_db_data())
def test_login_from_db(driver, username, password, expected):
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys(username)
driver.find_element(By.ID, "password").send_keys(password)
driver.find_element(By.ID, "submit").click()
if expected == "success":
assert "/dashboard" in driver.current_url
// Using better-sqlite3 for SQLite
const Database = require('better-sqlite3');
function loadDbData() {
const db = new Database('test_data/testdb.sqlite');
const data = db.prepare('SELECT username, password, expected FROM test_users').all();
db.close();
return data;
}
const testData = loadDbData();
describe('DB-Driven Login Tests', function() {
testData.forEach((row) => {
it(`should test login for ${row.username}`, async function() {
// Test implementation
});
});
});
using System.Data.SqlClient;
private static IEnumerable<TestCaseData> LoadDbData()
{
using var conn = new SqlConnection("connection_string");
conn.Open();
using var cmd = new SqlCommand("SELECT username, password, expected FROM test_users", conn);
using var reader = cmd.ExecuteReader();
while (reader.Read())
{
yield return new TestCaseData(
reader["username"].ToString(),
reader["password"].ToString(),
reader["expected"].ToString()
);
}
}

Best Practices

  1. Keep data files in version control - But exclude sensitive data
  2. Use meaningful test IDs - ids=[p['name'] for p in products]
  3. Validate data files - Check for missing columns, invalid values
  4. Separate environments - dev_data.csv, staging_data.csv
  5. Consider data cleanup - Tests should clean up created data

Next Steps