fix: allow opt out from `IndexedDB` in storagestate (#34650)

This commit is contained in:
Simon Knott 2025-02-06 16:40:14 +01:00 committed by GitHub
parent 8d751cfe50
commit 902e83fe87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 68 additions and 28 deletions

View File

@ -909,3 +909,9 @@ Returns storage state for this request context, contains current cookies and loc
### option: APIRequestContext.storageState.path = %%-storagestate-option-path-%% ### option: APIRequestContext.storageState.path = %%-storagestate-option-path-%%
* since: v1.16 * since: v1.16
### option: APIRequestContext.storageState.indexedDB
* since: v1.51
- `indexedDB` ?<boolean>
Defaults to `true`. Set to `false` to omit IndexedDB from snapshot.

View File

@ -1545,6 +1545,12 @@ IndexedDBs with typed arrays are currently not supported.
### option: BrowserContext.storageState.path = %%-storagestate-option-path-%% ### option: BrowserContext.storageState.path = %%-storagestate-option-path-%%
* since: v1.8 * since: v1.8
### option: BrowserContext.storageState.indexedDB
* since: v1.51
- `indexedDB` ?<boolean>
Defaults to `true`. Set to `false` to omit IndexedDB from snapshot.
## property: BrowserContext.tracing ## property: BrowserContext.tracing
* since: v1.12 * since: v1.12
- type: <[Tracing]> - type: <[Tracing]>

View File

@ -425,8 +425,8 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
}); });
} }
async storageState(options: { path?: string } = {}): Promise<StorageState> { async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise<StorageState> {
const state = await this._channel.storageState(); const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) { if (options.path) {
await mkdirIfNeeded(options.path); await mkdirIfNeeded(options.path);
await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');

View File

@ -260,8 +260,8 @@ export class APIRequestContext extends ChannelOwner<channels.APIRequestContextCh
}); });
} }
async storageState(options: { path?: string } = {}): Promise<StorageState> { async storageState(options: { path?: string, indexedDB?: boolean } = {}): Promise<StorageState> {
const state = await this._channel.storageState(); const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) { if (options.path) {
await mkdirIfNeeded(options.path); await mkdirIfNeeded(options.path);
await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8'); await fs.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');

View File

@ -234,7 +234,9 @@ scheme.APIRequestContextFetchLogParams = tObject({
scheme.APIRequestContextFetchLogResult = tObject({ scheme.APIRequestContextFetchLogResult = tObject({
log: tArray(tString), log: tArray(tString),
}); });
scheme.APIRequestContextStorageStateParams = tOptional(tObject({})); scheme.APIRequestContextStorageStateParams = tObject({
indexedDB: tOptional(tBoolean),
});
scheme.APIRequestContextStorageStateResult = tObject({ scheme.APIRequestContextStorageStateResult = tObject({
cookies: tArray(tType('NetworkCookie')), cookies: tArray(tType('NetworkCookie')),
origins: tArray(tType('OriginStorage')), origins: tArray(tType('OriginStorage')),
@ -992,7 +994,9 @@ scheme.BrowserContextSetOfflineParams = tObject({
offline: tBoolean, offline: tBoolean,
}); });
scheme.BrowserContextSetOfflineResult = tOptional(tObject({})); scheme.BrowserContextSetOfflineResult = tOptional(tObject({}));
scheme.BrowserContextStorageStateParams = tOptional(tObject({})); scheme.BrowserContextStorageStateParams = tObject({
indexedDB: tOptional(tBoolean),
});
scheme.BrowserContextStorageStateResult = tObject({ scheme.BrowserContextStorageStateResult = tObject({
cookies: tArray(tType('NetworkCookie')), cookies: tArray(tType('NetworkCookie')),
origins: tArray(tType('OriginStorage')), origins: tArray(tType('OriginStorage')),

View File

@ -508,14 +508,14 @@ export abstract class BrowserContext extends SdkObject {
this._origins.add(origin); this._origins.add(origin);
} }
async storageState(): Promise<channels.BrowserContextStorageStateResult> { async storageState(indexedDB = true): Promise<channels.BrowserContextStorageStateResult> {
const result: channels.BrowserContextStorageStateResult = { const result: channels.BrowserContextStorageStateResult = {
cookies: await this.cookies(), cookies: await this.cookies(),
origins: [] origins: []
}; };
const originsToSave = new Set(this._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. // First try collecting storage stage from existing pages.
for (const page of this.pages()) { for (const page of this.pages()) {

View File

@ -291,7 +291,7 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
} }
async storageState(params: channels.BrowserContextStorageStateParams, metadata: CallMetadata): Promise<channels.BrowserContextStorageStateResult> { async storageState(params: channels.BrowserContextStorageStateParams, metadata: CallMetadata): Promise<channels.BrowserContextStorageStateResult> {
return await this._context.storageState(); return await this._context.storageState(params.indexedDB);
} }
async close(params: channels.BrowserContextCloseParams, metadata: CallMetadata): Promise<void> { async close(params: channels.BrowserContextCloseParams, metadata: CallMetadata): Promise<void> {

View File

@ -194,8 +194,8 @@ export class APIRequestContextDispatcher extends Dispatcher<APIRequestContext, c
this.adopt(tracing); this.adopt(tracing);
} }
async storageState(): Promise<channels.APIRequestContextStorageStateResult> { async storageState(params: channels.APIRequestContextStorageStateParams): Promise<channels.APIRequestContextStorageStateResult> {
return this._object.storageState(); return this._object.storageState(params.indexedDB);
} }
async dispose(params: channels.APIRequestContextDisposeParams, metadata: CallMetadata): Promise<void> { async dispose(params: channels.APIRequestContextDisposeParams, metadata: CallMetadata): Promise<void> {

View File

@ -133,7 +133,7 @@ export abstract class APIRequestContext extends SdkObject {
abstract _defaultOptions(): FetchRequestOptions; abstract _defaultOptions(): FetchRequestOptions;
abstract _addCookies(cookies: channels.NetworkCookie[]): Promise<void>; abstract _addCookies(cookies: channels.NetworkCookie[]): Promise<void>;
abstract _cookies(url: URL): Promise<channels.NetworkCookie[]>; abstract _cookies(url: URL): Promise<channels.NetworkCookie[]>;
abstract storageState(): Promise<channels.APIRequestContextStorageStateResult>; abstract storageState(indexedDB?: boolean): Promise<channels.APIRequestContextStorageStateResult>;
private _storeResponseBody(body: Buffer): string { private _storeResponseBody(body: Buffer): string {
const uid = createGuid(); const uid = createGuid();
@ -618,8 +618,8 @@ export class BrowserContextAPIRequestContext extends APIRequestContext {
return await this._context.cookies(url.toString()); return await this._context.cookies(url.toString());
} }
override async storageState(): Promise<channels.APIRequestContextStorageStateResult> { override async storageState(indexedDB?: boolean): Promise<channels.APIRequestContextStorageStateResult> {
return this._context.storageState(); return this._context.storageState(indexedDB);
} }
} }
@ -684,10 +684,10 @@ export class GlobalAPIRequestContext extends APIRequestContext {
return this._cookieStore.cookies(url); return this._cookieStore.cookies(url);
} }
override async storageState(): Promise<channels.APIRequestContextStorageStateResult> { override async storageState(indexedDB = true): Promise<channels.APIRequestContextStorageStateResult> {
return { return {
cookies: this._cookieStore.allCookies(), cookies: this._cookieStore.allCookies(),
origins: this._origins || [] origins: (this._origins || []).map(origin => ({ ...origin, indexedDB: indexedDB ? origin.indexedDB : [] })),
}; };
} }
} }

View File

@ -19,8 +19,8 @@ import type { source } from './isomorphic/utilityScriptSerializers';
export type Storage = Omit<channels.OriginStorage, 'origin'>; export type Storage = Omit<channels.OriginStorage, 'origin'>;
export async function collect(serializers: ReturnType<typeof source>, isFirefox: boolean): Promise<Storage> { export async function collect(serializers: ReturnType<typeof source>, isFirefox: boolean, recordIndexedDB: boolean): Promise<Storage> {
const idbResult = await Promise.all((await indexedDB.databases()).map(async dbInfo => { async function collectDB(dbInfo: IDBDatabaseInfo) {
if (!dbInfo.name) if (!dbInfo.name)
throw new Error('Database name is empty'); throw new Error('Database name is empty');
if (!dbInfo.version) if (!dbInfo.version)
@ -119,13 +119,13 @@ export async function collect(serializers: ReturnType<typeof source>, isFirefox:
version: dbInfo.version, version: dbInfo.version,
stores, stores,
}; };
})).catch(e => { }
throw new Error('Unable to serialize IndexedDB: ' + e.message);
});
return { return {
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name)! })), 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);
}) : [],
}; };
} }

View File

@ -9274,6 +9274,11 @@ export interface BrowserContext {
* @param options * @param options
*/ */
storageState(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 * 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 * [`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 * @param options
*/ */
storageState(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 * 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 * [`path`](https://playwright.dev/docs/api/class-apirequestcontext#api-request-context-storage-state-option-path) is

View File

@ -346,7 +346,7 @@ export interface APIRequestContextChannel extends APIRequestContextEventTarget,
fetch(params: APIRequestContextFetchParams, metadata?: CallMetadata): Promise<APIRequestContextFetchResult>; fetch(params: APIRequestContextFetchParams, metadata?: CallMetadata): Promise<APIRequestContextFetchResult>;
fetchResponseBody(params: APIRequestContextFetchResponseBodyParams, metadata?: CallMetadata): Promise<APIRequestContextFetchResponseBodyResult>; fetchResponseBody(params: APIRequestContextFetchResponseBodyParams, metadata?: CallMetadata): Promise<APIRequestContextFetchResponseBodyResult>;
fetchLog(params: APIRequestContextFetchLogParams, metadata?: CallMetadata): Promise<APIRequestContextFetchLogResult>; fetchLog(params: APIRequestContextFetchLogParams, metadata?: CallMetadata): Promise<APIRequestContextFetchLogResult>;
storageState(params?: APIRequestContextStorageStateParams, metadata?: CallMetadata): Promise<APIRequestContextStorageStateResult>; storageState(params: APIRequestContextStorageStateParams, metadata?: CallMetadata): Promise<APIRequestContextStorageStateResult>;
disposeAPIResponse(params: APIRequestContextDisposeAPIResponseParams, metadata?: CallMetadata): Promise<APIRequestContextDisposeAPIResponseResult>; disposeAPIResponse(params: APIRequestContextDisposeAPIResponseParams, metadata?: CallMetadata): Promise<APIRequestContextDisposeAPIResponseResult>;
dispose(params: APIRequestContextDisposeParams, metadata?: CallMetadata): Promise<APIRequestContextDisposeResult>; dispose(params: APIRequestContextDisposeParams, metadata?: CallMetadata): Promise<APIRequestContextDisposeResult>;
} }
@ -402,8 +402,12 @@ export type APIRequestContextFetchLogOptions = {
export type APIRequestContextFetchLogResult = { export type APIRequestContextFetchLogResult = {
log: string[], log: string[],
}; };
export type APIRequestContextStorageStateParams = {}; export type APIRequestContextStorageStateParams = {
export type APIRequestContextStorageStateOptions = {}; indexedDB?: boolean,
};
export type APIRequestContextStorageStateOptions = {
indexedDB?: boolean,
};
export type APIRequestContextStorageStateResult = { export type APIRequestContextStorageStateResult = {
cookies: NetworkCookie[], cookies: NetworkCookie[],
origins: OriginStorage[], origins: OriginStorage[],
@ -1564,7 +1568,7 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT
setNetworkInterceptionPatterns(params: BrowserContextSetNetworkInterceptionPatternsParams, metadata?: CallMetadata): Promise<BrowserContextSetNetworkInterceptionPatternsResult>; setNetworkInterceptionPatterns(params: BrowserContextSetNetworkInterceptionPatternsParams, metadata?: CallMetadata): Promise<BrowserContextSetNetworkInterceptionPatternsResult>;
setWebSocketInterceptionPatterns(params: BrowserContextSetWebSocketInterceptionPatternsParams, metadata?: CallMetadata): Promise<BrowserContextSetWebSocketInterceptionPatternsResult>; setWebSocketInterceptionPatterns(params: BrowserContextSetWebSocketInterceptionPatternsParams, metadata?: CallMetadata): Promise<BrowserContextSetWebSocketInterceptionPatternsResult>;
setOffline(params: BrowserContextSetOfflineParams, metadata?: CallMetadata): Promise<BrowserContextSetOfflineResult>; setOffline(params: BrowserContextSetOfflineParams, metadata?: CallMetadata): Promise<BrowserContextSetOfflineResult>;
storageState(params?: BrowserContextStorageStateParams, metadata?: CallMetadata): Promise<BrowserContextStorageStateResult>; storageState(params: BrowserContextStorageStateParams, metadata?: CallMetadata): Promise<BrowserContextStorageStateResult>;
pause(params?: BrowserContextPauseParams, metadata?: CallMetadata): Promise<BrowserContextPauseResult>; pause(params?: BrowserContextPauseParams, metadata?: CallMetadata): Promise<BrowserContextPauseResult>;
enableRecorder(params: BrowserContextEnableRecorderParams, metadata?: CallMetadata): Promise<BrowserContextEnableRecorderResult>; enableRecorder(params: BrowserContextEnableRecorderParams, metadata?: CallMetadata): Promise<BrowserContextEnableRecorderResult>;
newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: CallMetadata): Promise<BrowserContextNewCDPSessionResult>; newCDPSession(params: BrowserContextNewCDPSessionParams, metadata?: CallMetadata): Promise<BrowserContextNewCDPSessionResult>;
@ -1797,8 +1801,12 @@ export type BrowserContextSetOfflineOptions = {
}; };
export type BrowserContextSetOfflineResult = void; export type BrowserContextSetOfflineResult = void;
export type BrowserContextStorageStateParams = {}; export type BrowserContextStorageStateParams = {
export type BrowserContextStorageStateOptions = {}; indexedDB?: boolean,
};
export type BrowserContextStorageStateOptions = {
indexedDB?: boolean,
};
export type BrowserContextStorageStateResult = { export type BrowserContextStorageStateResult = {
cookies: NetworkCookie[], cookies: NetworkCookie[],
origins: OriginStorage[], origins: OriginStorage[],

View File

@ -376,6 +376,8 @@ APIRequestContext:
items: string items: string
storageState: storageState:
parameters:
indexedDB: boolean?
returns: returns:
cookies: cookies:
type: array type: array
@ -1234,6 +1236,8 @@ BrowserContext:
offline: boolean offline: boolean
storageState: storageState:
parameters:
indexedDB: boolean?
returns: returns:
cookies: cookies:
type: array type: array

View File

@ -447,4 +447,6 @@ it('should support IndexedDB', async ({ page, server, contextFactory }) => {
- listitem: - listitem:
- text: /Pet the cat/ - text: /Pet the cat/
`); `);
expect(await context.storageState({ indexedDB: false })).toEqual({ cookies: [], origins: [] });
}); });