How to Implement Data-Driven Testing with Selenium WebDriver
Data-driven testing is a powerful approach that allows you to execute the same test script with multiple sets of test data. This methodology separates test logic from test data, making your Selenium WebDriver tests more maintainable, scalable, and comprehensive. Instead of hardcoding test values, you can feed different data sets to your tests from external sources like CSV files, JSON files, or databases.
Understanding Data-Driven Testing
Data-driven testing involves: - Test Data: External data sources containing input values and expected results - Test Script: The automation code that remains constant across different data sets - Test Framework: The structure that reads data and executes tests iteratively
This approach is particularly valuable when you need to test the same functionality with various input combinations, such as login scenarios with different usernames and passwords, or form validation with multiple data sets.
Setting Up Data-Driven Tests in Python
Using CSV Files
CSV (Comma-Separated Values) files are one of the most common data sources for data-driven testing. Here's how to implement it:
import csv
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class TestDataDriven:
def setup_method(self):
self.driver = webdriver.Chrome()
self.driver.implicitly_wait(10)
def teardown_method(self):
self.driver.quit()
def read_csv_data(self, filename):
"""Read test data from CSV file"""
test_data = []
with open(filename, 'r', newline='', encoding='utf-8') as file:
reader = csv.DictReader(file)
for row in reader:
test_data.append(row)
return test_data
@pytest.mark.parametrize("test_data",
lambda: TestDataDriven().read_csv_data('login_data.csv'))
def test_login_with_csv_data(self, test_data):
"""Test login functionality with CSV data"""
self.driver.get("https://example.com/login")
# Find and interact with elements
username_field = self.driver.find_element(By.ID, "username")
password_field = self.driver.find_element(By.ID, "password")
login_button = self.driver.find_element(By.ID, "login-btn")
# Input test data
username_field.clear()
username_field.send_keys(test_data['username'])
password_field.clear()
password_field.send_keys(test_data['password'])
login_button.click()
# Verify expected result
if test_data['expected_result'] == 'success':
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "dashboard"))
)
else:
error_message = self.driver.find_element(By.CLASS_NAME, "error-message")
assert error_message.is_displayed()
Create a corresponding CSV file (login_data.csv
):
username,password,expected_result
valid_user,valid_pass,success
invalid_user,invalid_pass,failure
valid_user,invalid_pass,failure
,valid_pass,failure
valid_user,,failure
Using JSON Files
JSON files provide more flexibility for complex data structures:
import json
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestJSONDataDriven:
def setup_method(self):
self.driver = webdriver.Chrome()
def teardown_method(self):
self.driver.quit()
def load_json_data(self, filename):
"""Load test data from JSON file"""
with open(filename, 'r', encoding='utf-8') as file:
return json.load(file)
@pytest.mark.parametrize("test_case",
lambda: TestJSONDataDriven().load_json_data('form_data.json'))
def test_form_submission(self, test_case):
"""Test form submission with JSON data"""
self.driver.get("https://example.com/register")
# Fill form fields
for field_name, field_value in test_case['input_data'].items():
element = self.driver.find_element(By.NAME, field_name)
element.clear()
element.send_keys(field_value)
# Submit form
submit_button = self.driver.find_element(By.ID, "submit-btn")
submit_button.click()
# Validate results
if test_case['expected_result']['success']:
success_message = self.driver.find_element(By.CLASS_NAME, "success")
assert success_message.is_displayed()
else:
error_elements = self.driver.find_elements(By.CLASS_NAME, "error")
assert len(error_elements) > 0
Corresponding JSON file (form_data.json
):
[
{
"test_name": "Valid registration",
"input_data": {
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "1234567890"
},
"expected_result": {
"success": true,
"message": "Registration successful"
}
},
{
"test_name": "Invalid email format",
"input_data": {
"first_name": "Jane",
"last_name": "Smith",
"email": "invalid-email",
"phone": "0987654321"
},
"expected_result": {
"success": false,
"error_fields": ["email"]
}
}
]
Data-Driven Testing in Java
Using TestNG with DataProvider
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.Assert;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class DataDrivenTest {
private WebDriver driver;
@DataProvider(name = "loginData")
public Object[][] getLoginData() throws IOException {
return readCSVData("src/test/resources/login_data.csv");
}
@DataProvider(name = "searchData")
public Object[][] getSearchData() {
return new Object[][] {
{"Selenium WebDriver", "automation tool", true},
{"Java programming", "programming language", true},
{"InvalidSearchTerm123", "no results", false}
};
}
@Test(dataProvider = "loginData")
public void testLogin(String username, String password, String expectedResult) {
driver = new ChromeDriver();
driver.get("https://example.com/login");
WebElement usernameField = driver.findElement(By.id("username"));
WebElement passwordField = driver.findElement(By.id("password"));
WebElement loginButton = driver.findElement(By.id("login-btn"));
usernameField.sendKeys(username);
passwordField.sendKeys(password);
loginButton.click();
if ("success".equals(expectedResult)) {
Assert.assertTrue(driver.findElement(By.className("dashboard")).isDisplayed());
} else {
Assert.assertTrue(driver.findElement(By.className("error-message")).isDisplayed());
}
driver.quit();
}
@Test(dataProvider = "searchData")
public void testSearch(String searchTerm, String expectedContent, boolean shouldFind) {
driver = new ChromeDriver();
driver.get("https://example.com/search");
WebElement searchBox = driver.findElement(By.id("search-input"));
WebElement searchButton = driver.findElement(By.id("search-btn"));
searchBox.sendKeys(searchTerm);
searchButton.click();
List<WebElement> results = driver.findElements(By.className("search-result"));
if (shouldFind) {
Assert.assertTrue(results.size() > 0, "Expected to find search results");
} else {
Assert.assertTrue(results.size() == 0, "Expected no search results");
}
driver.quit();
}
private Object[][] readCSVData(String filePath) throws IOException {
List<Object[]> data = new ArrayList<>();
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String line;
// Skip header
reader.readLine();
while ((line = reader.readLine()) != null) {
String[] values = line.split(",");
data.add(values);
}
reader.close();
return data.toArray(new Object[0][]);
}
}
Using JUnit 5 with ParameterizedTest
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.By;
import static org.junit.jupiter.api.Assertions.*;
public class JUnitDataDrivenTest {
private WebDriver driver;
@BeforeEach
void setUp() {
driver = new ChromeDriver();
}
@AfterEach
void tearDown() {
if (driver != null) {
driver.quit();
}
}
@ParameterizedTest
@CsvFileSource(resources = "/login_data.csv", numLinesToSkip = 1)
void testLoginWithCSV(String username, String password, String expectedResult) {
driver.get("https://example.com/login");
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.id("login-btn")).click();
if ("success".equals(expectedResult)) {
assertTrue(driver.findElement(By.className("dashboard")).isDisplayed());
} else {
assertTrue(driver.findElement(By.className("error-message")).isDisplayed());
}
}
@ParameterizedTest
@ValueSource(strings = {"chrome", "firefox", "edge"})
void testCrossBrowser(String browserName) {
// Implementation would vary based on browser
// This demonstrates testing across different browsers
assertNotNull(browserName);
}
}
Data-Driven Testing in JavaScript/Node.js
Using Mocha with External Data
const { Builder, By, until } = require('selenium-webdriver');
const fs = require('fs');
const csv = require('csv-parser');
const assert = require('assert');
describe('Data-Driven Tests', function() {
let driver;
beforeEach(async function() {
driver = await new Builder().forBrowser('chrome').build();
});
afterEach(async function() {
await driver.quit();
});
// Read CSV data and create dynamic tests
const testData = [];
before(function(done) {
fs.createReadStream('test_data.csv')
.pipe(csv())
.on('data', (row) => {
testData.push(row);
})
.on('end', () => {
done();
});
});
it('should run login tests with CSV data', async function() {
for (const data of testData) {
await driver.get('https://example.com/login');
const usernameField = await driver.findElement(By.id('username'));
const passwordField = await driver.findElement(By.id('password'));
const loginButton = await driver.findElement(By.id('login-btn'));
await usernameField.clear();
await usernameField.sendKeys(data.username);
await passwordField.clear();
await passwordField.sendKeys(data.password);
await loginButton.click();
if (data.expected_result === 'success') {
await driver.wait(until.elementLocated(By.className('dashboard')), 5000);
} else {
const errorMessage = await driver.findElement(By.className('error-message'));
assert(await errorMessage.isDisplayed());
}
}
});
});
Using Jest with JSON Data
const { Builder, By, until } = require('selenium-webdriver');
const testData = require('./test-data.json');
describe('Data-Driven Login Tests', () => {
let driver;
beforeEach(async () => {
driver = await new Builder().forBrowser('chrome').build();
});
afterEach(async () => {
await driver.quit();
});
test.each(testData)('Login test with %s', async (testCase) => {
await driver.get('https://example.com/login');
const usernameField = await driver.findElement(By.id('username'));
const passwordField = await driver.findElement(By.id('password'));
const loginButton = await driver.findElement(By.id('login-btn'));
await usernameField.sendKeys(testCase.username);
await passwordField.sendKeys(testCase.password);
await loginButton.click();
if (testCase.shouldSucceed) {
await driver.wait(until.elementLocated(By.className('dashboard')), 5000);
} else {
const errorElement = await driver.findElement(By.className('error-message'));
expect(await errorElement.isDisplayed()).toBe(true);
}
});
});
Advanced Data-Driven Testing Techniques
Using Excel Files (Java Example)
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ExcelDataProvider {
public static Object[][] readExcelData(String filePath, String sheetName) throws IOException {
List<Object[]> data = new ArrayList<>();
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis)) {
Sheet sheet = workbook.getSheet(sheetName);
for (int i = 1; i <= sheet.getLastRowNum(); i++) { // Skip header
Row row = sheet.getRow(i);
if (row != null) {
Object[] rowData = new Object[row.getLastCellNum()];
for (int j = 0; j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
rowData[j] = getCellValue(cell);
}
data.add(rowData);
}
}
}
return data.toArray(new Object[0][]);
}
private static Object getCellValue(Cell cell) {
if (cell == null) return "";
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
return cell.getNumericCellValue();
case BOOLEAN:
return cell.getBooleanCellValue();
default:
return "";
}
}
}
Database-Driven Testing (Python Example)
import sqlite3
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestDatabaseDriven:
def setup_method(self):
self.driver = webdriver.Chrome()
def teardown_method(self):
self.driver.quit()
def get_test_data_from_database(self):
"""Fetch test data from database"""
conn = sqlite3.connect('test_data.db')
cursor = conn.cursor()
cursor.execute("SELECT username, password, expected_result FROM login_tests")
test_data = cursor.fetchall()
conn.close()
return test_data
@pytest.mark.parametrize("username,password,expected_result",
lambda: TestDatabaseDriven().get_test_data_from_database())
def test_login_from_database(self, username, password, expected_result):
"""Test login with database-driven data"""
self.driver.get("https://example.com/login")
self.driver.find_element(By.ID, "username").send_keys(username)
self.driver.find_element(By.ID, "password").send_keys(password)
self.driver.find_element(By.ID, "login-btn").click()
if expected_result == 'success':
assert self.driver.find_element(By.CLASS_NAME, "dashboard").is_displayed()
else:
assert self.driver.find_element(By.CLASS_NAME, "error-message").is_displayed()
Best Practices for Data-Driven Testing
1. Data Organization and Management
- Separate test data by functionality: Create different data files for login, registration, search, etc.
- Use meaningful file names:
login_test_data.csv
,product_search_data.json
- Include test case descriptions: Add columns for test case names and descriptions
- Validate data integrity: Ensure data consistency and completeness before test execution
2. Test Data Design
- Cover edge cases: Include boundary values, special characters, and null/empty values
- Include negative test cases: Test with invalid data to ensure proper error handling
- Use realistic data: Mirror production-like data patterns when possible
- Keep data maintainable: Avoid hardcoding values that change frequently
3. Framework Integration
When implementing data-driven testing alongside other Selenium practices, consider how it integrates with comprehensive element waiting strategies and proper error handling techniques.
4. Performance Considerations
- Optimize data loading: Load data once and reuse across tests
- Use parallel execution: Run data-driven tests in parallel when possible
- Implement proper cleanup: Ensure browser instances are properly closed
- Monitor resource usage: Keep track of memory and CPU usage during large data sets
5. Reporting and Debugging
import logging
from datetime import datetime
class DataDrivenReporter:
def __init__(self):
self.setup_logging()
self.results = []
def setup_logging(self):
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('data_driven_tests.log'),
logging.StreamHandler()
]
)
def log_test_result(self, test_data, result, execution_time):
"""Log individual test results"""
log_entry = {
'timestamp': datetime.now().isoformat(),
'test_data': test_data,
'result': result,
'execution_time': execution_time
}
self.results.append(log_entry)
logging.info(f"Test completed: {test_data} - {result} - {execution_time}s")
def generate_report(self):
"""Generate comprehensive test report"""
total_tests = len(self.results)
passed_tests = len([r for r in self.results if r['result'] == 'PASS'])
failed_tests = total_tests - passed_tests
report = f"""
Data-Driven Test Report
======================
Total Tests: {total_tests}
Passed: {passed_tests}
Failed: {failed_tests}
Pass Rate: {(passed_tests/total_tests)*100:.2f}%
"""
return report
Data-driven testing with Selenium WebDriver provides a scalable approach to test automation that improves test coverage while reducing maintenance overhead. By separating test logic from test data, you can easily add new test scenarios without modifying your automation code. Whether you choose CSV files, JSON, Excel, or database sources, the key is to maintain clean, organized data that supports your testing objectives and integrates well with your overall test automation strategy.