Spread y rest usan la misma sintaxis ... pero hacen lo opuesto: spread expande un array u objeto en el lugar donde aparece, rest agrupa múltiples valores en uno. El beneficio concreto en automatización de pruebas son las fábricas de datos: un objeto base con spread y sobrescrituras genera cada variante sin repetir todos los campos. Esta guía cubre los dos operadores con los patrones que aparecen en configs de Playwright, definiciones de fixtures y headers de requests, más la trampa de la copia superficial que corrompe datos de prueba cuando los objetos anidados se comparten entre variantes.
El operador spread: expandir cosas
El operador spread (...) expande un iterable (array u objeto) en el lugar donde se usa.
Spread en arrays
const parte1 = [1, 2, 3];
const parte2 = [4, 5, 6];
const combinado = [...parte1, ...parte2];
// [1, 2, 3, 4, 5, 6]
// Agregar elementos antes o después
const conExtra = [0, ...parte1, ...parte2, 7];
// [0, 1, 2, 3, 4, 5, 6, 7]Copiar un array
const original = ['alice', 'bob', 'charlie'];
const copia = [...original];
copia.push('dave'); // Solo modifica la copia
console.log(original); // ['alice', 'bob', 'charlie'] — sin cambiosSpread en objetos
const configBase = { timeout: 30000, headless: true };
const configCI = { ...configBase, timeout: 60000 };
// { timeout: 60000, headless: true }
// timeout fue sobreescrito por el valor posteriorLa clave que aparece después es la que gana cuando hay conflicto.
Spread en datos de prueba: el caso de uso principal
El patrón más valioso para ingenieros QA: crear variantes de objeto desde una base:
const usuarioBase = {
email: 'test@example.com',
password: 'PassValida1',
role: 'member',
isActive: true,
emailVerificado: true,
};
// Crear variantes sin repetir todos los campos
const usuarioAdmin = { ...usuarioBase, role: 'admin' };
const usuarioInactivo = { ...usuarioBase, isActive: false };
const sinVerificar = { ...usuarioBase, emailVerificado: false };
const emailCustom = { ...usuarioBase, email: 'custom@test.com' };Esta es la base de las fábricas de datos de prueba. Defines el estado del camino feliz una vez y creas todos los casos borde a partir de él.
Combinar configuraciones de tests
const configPlaywrightBase = {
use: {
baseURL: 'https://lab.becomeqa.com',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
timeout: 30_000,
};
// Sobrescrituras para el entorno de CI
const sobrescritasCI = {
timeout: 60_000,
retries: 2,
};
const configFinal = { ...configPlaywrightBase, ...sobrescritasCI };Construir listas de casos de prueba
const casoExitoso = {
email: 'valido@example.com',
password: 'PassValida1',
resultadoEsperado: 'success',
statusEsperado: 200,
};
const casosDePrueba = [
casoExitoso,
{ ...casoExitoso, email: 'otro@test.com' },
{ ...casoExitoso, password: 'OtraPassValida2' },
{ ...casoExitoso, email: 'no-valido', resultadoEsperado: 'error', statusEsperado: 422 },
];Mucho más limpio que repetir todos los campos en cada caso.
Parámetros rest: agrupar cosas
Los parámetros rest agrupan múltiples elementos en un solo parámetro. Usa la misma sintaxis ... pero en la definición de la función, no en la llamada:
// REST: agrupa múltiples argumentos en un array
function logResultadosDePrueba(nombreTest: string, ...resultados: string[]) {
console.log(`Test: ${nombreTest}`);
resultados.forEach(r => console.log(` - ${r}`));
}
logResultadosDePrueba('Test de login', 'email validado', 'redirección funcionó', 'sesión creada');
// Test: Test de login
// - email validado
// - redirección funcionó
// - sesión creadaEl ...resultados agrupa todos los argumentos después de nombreTest en un array.
Rest en destructuring
Rest también funciona en destructuring para capturar "todo lo demás":
const [primero, segundo, ...resto] = [1, 2, 3, 4, 5];
// primero = 1, segundo = 2, resto = [3, 4, 5]
const { email, password, ...otrosCampos } = usuario;
// email y password se extraen
// otrosCampos = { role: 'member', isActive: true, ... }Es útil cuando necesitas campos específicos y quieres pasar el resto:
async function crearUsuarioYObtenerToken({ email, password, ...datosPerfil }: DatosCreacionUsuario) {
// Usar email y password para autenticación
const token = await auth.login(email, password);
// Usar el resto para configurar el perfil (sin los campos de auth)
await api.actualizarPerfil(token, datosPerfil);
return token;
}Patrones prácticos en Playwright
Patrón 1: Helper flexible para tests
type OpcionesClick = {
timeout?: number;
force?: boolean;
};
async function clickYEsperar(
page: Page,
selector: string,
{ timeout = 5000, force = false, ...opciones }: OpcionesClick = {}
) {
await page.locator(selector).click({ timeout, force, ...opciones });
await page.waitForLoadState('networkidle');
}Patrón 2: Construir headers de requests
const headersBase = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
const headersConAuth = {
...headersBase,
'Authorization': `Bearer ${token}`,
};
const headersAdmin = {
...headersConAuth,
'X-Admin-Key': process.env.ADMIN_KEY,
};Patrón 3: Combinar datos de prueba de múltiples fuentes
const datosBase = await leerArchivoFixture('usuario-base.json');
const especificoEntorno = await leerArchivoFixture(`${process.env.ENV}-sobrescrituras.json`);
const datosDePrueba = { ...datosBase, ...especificoEntorno };
// Los valores específicos del entorno sobreescriben los de basePatrón 4: Recolectar casos fallidos
async function ejecutarTodosLosCasos(casos: CasoDePrueba[]): Promise<string[]> {
const fallos: string[] = [];
for (const caso of casos) {
try {
await ejecutarCaso(caso);
} catch (error) {
fallos.push(`${caso.nombre}: ${error.message}`);
}
}
return fallos;
}
// En el punto de llamada:
const [primerFallo, ...otrosFallos] = await ejecutarTodosLosCasos(casosDePrueba);
if (primerFallo) {
console.log('Primer fallo:', primerFallo);
console.log('Fallos adicionales:', otrosFallos);
}Spread vs. rest: cómo distinguirlos
Spread: estás poniendo cosas adentro (expandiendo en un literal de array u objeto):const arr = [...items]; // expandiendo items en el array
const obj = { ...config }; // expandiendo config en el objeto
func(...args); // expandiendo args como argumentos de funciónfunction fn(...args) {} // agrupando argumentos de llamada
const [a, ...resto] = array; // agrupando el resto del array
const { x, ...otros } = obj; // agrupando el resto del objetoEl mismo símbolo .... El contexto determina el significado.
El error común: copia superficial
Spread crea una copia superficial. Los objetos anidados siguen siendo referencias compartidas:
const usuario = { nombre: 'Alice', direccion: { ciudad: 'Buenos Aires' } };
const copia = { ...usuario };
copia.nombre = 'Bob'; // Solo cambia la copia
copia.direccion.ciudad = 'Córdoba'; // Cambia AMBOS: usuario y copia
console.log(usuario.direccion.ciudad); // 'Córdoba' — no era lo esperadoSi necesitás una copia profunda (los objetos anidados también son independientes), necesitás otro enfoque:
// Copia profunda simple (funciona para datos serializables en JSON)
const copiaProfunda = JSON.parse(JSON.stringify(usuario));
// O structuredClone (JS moderno)
const copiaProfunda2 = structuredClone(usuario);Para la mayoría de los objetos de datos de prueba simples sin objetos anidados mutables, spread alcanza. Pero conoce la limitación.
Resumen
| Sintaxis | Contexto | Qué hace |
|----------|----------|---------|
| [...arr] | Literal de array | Expande arr en el nuevo array |
| {...obj} | Literal de objeto | Copia todas las propiedades de obj |
| fn(...args) | Llamada a función | Pasa elementos del array como argumentos separados |
| function fn(...params) | Definición de función | Agrupa múltiples argumentos en un array |
| const [a, ...resto] = arr | Destructuring de array | Agrupa los elementos restantes |
| const {x, ...resto} = obj | Destructuring de objeto | Agrupa las propiedades restantes |