fix: make evaluate not wait for scheduled navigations (#23402)
Fixes #23141.
This commit is contained in:
parent
2505d48b32
commit
6bb5c0a549
|
|
@ -471,7 +471,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
await frame.goto(internalMetadata, origin);
|
await frame.goto(internalMetadata, origin);
|
||||||
const storage = await frame.evaluateExpression(`({
|
const storage = await frame.evaluateExpression(`({
|
||||||
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
|
localStorage: Object.keys(localStorage).map(name => ({ name, value: localStorage.getItem(name) })),
|
||||||
})`, false, undefined, 'utility');
|
})`, { world: 'utility' });
|
||||||
originStorage.localStorage = storage.localStorage;
|
originStorage.localStorage = storage.localStorage;
|
||||||
if (storage.localStorage.length)
|
if (storage.localStorage.length)
|
||||||
result.origins.push(originStorage);
|
result.origins.push(originStorage);
|
||||||
|
|
@ -536,7 +536,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||||
originState => {
|
originState => {
|
||||||
for (const { name, value } of (originState.localStorage || []))
|
for (const { name, value } of (originState.localStorage || []))
|
||||||
localStorage.setItem(name, value);
|
localStorage.setItem(name, value);
|
||||||
}`, true, originState, 'utility');
|
}`, { isFunction: true, world: 'utility' }, originState);
|
||||||
}
|
}
|
||||||
await page.close(internalMetadata);
|
await page.close(internalMetadata);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ export class CRPage implements PageDelegate {
|
||||||
|
|
||||||
async exposeBinding(binding: PageBinding) {
|
async exposeBinding(binding: PageBinding) {
|
||||||
await this._forAllFrameSessions(frame => frame._initBinding(binding));
|
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() {
|
async removeExposedBindings() {
|
||||||
|
|
@ -507,9 +507,9 @@ class FrameSession {
|
||||||
worldName: UTILITY_WORLD_NAME,
|
worldName: UTILITY_WORLD_NAME,
|
||||||
});
|
});
|
||||||
for (const binding of this._crPage._browserContext._pageBindings.values())
|
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)
|
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() === ':';
|
const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':';
|
||||||
if (isInitialEmptyPage) {
|
if (isInitialEmptyPage) {
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,12 @@ export class ElectronApplicationDispatcher extends Dispatcher<ElectronApplicatio
|
||||||
|
|
||||||
async evaluateExpression(params: channels.ElectronApplicationEvaluateExpressionParams): Promise<channels.ElectronApplicationEvaluateExpressionResult> {
|
async evaluateExpression(params: channels.ElectronApplicationEvaluateExpressionParams): Promise<channels.ElectronApplicationEvaluateExpressionResult> {
|
||||||
const handle = await this._object._nodeElectronHandlePromise;
|
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<channels.ElectronApplicationEvaluateExpressionHandleResult> {
|
async evaluateExpressionHandle(params: channels.ElectronApplicationEvaluateExpressionHandleParams): Promise<channels.ElectronApplicationEvaluateExpressionHandleResult> {
|
||||||
const handle = await this._object._nodeElectronHandlePromise;
|
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) };
|
return { handle: ElementHandleDispatcher.fromJSHandle(this, result) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,11 +209,11 @@ export class ElementHandleDispatcher extends JSHandleDispatcher implements chann
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleEvalOnSelectorResult> {
|
async evalOnSelector(params: channels.ElementHandleEvalOnSelectorParams, metadata: CallMetadata): Promise<channels.ElementHandleEvalOnSelectorResult> {
|
||||||
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<channels.ElementHandleEvalOnSelectorAllResult> {
|
async evalOnSelectorAll(params: channels.ElementHandleEvalOnSelectorAllParams, metadata: CallMetadata): Promise<channels.ElementHandleEvalOnSelectorAllResult> {
|
||||||
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<void> {
|
async waitForElementState(params: channels.ElementHandleWaitForElementStateParams, metadata: CallMetadata): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -76,11 +76,11 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Pa
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateExpression(params: channels.FrameEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionResult> {
|
async evaluateExpression(params: channels.FrameEvaluateExpressionParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionResult> {
|
||||||
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<channels.FrameEvaluateExpressionHandleResult> {
|
async evaluateExpressionHandle(params: channels.FrameEvaluateExpressionHandleParams, metadata: CallMetadata): Promise<channels.FrameEvaluateExpressionHandleResult> {
|
||||||
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<channels.FrameWaitForSelectorResult> {
|
async waitForSelector(params: channels.FrameWaitForSelectorParams, metadata: CallMetadata): Promise<channels.FrameWaitForSelectorResult> {
|
||||||
|
|
@ -92,11 +92,11 @@ export class FrameDispatcher extends Dispatcher<Frame, channels.FrameChannel, Pa
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelector(params: channels.FrameEvalOnSelectorParams, metadata: CallMetadata): Promise<channels.FrameEvalOnSelectorResult> {
|
async evalOnSelector(params: channels.FrameEvalOnSelectorParams, metadata: CallMetadata): Promise<channels.FrameEvalOnSelectorResult> {
|
||||||
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<channels.FrameEvalOnSelectorAllResult> {
|
async evalOnSelectorAll(params: channels.FrameEvalOnSelectorAllParams, metadata: CallMetadata): Promise<channels.FrameEvalOnSelectorAllResult> {
|
||||||
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<channels.FrameQuerySelectorResult> {
|
async querySelector(params: channels.FrameQuerySelectorParams, metadata: CallMetadata): Promise<channels.FrameQuerySelectorResult> {
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,11 @@ export class JSHandleDispatcher extends Dispatcher<js.JSHandle, channels.JSHandl
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateExpression(params: channels.JSHandleEvaluateExpressionParams): Promise<channels.JSHandleEvaluateExpressionResult> {
|
async evaluateExpression(params: channels.JSHandleEvaluateExpressionParams): Promise<channels.JSHandleEvaluateExpressionResult> {
|
||||||
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<channels.JSHandleEvaluateExpressionHandleResult> {
|
async evaluateExpressionHandle(params: channels.JSHandleEvaluateExpressionHandleParams): Promise<channels.JSHandleEvaluateExpressionHandleResult> {
|
||||||
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) };
|
return { handle: ElementHandleDispatcher.fromJSHandle(this.parentScope(), jsHandle) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,6 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async waitForSignalsCreatedBy<T>(action: () => Promise<T>): Promise<T> {
|
|
||||||
return this.frame._page._frameManager.waitForSignalsCreatedBy(null, false, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
override adoptIfNeeded(handle: js.JSHandle): Promise<js.JSHandle> | null {
|
override adoptIfNeeded(handle: js.JSHandle): Promise<js.JSHandle> | null {
|
||||||
if (handle instanceof ElementHandle && handle._context !== this)
|
if (handle instanceof ElementHandle && handle._context !== this)
|
||||||
return this.frame._page._delegate.adoptElementHandle(handle, 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);
|
return js.evaluateExpression(this, expression, { ...options, returnByValue: true }, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateExpressionAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg?: any): Promise<any> {
|
async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg?: any): Promise<js.JSHandle<any>> {
|
||||||
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
|
return js.evaluateExpression(this, expression, { ...options, returnByValue: false }, arg);
|
||||||
return this.evaluateExpression(expression, options, arg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async evaluateExpressionHandleAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg: any): Promise<any> {
|
|
||||||
return await this.frame._page._frameManager.waitForSignalsCreatedBy(null, false /* noWaitFor */, async () => {
|
|
||||||
return js.evaluateExpression(this, expression, { ...options, returnByValue: false }, arg);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override createHandle(remoteObject: js.RemoteObject): js.JSHandle {
|
override createHandle(remoteObject: js.RemoteObject): js.JSHandle {
|
||||||
|
|
@ -765,18 +753,18 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
return this._frame.selectors.queryAll(selector, this);
|
return this._frame.selectors.queryAll(selector, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evalOnSelector(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
const handle = await this._frame.selectors.query(selector, { strict }, this);
|
const handle = await this._frame.selectors.query(selector, { strict }, this);
|
||||||
if (!handle)
|
if (!handle)
|
||||||
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
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();
|
handle.dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelectorAllAndWaitForSignals(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evalOnSelectorAll(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
const arrayHandle = await this._frame.selectors.queryArrayInMainWorld(selector, this);
|
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();
|
arrayHandle.dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ export class RawMouseImpl implements input.RawMouse {
|
||||||
|
|
||||||
async wheel(x: number, y: number, buttons: Set<types.MouseButton>, modifiers: Set<types.KeyboardModifier>, deltaX: number, deltaY: number): Promise<void> {
|
async wheel(x: number, y: number, buttons: Set<types.MouseButton>, modifiers: Set<types.KeyboardModifier>, deltaX: number, deltaY: number): Promise<void> {
|
||||||
// Wheel events hit the compositor first, so wait one frame for it to be synced.
|
// 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', {
|
await this._client.send('Page.dispatchWheelEvent', {
|
||||||
deltaX,
|
deltaX,
|
||||||
deltaY,
|
deltaY,
|
||||||
|
|
|
||||||
|
|
@ -751,21 +751,15 @@ export class Frame extends SdkObject {
|
||||||
return this._context('utility');
|
return this._context('utility');
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateExpressionHandleAndWaitForSignals(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
|
async evaluateExpression(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean, world?: types.World } = {}, arg?: any): Promise<any> {
|
||||||
const context = await this._context(world);
|
const context = await this._context(options.world ?? 'main');
|
||||||
const handle = await context.evaluateExpressionHandleAndWaitForSignals(expression, { isFunction }, arg);
|
const value = await context.evaluateExpression(expression, options, arg);
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
async evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any, world: types.World = 'main'): Promise<any> {
|
|
||||||
const context = await this._context(world);
|
|
||||||
const value = await context.evaluateExpression(expression, { isFunction }, arg);
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateExpressionAndWaitForSignals(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean }, arg: any, world: types.World = 'main'): Promise<any> {
|
async evaluateExpressionHandle(expression: string, options: { isFunction?: boolean, exposeUtilityScript?: boolean, world?: types.World } = {}, arg?: any): Promise<js.JSHandle<any>> {
|
||||||
const context = await this._context(world);
|
const context = await this._context(options.world ?? 'main');
|
||||||
const value = await context.evaluateExpressionAndWaitForSignals(expression, options, arg);
|
const value = await context.evaluateExpressionHandle(expression, options, arg);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -841,18 +835,18 @@ export class Frame extends SdkObject {
|
||||||
}, { type, eventInit }, { mainWorld: true, ...options });
|
}, { type, eventInit }, { mainWorld: true, ...options });
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evalOnSelector(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
const handle = await this.selectors.query(selector, { strict });
|
const handle = await this.selectors.query(selector, { strict });
|
||||||
if (!handle)
|
if (!handle)
|
||||||
throw new Error(`Error: failed to find element matching selector "${selector}"`);
|
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();
|
handle.dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async evalOnSelectorAllAndWaitForSignals(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
async evalOnSelectorAll(selector: string, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
|
||||||
const arrayHandle = await this.selectors.queryArrayInMainWorld(selector);
|
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();
|
arrayHandle.dispose();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ export class HarTracer {
|
||||||
title: document.title,
|
title: document.title,
|
||||||
domContentLoaded: performance.timing.domContentLoadedEventStart,
|
domContentLoaded: performance.timing.domContentLoadedEventStart,
|
||||||
};
|
};
|
||||||
}), true, undefined, 'utility').then(result => {
|
}), { isFunction: true, world: 'utility' }).then(result => {
|
||||||
pageEntry.title = result.title;
|
pageEntry.title = result.title;
|
||||||
if (!this._options.omitTiming)
|
if (!this._options.omitTiming)
|
||||||
pageEntry.pageTimings.onContentLoad = result.domContentLoaded;
|
pageEntry.pageTimings.onContentLoad = result.domContentLoaded;
|
||||||
|
|
@ -164,7 +164,7 @@ export class HarTracer {
|
||||||
title: document.title,
|
title: document.title,
|
||||||
loaded: performance.timing.loadEventStart,
|
loaded: performance.timing.loadEventStart,
|
||||||
};
|
};
|
||||||
}), true, undefined, 'utility').then(result => {
|
}), { isFunction: true, world: 'utility' }).then(result => {
|
||||||
pageEntry.title = result.title;
|
pageEntry.title = result.title;
|
||||||
if (!this._options.omitTiming)
|
if (!this._options.omitTiming)
|
||||||
pageEntry.pageTimings.onLoad = result.loaded;
|
pageEntry.pageTimings.onLoad = result.loaded;
|
||||||
|
|
|
||||||
|
|
@ -108,10 +108,6 @@ export class ExecutionContext extends SdkObject {
|
||||||
return this._delegate.releaseHandle(objectId);
|
return this._delegate.releaseHandle(objectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForSignalsCreatedBy<T>(action: () => Promise<T>): Promise<T> {
|
|
||||||
return action();
|
|
||||||
}
|
|
||||||
|
|
||||||
adoptIfNeeded(handle: JSHandle): Promise<JSHandle> | null {
|
adoptIfNeeded(handle: JSHandle): Promise<JSHandle> | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -171,8 +167,14 @@ export class JSHandle<T = any> extends SdkObject {
|
||||||
return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg);
|
return evaluate(this._context, false /* returnByValue */, pageFunction, this, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateExpressionAndWaitForSignals(expression: string, isFunction: boolean | undefined, returnByValue: boolean, arg: any) {
|
async evaluateExpression(expression: string, options: { isFunction?: boolean }, arg: any) {
|
||||||
const value = await evaluateExpressionAndWaitForSignals(this._context, returnByValue, expression, isFunction, this, arg);
|
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<JSHandle<any>> {
|
||||||
|
const value = await evaluateExpression(this._context, expression, { ...options, returnByValue: false }, this, arg);
|
||||||
await this._context.doSlowMo();
|
await this._context.doSlowMo();
|
||||||
return value;
|
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<any> {
|
|
||||||
return await context.waitForSignalsCreatedBy(() => evaluateExpression(context, expression, { returnByValue, isFunction }, ...args));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseUnserializableValue(unserializableValue: string): any {
|
export function parseUnserializableValue(unserializableValue: string): any {
|
||||||
if (unserializableValue === 'NaN')
|
if (unserializableValue === 'NaN')
|
||||||
return NaN;
|
return NaN;
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ export class Recorder implements InstrumentationListener {
|
||||||
|
|
||||||
private _refreshOverlay() {
|
private _refreshOverlay() {
|
||||||
for (const page of this._context.pages())
|
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) {
|
async onBeforeCall(sdkObject: SdkObject, metadata: CallMetadata) {
|
||||||
|
|
|
||||||
|
|
@ -149,25 +149,25 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
|
||||||
async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void> {
|
async setMode(mode: 'none' | 'recording' | 'inspecting'): Promise<void> {
|
||||||
await this._page.mainFrame().evaluateExpression(((mode: Mode) => {
|
await this._page.mainFrame().evaluateExpression(((mode: Mode) => {
|
||||||
window.playwrightSetMode(mode);
|
window.playwrightSetMode(mode);
|
||||||
}).toString(), true, mode, 'main').catch(() => {});
|
}).toString(), { isFunction: true }, mode).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setFileIfNeeded(file: string): Promise<void> {
|
async setFileIfNeeded(file: string): Promise<void> {
|
||||||
await this._page.mainFrame().evaluateExpression(((file: string) => {
|
await this._page.mainFrame().evaluateExpression(((file: string) => {
|
||||||
window.playwrightSetFileIfNeeded(file);
|
window.playwrightSetFileIfNeeded(file);
|
||||||
}).toString(), true, file, 'main').catch(() => {});
|
}).toString(), { isFunction: true }, file).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setPaused(paused: boolean): Promise<void> {
|
async setPaused(paused: boolean): Promise<void> {
|
||||||
await this._page.mainFrame().evaluateExpression(((paused: boolean) => {
|
await this._page.mainFrame().evaluateExpression(((paused: boolean) => {
|
||||||
window.playwrightSetPaused(paused);
|
window.playwrightSetPaused(paused);
|
||||||
}).toString(), true, paused, 'main').catch(() => {});
|
}).toString(), { isFunction: true }, paused).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSources(sources: Source[]): Promise<void> {
|
async setSources(sources: Source[]): Promise<void> {
|
||||||
await this._page.mainFrame().evaluateExpression(((sources: Source[]) => {
|
await this._page.mainFrame().evaluateExpression(((sources: Source[]) => {
|
||||||
window.playwrightSetSources(sources);
|
window.playwrightSetSources(sources);
|
||||||
}).toString(), true, sources, 'main').catch(() => {});
|
}).toString(), { isFunction: true }, sources).catch(() => {});
|
||||||
|
|
||||||
// Testing harness for runCLI mode.
|
// Testing harness for runCLI mode.
|
||||||
if (process.env.PWTEST_CLI_IS_UNDER_TEST && sources.length)
|
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) => {
|
await this._page.mainFrame().evaluateExpression(((arg: any) => {
|
||||||
window.playwrightSetSelector(arg.selector, arg.focus);
|
window.playwrightSetSelector(arg.selector, arg.focus);
|
||||||
}).toString(), true, { selector, focus }, 'main').catch(() => {});
|
}).toString(), { isFunction: true }, { selector, focus }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
async updateCallLogs(callLogs: CallLog[]): Promise<void> {
|
||||||
await this._page.mainFrame().evaluateExpression(((callLogs: CallLog[]) => {
|
await this._page.mainFrame().evaluateExpression(((callLogs: CallLog[]) => {
|
||||||
window.playwrightUpdateLogs(callLogs);
|
window.playwrightUpdateLogs(callLogs);
|
||||||
}).toString(), true, callLogs, 'main').catch(() => {});
|
}).toString(), { isFunction: true }, callLogs).catch(() => {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ class StdinServer {
|
||||||
|
|
||||||
private _loadTrace(url: string) {
|
private _loadTrace(url: string) {
|
||||||
clearTimeout(this._pollTimer);
|
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) {
|
private _pollLoadTrace(url: string) {
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ export class RawMouseImpl implements input.RawMouse {
|
||||||
throw new Error('Mouse wheel is not supported in mobile WebKit');
|
throw new Error('Mouse wheel is not supported in mobile WebKit');
|
||||||
await this._session!.send('Page.updateScrollingState');
|
await this._session!.send('Page.updateScrollingState');
|
||||||
// Wheel events hit the compositor first, so wait one frame for it to be synced.
|
// 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', {
|
await this._pageProxySession.send('Input.dispatchWheelEvent', {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ export class WKPage implements PageDelegate {
|
||||||
const bootstrapScript = this._calculateBootstrapScript();
|
const bootstrapScript = this._calculateBootstrapScript();
|
||||||
if (bootstrapScript.length)
|
if (bootstrapScript.length)
|
||||||
promises.push(session.send('Page.setBootstrapScript', { source: bootstrapScript }));
|
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)
|
if (contextOptions.bypassCSP)
|
||||||
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
|
promises.push(session.send('Page.setBypassCSP', { enabled: true }));
|
||||||
const emulatedSize = this._page.emulatedSize();
|
const emulatedSize = this._page.emulatedSize();
|
||||||
|
|
@ -767,7 +767,7 @@ export class WKPage implements PageDelegate {
|
||||||
async exposeBinding(binding: PageBinding): Promise<void> {
|
async exposeBinding(binding: PageBinding): Promise<void> {
|
||||||
this._session.send('Runtime.addBinding', { name: binding.name });
|
this._session.send('Runtime.addBinding', { name: binding.name });
|
||||||
await this._updateBootstrapScript();
|
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<void> {
|
async removeExposedBindings(): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,7 @@ class UIMode {
|
||||||
|
|
||||||
private _dispatchEvent(message: any) {
|
private _dispatchEvent(message: any) {
|
||||||
// eslint-disable-next-line no-console
|
// 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() {
|
private async _listTests() {
|
||||||
|
|
|
||||||
|
|
@ -89,3 +89,18 @@ it('should access page after beforeunload', async ({ page, server }) => {
|
||||||
await page.evaluate(() => document.title);
|
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()),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,48 +43,6 @@ it('should not stall on JS navigation link', async ({ page, browserName }) => {
|
||||||
await page.click('a');
|
await page.click('a');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should await navigation when clicking anchor programmatically', async ({ page, server }) => {
|
|
||||||
const messages = initServer(server);
|
|
||||||
await page.setContent(`<a id="anchor" href="${server.EMPTY_PAGE}">empty.html</a>`);
|
|
||||||
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(`<a id="anchor" href="${server.EMPTY_PAGE}">empty.html</a>`);
|
|
||||||
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(`<a id="anchor" href="${server.EMPTY_PAGE}">empty.html</a>`);
|
|
||||||
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(`<a id="anchor" href="${server.EMPTY_PAGE}">empty.html</a>`);
|
|
||||||
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 }) => {
|
it('should await cross-process navigation when clicking anchor', async ({ page, server }) => {
|
||||||
const messages = initServer(server);
|
const messages = initServer(server);
|
||||||
await page.setContent(`<a href="${server.CROSS_PROCESS_PREFIX + '/empty.html'}">empty.html</a>`);
|
await page.setContent(`<a href="${server.CROSS_PROCESS_PREFIX + '/empty.html'}">empty.html</a>`);
|
||||||
|
|
@ -96,17 +54,6 @@ it('should await cross-process navigation when clicking anchor', async ({ page,
|
||||||
expect(messages.join('|')).toBe('route|navigated|click');
|
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(`<a id="anchor" href="${server.CROSS_PROCESS_PREFIX + '/empty.html'}">empty.html</a>`);
|
|
||||||
|
|
||||||
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 }) => {
|
it('should await form-get on click', async ({ page, server }) => {
|
||||||
const messages = [];
|
const messages = [];
|
||||||
server.setRoute('/empty.html?foo=bar', async (req, res) => {
|
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');
|
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 }) => {
|
it('should work with noWaitAfter: true', async ({ page, server }) => {
|
||||||
server.setRoute('/empty.html', async () => {});
|
server.setRoute('/empty.html', async () => {});
|
||||||
await page.setContent(`<a id="anchor" href="${server.EMPTY_PAGE}">empty.html</a>`);
|
await page.setContent(`<a id="anchor" href="${server.EMPTY_PAGE}">empty.html</a>`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue