playwright/docs/src/test-api-testing-js.md

260 lines
8.4 KiB
Markdown
Raw Normal View History

---
id: test-api-testing
title: "API testing"
---
Playwright can be used to get access to the [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) API of
your application.
Sometimes you may want to send requests to the server directly from Node.js without loading a page and running js code in it.
2021-10-18 06:31:38 -07:00
A few examples where it may come in handy:
- Test your server API.
2021-10-18 06:31:38 -07:00
- Prepare server side state before visiting the web application in a test.
- Validate server side post-conditions after running some actions in the browser.
All of that could be achieved via [APIRequestContext] methods.
<!-- TOC -->
## Writing API Test
[APIRequestContext] can send all kinds of HTTP(S) requests over network.
2021-10-18 06:31:38 -07:00
The following example demonstrates how to use Playwright to test issues creation via [GitHub API](https://docs.github.com/en/rest). The test suite will do the following:
- Create a new repository before running tests.
- Create a few issues and validate server state.
- Delete the repository after running tests.
2021-10-18 06:31:38 -07:00
### Configure
2021-10-18 06:31:38 -07:00
GitHub API requires authorization, so we'll configure the token once for all tests. While at it, we'll also set the `baseURL` to simplify the tests. You can either put them in the configuration file, or in the test file with `test.use()`.
2021-10-18 06:31:38 -07:00
```js js-flavor=ts
// playwright.config.ts
import { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
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}`,
},
}
2021-10-18 06:31:38 -07:00
};
export default config;
```
2021-10-18 06:31:38 -07:00
```js js-flavor=js
// playwright.config.js
// @ts-check
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
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}`,
},
}
};
module.exports = config;
```
2021-10-18 06:31:38 -07:00
### Write tests
2021-10-18 06:31:38 -07:00
Playwright Test comes with the built-in `request` fixture that respects configuration options like `baseURL` or `extraHTTPHeaders` we specified and is ready to send some requests.
2021-10-18 06:31:38 -07:00
Now we can add a few tests that will create new issues in the repository.
```js
2021-10-18 06:31:38 -07:00
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();
2021-10-18 06:31:38 -07:00
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'
}));
});
2021-10-18 06:31:38 -07:00
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();
2021-10-18 06:31:38 -07:00
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'
}));
});
```
2021-10-18 06:31:38 -07:00
### Setup and teardown
These tests assume that repository exists. You probably want to create a new one before running tests and delete it afterwards. Use `beforeAll` and `afterAll` hooks for that.
```js
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();
});
```
### Using request context
Behind the scenes, `request` fixture will actually call [`method: APIRequest.newContext`]. You can always do that manually if you'd like more control. Below is a standalone script that does the same as `beforeAll` and `afterAll` from above.
2021-10-18 06:31:38 -07:00
```js
const { request } = require('@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}`,
}
});
})()
```
## Prepare server state via API calls
The following test creates a new issue via API and then navigates to the list of all issues in the
project to check that it appears at the top of the list.
```js
test('last created issue should be first in the list', async ({ page, request }) => {
2021-10-18 06:31:38 -07:00
const newIssue = await request.post(`/repos/${USER}/${REPO}/issues`, {
data: {
title: '[Feature] request 1',
}
});
expect(newIssue.ok()).toBeTruthy();
2021-10-18 06:31:38 -07:00
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');
});
```
2021-10-18 06:31:38 -07:00
## Check the server state after running user actions
The following test creates a new issue via user interface in the browser and then uses checks if
2021-10-18 06:31:38 -07:00
it was created via API:
```js
2021-10-18 06:31:38 -07:00
test('last created issue should be on the server', async ({ page, request }) => {
await page.goto(`https://github.com/${USER}/${REPO}/issues`);
await page.click('text=New Issue');
await page.fill('[aria-label="Title"]', 'Bug report 1');
await page.fill('[aria-label="Comment body"]', 'Bug description');
await page.click('text=Submit new issue');
const issueId = page.url().substr(page.url().lastIndexOf('/'));
2021-10-18 06:31:38 -07:00
const newIssue = await request.get(`https://api.github.com/repos/${USER}/${REPO}/issues/${issueId}`);
expect(newIssue.ok()).toBeTruthy();
expect(newIssue).toEqual(expect.objectContaining({
title: 'Bug report 1'
}));
});
```
### API reference
- [`property: Playwright.request`]
- [`property: BrowserContext.request`]
- [`property: Page.request`]
- [`method: APIRequest.newContext`]
- [`method: APIRequestContext.delete`]
- [`method: APIRequestContext.fetch`]
- [`method: APIRequestContext.get`]
- [`method: APIRequestContext.post`]
## Reuse authentication state
Web apps use cookie-based or token-based authentication, where authenticated
2021-10-18 06:31:38 -07:00
state is stored as [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies).
Playwright provides [`method: APIRequestContext.storageState`] method that can be used to
2021-10-18 06:31:38 -07:00
retrieve storage state from an authenticated context and then create new contexts with that state.
Storage state is interchangeable between [BrowserContext] and [APIRequestContext]. You can
2021-10-18 06:31:38 -07:00
use it to log in via API calls and then create a new context with cookies already there.
The following code snippet retrieves state from an authenticated [APIRequestContext] and
creates a new [BrowserContext] with that state.
```js
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' });
```
### API reference
- [`method: Browser.newContext`]
- [`method: APIRequestContext.storageState`]
- [`method: APIRequest.newContext`]