mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
260 lines
8.4 KiB
Markdown
260 lines
8.4 KiB
Markdown
---
|
|
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.
|
|
A few examples where it may come in handy:
|
|
- Test your server API.
|
|
- 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.
|
|
|
|
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.
|
|
|
|
### Configure
|
|
|
|
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()`.
|
|
|
|
```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}`,
|
|
},
|
|
}
|
|
};
|
|
export default config;
|
|
```
|
|
|
|
```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;
|
|
```
|
|
|
|
### Write tests
|
|
|
|
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.
|
|
|
|
Now we can add a few tests that will create new issues in the repository.
|
|
```js
|
|
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'
|
|
}));
|
|
});
|
|
```
|
|
|
|
### 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.
|
|
|
|
```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 }) => {
|
|
const newIssue = await request.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');
|
|
});
|
|
```
|
|
|
|
## 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
|
|
it was created via API:
|
|
|
|
```js
|
|
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('/'));
|
|
|
|
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
|
|
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
|
|
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
|
|
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`]
|
|
|