store unserialised
This commit is contained in:
parent
33d8be42cb
commit
51077a0799
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -1526,8 +1526,8 @@ Whether to emulate network being offline for the browser context.
|
|||
- `unique` <[boolean]>
|
||||
- `multiEntry` <[boolean]>
|
||||
- `records` <[Array]<[Object]>>
|
||||
- `key` ?<[string]>
|
||||
- `value` <[string]>
|
||||
- `key` ?<[Object]>
|
||||
- `value` <[Object]>
|
||||
|
||||
Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
|
||||
|
||||
|
|
|
|||
|
|
@ -279,8 +279,8 @@ Specify environment variables that will be visible to the browser. Defaults to `
|
|||
- `unique` <[boolean]>
|
||||
- `multiEntry` <[boolean]>
|
||||
- `records` <[Array]<[Object]>>
|
||||
- `key` ?<[string]> opaque key, only defined if stores uses out-of-line keys
|
||||
- `value` <[string]> opaque value
|
||||
- `key` ?<[Object]>
|
||||
- `value` <[Object]>
|
||||
|
||||
Learn more about [storage state and auth](../auth.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ 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), 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, local storage and IndexedDB state can be used across different browsers. They depend on your application's authentication model: some apps might require both cookies and local storage or IndexedDB.
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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, LaunchOptions, StorageStateWithIndexedDB } 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';
|
||||
|
|
@ -425,7 +425,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
|||
});
|
||||
}
|
||||
|
||||
async storageState(options: { path?: string } = {}): Promise<StorageStateWithIndexedDB> {
|
||||
async storageState(options: { path?: string } = {}): Promise<StorageState> {
|
||||
const state = await this._channel.storageState();
|
||||
if (options.path) {
|
||||
await mkdirIfNeeded(options.path);
|
||||
|
|
|
|||
|
|
@ -39,13 +39,9 @@ export type StorageState = {
|
|||
cookies: channels.NetworkCookie[],
|
||||
origins: channels.OriginStorage[]
|
||||
};
|
||||
export type StorageStateWithIndexedDB = {
|
||||
cookies: channels.NetworkCookie[],
|
||||
origins: channels.OriginStorageWithRequiredIndexedDB[]
|
||||
};
|
||||
export type SetStorageState = {
|
||||
cookies?: channels.SetNetworkCookie[],
|
||||
origins?: channels.OriginStorage[]
|
||||
origins?: channels.SetOriginStorage[]
|
||||
};
|
||||
|
||||
export type LifecycleEvent = channels.LifecycleEvent;
|
||||
|
|
|
|||
|
|
@ -149,26 +149,26 @@ scheme.IndexedDBDatabase = tObject({
|
|||
name: tString,
|
||||
autoIncrement: tBoolean,
|
||||
keyPath: tOptional(tString),
|
||||
keyPathArray: tOptional(tType('string[]')),
|
||||
keyPathArray: tOptional(tArray(tString)),
|
||||
records: tArray(tObject({
|
||||
key: tOptional(tString),
|
||||
value: tString,
|
||||
key: tOptional(tAny),
|
||||
value: tAny,
|
||||
})),
|
||||
indexes: tArray(tObject({
|
||||
name: tString,
|
||||
keyPath: tOptional(tString),
|
||||
keyPathArray: tOptional(tType('string[]')),
|
||||
keyPathArray: tOptional(tArray(tString)),
|
||||
multiEntry: tBoolean,
|
||||
unique: tBoolean,
|
||||
})),
|
||||
})),
|
||||
});
|
||||
scheme.OriginStorage = tObject({
|
||||
scheme.SetOriginStorage = tObject({
|
||||
origin: tString,
|
||||
localStorage: tArray(tType('NameValue')),
|
||||
indexedDB: tOptional(tArray(tType('IndexedDBDatabase'))),
|
||||
});
|
||||
scheme.OriginStorageWithRequiredIndexedDB = tObject({
|
||||
scheme.OriginStorage = tObject({
|
||||
origin: tString,
|
||||
localStorage: tArray(tType('NameValue')),
|
||||
indexedDB: tArray(tType('IndexedDBDatabase')),
|
||||
|
|
@ -388,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),
|
||||
});
|
||||
|
|
@ -714,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({
|
||||
|
|
@ -783,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({
|
||||
|
|
@ -990,7 +990,7 @@ scheme.BrowserContextSetOfflineResult = tOptional(tObject({}));
|
|||
scheme.BrowserContextStorageStateParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextStorageStateResult = tObject({
|
||||
cookies: tArray(tType('NetworkCookie')),
|
||||
origins: tArray(tType('OriginStorageWithRequiredIndexedDB')),
|
||||
origins: tArray(tType('OriginStorage')),
|
||||
});
|
||||
scheme.BrowserContextPauseParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextPauseResult = tOptional(tObject({}));
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ import type { Artifact } from './artifact';
|
|||
import { Clock } from './clock';
|
||||
import type { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
|
||||
import { RecorderApp } from './recorder/recorderApp';
|
||||
import * as utilitySerializers from './isomorphic/utilityScriptSerializers';
|
||||
|
||||
export abstract class BrowserContext extends SdkObject {
|
||||
static Events = {
|
||||
|
|
@ -574,18 +573,6 @@ export abstract class BrowserContext extends SdkObject {
|
|||
};
|
||||
}
|
||||
|
||||
function serializeRecords(indexedDBs: channels.IndexedDBDatabase[]) {
|
||||
for (const db of indexedDBs) {
|
||||
for (const store of db.stores) {
|
||||
for (const record of store.records) {
|
||||
if (record.key !== undefined)
|
||||
record.key = JSON.stringify(utilitySerializers.serializeAsCallArgument(record.value, v => ({ fallThrough: v })));
|
||||
record.value = JSON.stringify(utilitySerializers.serializeAsCallArgument(record.value, v => ({ fallThrough: v })));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First try collecting storage stage from existing pages.
|
||||
for (const page of this.pages()) {
|
||||
const origin = page.mainFrame().origin();
|
||||
|
|
@ -593,9 +580,8 @@ export abstract class BrowserContext extends SdkObject {
|
|||
continue;
|
||||
try {
|
||||
const storage = await page.mainFrame().nonStallingEvaluateInExistingContext(`(${_collectStorageScript.toString()})()`, 'utility');
|
||||
serializeRecords(storage.indexedDB);
|
||||
if (storage.localStorage.length || storage.indexedDB?.length)
|
||||
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB } as channels.OriginStorageWithRequiredIndexedDB);
|
||||
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB } as channels.OriginStorage);
|
||||
originsToSave.delete(origin);
|
||||
} catch {
|
||||
// When failed on the live page, we'll retry on the blank page below.
|
||||
|
|
@ -614,9 +600,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' });
|
||||
serializeRecords(storage.indexedDB);
|
||||
if (storage.localStorage.length || storage.indexedDB.length)
|
||||
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB } as channels.OriginStorageWithRequiredIndexedDB);
|
||||
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB } as channels.OriginStorage);
|
||||
}
|
||||
await page.close(internalMetadata);
|
||||
}
|
||||
|
|
@ -680,16 +665,6 @@ export abstract class BrowserContext extends SdkObject {
|
|||
const frame = page.mainFrame();
|
||||
await frame.goto(metadata, originState.origin);
|
||||
|
||||
for (const dbInfo of (originState.indexedDB || [])) {
|
||||
for (const store of dbInfo.stores) {
|
||||
for (const record of store.records) {
|
||||
if (record.key !== undefined)
|
||||
record.key = utilitySerializers.parseEvaluationResultValue(JSON.parse(record.key));
|
||||
record.value = utilitySerializers.parseEvaluationResultValue(JSON.parse(record.value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function _restoreStorageState(originState: channels.OriginStorage) {
|
||||
for (const { name, value } of (originState.localStorage || []))
|
||||
localStorage.setItem(name, value);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
58
packages/playwright-core/types/types.d.ts
vendored
58
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -9330,9 +9330,9 @@ export interface BrowserContext {
|
|||
}>;
|
||||
|
||||
records: Array<{
|
||||
key?: string;
|
||||
key?: Object;
|
||||
|
||||
value: string;
|
||||
value: Object;
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
|
|
@ -10132,15 +10132,9 @@ export interface Browser {
|
|||
}>;
|
||||
|
||||
records: Array<{
|
||||
/**
|
||||
* opaque key, only defined if stores uses out-of-line keys
|
||||
*/
|
||||
key?: string;
|
||||
key?: Object;
|
||||
|
||||
/**
|
||||
* opaque value
|
||||
*/
|
||||
value: string;
|
||||
value: Object;
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
|
|
@ -18478,6 +18472,40 @@ export interface APIRequestContext {
|
|||
|
||||
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?: Object;
|
||||
|
||||
value: Object;
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
|
|
@ -22348,15 +22376,9 @@ export interface BrowserContextOptions {
|
|||
}>;
|
||||
|
||||
records: Array<{
|
||||
/**
|
||||
* opaque key, only defined if stores uses out-of-line keys
|
||||
*/
|
||||
key?: string;
|
||||
key?: Object;
|
||||
|
||||
/**
|
||||
* opaque value
|
||||
*/
|
||||
value: string;
|
||||
value: Object;
|
||||
}>;
|
||||
}>;
|
||||
}>;
|
||||
|
|
|
|||
22
packages/protocol/src/channels.d.ts
vendored
22
packages/protocol/src/channels.d.ts
vendored
|
|
@ -280,8 +280,8 @@ export type IndexedDBDatabase = {
|
|||
keyPath?: string,
|
||||
keyPathArray?: string[],
|
||||
records: {
|
||||
key?: string,
|
||||
value: string,
|
||||
key?: any,
|
||||
value: any,
|
||||
}[],
|
||||
indexes: {
|
||||
name: string,
|
||||
|
|
@ -293,13 +293,13 @@ export type IndexedDBDatabase = {
|
|||
}[],
|
||||
};
|
||||
|
||||
export type OriginStorage = {
|
||||
export type SetOriginStorage = {
|
||||
origin: string,
|
||||
localStorage: NameValue[],
|
||||
indexedDB?: IndexedDBDatabase[],
|
||||
};
|
||||
|
||||
export type OriginStorageWithRequiredIndexedDB = {
|
||||
export type OriginStorage = {
|
||||
origin: string,
|
||||
localStorage: NameValue[],
|
||||
indexedDB: IndexedDBDatabase[],
|
||||
|
|
@ -639,7 +639,7 @@ export type PlaywrightNewRequestParams = {
|
|||
timeout?: number,
|
||||
storageState?: {
|
||||
cookies?: NetworkCookie[],
|
||||
origins?: OriginStorage[],
|
||||
origins?: SetOriginStorage[],
|
||||
},
|
||||
tracesDir?: string,
|
||||
};
|
||||
|
|
@ -670,7 +670,7 @@ export type PlaywrightNewRequestOptions = {
|
|||
timeout?: number,
|
||||
storageState?: {
|
||||
cookies?: NetworkCookie[],
|
||||
origins?: OriginStorage[],
|
||||
origins?: SetOriginStorage[],
|
||||
},
|
||||
tracesDir?: string,
|
||||
};
|
||||
|
|
@ -1249,7 +1249,7 @@ export type BrowserNewContextParams = {
|
|||
},
|
||||
storageState?: {
|
||||
cookies?: SetNetworkCookie[],
|
||||
origins?: OriginStorage[],
|
||||
origins?: SetOriginStorage[],
|
||||
},
|
||||
};
|
||||
export type BrowserNewContextOptions = {
|
||||
|
|
@ -1315,7 +1315,7 @@ export type BrowserNewContextOptions = {
|
|||
},
|
||||
storageState?: {
|
||||
cookies?: SetNetworkCookie[],
|
||||
origins?: OriginStorage[],
|
||||
origins?: SetOriginStorage[],
|
||||
},
|
||||
};
|
||||
export type BrowserNewContextResult = {
|
||||
|
|
@ -1384,7 +1384,7 @@ export type BrowserNewContextForReuseParams = {
|
|||
},
|
||||
storageState?: {
|
||||
cookies?: SetNetworkCookie[],
|
||||
origins?: OriginStorage[],
|
||||
origins?: SetOriginStorage[],
|
||||
},
|
||||
};
|
||||
export type BrowserNewContextForReuseOptions = {
|
||||
|
|
@ -1450,7 +1450,7 @@ export type BrowserNewContextForReuseOptions = {
|
|||
},
|
||||
storageState?: {
|
||||
cookies?: SetNetworkCookie[],
|
||||
origins?: OriginStorage[],
|
||||
origins?: SetOriginStorage[],
|
||||
},
|
||||
};
|
||||
export type BrowserNewContextForReuseResult = {
|
||||
|
|
@ -1793,7 +1793,7 @@ export type BrowserContextStorageStateParams = {};
|
|||
export type BrowserContextStorageStateOptions = {};
|
||||
export type BrowserContextStorageStateResult = {
|
||||
cookies: NetworkCookie[],
|
||||
origins: OriginStorageWithRequiredIndexedDB[],
|
||||
origins: OriginStorage[],
|
||||
};
|
||||
export type BrowserContextPauseParams = {};
|
||||
export type BrowserContextPauseOptions = {};
|
||||
|
|
|
|||
|
|
@ -235,14 +235,16 @@ IndexedDBDatabase:
|
|||
name: string
|
||||
autoIncrement: boolean
|
||||
keyPath: string?
|
||||
keyPathArray: string[]?
|
||||
keyPathArray:
|
||||
type: array?
|
||||
items: string
|
||||
records:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
key: string?
|
||||
value: string
|
||||
key: json?
|
||||
value: json
|
||||
indexes:
|
||||
type: array
|
||||
items:
|
||||
|
|
@ -250,11 +252,13 @@ IndexedDBDatabase:
|
|||
properties:
|
||||
name: string
|
||||
keyPath: string?
|
||||
keyPathArray: string[]?
|
||||
keyPathArray:
|
||||
type: array?
|
||||
items: string
|
||||
multiEntry: boolean
|
||||
unique: boolean
|
||||
|
||||
OriginStorage:
|
||||
SetOriginStorage:
|
||||
type: object
|
||||
properties:
|
||||
origin: string
|
||||
|
|
@ -265,7 +269,7 @@ OriginStorage:
|
|||
type: array?
|
||||
items: IndexedDBDatabase
|
||||
|
||||
OriginStorageWithRequiredIndexedDB:
|
||||
OriginStorage:
|
||||
type: object
|
||||
properties:
|
||||
origin: string
|
||||
|
|
@ -774,7 +778,7 @@ Playwright:
|
|||
items: NetworkCookie
|
||||
origins:
|
||||
type: array?
|
||||
items: OriginStorage
|
||||
items: SetOriginStorage
|
||||
tracesDir: string?
|
||||
|
||||
returns:
|
||||
|
|
@ -1008,7 +1012,7 @@ Browser:
|
|||
items: SetNetworkCookie
|
||||
origins:
|
||||
type: array?
|
||||
items: OriginStorage
|
||||
items: SetOriginStorage
|
||||
returns:
|
||||
context: BrowserContext
|
||||
|
||||
|
|
@ -1030,7 +1034,7 @@ Browser:
|
|||
items: SetNetworkCookie
|
||||
origins:
|
||||
type: array?
|
||||
items: OriginStorage
|
||||
items: SetOriginStorage
|
||||
returns:
|
||||
context: BrowserContext
|
||||
|
||||
|
|
@ -1228,7 +1232,7 @@ BrowserContext:
|
|||
items: NetworkCookie
|
||||
origins:
|
||||
type: array
|
||||
items: OriginStorageWithRequiredIndexedDB
|
||||
items: OriginStorage
|
||||
|
||||
pause:
|
||||
experimental: True
|
||||
|
|
|
|||
|
|
@ -342,18 +342,15 @@ it('should support IndexedDB', async ({ page, server, contextFactory }) => {
|
|||
keyPath: 'taskTitle',
|
||||
records: [
|
||||
{
|
||||
value: JSON.stringify({
|
||||
o: [
|
||||
{ k: 'taskTitle', v: 'Pet the cat' },
|
||||
{ k: 'hours', v: '1' },
|
||||
{ k: 'minutes', v: '1' },
|
||||
{ k: 'day', v: '01' },
|
||||
{ k: 'month', v: 'January' },
|
||||
{ k: 'year', v: '2025' },
|
||||
{ k: 'notified', v: 'no' }
|
||||
],
|
||||
id: 1
|
||||
}),
|
||||
value: {
|
||||
day: '01',
|
||||
hours: '1',
|
||||
minutes: '1',
|
||||
month: 'January',
|
||||
notified: 'no',
|
||||
taskTitle: 'Pet the cat',
|
||||
year: '2025',
|
||||
},
|
||||
},
|
||||
],
|
||||
indexes: [
|
||||
|
|
|
|||
Loading…
Reference in a new issue