El { page, request } en cada función de test de Playwright es destructuring: extrae esas propiedades del objeto fixture que el framework pasa automáticamente. Una vez que eso queda claro, los fixtures personalizados dejan de parecer magia y empiezan a verse como el mismo patrón repetido. Esta guía cubre el acceso a objetos, el destructuring con renombrado y valores por defecto, el destructuring anidado para respuestas de API, y el comportamiento de copia superficial del spread que hace que las mutaciones en datos de prueba se propaguen entre variantes.

Qué es un objeto

Un objeto es una colección de pares clave-valor. Las claves son strings; los valores pueden ser cualquier cosa:

const user = {
  id: 1,
  email: 'alice@example.com',
  role: 'admin',
  isActive: true,
};

Accedes a los valores con notación de punto o de corchetes:

console.log(user.email);     // 'alice@example.com'
console.log(user['role']);   // 'admin'
console.log(user.id);       // 1

Modificas valores de la misma forma:

user.email = 'nuevaalice@example.com';
user.role = 'member';

Objetos en datos de prueba

La mayoría de los datos de prueba en tests de Playwright se expresan como objetos o arrays de objetos:

const loginCredentials = {
  email: 'qa_test@example.com',
  password: 'PassValida123!',
};

await page.fill('[data-testid="email"]', loginCredentials.email);
await page.fill('[data-testid="password"]', loginCredentials.password);

Colecciones de casos de prueba:

const EMAILS_INVALIDOS = [
  { input: '',              descripcion: 'vacío' },
  { input: 'no-es-email',  descripcion: 'falta el @' },
  { input: 'falta@',       descripcion: 'falta el dominio' },
  { input: 'a@b',          descripcion: 'sin TLD' },
];

Cada EMAILS_INVALIDOS[0] es un objeto con las propiedades input y descripcion.

Destructuring: qué es

El destructuring permite extraer valores de un objeto (o array) en variables individuales en una sola línea, en lugar de múltiples líneas.

Sin destructuring

const user = { id: 1, name: 'Alice', email: 'alice@test.com', role: 'admin' };

const id    = user.id;
const name  = user.name;
const email = user.email;
const role  = user.role;

Con destructuring

const { id, name, email, role } = user;

El mismo resultado: cuatro variables con los mismos valores, pero en una línea en lugar de cuatro.

Renombrar al hacer destructuring

Si quieres que la variable tenga un nombre diferente a la clave:

const config = {
  database_host: 'localhost',
  database_port: 5432,
};

const { database_host: host, database_port: port } = config;

console.log(host); // 'localhost'
console.log(port); // 5432

Valores por defecto en el destructuring

Si una clave puede no existir, puedes proveer un valor por defecto:

const product = { name: 'Laptop', price: 999 };

const { name, price, discount = 0 } = product;
// discount = 0 (no estaba en product, usa el valor por defecto)

Destructuring en parámetros de funciones

El lugar más común donde verás destructuring en Playwright es en las funciones de test:

// Sin destructuring
test('el usuario puede iniciar sesión', async (args) => {
  const page = args.page;
  const request = args.request;
  // ...
});

// Con destructuring (patrón estándar de Playwright)
test('el usuario puede iniciar sesión', async ({ page, request }) => {
  // page y request disponibles directamente
});

Esto es destructuring de objetos en el parámetro de la función. La sintaxis { page, request } extrae esas propiedades del objeto fixture que Playwright pasa.

Por eso el código de tests de Playwright parece tener "variables mágicas": se están desestructurando del objeto fixture automáticamente.

Fixtures personalizados: destructuring en la práctica

Cuando creas fixtures personalizados, vas a escribir este patrón:

export const test = base.extend<{ loginPage: LoginPage }>({
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await use(loginPage);
  },
});

// Luego en un test:
test('el login funciona', async ({ page, loginPage }) => {
  //                              ^^^ desestructurado del objeto fixture
  await loginPage.login('user@test.com', 'pass');
});

Entender el destructuring hace que los fixtures tengan sentido.

Destructuring anidado

Los objetos pueden contener otros objetos. Podés desestructurar múltiples niveles a la vez:

const apiResponse = {
  status: 200,
  data: {
    user: {
      id: 123,
      email: 'alice@test.com',
    },
    token: 'eyJhbGciOiJIUzI1NiJ9...',
  },
};

// Destructuring anidado
const { status, data: { user: { id, email }, token } } = apiResponse;

console.log(status); // 200
console.log(id);     // 123
console.log(email);  // 'alice@test.com'
console.log(token);  // 'eyJhbGciOiJIUzI1NiJ9...'

Parece complejo al principio. En la práctica, raramente bajas más de 2 niveles. Si se complica, haz el destructuring en pasos:

const { data } = apiResponse;
const { user, token } = data;
const { id, email } = user;

El mismo resultado, más fácil de leer.

Destructuring de arrays (brevemente)

Los arrays usan [] en lugar de {}:

const coordenadas = [40.7128, -74.0060];

const [latitud, longitud] = coordenadas;
// latitud = 40.7128, longitud = -74.0060

Para saltear elementos, usa comas:

const [primero, , tercero] = ['a', 'b', 'c'];
// primero = 'a', tercero = 'c'

El operador spread con objetos

El operador spread (...) copia todas las propiedades de un objeto en otro:

const baseUser = {
  role: 'member',
  isActive: true,
};

const adminUser = {
  ...baseUser,       // copia role e isActive
  email: 'admin@test.com',
  role: 'admin',    // sobreescribe el valor del spread
};

// { role: 'admin', isActive: true, email: 'admin@test.com' }

En datos de prueba: crear variantes

const defaultUser = {
  email: 'test@example.com',
  password: 'PassValida1',
  role: 'member',
  isActive: true,
};

const adminUser    = { ...defaultUser, role: 'admin' };
const inactiveUser = { ...defaultUser, isActive: false };
const customEmail  = { ...defaultUser, email: 'custom@example.com' };

Este patrón, "objeto base + sobrescrituras", es muy común en las fábricas de datos de prueba. Definís el estado válido por defecto una vez y lo extendés con spread para crear variantes.

Patrones prácticos en tests de Playwright

Destructuring del cuerpo de respuesta de API

const response = await request.post('/api/users', {
  data: { email: 'nuevo@test.com', password: 'PassValida1' },
});

const { id, email, role, created_at } = await response.json();

expect(id).toBeTruthy();
expect(email).toBe('nuevo@test.com');
expect(role).toBe('member');

Pasar parámetros de objeto a helpers

async function fillLoginForm(page: Page, { email, password }: { email: string; password: string }) {
  await page.fill('[data-testid="email"]', email);
  await page.fill('[data-testid="password"]', password);
}

// En el punto de llamada:
await fillLoginForm(page, { email: 'user@test.com', password: 'pass' });

Combinar configuración de tests

const baseConfig = {
  baseURL: 'https://lab.becomeqa.com',
  timeout: 30000,
};

const ciConfig = {
  ...baseConfig,
  timeout: 60000,  // más lento en CI
  video: 'retain-on-failure',
};

Errores comunes

Hacer destructuring desde undefined

const response = await fetch('/api/user');
const { id } = await response.json(); // Si la respuesta es null/undefined, lanza un error

Siempre verifica que el valor exista antes de hacer destructuring en objetos profundamente anidados.

Modificar copias con spread no afecta al original (en el primer nivel)

const original = { name: 'Alice', data: { score: 100 } };
const copy = { ...original };
copy.name = 'Bob';           // original.name sigue siendo 'Alice'
copy.data.score = 200;       // original.data.score también cambia

El spread es una copia superficial. Los objetos anidados siguen siendo referencias compartidas.

Resumen

| Sintaxis | Qué hace |

|----------|---------|

| const { a, b } = obj | Extrae a y b de obj |

| const { a: x } = obj | Extrae a de obj, lo llama x |

| const { a = 5 } = obj | Extrae a, valor por defecto 5 si no existe |

| async ({ page, request }) => {} | Desestructura fixtures de Playwright |

| { ...obj, key: 'value' } | Extiende obj con spread, sobreescribe o agrega key |

→ See also: JavaScript para QA Engineers: El Mínimo que Necesitas para Empezar a Automatizar | Arrays JavaScript: map, filter, find y forEach — Guía de Campo para QA | TypeScript para QA: Por Qué los Tipos Estáticos Mejoran tus Tests