Spec-Zone.ru › Playwright

Тестирование API

Введение​

Playwright можно использовать для доступа к REST-API вашего приложения.

Иногда вам может потребоваться отправлять запросы на сервер непосредственно из Node.js без загрузки страницы и выполнения кода js на ней. Вот несколько примеров, где это может пригодиться:

  • Тестирование API сервера.
  • Подготовка состояния сервера перед посещением веб-приложения в тесте.
  • Проверка пост-условий на стороне сервера после выполнения некоторых действий в браузере.

Все это можно сделать с помощью методов APIRequestContext.

Написание теста API​

APIRequestContext может отправлять все типы HTTP(S) запросов по сети.

Следующий пример демонстрирует, как использовать Playwright для тестирования создания проблем через GitHub API. Набор тестов выполнит следующие действия:

  • Создать новый репозиторий перед запуском тестов.
  • Создать несколько проблем и проверить состояние сервера.
  • Удалить репозиторий после запуска тестов.

Настройка​

GitHub API требует авторизации, поэтому мы настроим токен один раз для всех тестов. При этом мы также установим baseURL для упрощения тестов. Вы можете поместить их в файл конфигурации или в файл теста с test.use().

import { defineConfig } from '@playwright/test';
export default defineConfig({
  use: {
    // All requests we send go to this API endpoint.
    baseURL: 'https://api.github.com',
    extraHTTPHeaders: {
      // We set this header per GitHub guidelines.
      'Accept': 'application/vnd.github.v3+json',
      // Add authorization token to all requests.
      // Assuming personal access token available in the environment.
      'Authorization': `token ${process.env.API_TOKEN}`,
    },
  }
});

Настройка прокси-сервера

Если ваши тесты должны выполняться через прокси-сервер, вы можете указать это в конфигурации, и request фикстура автоматически его подхватит:

import { defineConfig } from '@playwright/test';
export default defineConfig({
  use: {
    proxy: {
      server: 'http://my-proxy:8080',
      username: 'user',
      password: 'secret'
    },
  }
});

Написание тестов​

Playwright Test поставляется с встроенной request фикстурой, которая учитывает параметры конфигурации, такие как baseURL или extraHTTPHeaders, которые мы указали, и готова отправить несколько запросов.

Теперь мы можем добавить несколько тестов, которые будут создавать новые проблемы в репозитории.

const REPO = 'test-repo-1';
const USER = 'github-username';

test('should create a bug report', async ({ request }) => {
  const newIssue = await request.post(`/repos/${USER}/${REPO}/issues`, {
    data: {
      title: '[Bug] report 1',
      body: 'Bug description',
    }
  });
  expect(newIssue.ok()).toBeTruthy();

  const issues = await request.get(`/repos/${USER}/${REPO}/issues`);
  expect(issues.ok()).toBeTruthy();
  expect(await issues.json()).toContainEqual(expect.objectContaining({
    title: '[Bug] report 1',
    body: 'Bug description'
  }));
});

test('should create a feature request', async ({ request }) => {
  const newIssue = await request.post(`/repos/${USER}/${REPO}/issues`, {
    data: {
      title: '[Feature] request 1',
      body: 'Feature description',
    }
  });
  expect(newIssue.ok()).toBeTruthy();

  const issues = await request.get(`/repos/${USER}/${REPO}/issues`);
  expect(issues.ok()).toBeTruthy();
  expect(await issues.json()).toContainEqual(expect.objectContaining({
    title: '[Feature] request 1',
    body: 'Feature description'
  }));
});

Настройка и завершение​

Эти тесты предполагают, что репозиторий существует. Вероятно, вам нужно будет создать новый репозиторий перед запуском тестов и удалить его после. Используйте beforeAll и afterAll хуки для этого.

test.beforeAll(async ({ request }) => {
  // Create a new repository
  const response = await request.post('/user/repos', {
    data: {
      name: REPO
    }
  });
  expect(response.ok()).toBeTruthy();
});

test.afterAll(async ({ request }) => {
  // Delete the repository
  const response = await request.delete(`/repos/${USER}/${REPO}`);
  expect(response.ok()).toBeTruthy();
});

Использование контекста запроса​

За кулисами request фикстура фактически вызовет apiRequest.newContext(). Вы всегда можете сделать это вручную, если хотите больше контроля. Ниже приведен самостоятельный скрипт, который делает то же самое, что beforeAll и afterAll из вышеприведённого примера.

import { request } from '@playwright/test';
const REPO = 'test-repo-1';
const USER = 'github-username';

(async () => {
  // Create a context that will issue http requests.
  const context = await request.newContext({
    baseURL: 'https://api.github.com',
  });

  // Create a repository.
  await context.post('/user/repos', {
    headers: {
      'Accept': 'application/vnd.github.v3+json',
      // Add GitHub personal access token.
      'Authorization': `token ${process.env.API_TOKEN}`,
    },
    data: {
      name: REPO
    }
  });

  // Delete a repository.
  await context.delete(`/repos/${USER}/${REPO}`, {
    headers: {
      'Accept': 'application/vnd.github.v3+json',
      // Add GitHub personal access token.
      'Authorization': `token ${process.env.API_TOKEN}`,
    }
  });
})();

Отправка API запросов из UI тестов​

При запуске тестов внутри браузеров вы можете отправлять запросы к HTTP API вашего приложения. Это может быть полезно, если вам нужно подготовить состояние сервера перед запуском теста или проверить некоторые пост-условия на сервере после выполнения некоторых действий в браузере. Все это можно сделать с помощью методов APIRequestContext.

Установление предварительных условий​

Следующий тест создаёт новую проблему через API, а затем переходит к списку всех проблем в проекте, чтобы проверить, что она отображается в верхней части списка.

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

const REPO = 'test-repo-1';
const USER = 'github-username';

// Request context is reused by all tests in the file.
let apiContext;

test.beforeAll(async ({ playwright }) => {
  apiContext = await playwright.request.newContext({
    // All requests we send go to this API endpoint.
    baseURL: 'https://api.github.com',
    extraHTTPHeaders: {
      // We set this header per GitHub guidelines.
      'Accept': 'application/vnd.github.v3+json',
      // Add authorization token to all requests.
      // Assuming personal access token available in the environment.
      'Authorization': `token ${process.env.API_TOKEN}`,
    },
  });
});

test.afterAll(async ({ }) => {
  // Dispose all responses.
  await apiContext.dispose();
});

test('last created issue should be first in the list', async ({ page }) => {
  const newIssue = await apiContext.post(`/repos/${USER}/${REPO}/issues`, {
    data: {
      title: '[Feature] request 1',
    }
  });
  expect(newIssue.ok()).toBeTruthy();

  await page.goto(`https://github.com/${USER}/${REPO}/issues`);
  const firstIssue = page.locator(`a[data-hovercard-type='issue']`).first();
  await expect(firstIssue).toHaveText('[Feature] request 1');
});

Проверка пост-условий​

Следующий тест создаёт новую проблему через пользовательский интерфейс в браузере, а затем проверяет, была ли она создана через API:

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

const REPO = 'test-repo-1';
const USER = 'github-username';

// Request context is reused by all tests in the file.
let apiContext;

test.beforeAll(async ({ playwright }) => {
  apiContext = await playwright.request.newContext({
    // All requests we send go to this API endpoint.
    baseURL: 'https://api.github.com',
    extraHTTPHeaders: {
      // We set this header per GitHub guidelines.
      'Accept': 'application/vnd.github.v3+json',
      // Add authorization token to all requests.
      // Assuming personal access token available in the environment.
      'Authorization': `token ${process.env.API_TOKEN}`,
    },
  });
});

test.afterAll(async ({ }) => {
  // Dispose all responses.
  await apiContext.dispose();
});

test('last created issue should be on the server', async ({ page }) => {
  await page.goto(`https://github.com/${USER}/${REPO}/issues`);
  await page.getByText('New Issue').click();
  await page.getByRole('textbox', { name: 'Title' }).fill('Bug report 1');
  await page.getByRole('textbox', { name: 'Comment body' }).fill('Bug description');
  await page.getByText('Submit new issue').click();
  const issueId = page.url().substr(page.url().lastIndexOf('/'));

  const newIssue = await apiContext.get(
      `https://api.github.com/repos/${USER}/${REPO}/issues/${issueId}`
  );
  expect(newIssue.ok()).toBeTruthy();
  expect(newIssue.json()).toEqual(expect.objectContaining({
    title: 'Bug report 1'
  }));
});

Использование состояния аутентификации​

Веб-приложения используют аутентификацию на основе cookie или токена, где аутентифицированное состояние хранится в виде cookie. Playwright предоставляет метод apiRequestContext.storageState(), который можно использовать для извлечения состояния хранилища из аутентифицированного контекста и создания новых контекстов с этим состоянием.

Состояние хранилища взаимозаменяемо между BrowserContext и APIRequestContext. Вы можете использовать его для входа через API-запросы и затем создать новый контекст с уже имеющимися cookie. Следующий фрагмент кода извлекает состояние из аутентифицированного APIRequestContext и создаёт новый BrowserContext с этим состоянием.

const requestContext = await request.newContext({
  httpCredentials: {
    username: 'user',
    password: 'passwd'
  }
});
await requestContext.get(`https://api.example.com/login`);
// Save storage state into the file.
await requestContext.storageState({ path: 'state.json' });

// Create a new context with the saved storage state.
const context = await browser.newContext({ storageState: 'state.json' });

Запрос контекста против глобального запроса​

Существует два типа APIRequestContext:

  • ассоциированный с BrowserContext
  • изолированный экземпляр, созданный с помощью apiRequest.newContext()

Основное различие заключается в том, что APIRequestContext, доступный через browserContext.request и page.request, заполнит заголовок запроса Cookie из контекста браузера и автоматически обновит cookie браузера, если APIResponse имеет заголовок Set-Cookie:

test('context request will share cookie storage with its browser context', async ({
  page,
  context,
}) => {
  await context.route('https://www.github.com/', async route => {
    // Send an API request that shares cookie storage with the browser context.
    const response = await context.request.fetch(route.request());
    const responseHeaders = response.headers();

    // The response will have 'Set-Cookie' header.
    const responseCookies = new Map(responseHeaders['set-cookie']
        .split('\n')
        .map(c => c.split(';', 2)[0].split('=')));
    // The response will have 3 cookies in 'Set-Cookie' header.
    expect(responseCookies.size).toBe(3);
    const contextCookies = await context.cookies();
    // The browser context will already contain all the cookies from the API response.
    expect(new Map(contextCookies.map(({ name, value }) =>
      [name, value])
    )).toEqual(responseCookies);

    await route.fulfill({
      response,
      headers: { ...responseHeaders, foo: 'bar' },
    });
  });
  await page.goto('https://www.github.com/');
});

Если вы не хотите, чтобы APIRequestContext использовал и обновлял cookie из контекста браузера, вы можете вручную создать новый экземпляр APIRequestContext, который будет иметь свои собственные изолированные cookie:

test('global context request has isolated cookie storage', async ({
  page,
  context,
  browser,
  playwright
}) => {
  // Create a new instance of APIRequestContext with isolated cookie storage.
  const request = await playwright.request.newContext();
  await context.route('https://www.github.com/', async route => {
    const response = await request.fetch(route.request());
    const responseHeaders = response.headers();

    const responseCookies = new Map(responseHeaders['set-cookie']
        .split('\n')
        .map(c => c.split(';', 2)[0].split('=')));
    // The response will have 3 cookies in 'Set-Cookie' header.
    expect(responseCookies.size).toBe(3);
    const contextCookies = await context.cookies();
    // The browser context will not have any cookies from the isolated API request.
    expect(contextCookies.length).toBe(0);

    // Manually export cookie storage.
    const storageState = await request.storageState();
    // Create a new context and initialize it with the cookies from the global request.
    const browserContext2 = await browser.newContext({ storageState });
    const contextCookies2 = await browserContext2.cookies();
    // The new browser context will already contain all the cookies from the API response.
    expect(
        new Map(contextCookies2.map(({ name, value }) => [name, value]))
    ).toEqual(responseCookies);

    await route.fulfill({
      response,
      headers: { ...responseHeaders, foo: 'bar' },
    });
  });
  await page.goto('https://www.github.com/');
  await request.dispose();
});

© 2024 Microsoft
Licensed under the Apache License, Version 2.0.
https://playwright.dev/docs/api-testing

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API