--- id: test-auth title: "Authentication" --- Tests written with Playwright execute in isolated clean-slate environments called [browser contexts](./browser-contexts.md). 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. ## 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. ::: ### Sign in via API request 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 js-flavor=js // 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(); } export default globalSetup; ``` ```js js-flavor=ts // 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; ``` ## 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. :::