mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
docs: improve the auth state docs (#9052)
This commit is contained in:
parent
04858b3959
commit
d1a2803a57
@ -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.
|
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
|
### API reference
|
||||||
- [`method: BrowserContext.storageState`]
|
- [`method: BrowserContext.storageState`]
|
||||||
- [`method: Browser.newContext`]
|
- [`method: Browser.newContext`]
|
||||||
|
313
docs/src/test-auth-js.md
Normal file
313
docs/src/test-auth-js.md
Normal 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.
|
||||||
|
:::
|
@ -22,7 +22,7 @@
|
|||||||
"build-installer": "babel -s --extensions \".ts\" --out-dir lib/utils/ src/utils",
|
"build-installer": "babel -s --extensions \".ts\" --out-dir lib/utils/ src/utils",
|
||||||
"doc": "node utils/doclint/cli.js",
|
"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",
|
"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/",
|
"clean": "rimraf lib && rimraf src/generated/",
|
||||||
"prepare": "node install-from-github.js",
|
"prepare": "node install-from-github.js",
|
||||||
"build": "node utils/build/build.js",
|
"build": "node utils/build/build.js",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user