diff --git a/docs/README.md b/docs/README.md index dfff872914..1c9c0a4749 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,6 +33,7 @@ Playwright is a library to automate [Chromium](https://www.chromium.org/Home), [ - [Navigation and Loading](./loading.md) - [Multi-page scenarios](./multi-pages.md) 1. Tutorials + - [Authentication](./auth.md) - [Page object models](./pom.md) 1. Integrations - [Test runners](./test-runners.md) diff --git a/docs/api.md b/docs/api.md index 819648b126..d8b0dff16d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -617,7 +617,7 @@ await browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667}); Provide credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication). -> **NOTE** Browsers may cache credentials that resulted in successful auth. That means passing different credentials after successful authentication or passing `null` to disable authentication is unreliable. Instead, create a separate browser context that will not have previous credentials cached. +> **NOTE** Browsers may cache credentials after successful authentication. Passing different credentials or passing `null` to disable authentication will be unreliable. To remove or replace credentials, create a new browser context instead. #### browserContext.setOffline(offline) - `offline` <[boolean]> Whether to emulate network being offline for the browser context. diff --git a/docs/auth.md b/docs/auth.md new file mode 100644 index 0000000000..2197b38397 --- /dev/null +++ b/docs/auth.md @@ -0,0 +1,178 @@ +# Authentication +Playwright can be used to automate scenarios that require authentication. + +Tests written with Playwright execute in isolated clean-slate environments called +[browser contexts](./core-concepts.md#browser-contexts). 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. + +> 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 [`browser.newContext`](./network.md#http-authentication). + + +- [Automate logging in](#automate-logging-in) +- [Reuse authentication state](#reuse-authentication-state) + * [Cookies](#cookies) + * [Local storage](#local-storage) + * [Session storage](#session-storage) + * [Lifecycle](#lifecycle) + * [Example](#example) + * [API reference](#api-reference) +- [Multi-factor authentication](#multi-factor-authentication) + * [Persistent authentication](#persistent-authentication) + * [Lifecycle](#lifecycle-1) + * [API reference](#api-reference-1) + + +## Automate logging in + +The Playwright API can automate interaction with a login form. See +[Input guide](./input.md) for more details. + +The following example automates login on GitHub. Once these steps are executed, +the browser context will be authenticated. + +```js +const page = await context.newPage(); +await page.goto('https://github.com/login'); + +// Interact with login form +await page.click('text=Login'); +await page.fill('input[name="login"]', USERNAME); +await page.fill('input[name="password"]', PASSWORD); +await page.click('text=Submit'); +// Verify app is logged in +``` + +These steps can be executed for every browser context. However, redoing login +for every test can slow down test execution. To prevent that, we will reuse +existing authentication state in new browser contexts. + +## Reuse authentication state + +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). +The Playwright API can be used to retrieve this state from authenticated contexts +and then load it into new contexts. + +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. + +The following code snippets retrieve state from an authenticated page/context and +load them into a new context. + +### Cookies + +```js +// Get cookies and store as an env variable +const cookies = await context.cookies(); +process.env.COOKIES = JSON.stringify(cookies); + +// Set cookies in a new context +const deserializedCookies = JSON.parse(process.env.COOKIES) +await context.addCookies(deserializedCookies); +``` + +### Local storage +Local storage ([`window.localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)) +is specific to a particular domain. + +```js +// Get local storage and store as env variable +const localStorage = await page.evaluate(() => JSON.stringify(window.localStorage)); +process.env.LOCAL_STORAGE = localStorage; + +// Set local storage in a new context +const localStorage = process.env.LOCAL_STORAGE; +await context.addInitScript(storage => { + if (window.location.hostname === 'example.com') { + const entries = JSON.parse(storage); + Object.keys(entries).forEach(key => { + window.localStorage.setItem(key, entries[key]); + }); + } +}, localStorage); +``` + +### Session storage +Session storage ([`window.sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage)) +is specific to a particular domain. + +```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); + Object.keys(entries).forEach(key => { + window.sessionStorage.setItem(key, entries[key]); + }); + } +}, sessionStorage); +``` + +### Lifecycle + +Logging in via the UI and then reusing authentication state can be combined to +implement **login once and run multiple scenarios**. The lifecycle looks like: + +1. Run tests (for example, with `npm run test`). +2. Login via UI and retrieve authentication state. + * In Jest, this can be executed in [`globalSetup`](https://jestjs.io/docs/en/configuration#globalsetup-string). +3. In each test, load authentication state in `beforeEach` or `beforeAll` step. + +This approach will also **work in CI environments**, since it does not rely +on any external state. + +### Example + +[This example script](examples/authentication.js) logs in on GitHub.com with +Chromium, and then reuses the logged in cookie state in WebKit. + +### API reference +- [class `BrowserContext`](./api.md#class-browsercontext) +- [`browserContext.cookies`](./api.md#browsercontextcookiesurls) +- [`browserContext.addCookies`](./api.md#browsercontextaddcookiescookies) +- [`page.evaluate`](./api.md#pageevaluatepagefunction-arg) +- [`browserContext.addInitScript`](./api.md#browsercontextaddinitscriptscript-arg) + +## 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 +Web browsers use a directory on disk to store user history, cookies, IndexedDB +and other local state. This disk location is called the [User data directory](https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md). + +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 `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 +``` + +### Lifecycle + +1. Create a user data directory on disk +2. Launch a persistent context with the user data directory and login the MFA account. +3. Reuse user data directory to run automation scenarios. + +### API reference +- [class `BrowserContext`](./api.md#class-browsercontext) +- [`browserType.launchPersistentContext`](./api.md#browsertypelaunchpersistentcontextuserdatadir-options) diff --git a/docs/core-concepts.md b/docs/core-concepts.md index 0263eb7bac..93a5f88e4b 100644 --- a/docs/core-concepts.md +++ b/docs/core-concepts.md @@ -70,10 +70,6 @@ const context = await browser.newContext({ }); ``` -#### Example - -[This script](examples/authentication.js) logs in on GitHub.com through Chromium, and then reuses the login cookies state in WebKit. This recipe can be used to speed up tests by logging in once and reusing login state. - #### API reference - [class `BrowserContext`](./api.md#class-browsercontext) diff --git a/docs/network.md b/docs/network.md index f0d5b0d019..d88e9490fd 100644 --- a/docs/network.md +++ b/docs/network.md @@ -28,12 +28,9 @@ const page = await context.newPage(); await page.goto('https://example.com'); ``` -You can also use [`browserContext.setHTTPCredentials`](./api.md#browsercontextsethttpcredentialshttpcredentials) to update HTTP credentials of an existing context. - #### API reference - [`browser.newContext([options])`](./api.md#browsernewcontextoptions) -- [`browserContext.setHTTPCredentials(httpCredentials)`](./api.md#browsercontextsethttpcredentialshttpcredentials)