chore: preserve window while reusing window (#16225)
This commit is contained in:
parent
03fe75251b
commit
74f7005c02
|
|
@ -65,7 +65,14 @@ export class PlaywrightServer {
|
|||
if (this._mode === 'reuse-browser') {
|
||||
const callMetadata = serverSideCallMetadata();
|
||||
const browser = await this._preLaunchedPlaywright!.chromium.launch(callMetadata, { headless: false });
|
||||
const { context } = await browser.newContextForReuse({ viewport: { width: 800, height: 600 } }, callMetadata);
|
||||
const { context } = await browser.newContextForReuse({
|
||||
viewport: {
|
||||
width: 800,
|
||||
height: 600
|
||||
},
|
||||
locale: 'en-US',
|
||||
deviceScaleFactor: process.platform === 'darwin' ? 2 : 1
|
||||
}, callMetadata);
|
||||
const page = await context.newPage(callMetadata);
|
||||
await page.mainFrame().setContent(callMetadata, `
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -94,6 +94,10 @@ export abstract class Browser extends SdkObject {
|
|||
|
||||
async newContextForReuse(params: channels.BrowserNewContextForReuseParams, metadata: CallMetadata): Promise<{ context: BrowserContext, needsReset: boolean }> {
|
||||
const hash = BrowserContext.reusableContextHash(params);
|
||||
for (const context of this.contexts()) {
|
||||
if (context !== this._contextForReuse?.context)
|
||||
await context.close(metadata);
|
||||
}
|
||||
if (!this._contextForReuse || hash !== this._contextForReuse.hash || !this._contextForReuse.context.canResetForReuse()) {
|
||||
if (this._contextForReuse)
|
||||
await this._contextForReuse.context.close(metadata);
|
||||
|
|
|
|||
|
|
@ -145,6 +145,13 @@ export abstract class BrowserContext extends SdkObject {
|
|||
|
||||
static reusableContextHash(params: channels.BrowserNewContextForReuseParams): string {
|
||||
const paramsCopy = { ...params };
|
||||
|
||||
for (const k of Object.keys(paramsCopy)) {
|
||||
const key = k as keyof channels.BrowserNewContextForReuseParams;
|
||||
if (paramsCopy[key] === defaultNewContextParamValues[key])
|
||||
delete paramsCopy[key];
|
||||
}
|
||||
|
||||
for (const key of paramsThatAllowContextReuse)
|
||||
delete paramsCopy[key];
|
||||
return JSON.stringify(paramsCopy);
|
||||
|
|
@ -159,6 +166,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
|
||||
await this._cancelAllRoutesInFlight();
|
||||
|
||||
// Close extra pages early.
|
||||
let page: Page | undefined = this.pages()[0];
|
||||
const [, ...otherPages] = this.pages();
|
||||
for (const p of otherPages)
|
||||
|
|
@ -184,6 +192,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
await this.setExtraHTTPHeaders(this._options.extraHTTPHeaders || []);
|
||||
await this.setGeolocation(this._options.geolocation);
|
||||
await this.setOffline(!!this._options.offline);
|
||||
await this.setUserAgent(this._options.userAgent);
|
||||
await this.clearCookies();
|
||||
|
||||
await page?.resetForReuse(metadata);
|
||||
|
|
@ -218,6 +227,7 @@ export abstract class BrowserContext extends SdkObject {
|
|||
abstract clearCookies(): Promise<void>;
|
||||
abstract setGeolocation(geolocation?: types.Geolocation): Promise<void>;
|
||||
abstract setExtraHTTPHeaders(headers: types.HeadersArray): Promise<void>;
|
||||
abstract setUserAgent(userAgent: string | undefined): Promise<void>;
|
||||
abstract setOffline(offline: boolean): Promise<void>;
|
||||
abstract cancelDownload(uuid: string): Promise<void>;
|
||||
protected abstract doGetCookies(urls: string[]): Promise<channels.NetworkCookie[]>;
|
||||
|
|
@ -637,5 +647,19 @@ const paramsThatAllowContextReuse: (keyof channels.BrowserNewContextForReusePara
|
|||
'forcedColors',
|
||||
'reducedMotion',
|
||||
'screen',
|
||||
'viewport'
|
||||
'userAgent',
|
||||
'viewport',
|
||||
];
|
||||
|
||||
const defaultNewContextParamValues: channels.BrowserNewContextForReuseParams = {
|
||||
noDefaultViewport: false,
|
||||
ignoreHTTPSErrors: false,
|
||||
javaScriptEnabled: true,
|
||||
bypassCSP: false,
|
||||
offline: false,
|
||||
isMobile: false,
|
||||
hasTouch: false,
|
||||
acceptDownloads: true,
|
||||
strictSelectors: false,
|
||||
serviceWorkers: 'allow',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -438,6 +438,13 @@ export class CRBrowserContext extends BrowserContext {
|
|||
await (sw as CRServiceWorker).updateExtraHTTPHeaders(false);
|
||||
}
|
||||
|
||||
async setUserAgent(userAgent: string | undefined): Promise<void> {
|
||||
this._options.userAgent = userAgent;
|
||||
for (const page of this.pages())
|
||||
await (page._delegate as CRPage).updateUserAgent();
|
||||
// TODO: service workers don't have Emulation domain?
|
||||
}
|
||||
|
||||
async setOffline(offline: boolean): Promise<void> {
|
||||
this._options.offline = offline;
|
||||
for (const page of this.pages())
|
||||
|
|
|
|||
|
|
@ -199,8 +199,8 @@ export class CRPage implements PageDelegate {
|
|||
await this._forAllFrameSessions(frame => frame._updateHttpCredentials(false));
|
||||
}
|
||||
|
||||
async updateEmulatedViewportSize(): Promise<void> {
|
||||
await this._mainFrameSession._updateViewport();
|
||||
async updateEmulatedViewportSize(preserveWindowBoundaries?: boolean): Promise<void> {
|
||||
await this._mainFrameSession._updateViewport(preserveWindowBoundaries);
|
||||
}
|
||||
|
||||
async bringToFront(): Promise<void> {
|
||||
|
|
@ -211,6 +211,10 @@ export class CRPage implements PageDelegate {
|
|||
await this._forAllFrameSessions(frame => frame._updateEmulateMedia());
|
||||
}
|
||||
|
||||
async updateUserAgent(): Promise<void> {
|
||||
await this._forAllFrameSessions(frame => frame._updateUserAgent());
|
||||
}
|
||||
|
||||
async updateRequestInterception(): Promise<void> {
|
||||
await this._forAllFrameSessions(frame => frame._updateRequestInterception());
|
||||
}
|
||||
|
|
@ -399,6 +403,7 @@ class FrameSession {
|
|||
private _screencastClients = new Set<any>();
|
||||
private _evaluateOnNewDocumentIdentifiers: string[] = [];
|
||||
private _exposedBindingNames: string[] = [];
|
||||
private _metricsOverride: Protocol.Emulation.setDeviceMetricsOverrideParameters | undefined;
|
||||
|
||||
constructor(crPage: CRPage, client: CRSession, targetId: string, parentSession: FrameSession | null) {
|
||||
this._client = client;
|
||||
|
|
@ -542,7 +547,7 @@ class FrameSession {
|
|||
if (options.javaScriptEnabled === false)
|
||||
promises.push(this._client.send('Emulation.setScriptExecutionDisabled', { value: true }));
|
||||
if (options.userAgent || options.locale)
|
||||
promises.push(this._client.send('Emulation.setUserAgentOverride', { userAgent: options.userAgent || '', acceptLanguage: options.locale }));
|
||||
promises.push(this._updateUserAgent());
|
||||
if (options.locale)
|
||||
promises.push(emulateLocale(this._client, options.locale));
|
||||
if (options.timezoneId)
|
||||
|
|
@ -995,7 +1000,7 @@ class FrameSession {
|
|||
await this._networkManager.authenticate(credentials);
|
||||
}
|
||||
|
||||
async _updateViewport(): Promise<void> {
|
||||
async _updateViewport(preserveWindowBoundaries?: boolean): Promise<void> {
|
||||
if (this._crPage._browserContext._browser.isClank())
|
||||
return;
|
||||
assert(this._isMainFrame());
|
||||
|
|
@ -1006,18 +1011,22 @@ class FrameSession {
|
|||
const viewportSize = emulatedSize.viewport;
|
||||
const screenSize = emulatedSize.screen;
|
||||
const isLandscape = viewportSize.width > viewportSize.height;
|
||||
const metricsOverride: Protocol.Emulation.setDeviceMetricsOverrideParameters = {
|
||||
mobile: !!options.isMobile,
|
||||
width: viewportSize.width,
|
||||
height: viewportSize.height,
|
||||
screenWidth: screenSize.width,
|
||||
screenHeight: screenSize.height,
|
||||
deviceScaleFactor: options.deviceScaleFactor || 1,
|
||||
screenOrientation: isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' },
|
||||
dontSetVisibleSize: preserveWindowBoundaries
|
||||
};
|
||||
if (JSON.stringify(this._metricsOverride) === JSON.stringify(metricsOverride))
|
||||
return;
|
||||
const promises = [
|
||||
this._client.send('Emulation.setDeviceMetricsOverride', {
|
||||
mobile: !!options.isMobile,
|
||||
width: viewportSize.width,
|
||||
height: viewportSize.height,
|
||||
screenWidth: screenSize.width,
|
||||
screenHeight: screenSize.height,
|
||||
deviceScaleFactor: options.deviceScaleFactor || 1,
|
||||
screenOrientation: isLandscape ? { angle: 90, type: 'landscapePrimary' } : { angle: 0, type: 'portraitPrimary' },
|
||||
}),
|
||||
this._client.send('Emulation.setDeviceMetricsOverride', metricsOverride),
|
||||
];
|
||||
if (this._windowId) {
|
||||
if (!preserveWindowBoundaries && this._windowId) {
|
||||
let insets = { width: 0, height: 0 };
|
||||
if (this._crPage._browserContext._browser.options.headful) {
|
||||
// TODO: popup windows have their own insets.
|
||||
|
|
@ -1041,6 +1050,7 @@ class FrameSession {
|
|||
}));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
this._metricsOverride = metricsOverride;
|
||||
}
|
||||
|
||||
async windowBounds(): Promise<WindowBounds> {
|
||||
|
|
@ -1071,6 +1081,11 @@ class FrameSession {
|
|||
await this._client.send('Emulation.setEmulatedMedia', { media: emulatedMedia.media || '', features });
|
||||
}
|
||||
|
||||
async _updateUserAgent(): Promise<void> {
|
||||
const options = this._crPage._browserContext._options;
|
||||
await this._client.send('Emulation.setUserAgentOverride', { userAgent: options.userAgent || '', acceptLanguage: options.locale });
|
||||
}
|
||||
|
||||
private async _setDefaultFontFamilies(session: CRSession) {
|
||||
const fontFamilies = platformToFontFamilies[this._crPage._browserContext._browser._platform()];
|
||||
await session.send('Page.setFontFamilies', fontFamilies);
|
||||
|
|
|
|||
|
|
@ -310,6 +310,10 @@ export class FFBrowserContext extends BrowserContext {
|
|||
await this._browser._connection.send('Browser.setExtraHTTPHeaders', { browserContextId: this._browserContextId, headers: allHeaders });
|
||||
}
|
||||
|
||||
async setUserAgent(userAgent: string | undefined): Promise<void> {
|
||||
await this._browser._connection.send('Browser.setUserAgentOverride', { browserContextId: this._browserContextId, userAgent: userAgent || null });
|
||||
}
|
||||
|
||||
async setOffline(offline: boolean): Promise<void> {
|
||||
this._options.offline = offline;
|
||||
await this._browser._connection.send('Browser.setOnlineOverride', { browserContextId: this._browserContextId, override: offline ? 'offline' : 'online' });
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export interface PageDelegate {
|
|||
navigateFrame(frame: frames.Frame, url: string, referrer: string | undefined): Promise<frames.GotoResult>;
|
||||
|
||||
updateExtraHTTPHeaders(): Promise<void>;
|
||||
updateEmulatedViewportSize(): Promise<void>;
|
||||
updateEmulatedViewportSize(preserveWindowBoundaries?: boolean): Promise<void>;
|
||||
updateEmulateMedia(): Promise<void>;
|
||||
updateRequestInterception(): Promise<void>;
|
||||
updateFileChooserInterception(): Promise<void>;
|
||||
|
|
@ -247,10 +247,11 @@ export class Page extends SdkObject {
|
|||
this._extraHTTPHeaders = undefined;
|
||||
this._interceptFileChooser = false;
|
||||
|
||||
await this._delegate.updateEmulatedViewportSize();
|
||||
await this._delegate.updateEmulateMedia();
|
||||
await this._delegate.updateExtraHTTPHeaders();
|
||||
await this._delegate.updateFileChooserInterception();
|
||||
await Promise.all([
|
||||
this._delegate.updateEmulatedViewportSize(true),
|
||||
this._delegate.updateEmulateMedia(),
|
||||
this._delegate.updateFileChooserInterception(),
|
||||
]);
|
||||
}
|
||||
|
||||
async _doSlowMo() {
|
||||
|
|
|
|||
|
|
@ -292,6 +292,12 @@ export class WKBrowserContext extends BrowserContext {
|
|||
await (page._delegate as WKPage).updateExtraHTTPHeaders();
|
||||
}
|
||||
|
||||
async setUserAgent(userAgent: string | undefined): Promise<void> {
|
||||
this._options.userAgent = userAgent;
|
||||
for (const page of this.pages())
|
||||
await (page._delegate as WKPage).updateUserAgent();
|
||||
}
|
||||
|
||||
async setOffline(offline: boolean): Promise<void> {
|
||||
this._options.offline = offline;
|
||||
for (const page of this.pages())
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ export class WKPage implements PageDelegate {
|
|||
|
||||
const contextOptions = this._browserContext._options;
|
||||
if (contextOptions.userAgent)
|
||||
promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent }));
|
||||
promises.push(this.updateUserAgent());
|
||||
const emulatedMedia = this._page.emulatedMedia();
|
||||
if (emulatedMedia.media || emulatedMedia.colorScheme || emulatedMedia.reducedMotion)
|
||||
promises.push(WKPage._setEmulateMedia(session, emulatedMedia.media, emulatedMedia.colorScheme, emulatedMedia.reducedMotion));
|
||||
|
|
@ -679,6 +679,11 @@ export class WKPage implements PageDelegate {
|
|||
await this._updateViewport();
|
||||
}
|
||||
|
||||
async updateUserAgent(): Promise<void> {
|
||||
const contextOptions = this._browserContext._options;
|
||||
this._updateState('Page.overrideUserAgent', { value: contextOptions.userAgent });
|
||||
}
|
||||
|
||||
async bringToFront(): Promise<void> {
|
||||
this._pageProxySession.send('Target.activate', {
|
||||
targetId: this._session.sessionId
|
||||
|
|
|
|||
|
|
@ -36,10 +36,23 @@ test('should reuse context', async ({ runInlineTest }) => {
|
|||
expect(context._guid).toBe(lastContextGuid);
|
||||
});
|
||||
|
||||
test.describe('Dark', () => {
|
||||
test.use({ userAgent: 'dark' });
|
||||
test.describe(() => {
|
||||
test.use({ colorScheme: 'dark' });
|
||||
test('dark', async ({ context }) => {
|
||||
expect(context._guid).toBe(lastContextGuid);
|
||||
});
|
||||
});
|
||||
|
||||
test('three', async ({ context }) => {
|
||||
test.describe(() => {
|
||||
test.use({ userAgent: 'UA' });
|
||||
test('UA', async ({ context }) => {
|
||||
expect(context._guid).toBe(lastContextGuid);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe(() => {
|
||||
test.use({ timezoneId: 'Europe/Berlin' });
|
||||
test('tz', async ({ context }) => {
|
||||
expect(context._guid).not.toBe(lastContextGuid);
|
||||
});
|
||||
});
|
||||
|
|
@ -47,7 +60,7 @@ test('should reuse context', async ({ runInlineTest }) => {
|
|||
}, { workers: 1 });
|
||||
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.passed).toBe(3);
|
||||
expect(result.passed).toBe(5);
|
||||
});
|
||||
|
||||
test('should not reuse context with video', async ({ runInlineTest }) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue