2021-09-21 13:20:50 -07:00
---
id: test-auth
title: "Authentication"
---
Tests written with Playwright execute in isolated clean-slate environments called
2021-11-09 07:42:04 -08:00
[browser contexts ](./browser-contexts.md ). Each test gets a brand
2021-09-21 13:20:50 -07:00
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.
2022-06-10 16:34:31 -08:00
```js tab=js-ts
2021-09-21 13:20:50 -07:00
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.
});
```
2022-06-10 16:34:31 -08:00
```js tab=js-js
2021-09-21 13:20:50 -07:00
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:
2022-06-10 16:34:31 -08:00
```js tab=js-js
2021-09-21 13:20:50 -07:00
// 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');
2022-04-05 22:13:02 +02:00
await page.fill('input[name="login"]', 'user');
2021-09-21 13:20:50 -07:00
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();
};
```
2022-06-10 16:34:31 -08:00
```js tab=js-ts
2021-09-21 13:20:50 -07:00
// 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');
2022-04-05 22:13:02 +02:00
await page.fill('input[name="login"]', 'user');
2021-09-21 13:20:50 -07:00
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:
2022-06-10 16:34:31 -08:00
```js tab=js-ts
2021-09-21 13:20:50 -07:00
// playwright.config.ts
2022-05-27 12:36:59 -07:00
import type { PlaywrightTestConfig } from '@playwright/test ';
2021-09-21 13:20:50 -07:00
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;
```
2022-06-10 16:34:31 -08:00
```js tab=js-js
2021-09-21 13:20:50 -07:00
// 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.
2022-06-10 16:34:31 -08:00
```js tab=js-ts
2021-09-21 13:20:50 -07:00
import { test } from '@playwright/test ';
test('test', async ({ page }) => {
// page is signed in.
});
```
2022-06-10 16:34:31 -08:00
```js tab=js-js
2021-09-21 13:20:50 -07:00
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.
:::
2021-10-18 15:03:45 -07:00
### Sign in via API request
2021-10-19 07:38:27 -07:00
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:
2021-10-18 15:03:45 -07:00
2022-06-10 16:34:31 -08:00
```js tab=js-js
2021-10-18 15:03:45 -07:00
// global-setup.js
const { request } = require('@playwright/test ');
module.exports = async () => {
const requestContext = await request.newContext();
await requestContext.post('https://github.com/login', {
form: {
'user': 'user',
'password': 'password'
}
});
// Save signed-in state to 'storageState.json'.
await requestContext.storageState({ path: 'storageState.json' });
await requestContext.dispose();
}
```
2022-06-10 16:34:31 -08:00
```js tab=js-ts
2021-10-18 15:03:45 -07:00
// global-setup.ts
import { request } from '@playwright/test ';
async function globalSetup() {
const requestContext = await request.newContext();
await requestContext.post('https://github.com/login', {
form: {
'user': 'user',
'password': 'password'
}
});
// Save signed-in state to 'storageState.json'.
await requestContext.storageState({ path: 'storageState.json' });
await requestContext.dispose();
}
export default globalSetup;
```
2021-09-21 15:02:47 -07:00
## Multiple signed in roles
2021-09-21 13:20:50 -07: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 globalSetup and saving that state into different files.
2022-06-10 16:34:31 -08:00
```js tab=js-js
2021-09-21 13:20:50 -07:00
// 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();
};
```
2022-06-10 16:34:31 -08:00
```js tab=js-ts
2021-09-21 13:20:50 -07:00
// 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:
2022-06-10 16:34:31 -08:00
```js tab=js-ts
2021-09-21 13:20:50 -07:00
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.
});
});
```
2022-06-10 16:34:31 -08:00
```js tab=js-js
2021-09-21 13:20:50 -07:00
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.
});
});
```
2022-06-20 16:05:43 -07:00
### Testing multiple roles together
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 files would work.
```js tab=js-ts
import { test } from '@playwright/test ';
test('admin and user', async ({ browser }) => {
// adminContext and all pages inside, including adminPage, are signed in as "admin".
const adminContext = await browser.newContext({ storageState: 'adminStorageState.json' });
const adminPage = await adminContext.newPage();
// userContext and all pages inside, including userPage, are signed in as "user".
const userContext = await browser.newContext({ storageState: 'userStorageState.json' });
const userPage = await userContext.newPage();
// ... interact with both adminPage and userPage ...
});
```
```js tab=js-js
const { test } = require('@playwright/test ');
test('admin and user', async ({ browser }) => {
// adminContext and all pages inside, including adminPage, are signed in as "admin".
const adminContext = await browser.newContext({ storageState: 'adminStorageState.json' });
const adminPage = await adminContext.newPage();
// userContext and all pages inside, including userPage, are signed in as "user".
const userContext = await browser.newContext({ storageState: 'userStorageState.json' });
const userPage = await userContext.newPage();
// ... interact with both adminPage and userPage ...
});
```
### Testing multiple roles with POM fixtures
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 files would work.
Below is an example that [creates fixtures ](./test-fixtures.md#creating-a-fixture ) for two [Page Object Models ](./test-pom.md ) - admin POM and user POM. It assumes `adminStorageState.json` and `userStorageState.json` files were created.
```js tab=js-ts
// fixtures.ts
import { test as base, Page, Browser, Locator } from '@playwright/test ';
export { expect } from '@playwright/test ';
// 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;
}
async create(browser: Browser) {
const context = await browser.newContext({ storageState: 'adminStorageState.json' });
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 ');
}
async create(browser: Browser) {
const context = await browser.newContext({ storageState: 'userStorageState.json' });
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
const { test: base } = require('@playwright/test ');
// 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;
}
async create(browser) {
const context = await browser.newContext({ storageState: 'adminStorageState.json' });
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 ');
}
async create(browser) {
const context = await browser.newContext({ storageState: 'userStorageState.json' });
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');
});
```
2021-09-21 15:02:47 -07:00
## Reuse the signed in page in multiple tests
2021-09-21 13:20:50 -07: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-06-10 16:34:31 -08:00
```js tab=js-js
2021-09-21 13:20:50 -07:00
// example.spec.js
// @ts -check
const { test } = require('@playwright/test ');
2022-02-02 20:44:11 -08:00
test.describe.configure({ mode: 'serial' });
2021-09-21 13:20:50 -07:00
2022-02-02 20:44:11 -08:00
/** @type {import('@playwright/test ').Page} */
let page;
2021-09-21 13:20:50 -07:00
2022-02-02 20:44:11 -08:00
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');
});
2021-09-21 13:20:50 -07:00
2022-02-02 20:44:11 -08:00
test.afterAll(async () => {
await page.close();
});
test('first test', async () => {
// page is signed in.
});
test('second test', async () => {
// page is signed in.
2021-09-21 13:20:50 -07:00
});
```
2022-06-10 16:34:31 -08:00
```js tab=js-ts
2021-09-21 13:20:50 -07:00
// example.spec.ts
import { test, Page } from '@playwright/test ';
2022-02-02 20:44:11 -08:00
test.describe.configure({ mode: 'serial' });
2021-09-21 13:20:50 -07:00
2022-02-02 20:44:11 -08:00
let page: Page;
2021-09-21 13:20:50 -07:00
2022-02-02 20:44:11 -08:00
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');
});
2021-09-21 13:20:50 -07:00
2022-02-02 20:44:11 -08:00
test.afterAll(async () => {
await page.close();
});
2021-09-21 13:20:50 -07:00
2022-02-02 20:44:11 -08:00
test('first test', async () => {
// page is signed in.
});
test('second test', async () => {
// page is signed in.
2021-09-21 13:20:50 -07:00
});
```
:::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.
:::