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 cambios

Spread en objetos

const configBase = { timeout: 30000, headless: true };
const configCI = { ...configBase, timeout: 60000 };
// { timeout: 60000, headless: true }
// timeout fue sobreescrito por el valor posterior

La 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 creada

El ...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 base

Patró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ón

Rest: estás sacando cosas afuera (agrupando en un parámetro):

function fn(...args) {}          // agrupando argumentos de llamada
const [a, ...resto] = array;    // agrupando el resto del array
const { x, ...otros } = obj;    // agrupando el resto del objeto

El 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 esperado

Si 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 |

→ See also: Objetos JavaScript y Desestructuración para Ingenieros QA | JavaScript para QA Engineers: El Mínimo que Necesitas para Empezar a Automatizar | TypeScript para QA: Por Qué los Tipos Estáticos Mejoran tus Tests