{ page, request } в каждой тестовой функции Playwright работает как деструктуризация: извлекает свойства из объекта фикстур, который фреймворк передаёт автоматически. Когда это доходит, кастомные фикстуры перестают выглядеть как магия и становятся тем же самым паттерном, повторённым снова. Этот гайд охватывает доступ к объектам, деструктуризацию с переименованием и дефолтами, вложенную деструктуризацию для API-ответов и поведение spread при поверхностном копировании, из-за которого мутации тест-данных просачиваются между вариантами.

Что такое объект

Объект хранит пары ключ-значение. Ключи строки, значения могут быть любыми:

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

Доступ к значениям через точку или скобки:

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

Менять значения можно тем же способом:

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

Объекты в тест-данных

Большинство тест-данных в тестах Playwright выражается через объекты или массивы объектов:

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

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

Коллекции тест-кейсов:

const INVALID_EMAILS = [
  { input: '',              description: 'пустое поле' },
  { input: 'not-an-email', description: 'без @' },
  { input: 'missing@',     description: 'без домена' },
  { input: 'a@b',          description: 'без TLD' },
];

Каждый INVALID_EMAILS[0] содержит свойства input и description.

Деструктуризация

Деструктуризация позволяет вытащить значения из объекта (или массива) в отдельные переменные одной строкой вместо нескольких.

Без деструктуризации

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;

С деструктуризацией

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

Результат тот же: четыре переменные с теми же значениями, но одна строка вместо четырёх.

Переименование при деструктуризации

Если нужно назвать переменную иначе чем ключ:

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

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

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

Дефолтные значения при деструктуризации

Если ключ может отсутствовать, можно задать дефолт:

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

const { name, price, discount = 0 } = product;
// discount = 0 (не было в product, используется дефолт)

Деструктуризация в параметрах функций

Чаще всего деструктуризация встречается в тестовых функциях Playwright:

// Без деструктуризации
test('пользователь может войти', async (args) => {
  const page = args.page;
  const request = args.request;
  // ...
});

// С деструктуризацией (стандартный паттерн Playwright)
test('пользователь может войти', async ({ page, request }) => {
  // page и request доступны напрямую
});

{ page, request } в параметре функции деструктурирует объект. Она извлекает свойства из объекта фикстур который передаёт Playwright.

Именно поэтому тестовый код Playwright выглядит так, будто там «магические переменные», они деструктурируются из объекта фикстур автоматически.

Кастомные фикстуры: деструктуризация на практике

При создании кастомных фикстур пишешь этот паттерн:

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

// В тесте:
test('логин работает', async ({ page, loginPage }) => {
  //                           ^^^ деструктурировано из объекта фикстур
  await loginPage.login('user@test.com', 'pass');
});

Понял деструктуризацию. Фикстуры перестают быть загадкой.

Вложенная деструктуризация

Объекты могут содержать другие объекты. Можно деструктурировать несколько уровней сразу:

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

// Вложенная деструктуризация
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...'

Поначалу выглядит сложно. На практике редко уходишь глубже двух уровней. Если становится запутанным, деструктурируй по шагам:

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

Результат тот же, читается легче.

Деструктуризация массивов

Массивы используют [] вместо {}:

const coordinates = [40.7128, -74.0060];

const [latitude, longitude] = coordinates;
// latitude = 40.7128, longitude = -74.0060

Пропустить элементы можно через запятую:

const [first, , third] = ['a', 'b', 'c'];
// first = 'a', third = 'c'

Оператор spread с объектами

Оператор spread (...) копирует все свойства одного объекта в другой:

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

const adminUser = {
  ...baseUser,       // копирует role и isActive
  email: 'admin@test.com',
  role: 'admin',    // перекрывает значение из spread
};

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

В тест-данных: создание вариантов

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

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

Паттерн «базовый объект + переопределения» очень распространён в фабриках тест-данных. Описываешь дефолтное валидное состояние один раз и через spread создаёшь варианты.

Практические паттерны в тестах Playwright

Деструктуризация тела API-ответа

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

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

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

Передача объектных параметров в хелперы

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);
}

// На месте вызова:
await fillLoginForm(page, { email: 'user@test.com', password: 'pass' });

Слияние конфигурации тестов

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

const ciConfig = {
  ...baseConfig,
  timeout: 60000,  // медленнее на CI
  video: 'retain-on-failure',
};

Частые ошибки

Деструктуризация из undefined

const response = await fetch('/api/user');
const { id } = await response.json(); // Если response равен null/undefined, выбросит ошибку

Всегда проверяй что значение существует перед деструктуризацией глубоко вложенных объектов.

Изменение spread-копии не затрагивает оригинал... частично

const original = { name: 'Alice', data: { score: 100 } };
const copy = { ...original };
copy.name = 'Bob';           // original.name остаётся 'Alice'
copy.data.score = 200;       // original.data.score тоже меняется!

Spread делает поверхностную копию. Вложенные объекты остаются общими ссылками.

Краткая справка

| Синтаксис | Что делает |

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

| const { a, b } = obj | Извлечь a и b из obj |

| const { a: x } = obj | Извлечь a из obj, назвать x |

| const { a = 5 } = obj | Извлечь a, дефолт 5 если отсутствует |

| async ({ page, request }) => {} | Деструктурировать фикстуры Playwright |

| { ...obj, key: 'value' } | Spread obj, добавить или перекрыть key |

→ See also: JavaScript для QA-инженеров: необходимый минимум для автоматизации | JavaScript массивы: map, filter, find и forEach — полевое руководство для QA | TypeScript для QA: почему статическая типизация улучшает тесты