store data
This commit is contained in:
parent
e3cc3a7f62
commit
e596207f99
|
|
@ -264,6 +264,8 @@ Specify environment variables that will be visible to the browser. Defaults to `
|
|||
- `localStorage` <[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
- `value` <[string]>
|
||||
- `indexedDB` ?<[Array]<[Object]>>
|
||||
- `name` <[string]>
|
||||
|
||||
Learn more about [storage state and auth](../auth.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -142,9 +142,28 @@ scheme.NameValue = tObject({
|
|||
name: tString,
|
||||
value: tString,
|
||||
});
|
||||
scheme.IndexedDBDatabase = tObject({
|
||||
name: tString,
|
||||
stores: tArray(tObject({
|
||||
name: tString,
|
||||
autoIncrement: tBoolean,
|
||||
keyPath: tArray(tString),
|
||||
records: tArray(tObject({
|
||||
key: tString,
|
||||
value: tString,
|
||||
})),
|
||||
indexes: tArray(tObject({
|
||||
name: tString,
|
||||
keyPath: tArray(tString),
|
||||
multiEntry: tBoolean,
|
||||
unique: tBoolean,
|
||||
})),
|
||||
})),
|
||||
});
|
||||
scheme.OriginStorage = tObject({
|
||||
origin: tString,
|
||||
localStorage: tArray(tType('NameValue')),
|
||||
indexedDB: tOptional(tArray(tType('IndexedDBDatabase'))),
|
||||
});
|
||||
scheme.SerializedError = tObject({
|
||||
error: tOptional(tObject({
|
||||
|
|
|
|||
|
|
@ -513,9 +513,78 @@ export abstract class BrowserContext extends SdkObject {
|
|||
};
|
||||
const originsToSave = new Set(this._origins);
|
||||
|
||||
function _collectStorageScript() {
|
||||
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 transaction = db.transaction(db.objectStoreNames, 'readonly');
|
||||
const stores = await Promise.all([...db.objectStoreNames].map(async storeName => {
|
||||
const objectStore = transaction.objectStore(storeName);
|
||||
const keys = await new Promise<any[]>((resolve, reject) => {
|
||||
const request = objectStore.getAllKeys();
|
||||
request.addEventListener('success', () => resolve(request.result));
|
||||
request.addEventListener('error', reject);
|
||||
});
|
||||
|
||||
const records = await Promise.all(keys.map(async key => {
|
||||
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,
|
||||
value: JSON.stringify(record)
|
||||
};
|
||||
}));
|
||||
|
||||
const indexes = await Promise.all([...objectStore.indexNames].map(async indexName => {
|
||||
const index = objectStore.index(indexName);
|
||||
return {
|
||||
name: index.name,
|
||||
keyPath: Array.isArray(index.keyPath) ? index.keyPath : [index.keyPath],
|
||||
multiEntry: index.multiEntry,
|
||||
unique: index.unique,
|
||||
};
|
||||
}));
|
||||
|
||||
return {
|
||||
name: storeName,
|
||||
records: records.filter(Boolean),
|
||||
indexes,
|
||||
autoIncrement: objectStore.autoIncrement,
|
||||
keyPath: Array.isArray(objectStore.keyPath) ? objectStore.keyPath : [objectStore.keyPath],
|
||||
};
|
||||
}));
|
||||
|
||||
return {
|
||||
name: dbInfo.name,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -526,8 +595,8 @@ export abstract class BrowserContext extends SdkObject {
|
|||
continue;
|
||||
try {
|
||||
const storage = await page.mainFrame().nonStallingEvaluateInExistingContext(`(${_collectStorageScript.toString()})()`, 'utility');
|
||||
if (storage.localStorage.length)
|
||||
result.origins.push({ origin, localStorage: storage.localStorage } as channels.OriginStorage);
|
||||
if (storage.localStorage.length || storage.indexedDB?.length)
|
||||
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.
|
||||
|
|
@ -546,8 +615,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.localStorage.length)
|
||||
result.origins.push({ origin, localStorage: storage.localStorage } as channels.OriginStorage);
|
||||
if (storage.localStorage.length || storage.indexedDB?.length)
|
||||
result.origins.push({ origin, localStorage: storage.localStorage, indexedDB: storage.indexedDB } as channels.OriginStorage);
|
||||
}
|
||||
await page.close(internalMetadata);
|
||||
}
|
||||
|
|
|
|||
8
packages/playwright-core/types/types.d.ts
vendored
8
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -10060,6 +10060,10 @@ export interface Browser {
|
|||
|
||||
value: string;
|
||||
}>;
|
||||
|
||||
indexedDB?: Array<{
|
||||
name: string;
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
|
||||
|
|
@ -22227,6 +22231,10 @@ export interface BrowserContextOptions {
|
|||
|
||||
value: string;
|
||||
}>;
|
||||
|
||||
indexedDB?: Array<{
|
||||
name: string;
|
||||
}>;
|
||||
}>;
|
||||
};
|
||||
|
||||
|
|
|
|||
20
packages/protocol/src/channels.d.ts
vendored
20
packages/protocol/src/channels.d.ts
vendored
|
|
@ -271,9 +271,29 @@ export type NameValue = {
|
|||
value: string,
|
||||
};
|
||||
|
||||
export type IndexedDBDatabase = {
|
||||
name: string,
|
||||
stores: {
|
||||
name: string,
|
||||
autoIncrement: boolean,
|
||||
keyPath: string[],
|
||||
records: {
|
||||
key: string,
|
||||
value: string,
|
||||
}[],
|
||||
indexes: {
|
||||
name: string,
|
||||
keyPath: string[],
|
||||
multiEntry: boolean,
|
||||
unique: boolean,
|
||||
}[],
|
||||
}[],
|
||||
};
|
||||
|
||||
export type OriginStorage = {
|
||||
origin: string,
|
||||
localStorage: NameValue[],
|
||||
indexedDB?: IndexedDBDatabase[],
|
||||
};
|
||||
|
||||
export type SerializedError = {
|
||||
|
|
|
|||
|
|
@ -222,6 +222,38 @@ NameValue:
|
|||
name: string
|
||||
value: string
|
||||
|
||||
IndexedDBDatabase:
|
||||
type: object
|
||||
properties:
|
||||
name: string
|
||||
stores:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name: string
|
||||
autoIncrement: boolean
|
||||
keyPath:
|
||||
type: array
|
||||
items: string
|
||||
records:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
key: string
|
||||
value: string
|
||||
indexes:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name: string
|
||||
keyPath:
|
||||
type: array
|
||||
items: string
|
||||
multiEntry: boolean
|
||||
unique: boolean
|
||||
|
||||
OriginStorage:
|
||||
type: object
|
||||
|
|
@ -230,7 +262,9 @@ OriginStorage:
|
|||
localStorage:
|
||||
type: array
|
||||
items: NameValue
|
||||
|
||||
indexedDB:
|
||||
type: array?
|
||||
items: IndexedDBDatabase
|
||||
|
||||
SerializedError:
|
||||
type: object
|
||||
|
|
|
|||
|
|
@ -316,3 +316,83 @@ it('should roundtrip local storage in third-party context', async ({ page, conte
|
|||
expect(localStorage).toEqual({ name1: 'value1' });
|
||||
await context2.close();
|
||||
});
|
||||
|
||||
it('should support IndexedDB', async ({ page, contextFactory }) => {
|
||||
await page.goto('https://mdn.github.io/dom-examples/to-do-notifications/');
|
||||
await page.getByLabel('Task title').fill('Pet the cat');
|
||||
await page.getByLabel('Hours').fill('1');
|
||||
await page.getByLabel('Mins').fill('1');
|
||||
await page.getByText('Add Task').click();
|
||||
|
||||
const storageState = await page.context().storageState();
|
||||
expect(storageState.origins).toEqual([
|
||||
{
|
||||
origin: 'https://mdn.github.io',
|
||||
localStorage: [],
|
||||
indexedDB: [
|
||||
{
|
||||
name: 'toDoList',
|
||||
stores: [
|
||||
{
|
||||
name: 'toDoList',
|
||||
autoIncrement: false,
|
||||
keyPath: ['taskTitle'],
|
||||
records: [
|
||||
{
|
||||
key: 'Pet the cat',
|
||||
value: JSON.stringify({
|
||||
taskTitle: 'Pet the cat',
|
||||
hours: '1',
|
||||
minutes: '1',
|
||||
day: '01',
|
||||
month: 'January',
|
||||
year: '2025',
|
||||
notified: 'no'
|
||||
})
|
||||
}
|
||||
],
|
||||
indexes: [
|
||||
{
|
||||
'name': 'day',
|
||||
'keyPath': ['day'],
|
||||
'multiEntry': false,
|
||||
'unique': false,
|
||||
},
|
||||
{
|
||||
'name': 'hours',
|
||||
'keyPath': ['hours'],
|
||||
'multiEntry': false,
|
||||
'unique': false,
|
||||
},
|
||||
{
|
||||
'name': 'minutes',
|
||||
'keyPath': ['minutes'],
|
||||
'multiEntry': false,
|
||||
'unique': false,
|
||||
},
|
||||
{
|
||||
'name': 'month',
|
||||
'keyPath': ['month'],
|
||||
'multiEntry': false,
|
||||
'unique': false,
|
||||
},
|
||||
{
|
||||
'name': 'notified',
|
||||
'keyPath': ['notified'],
|
||||
'multiEntry': false,
|
||||
'unique': false,
|
||||
},
|
||||
{
|
||||
'name': 'year',
|
||||
'keyPath': ['year'],
|
||||
'multiEntry': false,
|
||||
'unique': false,
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue