Que Es el Testing Data-Driven?
El testing data-driven separa la logica del test de los datos del test. En vez de escribir un test separado para cada combinacion de entrada, escribes un test que se ejecuta multiples veces con diferentes conjuntos de datos.
Sin enfoque data-driven (5 tests separados):
test('login con credenciales admin', async ({ page }) => {
await loginPage.login('admin@test.com', 'AdminPass1');
await expect(page).toHaveURL('/dashboard');
});
test('login con credenciales editor', async ({ page }) => {
await loginPage.login('editor@test.com', 'EditorPass1');
await expect(page).toHaveURL('/dashboard');
});
// ... 3 tests mas casi identicos
Con enfoque data-driven (1 test, 5 conjuntos de datos):
const validUsers = [
{ email: 'admin@test.com', password: 'AdminPass1', role: 'admin' },
{ email: 'editor@test.com', password: 'EditorPass1', role: 'editor' },
{ email: 'viewer@test.com', password: 'ViewerPass1', role: 'viewer' },
{ email: 'manager@test.com', password: 'MgrPass1', role: 'manager' },
{ email: 'support@test.com', password: 'SupportPass1', role: 'support' },
];
for (const user of validUsers) {
test(`login con credenciales de ${user.role}`, async ({ page }) => {
await loginPage.login(user.email, user.password);
await expect(page).toHaveURL('/dashboard');
});
}
Testing Data-Driven en Playwright
Usando test.describe y Loops
const loginScenarios = [
{ email: 'admin@test.com', password: 'valid', expectSuccess: true },
{ email: 'admin@test.com', password: 'wrong', expectSuccess: false },
{ email: '', password: 'valid', expectSuccess: false },
{ email: 'invalid-format', password: 'valid', expectSuccess: false },
{ email: 'locked@test.com', password: 'valid', expectSuccess: false },
];
test.describe('Escenarios de login', () => {
for (const scenario of loginScenarios) {
test(`login con email="${scenario.email}" debe ${scenario.expectSuccess ? 'tener exito' : 'fallar'}`, async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(scenario.email, scenario.password);
if (scenario.expectSuccess) {
await expect(page).toHaveURL('/dashboard');
} else {
await expect(loginPage.errorMessage).toBeVisible();
}
});
}
});
Usando Archivos JSON Externos
{
"validUsers": [
{ "email": "admin@test.com", "password": "AdminPass1", "role": "admin" },
{ "email": "editor@test.com", "password": "EditorPass1", "role": "editor" }
],
"invalidCredentials": [
{ "email": "wrong@test.com", "password": "wrong", "error": "Credenciales invalidas" },
{ "email": "", "password": "", "error": "Email es requerido" }
]
}
import testData from './test-data/users.json';
test.describe('Login valido', () => {
for (const user of testData.validUsers) {
test(`${user.role} puede hacer login`, async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login(user.email, user.password);
await expect(page).toHaveURL('/dashboard');
});
}
});
Usando Archivos CSV
import fs from 'fs';
function loadCSV(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.trim().split('\n');
const headers = lines[0].split(',');
return lines.slice(1).map(line => {
const values = line.split(',');
return headers.reduce((obj, header, i) => {
obj[header.trim()] = values[i].trim();
return obj;
}, {});
});
}
const priceData = loadCSV('test-data/prices.csv');
for (const row of priceData) {
test(`producto ${row.name} tiene precio ${row.expectedPrice}`, async ({ page }) => {
await page.goto(`/products/${row.slug}`);
const price = await page.textContent('.price');
expect(price).toBe(row.expectedPrice);
});
}
Patrones Comunes Data-Driven
Testing de Valores Limite
const ageValidation = [
{ age: -1, valid: false, desc: 'negativo' },
{ age: 0, valid: false, desc: 'cero' },
{ age: 1, valid: true, desc: 'minimo valido' },
{ age: 17, valid: false, desc: 'menor de edad' },
{ age: 18, valid: true, desc: 'exactamente edad minima' },
{ age: 120, valid: true, desc: 'maximo valido' },
{ age: 121, valid: false, desc: 'sobre maximo' },
];
for (const { age, valid, desc } of ageValidation) {
test(`edad ${age} (${desc}) debe ser ${valid ? 'aceptada' : 'rechazada'}`, async ({ page }) => {
await registrationPage.enterAge(age);
await registrationPage.submit();
if (valid) {
await expect(registrationPage.successMessage).toBeVisible();
} else {
await expect(registrationPage.errorMessage).toBeVisible();
}
});
}
Datos Cross-Viewport
const viewports = [
{ width: 375, height: 667, name: 'iPhone SE' },
{ width: 768, height: 1024, name: 'iPad' },
{ width: 1920, height: 1080, name: 'Desktop' },
];
for (const viewport of viewports) {
test(`homepage renderiza correctamente en ${viewport.name}`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.goto('/');
await expect(page.locator('.hero')).toBeVisible();
});
}
Datos Basados en Entorno
Usa variables de entorno para cambiar conjuntos de datos entre ambientes:
const environments = {
dev: {
baseUrl: 'https://dev.example.com',
adminEmail: 'admin@dev.test.com',
adminPassword: 'DevPass123',
},
staging: {
baseUrl: 'https://staging.example.com',
adminEmail: 'admin@staging.test.com',
adminPassword: 'StagingPass123',
},
};
const env = environments[process.env.TEST_ENV || 'dev'];
test('admin puede acceder al dashboard', async ({ page }) => {
await page.goto(env.baseUrl);
await loginPage.login(env.adminEmail, env.adminPassword);
await expect(page).toHaveURL(`${env.baseUrl}/dashboard`);
});
Evitando la Explosion Combinatoria
Con multiples parametros, las combinaciones crecen exponencialmente. Usa testing pairwise para reducirlas manteniendo cobertura.
El Problema
5 campos x 10 valores cada uno = 100,000 combinaciones. Probar todas es impractico.
La Solucion: Testing Pairwise
En vez de todas las combinaciones, prueba cada par de valores al menos una vez. Tipicamente requiere solo 50-100 test cases.
const checkoutData = [
{ payment: 'visa', shipping: 'standard', currency: 'USD' },
{ payment: 'visa', shipping: 'express', currency: 'EUR' },
{ payment: 'mastercard', shipping: 'standard', currency: 'EUR' },
{ payment: 'mastercard', shipping: 'express', currency: 'USD' },
{ payment: 'paypal', shipping: 'standard', currency: 'USD' },
{ payment: 'paypal', shipping: 'express', currency: 'EUR' },
];
Fabricas de Datos de Test
Para datos complejos, usa funciones factory:
function createOrder(overrides = {}) {
return {
product: 'Widget',
quantity: 1,
price: 29.99,
currency: 'USD',
shipping: 'standard',
...overrides,
};
}
const orders = [
createOrder({ quantity: 1, shipping: 'standard' }),
createOrder({ quantity: 100, shipping: 'express' }),
createOrder({ quantity: 0, price: 0 }),
createOrder({ currency: 'EUR', price: 24.99 }),
];
Ejercicio: Construye Tests Data-Driven
Crea un suite data-driven para un formulario de registro con campos: nombre, email, password, edad, pais.
- Crea un archivo JSON con 15+ test cases cubriendo entradas validas, valores limite e invalidos
- Escribe un test parametrizado que lea del archivo JSON
- Incluye al menos 3 tests de valores limite para edad
- Incluye al menos 3 tests negativos para validacion de email
- Agrega testing cross-viewport para 3 tamanos de pantalla
Puntos Clave
- Testing data-driven elimina duplicacion parametrizando entradas
- Fuentes externas (JSON, CSV) permiten mantener datos independientemente
- Usa analisis de valores limite para crear conjuntos de datos significativos
- Cuidado con la explosion combinatoria — usa testing pairwise
- Las funciones factory crean datos flexibles y legibles
- Variables de entorno cambian datos entre dev, staging y produccion