Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/src/api/class-apirequestcontext.md
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,23 @@ context cookies from the response. The method will automatically follow redirect
- `localStorage` <[Array]<[Object]>>
- `name` <[string]>
- `value` <[string]>
- `indexedDB` ?<[Array]<[Object]>>
- `name` <[string]>
- `version` <[int]>
- `stores` <[Array]<[Object]>>
- `name` <[string]>
- `keyPath` ?<[string]>
- `keyPathArray` ?<[Array]<[string]>>
- `autoIncrement` <[boolean]>
- `indexes` <[Array]<[Object]>>
- `name` <[string]>
- `keyPath` ?<[string]>
- `keyPathArray` ?<[Array]<[string]>>
- `unique` <[boolean]>
- `multiEntry` <[boolean]>
- `records` <[Array]<[Object]>>
- `key` ?<[Object]>
- `value` <[Object]>

Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to the constructor.

Expand Down
21 changes: 19 additions & 2 deletions docs/src/api/class-browsercontext.md
Original file line number Diff line number Diff line change
Expand Up @@ -1511,8 +1511,25 @@ Whether to emulate network being offline for the browser context.
- `localStorage` <[Array]<[Object]>>
- `name` <[string]>
- `value` <[string]>

Returns storage state for this browser context, contains current cookies and local storage snapshot.
- `indexedDB` <[Array]<[Object]>>
- `name` <[string]>
- `version` <[int]>
- `stores` <[Array]<[Object]>>
- `name` <[string]>
- `keyPath` ?<[string]>
- `keyPathArray` ?<[Array]<[string]>>
- `autoIncrement` <[boolean]>
- `indexes` <[Array]<[Object]>>
- `name` <[string]>
- `keyPath` ?<[string]>
- `keyPathArray` ?<[Array]<[string]>>
- `unique` <[boolean]>
- `multiEntry` <[boolean]>
- `records` <[Array]<[Object]>>
- `key` ?<[Object]>
- `value` <[Object]>

Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.

## async method: BrowserContext.storageState
* since: v1.8
Expand Down
21 changes: 19 additions & 2 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,11 +259,28 @@ Specify environment variables that will be visible to the browser. Defaults to `
- `httpOnly` <[boolean]>
- `secure` <[boolean]>
- `sameSite` <[SameSiteAttribute]<"Strict"|"Lax"|"None">> sameSite flag
- `origins` <[Array]<[Object]>> localStorage to set for context
- `origins` <[Array]<[Object]>>
- `origin` <[string]>
- `localStorage` <[Array]<[Object]>>
- `localStorage` <[Array]<[Object]>> localStorage to set for context
- `name` <[string]>
- `value` <[string]>
- `indexedDB` ?<[Array]<[Object]>> indexedDB to set for context
- `name` <[string]> database name
- `version` <[int]> database version
- `stores` <[Array]<[Object]>>
- `name` <[string]>
- `keyPath` ?<[string]>
- `keyPathArray` ?<[Array]<[string]>>
- `autoIncrement` <[boolean]>
- `indexes` <[Array]<[Object]>>
- `name` <[string]>
- `keyPath` ?<[string]>
- `keyPathArray` ?<[Array]<[string]>>
- `unique` <[boolean]>
- `multiEntry` <[boolean]>
- `records` <[Array]<[Object]>>
- `key` ?<[Object]>
- `value` <[Object]>

Learn more about [storage state and auth](../auth.md).

Expand Down
6 changes: 3 additions & 3 deletions docs/src/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,9 @@ existing authentication state instead.
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.

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 [`method: BrowserContext.storageState`] method that can be used to retrieve storage state from authenticated contexts and then create new contexts with prepopulated 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), in [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) or in [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API). Playwright provides [`method: BrowserContext.storageState`] method that can be used to retrieve storage state from authenticated contexts and then create new contexts with prepopulated state.

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.
Cookies, local storage and IndexedDB state can be used across different browsers. They depend on your application's authentication model which may require some combination of cookies, local storage or IndexedDB.

The following code snippet retrieves state from an authenticated context and creates a new context with that state.

Expand Down Expand Up @@ -583,7 +583,7 @@ test('admin and user', async ({ adminPage, userPage }) => {

### Session storage

Reusing authenticated state covers [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) based authentication. Rarely, [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) is used for storing information associated with the signed-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.
Reusing authenticated state covers [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies), [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage) and [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) based authentication. Rarely, [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) is used for storing information associated with the signed-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.

```js
// Get session storage and store as env variable
Expand Down
4 changes: 2 additions & 2 deletions docs/src/codegen.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ pwsh bin/Debug/netX/playwright.ps1 codegen --timezone="Europe/Rome" --geolocatio

### Preserve authenticated state

Run `codegen` with `--save-storage` to save [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) at the end of the session. This is useful to separately record an authentication step and reuse it later when recording more tests.
Run `codegen` with `--save-storage` to save [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies), [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) data at the end of the session. This is useful to separately record an authentication step and reuse it later when recording more tests.

```bash js
npx playwright codegen github.com/microsoft/playwright --save-storage=auth.json
Expand Down Expand Up @@ -375,7 +375,7 @@ Make sure you only use the `auth.json` locally as it contains sensitive informat

#### Load authenticated state

Run with `--load-storage` to consume the previously loaded storage from the `auth.json`. This way, all [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) and [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) will be restored, bringing most web apps to the authenticated state without the need to login again. This means you can continue generating tests from the logged in state.
Run with `--load-storage` to consume the previously loaded storage from the `auth.json`. This way, all [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies), [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) data will be restored, bringing most web apps to the authenticated state without the need to login again. This means you can continue generating tests from the logged in state.

```bash js
npx playwright codegen --load-storage=auth.json github.com/microsoft/playwright
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { Worker } from './worker';
import { Events } from './events';
import { TimeoutSettings } from '../common/timeoutSettings';
import { Waiter } from './waiter';
import type { Headers, WaitForEventOptions, BrowserContextOptions, StorageState, LaunchOptions } from './types';
import type { Headers, WaitForEventOptions, BrowserContextOptions, LaunchOptions, StorageState } from './types';
import { type URLMatch, headersObjectToArray, isRegExp, isString, urlMatchesEqual, mkdirIfNeeded } from '../utils';
import type * as api from '../../types/types';
import type * as structs from '../../types/structs';
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export type StorageState = {
};
export type SetStorageState = {
cookies?: channels.SetNetworkCookie[],
origins?: channels.OriginStorage[]
origins?: channels.SetOriginStorage[]
};

export type LifecycleEvent = channels.LifecycleEvent;
Expand Down
33 changes: 30 additions & 3 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,36 @@ scheme.NameValue = tObject({
name: tString,
value: tString,
});
scheme.IndexedDBDatabase = tObject({
name: tString,
version: tNumber,
stores: tArray(tObject({
name: tString,
autoIncrement: tBoolean,
keyPath: tOptional(tString),
keyPathArray: tOptional(tArray(tString)),
records: tArray(tObject({
key: tOptional(tAny),
value: tAny,
})),
indexes: tArray(tObject({
name: tString,
keyPath: tOptional(tString),
keyPathArray: tOptional(tArray(tString)),
multiEntry: tBoolean,
unique: tBoolean,
})),
})),
});
scheme.SetOriginStorage = tObject({
origin: tString,
localStorage: tArray(tType('NameValue')),
indexedDB: tOptional(tArray(tType('IndexedDBDatabase'))),
});
scheme.OriginStorage = tObject({
origin: tString,
localStorage: tArray(tType('NameValue')),
indexedDB: tArray(tType('IndexedDBDatabase')),
});
scheme.SerializedError = tObject({
error: tOptional(tObject({
Expand Down Expand Up @@ -361,7 +388,7 @@ scheme.PlaywrightNewRequestParams = tObject({
timeout: tOptional(tNumber),
storageState: tOptional(tObject({
cookies: tOptional(tArray(tType('NetworkCookie'))),
origins: tOptional(tArray(tType('OriginStorage'))),
origins: tOptional(tArray(tType('SetOriginStorage'))),
})),
tracesDir: tOptional(tString),
});
Expand Down Expand Up @@ -687,7 +714,7 @@ scheme.BrowserNewContextParams = tObject({
})),
storageState: tOptional(tObject({
cookies: tOptional(tArray(tType('SetNetworkCookie'))),
origins: tOptional(tArray(tType('OriginStorage'))),
origins: tOptional(tArray(tType('SetOriginStorage'))),
})),
});
scheme.BrowserNewContextResult = tObject({
Expand Down Expand Up @@ -756,7 +783,7 @@ scheme.BrowserNewContextForReuseParams = tObject({
})),
storageState: tOptional(tObject({
cookies: tOptional(tArray(tType('SetNetworkCookie'))),
origins: tOptional(tArray(tType('OriginStorage'))),
origins: tOptional(tArray(tType('SetOriginStorage'))),
})),
});
scheme.BrowserNewContextForReuseResult = tObject({
Expand Down
26 changes: 9 additions & 17 deletions packages/playwright-core/src/server/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import type { Artifact } from './artifact';
import { Clock } from './clock';
import type { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
import { RecorderApp } from './recorder/recorderApp';
import * as storageScript from './injected/storageScript';

export abstract class BrowserContext extends SdkObject {
static Events = {
Expand Down Expand Up @@ -519,11 +520,9 @@ export abstract class BrowserContext extends SdkObject {
if (!origin || !originsToSave.has(origin))
continue;
try {
const storage = await page.mainFrame().nonStallingEvaluateInExistingContext(`({
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
})`, 'utility');
if (storage.localStorage.length)
result.origins.push({ origin, localStorage: storage.localStorage } as channels.OriginStorage);
const storage: Awaited<ReturnType<typeof storageScript.collect>> = await page.mainFrame().nonStallingEvaluateInExistingContext(`(${storageScript.collect})()`, 'utility');
if (storage.localStorage.length || storage.indexedDB?.length)
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB });
originsToSave.delete(origin);
} catch {
// When failed on the live page, we'll retry on the blank page below.
Expand All @@ -539,15 +538,11 @@ export abstract class BrowserContext extends SdkObject {
return true;
});
for (const origin of originsToSave) {
const originStorage: channels.OriginStorage = { origin, localStorage: [] };
const frame = page.mainFrame();
await frame.goto(internalMetadata, origin);
const storage = await frame.evaluateExpression(`({
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
})`, { world: 'utility' });
originStorage.localStorage = storage.localStorage;
if (storage.localStorage.length)
result.origins.push(originStorage);
const storage: Awaited<ReturnType<typeof storageScript.collect>> = await frame.evaluateExpression(`(${storageScript.collect})()`, { world: 'utility' });
if (storage.localStorage.length || storage.indexedDB.length)
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB });
}
await page.close(internalMetadata);
}
Expand Down Expand Up @@ -610,11 +605,8 @@ export abstract class BrowserContext extends SdkObject {
for (const originState of state.origins) {
const frame = page.mainFrame();
await frame.goto(metadata, originState.origin);
await frame.evaluateExpression(`
originState => {
for (const { name, value } of (originState.localStorage || []))
localStorage.setItem(name, value);
}`, { isFunction: true, world: 'utility' }, originState);
const args: Parameters<typeof storageScript.restore> = [originState];
await frame.evaluateExpression(storageScript.restore.toString(), { isFunction: true, world: 'utility' }, args);
}
await page.close(internalMetadata);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ export class GlobalAPIRequestContext extends APIRequestContext {
proxy.server = url;
}
if (options.storageState) {
this._origins = options.storageState.origins;
this._origins = options.storageState.origins?.map(origin => ({ indexedDB: [], ...origin }));
this._cookieStore.addCookies(options.storageState.cookies || []);
}
verifyClientCertificates(options.clientCertificates);
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1717,7 +1717,7 @@ export class Frame extends SdkObject {
}, { source, arg });
}

async resetStorageForCurrentOriginBestEffort(newStorage: channels.OriginStorage | undefined) {
async resetStorageForCurrentOriginBestEffort(newStorage: channels.SetOriginStorage | undefined) {
const context = await this._utilityContext();
await context.evaluate(async ({ ls }) => {
// Clean DOMStorage.
Expand Down
Loading
Loading