Extraer texto de una lista de elementos de la página, filtrar una respuesta de API por estado y construir conjuntos de datos de prueba usan los mismos cuatro métodos de JavaScript: map, filter, find y forEach. El error más común en Playwright específicamente es llamar a forEach con un callback async: dispara promesas sin esperar, así que las aserciones se ejecutan antes de que lleguen los datos. Esta guía cubre cada método con los patrones que aparecen en suites de tests reales, incluyendo cómo Promise.all con map reemplaza a forEach para el trabajo asíncrono con elementos.

Arrays: un repaso de un minuto

Un array es una lista ordenada de valores:

const usuarios = ['Alice', 'Bob', 'Charlie'];
const precios = [12.99, 5.50, 29.00, 8.75];
const casosDePrueba = [
  { input: 'valido@email.com', debepasar: true },
  { input: 'no-es-un-email',  debepasar: false },
  { input: '',                debepasar: false },
];

Los elementos se acceden por índice (comenzando en 0):

console.log(usuarios[0]);     // 'Alice'
console.log(usuarios[2]);     // 'Charlie'
console.log(usuarios.length); // 3

forEach: hacer algo con cada ítem

forEach ejecuta una función una vez por cada ítem del array. Úsalo cuando quieres hacer algo con cada ítem pero no necesitas recibir un resultado de vuelta.

const nombres = ['alice', 'bob', 'charlie'];

nombres.forEach((nombre) => {
  console.log(`Testeando usuario: ${nombre}`);
});

// Salida:
// Testeando usuario: alice
// Testeando usuario: bob
// Testeando usuario: charlie

En tests de Playwright

const urlsLogin = [
  '/en/login',
  '/ru/login',
  '/es/login',
];

loginUrls.forEach(async (url) => {
  await page.goto(url);
  await expect(page.locator('h1')).toBeVisible();
});

Cuándo usar forEach: cuando querés efectos secundarios: logging, hacer clics, completar formularios. No lo uses cuando necesitás transformar o filtrar datos. Para eso hay una herramienta mejor.

map: transformar cada ítem

map crea un nuevo array aplicando una función a cada ítem. El array original no cambia.

Piénsalo así: "por cada ítem, devuélveme algo diferente."

const precios = [10, 20, 30];
const conDescuento = precios.map((precio) => precio * 0.9);

console.log(conDescuento); // [9, 18, 27]
console.log(precios);      // [10, 20, 30]  ← sin cambios

Ejemplo práctico: extraer texto de una lista de elementos

A menudo obtienes un array de locators de Playwright y necesitas extraer el texto de cada uno:

// Obtener todos los textos de celdas de tabla
const celdas = await page.locator('table tbody td').all();
const textos = await Promise.all(
  celdas.map((celda) => celda.textContent())
);
// textos = ['Alice', '28', 'Admin', 'Bob', '34', 'User', ...]

Construir datos de prueba con map

const idsUsuario = [1, 2, 3, 4, 5];

const usuariosDePrueba = idsUsuario.map((id) => ({
  id,
  username: `user_${id}`,
  email: `user${id}@test.com`,
  rol: id === 1 ? 'admin' : 'member',
}));

/*
[
  { id: 1, username: 'user_1', email: 'user1@test.com', rol: 'admin' },
  { id: 2, username: 'user_2', email: 'user2@test.com', rol: 'member' },
  ...
]
*/

Transformar datos de respuesta de API

// La API devuelve datos crudos
const usuariosApi = [
  { first_name: 'Alice', last_name: 'Smith', is_active: 1 },
  { first_name: 'Bob',   last_name: 'Jones', is_active: 0 },
];

// Transformarlos para que coincidan con lo que muestra la UI
const usuariosMostrados = usuariosApi.map((u) => ({
  nombre: `${u.first_name} ${u.last_name}`,
  activo: u.is_active === 1,
}));

// [{ nombre: 'Alice Smith', activo: true }, { nombre: 'Bob Jones', activo: false }]

Cuándo usar map: cuando quieres transformar cada ítem de un array en algo diferente. El resultado siempre tiene la misma longitud que la entrada.

filter: quedarte solo con lo que necesitás

filter crea un nuevo array que contiene solo los ítems que pasan una condición. Los que no la pasan se eliminan.

const precios = [5, 12, 3, 25, 8, 40, 1];
const caros = precios.filter((precio) => precio > 10);

console.log(caros);   // [12, 25, 40]
console.log(precios); // [5, 12, 3, 25, 8, 40, 1]  ← sin cambios

Filtrar casos de prueba

Acá es donde filter brilla en la automatización de tests. Cuando tienes una lista grande de casos de prueba, puedes dividirlos:

const todosCasos = [
  { input: 'valido@email.com',   debepasar: true },
  { input: 'otro@test.org',      debepasar: true },
  { input: 'no-es-un-email',     debepasar: false },
  { input: '',                   debepasar: false },
  { input: 'falta@',             debepasar: false },
];

const casosValidos   = todosCasos.filter((tc) => tc.debepasar);
const casosInvalidos = todosCasos.filter((tc) => !tc.debepasar);

// casosValidos tiene 2 ítems, casosInvalidos tiene 3

Filtrar resultados de API

// Todas las órdenes de la API
const ordenes = [
  { id: 1, estado: 'completada', monto: 99 },
  { id: 2, estado: 'pendiente',  monto: 45 },
  { id: 3, estado: 'completada', monto: 12 },
  { id: 4, estado: 'cancelada',  monto: 75 },
];

// Quedarse solo con las órdenes completadas para verificación
const ordenesCompletadas = ordenes.filter((o) => o.estado === 'completada');
// [{ id: 1, ... }, { id: 3, ... }]

Filtrar elementos de la página por texto

// Todas las filas de una tabla
const filas = await page.locator('table tbody tr').all();

// Solo las filas que contienen 'Admin'
const filasAdmin = [];
for (const fila of filas) {
  const texto = await fila.textContent();
  if (texto?.includes('Admin')) {
    filasAdmin.push(fila);
  }
}

Cuándo usar filter: cuando quieres un subconjunto de un array. El resultado puede ser más corto que la entrada, o incluso vacío.

find: obtener la primera coincidencia

find devuelve el primer ítem que pasa una condición. Si nada coincide, devuelve undefined. A diferencia de filter, deja de buscar en cuanto encuentra una coincidencia.

const usuarios = [
  { id: 1, nombre: 'Alice', rol: 'admin' },
  { id: 2, nombre: 'Bob',   rol: 'member' },
  { id: 3, nombre: 'Carol', rol: 'admin' },
];

const primerAdmin = usuarios.find((u) => u.rol === 'admin');
// { id: 1, nombre: 'Alice', rol: 'admin' }

const usuarioInexistente = usuarios.find((u) => u.nombre === 'Dave');
// undefined

Encontrar datos de prueba por ID

const productos = [
  { id: 'PROD-001', nombre: 'Laptop', precio: 999 },
  { id: 'PROD-002', nombre: 'Mouse',  precio: 29  },
  { id: 'PROD-003', nombre: 'Escritorio', precio: 349 },
];

const productoObjetivo = productos.find((p) => p.id === 'PROD-002');
// { id: 'PROD-002', nombre: 'Mouse', precio: 29 }

Siempre verificar si es undefined

const producto = productos.find((p) => p.id === 'PROD-999');

if (!producto) {
  throw new Error('Datos de prueba no encontrados: PROD-999');
}

// Ahora es seguro usar producto
await page.fill('[data-testid="buscar"]', producto.nombre);

Cuándo usar find: cuando necesitas exactamente un ítem de un array y sabes qué buscas. Si necesitas todas las coincidencias, usa filter.

Combinarlos

El poder real viene de encadenarlos:

const ordenesApi = [
  { id: 1, usuario: 'alice', estado: 'completada', monto: 150, items: 3 },
  { id: 2, usuario: 'bob',   estado: 'pendiente',  monto: 50,  items: 1 },
  { id: 3, usuario: 'alice', estado: 'completada', monto: 200, items: 5 },
  { id: 4, usuario: 'carol', estado: 'cancelada',  monto: 75,  items: 2 },
  { id: 5, usuario: 'alice', estado: 'pendiente',  monto: 30,  items: 1 },
];

// Obtener el monto total de las órdenes completadas de Alice
const totalAlice = ordenesApi
  .filter((o) => o.usuario === 'alice' && o.estado === 'completada')
  .map((o) => o.monto)
  .reduce((suma, monto) => suma + monto, 0);

// 150 + 200 = 350

O para verificación:

// Verificar que todos los precios de productos visibles son mayores a cero
const locatorsPrecios = await page.locator('[data-testid="precio-producto"]').all();
const precios = await Promise.all(
  locatorsPrecios.map(async (el) => {
    const texto = await el.textContent();
    return parseFloat(texto?.replace('$', '') ?? '0');
  })
);

const tienePrecioNegativo = precios.some((p) => p <= 0);
expect(tienePrecioNegativo).toBe(false);

Referencia rápida

| Método | Qué devuelve | Usalo cuando |

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

| forEach | Nada (undefined) | Querés efectos secundarios (logging, acciones) |

| map | Nuevo array, misma longitud | Querés transformar cada ítem |

| filter | Nuevo array, igual o más corto | Querés un subconjunto de ítems |

| find | Un ítem o undefined | Querés la primera coincidencia |

Errores comunes

Usar map cuando quieres forEach

// Mal: map es para devolver valores, no para efectos secundarios
usuarios.map((usuario) => {
  console.log(usuario.nombre); // funciona pero es innecesario
});

// Bien
usuarios.forEach((usuario) => {
  console.log(usuario.nombre);
});

Olvidar que find puede devolver undefined

// Esto va a fallar si no se encuentra el usuario
const usuario = usuarios.find((u) => u.id === 99);
usuario.nombre; // TypeError: Cannot read properties of undefined

Mutar el array original con map

Si haces usuarios.map((u) => { u.rol = 'admin'; return u; }), mutaste los objetos originales, no solo creaste nuevos. Crea nuevos objetos en su lugar:

// Bien: crea nuevos objetos
usuarios.map((u) => ({ ...u, rol: 'admin' }));

Nota sobre async en Playwright

Cuando el callback es async (lo que pasa constantemente en Playwright), envuelve con Promise.all:

// Esto no funciona: forEach ignora las promesas
celdas.forEach(async (celda) => {
  const texto = await celda.textContent();  // se dispara y se olvida
});

// Esto funciona: espera todas las promesas
const textos = await Promise.all(
  celdas.map(async (celda) => await celda.textContent())
);

Este es el error de "async en arrays" más común en Playwright. Ante la duda, usa Promise.all con map.

→ See also: JavaScript para QA Engineers: El Mínimo que Necesitas para Empezar a Automatizar | Async/Await en Español Sencillo (para Testers que se Confunden con las Promesas) | Objetos JavaScript y Desestructuración para Ingenieros QA