diff --git a/.gitignore b/.gitignore index f7febe1dde..ed0ce8abca 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ drivers/ nohup.out .trace .tmp -allure* \ No newline at end of file +allure* +playwright-report diff --git a/src/dispatchers/browserContextDispatcher.ts b/src/dispatchers/browserContextDispatcher.ts index 6a937d8b8f..093159a9a5 100644 --- a/src/dispatchers/browserContextDispatcher.ts +++ b/src/dispatchers/browserContextDispatcher.ts @@ -104,7 +104,7 @@ export class BrowserContextDispatcher extends Dispatcher { diff --git a/src/server/browserContext.ts b/src/server/browserContext.ts index 989beb33e8..3612351ea7 100644 --- a/src/server/browserContext.ts +++ b/src/server/browserContext.ts @@ -110,7 +110,7 @@ export abstract class BrowserContext extends SdkObject { }); if (debugMode() === 'console') - await this.extendInjectedScript('main', consoleApiSource.source); + await this.extendInjectedScript(consoleApiSource.source); } async _ensureVideosPath() { @@ -168,16 +168,15 @@ export abstract class BrowserContext extends SdkObject { return this._doSetHTTPCredentials(httpCredentials); } - async exposeBinding(name: string, needsHandle: boolean, playwrightBinding: frames.FunctionWithSource, world: types.World): Promise { - const identifier = PageBinding.identifier(name, world); - if (this._pageBindings.has(identifier)) + async exposeBinding(name: string, needsHandle: boolean, playwrightBinding: frames.FunctionWithSource): Promise { + if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`); for (const page of this.pages()) { - if (page.getBinding(name, world)) + if (page.getBinding(name)) throw new Error(`Function "${name}" has been already registered in one of the pages`); } - const binding = new PageBinding(name, playwrightBinding, needsHandle, world); - this._pageBindings.set(identifier, binding); + const binding = new PageBinding(name, playwrightBinding, needsHandle); + this._pageBindings.set(name, binding); await this._doExposeBinding(binding); } @@ -373,8 +372,8 @@ export abstract class BrowserContext extends SdkObject { } } - async extendInjectedScript(world: types.World, source: string, arg?: any) { - const installInFrame = (frame: frames.Frame) => frame.extendInjectedScript(world, source, arg).catch(() => {}); + async extendInjectedScript(source: string, arg?: any) { + const installInFrame = (frame: frames.Frame) => frame.extendInjectedScript(source, arg).catch(() => {}); const installInPage = (page: Page) => { page.on(Page.Events.InternalFrameNavigatedToNewDocument, installInFrame); return Promise.all(page.frames().map(installInFrame)); diff --git a/src/server/chromium/crPage.ts b/src/server/chromium/crPage.ts index 1483679cca..d22dd30e90 100644 --- a/src/server/chromium/crPage.ts +++ b/src/server/chromium/crPage.ts @@ -172,7 +172,7 @@ export class CRPage implements PageDelegate { async exposeBinding(binding: PageBinding) { await this._forAllFrameSessions(frame => frame._initBinding(binding)); - await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(binding.source, false, {}, binding.world).catch(e => {}))); + await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(binding.source, false, {}).catch(e => {}))); } async updateExtraHTTPHeaders(): Promise { @@ -474,7 +474,7 @@ class FrameSession { worldName: UTILITY_WORLD_NAME, }); for (const binding of this._crPage._browserContext._pageBindings.values()) - frame.evaluateExpression(binding.source, false, undefined, binding.world).catch(e => {}); + frame.evaluateExpression(binding.source, false, undefined).catch(e => {}); for (const source of this._crPage._browserContext._evaluateOnNewDocumentSources) frame.evaluateExpression(source, false, undefined, 'main').catch(e => {}); } @@ -758,10 +758,9 @@ class FrameSession { } async _initBinding(binding: PageBinding) { - const worldName = binding.world === 'utility' ? UTILITY_WORLD_NAME : undefined; await Promise.all([ - this._client.send('Runtime.addBinding', { name: binding.name, executionContextName: worldName }), - this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: binding.source, worldName }) + this._client.send('Runtime.addBinding', { name: binding.name }), + this._client.send('Page.addScriptToEvaluateOnNewDocument', { source: binding.source }) ]); } diff --git a/src/server/firefox/ffBrowser.ts b/src/server/firefox/ffBrowser.ts index 8fe16baa73..81c07a6b81 100644 --- a/src/server/firefox/ffBrowser.ts +++ b/src/server/firefox/ffBrowser.ts @@ -24,7 +24,7 @@ import { Page, PageBinding, PageDelegate } from '../page'; import { ConnectionTransport } from '../transport'; import * as types from '../types'; import { ConnectionEvents, FFConnection } from './ffConnection'; -import { FFPage, UTILITY_WORLD_NAME } from './ffPage'; +import { FFPage } from './ffPage'; import { Protocol } from './protocol'; export class FFBrowser extends Browser { @@ -326,8 +326,7 @@ export class FFBrowserContext extends BrowserContext { } async _doExposeBinding(binding: PageBinding) { - const worldName = binding.world === 'utility' ? UTILITY_WORLD_NAME : ''; - await this._browser._connection.send('Browser.addBinding', { browserContextId: this._browserContextId, worldName, name: binding.name, script: binding.source }); + await this._browser._connection.send('Browser.addBinding', { browserContextId: this._browserContextId, name: binding.name, script: binding.source }); } async _doUpdateRequestInterception(): Promise { diff --git a/src/server/firefox/ffPage.ts b/src/server/firefox/ffPage.ts index 5a10a84c3e..2d93e0f91d 100644 --- a/src/server/firefox/ffPage.ts +++ b/src/server/firefox/ffPage.ts @@ -317,8 +317,7 @@ export class FFPage implements PageDelegate { } async exposeBinding(binding: PageBinding) { - const worldName = binding.world === 'utility' ? UTILITY_WORLD_NAME : ''; - await this._session.send('Page.addBinding', { name: binding.name, script: binding.source, worldName }); + await this._session.send('Page.addBinding', { name: binding.name, script: binding.source }); } didClose() { diff --git a/src/server/frames.ts b/src/server/frames.ts index 510aa71965..43406123c8 100644 --- a/src/server/frames.ts +++ b/src/server/frames.ts @@ -1299,8 +1299,8 @@ export class Frame extends SdkObject { this._networkIdleTimer = undefined; } - async extendInjectedScript(world: types.World, source: string, arg?: any): Promise { - const context = await this._context(world); + async extendInjectedScript(source: string, arg?: any): Promise { + const context = await this._context('main'); const injectedScriptHandle = await context.injectedScript(); return injectedScriptHandle.evaluateHandle((injectedScript, {source, arg}) => { return injectedScript.extend(source, arg); diff --git a/src/server/page.ts b/src/server/page.ts index 77dd2ba92e..a261c8b11c 100644 --- a/src/server/page.ts +++ b/src/server/page.ts @@ -268,14 +268,13 @@ export class Page extends SdkObject { this._timeoutSettings.setDefaultTimeout(timeout); } - async exposeBinding(name: string, needsHandle: boolean, playwrightBinding: frames.FunctionWithSource, world: types.World = 'main') { - const identifier = PageBinding.identifier(name, world); - if (this._pageBindings.has(identifier)) + async exposeBinding(name: string, needsHandle: boolean, playwrightBinding: frames.FunctionWithSource) { + if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`); - if (this._browserContext._pageBindings.has(identifier)) + if (this._browserContext._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered in the browser context`); - const binding = new PageBinding(name, playwrightBinding, needsHandle, world); - this._pageBindings.set(identifier, binding); + const binding = new PageBinding(name, playwrightBinding, needsHandle); + this._pageBindings.set(name, binding); await this._delegate.exposeBinding(binding); } @@ -490,9 +489,8 @@ export class Page extends SdkObject { return [...this._browserContext._pageBindings.values(), ...this._pageBindings.values()]; } - getBinding(name: string, world: types.World) { - const identifier = PageBinding.identifier(name, world); - return this._pageBindings.get(identifier) || this._browserContext._pageBindings.get(identifier); + getBinding(name: string) { + return this._pageBindings.get(name) || this._browserContext._pageBindings.get(name); } setScreencastOptions(options: { width: number, height: number, quality: number } | null) { @@ -549,25 +547,19 @@ export class PageBinding { readonly playwrightFunction: frames.FunctionWithSource; readonly source: string; readonly needsHandle: boolean; - readonly world: types.World; - constructor(name: string, playwrightFunction: frames.FunctionWithSource, needsHandle: boolean, world: types.World) { + constructor(name: string, playwrightFunction: frames.FunctionWithSource, needsHandle: boolean) { this.name = name; this.playwrightFunction = playwrightFunction; this.source = `(${addPageBinding.toString()})(${JSON.stringify(name)}, ${needsHandle})`; this.needsHandle = needsHandle; - this.world = world; - } - - static identifier(name: string, world: types.World) { - return world + ':' + name; } static async dispatch(page: Page, payload: string, context: dom.FrameExecutionContext) { const {name, seq, args} = JSON.parse(payload); try { assert(context.world); - const binding = page.getBinding(name, context.world)!; + const binding = page.getBinding(name)!; let result: any; if (binding.needsHandle) { const handle = await context.evaluateHandle(takeHandle, { name, seq }).catch(e => null); diff --git a/src/server/supplements/injected/recorder.ts b/src/server/supplements/injected/recorder.ts index e612664973..66ee13c9ae 100644 --- a/src/server/supplements/injected/recorder.ts +++ b/src/server/supplements/injected/recorder.ts @@ -29,8 +29,6 @@ declare module globalThis { let _playwrightRefreshOverlay: () => void; } -const scriptSymbol = Symbol('scriptSymbol'); - export class Recorder { private _injectedScript: InjectedScript; private _performingAction = false; @@ -132,9 +130,8 @@ export class Recorder { } private _refreshListenersIfNeeded() { - if ((document.documentElement as any)[scriptSymbol]) + if (this._outerGlassPaneElement.parentElement) return; - (document.documentElement as any)[scriptSymbol] = true; removeEventListeners(this._listeners); this._listeners = [ addEventListener(document, 'click', event => this._onClick(event as MouseEvent), true), diff --git a/src/server/supplements/recorderSupplement.ts b/src/server/supplements/recorderSupplement.ts index 045aab9794..9a233ddd44 100644 --- a/src/server/supplements/recorderSupplement.ts +++ b/src/server/supplements/recorderSupplement.ts @@ -184,11 +184,11 @@ export class RecorderSupplement implements InstrumentationListener { // Input actions that potentially lead to navigation are intercepted on the page and are // performed by the Playwright. await this._context.exposeBinding('_playwrightRecorderPerformAction', false, - (source: BindingSource, action: actions.Action) => this._performAction(source.frame, action), 'utility'); + (source: BindingSource, action: actions.Action) => this._performAction(source.frame, action)); // Other non-essential actions are simply being recorded. await this._context.exposeBinding('_playwrightRecorderRecordAction', false, - (source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action), 'utility'); + (source: BindingSource, action: actions.Action) => this._recordAction(source.frame, action)); await this._context.exposeBinding('_playwrightRecorderState', false, source => { let actionSelector = this._highlightedSelector; @@ -205,20 +205,20 @@ export class RecorderSupplement implements InstrumentationListener { actionSelector, }; return uiState; - }, 'utility'); + }); await this._context.exposeBinding('_playwrightRecorderSetSelector', false, async (_, selector: string) => { this._setMode('none'); await this._recorderApp?.setSelector(selector, true); await this._recorderApp?.bringToFront(); - }, 'utility'); + }); await this._context.exposeBinding('_playwrightResume', false, () => { this._debugger.resume(false); - }, 'main'); + }); - await this._context.extendInjectedScript('utility', recorderSource.source, { isUnderTest: isUnderTest() }); - await this._context.extendInjectedScript('main', consoleApiSource.source); + await this._context.extendInjectedScript(recorderSource.source, { isUnderTest: isUnderTest() }); + await this._context.extendInjectedScript(consoleApiSource.source); if (this._debugger.isPaused()) this._pausedStateChanged(); diff --git a/src/server/trace/viewer/traceViewer.ts b/src/server/trace/viewer/traceViewer.ts index 18e618187e..11fba5d72c 100644 --- a/src/server/trace/viewer/traceViewer.ts +++ b/src/server/trace/viewer/traceViewer.ts @@ -169,7 +169,7 @@ Please run 'npx playwright install' to install Playwright browsers await controller.run(async progress => { await context._browser._defaultContext!._loadDefaultContextAsIs(progress); }); - await context.extendInjectedScript('main', consoleApiSource.source); + await context.extendInjectedScript(consoleApiSource.source); const [page] = context.pages(); if (traceViewerBrowser === 'chromium') diff --git a/src/server/webkit/wkBrowser.ts b/src/server/webkit/wkBrowser.ts index 7fd3b10925..4c49c00ab2 100644 --- a/src/server/webkit/wkBrowser.ts +++ b/src/server/webkit/wkBrowser.ts @@ -308,7 +308,7 @@ export class WKBrowserContext extends BrowserContext { async _doAddInitScript(source: string) { this._evaluateOnNewDocumentSources.push(source); for (const page of this.pages()) - await (page._delegate as WKPage)._updateBootstrapScript('main'); + await (page._delegate as WKPage)._updateBootstrapScript(); } async _doExposeBinding(binding: PageBinding) { diff --git a/src/server/webkit/wkPage.ts b/src/server/webkit/wkPage.ts index cf3599db4a..a0457338be 100644 --- a/src/server/webkit/wkPage.ts +++ b/src/server/webkit/wkPage.ts @@ -185,12 +185,10 @@ export class WKPage implements PageDelegate { promises.push(session.send('Page.overrideUserAgent', { value: contextOptions.userAgent })); if (this._page._state.mediaType || this._page._state.colorScheme || this._page._state.reducedMotion) promises.push(WKPage._setEmulateMedia(session, this._page._state.mediaType, this._page._state.colorScheme, this._page._state.reducedMotion)); - for (const world of ['main', 'utility'] as const) { - const bootstrapScript = this._calculateBootstrapScript(world); - if (bootstrapScript.length) - promises.push(session.send('Page.setBootstrapScript', { source: bootstrapScript, worldName: webkitWorldName(world) })); - this._page.frames().map(frame => frame.evaluateExpression(bootstrapScript, false, undefined, world).catch(e => {})); - } + const bootstrapScript = this._calculateBootstrapScript(); + if (bootstrapScript.length) + promises.push(session.send('Page.setBootstrapScript', { source: bootstrapScript })); + this._page.frames().map(frame => frame.evaluateExpression(bootstrapScript, false, undefined).catch(e => {})); if (contextOptions.bypassCSP) promises.push(session.send('Page.setBypassCSP', { enabled: true })); if (this._page._state.emulatedSize) { @@ -720,38 +718,34 @@ export class WKPage implements PageDelegate { } async exposeBinding(binding: PageBinding): Promise { - await this._updateBootstrapScript(binding.world); + await this._updateBootstrapScript(); await this._evaluateBindingScript(binding); } private async _evaluateBindingScript(binding: PageBinding): Promise { const script = this._bindingToScript(binding); - await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(script, false, {}, binding.world).catch(e => {}))); + await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(script, false, {}).catch(e => {}))); } async evaluateOnNewDocument(script: string): Promise { - await this._updateBootstrapScript('main'); + await this._updateBootstrapScript(); } private _bindingToScript(binding: PageBinding): string { return `self.${binding.name} = (param) => console.debug('${BINDING_CALL_MESSAGE}', {}, param); ${binding.source}`; } - private _calculateBootstrapScript(world: types.World): string { + private _calculateBootstrapScript(): string { const scripts: string[] = []; - for (const binding of this._page.allBindings()) { - if (binding.world === world) - scripts.push(this._bindingToScript(binding)); - } - if (world === 'main') { - scripts.push(...this._browserContext._evaluateOnNewDocumentSources); - scripts.push(...this._page._evaluateOnNewDocumentSources); - } + for (const binding of this._page.allBindings()) + scripts.push(this._bindingToScript(binding)); + scripts.push(...this._browserContext._evaluateOnNewDocumentSources); + scripts.push(...this._page._evaluateOnNewDocumentSources); return scripts.join(';'); } - async _updateBootstrapScript(world: types.World): Promise { - await this._updateState('Page.setBootstrapScript', { source: this._calculateBootstrapScript(world), worldName: webkitWorldName(world) }); + async _updateBootstrapScript(): Promise { + await this._updateState('Page.setBootstrapScript', { source: this._calculateBootstrapScript() }); } async closePage(runBeforeUnload: boolean): Promise { @@ -1100,13 +1094,6 @@ export class WKPage implements PageDelegate { } } -function webkitWorldName(world: types.World) { - switch (world) { - case 'main': return undefined; - case 'utility': return UTILITY_WORLD_NAME; - } -} - /** * WebKit Remote Addresses look like: * diff --git a/tests/page/page-expose-function.spec.ts b/tests/page/page-expose-function.spec.ts index becd285dcd..f5ea26f781 100644 --- a/tests/page/page-expose-function.spec.ts +++ b/tests/page/page-expose-function.spec.ts @@ -16,7 +16,6 @@ */ import { test as it, expect } from './pageTest'; -import { attachFrame } from '../config/utils'; import type { ElementHandle } from '../../index'; it('exposeBinding should work', async ({page}) => { @@ -238,37 +237,6 @@ it('should not result in unhandled rejection', async ({page, isAndroid}) => { expect(await page.evaluate('1 + 1').catch(e => e)).toBeInstanceOf(Error); }); -it('should work with internal bindings', async ({page, toImpl, server, mode, browserName, isElectron, isAndroid}) => { - it.skip(mode !== 'default'); - it.skip(browserName !== 'chromium'); - it.skip(isAndroid); - it.skip(isElectron); - - const implPage: import('../../src/server/page').Page = toImpl(page); - let foo; - await implPage.exposeBinding('foo', false, ({}, arg) => { - foo = arg; - }, 'utility'); - expect(await page.evaluate('!!window.foo')).toBe(false); - expect(await implPage.mainFrame().evaluateExpression('!!window.foo', false, {}, 'utility')).toBe(true); - expect(foo).toBe(undefined); - await implPage.mainFrame().evaluateExpression('window.foo(123)', false, {}, 'utility'); - expect(foo).toBe(123); - - // should work after reload - await page.goto(server.EMPTY_PAGE); - expect(await page.evaluate('!!window.foo')).toBe(false); - await implPage.mainFrame().evaluateExpression('window.foo(456)', false, {}, 'utility'); - expect(foo).toBe(456); - - // should work inside frames - const frame = await attachFrame(page, 'myframe', server.CROSS_PROCESS_PREFIX + '/empty.html'); - expect(await frame.evaluate('!!window.foo')).toBe(false); - const implFrame: import('../../src/server/frames').Frame = toImpl(frame); - await implFrame.evaluateExpression('window.foo(789)', false, {}, 'utility'); - expect(foo).toBe(789); -}); - it('exposeBinding(handle) should work with element handles', async ({ page}) => { let cb; const promise = new Promise(f => cb = f);