Фикстура request в Playwright делает HTTP-запросы без открытия браузера и завершается за 50–200 миллисекунд против 3–5 секунд для UI-теста. Она доступна вместе с page в любом Playwright-тесте, что открывает гибридный паттерн: создавать тестовые данные через API, проверять их через UI, очищать через API. Всё в одном тестовом файле без лишних инструментов. Здесь разобраны GET, POST, аутентификация, тестирование ошибочных ответов и паттерны API+UI которые устраняют флакующую настройку.

Почему API-тесты нужны в твоём Playwright-сьюте

Аргумент за API-тестирование не в замене UI-тестов. Он в тестировании правильных вещей на правильном уровне.

UI-тест создающий travel-запись проходит через браузер, рендерит форму, заполняет поля, кликает submit, ждёт обновления страницы, затем проверяет результат. Это занимает 3–5 секунд и может упасть по дюжине причин не связанных с реальной функцией: медленная анимация, изменённый локатор, флакующий сетевой запрос.

API-тест создающий travel-запись отправляет один HTTP-запрос и проверяет ответ. Это занимает 50–200 миллисекунд и падает только когда само API сломано.

Используй API-тесты для: валидации данных, правил аутентификации, ошибочных ответов, бизнес-логики в бэкенде. Используй UI-тесты для: пользовательских флоу, визуального рендеринга, фронтенд-взаимодействий. Оба имеют место. Использовать только UI-тесты когда баг в API: это ошибка.

Фикстура request

Playwright предоставляет APIRequestContext через фикстуру request. Она доступна в любом тесте так же как page:

import { test, expect } from '@playwright/test';

test('GET /api/items returns a list', async ({ request }) => {
  const response = await request.get('https://lab.becomeqa.com/api/items');

  expect(response.status()).toBe(200);

  const items = await response.json();
  expect(items.length).toBeGreaterThan(0);
});

Браузер не открывается. Страница не загружается. Просто HTTP-запрос и ответ. Фикстура request автоматически обрабатывает куки, заголовки и конфигурацию baseURL.

GET-запросы и валидация ответа

Наиболее распространённый паттерн API-теста: вызов эндпоинта, проверка статуса, проверка формы данных.

test('GET /api/items returns correct structure', async ({ request }) => {
  const response = await request.get('https://lab.becomeqa.com/api/items');

  // Проверяем HTTP-статус
  expect(response.status()).toBe(200);
  expect(response.ok()).toBeTruthy(); // true для 2xx кодов

  // Парсим тело ответа
  const items = await response.json();

  // Проверяем массив
  expect(Array.isArray(items)).toBe(true);
  expect(items.length).toBeGreaterThan(0);

  // Проверяем форму первого элемента
  const firstItem = items[0];
  expect(firstItem).toHaveProperty('id');
  expect(firstItem).toHaveProperty('destination');
  expect(firstItem).toHaveProperty('status');
});

response.ok() это сокращение для status >= 200 && status < 300. Используй когда важно только что запрос успешен.

Проверка заголовков ответа:

const contentType = response.headers()['content-type'];
expect(contentType).toContain('application/json');

POST-запросы

POST-запросы отправляют данные для создания ресурса. Передавай тело как JSON:

test('POST /api/items creates a new item', async ({ request }) => {
  const response = await request.post('https://lab.becomeqa.com/api/items', {
    data: {
      destination: 'Tokyo',
      status: 'planned',
      notes: 'Cherry blossom season'
    }
  });

  expect(response.status()).toBe(201);

  const created = await response.json();
  expect(created).toHaveProperty('id');
  expect(created.destination).toBe('Tokyo');
  expect(created.status).toBe('planned');
});

Опция data автоматически сериализует в JSON и устанавливает заголовок Content-Type: application/json.

Для запросов с form-encoded данными используй form вместо data:

const response = await request.post('/api/login', {
  form: {
    username: 'admin@becomeqa.com',
    password: 'testpass123'
  }
});

Аутентификация

Большинство реальных API требуют аутентификации. Два распространённых паттерна.

Bearer-токен в заголовке

test('authenticated GET returns user data', async ({ request }) => {
  // Сначала получаем токен
  const loginResponse = await request.post('https://lab.becomeqa.com/api/auth/login', {
    data: {
      username: 'admin@becomeqa.com',
      password: 'testpass123'
    }
  });

  const { token } = await loginResponse.json();

  // Используем токен в последующих запросах
  const response = await request.get('https://lab.becomeqa.com/api/items', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  expect(response.status()).toBe(200);
});

Если приложение использует куки для auth, можно залогиниться через браузер и переиспользовать сессию для API-вызовов. Объект page.request разделяет куки с контекстом браузера:

test('API call with browser session', async ({ page, request }) => {
  // Логинимся через UI
  await page.goto('https://lab.becomeqa.com');
  await page.getByRole('button', { name: 'Login' }).click();
  await page.getByLabel('Username').fill('admin@becomeqa.com');
  await page.getByLabel('Password').fill('testpass123');
  await page.getByRole('button', { name: 'Submit' }).click();

  // Теперь используем page.request: он несёт auth-куку
  const response = await page.request.get('https://lab.becomeqa.com/api/items');
  expect(response.status()).toBe(200);
});

Важное различие: request (фикстура) это самостоятельный контекст без куков браузера. page.request разделяет куки с текущей сессией браузера.

Тестирование ошибочных ответов

API должны возвращать значимые ошибки. Тестируй их явно.

test('GET /api/items/:id returns 404 for unknown id', async ({ request }) => {
  const response = await request.get('https://lab.becomeqa.com/api/items/nonexistent-id-99999');

  expect(response.status()).toBe(404);

  const body = await response.json();
  expect(body).toHaveProperty('error');
});

test('POST /api/items returns 400 for missing required fields', async ({ request }) => {
  const response = await request.post('https://lab.becomeqa.com/api/items', {
    data: {
      // отсутствует обязательное поле 'destination'
      status: 'planned'
    }
  });

  expect(response.status()).toBe(400);
});

test('protected endpoint returns 401 without auth', async ({ request }) => {
  const response = await request.get('https://lab.becomeqa.com/api/items');
  // нет заголовка Authorization
  expect(response.status()).toBe(401);
});

Эти тесты проверяют что API корректно обрабатывает некорректный ввод, а не только happy path.

Комбинирование API и UI тестов

Один из наиболее мощных паттернов: используй API для настройки тестовых данных, затем проверяй через UI. Или наоборот: действуй через UI, проверяй через API.

API-настройка, UI-проверка

test('created item appears in the UI table', async ({ page, request }) => {
  // Создаём элемент через API: быстро и надёжно
  const createResponse = await request.post('https://lab.becomeqa.com/api/items', {
    data: { destination: 'Lisbon', status: 'planned' }
  });
  const { id } = await createResponse.json();

  // Проверяем что он появился в UI
  await page.goto('https://lab.becomeqa.com');
  // ... шаги логина ...
  await expect(page.getByText('Lisbon')).toBeVisible();

  // Очищаем через API после теста
  await request.delete(`https://lab.becomeqa.com/api/items/${id}`);
});

Этот тест быстрее и надёжнее. Настройка через UI наиболее частый источник флакинга в тестах которые реально не тестируют UI.

UI-действие, API-проверка

test('deleting an item via UI removes it from the API', async ({ page, request }) => {
  // Настройка через API
  const createResponse = await request.post('https://lab.becomeqa.com/api/items', {
    data: { destination: 'Oslo', status: 'planned' }
  });
  const { id } = await createResponse.json();

  // Действие через UI
  await page.goto('https://lab.becomeqa.com');
  // ... логин, поиск элемента, клик удаления ...

  // Проверка через API
  const checkResponse = await request.get(`https://lab.becomeqa.com/api/items/${id}`);
  expect(checkResponse.status()).toBe(404);
});

Настройка base URL для API-тестов

Повторять полный URL в каждом тесте это шум. Задай baseURL в playwright.config.ts:

export default defineConfig({
  use: {
    baseURL: 'https://lab.becomeqa.com',
  },
});

Теперь используй относительные пути:

const response = await request.get('/api/items');
const response = await request.post('/api/items', { data: { ... } });

Для проектов с разными базовыми URL для UI и API создай кастомную фикстуру которая настраивает API-контекст отдельно.

Добавь extraHTTPHeaders в конфиг если API требует общий заголовок в каждом запросе (например API-ключ или кастомный X-App-Version). Задай один раз в конфиге вместо повторения в каждом тесте.

export default defineConfig({
  use: {
    baseURL: 'https://lab.becomeqa.com',
    extraHTTPHeaders: {
      'X-Api-Key': process.env.API_KEY || '',
    },
  },
});

Организация API-тестов отдельно

Держи API-тесты в отдельной папке чтобы запускать их независимо от UI-тестов:

tests/
  ui/
    login.spec.ts
    items.spec.ts
  api/
    items-api.spec.ts
    auth-api.spec.ts

# Только API-тесты: быстро, без браузера
npx playwright test tests/api/

# Только UI-тесты
npx playwright test tests/ui/

API-тесты работают значительно быстрее UI-тестов. Раздельный запуск в CI позволяет получить быструю обратную связь о бэкенд-багах до завершения более медленного UI-сьюта.

FAQ

Нужен ли Postman если использую Playwright для API-тестирования

Postman полезен для ручного исследования API: выяснить какие эндпоинты существуют, какие параметры принимают, как выглядят ответы. Когда знаешь что тестировать, пиши автоматизированную версию в Playwright. Оба инструмента имеют место.

Должен ли каждый API-эндпоинт иметь тест

Сосредоточься на важных эндпоинтах: аутентификации, основных операциях с данными и любых эндпоинтах со сложной логикой валидации. CRUD API для travel-элементов нуждается в тестах для create, read, update, delete и как минимум основных ошибочных кейсов (404, 400, 401). Не каждая комбинация входных данных.

Как тестировать загрузку файлов через API

Используй опцию multipart:

const response = await request.post('/api/upload', {
  multipart: {
    file: {
      name: 'test.pdf',
      mimeType: 'application/pdf',
      buffer: Buffer.from('fake pdf content'),
    }
  }
});

Можно ли использовать фикстуру request без page

Да. Тесты которые используют только request без page работают без открытия браузера вообще. Это чистые HTTP-тесты, работающие так же быстро как любой другой HTTP-клиент.

→ See also: API-тестирование 101: всё, что нужно знать QA-инженеру в 2026 году | API-тестирование с Playwright APIRequestContext (без Postman) | Авторизация в API-тестах: API-ключи, Bearer-токены, OAuth2, JWT | Продвинутое API тестирование с Playwright: паттерны для реальных проектов