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 csvimport pytestfrom 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 jsonimport 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.01const 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 Excelimport 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 Excelimport openpyxlimport 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 Excelconst 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 Excelusing 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 sqlite3import 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 SQLiteconst 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
- Keep data files in version control - But exclude sensitive data
- Use meaningful test IDs -
ids=[p['name'] for p in products] - Validate data files - Check for missing columns, invalid values
- Separate environments - dev_data.csv, staging_data.csv
- Consider data cleanup - Tests should clean up created data
Next Steps
- Page Object Model - Structure test code
- Page Factory - Annotation-based elements
- Parallel Execution - Run data tests fast
- pytest Integration - Python parameterization
- Cucumber BDD - Scenario outlines with data tables