diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index da21dff708..80681130ef 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -86,7 +86,7 @@ export abstract class BrowserContext extends SdkObject { private _customCloseHandler?: () => Promise; readonly _tempDirs: string[] = []; private _settingStorageState = false; - readonly initScripts: InitScript[] = []; + initScripts: InitScript[] = []; private _routesInFlight = new Set(); private _debugger!: Debugger; _closeReason: string | undefined; @@ -271,9 +271,7 @@ export abstract class BrowserContext extends SdkObject { protected abstract doClearPermissions(): Promise; protected abstract doSetHTTPCredentials(httpCredentials?: types.Credentials): Promise; protected abstract doAddInitScript(initScript: InitScript): Promise; - protected abstract doRemoveInitScripts(): Promise; - protected abstract doExposeBinding(binding: PageBinding): Promise; - protected abstract doRemoveExposedBindings(): Promise; + protected abstract doRemoveNonInternalInitScripts(): Promise; protected abstract doUpdateRequestInterception(): Promise; protected abstract doClose(reason: string | undefined): Promise; protected abstract onClosePersistent(): void; @@ -320,15 +318,16 @@ export abstract class BrowserContext extends SdkObject { } const binding = new PageBinding(name, playwrightBinding, needsHandle); this._pageBindings.set(name, binding); - await this.doExposeBinding(binding); + await this.doAddInitScript(binding.initScript); + const frames = this.pages().map(page => page.frames()).flat(); + await Promise.all(frames.map(frame => frame.evaluateExpression(binding.initScript.source).catch(e => {}))); } async _removeExposedBindings() { - for (const key of this._pageBindings.keys()) { - if (!key.startsWith('__pw')) + for (const [key, binding] of this._pageBindings) { + if (!binding.internal) this._pageBindings.delete(key); } - await this.doRemoveExposedBindings(); } async grantPermissions(permissions: string[], origin?: string) { @@ -414,8 +413,8 @@ export abstract class BrowserContext extends SdkObject { } async _removeInitScripts(): Promise { - this.initScripts.splice(0, this.initScripts.length); - await this.doRemoveInitScripts(); + this.initScripts = this.initScripts.filter(script => script.internal); + await this.doRemoveNonInternalInitScripts(); } async setRequestInterceptor(handler: network.RouteHandler | undefined): Promise { diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 777ff2eee8..42b916c186 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -21,7 +21,7 @@ import { Browser } from '../browser'; import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext'; import { assert, createGuid } from '../../utils'; import * as network from '../network'; -import type { InitScript, PageBinding, PageDelegate, Worker } from '../page'; +import type { InitScript, PageDelegate, Worker } from '../page'; import { Page } from '../page'; import { Frame } from '../frames'; import type { Dialog } from '../dialog'; @@ -491,19 +491,9 @@ export class CRBrowserContext extends BrowserContext { await (page._delegate as CRPage).addInitScript(initScript); } - async doRemoveInitScripts() { + async doRemoveNonInternalInitScripts() { for (const page of this.pages()) - await (page._delegate as CRPage).removeInitScripts(); - } - - async doExposeBinding(binding: PageBinding) { - for (const page of this.pages()) - await (page._delegate as CRPage).exposeBinding(binding); - } - - async doRemoveExposedBindings() { - for (const page of this.pages()) - await (page._delegate as CRPage).removeExposedBindings(); + await (page._delegate as CRPage).removeNonInternalInitScripts(); } async doUpdateRequestInterception(): Promise { diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 53b96ad403..904ed5a479 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -26,7 +26,7 @@ import * as dom from '../dom'; import * as frames from '../frames'; import { helper } from '../helper'; import * as network from '../network'; -import type { InitScript, PageBinding, PageDelegate } from '../page'; +import { type InitScript, PageBinding, type PageDelegate } from '../page'; import { Page, Worker } from '../page'; import type { Progress } from '../progress'; import type * as types from '../types'; @@ -182,15 +182,6 @@ export class CRPage implements PageDelegate { return this._sessionForFrame(frame)._navigate(frame, url, referrer); } - async exposeBinding(binding: PageBinding) { - await this._forAllFrameSessions(frame => frame._initBinding(binding)); - await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(binding.source).catch(e => {}))); - } - - async removeExposedBindings() { - await this._forAllFrameSessions(frame => frame._removeExposedBindings()); - } - async updateExtraHTTPHeaders(): Promise { const headers = network.mergeHeaders([ this._browserContext._options.extraHTTPHeaders, @@ -260,7 +251,7 @@ export class CRPage implements PageDelegate { await this._forAllFrameSessions(frame => frame._evaluateOnNewDocument(initScript, world)); } - async removeInitScripts() { + async removeNonInternalInitScripts() { await this._forAllFrameSessions(frame => frame._removeEvaluatesOnNewDocument()); } @@ -420,7 +411,6 @@ class FrameSession { private _screencastId: string | null = null; private _screencastClients = new Set(); private _evaluateOnNewDocumentIdentifiers: string[] = []; - private _exposedBindingNames: string[] = []; private _metricsOverride: Protocol.Emulation.setDeviceMetricsOverrideParameters | undefined; private _workerSessions = new Map(); @@ -519,9 +509,7 @@ class FrameSession { grantUniveralAccess: true, worldName: UTILITY_WORLD_NAME, }); - for (const binding of this._crPage._browserContext._pageBindings.values()) - frame.evaluateExpression(binding.source).catch(e => {}); - for (const initScript of this._crPage._browserContext.initScripts) + for (const initScript of this._crPage._page.allInitScripts()) frame.evaluateExpression(initScript.source).catch(e => {}); } @@ -541,6 +529,7 @@ class FrameSession { this._client.send('Log.enable', {}), lifecycleEventsEnabled = this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }), this._client.send('Runtime.enable', {}), + this._client.send('Runtime.addBinding', { name: PageBinding.kPlaywrightBinding }), this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: '', worldName: UTILITY_WORLD_NAME, @@ -573,11 +562,7 @@ class FrameSession { promises.push(this._updateGeolocation(true)); promises.push(this._updateEmulateMedia()); promises.push(this._updateFileChooserInterception(true)); - for (const binding of this._crPage._page.allBindings()) - promises.push(this._initBinding(binding)); - for (const initScript of this._crPage._browserContext.initScripts) - promises.push(this._evaluateOnNewDocument(initScript, 'main')); - for (const initScript of this._crPage._page.initScripts) + for (const initScript of this._crPage._page.allInitScripts()) promises.push(this._evaluateOnNewDocument(initScript, 'main')); if (screencastOptions) promises.push(this._startVideoRecording(screencastOptions)); @@ -834,25 +819,6 @@ class FrameSession { this._page._addConsoleMessage(event.type, values, toConsoleMessageLocation(event.stackTrace)); } - async _initBinding(binding: PageBinding) { - const [, response] = await Promise.all([ - this._client.send('Runtime.addBinding', { name: binding.name }), - this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: binding.source }) - ]); - this._exposedBindingNames.push(binding.name); - if (!binding.name.startsWith('__pw')) - this._evaluateOnNewDocumentIdentifiers.push(response.identifier); - } - - async _removeExposedBindings() { - const toRetain: string[] = []; - const toRemove: string[] = []; - for (const name of this._exposedBindingNames) - (name.startsWith('__pw_') ? toRetain : toRemove).push(name); - this._exposedBindingNames = toRetain; - await Promise.all(toRemove.map(name => this._client.send('Runtime.removeBinding', { name }))); - } - async _onBindingCalled(event: Protocol.Runtime.bindingCalledPayload) { const pageOrError = await this._crPage.pageOrError(); if (!(pageOrError instanceof Error)) { @@ -1102,7 +1068,8 @@ class FrameSession { async _evaluateOnNewDocument(initScript: InitScript, world: types.World): Promise { const worldName = world === 'utility' ? UTILITY_WORLD_NAME : undefined; const { identifier } = await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: initScript.source, worldName }); - this._evaluateOnNewDocumentIdentifiers.push(identifier); + if (!initScript.internal) + this._evaluateOnNewDocumentIdentifiers.push(identifier); } async _removeEvaluatesOnNewDocument(): Promise { diff --git a/packages/playwright-core/src/server/firefox/ffBrowser.ts b/packages/playwright-core/src/server/firefox/ffBrowser.ts index 7ed2d8cb2f..94b90bbcea 100644 --- a/packages/playwright-core/src/server/firefox/ffBrowser.ts +++ b/packages/playwright-core/src/server/firefox/ffBrowser.ts @@ -21,7 +21,8 @@ import type { BrowserOptions } from '../browser'; import { Browser } from '../browser'; import { assertBrowserContextIsNotOwned, BrowserContext, verifyGeolocation } from '../browserContext'; import * as network from '../network'; -import type { InitScript, Page, PageBinding, PageDelegate } from '../page'; +import type { InitScript, Page, PageDelegate } from '../page'; +import { PageBinding } from '../page'; import type { ConnectionTransport } from '../transport'; import type * as types from '../types'; import type * as channels from '@protocol/channels'; @@ -178,7 +179,10 @@ export class FFBrowserContext extends BrowserContext { override async _initialize() { assert(!this._ffPages().length); const browserContextId = this._browserContextId; - const promises: Promise[] = [super._initialize()]; + const promises: Promise[] = [ + super._initialize(), + this._browser.session.send('Browser.addBinding', { browserContextId: this._browserContextId, name: PageBinding.kPlaywrightBinding, script: '' }), + ]; if (this._options.acceptDownloads !== 'internal-browser-default') { promises.push(this._browser.session.send('Browser.setDownloadOptions', { browserContextId, @@ -353,21 +357,17 @@ export class FFBrowserContext extends BrowserContext { } async doAddInitScript(initScript: InitScript) { - await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: this.initScripts.map(script => ({ script: script.source })) }); + await this._updateInitScripts(); } - async doRemoveInitScripts() { - await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: [] }); + async doRemoveNonInternalInitScripts() { + await this._updateInitScripts(); } - async doExposeBinding(binding: PageBinding) { - await this._browser.session.send('Browser.addBinding', { browserContextId: this._browserContextId, name: binding.name, script: binding.source }); - } - - async doRemoveExposedBindings() { - // TODO: implement me. - // This is not a critical problem, what ends up happening is - // an old binding will be restored upon page reload and will point nowhere. + private async _updateInitScripts() { + const bindingScripts = [...this._pageBindings.values()].map(binding => binding.initScript.source); + const initScripts = this.initScripts.map(script => script.source); + await this._browser.session.send('Browser.setInitScripts', { browserContextId: this._browserContextId, scripts: [...bindingScripts, ...initScripts].map(script => ({ script })) }); } async doUpdateRequestInterception(): Promise { diff --git a/packages/playwright-core/src/server/firefox/ffPage.ts b/packages/playwright-core/src/server/firefox/ffPage.ts index aac22d5e50..6778a777f2 100644 --- a/packages/playwright-core/src/server/firefox/ffPage.ts +++ b/packages/playwright-core/src/server/firefox/ffPage.ts @@ -20,7 +20,7 @@ import * as dom from '../dom'; import type * as frames from '../frames'; import type { RegisteredListener } from '../../utils/eventsHelper'; import { eventsHelper } from '../../utils/eventsHelper'; -import type { PageBinding, PageDelegate } from '../page'; +import type { PageDelegate } from '../page'; import { InitScript } from '../page'; import { Page, Worker } from '../page'; import type * as types from '../types'; @@ -114,7 +114,7 @@ export class FFPage implements PageDelegate { }); // Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy. // Therefore, we can end up with an initialized page without utility world, although very unlikely. - this.addInitScript(new InitScript(''), UTILITY_WORLD_NAME).catch(e => this._markAsError(e)); + this.addInitScript(new InitScript('', true), UTILITY_WORLD_NAME).catch(e => this._markAsError(e)); } potentiallyUninitializedPage(): Page { @@ -336,14 +336,6 @@ export class FFPage implements PageDelegate { this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this.pageOrError()); } - async exposeBinding(binding: PageBinding) { - await this._session.send('Page.addBinding', { name: binding.name, script: binding.source }); - } - - async removeExposedBindings() { - // TODO: implement me. - } - didClose() { this._markAsError(new TargetClosedError()); this._session.dispose(); @@ -412,9 +404,9 @@ export class FFPage implements PageDelegate { await this._session.send('Page.setInitScripts', { scripts: this._initScripts.map(s => ({ script: s.initScript.source, worldName: s.worldName })) }); } - async removeInitScripts() { - this._initScripts = []; - await this._session.send('Page.setInitScripts', { scripts: [] }); + async removeNonInternalInitScripts() { + this._initScripts = this._initScripts.filter(s => s.initScript.internal); + await this._session.send('Page.setInitScripts', { scripts: this._initScripts.map(s => ({ script: s.initScript.source, worldName: s.worldName })) }); } async closePage(runBeforeUnload: boolean): Promise { diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index 6206681048..8176a1ba82 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -54,10 +54,8 @@ export interface PageDelegate { reload(): Promise; goBack(): Promise; goForward(): Promise; - exposeBinding(binding: PageBinding): Promise; - removeExposedBindings(): Promise; addInitScript(initScript: InitScript): Promise; - removeInitScripts(): Promise; + removeNonInternalInitScripts(): Promise; closePage(runBeforeUnload: boolean): Promise; potentiallyUninitializedPage(): Page; pageOrError(): Promise; @@ -154,7 +152,7 @@ export class Page extends SdkObject { private _emulatedMedia: Partial = {}; private _interceptFileChooser = false; private readonly _pageBindings = new Map(); - readonly initScripts: InitScript[] = []; + initScripts: InitScript[] = []; readonly _screenshotter: Screenshotter; readonly _frameManager: frames.FrameManager; readonly accessibility: accessibility.Accessibility; @@ -342,15 +340,15 @@ export class Page extends SdkObject { throw new Error(`Function "${name}" has been already registered in the browser context`); const binding = new PageBinding(name, playwrightBinding, needsHandle); this._pageBindings.set(name, binding); - await this._delegate.exposeBinding(binding); + await this._delegate.addInitScript(binding.initScript); + await Promise.all(this.frames().map(frame => frame.evaluateExpression(binding.initScript.source).catch(e => {}))); } async _removeExposedBindings() { - for (const key of this._pageBindings.keys()) { - if (!key.startsWith('__pw')) + for (const [key, binding] of this._pageBindings) { + if (!binding.internal) this._pageBindings.delete(key); } - await this._delegate.removeExposedBindings(); } setExtraHTTPHeaders(headers: types.HeadersArray) { @@ -533,8 +531,8 @@ export class Page extends SdkObject { } async _removeInitScripts() { - this.initScripts.splice(0, this.initScripts.length); - await this._delegate.removeInitScripts(); + this.initScripts = this.initScripts.filter(script => script.internal); + await this._delegate.removeNonInternalInitScripts(); } needsRequestInterception(): boolean { @@ -727,8 +725,9 @@ export class Page extends SdkObject { this._browserContext.addVisitedOrigin(origin); } - allBindings() { - return [...this._browserContext._pageBindings.values(), ...this._pageBindings.values()]; + allInitScripts() { + const bindings = [...this._browserContext._pageBindings.values(), ...this._pageBindings.values()]; + return [...bindings.map(binding => binding.initScript), ...this._browserContext.initScripts, ...this.initScripts]; } getBinding(name: string) { @@ -819,23 +818,29 @@ type BindingPayload = { }; export class PageBinding { + static kPlaywrightBinding = '__playwright__binding__'; + readonly name: string; readonly playwrightFunction: frames.FunctionWithSource; - readonly source: string; + readonly initScript: InitScript; readonly needsHandle: boolean; + readonly internal: boolean; constructor(name: string, playwrightFunction: frames.FunctionWithSource, needsHandle: boolean) { this.name = name; this.playwrightFunction = playwrightFunction; - this.source = `(${addPageBinding.toString()})(${JSON.stringify(name)}, ${needsHandle}, (${source})())`; + this.initScript = new InitScript(`(${addPageBinding.toString()})(${JSON.stringify(PageBinding.kPlaywrightBinding)}, ${JSON.stringify(name)}, ${needsHandle}, (${source})())`, true /* internal */); this.needsHandle = needsHandle; + this.internal = name.startsWith('__pw'); } static async dispatch(page: Page, payload: string, context: dom.FrameExecutionContext) { const { name, seq, serializedArgs } = JSON.parse(payload) as BindingPayload; try { assert(context.world); - const binding = page.getBinding(name)!; + const binding = page.getBinding(name); + if (!binding) + throw new Error(`Function "${name}" is not exposed`); let result: any; if (binding.needsHandle) { const handle = await context.evaluateHandle(takeHandle, { name, seq }).catch(e => null); @@ -877,10 +882,8 @@ export class PageBinding { } } -function addPageBinding(bindingName: string, needsHandle: boolean, utilityScriptSerializers: ReturnType) { - const binding = (globalThis as any)[bindingName]; - if (binding.__installed) - return; +function addPageBinding(playwrightBinding: string, bindingName: string, needsHandle: boolean, utilityScriptSerializers: ReturnType) { + const binding = (globalThis as any)[playwrightBinding]; (globalThis as any)[bindingName] = (...args: any[]) => { const me = (globalThis as any)[bindingName]; if (needsHandle && args.slice(1).some(arg => arg !== undefined)) @@ -919,8 +922,9 @@ function addPageBinding(bindingName: string, needsHandle: boolean, utilityScript export class InitScript { readonly source: string; + readonly internal: boolean; - constructor(source: string) { + constructor(source: string, internal?: boolean) { const guid = createGuid(); this.source = `(() => { globalThis.__pwInitScripts = globalThis.__pwInitScripts || {}; @@ -930,6 +934,7 @@ export class InitScript { globalThis.__pwInitScripts[${JSON.stringify(guid)}] = true; ${source} })();`; + this.internal = !!internal; } } diff --git a/packages/playwright-core/src/server/webkit/wkBrowser.ts b/packages/playwright-core/src/server/webkit/wkBrowser.ts index c94486a5c1..6ebdeed078 100644 --- a/packages/playwright-core/src/server/webkit/wkBrowser.ts +++ b/packages/playwright-core/src/server/webkit/wkBrowser.ts @@ -22,7 +22,7 @@ import type { RegisteredListener } from '../../utils/eventsHelper'; import { assert } from '../../utils'; import { eventsHelper } from '../../utils/eventsHelper'; import * as network from '../network'; -import type { InitScript, Page, PageBinding, PageDelegate } from '../page'; +import type { InitScript, Page, PageDelegate } from '../page'; import type { ConnectionTransport } from '../transport'; import type * as types from '../types'; import type * as channels from '@protocol/channels'; @@ -320,21 +320,11 @@ export class WKBrowserContext extends BrowserContext { await (page._delegate as WKPage)._updateBootstrapScript(); } - async doRemoveInitScripts() { + async doRemoveNonInternalInitScripts() { for (const page of this.pages()) await (page._delegate as WKPage)._updateBootstrapScript(); } - async doExposeBinding(binding: PageBinding) { - for (const page of this.pages()) - await (page._delegate as WKPage).exposeBinding(binding); - } - - async doRemoveExposedBindings() { - for (const page of this.pages()) - await (page._delegate as WKPage).removeExposedBindings(); - } - async doUpdateRequestInterception(): Promise { for (const page of this.pages()) await (page._delegate as WKPage).updateRequestInterception(); diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index d16d013973..26ffd2dbab 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -30,7 +30,7 @@ import { eventsHelper } from '../../utils/eventsHelper'; import { helper } from '../helper'; import type { JSHandle } from '../javascript'; import * as network from '../network'; -import type { InitScript, PageBinding, PageDelegate } from '../page'; +import { type InitScript, PageBinding, type PageDelegate } from '../page'; import { Page } from '../page'; import type { Progress } from '../progress'; import type * as types from '../types'; @@ -179,6 +179,7 @@ export class WKPage implements PageDelegate { const promises: Promise[] = [ // Resource tree should be received before first execution context. session.send('Runtime.enable'), + session.send('Runtime.addBinding', { name: PageBinding.kPlaywrightBinding }), session.send('Page.createUserWorld', { name: UTILITY_WORLD_NAME }).catch(_ => {}), // Worlds are per-process session.send('Console.enable'), session.send('Network.enable'), @@ -200,8 +201,6 @@ export class WKPage implements PageDelegate { const emulatedMedia = this._page.emulatedMedia(); if (emulatedMedia.media || emulatedMedia.colorScheme || emulatedMedia.reducedMotion || emulatedMedia.forcedColors) promises.push(WKPage._setEmulateMedia(session, emulatedMedia.media, emulatedMedia.colorScheme, emulatedMedia.reducedMotion, emulatedMedia.forcedColors)); - for (const binding of this._page.allBindings()) - promises.push(session.send('Runtime.addBinding', { name: binding.name })); const bootstrapScript = this._calculateBootstrapScript(); if (bootstrapScript.length) promises.push(session.send('Page.setBootstrapScript', { source: bootstrapScript })); @@ -768,21 +767,11 @@ export class WKPage implements PageDelegate { }); } - async exposeBinding(binding: PageBinding): Promise { - this._session.send('Runtime.addBinding', { name: binding.name }); - await this._updateBootstrapScript(); - await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(binding.source).catch(e => {}))); - } - - async removeExposedBindings(): Promise { - await this._updateBootstrapScript(); - } - async addInitScript(initScript: InitScript): Promise { await this._updateBootstrapScript(); } - async removeInitScripts() { + async removeNonInternalInitScripts() { await this._updateBootstrapScript(); } @@ -795,11 +784,7 @@ export class WKPage implements PageDelegate { } scripts.push('if (!window.safari) window.safari = { pushNotification: { toString() { return "[object SafariRemoteNotification]"; } } };'); scripts.push('if (!window.GestureEvent) window.GestureEvent = function GestureEvent() {};'); - - for (const binding of this._page.allBindings()) - scripts.push(binding.source); - scripts.push(...this._browserContext.initScripts.map(s => s.source)); - scripts.push(...this._page.initScripts.map(s => s.source)); + scripts.push(...this._page.allInitScripts().map(script => script.source)); return scripts.join(';\n'); } diff --git a/tests/assets/cached/bfcached.html b/tests/assets/cached/bfcached.html new file mode 100644 index 0000000000..8c12b001cd --- /dev/null +++ b/tests/assets/cached/bfcached.html @@ -0,0 +1,11 @@ + + +
BFCached
+ diff --git a/tests/library/chromium/bfcache.spec.ts b/tests/library/chromium/bfcache.spec.ts new file mode 100644 index 0000000000..c60f4befaf --- /dev/null +++ b/tests/library/chromium/bfcache.spec.ts @@ -0,0 +1,37 @@ +/** + * Copyright Microsoft Corporation. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { contextTest as test, expect } from '../../config/browserTest'; + +test.use({ + launchOptions: async ({ launchOptions }, use) => { + await use({ ...launchOptions, ignoreDefaultArgs: ['--disable-back-forward-cache'] }); + } +}); + +test('bindings should work after restoring from bfcache', async ({ page, server }) => { + await page.exposeFunction('add', (a, b) => a + b); + + await page.goto(server.PREFIX + '/cached/bfcached.html'); + expect(await page.evaluate('window.add(1, 2)')).toBe(3); + + await page.setContent(`click me`); + await page.click('a'); + + await page.goBack({ waitUntil: 'commit' }); + await page.evaluate('window.didShow'); + expect(await page.evaluate('window.add(2, 3)')).toBe(5); +}); diff --git a/tests/page/page-history.spec.ts b/tests/page/page-history.spec.ts index cf6eb2d456..a709ade4ca 100644 --- a/tests/page/page-history.spec.ts +++ b/tests/page/page-history.spec.ts @@ -92,15 +92,17 @@ it('page.goBack should work for file urls', async ({ page, server, asset, browse }); it('goBack/goForward should work with bfcache-able pages', async ({ page, server }) => { - await page.goto(server.PREFIX + '/cached/one-style.html'); - await page.setContent(`click me`); + await page.goto(server.PREFIX + '/cached/bfcached.html'); + await page.setContent(`click me`); await page.click('a'); let response = await page.goBack(); - expect(response.url()).toBe(server.PREFIX + '/cached/one-style.html'); + expect(response.url()).toBe(server.PREFIX + '/cached/bfcached.html'); + // BFCache should be disabled. + expect(await page.evaluate('window.didShow')).toEqual({ persisted: false }); response = await page.goForward(); - expect(response.url()).toBe(server.PREFIX + '/cached/one-style.html?foo'); + expect(response.url()).toBe(server.PREFIX + '/cached/bfcached.html?foo'); }); it('page.reload should work', async ({ page, server }) => {