chore: do not initialize full sessions for pages used in session restore (#12886)

This commit is contained in:
Pavel Feldman 2022-03-18 17:17:37 -08:00 committed by GitHub
parent 4aaa63beaa
commit 98ed81dc00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 127 additions and 75 deletions

View File

@ -38,9 +38,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
}
async newContext(params: channels.BrowserNewContextParams, metadata: CallMetadata): Promise<channels.BrowserNewContextResult> {
const context = await this._object.newContext(params);
if (params.storageState)
await context.setStorageState(metadata, params.storageState);
const context = await this._object.newContext(metadata, params);
return { context: new BrowserContextDispatcher(this._scope, context) };
}
@ -91,12 +89,10 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
async newContext(params: channels.BrowserNewContextParams, metadata: CallMetadata): Promise<channels.BrowserNewContextResult> {
if (params.recordVideo)
params.recordVideo.dir = this._object.options.artifactsDir;
const context = await this._object.newContext(params);
const context = await this._object.newContext(metadata, params);
this._contexts.add(context);
context._setSelectors(this.selectors);
context.on(BrowserContext.Events.Close, () => this._contexts.delete(context));
if (params.storageState)
await context.setStorageState(metadata, params.storageState);
return { context: new BrowserContextDispatcher(this._scope, context) };
}

View File

@ -15,13 +15,13 @@
*/
import * as types from './types';
import { BrowserContext } from './browserContext';
import { BrowserContext, validateBrowserContextOptions } from './browserContext';
import { Page } from './page';
import { Download } from './download';
import { ProxySettings } from './types';
import { ChildProcess } from 'child_process';
import { RecentLogsCollector } from '../utils/debugLogger';
import { SdkObject } from './instrumentation';
import { CallMetadata, SdkObject } from './instrumentation';
import { Artifact } from './artifact';
import { Selectors } from './selectors';
@ -74,12 +74,20 @@ export abstract class Browser extends SdkObject {
this.options = options;
}
abstract newContext(options: types.BrowserContextOptions): Promise<BrowserContext>;
abstract doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext>;
abstract contexts(): BrowserContext[];
abstract isConnected(): boolean;
abstract version(): string;
abstract userAgent(): string;
async newContext(metadata: CallMetadata, options: types.BrowserContextOptions): Promise<BrowserContext> {
validateBrowserContextOptions(options, this.options);
const context = await this.doCreateNewContext(options);
if (options.storageState)
await context.setStorageState(metadata, options.storageState);
return context;
}
_downloadCreated(page: Page, uuid: string, url: string, suggestedFilename?: string) {
const download = new Download(page, this.options.downloadsPath || '', uuid, url, suggestedFilename);
this._downloads.set(uuid, download);

View File

@ -68,6 +68,7 @@ export abstract class BrowserContext extends SdkObject {
readonly fetchRequest: BrowserContextAPIRequestContext;
private _customCloseHandler?: () => Promise<any>;
readonly _tempDirs: string[] = [];
private _settingStorageState = false;
constructor(browser: Browser, options: types.BrowserContextOptions, browserContextId: string | undefined) {
super(browser, 'browser-context');
@ -375,7 +376,13 @@ export abstract class BrowserContext extends SdkObject {
return result;
}
isSettingStorageState(): boolean {
return this._settingStorageState;
}
async setStorageState(metadata: CallMetadata, state: types.SetStorageState) {
this._settingStorageState = true;
try {
if (state.cookies)
await this.addCookies(state.cookies);
if (state.origins && state.origins.length) {
@ -395,6 +402,9 @@ export abstract class BrowserContext extends SdkObject {
}
await page.close(internalMetadata);
}
} finally {
this._settingStorageState = false;
}
}
async extendInjectedScript(source: string, arg?: any) {

View File

@ -16,7 +16,7 @@
*/
import { Browser, BrowserOptions } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
import { assert } from '../../utils/utils';
import * as network from '../network';
import { Page, PageBinding, PageDelegate, Worker } from '../page';
@ -92,9 +92,7 @@ export class CRBrowser extends Browser {
this._session.on('Browser.downloadProgress', this._onDownloadProgress.bind(this));
}
async newContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
validateBrowserContextOptions(options, this.options);
async doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
let proxyBypassList = undefined;
if (options.proxy) {
if (process.env.PLAYWRIGHT_DISABLE_FORCED_CHROMIUM_PROXIED_LOOPBACK)

View File

@ -439,7 +439,8 @@ class FrameSession {
}
async _initialize(hasUIWindow: boolean) {
if (hasUIWindow &&
const isSettingStorageState = this._page._browserContext.isSettingStorageState();
if (!isSettingStorageState && hasUIWindow &&
!this._crPage._browserContext._browser.isClank() &&
!this._crPage._browserContext._options.noDefaultViewport) {
const { windowId } = await this._client.send('Browser.getWindowForTarget');
@ -447,7 +448,7 @@ class FrameSession {
}
let screencastOptions: types.PageScreencastOptions | undefined;
if (this._isMainFrame() && this._crPage._browserContext._options.recordVideo && hasUIWindow) {
if (!isSettingStorageState && this._isMainFrame() && this._crPage._browserContext._options.recordVideo && hasUIWindow) {
const screencastId = createGuid();
const outputFile = path.join(this._crPage._browserContext._options.recordVideo.dir, screencastId + '.webm');
screencastOptions = {
@ -512,6 +513,7 @@ class FrameSession {
this._networkManager.initialize(),
this._client.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }),
];
if (!isSettingStorageState) {
if (this._isMainFrame())
promises.push(this._client.send('Emulation.setFocusEmulationEnabled', { enabled: true }));
const options = this._crPage._browserContext._options;
@ -547,6 +549,7 @@ class FrameSession {
promises.push(this._evaluateOnNewDocument(source, 'main'));
if (screencastOptions)
promises.push(this._startVideoRecording(screencastOptions));
}
promises.push(this._client.send('Runtime.runIfWaitingForDebugger'));
promises.push(this._firstNonInitialNavigationCommittedPromise);
await Promise.all(promises);

View File

@ -18,7 +18,7 @@
import { kBrowserClosedError } from '../../utils/errors';
import { assert } from '../../utils/utils';
import { Browser, BrowserOptions } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
import * as network from '../network';
import { Page, PageBinding, PageDelegate } from '../page';
import { ConnectionTransport } from '../transport';
@ -76,8 +76,7 @@ export class FFBrowser extends Browser {
return !this._connection._closed;
}
async newContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
validateBrowserContextOptions(options, this.options);
async doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
if (options.isMobile)
throw new Error('options.isMobile is not supported in Firefox');
const { browserContextId } = await this._connection.send('Browser.createBrowserContext', { removeOnDetach: true });

View File

@ -257,6 +257,7 @@ export type BrowserContextOptions = {
omitContent?: boolean,
path: string
},
storageState?: SetStorageState,
strictSelectors?: boolean,
proxy?: ProxySettings,
baseURL?: string,

View File

@ -16,7 +16,7 @@
*/
import { Browser, BrowserOptions } from '../browser';
import { assertBrowserContextIsNotOwned, BrowserContext, validateBrowserContextOptions, verifyGeolocation } from '../browserContext';
import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext';
import { eventsHelper, RegisteredListener } from '../../utils/eventsHelper';
import { assert } from '../../utils/utils';
import * as network from '../network';
@ -79,8 +79,7 @@ export class WKBrowser extends Browser {
this._didClose();
}
async newContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
validateBrowserContextOptions(options, this.options);
async doCreateNewContext(options: types.BrowserContextOptions): Promise<BrowserContext> {
const createOptions = options.proxy ? {
proxyServer: options.proxy.server,
proxyBypassList: options.proxy.bypass

View File

@ -114,6 +114,8 @@ export class WKPage implements PageDelegate {
}
private async _initializePageProxySession() {
if (this._page._browserContext.isSettingStorageState())
return;
const promises: Promise<any>[] = [
this._pageProxySession.send('Dialog.enable'),
this._pageProxySession.send('Emulation.setActiveAndFocused', { active: true }),
@ -183,6 +185,10 @@ export class WKPage implements PageDelegate {
promises.push(session.send('Network.setInterceptionEnabled', { enabled: true }));
promises.push(session.send('Network.addInterception', { url: '.*', stage: 'request', isRegex: true }));
}
if (this._page._browserContext.isSettingStorageState()) {
await Promise.all(promises);
return;
}
const contextOptions = this._browserContext._options;
if (contextOptions.userAgent)

View File

@ -48,8 +48,8 @@ it('should capture local storage', async ({ contextFactory }) => {
}]);
});
it('should set local storage', async ({ browser }) => {
const context = await browser.newContext({
it('should set local storage', async ({ contextFactory }) => {
const context = await contextFactory({
storageState: {
cookies: [],
origins: [
@ -162,3 +162,35 @@ it('should not emit events about internal page', async ({ contextFactory }) => {
await context.storageState();
expect(events).toHaveLength(0);
});
it('should not restore localStorage twice', async ({ contextFactory }) => {
const context = await contextFactory({
storageState: {
cookies: [],
origins: [
{
origin: 'https://www.example.com',
localStorage: [{
name: 'name1',
value: 'value1'
}]
},
]
}
});
const page = await context.newPage();
await page.route('**/*', route => {
route.fulfill({ body: '<html></html>' }).catch(() => {});
});
await page.goto('https://www.example.com');
const localStorage1 = await page.evaluate('window.localStorage');
expect(localStorage1).toEqual({ name1: 'value1' });
await page.evaluate(() => window.localStorage['name1'] = 'value2');
await page.goto('https://www.example.com');
const localStorage2 = await page.evaluate('window.localStorage');
expect(localStorage2).toEqual({ name1: 'value2' });
await context.close();
});