Extracting text from a list of page elements, filtering an API response by status, and building test data sets all use the same four JavaScript methods: map, filter, find, and forEach. The most common mistake in Playwright specifically is calling forEach with an async callback: it fires promises without waiting, so assertions run before data arrives. This guide covers each method with the patterns that appear in real test suites, including how Promise.all with map replaces forEach for async element work.
Arrays: a one-minute recap
An array is an ordered list of values:
const users = ['Alice', 'Bob', 'Charlie'];
const prices = [12.99, 5.50, 29.00, 8.75];
const testCases = [
{ input: 'valid@email.com', shouldPass: true },
{ input: 'not-an-email', shouldPass: false },
{ input: '', shouldPass: false },
];You access elements by index (starting at 0):
console.log(users[0]); // 'Alice'
console.log(users[2]); // 'Charlie'
console.log(users.length); // 3Everything else in this guide builds on top of this.
forEach: do something with each item
forEach runs a function once for every item in the array. Use it when you want to do something with each item but don't need a result back.
const usernames = ['alice', 'bob', 'charlie'];
usernames.forEach((name) => {
console.log(`Testing user: ${name}`);
});
// Output:
// Testing user: alice
// Testing user: bob
// Testing user: charlieIn Playwright tests
const loginUrls = [
'/en/login',
'/ru/login',
'/es/login',
];
// Check that all locale login pages load (not a great pattern,
// but shows forEach in action)
loginUrls.forEach(async (url) => {
await page.goto(url);
await expect(page.locator('h1')).toBeVisible();
});map: transform every item
map creates a new array by applying a function to every item. The original array doesn't change.
Think of it as: "for each item, give me back something different."
const prices = [10, 20, 30];
const discounted = prices.map((price) => price * 0.9);
console.log(discounted); // [9, 18, 27]
console.log(prices); // [10, 20, 30] ← unchangedPractical example: extracting text from a list of elements
You often get back an array of locators from Playwright and need to extract the text from each one:
// Get all table cell texts
const cells = await page.locator('table tbody td').all();
const texts = await Promise.all(
cells.map((cell) => cell.textContent())
);
// texts = ['Alice', '28', 'Admin', 'Bob', '34', 'User', ...]Building test data with map
const userIds = [1, 2, 3, 4, 5];
const testUsers = userIds.map((id) => ({
id,
username: `user_${id}`,
email: `user${id}@test.com`,
role: id === 1 ? 'admin' : 'member',
}));
/*
[
{ id: 1, username: 'user_1', email: 'user1@test.com', role: 'admin' },
{ id: 2, username: 'user_2', email: 'user2@test.com', role: 'member' },
...
]
*/Transforming API response data
// API returns raw data
const apiUsers = [
{ first_name: 'Alice', last_name: 'Smith', is_active: 1 },
{ first_name: 'Bob', last_name: 'Jones', is_active: 0 },
];
// Transform it to match what your UI shows
const displayUsers = apiUsers.map((u) => ({
name: `${u.first_name} ${u.last_name}`,
active: u.is_active === 1,
}));
// [{ name: 'Alice Smith', active: true }, { name: 'Bob Jones', active: false }]filter: keep only what you need
filter creates a new array containing only the items that pass a test. Items that fail the test are dropped.
const prices = [5, 12, 3, 25, 8, 40, 1];
const expensive = prices.filter((price) => price > 10);
console.log(expensive); // [12, 25, 40]
console.log(prices); // [5, 12, 3, 25, 8, 40, 1] ← unchangedFiltering test cases
This is where filter shines in test automation. When you have a big list of test cases, you can split them:
const allTestCases = [
{ input: 'valid@email.com', shouldPass: true },
{ input: 'another@test.org', shouldPass: true },
{ input: 'not-an-email', shouldPass: false },
{ input: '', shouldPass: false },
{ input: 'missing@', shouldPass: false },
];
const validCases = allTestCases.filter((tc) => tc.shouldPass);
const invalidCases = allTestCases.filter((tc) => !tc.shouldPass);
// validCases has 2 items, invalidCases has 3Filtering API results
// All orders from API
const orders = [
{ id: 1, status: 'completed', amount: 99 },
{ id: 2, status: 'pending', amount: 45 },
{ id: 3, status: 'completed', amount: 12 },
{ id: 4, status: 'cancelled', amount: 75 },
];
// Keep only completed orders for verification
const completedOrders = orders.filter((o) => o.status === 'completed');
// [{ id: 1, ... }, { id: 3, ... }]Filtering page elements by text
// All rows in a table
const rows = await page.locator('table tbody tr').all();
// Only rows that contain 'Admin'
const adminRows = [];
for (const row of rows) {
const text = await row.textContent();
if (text?.includes('Admin')) {
adminRows.push(row);
}
}find: get the first match
find returns the first item that passes a test. If nothing matches, it returns undefined. Unlike filter, it stops searching as soon as it finds a match.
const users = [
{ id: 1, name: 'Alice', role: 'admin' },
{ id: 2, name: 'Bob', role: 'member' },
{ id: 3, name: 'Carol', role: 'admin' },
];
const firstAdmin = users.find((u) => u.role === 'admin');
// { id: 1, name: 'Alice', role: 'admin' }
const missingUser = users.find((u) => u.name === 'Dave');
// undefinedFinding test data by ID
const products = [
{ id: 'PROD-001', name: 'Laptop', price: 999 },
{ id: 'PROD-002', name: 'Mouse', price: 29 },
{ id: 'PROD-003', name: 'Desk', price: 349 },
];
const targetProduct = products.find((p) => p.id === 'PROD-002');
// { id: 'PROD-002', name: 'Mouse', price: 29 }Always check for undefined
const product = products.find((p) => p.id === 'PROD-999');
if (!product) {
throw new Error('Test data not found: PROD-999');
}
// Now safe to use product
await page.fill('[data-testid="search"]', product.name);filter.
Combining them
The real power comes from chaining these together:
const apiOrders = [
{ id: 1, user: 'alice', status: 'completed', amount: 150, items: 3 },
{ id: 2, user: 'bob', status: 'pending', amount: 50, items: 1 },
{ id: 3, user: 'alice', status: 'completed', amount: 200, items: 5 },
{ id: 4, user: 'carol', status: 'cancelled', amount: 75, items: 2 },
{ id: 5, user: 'alice', status: 'pending', amount: 30, items: 1 },
];
// Get the total amount of Alice's completed orders
const aliceCompletedTotal = apiOrders
.filter((o) => o.user === 'alice' && o.status === 'completed')
.map((o) => o.amount)
.reduce((sum, amount) => sum + amount, 0);
// 150 + 200 = 350Or for verification:
// Verify that all visible product prices are above zero
const priceLocators = await page.locator('[data-testid="product-price"]').all();
const prices = await Promise.all(
priceLocators.map(async (el) => {
const text = await el.textContent();
return parseFloat(text?.replace('$', '') ?? '0');
})
);
const hasNegativePrice = prices.some((p) => p <= 0);
expect(hasNegativePrice).toBe(false);Quick reference
| Method | What it returns | Use when |
|--------|----------------|----------|
| forEach | Nothing (undefined) | You want side effects (logging, actions) |
| map | New array, same length | You want to transform each item |
| filter | New array, shorter or equal | You want a subset of items |
| find | One item or undefined | You want the first match |
Common mistakes
Usingmap when you want forEach:
// Wrong — map is for returning values, not side effects
users.map((user) => {
console.log(user.name); // works but wasteful
});
// Right
users.forEach((user) => {
console.log(user.name);
});find can return undefined:
// This will throw if user is not found
const user = users.find((u) => u.id === 99);
user.name; // TypeError: Cannot read properties of undefinedmap:
If you do users.map((u) => { u.role = 'admin'; return u; }), you've mutated the original objects, not just created new ones. Create new objects instead:
// Right — creates new objects
users.map((u) => ({ ...u, role: 'admin' }));Async note for Playwright
When your callback is async (which happens constantly in Playwright), wrap with Promise.all:
// This doesn't work — forEach ignores promises
cells.forEach(async (cell) => {
const text = await cell.textContent(); // ⚠️ fire-and-forget
});
// This works — waits for all promises
const texts = await Promise.all(
cells.map(async (cell) => await cell.textContent())
);This is the most common "async in arrays" gotcha in Playwright. When in doubt, use Promise.all with map.
You now have the core array methods used in real Playwright test suites. map, filter, find, and forEach cover 90% of what you'll need when working with test data, API responses, and arrays of page elements.