From 6bb5c0a549ed83518392d034f82466954e2c1a28 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 31 May 2023 14:08:44 -0700 Subject: [PATCH] fix: make `evaluate` not wait for scheduled navigations (#23402) Fixes #23141. --- .../src/server/browserContext.ts | 4 +- .../src/server/chromium/crPage.ts | 6 +- .../server/dispatchers/electronDispatcher.ts | 4 +- .../dispatchers/elementHandlerDispatcher.ts | 4 +- .../src/server/dispatchers/frameDispatcher.ts | 8 +- .../server/dispatchers/jsHandleDispatcher.ts | 4 +- packages/playwright-core/src/server/dom.ts | 24 ++---- .../src/server/firefox/ffInput.ts | 2 +- packages/playwright-core/src/server/frames.ts | 26 +++--- .../src/server/har/harTracer.ts | 4 +- .../playwright-core/src/server/javascript.ts | 18 ++-- .../playwright-core/src/server/recorder.ts | 2 +- .../src/server/recorder/recorderApp.ts | 12 +-- .../src/server/trace/viewer/traceViewer.ts | 2 +- .../src/server/webkit/wkInput.ts | 2 +- .../src/server/webkit/wkPage.ts | 4 +- packages/playwright-test/src/runner/uiMode.ts | 2 +- tests/library/beforeunload.spec.ts | 15 ++++ tests/page/page-autowaiting-basic.spec.ts | 86 ------------------- 19 files changed, 69 insertions(+), 160 deletions(-) diff --git a/packages/playwright-core/src/server/browserContext.ts b/packages/playwright-core/src/server/browserContext.ts index c92b671304..3b352e7c9b 100644 --- a/packages/playwright-core/src/server/browserContext.ts +++ b/packages/playwright-core/src/server/browserContext.ts @@ -471,7 +471,7 @@ export abstract class BrowserContext extends SdkObject { await frame.goto(internalMetadata, origin); const storage = await frame.evaluateExpression(`({ localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })), - })`, false, undefined, 'utility'); + })`, { world: 'utility' }); originStorage.localStorage = storage.localStorage; if (storage.localStorage.length) result.origins.push(originStorage); @@ -536,7 +536,7 @@ export abstract class BrowserContext extends SdkObject { originState => { for (const { name, value } of (originState.localStorage || [])) localStorage.setItem(name, value); - }`, true, originState, 'utility'); + }`, { isFunction: true, world: 'utility' }, originState); } await page.close(internalMetadata); } diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 7a4a2ffce2..7e93d85343 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -176,7 +176,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, {}).catch(e => {}))); + await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(binding.source).catch(e => {}))); } async removeExposedBindings() { @@ -507,9 +507,9 @@ class FrameSession { worldName: UTILITY_WORLD_NAME, }); for (const binding of this._crPage._browserContext._pageBindings.values()) - frame.evaluateExpression(binding.source, false, undefined).catch(e => {}); + frame.evaluateExpression(binding.source).catch(e => {}); for (const source of this._crPage._browserContext.initScripts) - frame.evaluateExpression(source, false, undefined, 'main').catch(e => {}); + frame.evaluateExpression(source).catch(e => {}); } const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':'; if (isInitialEmptyPage) { diff --git a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts index 12f6e91d90..a636ac8c53 100644 --- a/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/electronDispatcher.ts @@ -58,12 +58,12 @@ export class ElectronApplicationDispatcher extends Dispatcher { const handle = await this._object._nodeElectronHandlePromise; - return { value: serializeResult(await handle.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, true /* returnByValue */, parseArgument(params.arg))) }; + return { value: serializeResult(await handle.evaluateExpression(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg))) }; } async evaluateExpressionHandle(params: channels.ElectronApplicationEvaluateExpressionHandleParams): Promise { const handle = await this._object._nodeElectronHandlePromise; - const result = await handle.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg)); + const result = await handle.evaluateExpressionHandle(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg)); return { handle: ElementHandleDispatcher.fromJSHandle(this, result) }; } diff --git a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts index cda8d4a999..7c7cc2e80b 100644 --- a/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/elementHandlerDispatcher.ts @@ -209,11 +209,11 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann } async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams, metadata: CallMetadata): Promise { - return { value: serializeResult(await this._elementHandle.evalOnSelectorAndWaitForSignals(params.selector, !!params.strict, params.expression, params.isFunction, parseArgument(params.arg))) }; + return { value: serializeResult(await this._elementHandle.evalOnSelector(params.selector, !!params.strict, params.expression, params.isFunction, parseArgument(params.arg))) }; } async evalOnSelectorAll(params: channels.ElementHandleEvalOnSelectorAllParams, metadata: CallMetadata): Promise { - return { value: serializeResult(await this._elementHandle.evalOnSelectorAllAndWaitForSignals(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; + return { value: serializeResult(await this._elementHandle.evalOnSelectorAll(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; } async waitForElementState(params: channels.ElementHandleWaitForElementStateParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts index 8098f3c934..72c935f9f0 100644 --- a/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/frameDispatcher.ts @@ -76,11 +76,11 @@ export class FrameDispatcher extends Dispatcher { - return { value: serializeResult(await this._frame.evaluateExpressionAndWaitForSignals(params.expression, { isFunction: params.isFunction, exposeUtilityScript: params.exposeUtilityScript }, parseArgument(params.arg), 'main')) }; + return { value: serializeResult(await this._frame.evaluateExpression(params.expression, { isFunction: params.isFunction, exposeUtilityScript: params.exposeUtilityScript }, parseArgument(params.arg))) }; } async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, metadata: CallMetadata): Promise { - return { handle: ElementHandleDispatcher.fromJSHandle(this.parentScope(), await this._frame.evaluateExpressionHandleAndWaitForSignals(params.expression, params.isFunction, parseArgument(params.arg), 'main')) }; + return { handle: ElementHandleDispatcher.fromJSHandle(this.parentScope(), await this._frame.evaluateExpressionHandle(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg))) }; } async waitForSelector(params: channels.FrameWaitForSelectorParams, metadata: CallMetadata): Promise { @@ -92,11 +92,11 @@ export class FrameDispatcher extends Dispatcher { - return { value: serializeResult(await this._frame.evalOnSelectorAndWaitForSignals(params.selector, !!params.strict, params.expression, params.isFunction, parseArgument(params.arg))) }; + return { value: serializeResult(await this._frame.evalOnSelector(params.selector, !!params.strict, params.expression, params.isFunction, parseArgument(params.arg))) }; } async evalOnSelectorAll(params: channels.FrameEvalOnSelectorAllParams, metadata: CallMetadata): Promise { - return { value: serializeResult(await this._frame.evalOnSelectorAllAndWaitForSignals(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; + return { value: serializeResult(await this._frame.evalOnSelectorAll(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; } async querySelector(params: channels.FrameQuerySelectorParams, metadata: CallMetadata): Promise { diff --git a/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts b/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts index 869d766a25..5b338ce595 100644 --- a/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts @@ -36,11 +36,11 @@ export class JSHandleDispatcher extends Dispatcher { - return { value: serializeResult(await this._object.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, true /* returnByValue */, parseArgument(params.arg))) }; + return { value: serializeResult(await this._object.evaluateExpression(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg))) }; } async evaluateExpressionHandle(params: channels.JSHandleEvaluateExpressionHandleParams): Promise { - const jsHandle = await this._object.evaluateExpressionAndWaitForSignals(params.expression, params.isFunction, false /* returnByValue */, parseArgument(params.arg)); + const jsHandle = await this._object.evaluateExpressionHandle(params.expression, { isFunction: params.isFunction }, parseArgument(params.arg)); return { handle: ElementHandleDispatcher.fromJSHandle(this.parentScope(), jsHandle) }; } diff --git a/packages/playwright-core/src/server/dom.ts b/packages/playwright-core/src/server/dom.ts index 8ce4484700..2a1fb3c41f 100644 --- a/packages/playwright-core/src/server/dom.ts +++ b/packages/playwright-core/src/server/dom.ts @@ -52,10 +52,6 @@ export class FrameExecutionContext extends js.ExecutionContext { this.world = world; } - override async waitForSignalsCreatedBy(action: () => Promise): Promise { - return this.frame._page._frameManager.waitForSignalsCreatedBy(null, false, action); - } - override adoptIfNeeded(handle: js.JSHandle): Promise | null { if (handle instanceof ElementHandle && handle._context !== this) return this.frame._page._delegate.adoptElementHandle(handle, this); @@ -74,16 +70,8 @@ export class FrameExecutionContext extends js.ExecutionContext { return js.evaluateExpression(this, expression, { ...options, returnByValue: true }, arg); } - async evaluateExpressionAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg?: any): Promise { - return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => { - return this.evaluateExpression(expression, options, arg); - }); - } - - async evaluateExpressionHandleAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg: any): Promise { - return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => { - return js.evaluateExpression(this, expression, { ...options, returnByValue: false }, arg); - }); + async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg?: any): Promise> { + return js.evaluateExpression(this, expression, { ...options, returnByValue: false }, arg); } override createHandle(remoteObject: js.RemoteObject): js.JSHandle { @@ -765,18 +753,18 @@ export class ElementHandle extends js.JSHandle { return this._frame.selectors.queryAll(selector, this); } - async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise { + async evalOnSelector(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise { const handle = await this._frame.selectors.query(selector, { strict }, this); if (!handle) throw new Error(`Error: failed to find element matching selector "${selector}"`); - const result = await handle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg); + const result = await handle.evaluateExpression(expression, { isFunction }, arg); handle.dispose(); return result; } - async evalOnSelectorAllAndWaitForSignals(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise { + async evalOnSelectorAll(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise { const arrayHandle = await this._frame.selectors.queryArrayInMainWorld(selector, this); - const result = await arrayHandle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg); + const result = await arrayHandle.evaluateExpression(expression, { isFunction }, arg); arrayHandle.dispose(); return result; } diff --git a/packages/playwright-core/src/server/firefox/ffInput.ts b/packages/playwright-core/src/server/firefox/ffInput.ts index 6791c6b363..e69ab54633 100644 --- a/packages/playwright-core/src/server/firefox/ffInput.ts +++ b/packages/playwright-core/src/server/firefox/ffInput.ts @@ -145,7 +145,7 @@ export class RawMouseImpl implements input.RawMouse { async wheel(x: number, y: number, buttons: Set, modifiers: Set, deltaX: number, deltaY: number): Promise { // Wheel events hit the compositor first, so wait one frame for it to be synced. - await this._page!.mainFrame().evaluateExpression(`new Promise(requestAnimationFrame)`, false, false, 'utility'); + await this._page!.mainFrame().evaluateExpression(`new Promise(requestAnimationFrame)`, { world: 'utility' }); await this._client.send('Page.dispatchWheelEvent', { deltaX, deltaY, diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index a4b7a3c517..9ac6c8658a 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -751,21 +751,15 @@ export class Frame extends SdkObject { return this._context('utility'); } - async evaluateExpressionHandleAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise { - const context = await this._context(world); - const handle = await context.evaluateExpressionHandleAndWaitForSignals(expression, { isFunction }, arg); - return handle; - } - - async evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise { - const context = await this._context(world); - const value = await context.evaluateExpression(expression, { isFunction }, arg); + async evaluateExpression(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean, world?: types.World } = {}, arg?: any): Promise { + const context = await this._context(options.world ?? 'main'); + const value = await context.evaluateExpression(expression, options, arg); return value; } - async evaluateExpressionAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg: any, world: types.World = 'main'): Promise { - const context = await this._context(world); - const value = await context.evaluateExpressionAndWaitForSignals(expression, options, arg); + async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean, world?: types.World } = {}, arg?: any): Promise> { + const context = await this._context(options.world ?? 'main'); + const value = await context.evaluateExpressionHandle(expression, options, arg); return value; } @@ -841,18 +835,18 @@ export class Frame extends SdkObject { }, { type, eventInit }, { mainWorld: true, ...options }); } - async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise { + async evalOnSelector(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise { const handle = await this.selectors.query(selector, { strict }); if (!handle) throw new Error(`Error: failed to find element matching selector "${selector}"`); - const result = await handle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg); + const result = await handle.evaluateExpression(expression, { isFunction }, arg); handle.dispose(); return result; } - async evalOnSelectorAllAndWaitForSignals(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise { + async evalOnSelectorAll(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise { const arrayHandle = await this.selectors.queryArrayInMainWorld(selector); - const result = await arrayHandle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg); + const result = await arrayHandle.evaluateExpression(expression, { isFunction }, arg); arrayHandle.dispose(); return result; } diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 8629d6be31..09b175e9bd 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -150,7 +150,7 @@ export class HarTracer { title: document.title, domContentLoaded: performance.timing.domContentLoadedEventStart, }; - }), true, undefined, 'utility').then(result => { + }), { isFunction: true, world: 'utility' }).then(result => { pageEntry.title = result.title; if (!this._options.omitTiming) pageEntry.pageTimings.onContentLoad = result.domContentLoaded; @@ -164,7 +164,7 @@ export class HarTracer { title: document.title, loaded: performance.timing.loadEventStart, }; - }), true, undefined, 'utility').then(result => { + }), { isFunction: true, world: 'utility' }).then(result => { pageEntry.title = result.title; if (!this._options.omitTiming) pageEntry.pageTimings.onLoad = result.loaded; diff --git a/packages/playwright-core/src/server/javascript.ts b/packages/playwright-core/src/server/javascript.ts index 0de4a75bb5..3221ace0a4 100644 --- a/packages/playwright-core/src/server/javascript.ts +++ b/packages/playwright-core/src/server/javascript.ts @@ -108,10 +108,6 @@ export class ExecutionContext extends SdkObject { return this._delegate.releaseHandle(objectId); } - async waitForSignalsCreatedBy(action: () => Promise): Promise { - return action(); - } - adoptIfNeeded(handle: JSHandle): Promise | null { return null; } @@ -171,8 +167,14 @@ export class JSHandle extends SdkObject { return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg); } - async evaluateExpressionAndWaitForSignals(expression: string, isFunction: boolean | undefined, returnByValue: boolean, arg: any) { - const value = await evaluateExpressionAndWaitForSignals(this._context, returnByValue, expression, isFunction, this, arg); + async evaluateExpression(expression: string, options: { isFunction?: boolean }, arg: any) { + const value = await evaluateExpression(this._context, expression, { ...options, returnByValue: true }, this, arg); + await this._context.doSlowMo(); + return value; + } + + async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean }, arg: any): Promise> { + const value = await evaluateExpression(this._context, expression, { ...options, returnByValue: false }, this, arg); await this._context.doSlowMo(); return value; } @@ -298,10 +300,6 @@ export async function evaluateExpression(context: ExecutionContext, expression: } } -export async function evaluateExpressionAndWaitForSignals(context: ExecutionContext, returnByValue: boolean, expression: string, isFunction: boolean | undefined, ...args: any[]): Promise { - return await context.waitForSignalsCreatedBy(() => evaluateExpression(context, expression, { returnByValue, isFunction }, ...args)); -} - export function parseUnserializableValue(unserializableValue: string): any { if (unserializableValue === 'NaN') return NaN; diff --git a/packages/playwright-core/src/server/recorder.ts b/packages/playwright-core/src/server/recorder.ts index d2e53e9d62..edd1073d5d 100644 --- a/packages/playwright-core/src/server/recorder.ts +++ b/packages/playwright-core/src/server/recorder.ts @@ -242,7 +242,7 @@ export class Recorder implements InstrumentationListener { private _refreshOverlay() { for (const page of this._context.pages()) - page.mainFrame().evaluateExpression('window.__pw_refreshOverlay()', false, undefined, 'main').catch(() => {}); + page.mainFrame().evaluateExpression('window.__pw_refreshOverlay()').catch(() => {}); } async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) { diff --git a/packages/playwright-core/src/server/recorder/recorderApp.ts b/packages/playwright-core/src/server/recorder/recorderApp.ts index 3ad74f5242..9fdb884005 100644 --- a/packages/playwright-core/src/server/recorder/recorderApp.ts +++ b/packages/playwright-core/src/server/recorder/recorderApp.ts @@ -149,25 +149,25 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise { await this._page.mainFrame().evaluateExpression(((mode: Mode) => { window.playwrightSetMode(mode); - }).toString(), true, mode, 'main').catch(() => {}); + }).toString(), { isFunction: true }, mode).catch(() => {}); } async setFileIfNeeded(file: string): Promise { await this._page.mainFrame().evaluateExpression(((file: string) => { window.playwrightSetFileIfNeeded(file); - }).toString(), true, file, 'main').catch(() => {}); + }).toString(), { isFunction: true }, file).catch(() => {}); } async setPaused(paused: boolean): Promise { await this._page.mainFrame().evaluateExpression(((paused: boolean) => { window.playwrightSetPaused(paused); - }).toString(), true, paused, 'main').catch(() => {}); + }).toString(), { isFunction: true }, paused).catch(() => {}); } async setSources(sources: Source[]): Promise { await this._page.mainFrame().evaluateExpression(((sources: Source[]) => { window.playwrightSetSources(sources); - }).toString(), true, sources, 'main').catch(() => {}); + }).toString(), { isFunction: true }, sources).catch(() => {}); // Testing harness for runCLI mode. if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length) @@ -181,12 +181,12 @@ export class RecorderApp extends EventEmitter implements IRecorderApp { } await this._page.mainFrame().evaluateExpression(((arg: any) => { window.playwrightSetSelector(arg.selector, arg.focus); - }).toString(), true, { selector, focus }, 'main').catch(() => {}); + }).toString(), { isFunction: true }, { selector, focus }).catch(() => {}); } async updateCallLogs(callLogs: CallLog[]): Promise { await this._page.mainFrame().evaluateExpression(((callLogs: CallLog[]) => { window.playwrightUpdateLogs(callLogs); - }).toString(), true, callLogs, 'main').catch(() => {}); + }).toString(), { isFunction: true }, callLogs).catch(() => {}); } } diff --git a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts index 57e1ca0912..b1934cf633 100644 --- a/packages/playwright-core/src/server/trace/viewer/traceViewer.ts +++ b/packages/playwright-core/src/server/trace/viewer/traceViewer.ts @@ -150,7 +150,7 @@ class StdinServer { private _loadTrace(url: string) { clearTimeout(this._pollTimer); - this._page?.mainFrame().evaluateExpression(`window.setTraceURL(${JSON.stringify(url)})`, false, undefined).catch(() => {}); + this._page?.mainFrame().evaluateExpression(`window.setTraceURL(${JSON.stringify(url)})`).catch(() => {}); } private _pollLoadTrace(url: string) { diff --git a/packages/playwright-core/src/server/webkit/wkInput.ts b/packages/playwright-core/src/server/webkit/wkInput.ts index a0b7c3fce7..0732f246d1 100644 --- a/packages/playwright-core/src/server/webkit/wkInput.ts +++ b/packages/playwright-core/src/server/webkit/wkInput.ts @@ -153,7 +153,7 @@ export class RawMouseImpl implements input.RawMouse { throw new Error('Mouse wheel is not supported in mobile WebKit'); await this._session!.send('Page.updateScrollingState'); // Wheel events hit the compositor first, so wait one frame for it to be synced. - await this._page!.mainFrame().evaluateExpression(`new Promise(requestAnimationFrame)`, false, false, 'utility'); + await this._page!.mainFrame().evaluateExpression(`new Promise(requestAnimationFrame)`, { world: 'utility' }); await this._pageProxySession.send('Input.dispatchWheelEvent', { x, y, diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 47955290c6..1ebb724af8 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -202,7 +202,7 @@ export class WKPage implements PageDelegate { 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 => {})); + this._page.frames().map(frame => frame.evaluateExpression(bootstrapScript).catch(e => {})); if (contextOptions.bypassCSP) promises.push(session.send('Page.setBypassCSP', { enabled: true })); const emulatedSize = this._page.emulatedSize(); @@ -767,7 +767,7 @@ 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, false, {}).catch(e => {}))); + await Promise.all(this._page.frames().map(frame => frame.evaluateExpression(binding.source).catch(e => {}))); } async removeExposedBindings(): Promise { diff --git a/packages/playwright-test/src/runner/uiMode.ts b/packages/playwright-test/src/runner/uiMode.ts index 2df0ddfb03..4979cb4660 100644 --- a/packages/playwright-test/src/runner/uiMode.ts +++ b/packages/playwright-test/src/runner/uiMode.ts @@ -141,7 +141,7 @@ class UIMode { private _dispatchEvent(message: any) { // eslint-disable-next-line no-console - this._page.mainFrame().evaluateExpression(dispatchFuncSource, true, message).catch(e => this._originalStderrWrite.call(process.stderr, String(e))); + this._page.mainFrame().evaluateExpression(dispatchFuncSource, { isFunction: true }, message).catch(e => this._originalStderrWrite.call(process.stderr, String(e))); } private async _listTests() { diff --git a/tests/library/beforeunload.spec.ts b/tests/library/beforeunload.spec.ts index f3de13e90b..fb8b077d11 100644 --- a/tests/library/beforeunload.spec.ts +++ b/tests/library/beforeunload.spec.ts @@ -89,3 +89,18 @@ it('should access page after beforeunload', async ({ page, server }) => { await page.evaluate(() => document.title); }); +it('should not stall on evaluate when dismissing beforeunload', async ({ page, server }) => { + it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/23141' }); + + await page.goto(server.PREFIX + '/beforeunload.html'); + // We have to interact with a page so that 'beforeunload' handlers fire. + await page.click('body'); + + await Promise.all([ + page.evaluate(() => { + window.location.reload(); + }), + page.waitForEvent('dialog').then(dialog => dialog.dismiss()), + ]); +}); + diff --git a/tests/page/page-autowaiting-basic.spec.ts b/tests/page/page-autowaiting-basic.spec.ts index 9728bc4958..a9d36bdeb5 100644 --- a/tests/page/page-autowaiting-basic.spec.ts +++ b/tests/page/page-autowaiting-basic.spec.ts @@ -43,48 +43,6 @@ it('should not stall on JS navigation link', async ({ page, browserName }) => { await page.click('a'); }); -it('should await navigation when clicking anchor programmatically', async ({ page, server }) => { - const messages = initServer(server); - await page.setContent(`empty.html`); - await Promise.all([ - page.evaluate(() => (window as any).anchor.click()).then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); -}); - -it('should await navigation when clicking anchor via $eval', async ({ page, server }) => { - const messages = initServer(server); - await page.setContent(`empty.html`); - await Promise.all([ - page.$eval('#anchor', anchor => (anchor as any).click()).then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); -}); - -it('should await navigation when clicking anchor via handle.eval', async ({ page, server }) => { - const messages = initServer(server); - await page.setContent(`empty.html`); - const handle = await page.evaluateHandle('document'); - await Promise.all([ - handle.evaluate(doc => (doc as any).getElementById('anchor').click()).then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); -}); - -it('should await navigation when clicking anchor via handle.$eval', async ({ page, server }) => { - const messages = initServer(server); - await page.setContent(`empty.html`); - const handle = await page.$('body'); - await Promise.all([ - handle.$eval('#anchor', anchor => (anchor as any).click()).then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); -}); - it('should await cross-process navigation when clicking anchor', async ({ page, server }) => { const messages = initServer(server); await page.setContent(`empty.html`); @@ -96,17 +54,6 @@ it('should await cross-process navigation when clicking anchor', async ({ page, expect(messages.join('|')).toBe('route|navigated|click'); }); -it('should await cross-process navigation when clicking anchor programatically', async ({ page, server }) => { - const messages = initServer(server); - await page.setContent(`empty.html`); - - await Promise.all([ - page.evaluate(() => (window as any).anchor.click()).then(() => messages.push('click')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|click'); -}); - it('should await form-get on click', async ({ page, server }) => { const messages = []; server.setRoute('/empty.html?foo=bar', async (req, res) => { @@ -143,39 +90,6 @@ it('should await form-post on click', async ({ page, server }) => { expect(messages.join('|')).toBe('route|navigated|click'); }); -it('should await navigation when assigning location', async ({ page, server }) => { - const messages = initServer(server); - await Promise.all([ - page.evaluate(`window.location.href = "${server.EMPTY_PAGE}"`).then(() => messages.push('evaluate')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|evaluate'); -}); - -it('should await navigation when assigning location twice', async ({ page, server }) => { - const messages = []; - server.setRoute('/empty.html?cancel', async (req, res) => { res.end('done'); }); - server.setRoute('/empty.html?override', async (req, res) => { messages.push('routeoverride'); res.end('done'); }); - await page.evaluate(` - window.location.href = "${server.EMPTY_PAGE}?cancel"; - window.location.href = "${server.EMPTY_PAGE}?override"; - `); - messages.push('evaluate'); - expect(messages.join('|')).toBe('routeoverride|evaluate'); -}); - -it('should await navigation when evaluating reload', async ({ page, server, browserName }) => { - it.skip(browserName === 'firefox', 'With fission enabled, navigations in Firefox start asynchronously'); - - await page.goto(server.EMPTY_PAGE); - const messages = initServer(server); - await Promise.all([ - page.evaluate(`window.location.reload()`).then(() => messages.push('evaluate')), - page.waitForEvent('framenavigated').then(() => messages.push('navigated')), - ]); - expect(messages.join('|')).toBe('route|navigated|evaluate'); -}); - it('should work with noWaitAfter: true', async ({ page, server }) => { server.setRoute('/empty.html', async () => {}); await page.setContent(`empty.html`);