Большинство задач с тест-данными решается четырьмя методами JavaScript: map, filter, find и forEach. Они покрывают извлечение текста из элементов страницы, фильтрацию API-ответов и построение наборов тестовых данных. Самая частая ошибка в Playwright: вызов forEach с async-колбэком. Он запускает промисы без ожидания, и проверки выполняются раньше чем приходят данные. В этом гайде: каждый метод с паттернами из реальных тест-сьютов, включая как Promise.all с map заменяет forEach при асинхронной работе с элементами.
Массивы: краткое напоминание
Массив хранит упорядоченный список значений:
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 },
];Доступ к элементам по индексу (начиная с 0):
console.log(users[0]); // 'Alice'
console.log(users[2]); // 'Charlie'
console.log(users.length); // 3Всё остальное в этом гайде строится на этом.
forEach: сделать что-то с каждым элементом
forEach запускает функцию один раз для каждого элемента массива. Используй когда хочешь что-то сделать с каждым элементом, но результат не нужен.
const usernames = ['alice', 'bob', 'charlie'];
usernames.forEach((name) => {
console.log(`Testing user: ${name}`);
});
// Вывод:
// Testing user: alice
// Testing user: bob
// Testing user: charlieВ тестах Playwright
const loginUrls = [
'/en/login',
'/ru/login',
'/es/login',
];
// Проверяем что все страницы логина для разных локалей загружаются
loginUrls.forEach(async (url) => {
await page.goto(url);
await expect(page.locator('h1')).toBeVisible();
});map: трансформировать каждый элемент
map создаёт новый массив применяя функцию к каждому элементу. Исходный массив не изменяется.
Думай об этом так: «для каждого элемента верни мне что-то другое».
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] <- без измененийПрактический пример: извлечение текста из списка элементов
Часто получаешь массив локаторов от Playwright и нужно извлечь текст из каждого:
// Получаем текст всех ячеек таблицы
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', ...]Построение тестовых данных через 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' },
...
]
*/Трансформация данных API-ответа
// API возвращает сырые данные
const apiUsers = [
{ first_name: 'Alice', last_name: 'Smith', is_active: 1 },
{ first_name: 'Bob', last_name: 'Jones', is_active: 0 },
];
// Трансформируем под то что показывает UI
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: оставить только нужное
filter создаёт новый массив содержащий только элементы которые прошли проверку. Элементы не прошедшие проверку отбрасываются.
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] <- без измененийФильтрация тест-кейсов
Здесь filter раскрывается в тест-автоматизации. Когда есть большой список тест-кейсов, можно разделить их:
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 содержит 2 элемента, invalidCases - 3Фильтрация результатов API
// Все заказы из 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 },
];
// Оставляем только завершённые заказы для проверки
const completedOrders = orders.filter((o) => o.status === 'completed');
// [{ id: 1, ... }, { id: 3, ... }]Фильтрация элементов страницы по тексту
// Все строки таблицы
const rows = await page.locator('table tbody tr').all();
// Только строки содержащие 'Admin'
const adminRows = [];
for (const row of rows) {
const text = await row.textContent();
if (text?.includes('Admin')) {
adminRows.push(row);
}
}find: получить первое совпадение
find возвращает первый элемент прошедший проверку. Если ничего не совпало, возвращает undefined. В отличие от filter, прекращает поиск как только находит совпадение.
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');
// undefinedПоиск тестовых данных по 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 }Всегда проверяй на undefined
const product = products.find((p) => p.id === 'PROD-999');
if (!product) {
throw new Error('Test data not found: PROD-999');
}
// Теперь безопасно использовать product
await page.fill('[data-testid="search"]', product.name);filter.
Комбинирование методов
Настоящая сила появляется при цепочке методов:
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 },
];
// Получаем общую сумму завершённых заказов Alice
const aliceCompletedTotal = apiOrders
.filter((o) => o.user === 'alice' && o.status === 'completed')
.map((o) => o.amount)
.reduce((sum, amount) => sum + amount, 0);
// 150 + 200 = 350Или для проверки:
// Проверяем что все видимые цены товаров больше нуля
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);Краткая справка
| Метод | Что возвращает | Использовать когда |
|-------|---------------|-------------------|
| forEach | Ничего (undefined) | Нужны побочные эффекты (логирование, действия) |
| map | Новый массив, той же длины | Нужно трансформировать каждый элемент |
| filter | Новый массив, короче или равной длины | Нужно подмножество элементов |
| find | Один элемент или undefined | Нужно первое совпадение |
Частые ошибки
Использование map вместо forEach
// Неправильно: map предназначен для возврата значений, а не побочных эффектов
users.map((user) => {
console.log(user.name); // работает, но расточительно
});
// Правильно
users.forEach((user) => {
console.log(user.name);
});Забыть что find может вернуть undefined
// Упадёт если пользователь не найден
const user = users.find((u) => u.id === 99);
user.name; // TypeError: Cannot read properties of undefinedМутация исходного массива через map
Если делаешь users.map((u) => { u.role = 'admin'; return u; }), ты мутируешь исходные объекты, а не создаёшь новые. Создавай новые объекты:
// Правильно: создаёт новые объекты
users.map((u) => ({ ...u, role: 'admin' }));Примечание про async в Playwright
Когда колбэк асинхронный (а в Playwright это постоянно), оборачивай в Promise.all:
// Не работает: forEach игнорирует промисы
cells.forEach(async (cell) => {
const text = await cell.textContent(); // fire-and-forget
});
// Работает: ждёт все промисы
const texts = await Promise.all(
cells.map(async (cell) => await cell.textContent())
);Это самая частая ловушка с async в массивах в Playwright. Если сомневаешься, используй Promise.all с map.