From 902e83fe87a6014af359e6b646faf088a387805c Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Thu, 6 Feb 2025 16:40:14 +0100 Subject: [PATCH] fix: allow opt out from `IndexedDB` in storagestate (#34650) --- docs/src/api/class-apirequestcontext.md | 6 ++++++ docs/src/api/class-browsercontext.md | 6 ++++++ .../src/client/browserContext.ts | 4 ++-- packages/playwright-core/src/client/fetch.ts | 4 ++-- .../playwright-core/src/protocol/validator.ts | 8 ++++++-- .../src/server/browserContext.ts | 4 ++-- .../dispatchers/browserContextDispatcher.ts | 2 +- .../server/dispatchers/networkDispatchers.ts | 4 ++-- packages/playwright-core/src/server/fetch.ts | 10 +++++----- .../src/server/storageScript.ts | 12 +++++------ packages/playwright-core/types/types.d.ts | 10 ++++++++++ packages/protocol/src/channels.d.ts | 20 +++++++++++++------ packages/protocol/src/protocol.yml | 4 ++++ .../browsercontext-storage-state.spec.ts | 2 ++ 14 files changed, 68 insertions(+), 28 deletions(-) diff --git a/docs/src/api/class-apirequestcontext.md b/docs/src/api/class-apirequestcontext.md index b8c179de4f..838625bcbf 100644 --- a/docs/src/api/class-apirequestcontext.md +++ b/docs/src/api/class-apirequestcontext.md @@ -909,3 +909,9 @@ Returns storage state for this request context, contains current cookies and loc ### option: APIRequestContext.storageState.path = %%-storagestate-option-path-%% * since: v1.16 + +### option: APIRequestContext.storageState.indexedDB +* since: v1.51 +- `indexedDB` ? + +Defaults to `true`. Set to `false` to omit IndexedDB from snapshot. diff --git a/docs/src/api/class-browsercontext.md b/docs/src/api/class-browsercontext.md index 797786afac..156a577504 100644 --- a/docs/src/api/class-browsercontext.md +++ b/docs/src/api/class-browsercontext.md @@ -1545,6 +1545,12 @@ IndexedDBs with typed arrays are currently not supported. ### option: BrowserContext.storageState.path = %%-storagestate-option-path-%% * since: v1.8 +### option: BrowserContext.storageState.indexedDB +* since: v1.51 +- `indexedDB` ? + +Defaults to `true`. Set to `false` to omit IndexedDB from snapshot. + ## property: BrowserContext.tracing * since: v1.12 - type: <[Tracing]> diff --git a/packages/playwright-core/src/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index c87fc5bc7d..83655887e6 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -425,8 +425,8 @@ export class BrowserContext extends ChannelOwner }); } - async storageState(options: { path?: string } = {}): Promise { - const state = await this._channel.storageState(); + async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise { + const state = await this._channel.storageState({ indexedDB: options.indexedDB }); if (options.path) { await mkdirIfNeeded(options.path); await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); diff --git a/packages/playwright-core/src/client/fetch.ts b/packages/playwright-core/src/client/fetch.ts index efacce417d..3bf56720b5 100644 --- a/packages/playwright-core/src/client/fetch.ts +++ b/packages/playwright-core/src/client/fetch.ts @@ -260,8 +260,8 @@ export class APIRequestContext extends ChannelOwner { - const state = await this._channel.storageState(); + async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise { + const state = await this._channel.storageState({ indexedDB: options.indexedDB }); if (options.path) { await mkdirIfNeeded(options.path); await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index 35832219ee..d1e7c3d63c 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -234,7 +234,9 @@ scheme.APIRequestContextFetchLogParams = tObject({ scheme.APIRequestContextFetchLogResult = tObject({ log: tArray(tString), }); -scheme.APIRequestContextStorageStateParams = tOptional(tObject({})); +scheme.APIRequestContextStorageStateParams = tObject({ + indexedDB: tOptional(tBoolean), +}); scheme.APIRequestContextStorageStateResult = tObject({ cookies: tArray(tType('NetworkCookie')), origins: tArray(tType('OriginStorage')), @@ -992,7 +994,9 @@ scheme.BrowserContextSetOfflineParams = tObject({ offline: tBoolean, }); scheme.BrowserContextSetOfflineResult = tOptional(tObject({})); -scheme.BrowserContextStorageStateParams = tOptional(tObject({})); +scheme.BrowserContextStorageStateParams = tObject({ + indexedDB: tOptional(tBoolean), +}); scheme.BrowserContextStorageStateResult = tObject({ cookies: tArray(tType('NetworkCookie')), origins: tArray(tType('OriginStorage')), diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index d75be1c34d..c4469f52b0 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -508,14 +508,14 @@ export abstract class BrowserContext extends SdkObject { this._origins.add(origin); } - async storageState(): Promise { + async storageState(indexedDB = true): Promise { const result: channels.BrowserContextStorageStateResult = { cookies: await this.cookies(), origins: [] }; const originsToSave = new Set(this._origins); - const collectScript = `(${storageScript.collect})((${utilityScriptSerializers.source})(), ${this._browser.options.name === 'firefox'})`; + const collectScript = `(${storageScript.collect})((${utilityScriptSerializers.source})(), ${this._browser.options.name === 'firefox'}, ${indexedDB})`; // First try collecting storage stage from existing pages. for (const page of this.pages()) { diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index 0dcfb23e0a..9ee416ce85 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -291,7 +291,7 @@ export class BrowserContextDispatcher extends Dispatcher { - return await this._context.storageState(); + return await this._context.storageState(params.indexedDB); } async close(params: channels.BrowserContextCloseParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts index ba600f697e..6c2468176a 100644 --- a/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts +++ b/packages/playwright-core/src/server/dispatchers/networkDispatchers.ts @@ -194,8 +194,8 @@ export class APIRequestContextDispatcher extends Dispatcher { - return this._object.storageState(); + async storageState(params: channels.APIRequestContextStorageStateParams): Promise { + return this._object.storageState(params.indexedDB); } async dispose(params: channels.APIRequestContextDisposeParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/fetch.ts b/packages/playwright-core/src/server/fetch.ts index 44602e0c82..778fbcee95 100644 --- a/packages/playwright-core/src/server/fetch.ts +++ b/packages/playwright-core/src/server/fetch.ts @@ -133,7 +133,7 @@ export abstract class APIRequestContext extends SdkObject { abstract _defaultOptions(): FetchRequestOptions; abstract _addCookies(cookies: channels.NetworkCookie[]): Promise; abstract _cookies(url: URL): Promise; - abstract storageState(): Promise; + abstract storageState(indexedDB?: boolean): Promise; private _storeResponseBody(body: Buffer): string { const uid = createGuid(); @@ -618,8 +618,8 @@ export class BrowserContextAPIRequestContext extends APIRequestContext { return await this._context.cookies(url.toString()); } - override async storageState(): Promise { - return this._context.storageState(); + override async storageState(indexedDB?: boolean): Promise { + return this._context.storageState(indexedDB); } } @@ -684,10 +684,10 @@ export class GlobalAPIRequestContext extends APIRequestContext { return this._cookieStore.cookies(url); } - override async storageState(): Promise { + override async storageState(indexedDB = true): Promise { return { cookies: this._cookieStore.allCookies(), - origins: this._origins || [] + origins: (this._origins || []).map(origin => ({ ...origin, indexedDB: indexedDB ? origin.indexedDB : [] })), }; } } diff --git a/packages/playwright-core/src/server/storageScript.ts b/packages/playwright-core/src/server/storageScript.ts index 517a1529d3..04e6b3f6d1 100644 --- a/packages/playwright-core/src/server/storageScript.ts +++ b/packages/playwright-core/src/server/storageScript.ts @@ -19,8 +19,8 @@ import type { source } from './isomorphic/utilityScriptSerializers'; export type Storage = Omit; -export async function collect(serializers: ReturnType, isFirefox: boolean): Promise { - const idbResult = await Promise.all((await indexedDB.databases()).map(async dbInfo => { +export async function collect(serializers: ReturnType, isFirefox: boolean, recordIndexedDB: boolean): Promise { + async function collectDB(dbInfo: IDBDatabaseInfo) { if (!dbInfo.name) throw new Error('Database name is empty'); if (!dbInfo.version) @@ -119,13 +119,13 @@ export async function collect(serializers: ReturnType, isFirefox: version: dbInfo.version, stores, }; - })).catch(e => { - throw new Error('Unable to serialize IndexedDB: ' + e.message); - }); + } return { localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name)! })), - indexedDB: idbResult, + indexedDB: recordIndexedDB ? await Promise.all((await indexedDB.databases()).map(collectDB)).catch(e => { + throw new Error('Unable to serialize IndexedDB: ' + e.message); + }) : [], }; } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index cc77a1f4fc..5a74ee6d76 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -9274,6 +9274,11 @@ export interface BrowserContext { * @param options */ storageState(options?: { + /** + * Defaults to `true`. Set to `false` to omit IndexedDB from snapshot. + */ + indexedDB?: boolean; + /** * The file path to save the storage state to. If * [`path`](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state-option-path) is a @@ -18534,6 +18539,11 @@ export interface APIRequestContext { * @param options */ storageState(options?: { + /** + * Defaults to `true`. Set to `false` to omit IndexedDB from snapshot. + */ + indexedDB?: boolean; + /** * The file path to save the storage state to. If * [`path`](https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-storage-state-option-path) is diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index b0ccdcbc2a..522bbb779e 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -346,7 +346,7 @@ export interface APIRequestContextChannel extends APIRequestContextEventTarget, fetch(params: APIRequestContextFetchParams, metadata?: CallMetadata): Promise; fetchResponseBody(params: APIRequestContextFetchResponseBodyParams, metadata?: CallMetadata): Promise; fetchLog(params: APIRequestContextFetchLogParams, metadata?: CallMetadata): Promise; - storageState(params?: APIRequestContextStorageStateParams, metadata?: CallMetadata): Promise; + storageState(params: APIRequestContextStorageStateParams, metadata?: CallMetadata): Promise; disposeAPIResponse(params: APIRequestContextDisposeAPIResponseParams, metadata?: CallMetadata): Promise; dispose(params: APIRequestContextDisposeParams, metadata?: CallMetadata): Promise; } @@ -402,8 +402,12 @@ export type APIRequestContextFetchLogOptions = { export type APIRequestContextFetchLogResult = { log: string[], }; -export type APIRequestContextStorageStateParams = {}; -export type APIRequestContextStorageStateOptions = {}; +export type APIRequestContextStorageStateParams = { + indexedDB?: boolean, +}; +export type APIRequestContextStorageStateOptions = { + indexedDB?: boolean, +}; export type APIRequestContextStorageStateResult = { cookies: NetworkCookie[], origins: OriginStorage[], @@ -1564,7 +1568,7 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT setNetworkInterceptionPatterns(params: BrowserContextSetNetworkInterceptionPatternsParams, metadata?: CallMetadata): Promise; setWebSocketInterceptionPatterns(params: BrowserContextSetWebSocketInterceptionPatternsParams, metadata?: CallMetadata): Promise; setOffline(params: BrowserContextSetOfflineParams, metadata?: CallMetadata): Promise; - storageState(params?: BrowserContextStorageStateParams, metadata?: CallMetadata): Promise; + storageState(params: BrowserContextStorageStateParams, metadata?: CallMetadata): Promise; pause(params?: BrowserContextPauseParams, metadata?: CallMetadata): Promise; enableRecorder(params: BrowserContextEnableRecorderParams, metadata?: CallMetadata): Promise; newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: CallMetadata): Promise; @@ -1797,8 +1801,12 @@ export type BrowserContextSetOfflineOptions = { }; export type BrowserContextSetOfflineResult = void; -export type BrowserContextStorageStateParams = {}; -export type BrowserContextStorageStateOptions = {}; +export type BrowserContextStorageStateParams = { + indexedDB?: boolean, +}; +export type BrowserContextStorageStateOptions = { + indexedDB?: boolean, +}; export type BrowserContextStorageStateResult = { cookies: NetworkCookie[], origins: OriginStorage[], diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index b581eacb4e..316ac8c2a8 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -376,6 +376,8 @@ APIRequestContext: items: string storageState: + parameters: + indexedDB: boolean? returns: cookies: type: array @@ -1234,6 +1236,8 @@ BrowserContext: offline: boolean storageState: + parameters: + indexedDB: boolean? returns: cookies: type: array diff --git a/tests/library/browsercontext-storage-state.spec.ts b/tests/library/browsercontext-storage-state.spec.ts index abdf745c5d..d1431e88fd 100644 --- a/tests/library/browsercontext-storage-state.spec.ts +++ b/tests/library/browsercontext-storage-state.spec.ts @@ -447,4 +447,6 @@ it('should support IndexedDB', async ({ page, server, contextFactory }) => { - listitem: - text: /Pet the cat/ `); + + expect(await context.storageState({ indexedDB: false })).toEqual({ cookies: [], origins: [] }); });