This commit is contained in:
Simon Knott 2025-02-04 10:56:05 +01:00
parent e26bd2a0c0
commit da0ad1ef75
No known key found for this signature in database
GPG key ID: 8CEDC00028084AEC
8 changed files with 153 additions and 43 deletions

View file

@ -1511,8 +1511,25 @@ Whether to emulate network being offline for the browser context.
- `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` ?<[string]>
- `value` <[string]>
Returns storage state for this browser context, contains current cookies and local storage snapshot.
Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
## async method: BrowserContext.storageState
* since: v1.8

View file

@ -265,7 +265,22 @@ Specify environment variables that will be visible to the browser. Defaults to `
- `name` <[string]>
- `value` <[string]>
- `indexedDB` ?<[Array]<[Object]>>
- `name` <[string]> TODO: document more
- `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` ?<[string]>
- `value` <[string]>
Learn more about [storage state and auth](../auth.md).

View file

@ -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
@ -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

View file

@ -166,7 +166,7 @@ scheme.IndexedDBDatabase = tObject({
scheme.OriginStorage = tObject({
origin: tString,
localStorage: tArray(tType('NameValue')),
indexedDB: tOptional(tArray(tType('IndexedDBDatabase'))),
indexedDB: tArray(tType('IndexedDBDatabase')),
});
scheme.SerializedError = tObject({
error: tOptional(tObject({

View file

@ -515,20 +515,16 @@ export abstract class BrowserContext extends SdkObject {
const originsToSave = new Set(this._origins);
async function _collectStorageScript() {
async function _collectDatabase(dbInfo: IDBDatabaseInfo) {
if (!dbInfo.name)
return;
let db: IDBDatabase;
try {
db = await new Promise((resolve, reject) => {
const request = indexedDB.open(dbInfo.name!);
request.onerror = reject;
request.onsuccess = () => resolve(request.result);
});
} catch {
return;
}
const idbResult = await Promise.all((await indexedDB.databases()).map(async dbInfo => {
if (!dbInfo.name)
throw new Error('Database name is empty');
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(dbInfo.name!);
request.onerror = reject;
request.onsuccess = () => resolve(request.result);
});
const transaction = db.transaction(db.objectStoreNames, 'readonly');
const stores = await Promise.all([...db.objectStoreNames].map(async storeName => {
@ -540,22 +536,19 @@ export abstract class BrowserContext extends SdkObject {
});
const records = await Promise.all(keys.map(async key => {
const record = await new Promise<any[]>((resolve, reject) => {
const record = await new Promise<any>((resolve, reject) => {
const request = objectStore.get(key);
request.addEventListener('success', () => resolve(request.result));
request.addEventListener('error', reject);
});
if (!record)
return;
return {
key: objectStore.keyPath === null ? key.toString() : undefined,
value: record
};
}));
const indexes = await Promise.all([...objectStore.indexNames].map(async indexName => {
const indexes = [...objectStore.indexNames].map(indexName => {
const index = objectStore.index(indexName);
return {
name: index.name,
@ -564,7 +557,7 @@ export abstract class BrowserContext extends SdkObject {
multiEntry: index.multiEntry,
unique: index.unique,
};
}));
});
return {
name: storeName,
@ -581,13 +574,11 @@ export abstract class BrowserContext extends SdkObject {
version: dbInfo.version,
stores,
};
}
const idbResult = await Promise.all((await indexedDB.databases()).map(_collectDatabase).filter(Boolean));
}));
return {
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
indexedDB: idbResult.length ? idbResult : undefined,
indexedDB: idbResult,
};
}
@ -607,8 +598,7 @@ export abstract class BrowserContext extends SdkObject {
continue;
try {
const storage = await page.mainFrame().nonStallingEvaluateInExistingContext(`(${_collectStorageScript.toString()})()`, 'utility');
if (storage.indexedDB?.length)
serializeRecords(storage.indexedDB);
serializeRecords(storage.indexedDB);
if (storage.localStorage.length || storage.indexedDB?.length)
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB } as channels.OriginStorage);
originsToSave.delete(origin);
@ -629,9 +619,8 @@ export abstract class BrowserContext extends SdkObject {
const frame = page.mainFrame();
await frame.goto(internalMetadata, origin);
const storage = await frame.evaluateExpression(`(${_collectStorageScript.toString()})()`, { world: 'utility' });
if (storage.indexedDB?.length)
serializeRecords(storage.indexedDB);
if (storage.localStorage.length || storage.indexedDB?.length)
serializeRecords(storage.indexedDB);
if (storage.localStorage.length || storage.indexedDB.length)
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB } as channels.OriginStorage);
}
await page.close(internalMetadata);
@ -707,7 +696,7 @@ export abstract class BrowserContext extends SdkObject {
for (const { name, value } of (originState.localStorage || []))
localStorage.setItem(name, value);
await Promise.all((originState.indexedDB || []).map(async dbInfo => {
await Promise.all(originState.indexedDB.map(async dbInfo => {
await new Promise<void>((resolve, reject) => {
const openRequest = indexedDB.open(dbInfo.name, dbInfo.version);
openRequest.addEventListener('upgradeneeded', () => {

View file

@ -9260,7 +9260,8 @@ export interface BrowserContext {
setOffline(offline: boolean): Promise<void>;
/**
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
* Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB
* snapshot.
* @param options
*/
storageState(options?: {
@ -9301,6 +9302,40 @@ export interface BrowserContext {
value: string;
}>;
indexedDB: Array<{
name: string;
version: number;
stores: Array<{
name: string;
keyPath?: string;
keyPathArray?: Array<string>;
autoIncrement: boolean;
indexes: Array<{
name: string;
keyPath?: string;
keyPathArray?: Array<string>;
unique: boolean;
multiEntry: boolean;
}>;
records: Array<{
key?: string;
value: string;
}>;
}>;
}>;
}>;
}>;
@ -10062,10 +10097,37 @@ export interface Browser {
}>;
indexedDB?: Array<{
/**
* TODO: document more
*/
name: string;
version: number;
stores: Array<{
name: string;
keyPath?: string;
keyPathArray?: Array<string>;
autoIncrement: boolean;
indexes: Array<{
name: string;
keyPath?: string;
keyPathArray?: Array<string>;
unique: boolean;
multiEntry: boolean;
}>;
records: Array<{
key?: string;
value: string;
}>;
}>;
}>;
}>;
};
@ -22236,10 +22298,37 @@ export interface BrowserContextOptions {
}>;
indexedDB?: Array<{
/**
* TODO: document more
*/
name: string;
version: number;
stores: Array<{
name: string;
keyPath?: string;
keyPathArray?: Array<string>;
autoIncrement: boolean;
indexes: Array<{
name: string;
keyPath?: string;
keyPathArray?: Array<string>;
unique: boolean;
multiEntry: boolean;
}>;
records: Array<{
key?: string;
value: string;
}>;
}>;
}>;
}>;
};

View file

@ -296,7 +296,7 @@ export type IndexedDBDatabase = {
export type OriginStorage = {
origin: string,
localStorage: NameValue[],
indexedDB?: IndexedDBDatabase[],
indexedDB: IndexedDBDatabase[],
};
export type SerializedError = {

View file

@ -262,7 +262,7 @@ OriginStorage:
type: array
items: NameValue
indexedDB:
type: array?
type: array
items: IndexedDBDatabase
SerializedError: