Тестирование 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