docs: improve the auth state docs (#9052)

This commit is contained in:
Pavel Feldman 2021-09-21 13:20:50 -07:00 committed by GitHub
parent 04858b3959
commit d1a2803a57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 314 additions and 96 deletions

View File

@ -159,101 +159,6 @@ implement **login once and run multiple scenarios**. The lifecycle looks like:
This approach will also **work in CI environments**, since it does not rely on any external state.
### Reuse authentication in Playwright Test
* langs: js
When using [Playwright Test](./intro.md), you can log in once in the global setup
and then reuse authentication state in tests. That way all your tests are completely
isolated, yet you only waste time logging in once for the entire test suite run.
First, introduce the global setup that would sign in once. In this example we use the `baseURL` and `storageState` options from the configuration file.
```js js-flavor=js
// global-setup.js
const { chromium } = require('@playwright/test');
module.exports = async config => {
const { baseURL, storageState } = config.projects[0].use;
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(baseURL);
await page.fill('input[name="user"]', 'user');
await page.fill('input[name="password"]', 'password');
await page.click('text=Sign in');
await page.context().storageState({ path: storageState });
await browser.close();
};
```
```js js-flavor=ts
// global-setup.ts
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const { baseURL, storageState } = config.projects[0].use;
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(baseURL!);
await page.fill('input[name="user"]', 'user');
await page.fill('input[name="password"]', 'password');
await page.click('text=Sign in');
await page.context().storageState({ path: storageState as string });
await browser.close();
}
export default globalSetup;
```
Next specify `globalSetup`, `baseURL` and `storageState` in the configuration file.
```js js-flavor=js
// playwright.config.js
// @ts-check
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
globalSetup: require.resolve('./global-setup'),
use: {
baseURL: 'http://localhost:3000/',
storageState: 'state.json',
},
};
module.exports = config;
```
```js js-flavor=ts
// playwright.config.ts
import { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
globalSetup: require.resolve('./global-setup'),
use: {
baseURL: 'http://localhost:3000/',
storageState: 'state.json',
},
};
export default config;
```
Tests start already authenticated because we specify `storageState` that was populated by global setup.
```js js-flavor=ts
import { test } from '@playwright/test';
test('test', async ({ page }) => {
await page.goto('/');
// You are signed in!
});
```
```js js-flavor=js
const { test } = require('@playwright/test');
test('test', async ({ page }) => {
await page.goto('/');
// You are signed in!
});
```
### API reference
- [`method: BrowserContext.storageState`]
- [`method: Browser.newContext`]

313
docs/src/test-auth-js.md Normal file
View File

@ -0,0 +1,313 @@
---
id: test-auth
title: "Authentication"
---
Tests written with Playwright execute in isolated clean-slate environments called
[browser contexts](./core-concepts.md#browser-contexts). Each test gets a brand
new page created in a brand new context. This isolation model improves reproducibility
and prevents cascading test failures.
Below are the typical strategies for implementing the signed-in scenarios.
<!-- TOC -->
## Sign in with beforeEach
This is the simplest way where each test signs in inside the `beforeEach` hook. It also is the
least efficient one in case the log in process has high latencies.
```js js-flavor=ts
import { test } from '@playwright/test';
test.beforeEach(async ({ page }) => {
// Runs before each test and signs in each page.
await page.goto('https://github.com/login');
await page.click('text=Login');
await page.fill('input[name="login"]', 'username');
await page.fill('input[name="password"]', 'password');
await page.click('text=Submit');
});
test('first', async ({ page }) => {
// page is signed in.
});
test('second', async ({ page }) => {
// page is signed in.
});
```
```js js-flavor=js
const { test } = require('@playwright/test');
test.beforeEach(async ({ page }) => {
// Runs before each test and signs in each page.
await page.goto('https://github.com/login');
await page.click('text=Login');
await page.fill('input[name="login"]', 'username');
await page.fill('input[name="password"]', 'password');
await page.click('text=Submit');
});
test('first', async ({ page }) => {
// page is signed in.
});
test('second', async ({ page }) => {
// page is signed in.
});
```
Redoing login for every test can slow down test execution. To mitigate that, reuse
existing authentication state instead.
## Reuse signed in state
Playwright provides a way to reuse the signed-in state in the tests. That way you can log
in only once and then skip the log in step for all of the tests.
Create a new global setup script:
```js js-flavor=js
// global-setup.js
const { chromium } = require('@playwright/test');
module.exports = async config => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://github.com/login');
await page.fill('input[name="user"]', 'user');
await page.fill('input[name="password"]', 'password');
await page.click('text=Sign in');
// Save signed-in state to 'storageState.json'.
await page.context().storageState({ path: 'storageState.json' });
await browser.close();
};
```
```js js-flavor=ts
// global-setup.ts
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://github.com/login');
await page.fill('input[name="user"]', 'user');
await page.fill('input[name="password"]', 'password');
await page.click('text=Sign in');
// Save signed-in state to 'storageState.json'.
await page.context().storageState({ path: 'storageState.json' });
await browser.close();
}
export default globalSetup;
```
Register global setup script in the Playwright configuration file:
```js js-flavor=ts
// playwright.config.ts
import { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
globalSetup: require.resolve('./global-setup'),
use: {
// Tell all tests to load signed-in state from 'storageState.json'.
storageState: 'storageState.json'
}
};
export default config;
```
```js js-flavor=js
// playwright.config.js
// @ts-check
/** @type {import('@playwright/test').PlaywrightTestConfig} */
const config = {
globalSetup: require.resolve('./global-setup'),
use: {
// Tell all tests to load signed-in state from 'storageState.json'.
storageState: 'storageState.json'
}
};
module.exports = config;
```
Tests start already authenticated because we specify `storageState` that was populated by global setup.
```js js-flavor=ts
import { test } from '@playwright/test';
test('test', async ({ page }) => {
// page is signed in.
});
```
```js js-flavor=js
const { test } = require('@playwright/test');
test('test', async ({ page }) => {
// page is signed in.
});
```
:::note
If you can log in once and commit the `storageState.json` into the repository, you won't need the global
setup at all, just specify the `storageState.json` in Playwright Config as above and it'll be picked up.
:::
### Multiple signed in roles
Sometimes you have more than one signed-in user in your end to end tests. You can achieve that via logging in for these users multiple times in globalSetup and saving that state into different files.
```js js-flavor=js
// global-setup.js
const { chromium } = require('@playwright/test');
module.exports = async config => {
const browser = await chromium.launch();
const adminPage = await browser.newPage();
// ... log in
await adminPage.context().storageState({ path: 'adminStorageState.json' });
const userPage = await browser.newPage();
// ... log in
await userPage.context().storageState({ path: 'userStorageState.json' });
await browser.close();
};
```
```js js-flavor=ts
// global-setup.ts
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch();
const adminPage = await browser.newPage();
// ... log in
await adminPage.context().storageState({ path: 'adminStorageState.json' });
const userPage = await browser.newPage();
// ... log in
await userPage.context().storageState({ path: 'userStorageState.json' });
await browser.close();
}
export default globalSetup;
```
After that you can specify the user to use for each test file or each test group:
```js js-flavor=ts
import { test } from '@playwright/test';
test.use({ storageState: 'adminStorageState.json' });
test('admin test', async ({ page }) => {
// page is signed in as admin.
});
test.describe(() => {
test.use({ storageState: 'userStorageState.json' });
test('user test', async ({ page }) => {
// page is signed in as a user.
});
});
```
```js js-flavor=js
const { test } = require('@playwright/test');
test.use({ storageState: 'adminStorageState.json' });
test('admin test', async ({ page }) => {
// page is signed in as amin.
});
test.describe(() => {
test.use({ storageState: 'userStorageState.json' });
test('user test', async ({ page }) => {
// page is signed in as a user.
});
});
```
### Reuse the signed in page in multiple tests
Although discouraged, sometimes it is necessary to sacrifice the isolation and run a number of tests
in the same page. In that case, you can log into that page once in `beforeAll` and then use that same
page in all the tests. Note that you need to run these tests serially using `test.describe.serial` in
order to achieve that:
```js js-flavor=js
// example.spec.js
// @ts-check
const { test } = require('@playwright/test');
test.describe.serial('use the same page', () => {
/** @type {import('@playwright/test').Page} */
let page;
test.beforeAll(async ({ browser }) => {
// Create page yourself and sign in.
page = await browser.newPage();
await page.goto('https://github.com/login');
await page.fill('input[name="user"]', 'user');
await page.fill('input[name="password"]', 'password');
await page.click('text=Sign in');
});
test.afterAll(async () => {
await page.close();
});
test('first test', async () => {
// page is signed in.
});
test('second test', async () => {
// page is signed in.
});
});
```
```js js-flavor=ts
// example.spec.ts
import { test, Page } from '@playwright/test';
test.describe.serial('use the same page', () => {
let page: Page;
test.beforeAll(async ({ browser }) => {
// Create page once and sign in.
page = await browser.newPage();
await page.goto('https://github.com/login');
await page.fill('input[name="user"]', 'user');
await page.fill('input[name="password"]', 'password');
await page.click('text=Sign in');
});
test.afterAll(async () => {
await page.close();
});
test('first test', async () => {
// page is signed in.
});
test('second test', async () => {
// page is signed in.
});
});
```
:::note
You can also use `storageState` property when you are creating the [`method: Browser.newPage`] in order to
pass it an existing logged in state.
:::

View File

@ -22,7 +22,7 @@
"build-installer": "babel -s --extensions \".ts\" --out-dir lib/utils/ src/utils",
"doc": "node utils/doclint/cli.js",
"lint": "npm run eslint && npm run tsc && npm run doc && npm run check-deps && node utils/generate_channels.js && node utils/generate_types/ --check-clean && npm run test-types",
"flint": "concurrently \"npm run eslint\" \"npm run tsc\" \"npm run doc\" \"npm run check-deps\" \"node utils/generate_channels.js\" \"node utils/generate_types/ --check-clean\" \"npm run test-types\"",
"flint": "concurrently -s all \"npm run eslint\" \"npm run tsc\" \"npm run doc\" \"npm run check-deps\" \"node utils/generate_channels.js\" \"node utils/generate_types/ --check-clean\" \"npm run test-types\"",
"clean": "rimraf lib && rimraf src/generated/",
"prepare": "node install-from-github.js",
"build": "node utils/build/build.js",