2022-08-16 22:00:54 -07:00
---
id: auth
title: "Authentication"
---
Playwright can be used to automate scenarios that require authentication.
2022-09-08 18:40:18 +02:00
Tests written with Playwright execute in isolated clean-slate environments called [browser contexts ](./browser-contexts.md ). This isolation model improves reproducibility and prevents cascading test failures. New browser contexts can load existing authentication state. This eliminates the need to login in every context and speeds up test execution.
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
> Note: This guide covers cookie/token-based authentication (logging in via the app UI). For [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication) use [`method: Browser.newContext`].
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
## Automate logging in
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
The Playwright API can [automate interaction ](./input.md ) from a login form.
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
The following example automates logging into GitHub. Once these steps are executed,
the browser context will be authenticated.
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
```js tab=js-ts
import { test } from '@playwright/test ';
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test.beforeEach(async ({ page }) => {
// Runs before each test and signs in each page.
await page.goto('https://github.com/login');
2022-10-03 17:02:46 -07:00
await page.getByText('Login').click();
2022-10-04 09:29:26 -08:00
await page.getByLabel('User Name').fill('username');
await page.getByLabel('Password').fill('password');
2022-10-03 17:02:46 -07:00
await page.getByText('Submit').click();
2022-09-08 18:40:18 +02:00
});
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test('first', async ({ page }) => {
// page is signed in.
});
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test('second', async ({ page }) => {
// page is signed in.
});
```
```js tab=js-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');
2022-10-03 17:02:46 -07:00
await page.getByText('Login').click();
2022-10-04 09:29:26 -08:00
await page.getByLabel('User name').fill('username');
await page.getByLabel('Password').fill('password');
2022-10-03 17:02:46 -07:00
await page.getByText('Submit').click();
2022-09-08 18:40:18 +02:00
});
test('first', async ({ page }) => {
// page is signed in.
});
test('second', async ({ page }) => {
// page is signed in.
});
```
```js tab=js-library
2022-08-16 22:00:54 -07:00
const page = await context.newPage();
await page.goto('https://github.com/login');
// Interact with login form
2022-10-03 17:02:46 -07:00
await page.getByText('Login').click();
2022-10-04 09:29:26 -08:00
await page.getByLabel('User Name').fill(USERNAME);
await page.getByLabel('Password').fill(PASSWORD);
2022-10-03 17:02:46 -07:00
await page.getByText('Submit').click();
2022-09-08 18:40:18 +02:00
// Continue with the test
2022-08-16 22:00:54 -07:00
```
```java
Page page = context.newPage();
page.navigate("https://github.com/login");
// Interact with login form
2022-10-03 17:02:46 -07:00
page.getByText("Login").click();
2022-10-04 09:29:26 -08:00
page.getByLabel("User Name").fill(USERNAME);
page.getByLabel("Password").fill(PASSWORD);
2022-08-16 22:00:54 -07:00
page.locator("text=Submit").click();
2022-09-08 18:40:18 +02:00
// Continue with the test
2022-08-16 22:00:54 -07:00
```
```python async
page = await context.new_page()
await page.goto('https://github.com/login')
# Interact with login form
2022-10-03 17:02:46 -07:00
await page.get_by_text("Login").click()
2022-10-04 09:29:26 -08:00
await page.get_by_label("User Name").fill(USERNAME)
await page.get_by_label("Password").fill(PASSWORD)
2022-10-03 17:02:46 -07:00
await page.get_by_text('Submit').click()
2022-09-08 18:40:18 +02:00
# Continue with the test
2022-08-16 22:00:54 -07:00
```
```python sync
page = context.new_page()
page.goto('https://github.com/login')
# Interact with login form
2022-10-03 17:02:46 -07:00
page.get_by_text("Login").click()
2022-10-04 09:29:26 -08:00
page.get_by_label("User Name").fill(USERNAME)
page.get_by_label("Password").fill(PASSWORD)
2022-10-03 17:02:46 -07:00
page.get_by_text('Submit').click()
2022-09-08 18:40:18 +02:00
# Continue with the test
2022-08-16 22:00:54 -07:00
```
```csharp
var page = await context.NewPageAsync();
await page.GotoAsync("https://github.com/login");
// Interact with login form
2022-10-03 17:02:46 -07:00
await page.GetByText("Login").ClickAsync();
2022-10-04 09:29:26 -08:00
await page.GetByLabel("User Name").FillAsync(USERNAME);
await page.GetByLabel("Password").FillAsync(PASSWORD);
2022-10-03 17:02:46 -07:00
await page.GetByText("Submit").ClickAsync();
2022-09-08 18:40:18 +02:00
// Continue with the test
2022-08-16 22:00:54 -07:00
```
2022-09-08 18:40:18 +02:00
Redoing login for every test can slow down test execution. To mitigate that, reuse
existing authentication state instead.
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
## Reuse signed in state
* langs: java, csharp, python
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
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.
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
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 ) or in [local storage ](https://developer.mozilla.org/en-US/docs/Web/API/Storage ). Playwright provides [browserContext.storageState([options])](https://playwright.dev/docs/api/class-browsercontext#browser -context-storage-state) method that can be used to retrieve storage state from authenticated contexts and then create new contexts with prepopulated state.
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
Cookies and local storage state can be used across different browsers. They depend on your application's authentication model: some apps might require both cookies and local storage.
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
The following code snippet retrieves state from an authenticated context and creates a new context with that state.
2022-08-16 22:00:54 -07:00
```java
// Save storage state into the file.
context.storageState(new BrowserContext.StorageStateOptions().setPath(Paths.get("state.json")));
// Create a new context with the saved storage state.
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().setStorageStatePath(Paths.get("state.json")));
```
```python async
# Save storage state into the file.
storage = await context.storage_state(path="state.json")
# Create a new context with the saved storage state.
context = await browser.new_context(storage_state="state.json")
```
```python sync
# Save storage state into the file.
storage = context.storage_state(path="state.json")
# Create a new context with the saved storage state.
context = browser.new_context(storage_state="state.json")
```
```csharp
// Save storage state into the file.
await context.StorageStateAsync(new()
{
Path = "state.json"
});
// Create a new context with the saved storage state.
var context = await browser.NewContextAsync(new()
{
StorageStatePath = "state.json"
});
```
2022-09-08 18:40:18 +02:00
## Reuse signed in state
* langs: js
Playwright provides a way to reuse the signed-in state in the tests. That way you can log
2022-12-01 16:53:54 -08:00
in only once per project and then skip the log in step for all of the tests.
2022-09-08 18:40:18 +02:00
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 ) or in [local storage ](https://developer.mozilla.org/en-US/docs/Web/API/Storage ). Playwright provides [browserContext.storageState([options])](https://playwright.dev/docs/api/class-browsercontext#browser -context-storage-state) method that can be used to retrieve storage state from authenticated contexts and then create new contexts with prepopulated state.
2022-12-06 13:33:43 -08:00
You can run authentication steps once during the project [`property: TestProject.setup` ] phase and save the context state into [TestStore]. The stored value can later be reused to automatically restore authenticated context state in every test of the project. This way the login will run once per project before all tests.
2022-09-08 18:40:18 +02:00
2022-12-06 13:33:43 -08:00
Create a setup test that performs login and saves the context state into project store:
2022-09-08 18:40:18 +02:00
```js tab=js-js
2022-12-01 16:53:54 -08:00
// github-login.setup.js
2022-12-06 13:33:43 -08:00
const { setup, store } = require('@playwright/test ');
2022-09-08 18:40:18 +02:00
2022-12-05 21:37:37 -08:00
setup('sign in', async ({ page, context }) => {
2022-09-08 18:40:18 +02:00
await page.goto('https://github.com/login');
2022-10-04 09:29:26 -08:00
await page.getByLabel('User Name').fill('user');
await page.getByLabel('Password').fill('password');
2022-10-03 17:02:46 -07:00
await page.getByText('Sign in').click();
2022-12-01 16:53:54 -08:00
// Save signed-in state to an entry named 'github-test-user'.
const contextState = await context.storageState();
2022-12-06 13:33:43 -08:00
await store.set('github-test-user', contextState)
2022-12-01 16:53:54 -08:00
});
2022-09-08 18:40:18 +02:00
```
```js tab=js-ts
2022-12-01 16:53:54 -08:00
// github-login.setup.ts
2022-12-06 13:33:43 -08:00
import { setup, store } from '@playwright/setup ';
2022-09-08 18:40:18 +02:00
2022-12-05 21:37:37 -08:00
setup('sign in', async ({ page, context }) => {
2022-09-08 18:40:18 +02:00
await page.goto('https://github.com/login');
2022-10-04 09:29:26 -08:00
await page.getByLabel('User Name').fill('user');
await page.getByLabel('Password').fill('password');
2022-10-03 17:02:46 -07:00
await page.getByText('Sign in').click();
2022-09-08 18:40:18 +02:00
2022-12-01 16:53:54 -08:00
// Save signed-in state to an entry named 'github-test-user'.
const contextState = await context.storageState();
2022-12-06 13:33:43 -08:00
await store.set('github-test-user', contextState)
2022-12-01 16:53:54 -08:00
});
2022-09-08 18:40:18 +02:00
```
2022-12-01 16:53:54 -08:00
Configure project setup tests in the Playwright configuration file:
2022-09-08 18:40:18 +02:00
```js tab=js-ts
// playwright.config.ts
import type { PlaywrightTestConfig } from '@playwright/test ';
const config: PlaywrightTestConfig = {
2022-12-01 16:53:54 -08:00
projects: [
{
name: 'chromium',
// Specify files that should run before regular tests in the project.
setup: /.*.setup.ts$/,
},
2022-09-08 18:40:18 +02:00
};
export default config;
```
```js tab=js-js
// playwright.config.js
// @ts -check
/** @type {import('@playwright/test ').PlaywrightTestConfig} */
const config = {
2022-12-01 16:53:54 -08:00
projects: [
{
name: 'chromium',
// Specify files that should run before regular tests in the project.
setup: /.*.setup.ts$/,
},
2022-09-08 18:40:18 +02:00
};
module.exports = config;
```
2022-12-01 16:53:54 -08:00
Specify [`property: TestOptions.storageStateName` ] in the test files that need to be logged in. Playwright will use the previously saved state when creating a page.
2022-09-08 18:40:18 +02:00
```js tab=js-ts
2022-12-01 16:53:54 -08:00
import { test, expect } from '@playwright/test ';
// Name of the storage state entry. The entry is saved in the project setup.
test.use({
2022-12-06 13:06:47 -08:00
storageStateName: 'github-test-user'
2022-12-01 16:53:54 -08:00
})
2022-09-08 18:40:18 +02:00
test('test', async ({ page }) => {
// page is signed in.
});
```
```js tab=js-js
const { test } = require('@playwright/test ');
2022-12-01 16:53:54 -08:00
// Name of the storage state entry. The entry is saved in the project setup.
test.use({
2022-12-06 13:06:47 -08:00
storageStateName: 'github-test-user'
2022-12-01 16:53:54 -08:00
})
2022-09-08 18:40:18 +02:00
test('test', async ({ page }) => {
// page is signed in.
});
```
2022-12-01 16:53:54 -08:00
### Reusing signed in state between test runs
* langs: js
2022-09-08 18:40:18 +02:00
2022-12-06 13:33:43 -08:00
When you set an entry on [TestStore] Playwright will store it in a separate file under `.playwright-store/` . Playwright does not delete those files automatically. You can leverage this fact to persist storage state between test runs and only sign in if the entry is not in the store yet.
2022-12-01 16:53:54 -08:00
```js tab=js-js
// github-login.setup.js
2022-12-06 13:33:43 -08:00
const { setup, store } = require('@playwright/test ');
2022-12-01 16:53:54 -08:00
2022-12-05 21:37:37 -08:00
setup('sign in', async ({ page, context }) => {
if (storage.get('github-test-user'))
2022-12-01 16:53:54 -08:00
return;
// ... login here ...
2022-12-06 13:33:43 -08:00
await store.set('github-test-user', await context.storageState());
2022-12-01 16:53:54 -08:00
});
```
```js tab=js-ts
// github-login.setup.ts
2022-12-06 13:33:43 -08:00
import { setup, store } from '@playwright/test ';
2022-12-01 16:53:54 -08:00
2022-12-05 21:37:37 -08:00
setup('sign in', async ({ page, context }) => {
if (storage.get('github-test-user'))
2022-12-01 16:53:54 -08:00
return;
// ... login here ...
2022-12-06 13:33:43 -08:00
await store.set('github-test-user', await context.storageState());
2022-12-01 16:53:54 -08:00
});
```
2022-12-06 13:33:43 -08:00
You may need to periodically update the storage state entry if your app requires you to re-authenticate after some amount of time. For example, if your app prompts you to sign in every week even if you're on the same computer/browser, you'll need to update saved storage state at least this often. You can simply delete `.playwright-store/` directory to clear the store and run the tests again so that they populate it.
2022-09-08 18:40:18 +02:00
### Sign in via API request
* langs: js
If your web application supports signing in via API, you can use [APIRequestContext] to simplify sign in flow. Global setup script from the example above would change like this:
```js tab=js-js
2022-12-01 16:53:54 -08:00
// github-login.setup.js
2022-12-06 13:33:43 -08:00
const { setup, store } = require('@playwright/test ');
2022-09-08 18:40:18 +02:00
2022-12-05 21:37:37 -08:00
setup('sign in', async ({ request }) => {
2022-12-01 16:53:54 -08:00
await request.post('https://github.com/login', {
2022-09-08 18:40:18 +02:00
form: {
'user': 'user',
'password': 'password'
}
});
2022-12-01 16:53:54 -08:00
// Save signed-in state to an entry named 'github-test-user'.
const contextState = await request.storageState();
2022-12-06 13:33:43 -08:00
await store.set('github-test-user', contextState)
2022-12-01 16:53:54 -08:00
});
2022-09-08 18:40:18 +02:00
```
```js tab=js-ts
2022-12-01 16:53:54 -08:00
// github-login.setup.ts
2022-12-06 13:33:43 -08:00
import { setup, store } from '@playwright/test ';
2022-09-08 18:40:18 +02:00
2022-12-05 21:37:37 -08:00
setup('sign in', async ({ request }) => {
2022-12-01 16:53:54 -08:00
await request.post('https://github.com/login', {
2022-09-08 18:40:18 +02:00
form: {
'user': 'user',
'password': 'password'
}
});
2022-12-01 16:53:54 -08:00
// Save signed-in state to an entry named 'github-test-user'.
const contextState = await request.storageState();
2022-12-06 13:33:43 -08:00
await store.set('github-test-user', contextState)
2022-12-01 16:53:54 -08:00
});
2022-09-08 18:40:18 +02:00
```
### Avoiding multiple sessions per account at a time
* langs: js
By default, Playwright Test runs tests in parallel. If you reuse a single signed-in state for all your tests, this usually leads to the same account being signed in from multiple tests at the same time. If this behavior is undesirable for your application, you can sign in with a different account in each [worker process ](./test-parallel.md#worker-processes ) created by Playwright Test.
In this example we [override `storageState` fixture ](./test-fixtures.md#overriding-fixtures ) and ensure we only sign in once per worker, using [`property: TestInfo.workerIndex` ] to differentiate between workers.
```js tab=js-js
2022-12-06 13:06:47 -08:00
// login-fixture.js
2022-12-06 13:33:43 -08:00
const { test: base, store } = require('@playwright/test ');
2022-09-08 18:40:18 +02:00
const users = [
{ username: 'user-1', password: 'password-1' },
{ username: 'user-2', password: 'password-2' },
// ... put your test users here ...
];
2022-12-06 13:06:47 -08:00
exports.test = base.extend({
// Sign in corresponding user during test worker startup and save the state
2022-12-06 13:33:43 -08:00
// to store.
2022-12-06 13:06:47 -08:00
login: [async ({ browser }, use) => {
const page = await browser.newPage();
2022-12-01 16:53:54 -08:00
await page.goto('https://github.com/login');
2022-12-06 13:06:47 -08:00
// Use a unique credentials for each worker.
const index = test.info().parallelIndex;
await page.getByLabel('User Name').fill(users[index].username);
await page.getByLabel('Password').fill(users[index].password);
2022-12-01 16:53:54 -08:00
await page.getByText('Sign in').click();
const contextState = await page.context().storageState();
2022-12-06 13:33:43 -08:00
// Save with a worker-specific key.
await store.set(`test-user-${index}` , contextState);
2022-12-06 13:06:47 -08:00
await page.close();
await use();
}, { auto: true }],
});
exports.expect = base.expect;
2022-12-06 13:33:43 -08:00
exporta.store = base.store;
2022-09-08 18:40:18 +02:00
// example.spec.js
2022-12-06 13:06:47 -08:00
const { test, expect } = require('./login-fixture');
2022-12-01 16:53:54 -08:00
test.use({
2022-12-06 13:06:47 -08:00
// User worker specific user.
2022-12-01 16:53:54 -08:00
storageStateName: ({}, use) => use(`test-user-${test.info().parallelIndex}` )
});
2022-09-08 18:40:18 +02:00
test('test', async ({ page }) => {
// page is signed in.
});
```
```js tab=js-ts
2022-12-06 13:06:47 -08:00
// login-fixture.ts
2022-12-06 13:33:43 -08:00
import { test as base, store } from '@playwright/test ';
2022-09-08 18:40:18 +02:00
const users = [
{ username: 'user-1', password: 'password-1' },
{ username: 'user-2', password: 'password-2' },
// ... put your test users here ...
];
2022-12-06 13:06:47 -08:00
export const test = base.extend< { login: void }>({
// Sign in corresponding user during test worker startup and save the state
2022-12-06 13:33:43 -08:00
// to the store.
2022-12-06 13:06:47 -08:00
login: [async ({ browser }, use) => {
const page = await browser.newPage();
2022-12-01 16:53:54 -08:00
await page.goto('https://github.com/login');
2022-12-06 13:06:47 -08:00
// Use a unique credentials for each worker.
const index = test.info().parallelIndex;
await page.getByLabel('User Name').fill(users[index].username);
await page.getByLabel('Password').fill(users[index].password);
2022-12-01 16:53:54 -08:00
await page.getByText('Sign in').click();
const contextState = await page.context().storageState();
2022-12-06 13:33:43 -08:00
// Save with a worker-specific key.
await store.set(`test-user-${index}` , contextState);
2022-12-06 13:06:47 -08:00
await page.close();
await use();
}, { auto: true }],
});
export { expect } from '@playwright/test ';
2022-12-01 16:53:54 -08:00
2022-09-08 18:40:18 +02:00
// example.spec.ts
2022-12-06 13:06:47 -08:00
import { test } from './login-fixture';
2022-12-01 16:53:54 -08:00
test.use({
2022-12-06 13:06:47 -08:00
// User worker specific user.
2022-12-05 21:37:37 -08:00
storageStateName: ({}, use) => use(`test-user-${test.info().parallelIndex}` )
2022-12-01 16:53:54 -08:00
});
2022-09-08 18:40:18 +02:00
test('test', async ({ page }) => {
// page is signed in.
});
```
## Multiple signed in roles
* langs: js
2022-12-01 16:53:54 -08:00
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 project setup and saving that state into separate entries.
2022-09-08 18:40:18 +02:00
```js tab=js-js
2022-12-01 16:53:54 -08:00
// login.setup.js
2022-12-06 13:33:43 -08:00
const { setup, store } = require('@playwright/test ');
2022-12-01 16:53:54 -08:00
// Run all logins in parallel.
2022-12-05 21:37:37 -08:00
setup.describe.configure({
2022-12-01 16:53:54 -08:00
mode: 'parallel'
});
2022-12-05 21:37:37 -08:00
setup(`login as regular user` , async ({ page }) => {
2022-12-01 16:53:54 -08:00
await page.goto('https://github.com/login');
//...
const contextState = await page.context().storageState();
// Save the user state.
2022-12-06 13:33:43 -08:00
await store.set(`user` , contextState);
2022-12-01 16:53:54 -08:00
});
2022-12-05 21:37:37 -08:00
setup(`login as admin` , async ({ page }) => {
2022-12-01 16:53:54 -08:00
await page.goto('https://github.com/login');
//...
const contextState = await page.context().storageState();
// Save the admin state.
2022-12-06 13:33:43 -08:00
await store.set(`admin` , contextState);
2022-12-01 16:53:54 -08:00
});
2022-09-08 18:40:18 +02:00
```
```js tab=js-ts
2022-12-01 16:53:54 -08:00
// login.setup.ts
2022-12-06 13:33:43 -08:00
import { setup, store } from '@playwright/test ';
2022-09-08 18:40:18 +02:00
2022-12-01 16:53:54 -08:00
// Run all logins in parallel.
2022-12-05 21:37:37 -08:00
setup.describe.configure({
2022-12-01 16:53:54 -08:00
mode: 'parallel'
});
2022-12-05 21:37:37 -08:00
setup(`login as regular user` , async ({ page }) => {
2022-12-01 16:53:54 -08:00
await page.goto('https://github.com/login');
//...
const contextState = await page.context().storageState();
// Save the user state.
2022-12-06 13:33:43 -08:00
await store.set(`user` , contextState);
2022-12-01 16:53:54 -08:00
});
2022-12-05 21:37:37 -08:00
setup(`login as admin` , async ({ page }) => {
2022-12-01 16:53:54 -08:00
await page.goto('https://github.com/login');
//...
const contextState = await page.context().storageState();
// Save the admin state.
2022-12-06 13:33:43 -08:00
await store.set(`admin` , contextState);
2022-12-01 16:53:54 -08:00
});
2022-09-08 18:40:18 +02:00
```
After that you can specify the user to use for each test file or each test group:
```js tab=js-ts
import { test } from '@playwright/test ';
2022-12-01 16:53:54 -08:00
test.use({ storageStateName: 'admin' });
2022-09-08 18:40:18 +02:00
test('admin test', async ({ page }) => {
// page is signed in as admin.
});
test.describe(() => {
2022-12-01 16:53:54 -08:00
test.use({ storageStateName: 'user' });
2022-09-08 18:40:18 +02:00
test('user test', async ({ page }) => {
// page is signed in as a user.
});
});
```
```js tab=js-js
const { test } = require('@playwright/test ');
2022-12-01 16:53:54 -08:00
test.use({ storageStateName: 'admin' });
2022-09-08 18:40:18 +02:00
test('admin test', async ({ page }) => {
// page is signed in as amin.
});
test.describe(() => {
2022-12-01 16:53:54 -08:00
test.use({ storageStateName: 'user' });
2022-09-08 18:40:18 +02:00
test('user test', async ({ page }) => {
// page is signed in as a user.
});
});
```
### Testing multiple roles together
* langs: js
2022-12-01 16:53:54 -08:00
If you need to test how multiple authenticated roles interact together, use multiple [BrowserContext]s and [Page]s with different storage states in the same test. Any of the methods above to create multiple storage state entries would work.
2022-09-08 18:40:18 +02:00
```js tab=js-ts
2022-12-06 13:33:43 -08:00
import { test, store } from '@playwright/test ';
2022-09-08 18:40:18 +02:00
test('admin and user', async ({ browser }) => {
// adminContext and all pages inside, including adminPage, are signed in as "admin".
2022-12-06 13:33:43 -08:00
const adminContext = await browser.newContext({ storageState: await store.get('admin') });
2022-09-08 18:40:18 +02:00
const adminPage = await adminContext.newPage();
// userContext and all pages inside, including userPage, are signed in as "user".
2022-12-06 13:33:43 -08:00
const userContext = await browser.newContext({ storageState: await store.get('user') });
2022-09-08 18:40:18 +02:00
const userPage = await userContext.newPage();
// ... interact with both adminPage and userPage ...
});
```
```js tab=js-js
2022-12-06 13:33:43 -08:00
const { test, store } = require('@playwright/test ');
2022-09-08 18:40:18 +02:00
test('admin and user', async ({ browser }) => {
// adminContext and all pages inside, including adminPage, are signed in as "admin".
2022-12-06 13:33:43 -08:00
const adminContext = await browser.newContext({ storageState: await store.get('admin') });
2022-09-08 18:40:18 +02:00
const adminPage = await adminContext.newPage();
// userContext and all pages inside, including userPage, are signed in as "user".
2022-12-06 13:33:43 -08:00
const userContext = await browser.newContext({ storageState: await store.get('user') });
2022-09-08 18:40:18 +02:00
const userPage = await userContext.newPage();
// ... interact with both adminPage and userPage ...
});
```
### Testing multiple roles with POM fixtures
* langs: js
2022-12-01 16:53:54 -08:00
If many of your tests require multiple authenticated roles from within the same test, you can introduce fixtures for each role. Any of the methods above to create multiple storage state entries would work.
2022-09-08 18:40:18 +02:00
2022-11-07 15:31:42 -08:00
Below is an example that [creates fixtures ](./test-fixtures.md#creating-a-fixture ) for two [Page Object Models ](./pom.md ) - admin POM and user POM. It assumes `adminStorageState.json` and `userStorageState.json` files were created.
2022-09-08 18:40:18 +02:00
```js tab=js-ts
// fixtures.ts
import { test as base, Page, Browser, Locator } from '@playwright/test ';
2022-12-06 13:33:43 -08:00
export { expect, store } from '@playwright/test ';
2022-09-08 18:40:18 +02:00
// Page Object Model for the "admin" page.
// Here you can add locators and helper methods specific to the admin page.
class AdminPage {
// Page signed in as "admin".
page: Page;
constructor(page: Page) {
this.page = page;
}
static async create(browser: Browser) {
2022-12-06 13:33:43 -08:00
const context = await browser.newContext({ storageState: await store.get('admin') });
2022-09-08 18:40:18 +02:00
const page = await context.newPage();
return new AdminPage(page);
}
}
// Page Object Model for the "user" page.
// Here you can add locators and helper methods specific to the user page.
class UserPage {
// Page signed in as "user".
page: Page;
// Example locator pointing to "Welcome, User" greeting.
greeting: Locator;
constructor(page: Page) {
this.page = page;
this.greeting = page.locator('#greeting ');
}
static async create(browser: Browser) {
2022-12-06 13:33:43 -08:00
const context = await browser.newContext({ storageState: await store.get('user') });
2022-09-08 18:40:18 +02:00
const page = await context.newPage();
return new UserPage(page);
}
}
// Declare the types of your fixtures.
type MyFixtures = {
adminPage: AdminPage;
userPage: UserPage;
};
// Extend base test by providing "adminPage" and "userPage".
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
export const test = base.extend< MyFixtures > ({
adminPage: async ({ browser }, use) => {
await use(await AdminPage.create(browser));
},
userPage: async ({ browser }, use) => {
await use(await UserPage.create(browser));
},
});
// example.spec.ts
// Import test with our new fixtures.
import { test, expect } from './fixtures';
// Use adminPage and userPage fixtures in the test.
test('admin and user', async ({ adminPage, userPage }) => {
// ... interact with both adminPage and userPage ...
await adminPage.page.screenshot();
await expect(userPage.greeting).toHaveText('Welcome, User');
});
```
```js tab=js-js
// fixtures.js
2022-12-06 13:33:43 -08:00
const { test: base, store } = require('@playwright/test ');
2022-09-08 18:40:18 +02:00
// Page Object Model for the "admin" page.
// Here you can add locators and helper methods specific to the admin page.
class AdminPage {
constructor(page) {
// Page signed in as "admin".
this.page = page;
}
static async create(browser) {
2022-12-06 13:33:43 -08:00
const context = await browser.newContext({ storageState: await store.get('admin') });
2022-09-08 18:40:18 +02:00
const page = await context.newPage();
return new AdminPage(page);
}
}
// Page Object Model for the "user" page.
// Here you can add locators and helper methods specific to the user page.
class UserPage {
constructor(page) {
// Page signed in as "user".
this.page = page;
// Example locator pointing to "Welcome, User" greeting.
this.greeting = page.locator('#greeting ');
}
static async create(browser) {
2022-12-06 13:33:43 -08:00
const context = await browser.newContext({ storageState: await store.get('user') });
2022-09-08 18:40:18 +02:00
const page = await context.newPage();
return new UserPage(page);
}
}
// Extend base test by providing "adminPage" and "userPage".
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
exports.test = base.extend({
adminPage: async ({ browser }, use) => {
await use(await AdminPage.create(browser));
},
userPage: async ({ browser }, use) => {
await use(await UserPage.create(browser));
},
});
exports.expect = base.expect;
// example.spec.ts
// Import test with our new fixtures.
const { test, expect } = require('./fixtures');
// Use adminPage and userPage fixtures in the test.
test('admin and user', async ({ adminPage, userPage }) => {
// ... interact with both adminPage and userPage ...
await adminPage.page.screenshot();
await expect(userPage.greeting).toHaveText('Welcome, User');
});
```
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
## Reuse the signed in page in multiple tests
2022-08-16 22:00:54 -07:00
* langs: js
2022-09-08 18:40:18 +02:00
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:
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
```js tab=js-js
// example.spec.js
// @ts -check
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
const { test } = require('@playwright/test ');
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test.describe.configure({ mode: 'serial' });
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
/** @type {import('@playwright/test ').Page} */
let page;
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test.beforeAll(async ({ browser }) => {
// Create page yourself and sign in.
page = await browser.newPage();
await page.goto('https://github.com/login');
2022-10-04 09:29:26 -08:00
await page.getByLabel('User Name').fill('user');
await page.getByLabel('Password').fill('password');
2022-10-03 17:02:46 -07:00
await page.getByText('Sign in').click();
2022-09-08 18:40:18 +02:00
});
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test.afterAll(async () => {
await page.close();
});
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test('first test', async () => {
// page is signed in.
});
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test('second test', async () => {
// page is signed in.
});
```
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
```js tab=js-ts
// example.spec.ts
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
import { test, Page } from '@playwright/test ';
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test.describe.configure({ mode: 'serial' });
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
let page: Page;
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
test.beforeAll(async ({ browser }) => {
// Create page once and sign in.
page = await browser.newPage();
await page.goto('https://github.com/login');
2022-10-04 09:29:26 -08:00
await page.getByLabel('User Name').fill('user');
await page.getByLabel('Password').fill('password');
2022-10-03 17:02:46 -07:00
await page.getByText('Sign in').click();
2022-09-08 18:40:18 +02:00
});
2022-08-16 22:00:54 -07:00
2022-09-08 18:40:18 +02:00
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.
:::
2022-08-16 22:00:54 -07:00
## Session storage
2022-09-08 18:40:18 +02:00
Rarely, [session storage ](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage ) is used for storing information associated with the logged-in state. Session storage is specific to a particular domain and is not persisted across page loads. Playwright does not provide API to persist session storage, but the following snippet can be used to save/load session storage.
2022-08-16 22:00:54 -07:00
```js
// Get session storage and store as env variable
const sessionStorage = await page.evaluate(() => JSON.stringify(sessionStorage));
process.env.SESSION_STORAGE = sessionStorage;
// Set session storage in a new context
const sessionStorage = process.env.SESSION_STORAGE;
await context.addInitScript(storage => {
if (window.location.hostname === 'example.com') {
const entries = JSON.parse(storage);
for (const [key, value] of Object.entries(entries)) {
window.sessionStorage.setItem(key, value);
}
}
}, sessionStorage);
```
```java
// Get session storage and store as env variable
String sessionStorage = (String) page.evaluate("JSON.stringify(sessionStorage)");
System.getenv().put("SESSION_STORAGE", sessionStorage);
// Set session storage in a new context
String sessionStorage = System.getenv("SESSION_STORAGE");
context.addInitScript("(storage => {\n" +
" if (window.location.hostname === 'example.com') {\n" +
" const entries = JSON.parse(storage);\n" +
" for (const [key, value] of Object.entries(entries)) {\n" +
" window.sessionStorage.setItem(key, value);\n" +
" };\n" +
" }\n" +
"})('" + sessionStorage + "')");
```
```python async
import os
# Get session storage and store as env variable
session_storage = await page.evaluate("() => JSON.stringify(sessionStorage)")
os.environ["SESSION_STORAGE"] = session_storage
# Set session storage in a new context
session_storage = os.environ["SESSION_STORAGE"]
await context.add_init_script("""(storage => {
if (window.location.hostname === 'example.com') {
const entries = JSON.parse(storage)
for (const [key, value] of Object.entries(entries)) {
2022-11-10 09:43:42 -08:00
window.sessionStorage.setItem(key, value)
2022-08-16 22:00:54 -07:00
}
}
})('""" + session_storage + "')")
```
```python sync
import os
# Get session storage and store as env variable
session_storage = page.evaluate("() => JSON.stringify(sessionStorage)")
os.environ["SESSION_STORAGE"] = session_storage
# Set session storage in a new context
session_storage = os.environ["SESSION_STORAGE"]
context.add_init_script("""(storage => {
if (window.location.hostname === 'example.com') {
const entries = JSON.parse(storage)
for (const [key, value] of Object.entries(entries)) {
2022-11-10 09:43:42 -08:00
window.sessionStorage.setItem(key, value)
2022-08-16 22:00:54 -07:00
}
}
})('""" + session_storage + "')")
```
```csharp
// Get session storage and store as env variable
var sessionStorage = await page.EvaluateAsync< string > ("() => JSON.stringify(sessionStorage)");
Environment.SetEnvironmentVariable("SESSION_STORAGE", sessionStorage);
// Set session storage in a new context
var loadedSessionStorage = Environment.GetEnvironmentVariable("SESSION_STORAGE");
await context.AddInitScriptAsync(@"(storage => {
if (window.location.hostname === 'example.com') {
const entries = JSON.parse(storage);
for (const [key, value] of Object.entries(entries)) {
window.sessionStorage.setItem(key, value);
}
}
})('" + loadedSessionStorage + "')");
```
## Multi-factor authentication
Accounts with multi-factor authentication (MFA) cannot be fully automated, and need
manual intervention. Persistent authentication can be used to partially automate
MFA scenarios.
### Persistent authentication
Note that persistent authentication is not suited for CI environments since it
relies on a disk location. User data directories are specific to browser types
and cannot be shared across browser types.
User data directories can be used with the [`method: BrowserType.launchPersistentContext` ] API.
```js
const { chromium } = require('playwright');
const userDataDir = '/path/to/directory';
const context = await chromium.launchPersistentContext(userDataDir, { headless: false });
// Execute login steps manually in the browser window
```
```java
import com.microsoft.playwright.*;
public class Example {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
BrowserType chromium = playwright.chromium();
Path userDataDir = Paths.get("/path/to/directory");
BrowserContext context = chromium.launchPersistentContext(userDataDir,
new BrowserType.LaunchPersistentContextOptions().setHeadless(false));
// Execute login steps manually in the browser window
}
}
}
```
```python async
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
user_data_dir = '/path/to/directory'
browser = await p.chromium.launch_persistent_context(user_data_dir, headless=False)
# Execute login steps manually in the browser window
asyncio.run(main())
```
```python sync
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
user_data_dir = '/path/to/directory'
browser = p.chromium.launch_persistent_context(user_data_dir, headless=False)
# Execute login steps manually in the browser window
```
```csharp
using Microsoft.Playwright;
class Program
{
public static async Task Main()
{
using var playwright = await Playwright.CreateAsync();
var chromium = playwright.Chromium;
var context = chromium.LaunchPersistentContextAsync(@"C:\path\to\directory\", new()
{
Headless = false
});
}
}
```
2022-09-08 18:40:18 +02:00
#### Lifecycle
2022-08-16 22:00:54 -07:00
1. Create a user data directory on disk.
1. Launch a persistent context with the user data directory and login the MFA account.
1. Reuse user data directory to run automation scenarios.
2022-09-08 18:40:18 +02:00
## Manually Reuse Signed in State
* langs: js
The following code snippet retrieves state from an authenticated context and creates a new context with that state.
2022-10-03 17:02:46 -07:00
```js
2022-09-08 18:40:18 +02:00
// Save storage state into the file.
await context.storageState({ path: 'state.json' });
// Create a new context with the saved storage state.
const context = await browser.newContext({ storageState: 'state.json' });
```