mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
fix(storageState): try to collect storage state on existing pages first (#29915)
This helps in a case where navigating to an origin fails for some reason, for example because a registered service worker loads some content into the supposedly blank page. Fixes #29402.
This commit is contained in:
parent
2ce421b27a
commit
349b25e61a
@ -484,14 +484,34 @@ export abstract class BrowserContext extends SdkObject {
|
||||
cookies: await this.cookies(),
|
||||
origins: []
|
||||
};
|
||||
if (this._origins.size) {
|
||||
const originsToSave = new Set(this._origins);
|
||||
|
||||
// First try collecting storage stage from existing pages.
|
||||
for (const page of this.pages()) {
|
||||
const origin = page.mainFrame().origin();
|
||||
if (!origin || !originsToSave.has(origin))
|
||||
continue;
|
||||
try {
|
||||
const storage = await page.mainFrame().nonStallingEvaluateInExistingContext(`({
|
||||
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
|
||||
})`, false, 'utility');
|
||||
if (storage.localStorage.length)
|
||||
result.origins.push({ origin, localStorage: storage.localStorage } as channels.OriginStorage);
|
||||
originsToSave.delete(origin);
|
||||
} catch {
|
||||
// When failed on the live page, we'll retry on the blank page below.
|
||||
}
|
||||
}
|
||||
|
||||
// If there are still origins to save, create a blank page to iterate over origins.
|
||||
if (originsToSave.size) {
|
||||
const internalMetadata = serverSideCallMetadata();
|
||||
const page = await this.newPage(internalMetadata);
|
||||
await page._setServerRequestInterceptor(handler => {
|
||||
handler.fulfill({ body: '<html></html>', requestUrl: handler.request().url() }).catch(() => {});
|
||||
return true;
|
||||
});
|
||||
for (const origin of this._origins) {
|
||||
for (const origin of originsToSave) {
|
||||
const originStorage: channels.OriginStorage = { origin, localStorage: [] };
|
||||
const frame = page.mainFrame();
|
||||
await frame.goto(internalMetadata, origin);
|
||||
|
@ -914,6 +914,12 @@ export class Frame extends SdkObject {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
origin(): string | undefined {
|
||||
if (!this._url.startsWith('http'))
|
||||
return;
|
||||
return network.parsedURL(this._url)?.origin;
|
||||
}
|
||||
|
||||
parentFrame(): Frame | null {
|
||||
return this._parentFrame;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import type * as dom from './dom';
|
||||
import * as frames from './frames';
|
||||
import * as input from './input';
|
||||
import * as js from './javascript';
|
||||
import * as network from './network';
|
||||
import type * as network from './network';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type { ScreenshotOptions } from './screenshotter';
|
||||
import { Screenshotter, validateScreenshotOptions } from './screenshotter';
|
||||
@ -706,12 +706,9 @@ export class Page extends SdkObject {
|
||||
|
||||
frameNavigatedToNewDocument(frame: frames.Frame) {
|
||||
this.emit(Page.Events.InternalFrameNavigatedToNewDocument, frame);
|
||||
const url = frame.url();
|
||||
if (!url.startsWith('http'))
|
||||
return;
|
||||
const purl = network.parsedURL(url);
|
||||
if (purl)
|
||||
this._browserContext.addVisitedOrigin(purl.origin);
|
||||
const origin = frame.origin();
|
||||
if (origin)
|
||||
this._browserContext.addVisitedOrigin(origin);
|
||||
}
|
||||
|
||||
allBindings() {
|
||||
|
@ -34,17 +34,17 @@ it('should capture local storage', async ({ contextFactory }) => {
|
||||
});
|
||||
const { origins } = await context.storageState();
|
||||
expect(origins).toEqual([{
|
||||
origin: 'https://www.example.com',
|
||||
localStorage: [{
|
||||
name: 'name1',
|
||||
value: 'value1'
|
||||
}],
|
||||
}, {
|
||||
origin: 'https://www.domain.com',
|
||||
localStorage: [{
|
||||
name: 'name2',
|
||||
value: 'value2'
|
||||
}],
|
||||
}, {
|
||||
origin: 'https://www.example.com',
|
||||
localStorage: [{
|
||||
name: 'name1',
|
||||
value: 'value1'
|
||||
}],
|
||||
}]);
|
||||
});
|
||||
|
||||
@ -222,3 +222,56 @@ it('should serialize storageState with lone surrogates', async ({ page, context,
|
||||
const storageState = await context.storageState();
|
||||
expect(storageState.origins[0].localStorage[0].value).toBe(String.fromCharCode(55934));
|
||||
});
|
||||
|
||||
it('should work when service worker is intefering', async ({ page, context, server, isAndroid, isElectron }) => {
|
||||
it.skip(isAndroid);
|
||||
it.skip(isElectron);
|
||||
|
||||
server.setRoute('/', (req, res) => {
|
||||
res.writeHead(200, { 'content-type': 'text/html' });
|
||||
res.end(`
|
||||
<script>
|
||||
console.log('from page');
|
||||
window.localStorage.foo = 'bar';
|
||||
window.registrationPromise = navigator.serviceWorker.register('sw.js');
|
||||
window.activationPromise = new Promise(resolve => navigator.serviceWorker.oncontrollerchange = resolve);
|
||||
</script>
|
||||
`);
|
||||
});
|
||||
|
||||
server.setRoute('/sw.js', (req, res) => {
|
||||
res.writeHead(200, { 'content-type': 'application/javascript' });
|
||||
res.end(`
|
||||
const kHtmlPage = \`
|
||||
<script>
|
||||
console.log('from sw page');
|
||||
let counter = window.localStorage.counter || 0;
|
||||
++counter;
|
||||
window.localStorage.counter = counter;
|
||||
setTimeout(() => {
|
||||
window.location.href = counter + '.html';
|
||||
}, 0);
|
||||
</script>
|
||||
\`;
|
||||
|
||||
console.log('from sw 1');
|
||||
self.addEventListener('fetch', event => {
|
||||
console.log('fetching ' + event.request.url);
|
||||
const blob = new Blob([kHtmlPage], { type: 'text/html' });
|
||||
const response = new Response(blob, { status: 200 , statusText: 'OK' });
|
||||
event.respondWith(response);
|
||||
});
|
||||
|
||||
self.addEventListener('activate', event => {
|
||||
console.log('from sw 2');
|
||||
event.waitUntil(clients.claim());
|
||||
});
|
||||
`);
|
||||
});
|
||||
|
||||
await page.goto(server.PREFIX);
|
||||
await page.evaluate(() => window['activationPromise']);
|
||||
|
||||
const storageState = await context.storageState();
|
||||
expect(storageState.origins[0].localStorage[0]).toEqual({ name: 'foo', value: 'bar' });
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user