From c51ea0afd16fe7abb4857f3b094a5ce592848463 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Wed, 15 Jul 2020 14:04:39 -0700 Subject: [PATCH] feat(rpc): remove PageAttribution from the protocol, attribute on the client side (#2957) This also changes timeout error format to "page.click: Timeout 5000ms exceeded", so that all errors can be similarly prefixed with api name. We can now have different api names in different clients, and our protocol is more reasonable. --- src/dom.ts | 18 +-- src/logger.ts | 8 +- src/progress.ts | 27 +++-- src/rpc/channels.ts | 60 +++++----- src/rpc/client/browserType.ts | 31 +++-- src/rpc/client/channelOwner.ts | 36 +++--- src/rpc/client/elementHandle.ts | 112 +++++++++++++----- src/rpc/client/frame.ts | 183 ++++++++++++++++++++--------- src/rpc/client/page.ts | 4 +- src/rpc/server.ts | 3 + src/rpc/server/frameDispatcher.ts | 147 ++++++++++------------- test/autowaiting.spec.js | 2 +- test/click.spec.js | 24 ++-- test/defaultbrowsercontext.spec.js | 2 +- test/environments.js | 2 + test/evaluation.spec.js | 2 +- test/frame.spec.js | 2 +- test/jshandle.spec.js | 2 +- test/launcher.spec.js | 4 +- test/logger.spec.js | 17 +-- test/navigation.spec.js | 18 +-- test/page.spec.js | 6 +- test/queryselector.spec.js | 10 +- test/screenshot.spec.js | 4 +- test/waittask.spec.js | 26 ++-- 25 files changed, 429 insertions(+), 321 deletions(-) diff --git a/src/dom.ts b/src/dom.ts index 661c161f0f..129cdff29e 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -287,11 +287,11 @@ export class ElementHandle extends js.JSHandle { }; } - async _retryPointerAction(progress: Progress, action: (point: types.Point) => Promise, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { + async _retryPointerAction(progress: Progress, actionName: string, action: (point: types.Point) => Promise, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { let first = true; while (progress.isRunning()) { - progress.logger.info(`${first ? 'attempting' : 'retrying'} ${progress.apiName} action`); - const result = await this._performPointerAction(progress, action, options); + progress.logger.info(`${first ? 'attempting' : 'retrying'} ${actionName} action`); + const result = await this._performPointerAction(progress, actionName, action, options); first = false; if (result === 'error:notvisible') { if (options.force) @@ -316,7 +316,7 @@ export class ElementHandle extends js.JSHandle { return 'done'; } - async _performPointerAction(progress: Progress, action: (point: types.Point) => Promise, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:nothittarget' | 'done'> { + async _performPointerAction(progress: Progress, actionName: string, action: (point: types.Point) => Promise, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:nothittarget' | 'done'> { const { force = false, position } = options; if ((options as any).__testHookBeforeStable) await (options as any).__testHookBeforeStable(); @@ -357,9 +357,9 @@ export class ElementHandle extends js.JSHandle { let restoreModifiers: types.KeyboardModifier[] | undefined; if (options && options.modifiers) restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers); - progress.logger.info(` performing ${progress.apiName} action`); + progress.logger.info(` performing ${actionName} action`); await action(point); - progress.logger.info(` ${progress.apiName} action done`); + progress.logger.info(` ${actionName} action done`); progress.logger.info(' waiting for scheduled navigations to finish'); if ((options as any).__testHookAfterPointerAction) await (options as any).__testHookAfterPointerAction(); @@ -379,7 +379,7 @@ export class ElementHandle extends js.JSHandle { } _hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { - return this._retryPointerAction(progress, point => this._page.mouse.move(point.x, point.y), options); + return this._retryPointerAction(progress, 'hover', point => this._page.mouse.move(point.x, point.y), options); } click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise { @@ -390,7 +390,7 @@ export class ElementHandle extends js.JSHandle { } _click(progress: Progress, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { - return this._retryPointerAction(progress, point => this._page.mouse.click(point.x, point.y, options), options); + return this._retryPointerAction(progress, 'click', point => this._page.mouse.click(point.x, point.y, options), options); } dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise { @@ -401,7 +401,7 @@ export class ElementHandle extends js.JSHandle { } _dblclick(progress: Progress, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { - return this._retryPointerAction(progress, point => this._page.mouse.dblclick(point.x, point.y, options), options); + return this._retryPointerAction(progress, 'dblclick', point => this._page.mouse.dblclick(point.x, point.y, options), options); } async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise { diff --git a/src/logger.ts b/src/logger.ts index a1b9f89a85..a1cdd7f8ef 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -58,13 +58,15 @@ export class Logger { return this._innerLog('error', message, args); } - createScope(scopeName: string, record?: boolean): Logger { - this._loggerSink.log(this._name, 'info', `=> ${scopeName} started`, [], this._hints); + createScope(scopeName: string | undefined, record?: boolean): Logger { + if (scopeName) + this._loggerSink.log(this._name, 'info', `=> ${scopeName} started`, [], this._hints); return new Logger(this._loggerSink, this._name, this._hints, scopeName, record); } endScope(status: string) { - this._loggerSink.log(this._name, 'info', `<= ${this._scopeName} ${status}`, [], this._hints); + if (this._scopeName) + this._loggerSink.log(this._name, 'info', `<= ${this._scopeName} ${status}`, [], this._hints); } private _innerLog(severity: LoggerSeverity, message: string | Error, ...args: any[]) { diff --git a/src/progress.ts b/src/progress.ts index caecace190..dec9c02f31 100644 --- a/src/progress.ts +++ b/src/progress.ts @@ -20,7 +20,6 @@ import { assert } from './helper'; import { rewriteErrorMessage } from './utils/stackTrace'; export interface Progress { - readonly apiName: string; readonly aborted: Promise; readonly logger: Logger; timeUntilDeadline(): number; @@ -34,6 +33,11 @@ export async function runAbortableTask(task: (progress: Progress) => Promise< return controller.run(task); } +let useApiName = true; +export function setUseApiName(value: boolean) { + useApiName = value; +} + export class ProgressController { // Promise and callback that forcefully abort the progress. // This promise always rejects. @@ -70,10 +74,9 @@ export class ProgressController { assert(this._state === 'before'); this._state = 'running'; - const loggerScope = this._logger.createScope(this._apiName, true); + const loggerScope = this._logger.createScope(useApiName ? this._apiName : undefined, true); const progress: Progress = { - apiName: this._apiName, aborted: this._abortedPromise, logger: loggerScope, timeUntilDeadline: () => this._deadline ? this._deadline - monotonicTime() : 2147483647, // 2^31-1 safe setTimeout in Node. @@ -90,7 +93,7 @@ export class ProgressController { }, }; - const timeoutError = new TimeoutError(`Timeout ${this._timeout}ms exceeded during ${this._apiName}.`); + const timeoutError = new TimeoutError(`Timeout ${this._timeout}ms exceeded.`); const timer = setTimeout(() => this._forceAbort(timeoutError), progress.timeUntilDeadline()); try { const promise = task(progress); @@ -101,7 +104,11 @@ export class ProgressController { return result; } catch (e) { this._aborted(); - rewriteErrorMessage(e, e.message + formatLogRecording(loggerScope.recording(), this._apiName) + kLoggingNote); + rewriteErrorMessage(e, + (useApiName ? `${this._apiName}: ` : '') + + e.message + + formatLogRecording(loggerScope.recording()) + + kLoggingNote); clearTimeout(timer); this._state = 'aborted'; loggerScope.endScope(`failed`); @@ -124,14 +131,14 @@ async function runCleanup(cleanup: () => any) { const kLoggingNote = `\nNote: use DEBUG=pw:api environment variable and rerun to capture Playwright logs.`; -function formatLogRecording(log: string[], name: string): string { +function formatLogRecording(log: string[]): string { if (!log.length) return ''; - name = ` ${name} logs `; + const header = ` logs `; const headerLength = 60; - const leftLength = (headerLength - name.length) / 2; - const rightLength = headerLength - name.length - leftLength; - return `\n${'='.repeat(leftLength)}${name}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`; + const leftLength = (headerLength - header.length) / 2; + const rightLength = headerLength - header.length - leftLength; + return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`; } function monotonicTime(): number { diff --git a/src/rpc/channels.ts b/src/rpc/channels.ts index 6115b26a08..8d246f3058 100644 --- a/src/rpc/channels.ts +++ b/src/rpc/channels.ts @@ -173,43 +173,41 @@ export type PageInitializer = { isClosed: boolean }; -export type PageAttribution = { isPage?: boolean }; - export interface FrameChannel extends Channel { on(event: 'loadstate', callback: (params: { add?: types.LifecycleEvent, remove?: types.LifecycleEvent }) => void): this; - evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ value: any }>; - evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ value: any }>; - addScriptTag(params: { url?: string, content?: string, type?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }>; - addStyleTag(params: { url?: string, content?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }>; - check(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise; - click(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & PageAttribution): Promise; + evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>; + evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>; + addScriptTag(params: { url?: string, content?: string, type?: string }): Promise<{ element: ElementHandleChannel }>; + addStyleTag(params: { url?: string, content?: string }): Promise<{ element: ElementHandleChannel }>; + check(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; + click(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions): Promise; content(): Promise<{ value: string }>; - dblclick(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & PageAttribution): Promise; - dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions & PageAttribution): Promise; - evaluateExpression(params: { expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ value: any }>; - evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ handle: JSHandleChannel }>; - fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions & PageAttribution): Promise; - focus(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise; + dblclick(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions): Promise; + dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions): Promise; + evaluateExpression(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>; + evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ handle: JSHandleChannel }>; + fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions): Promise; + focus(params: { selector: string } & types.TimeoutOptions): Promise; frameElement(): Promise<{ element: ElementHandleChannel }>; - getAttribute(params: { selector: string, name: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }>; - goto(params: { url: string } & types.GotoOptions & PageAttribution): Promise<{ response: ResponseChannel | null }>; - hover(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.TimeoutOptions & PageAttribution): Promise; - innerHTML(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }>; - innerText(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }>; - press(params: { selector: string, key: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise; - querySelector(params: { selector: string} & PageAttribution): Promise<{ element: ElementHandleChannel | null }>; - querySelectorAll(params: { selector: string} & PageAttribution): Promise<{ elements: ElementHandleChannel[] }>; - selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise<{ values: string[] }>; - setContent(params: { html: string } & types.NavigateOptions & PageAttribution): Promise; - setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: Binary }[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise; - textContent(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }>; + getAttribute(params: { selector: string, name: string } & types.TimeoutOptions): Promise<{ value: string | null }>; + goto(params: { url: string } & types.GotoOptions): Promise<{ response: ResponseChannel | null }>; + hover(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.TimeoutOptions): Promise; + innerHTML(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }>; + innerText(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }>; + press(params: { selector: string, key: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; + querySelector(params: { selector: string}): Promise<{ element: ElementHandleChannel | null }>; + querySelectorAll(params: { selector: string}): Promise<{ elements: ElementHandleChannel[] }>; + selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions): Promise<{ values: string[] }>; + setContent(params: { html: string } & types.NavigateOptions): Promise; + setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: Binary }[] } & types.NavigatingActionWaitOptions): Promise; + textContent(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string | null }>; title(): Promise<{ value: string }>; - type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise; - uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise; - waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions & PageAttribution): Promise<{ handle: JSHandleChannel }>; - waitForNavigation(params: types.WaitForNavigationOptions & PageAttribution): Promise<{ response: ResponseChannel | null }>; - waitForSelector(params: { selector: string } & types.WaitForElementOptions & PageAttribution): Promise<{ element: ElementHandleChannel | null }>; + type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; + uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise; + waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }>; + waitForNavigation(params: types.WaitForNavigationOptions): Promise<{ response: ResponseChannel | null }>; + waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element: ElementHandleChannel | null }>; } export type FrameInitializer = { url: string, diff --git a/src/rpc/client/browserType.ts b/src/rpc/client/browserType.ts index 2752d50127..b4fe0db220 100644 --- a/src/rpc/client/browserType.ts +++ b/src/rpc/client/browserType.ts @@ -43,30 +43,39 @@ export class BrowserType extends ChannelOwner { const logger = options.logger; options = { ...options, logger: undefined }; - const browser = Browser.from((await this._channel.launch(options)).browser); - browser._logger = logger; - return browser; + return this._wrapApiCall('browserType.launch', async () => { + const browser = Browser.from((await this._channel.launch(options)).browser); + browser._logger = logger; + return browser; + }, logger); } async launchServer(options: types.LaunchServerOptions & { logger?: LoggerSink } = {}): Promise { + const logger = options.logger; options = { ...options, logger: undefined }; - return BrowserServer.from((await this._channel.launchServer(options)).server); + return this._wrapApiCall('browserType.launchServer', async () => { + return BrowserServer.from((await this._channel.launchServer(options)).server); + }, logger); } async launchPersistentContext(userDataDir: string, options: types.LaunchOptions & types.BrowserContextOptions & { logger?: LoggerSink } = {}): Promise { const logger = options.logger; options = { ...options, logger: undefined }; - const result = await this._channel.launchPersistentContext({ userDataDir, ...options }); - const context = BrowserContext.from(result.context); - context._logger = logger; - return context; + return this._wrapApiCall('browserType.launchPersistentContext', async () => { + const result = await this._channel.launchPersistentContext({ userDataDir, ...options }); + const context = BrowserContext.from(result.context); + context._logger = logger; + return context; + }, logger); } async connect(options: types.ConnectOptions & { logger?: LoggerSink }): Promise { const logger = options.logger; options = { ...options, logger: undefined }; - const browser = Browser.from((await this._channel.connect(options)).browser); - browser._logger = logger; - return browser; + return this._wrapApiCall('browserType.connect', async () => { + const browser = Browser.from((await this._channel.connect(options)).browser); + browser._logger = logger; + return browser; + }, logger); } } diff --git a/src/rpc/client/channelOwner.ts b/src/rpc/client/channelOwner.ts index 0e94b4455a..611f4c58fc 100644 --- a/src/rpc/client/channelOwner.ts +++ b/src/rpc/client/channelOwner.ts @@ -19,6 +19,7 @@ import { Channel } from '../channels'; import { Connection } from './connection'; import { assert } from '../../helper'; import { LoggerSink } from '../../loggerSink'; +import { rewriteErrorMessage } from '../../utils/stackTrace'; export abstract class ChannelOwner extends EventEmitter { private _connection: Connection; @@ -65,23 +66,7 @@ export abstract class ChannelOwner { - const method = String(prop); - const apiName = this._type + '.' + method; - if (this._logger && this._logger.isEnabled('api', 'info')) - this._logger.log('api', 'info', `=> ${apiName} started`, [], { color: 'cyan' }); - try { - const result = await this._connection.sendMessageToServer({ guid, method: String(prop), params }); - if (this._logger && this._logger.isEnabled('api', 'info')) - this._logger.log('api', 'info', `=> ${apiName} succeeded`, [], { color: 'cyan' }); - return result; - } catch (e) { - if (this._logger && this._logger.isEnabled('api', 'info')) - this._logger.log('api', 'info', `=> ${apiName} failed`, [], { color: 'cyan' }); - throw e; - } - }; + return (params: any) => this._connection.sendMessageToServer({ guid, method: String(prop), params }); }, }); (this._channel as any)._object = this; @@ -112,4 +97,21 @@ export abstract class ChannelOwner o._debugScopeState()) : undefined, }; } + + protected async _wrapApiCall(apiName: string, func: () => Promise, logger?: LoggerSink): Promise { + logger = logger || this._logger; + try { + if (logger && logger.isEnabled('api', 'info')) + logger.log('api', 'info', `=> ${apiName} started`, [], { color: 'cyan' }); + const result = await func(); + if (logger && logger.isEnabled('api', 'info')) + logger.log('api', 'info', `=> ${apiName} succeeded`, [], { color: 'cyan' }); + return result; + } catch (e) { + if (logger && logger.isEnabled('api', 'info')) + logger.log('api', 'info', `=> ${apiName} failed`, [], { color: 'cyan' }); + rewriteErrorMessage(e, `${apiName}: ` + e.message); + throw e; + } + } } diff --git a/src/rpc/client/elementHandle.ts b/src/rpc/client/elementHandle.ts index c9b8e9e821..54cb34dee1 100644 --- a/src/rpc/client/elementHandle.ts +++ b/src/rpc/client/elementHandle.ts @@ -43,115 +43,167 @@ export class ElementHandle extends JSHandle { } async ownerFrame(): Promise { - return Frame.fromNullable((await this._elementChannel.ownerFrame()).frame); + return this._wrapApiCall('elementHandle.ownerFrame', async () => { + return Frame.fromNullable((await this._elementChannel.ownerFrame()).frame); + }); } async contentFrame(): Promise { - return Frame.fromNullable((await this._elementChannel.contentFrame()).frame); + return this._wrapApiCall('elementHandle.contentFrame', async () => { + return Frame.fromNullable((await this._elementChannel.contentFrame()).frame); + }); } async getAttribute(name: string): Promise { - return (await this._elementChannel.getAttribute({ name })).value; + return this._wrapApiCall('elementHandle.getAttribute', async () => { + return (await this._elementChannel.getAttribute({ name })).value; + }); } async textContent(): Promise { - return (await this._elementChannel.textContent()).value; + return this._wrapApiCall('elementHandle.textContent', async () => { + return (await this._elementChannel.textContent()).value; + }); } async innerText(): Promise { - return (await this._elementChannel.innerText()).value; + return this._wrapApiCall('elementHandle.innerText', async () => { + return (await this._elementChannel.innerText()).value; + }); } async innerHTML(): Promise { - return (await this._elementChannel.innerHTML()).value; + return this._wrapApiCall('elementHandle.innerHTML', async () => { + return (await this._elementChannel.innerHTML()).value; + }); } async dispatchEvent(type: string, eventInit: Object = {}) { - await this._elementChannel.dispatchEvent({ type, eventInit }); + return this._wrapApiCall('elementHandle.dispatchEvent', async () => { + await this._elementChannel.dispatchEvent({ type, eventInit }); + }); } async scrollIntoViewIfNeeded(options: types.TimeoutOptions = {}) { - await this._elementChannel.scrollIntoViewIfNeeded(options); + return this._wrapApiCall('elementHandle.scrollIntoViewIfNeeded', async () => { + await this._elementChannel.scrollIntoViewIfNeeded(options); + }); } async hover(options: types.PointerActionOptions & types.PointerActionWaitOptions = {}): Promise { - await this._elementChannel.hover(options); + return this._wrapApiCall('elementHandle.hover', async () => { + await this._elementChannel.hover(options); + }); } async click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise { - return await this._elementChannel.click(options); + return this._wrapApiCall('elementHandle.click', async () => { + return await this._elementChannel.click(options); + }); } async dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise { - return await this._elementChannel.dblclick(options); + return this._wrapApiCall('elementHandle.dblclick', async () => { + return await this._elementChannel.dblclick(options); + }); } async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise { - const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options }); - return result.values; + return this._wrapApiCall('elementHandle.selectOption', async () => { + const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options }); + return result.values; + }); } async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise { - return await this._elementChannel.fill({ value, ...options }); + return this._wrapApiCall('elementHandle.fill', async () => { + return await this._elementChannel.fill({ value, ...options }); + }); } async selectText(options: types.TimeoutOptions): Promise { - await this._elementChannel.selectText(options); + return this._wrapApiCall('elementHandle.selectText', async () => { + await this._elementChannel.selectText(options); + }); } async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}) { - await this._elementChannel.setInputFiles({ files: await convertInputFiles(files), ...options }); + return this._wrapApiCall('elementHandle.setInputFiles', async () => { + await this._elementChannel.setInputFiles({ files: await convertInputFiles(files), ...options }); + }); } async focus(): Promise { - await this._elementChannel.focus(); + return this._wrapApiCall('elementHandle.focus', async () => { + await this._elementChannel.focus(); + }); } async type(text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise { - await this._elementChannel.type({ text, ...options }); + return this._wrapApiCall('elementHandle.type', async () => { + await this._elementChannel.type({ text, ...options }); + }); } async press(key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise { - await this._elementChannel.press({ key, ...options }); + return this._wrapApiCall('elementHandle.press', async () => { + await this._elementChannel.press({ key, ...options }); + }); } async check(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { - return await this._elementChannel.check(options); + return this._wrapApiCall('elementHandle.check', async () => { + return await this._elementChannel.check(options); + }); } async uncheck(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { - return await this._elementChannel.uncheck(options); + return this._wrapApiCall('elementHandle.uncheck', async () => { + return await this._elementChannel.uncheck(options); + }); } async boundingBox(): Promise { - return (await this._elementChannel.boundingBox()).value; + return this._wrapApiCall('elementHandle.boundingBox', async () => { + return (await this._elementChannel.boundingBox()).value; + }); } async screenshot(options: types.ElementScreenshotOptions = {}): Promise { - return Buffer.from((await this._elementChannel.screenshot(options)).binary, 'base64'); + return this._wrapApiCall('elementHandle.screenshot', async () => { + return Buffer.from((await this._elementChannel.screenshot(options)).binary, 'base64'); + }); } async $(selector: string): Promise | null> { - return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element) as ElementHandle | null; + return this._wrapApiCall('elementHandle.$', async () => { + return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element) as ElementHandle | null; + }); } async $$(selector: string): Promise[]> { - const result = await this._elementChannel.querySelectorAll({ selector }); - return result.elements.map(h => ElementHandle.from(h) as ElementHandle); + return this._wrapApiCall('elementHandle.$$', async () => { + const result = await this._elementChannel.querySelectorAll({ selector }); + return result.elements.map(h => ElementHandle.from(h) as ElementHandle); + }); } async $eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise; async $eval(selector: string, pageFunction: FuncOn, arg?: any): Promise; async $eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise { - const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); - return parseResult(result.value); + return this._wrapApiCall('elementHandle.$eval', async () => { + const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); + return parseResult(result.value); + }); } async $$eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise; async $$eval(selector: string, pageFunction: FuncOn, arg?: any): Promise; async $$eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise { - const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); - return parseResult(result.value); + return this._wrapApiCall('elementHandle.$$eval', async () => { + const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); + return parseResult(result.value); + }); } } diff --git a/src/rpc/client/frame.ts b/src/rpc/client/frame.ts index ca552e1469..4ab9dfaab5 100644 --- a/src/rpc/client/frame.ts +++ b/src/rpc/client/frame.ts @@ -77,90 +77,121 @@ export class Frame extends ChannelOwner { }); } + private _apiName(method: string) { + return this._page!._isPageCall ? 'page.' + method : 'frame.' + method; + } + async goto(url: string, options: GotoOptions = {}): Promise { - return network.Response.fromNullable((await this._channel.goto({ url, ...options, isPage: this._page!._isPageCall })).response); + return this._wrapApiCall(this._apiName('goto'), async () => { + return network.Response.fromNullable((await this._channel.goto({ url, ...options })).response); + }); } async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise { - return network.Response.fromNullable((await this._channel.waitForNavigation({ ...options, isPage: this._page!._isPageCall })).response); + return this._wrapApiCall(this._apiName('waitForNavigation'), async () => { + return network.Response.fromNullable((await this._channel.waitForNavigation({ ...options })).response); + }); } async waitForLoadState(state: types.LifecycleEvent = 'load', options: types.TimeoutOptions = {}): Promise { state = verifyLoadState(state); if (this._loadStates.has(state)) return; - const timeout = this._page!._timeoutSettings.navigationTimeout(options); - const apiName = this._page!._isPageCall ? 'page.waitForLoadState' : 'frame.waitForLoadState'; - const waiter = new Waiter(); - waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!')); - waiter.rejectOnEvent(this._page!, Events.Page.Crash, new Error('Navigation failed because page crashed!')); - waiter.rejectOnEvent(this._page!, Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this); - waiter.rejectOnTimeout(timeout, new TimeoutError(`Timeout ${timeout}ms exceeded during ${apiName}.`)); - await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => s === state); - waiter.dispose(); + return this._wrapApiCall(this._apiName('waitForLoadState'), async () => { + const timeout = this._page!._timeoutSettings.navigationTimeout(options); + const waiter = new Waiter(); + waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!')); + waiter.rejectOnEvent(this._page!, Events.Page.Crash, new Error('Navigation failed because page crashed!')); + waiter.rejectOnEvent(this._page!, Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this); + waiter.rejectOnTimeout(timeout, new TimeoutError(`Timeout ${timeout}ms exceeded.`)); + await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => s === state); + waiter.dispose(); + }); } async frameElement(): Promise { - return ElementHandle.from((await this._channel.frameElement()).element); + return this._wrapApiCall(this._apiName('frameElement'), async () => { + return ElementHandle.from((await this._channel.frameElement()).element); + }); } async evaluateHandle(pageFunction: Func1, arg: Arg): Promise>; async evaluateHandle(pageFunction: Func1, arg?: any): Promise>; async evaluateHandle(pageFunction: Func1, arg: Arg): Promise> { assertMaxArguments(arguments.length, 2); - const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), isPage: this._page!._isPageCall }); - return JSHandle.from(result.handle) as SmartHandle; + return this._wrapApiCall(this._apiName('evaluateHandle'), async () => { + const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); + return JSHandle.from(result.handle) as SmartHandle; + }); } async evaluate(pageFunction: Func1, arg: Arg): Promise; async evaluate(pageFunction: Func1, arg?: any): Promise; async evaluate(pageFunction: Func1, arg: Arg): Promise { assertMaxArguments(arguments.length, 2); - const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), isPage: this._page!._isPageCall }); - return parseResult(result.value); + return this._wrapApiCall(this._apiName('evaluate'), async () => { + const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); + return parseResult(result.value); + }); } async $(selector: string): Promise | null> { - const result = await this._channel.querySelector({ selector, isPage: this._page!._isPageCall }); - return ElementHandle.fromNullable(result.element) as ElementHandle | null; + return this._wrapApiCall(this._apiName('$'), async () => { + const result = await this._channel.querySelector({ selector }); + return ElementHandle.fromNullable(result.element) as ElementHandle | null; + }); } async waitForSelector(selector: string, options: types.WaitForElementOptions = {}): Promise | null> { - const result = await this._channel.waitForSelector({ selector, ...options, isPage: this._page!._isPageCall }); - return ElementHandle.fromNullable(result.element) as ElementHandle | null; + return this._wrapApiCall(this._apiName('waitForSelector'), async () => { + const result = await this._channel.waitForSelector({ selector, ...options }); + return ElementHandle.fromNullable(result.element) as ElementHandle | null; + }); } async dispatchEvent(selector: string, type: string, eventInit?: any, options: types.TimeoutOptions = {}): Promise { - await this._channel.dispatchEvent({ selector, type, eventInit: serializeArgument(eventInit), ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('dispatchEvent'), async () => { + await this._channel.dispatchEvent({ selector, type, eventInit: serializeArgument(eventInit), ...options }); + }); } async $eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise; async $eval(selector: string, pageFunction: FuncOn, arg?: any): Promise; async $eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise { assertMaxArguments(arguments.length, 3); - const result = await this._channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), isPage: this._page!._isPageCall }); - return parseResult(result.value); + return this._wrapApiCall(this._apiName('$eval'), async () => { + const result = await this._channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); + return parseResult(result.value); + }); } async $$eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise; async $$eval(selector: string, pageFunction: FuncOn, arg?: any): Promise; async $$eval(selector: string, pageFunction: FuncOn, arg: Arg): Promise { assertMaxArguments(arguments.length, 3); - const result = await this._channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), isPage: this._page!._isPageCall }); - return parseResult(result.value); + return this._wrapApiCall(this._apiName('$$eval'), async () => { + const result = await this._channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); + return parseResult(result.value); + }); } async $$(selector: string): Promise[]> { - const result = await this._channel.querySelectorAll({ selector, isPage: this._page!._isPageCall }); - return result.elements.map(e => ElementHandle.from(e) as ElementHandle); + return this._wrapApiCall(this._apiName('$$'), async () => { + const result = await this._channel.querySelectorAll({ selector }); + return result.elements.map(e => ElementHandle.from(e) as ElementHandle); + }); } async content(): Promise { - return (await this._channel.content()).value; + return this._wrapApiCall(this._apiName('content'), async () => { + return (await this._channel.content()).value; + }); } async setContent(html: string, options: types.NavigateOptions = {}): Promise { - await this._channel.setContent({ html, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('setContent'), async () => { + await this._channel.setContent({ html, ...options }); + }); } name(): string { @@ -184,79 +215,113 @@ export class Frame extends ChannelOwner { } async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string }): Promise { - const copy = { ...options }; - if (copy.path) { - copy.content = (await fsReadFileAsync(copy.path)).toString(); - copy.content += '//# sourceURL=' + copy.path.replace(/\n/g, ''); - } - return ElementHandle.from((await this._channel.addScriptTag({ ...copy, isPage: this._page!._isPageCall })).element); + return this._wrapApiCall(this._apiName('addScriptTag'), async () => { + const copy = { ...options }; + if (copy.path) { + copy.content = (await fsReadFileAsync(copy.path)).toString(); + copy.content += '//# sourceURL=' + copy.path.replace(/\n/g, ''); + } + return ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element); + }); } async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise { - const copy = { ...options }; - if (copy.path) - copy.content = (await fsReadFileAsync(copy.path)).toString(); - return ElementHandle.from((await this._channel.addStyleTag({ ...options, isPage: this._page!._isPageCall })).element); + return this._wrapApiCall(this._apiName('addStyleTag'), async () => { + const copy = { ...options }; + if (copy.path) + copy.content = (await fsReadFileAsync(copy.path)).toString(); + return ElementHandle.from((await this._channel.addStyleTag({ ...options })).element); + }); } async click(selector: string, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { - return await this._channel.click({ selector, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('click'), async () => { + return await this._channel.click({ selector, ...options }); + }); } async dblclick(selector: string, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { - return await this._channel.dblclick({ selector, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('dblclick'), async () => { + return await this._channel.dblclick({ selector, ...options }); + }); } async fill(selector: string, value: string, options: types.NavigatingActionWaitOptions = {}) { - return await this._channel.fill({ selector, value, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('fill'), async () => { + return await this._channel.fill({ selector, value, ...options }); + }); } async focus(selector: string, options: types.TimeoutOptions = {}) { - await this._channel.focus({ selector, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('focus'), async () => { + await this._channel.focus({ selector, ...options }); + }); } async textContent(selector: string, options: types.TimeoutOptions = {}): Promise { - return (await this._channel.textContent({ selector, ...options, isPage: this._page!._isPageCall })).value; + return this._wrapApiCall(this._apiName('textContent'), async () => { + return (await this._channel.textContent({ selector, ...options })).value; + }); } async innerText(selector: string, options: types.TimeoutOptions = {}): Promise { - return (await this._channel.innerText({ selector, ...options, isPage: this._page!._isPageCall })).value; + return this._wrapApiCall(this._apiName('innerText'), async () => { + return (await this._channel.innerText({ selector, ...options })).value; + }); } async innerHTML(selector: string, options: types.TimeoutOptions = {}): Promise { - return (await this._channel.innerHTML({ selector, ...options, isPage: this._page!._isPageCall })).value; + return this._wrapApiCall(this._apiName('innerHTML'), async () => { + return (await this._channel.innerHTML({ selector, ...options })).value; + }); } async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise { - return (await this._channel.getAttribute({ selector, name, ...options, isPage: this._page!._isPageCall })).value; + return this._wrapApiCall(this._apiName('getAttribute'), async () => { + return (await this._channel.getAttribute({ selector, name, ...options })).value; + }); } async hover(selector: string, options: types.PointerActionOptions & types.PointerActionWaitOptions = {}) { - await this._channel.hover({ selector, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('hover'), async () => { + await this._channel.hover({ selector, ...options }); + }); } async selectOption(selector: string, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise { - return (await this._channel.selectOption({ selector, ...convertSelectOptionValues(values), ...options, isPage: this._page!._isPageCall })).values; + return this._wrapApiCall(this._apiName('selectOption'), async () => { + return (await this._channel.selectOption({ selector, ...convertSelectOptionValues(values), ...options })).values; + }); } async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise { - await this._channel.setInputFiles({ selector, files: await convertInputFiles(files), ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('setInputFiles'), async () => { + await this._channel.setInputFiles({ selector, files: await convertInputFiles(files), ...options }); + }); } async type(selector: string, text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) { - await this._channel.type({ selector, text, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('type'), async () => { + await this._channel.type({ selector, text, ...options }); + }); } async press(selector: string, key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}) { - await this._channel.press({ selector, key, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('press'), async () => { + await this._channel.press({ selector, key, ...options }); + }); } async check(selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { - await this._channel.check({ selector, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('check'), async () => { + await this._channel.check({ selector, ...options }); + }); } async uncheck(selector: string, options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) { - await this._channel.uncheck({ selector, ...options, isPage: this._page!._isPageCall }); + return this._wrapApiCall(this._apiName('uncheck'), async () => { + await this._channel.uncheck({ selector, ...options }); + }); } async waitForTimeout(timeout: number) { @@ -266,12 +331,16 @@ export class Frame extends ChannelOwner { async waitForFunction(pageFunction: Func1, arg: Arg, options?: types.WaitForFunctionOptions): Promise>; async waitForFunction(pageFunction: Func1, arg?: any, options?: types.WaitForFunctionOptions): Promise>; async waitForFunction(pageFunction: Func1, arg: Arg, options: types.WaitForFunctionOptions = {}): Promise> { - const result = await this._channel.waitForFunction({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), ...options, isPage: this._page!._isPageCall }); - return JSHandle.from(result.handle) as SmartHandle; + return this._wrapApiCall(this._apiName('waitForFunction'), async () => { + const result = await this._channel.waitForFunction({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), ...options }); + return JSHandle.from(result.handle) as SmartHandle; + }); } async title(): Promise { - return (await this._channel.title()).value; + return this._wrapApiCall(this._apiName('title'), async () => { + return (await this._channel.title()).value; + }); } } diff --git a/src/rpc/client/page.ts b/src/rpc/client/page.ts index cf80ab1710..e34696c86a 100644 --- a/src/rpc/client/page.ts +++ b/src/rpc/client/page.ts @@ -391,7 +391,9 @@ export class Page extends ChannelOwner { } async screenshot(options: types.ScreenshotOptions = {}): Promise { - return Buffer.from((await this._channel.screenshot(options)).binary, 'base64'); + return this._wrapApiCall('page.screenshot', async () => { + return Buffer.from((await this._channel.screenshot(options)).binary, 'base64'); + }); } async title(): Promise { diff --git a/src/rpc/server.ts b/src/rpc/server.ts index 70cd60579d..ee61e5191a 100644 --- a/src/rpc/server.ts +++ b/src/rpc/server.ts @@ -19,6 +19,9 @@ import { DispatcherConnection } from './server/dispatcher'; import { Playwright } from '../server/playwright'; import { PlaywrightDispatcher } from './server/playwrightDispatcher'; import { Electron } from '../server/electron'; +import { setUseApiName } from '../progress'; + +setUseApiName(false); const dispatcherConnection = new DispatcherConnection(); const transport = new Transport(process.stdout, process.stdin); diff --git a/src/rpc/server/frameDispatcher.ts b/src/rpc/server/frameDispatcher.ts index c4b6d93664..370ec8a8f3 100644 --- a/src/rpc/server/frameDispatcher.ts +++ b/src/rpc/server/frameDispatcher.ts @@ -16,7 +16,7 @@ import { Frame, kAddLifecycleEvent, kRemoveLifecycleEvent } from '../../frames'; import * as types from '../../types'; -import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel, PageAttribution } from '../channels'; +import { ElementHandleChannel, FrameChannel, FrameInitializer, JSHandleChannel, ResponseChannel } from '../channels'; import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher'; import { convertSelectOptionValues, ElementHandleDispatcher, createHandle, convertInputFiles } from './elementHandlerDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher'; @@ -46,58 +46,48 @@ export class FrameDispatcher extends Dispatcher impleme }); } - async goto(params: { url: string } & types.GotoOptions & PageAttribution): Promise<{ response: ResponseChannel | null }> { - const target = params.isPage ? this._frame._page : this._frame; - return { response: lookupNullableDispatcher(await target.goto(params.url, params)) }; + async goto(params: { url: string } & types.GotoOptions): Promise<{ response: ResponseChannel | null }> { + return { response: lookupNullableDispatcher(await this._frame.goto(params.url, params)) }; } - async waitForNavigation(params: types.WaitForNavigationOptions & PageAttribution): Promise<{ response: ResponseChannel | null }> { - const target = params.isPage ? this._frame._page : this._frame; - return { response: lookupNullableDispatcher(await target.waitForNavigation(params)) }; + async waitForNavigation(params: types.WaitForNavigationOptions): Promise<{ response: ResponseChannel | null }> { + return { response: lookupNullableDispatcher(await this._frame.waitForNavigation(params)) }; } async frameElement(): Promise<{ element: ElementHandleChannel }> { return { element: new ElementHandleDispatcher(this._scope, await this._frame.frameElement()) }; } - async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any } & PageAttribution): Promise<{ value: any }> { - const target = params.isPage ? this._frame._page : this._frame; - return { value: serializeResult(await target._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))) }; + async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> { + return { value: serializeResult(await this._frame._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))) }; } - async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any } & PageAttribution): Promise<{ handle: JSHandleChannel }> { - const target = params.isPage ? this._frame._page : this._frame; - return { handle: createHandle(this._scope, await target._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) }; + async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ handle: JSHandleChannel }> { + return { handle: createHandle(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) }; } - async waitForSelector(params: { selector: string } & types.WaitForElementOptions & PageAttribution): Promise<{ element: ElementHandleChannel | null }> { - const target = params.isPage ? this._frame._page : this._frame; - return { element: ElementHandleDispatcher.createNullable(this._scope, await target.waitForSelector(params.selector, params)) }; + async waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element: ElementHandleChannel | null }> { + return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.waitForSelector(params.selector, params)) }; } - async dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - return target.dispatchEvent(params.selector, params.type, parseArgument(params.eventInit), params); + async dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions): Promise { + return this._frame.dispatchEvent(params.selector, params.type, parseArgument(params.eventInit), params); } - async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: any } & PageAttribution): Promise<{ value: any }> { - const target = params.isPage ? this._frame._page : this._frame; - return { value: serializeResult(await target._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; + async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> { + return { value: serializeResult(await this._frame._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; } - async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: any } & PageAttribution): Promise<{ value: any }> { - const target = params.isPage ? this._frame._page : this._frame; - return { value: serializeResult(await target._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; + async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> { + return { value: serializeResult(await this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) }; } - async querySelector(params: { selector: string } & PageAttribution): Promise<{ element: ElementHandleChannel | null }> { - const target = params.isPage ? this._frame._page : this._frame; - return { element: ElementHandleDispatcher.createNullable(this._scope, await target.$(params.selector)) }; + async querySelector(params: { selector: string }): Promise<{ element: ElementHandleChannel | null }> { + return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.$(params.selector)) }; } - async querySelectorAll(params: { selector: string } & PageAttribution): Promise<{ elements: ElementHandleChannel[] }> { - const target = params.isPage ? this._frame._page : this._frame; - const elements = await target.$$(params.selector); + async querySelectorAll(params: { selector: string }): Promise<{ elements: ElementHandleChannel[] }> { + const elements = await this._frame.$$(params.selector); return { elements: elements.map(e => new ElementHandleDispatcher(this._scope, e)) }; } @@ -105,99 +95,80 @@ export class FrameDispatcher extends Dispatcher impleme return { value: await this._frame.content() }; } - async setContent(params: { html: string } & types.NavigateOptions & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.setContent(params.html, params); + async setContent(params: { html: string } & types.NavigateOptions): Promise { + await this._frame.setContent(params.html, params); } - async addScriptTag(params: { url?: string, content?: string, type?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }> { - const target = params.isPage ? this._frame._page : this._frame; - return { element: new ElementHandleDispatcher(this._scope, await target.addScriptTag(params)) }; + async addScriptTag(params: { url?: string, content?: string, type?: string }): Promise<{ element: ElementHandleChannel }> { + return { element: new ElementHandleDispatcher(this._scope, await this._frame.addScriptTag(params)) }; } - async addStyleTag(params: { url?: string, content?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }> { - const target = params.isPage ? this._frame._page : this._frame; - return { element: new ElementHandleDispatcher(this._scope, await target.addStyleTag(params)) }; + async addStyleTag(params: { url?: string, content?: string }): Promise<{ element: ElementHandleChannel }> { + return { element: new ElementHandleDispatcher(this._scope, await this._frame.addStyleTag(params)) }; } - async click(params: { selector: string } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.click(params.selector, params); + async click(params: { selector: string } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean }): Promise { + await this._frame.click(params.selector, params); } - async dblclick(params: { selector: string } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean } & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.dblclick(params.selector, params); + async dblclick(params: { selector: string } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean }): Promise { + await this._frame.dblclick(params.selector, params); } - async fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.fill(params.selector, params.value, params); + async fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions): Promise { + await this._frame.fill(params.selector, params.value, params); } - async focus(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.focus(params.selector, params); + async focus(params: { selector: string } & types.TimeoutOptions): Promise { + await this._frame.focus(params.selector, params); } - async textContent(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }> { - const target = params.isPage ? this._frame._page : this._frame; - return { value: await target.textContent(params.selector, params) }; + async textContent(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string | null }> { + return { value: await this._frame.textContent(params.selector, params) }; } - async innerText(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }> { - const target = params.isPage ? this._frame._page : this._frame; - return { value: await target.innerText(params.selector, params) }; + async innerText(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }> { + return { value: await this._frame.innerText(params.selector, params) }; } - async innerHTML(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }> { - const target = params.isPage ? this._frame._page : this._frame; - return { value: await target.innerHTML(params.selector, params) }; + async innerHTML(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }> { + return { value: await this._frame.innerHTML(params.selector, params) }; } - async getAttribute(params: { selector: string, name: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }> { - const target = params.isPage ? this._frame._page : this._frame; - return { value: await target.getAttribute(params.selector, params.name, params) }; + async getAttribute(params: { selector: string, name: string } & types.TimeoutOptions): Promise<{ value: string | null }> { + return { value: await this._frame.getAttribute(params.selector, params.name, params) }; } - async hover(params: { selector: string } & types.PointerActionOptions & types.TimeoutOptions & { force?: boolean } & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.hover(params.selector, params); + async hover(params: { selector: string } & types.PointerActionOptions & types.TimeoutOptions & { force?: boolean }): Promise { + await this._frame.hover(params.selector, params); } - async selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise<{ values: string[] }> { - const target = params.isPage ? this._frame._page : this._frame; - return { values: await target.selectOption(params.selector, convertSelectOptionValues(params.elements, params.options), params) }; + async selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions): Promise<{ values: string[] }> { + return { values: await this._frame.selectOption(params.selector, convertSelectOptionValues(params.elements, params.options), params) }; } - async setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: string }[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.setInputFiles(params.selector, convertInputFiles(params.files), params); + async setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: string }[] } & types.NavigatingActionWaitOptions): Promise { + await this._frame.setInputFiles(params.selector, convertInputFiles(params.files), params); } - async type(params: { selector: string, text: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.type(params.selector, params.text, params); + async type(params: { selector: string, text: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean }): Promise { + await this._frame.type(params.selector, params.text, params); } - async press(params: { selector: string, key: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.press(params.selector, params.key, params); + async press(params: { selector: string, key: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean }): Promise { + await this._frame.press(params.selector, params.key, params); } - async check(params: { selector: string } & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.check(params.selector, params); + async check(params: { selector: string } & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean }): Promise { + await this._frame.check(params.selector, params); } - async uncheck(params: { selector: string } & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } & PageAttribution): Promise { - const target = params.isPage ? this._frame._page : this._frame; - await target.uncheck(params.selector, params); + async uncheck(params: { selector: string } & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean }): Promise { + await this._frame.uncheck(params.selector, params); } - async waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions & PageAttribution): Promise<{ handle: JSHandleChannel }> { - const target = params.isPage ? this._frame._page : this._frame; - return { handle: createHandle(this._scope, await target._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params)) }; + async waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }> { + return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params)) }; } async title(): Promise<{ value: string }> { diff --git a/test/autowaiting.spec.js b/test/autowaiting.spec.js index 3a961e3eac..0f03d6bf96 100644 --- a/test/autowaiting.spec.js +++ b/test/autowaiting.spec.js @@ -191,7 +191,7 @@ describe('Auto waiting', () => { await page.setContent(`click me`); const __testHookAfterPointerAction = () => new Promise(f => setTimeout(f, 6000)); const error = await page.click('a', { timeout: 5000, __testHookAfterPointerAction }).catch(e => e); - expect(error.message).toContain('Timeout 5000ms exceeded during page.click.'); + expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); expect(error.message).toContain('waiting for scheduled navigations to finish'); expect(error.message).toContain(`navigated to "${server.PREFIX + '/frames/one-frame.html'}"`); }); diff --git a/test/click.spec.js b/test/click.spec.js index c4bdf311e0..562213643b 100644 --- a/test/click.spec.js +++ b/test/click.spec.js @@ -71,7 +71,7 @@ describe('Page.click', function() { const error = await page.click('button', { timeout: 2000, __testHookBeforePointerAction: () => new Promise(f => setTimeout(f, 2500))}).catch(e => e); await page.waitForTimeout(5000); // Give it some time to click after the test hook is done waiting. expect(await page.evaluate(() => result)).toBe('Was not clicked'); - expect(error.message).toContain('Timeout 2000ms exceeded during page.click.'); + expect(error.message).toContain('page.click: Timeout 2000ms exceeded.'); }); it('should click the button after navigation ', async({page, server}) => { await page.goto(server.PREFIX + '/input/button.html'); @@ -188,7 +188,7 @@ describe('Page.click', function() { await page.goto(server.PREFIX + '/input/button.html'); await page.$eval('button', b => b.style.display = 'none'); const error = await page.click('button', { timeout: 5000 }).catch(e => e); - expect(error.message).toContain('Timeout 5000ms exceeded during page.click.'); + expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); expect(error.message).toContain('element is not visible - waiting'); }); @@ -196,7 +196,7 @@ describe('Page.click', function() { await page.goto(server.PREFIX + '/input/button.html'); await page.$eval('button', b => b.style.visibility = 'hidden'); const error = await page.click('button', { timeout: 5000 }).catch(e => e); - expect(error.message).toContain('Timeout 5000ms exceeded during page.click.'); + expect(error.message).toContain('page.click: Timeout 5000ms exceeded.'); expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); expect(error.message).toContain('element is not visible - waiting'); }); @@ -440,7 +440,7 @@ describe('Page.click', function() { button.style.marginLeft = '200px'; }); const error = await button.click({ timeout: 5000 }).catch(e => e); - expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.'); + expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); expect(error.message).toContain('waiting for element to be visible, enabled and not moving'); expect(error.message).toContain('element is moving - waiting'); }); @@ -489,9 +489,9 @@ describe('Page.click', function() { document.body.appendChild(blocker); }); const error = await button.click({ timeout: 5000 }).catch(e => e); - expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.'); + expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); expect(error.message).toContain('element does not receive pointer events'); - expect(error.message).toContain('retrying elementHandle.click action'); + expect(error.message).toContain('retrying click action'); }); it('should fail when obscured and not waiting for hit target', async({page, server}) => { await page.goto(server.PREFIX + '/input/button.html'); @@ -525,7 +525,7 @@ describe('Page.click', function() { await page.setContent(''); const error = await page.click('text=Click target', { timeout: 3000 }).catch(e => e); expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); - expect(error.message).toContain('Timeout 3000ms exceeded during page.click.'); + expect(error.message).toContain('page.click: Timeout 3000ms exceeded.'); expect(error.message).toContain('element is disabled - waiting'); }); it('should wait for input to be enabled', async({page, server}) => { @@ -720,9 +720,9 @@ describe('Page.click', function() { const error = await promise; expect(clicked).toBe(false); expect(await page.evaluate(() => window.clicked)).toBe(undefined); - expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.'); + expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.'); expect(error.message).toContain('element does not receive pointer events'); - expect(error.message).toContain('retrying elementHandle.click action'); + expect(error.message).toContain('retrying click action'); }); it('should dispatch microtasks in order', async({page, server}) => { await page.setContent(` @@ -775,7 +775,7 @@ describe('Page.click', function() { const error = await page.dblclick('text=button1', { __testHookAfterStable, timeout: 3000 }).catch(e => e); expect(await page.evaluate(() => window.button1)).toBe(undefined); expect(await page.evaluate(() => window.button2)).toBe(undefined); - expect(error.message).toContain('Timeout 3000ms exceeded during page.dblclick.'); + expect(error.message).toContain('page.dblclick: Timeout 3000ms exceeded.'); expect(error.message).toContain('element does not match the selector anymore'); }); it.skip(USES_HOOKS).fail(true)('should retarget when element is recycled before enabled check', async ({page, server}) => { @@ -806,7 +806,7 @@ describe('Page.click', function() { const error = await handle.click({ __testHookBeforeStable, timeout: 3000 }).catch(e => e); expect(await page.evaluate(() => window.button1)).toBe(undefined); expect(await page.evaluate(() => window.button2)).toBe(undefined); - expect(error.message).toContain('Timeout 3000ms exceeded during elementHandle.click.'); + expect(error.message).toContain('elementHandle.click: Timeout 3000ms exceeded.'); expect(error.message).toContain('element is disabled - waiting'); }); it('should not retarget when element changes on hover', async ({page, server}) => { @@ -840,7 +840,7 @@ describe('Page.click', function() { const dialogPromise = page.waitForEvent('dialog'); await page.setContent(`
Click me
`); const error = await page.click('div', { timeout: 3000 }).catch(e => e); - expect(error.message).toContain('Timeout 3000ms exceeded during page.click.'); + expect(error.message).toContain('page.click: Timeout 3000ms exceeded.'); const dialog = await dialogPromise; await dialog.dismiss(); }); diff --git a/test/defaultbrowsercontext.spec.js b/test/defaultbrowsercontext.spec.js index cfd109f472..02896402f8 100644 --- a/test/defaultbrowsercontext.spec.js +++ b/test/defaultbrowsercontext.spec.js @@ -356,7 +356,7 @@ describe('launchPersistentContext()', function() { const userDataDir = await makeUserDataDir(); const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) }; const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e); - expect(error.message).toContain(`Timeout 5000ms exceeded during browserType.launchPersistentContext.`); + expect(error.message).toContain(`browserType.launchPersistentContext: Timeout 5000ms exceeded.`); await removeUserDataDir(userDataDir); }); it.skip(USES_HOOKS)('should handle exception', async({browserType, defaultBrowserOptions}) => { diff --git a/test/environments.js b/test/environments.js index 02c053a7f1..7fc2b3c106 100644 --- a/test/environments.js +++ b/test/environments.js @@ -25,6 +25,7 @@ const { DispatcherConnection } = require('../lib/rpc/server/dispatcher'); const { Connection } = require('../lib/rpc/client/connection'); const { Transport } = require('../lib/rpc/transport'); const { PlaywrightDispatcher } = require('../lib/rpc/server/playwrightDispatcher'); +const { setUseApiName } = require('../lib/progress'); class ServerEnvironment { async beforeAll(state) { @@ -167,6 +168,7 @@ class PlaywrightEnvironment { async beforeAll(state) { if (process.env.PWCHANNEL) { + setUseApiName(false); const connection = new Connection(); if (process.env.PWCHANNEL === 'wire') { this.spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'rpc', 'server'), [], { diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js index cbb1094ba4..ed197d9724 100644 --- a/test/evaluation.spec.js +++ b/test/evaluation.spec.js @@ -611,7 +611,7 @@ describe('Frame.evaluate', function() { const childResult = await childFrame.evaluate(() => window.__foo); expect(childResult).toEqual({ bar: 'baz' }); const error = await childFrame.evaluate(foo => foo.bar, handle).catch(e => e); - expect(error.message).toBe('JSHandles can be evaluated only in the context they were created!'); + expect(error.message).toContain('JSHandles can be evaluated only in the context they were created!'); }); it('should allow cross-frame element handles', async({page, server}) => { await page.goto(server.PREFIX + '/frames/one-frame.html'); diff --git a/test/frame.spec.js b/test/frame.spec.js index 827c18d946..fc0b432c34 100644 --- a/test/frame.spec.js +++ b/test/frame.spec.js @@ -53,7 +53,7 @@ describe('Frame.frameElement', function() { const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); await page.$eval('#frame1', e => e.remove()); const error = await frame1.frameElement().catch(e => e); - expect(error.message).toBe('Frame has been detached.'); + expect(error.message).toContain('Frame has been detached.'); }); }); diff --git a/test/jshandle.spec.js b/test/jshandle.spec.js index 0ab4a5e3a0..62a011fa68 100644 --- a/test/jshandle.spec.js +++ b/test/jshandle.spec.js @@ -62,7 +62,7 @@ describe('Page.evaluateHandle', function() { const a = { x: 1 }; a.y = a; const error = await page.evaluate(x => x, a).catch(e => e); - expect(error.message).toBe('Argument is a circular structure'); + expect(error.message).toContain('Argument is a circular structure'); }); it('should accept same handle multiple times', async({page, server}) => { const foo = await page.evaluateHandle(() => 1); diff --git a/test/launcher.spec.js b/test/launcher.spec.js index 5ba1448e23..be52970f12 100644 --- a/test/launcher.spec.js +++ b/test/launcher.spec.js @@ -48,7 +48,7 @@ describe('Playwright', function() { const options = Object.assign({}, defaultBrowserOptions, {executablePath: path.join(__dirname, 'assets', 'dummy_bad_browser_executable.js')}); let waitError = null; await browserType.launch(options).catch(e => waitError = e); - expect(waitError.message).toContain('browserType.launch logs'); + expect(waitError.message).toContain('== logs =='); }); it('should reject if executable path is invalid', async({browserType, defaultBrowserOptions}) => { let waitError = null; @@ -59,7 +59,7 @@ describe('Playwright', function() { it.skip(USES_HOOKS)('should handle timeout', async({browserType, defaultBrowserOptions}) => { const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) }; const error = await browserType.launch(options).catch(e => e); - expect(error.message).toContain(`Timeout 5000ms exceeded during browserType.launch.`); + expect(error.message).toContain(`browserType.launch: Timeout 5000ms exceeded.`); expect(error.message).toContain(`[browser] `); expect(error.message).toContain(`[browser] pid=`); }); diff --git a/test/logger.spec.js b/test/logger.spec.js index b24b1df1b7..0b851e3297 100644 --- a/test/logger.spec.js +++ b/test/logger.spec.js @@ -29,13 +29,8 @@ describe('Logger', function() { await browser.close(); expect(log.length > 0).toBeTruthy(); expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy(); - if (CHANNEL) { - expect(log.filter(item => item.message.includes('browser.newContext started')).length > 0).toBeTruthy(); - expect(log.filter(item => item.message.includes('browser.newContext succeeded')).length > 0).toBeTruthy(); - } else { - expect(log.filter(item => item.message.includes('browserType.launch started')).length > 0).toBeTruthy(); - expect(log.filter(item => item.message.includes('browserType.launch succeeded')).length > 0).toBeTruthy(); - } + expect(log.filter(item => item.message.includes('browserType.launch started')).length > 0).toBeTruthy(); + expect(log.filter(item => item.message.includes('browserType.launch succeeded')).length > 0).toBeTruthy(); }); it('should log context-level', async({browserType, defaultBrowserOptions}) => { const log = []; @@ -52,11 +47,7 @@ describe('Logger', function() { await browser.close(); expect(log.length > 0).toBeTruthy(); - if (CHANNEL) { - expect(log.filter(item => item.message.includes('context.newPage')).length > 0).toBeTruthy(); - expect(log.filter(item => item.message.includes('frame.click')).length > 0).toBeTruthy(); - } else { - expect(log.filter(item => item.message.includes('page.click')).length > 0).toBeTruthy(); - } + expect(log.filter(item => item.message.includes('page.setContent')).length > 0).toBeTruthy(); + expect(log.filter(item => item.message.includes('page.click')).length > 0).toBeTruthy(); }); }); diff --git a/test/navigation.spec.js b/test/navigation.spec.js index c8ad14d34a..96a54dd671 100644 --- a/test/navigation.spec.js +++ b/test/navigation.spec.js @@ -198,7 +198,7 @@ describe('Page.goto', function() { server.setRoute('/empty.html', (req, res) => { }); let error = null; await page.goto(server.PREFIX + '/empty.html', {timeout: 1}).catch(e => error = e); - expect(error.message).toContain('Timeout 1ms exceeded during page.goto.'); + expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); expect(error.message).toContain(server.PREFIX + '/empty.html'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); @@ -209,7 +209,7 @@ describe('Page.goto', function() { page.context().setDefaultNavigationTimeout(2); page.setDefaultNavigationTimeout(1); await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('Timeout 1ms exceeded during page.goto.'); + expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); expect(error.message).toContain(server.PREFIX + '/empty.html'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); @@ -219,7 +219,7 @@ describe('Page.goto', function() { let error = null; page.context().setDefaultNavigationTimeout(2); await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('Timeout 2ms exceeded during page.goto.'); + expect(error.message).toContain('page.goto: Timeout 2ms exceeded.'); expect(error.message).toContain(server.PREFIX + '/empty.html'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); @@ -230,7 +230,7 @@ describe('Page.goto', function() { page.context().setDefaultTimeout(2); page.setDefaultTimeout(1); await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('Timeout 1ms exceeded during page.goto.'); + expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); expect(error.message).toContain(server.PREFIX + '/empty.html'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); @@ -240,7 +240,7 @@ describe('Page.goto', function() { let error = null; page.context().setDefaultTimeout(2); await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('Timeout 2ms exceeded during page.goto.'); + expect(error.message).toContain('page.goto: Timeout 2ms exceeded.'); expect(error.message).toContain(server.PREFIX + '/empty.html'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); @@ -251,7 +251,7 @@ describe('Page.goto', function() { page.setDefaultTimeout(0); page.setDefaultNavigationTimeout(1); await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); - expect(error.message).toContain('Timeout 1ms exceeded during page.goto.'); + expect(error.message).toContain('page.goto: Timeout 1ms exceeded.'); expect(error.message).toContain(server.PREFIX + '/empty.html'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); @@ -578,7 +578,7 @@ describe.skip(CHANNEL)('Page.waitForNavigation', function() { const promise = page.waitForNavigation({ url: '**/frame.html', timeout: 5000 }); await page.goto(server.EMPTY_PAGE); const error = await promise.catch(e => e); - expect(error.message).toContain('Timeout 5000ms exceeded during page.waitForNavigation.'); + expect(error.message).toContain('page.waitForNavigation: Timeout 5000ms exceeded.'); expect(error.message).toContain('waiting for navigation to "**/frame.html" until "load"'); expect(error.message).toContain(`navigated to "${server.EMPTY_PAGE}"`); }); @@ -774,7 +774,7 @@ describe('Page.waitForLoadState', () => { server.setRoute('/one-style.css', (req, res) => response = res); await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e); - expect(error.message).toContain('Timeout 1ms exceeded during page.waitForLoadState.'); + expect(error.message).toContain('page.waitForLoadState: Timeout 1ms exceeded.'); }); it('should resolve immediately if loaded', async({page, server}) => { await page.goto(server.PREFIX + '/one-style.html'); @@ -971,7 +971,7 @@ describe('Frame.goto', function() { server.setRoute('/frames/script.js', () => {}); const url = server.PREFIX + '/frames/child-redirect.html'; const error = await page.goto(url, { timeout: 5000, waitUntil: 'networkidle' }).catch(e => e); - expect(error.message).toContain('Timeout 5000ms exceeded during page.goto.'); + expect(error.message).toContain('page.goto: Timeout 5000ms exceeded.'); expect(error.message).toContain(`navigating to "${url}", waiting until "networkidle"`); }); it('should return matching responses', async({page, server}) => { diff --git a/test/page.spec.js b/test/page.spec.js index ec1ffbbacc..f3f1e4f60e 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -662,7 +662,7 @@ describe('Page.setContent', function() { // stall for image server.setRoute(imgPath, (req, res) => {}); const error = await page.setContent(``).catch(e => e); - expect(error.message).toContain('Timeout 1ms exceeded during page.setContent.'); + expect(error.message).toContain('page.setContent: Timeout 1ms exceeded.'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); it('should await resources to load', async({page, server}) => { @@ -707,7 +707,7 @@ describe('Page.addScriptTag', function() { } catch (e) { error = e; } - expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property'); + expect(error.message).toContain('Provide an object with a `url`, `path` or `content` property'); }); it('should work with a url', async({page, server}) => { @@ -799,7 +799,7 @@ describe('Page.addStyleTag', function() { } catch (e) { error = e; } - expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property'); + expect(error.message).toContain('Provide an object with a `url`, `path` or `content` property'); }); it('should work with a url', async({page, server}) => { diff --git a/test/queryselector.spec.js b/test/queryselector.spec.js index f3b040c1e6..bfb0254700 100644 --- a/test/queryselector.spec.js +++ b/test/queryselector.spec.js @@ -120,11 +120,11 @@ describe('Page.$eval', function() { }); it('should throw on multiple * captures', async({page, server}) => { const error = await page.$eval('*css=div >> *css=span', e => e.outerHTML).catch(e => e); - expect(error.message).toBe('Only one of the selectors can capture using * modifier'); + expect(error.message).toContain('Only one of the selectors can capture using * modifier'); }); it('should throw on malformed * capture', async({page, server}) => { const error = await page.$eval('*=div', e => e.outerHTML).catch(e => e); - expect(error.message).toBe('Unknown engine "" while parsing selector *=div'); + expect(error.message).toContain('Unknown engine "" while parsing selector *=div'); }); it('should work with spaces in css attributes', async({page, server}) => { await page.setContent('
'); @@ -378,7 +378,7 @@ describe('ElementHandle.$eval', function() { await page.setContent(htmlContent); const elementHandle = await page.$('#myId'); const errorMessage = await elementHandle.$eval('.a', node => node.innerText).catch(error => error.message); - expect(errorMessage).toBe(`Error: failed to find element matching selector ".a"`); + expect(errorMessage).toContain(`Error: failed to find element matching selector ".a"`); }); }); describe('ElementHandle.$$eval', function() { @@ -775,7 +775,7 @@ describe('selectors.register', () => { // Selector names are case-sensitive. const error = await page.$('tAG=DIV').catch(e => e); - expect(error.message).toBe('Unknown engine "tAG" while parsing selector tAG=DIV'); + expect(error.message).toContain('Unknown engine "tAG" while parsing selector tAG=DIV'); }); it('should work with path', async ({playwright, page}) => { await utils.registerEngine(playwright, 'foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') }); @@ -815,7 +815,7 @@ describe('selectors.register', () => { }); it('should handle errors', async ({playwright, page}) => { let error = await page.$('neverregister=ignored').catch(e => e); - expect(error.message).toBe('Unknown engine "neverregister" while parsing selector neverregister=ignored'); + expect(error.message).toContain('Unknown engine "neverregister" while parsing selector neverregister=ignored'); const createDummySelector = () => ({ create(root, target) { diff --git a/test/screenshot.spec.js b/test/screenshot.spec.js index 602a758e2e..2246312197 100644 --- a/test/screenshot.spec.js +++ b/test/screenshot.spec.js @@ -379,7 +379,7 @@ describe.skip(ffheadful)('ElementHandle.screenshot', function() { await page.setContent('
'); const div = await page.$('div'); const error = await div.screenshot({ timeout: 3000 }).catch(e => e); - expect(error.message).toContain('Timeout 3000ms exceeded during elementHandle.screenshot'); + expect(error.message).toContain('elementHandle.screenshot: Timeout 3000ms exceeded'); expect(error.message).toContain('element is not visible'); }); it('should wait for visible', async({page, server, golden}) => { @@ -490,7 +490,7 @@ describe.skip(ffheadful)('ElementHandle.screenshot', function() { await page.goto(server.PREFIX + '/grid.html'); const __testHookAfterScreenshot = () => new Promise(f => setTimeout(f, 5000)); const error = await page.screenshot({ fullPage: true, __testHookAfterScreenshot, timeout: 3000 }).catch(e => e); - expect(error.message).toContain('Timeout 3000ms exceeded during page.screenshot'); + expect(error.message).toContain('page.screenshot: Timeout 3000ms exceeded'); await utils.verifyViewport(page, 350, 360); await page.setViewportSize({ width: 400, height: 400 }); await page.waitForTimeout(3000); // Give it some time to wrongly restore previous viewport. diff --git a/test/waittask.spec.js b/test/waittask.spec.js index c761b26209..6ae957c5bf 100644 --- a/test/waittask.spec.js +++ b/test/waittask.spec.js @@ -69,12 +69,12 @@ describe('Frame.waitForFunction', function() { const savedCounter = counter; await page.waitForTimeout(2000); // Give it some time to produce more logs. - expect(error.message).toContain('Timeout 1000ms exceeded during page.waitForFunction'); + expect(error.message).toContain('page.waitForFunction: Timeout 1000ms exceeded'); expect(counter).toBe(savedCounter); }); it('should throw on polling:mutation', async({page, server}) => { const error = await page.waitForFunction(() => true, {}, {polling: 'mutation'}).catch(e => e); - expect(error.message).toBe('Unknown polling option: mutation'); + expect(error.message).toContain('Unknown polling option: mutation'); }); it('should poll on raf', async({page, server}) => { const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'}); @@ -147,7 +147,7 @@ describe('Frame.waitForFunction', function() { let error = null; await page.waitForFunction('false', {}, {timeout: 10}).catch(e => error = e); expect(error).toBeTruthy(); - expect(error.message).toContain('Timeout 10ms exceeded during page.waitForFunction'); + expect(error.message).toContain('page.waitForFunction: Timeout 10ms exceeded'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); it('should respect default timeout', async({page}) => { @@ -155,7 +155,7 @@ describe('Frame.waitForFunction', function() { let error = null; await page.waitForFunction('false').catch(e => error = e); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); - expect(error.message).toContain('Timeout 1ms exceeded during page.waitForFunction'); + expect(error.message).toContain('page.waitForFunction: Timeout 1ms exceeded'); }); it('should disable timeout when its set to 0', async({page}) => { const watchdog = page.waitForFunction(() => { @@ -203,7 +203,7 @@ describe('Frame.waitForSelector', function() { await page.goto(server.EMPTY_PAGE); let error; await page.waitForSelector('*', { waitFor: 'attached' }).catch(e => error = e); - expect(error.message).toBe('options.waitFor is not supported, did you mean options.state?'); + expect(error.message).toContain('options.waitFor is not supported, did you mean options.state?'); }); it('should tolerate waitFor=visible', async({page, server}) => { await page.goto(server.EMPTY_PAGE); @@ -262,7 +262,7 @@ describe('Frame.waitForSelector', function() { await giveItTimeToLog(frame); const error = await watchdog.catch(e => e); - expect(error.message).toContain(`Timeout 5000ms exceeded during frame.waitForSelector.`); + expect(error.message).toContain(`frame.waitForSelector: Timeout 5000ms exceeded.`); expect(error.message).toContain(`waiting for selector "div" to be visible`); expect(error.message).toContain(`selector resolved to hidden
hello
`); expect(error.message).toContain(`selector resolved to visible
hello
`); @@ -377,10 +377,10 @@ describe('Frame.waitForSelector', function() { it('should not consider visible when zero-sized', async({page, server}) => { await page.setContent(`
1
`); let error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e); - expect(error.message).toContain('Timeout 1000ms exceeded during page.waitForSelector'); + expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded'); await page.evaluate(() => document.querySelector('div').style.width = '10px'); error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e); - expect(error.message).toContain('Timeout 1000ms exceeded during page.waitForSelector'); + expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded'); await page.evaluate(() => document.querySelector('div').style.height = '10px'); expect(await page.waitForSelector('div', { timeout: 1000 })).toBeTruthy(); }); @@ -433,7 +433,7 @@ describe('Frame.waitForSelector', function() { let error = null; await page.waitForSelector('div', { timeout: 3000, state: 'attached' }).catch(e => error = e); expect(error).toBeTruthy(); - expect(error.message).toContain('Timeout 3000ms exceeded during page.waitForSelector'); + expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded'); expect(error.message).toContain('waiting for selector "div"'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); }); @@ -442,7 +442,7 @@ describe('Frame.waitForSelector', function() { let error = null; await page.waitForSelector('div', { state: 'hidden', timeout: 1000 }).catch(e => error = e); expect(error).toBeTruthy(); - expect(error.message).toContain('Timeout 1000ms exceeded during page.waitForSelector'); + expect(error.message).toContain('page.waitForSelector: Timeout 1000ms exceeded'); expect(error.message).toContain('waiting for selector "div" to be hidden'); }); it('should respond to node attribute mutation', async({page, server}) => { @@ -471,7 +471,7 @@ describe('Frame.waitForSelector', function() { it('should throw for visibility option', async({page, server}) => { await page.setContent('
test
'); const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e); - expect(error.message).toBe('options.visibility is not supported, did you mean options.state?'); + expect(error.message).toContain('options.visibility is not supported, did you mean options.state?'); }); it('should throw for true state option', async({page, server}) => { await page.setContent('
test
'); @@ -523,7 +523,7 @@ describe('Frame.waitForSelector xpath', function() { let error = null; await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e); expect(error).toBeTruthy(); - expect(error.message).toContain('Timeout 3000ms exceeded during page.waitForSelector'); + expect(error.message).toContain('page.waitForSelector: Timeout 3000ms exceeded'); expect(error.message).toContain('waiting for selector "//div"'); expect(error).toBeInstanceOf(playwright.errors.TimeoutError); });