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.
This commit is contained in:
Dmitry Gozman 2020-07-15 14:04:39 -07:00 committed by GitHub
parent 7f6171579b
commit c51ea0afd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 429 additions and 321 deletions

View file

@ -287,11 +287,11 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}; };
} }
async _retryPointerAction(progress: Progress, action: (point: types.Point) => Promise<void>, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { async _retryPointerAction(progress: Progress, actionName: string, action: (point: types.Point) => Promise<void>, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> {
let first = true; let first = true;
while (progress.isRunning()) { while (progress.isRunning()) {
progress.logger.info(`${first ? 'attempting' : 'retrying'} ${progress.apiName} action`); progress.logger.info(`${first ? 'attempting' : 'retrying'} ${actionName} action`);
const result = await this._performPointerAction(progress, action, options); const result = await this._performPointerAction(progress, actionName, action, options);
first = false; first = false;
if (result === 'error:notvisible') { if (result === 'error:notvisible') {
if (options.force) if (options.force)
@ -316,7 +316,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
return 'done'; return 'done';
} }
async _performPointerAction(progress: Progress, action: (point: types.Point) => Promise<void>, 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<void>, options: types.PointerActionOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notvisible' | 'error:notconnected' | 'error:notinviewport' | 'error:nothittarget' | 'done'> {
const { force = false, position } = options; const { force = false, position } = options;
if ((options as any).__testHookBeforeStable) if ((options as any).__testHookBeforeStable)
await (options as any).__testHookBeforeStable(); await (options as any).__testHookBeforeStable();
@ -357,9 +357,9 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
let restoreModifiers: types.KeyboardModifier[] | undefined; let restoreModifiers: types.KeyboardModifier[] | undefined;
if (options && options.modifiers) if (options && options.modifiers)
restoreModifiers = await this._page.keyboard._ensureModifiers(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); 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'); progress.logger.info(' waiting for scheduled navigations to finish');
if ((options as any).__testHookAfterPointerAction) if ((options as any).__testHookAfterPointerAction)
await (options as any).__testHookAfterPointerAction(); await (options as any).__testHookAfterPointerAction();
@ -379,7 +379,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
_hover(progress: Progress, options: types.PointerActionOptions & types.PointerActionWaitOptions): Promise<'error:notconnected' | 'done'> { _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<void> { click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
@ -390,7 +390,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
_click(progress: Progress, options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { _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<void> { dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
@ -401,7 +401,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
_dblclick(progress: Progress, options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions): Promise<'error:notconnected' | 'done'> { _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<string[]> { async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {

View file

@ -58,13 +58,15 @@ export class Logger {
return this._innerLog('error', message, args); return this._innerLog('error', message, args);
} }
createScope(scopeName: string, record?: boolean): Logger { createScope(scopeName: string | undefined, record?: boolean): Logger {
this._loggerSink.log(this._name, 'info', `=> ${scopeName} started`, [], this._hints); if (scopeName)
this._loggerSink.log(this._name, 'info', `=> ${scopeName} started`, [], this._hints);
return new Logger(this._loggerSink, this._name, this._hints, scopeName, record); return new Logger(this._loggerSink, this._name, this._hints, scopeName, record);
} }
endScope(status: string) { 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[]) { private _innerLog(severity: LoggerSeverity, message: string | Error, ...args: any[]) {

View file

@ -20,7 +20,6 @@ import { assert } from './helper';
import { rewriteErrorMessage } from './utils/stackTrace'; import { rewriteErrorMessage } from './utils/stackTrace';
export interface Progress { export interface Progress {
readonly apiName: string;
readonly aborted: Promise<void>; readonly aborted: Promise<void>;
readonly logger: Logger; readonly logger: Logger;
timeUntilDeadline(): number; timeUntilDeadline(): number;
@ -34,6 +33,11 @@ export async function runAbortableTask<T>(task: (progress: Progress) => Promise<
return controller.run(task); return controller.run(task);
} }
let useApiName = true;
export function setUseApiName(value: boolean) {
useApiName = value;
}
export class ProgressController { export class ProgressController {
// Promise and callback that forcefully abort the progress. // Promise and callback that forcefully abort the progress.
// This promise always rejects. // This promise always rejects.
@ -70,10 +74,9 @@ export class ProgressController {
assert(this._state === 'before'); assert(this._state === 'before');
this._state = 'running'; this._state = 'running';
const loggerScope = this._logger.createScope(this._apiName, true); const loggerScope = this._logger.createScope(useApiName ? this._apiName : undefined, true);
const progress: Progress = { const progress: Progress = {
apiName: this._apiName,
aborted: this._abortedPromise, aborted: this._abortedPromise,
logger: loggerScope, logger: loggerScope,
timeUntilDeadline: () => this._deadline ? this._deadline - monotonicTime() : 2147483647, // 2^31-1 safe setTimeout in Node. 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()); const timer = setTimeout(() => this._forceAbort(timeoutError), progress.timeUntilDeadline());
try { try {
const promise = task(progress); const promise = task(progress);
@ -101,7 +104,11 @@ export class ProgressController {
return result; return result;
} catch (e) { } catch (e) {
this._aborted(); 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); clearTimeout(timer);
this._state = 'aborted'; this._state = 'aborted';
loggerScope.endScope(`failed`); 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.`; 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) if (!log.length)
return ''; return '';
name = ` ${name} logs `; const header = ` logs `;
const headerLength = 60; const headerLength = 60;
const leftLength = (headerLength - name.length) / 2; const leftLength = (headerLength - header.length) / 2;
const rightLength = headerLength - name.length - leftLength; const rightLength = headerLength - header.length - leftLength;
return `\n${'='.repeat(leftLength)}${name}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`; return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
} }
function monotonicTime(): number { function monotonicTime(): number {

View file

@ -173,43 +173,41 @@ export type PageInitializer = {
isClosed: boolean isClosed: boolean
}; };
export type PageAttribution = { isPage?: boolean };
export interface FrameChannel extends Channel { export interface FrameChannel extends Channel {
on(event: 'loadstate', callback: (params: { add?: types.LifecycleEvent, remove?: types.LifecycleEvent }) => void): this; 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 }>; evalOnSelector(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ value: any }>; evalOnSelectorAll(params: { selector: string; expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
addScriptTag(params: { url?: string, content?: string, type?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }>; addScriptTag(params: { url?: string, content?: string, type?: string }): Promise<{ element: ElementHandleChannel }>;
addStyleTag(params: { url?: string, content?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }>; addStyleTag(params: { url?: string, content?: string }): Promise<{ element: ElementHandleChannel }>;
check(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise<void>; check(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
click(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & PageAttribution): Promise<void>; click(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions): Promise<void>;
content(): Promise<{ value: string }>; content(): Promise<{ value: string }>;
dblclick(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & PageAttribution): Promise<void>; dblclick(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions): Promise<void>;
dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions & PageAttribution): Promise<void>; dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions): Promise<void>;
evaluateExpression(params: { expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ value: any }>; evaluateExpression(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ value: any }>;
evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any} & PageAttribution): Promise<{ handle: JSHandleChannel }>; evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any}): Promise<{ handle: JSHandleChannel }>;
fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions & PageAttribution): Promise<void>; fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions): Promise<void>;
focus(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<void>; focus(params: { selector: string } & types.TimeoutOptions): Promise<void>;
frameElement(): Promise<{ element: ElementHandleChannel }>; frameElement(): Promise<{ element: ElementHandleChannel }>;
getAttribute(params: { selector: string, name: 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 & PageAttribution): Promise<{ response: ResponseChannel | null }>; goto(params: { url: string } & types.GotoOptions): Promise<{ response: ResponseChannel | null }>;
hover(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.TimeoutOptions & PageAttribution): Promise<void>; hover(params: { selector: string, force?: boolean } & types.PointerActionOptions & types.TimeoutOptions): Promise<void>;
innerHTML(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }>; innerHTML(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }>;
innerText(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }>; innerText(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }>;
press(params: { selector: string, key: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise<void>; press(params: { selector: string, key: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
querySelector(params: { selector: string} & PageAttribution): Promise<{ element: ElementHandleChannel | null }>; querySelector(params: { selector: string}): Promise<{ element: ElementHandleChannel | null }>;
querySelectorAll(params: { selector: string} & PageAttribution): Promise<{ elements: ElementHandleChannel[] }>; querySelectorAll(params: { selector: string}): Promise<{ elements: ElementHandleChannel[] }>;
selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise<{ values: string[] }>; selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions): Promise<{ values: string[] }>;
setContent(params: { html: string } & types.NavigateOptions & PageAttribution): Promise<void>; setContent(params: { html: string } & types.NavigateOptions): Promise<void>;
setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: Binary }[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise<void>; setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: Binary }[] } & types.NavigatingActionWaitOptions): Promise<void>;
textContent(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }>; textContent(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string | null }>;
title(): Promise<{ value: string }>; title(): Promise<{ value: string }>;
type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise<void>; type(params: { selector: string, text: string, delay?: number, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions & PageAttribution): Promise<void>; uncheck(params: { selector: string, force?: boolean, noWaitAfter?: boolean } & types.TimeoutOptions): Promise<void>;
waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions & PageAttribution): Promise<{ handle: JSHandleChannel }>; waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }>;
waitForNavigation(params: types.WaitForNavigationOptions & PageAttribution): Promise<{ response: ResponseChannel | null }>; waitForNavigation(params: types.WaitForNavigationOptions): Promise<{ response: ResponseChannel | null }>;
waitForSelector(params: { selector: string } & types.WaitForElementOptions & PageAttribution): Promise<{ element: ElementHandleChannel | null }>; waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element: ElementHandleChannel | null }>;
} }
export type FrameInitializer = { export type FrameInitializer = {
url: string, url: string,

View file

@ -43,30 +43,39 @@ export class BrowserType extends ChannelOwner<BrowserTypeChannel, BrowserTypeIni
async launch(options: types.LaunchOptions & { logger?: LoggerSink } = {}): Promise<Browser> { async launch(options: types.LaunchOptions & { logger?: LoggerSink } = {}): Promise<Browser> {
const logger = options.logger; const logger = options.logger;
options = { ...options, logger: undefined }; options = { ...options, logger: undefined };
const browser = Browser.from((await this._channel.launch(options)).browser); return this._wrapApiCall('browserType.launch', async () => {
browser._logger = logger; const browser = Browser.from((await this._channel.launch(options)).browser);
return browser; browser._logger = logger;
return browser;
}, logger);
} }
async launchServer(options: types.LaunchServerOptions & { logger?: LoggerSink } = {}): Promise<BrowserServer> { async launchServer(options: types.LaunchServerOptions & { logger?: LoggerSink } = {}): Promise<BrowserServer> {
const logger = options.logger;
options = { ...options, logger: undefined }; 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<BrowserContext> { async launchPersistentContext(userDataDir: string, options: types.LaunchOptions & types.BrowserContextOptions & { logger?: LoggerSink } = {}): Promise<BrowserContext> {
const logger = options.logger; const logger = options.logger;
options = { ...options, logger: undefined }; options = { ...options, logger: undefined };
const result = await this._channel.launchPersistentContext({ userDataDir, ...options }); return this._wrapApiCall('browserType.launchPersistentContext', async () => {
const context = BrowserContext.from(result.context); const result = await this._channel.launchPersistentContext({ userDataDir, ...options });
context._logger = logger; const context = BrowserContext.from(result.context);
return context; context._logger = logger;
return context;
}, logger);
} }
async connect(options: types.ConnectOptions & { logger?: LoggerSink }): Promise<Browser> { async connect(options: types.ConnectOptions & { logger?: LoggerSink }): Promise<Browser> {
const logger = options.logger; const logger = options.logger;
options = { ...options, logger: undefined }; options = { ...options, logger: undefined };
const browser = Browser.from((await this._channel.connect(options)).browser); return this._wrapApiCall('browserType.connect', async () => {
browser._logger = logger; const browser = Browser.from((await this._channel.connect(options)).browser);
return browser; browser._logger = logger;
return browser;
}, logger);
} }
} }

View file

@ -19,6 +19,7 @@ import { Channel } from '../channels';
import { Connection } from './connection'; import { Connection } from './connection';
import { assert } from '../../helper'; import { assert } from '../../helper';
import { LoggerSink } from '../../loggerSink'; import { LoggerSink } from '../../loggerSink';
import { rewriteErrorMessage } from '../../utils/stackTrace';
export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}> extends EventEmitter { export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}> extends EventEmitter {
private _connection: Connection; private _connection: Connection;
@ -65,23 +66,7 @@ export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}
return obj.addListener; return obj.addListener;
if (prop === 'removeEventListener') if (prop === 'removeEventListener')
return obj.removeListener; return obj.removeListener;
return (params: any) => this._connection.sendMessageToServer({ guid, method: String(prop), params });
return async (params: any) => {
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;
}
};
}, },
}); });
(this._channel as any)._object = this; (this._channel as any)._object = this;
@ -112,4 +97,21 @@ export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}
objects: this._isScope ? Array.from(this._objects.values()).map(o => o._debugScopeState()) : undefined, objects: this._isScope ? Array.from(this._objects.values()).map(o => o._debugScopeState()) : undefined,
}; };
} }
protected async _wrapApiCall<T>(apiName: string, func: () => Promise<T>, logger?: LoggerSink): Promise<T> {
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;
}
}
} }

View file

@ -43,115 +43,167 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> {
} }
async ownerFrame(): Promise<Frame | null> { async ownerFrame(): Promise<Frame | null> {
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<Frame | null> { async contentFrame(): Promise<Frame | null> {
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<string | null> { async getAttribute(name: string): Promise<string | null> {
return (await this._elementChannel.getAttribute({ name })).value; return this._wrapApiCall('elementHandle.getAttribute', async () => {
return (await this._elementChannel.getAttribute({ name })).value;
});
} }
async textContent(): Promise<string | null> { async textContent(): Promise<string | null> {
return (await this._elementChannel.textContent()).value; return this._wrapApiCall('elementHandle.textContent', async () => {
return (await this._elementChannel.textContent()).value;
});
} }
async innerText(): Promise<string> { async innerText(): Promise<string> {
return (await this._elementChannel.innerText()).value; return this._wrapApiCall('elementHandle.innerText', async () => {
return (await this._elementChannel.innerText()).value;
});
} }
async innerHTML(): Promise<string> { async innerHTML(): Promise<string> {
return (await this._elementChannel.innerHTML()).value; return this._wrapApiCall('elementHandle.innerHTML', async () => {
return (await this._elementChannel.innerHTML()).value;
});
} }
async dispatchEvent(type: string, eventInit: Object = {}) { 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 = {}) { 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<void> { async hover(options: types.PointerActionOptions & types.PointerActionWaitOptions = {}): Promise<void> {
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<void> { async click(options: types.MouseClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
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<void> { async dblclick(options: types.MouseMultiClickOptions & types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}): Promise<void> {
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<string[]> { async selectOption(values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options }); return this._wrapApiCall('elementHandle.selectOption', async () => {
return result.values; const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options });
return result.values;
});
} }
async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> { async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<void> {
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<void> { async selectText(options: types.TimeoutOptions): Promise<void> {
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 = {}) { 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<void> { async focus(): Promise<void> {
await this._elementChannel.focus(); return this._wrapApiCall('elementHandle.focus', async () => {
await this._elementChannel.focus();
});
} }
async type(text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise<void> { async type(text: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise<void> {
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<void> { async press(key: string, options: { delay?: number } & types.NavigatingActionWaitOptions = {}): Promise<void> {
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 = {}) { 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 = {}) { 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<types.Rect | null> { async boundingBox(): Promise<types.Rect | null> {
return (await this._elementChannel.boundingBox()).value; return this._wrapApiCall('elementHandle.boundingBox', async () => {
return (await this._elementChannel.boundingBox()).value;
});
} }
async screenshot(options: types.ElementScreenshotOptions = {}): Promise<Buffer> { async screenshot(options: types.ElementScreenshotOptions = {}): Promise<Buffer> {
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<ElementHandle<Element> | null> { async $(selector: string): Promise<ElementHandle<Element> | null> {
return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element) as ElementHandle<Element> | null; return this._wrapApiCall('elementHandle.$', async () => {
return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element) as ElementHandle<Element> | null;
});
} }
async $$(selector: string): Promise<ElementHandle<Element>[]> { async $$(selector: string): Promise<ElementHandle<Element>[]> {
const result = await this._elementChannel.querySelectorAll({ selector }); return this._wrapApiCall('elementHandle.$$', async () => {
return result.elements.map(h => ElementHandle.from(h) as ElementHandle<Element>); const result = await this._elementChannel.querySelectorAll({ selector });
return result.elements.map(h => ElementHandle.from(h) as ElementHandle<Element>);
});
} }
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>; async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>; async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> { async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); return this._wrapApiCall('elementHandle.$eval', async () => {
return parseResult(result.value); const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
});
} }
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>; async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>; async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> { async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); return this._wrapApiCall('elementHandle.$$eval', async () => {
return parseResult(result.value); const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
});
} }
} }

View file

@ -77,90 +77,121 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
}); });
} }
private _apiName(method: string) {
return this._page!._isPageCall ? 'page.' + method : 'frame.' + method;
}
async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> { async goto(url: string, options: GotoOptions = {}): Promise<network.Response | null> {
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<network.Response | null> { async waitForNavigation(options: types.WaitForNavigationOptions = {}): Promise<network.Response | null> {
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<void> { async waitForLoadState(state: types.LifecycleEvent = 'load', options: types.TimeoutOptions = {}): Promise<void> {
state = verifyLoadState(state); state = verifyLoadState(state);
if (this._loadStates.has(state)) if (this._loadStates.has(state))
return; return;
const timeout = this._page!._timeoutSettings.navigationTimeout(options); return this._wrapApiCall(this._apiName('waitForLoadState'), async () => {
const apiName = this._page!._isPageCall ? 'page.waitForLoadState' : 'frame.waitForLoadState'; const timeout = this._page!._timeoutSettings.navigationTimeout(options);
const waiter = new Waiter(); 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.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.Crash, new Error('Navigation failed because page crashed!'));
waiter.rejectOnEvent<Frame>(this._page!, Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this); waiter.rejectOnEvent<Frame>(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}.`)); waiter.rejectOnTimeout(timeout, new TimeoutError(`Timeout ${timeout}ms exceeded.`));
await waiter.waitForEvent<types.LifecycleEvent>(this._eventEmitter, 'loadstate', s => s === state); await waiter.waitForEvent<types.LifecycleEvent>(this._eventEmitter, 'loadstate', s => s === state);
waiter.dispose(); waiter.dispose();
});
} }
async frameElement(): Promise<ElementHandle> { async frameElement(): Promise<ElementHandle> {
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<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>>; async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>>;
async evaluateHandle<R>(pageFunction: Func1<void, R>, arg?: any): Promise<SmartHandle<R>>; async evaluateHandle<R>(pageFunction: Func1<void, R>, arg?: any): Promise<SmartHandle<R>>;
async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>> { async evaluateHandle<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<SmartHandle<R>> {
assertMaxArguments(arguments.length, 2); 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 this._wrapApiCall(this._apiName('evaluateHandle'), async () => {
return JSHandle.from(result.handle) as SmartHandle<R>; const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return JSHandle.from(result.handle) as SmartHandle<R>;
});
} }
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R>; async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R>;
async evaluate<R>(pageFunction: Func1<void, R>, arg?: any): Promise<R>; async evaluate<R>(pageFunction: Func1<void, R>, arg?: any): Promise<R>;
async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R> { async evaluate<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2); 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 this._wrapApiCall(this._apiName('evaluate'), async () => {
return parseResult(result.value); const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
});
} }
async $(selector: string): Promise<ElementHandle<Element> | null> { async $(selector: string): Promise<ElementHandle<Element> | null> {
const result = await this._channel.querySelector({ selector, isPage: this._page!._isPageCall }); return this._wrapApiCall(this._apiName('$'), async () => {
return ElementHandle.fromNullable(result.element) as ElementHandle<Element> | null; const result = await this._channel.querySelector({ selector });
return ElementHandle.fromNullable(result.element) as ElementHandle<Element> | null;
});
} }
async waitForSelector(selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> { async waitForSelector(selector: string, options: types.WaitForElementOptions = {}): Promise<ElementHandle<Element> | null> {
const result = await this._channel.waitForSelector({ selector, ...options, isPage: this._page!._isPageCall }); return this._wrapApiCall(this._apiName('waitForSelector'), async () => {
return ElementHandle.fromNullable(result.element) as ElementHandle<Element> | null; const result = await this._channel.waitForSelector({ selector, ...options });
return ElementHandle.fromNullable(result.element) as ElementHandle<Element> | null;
});
} }
async dispatchEvent(selector: string, type: string, eventInit?: any, options: types.TimeoutOptions = {}): Promise<void> { async dispatchEvent(selector: string, type: string, eventInit?: any, options: types.TimeoutOptions = {}): Promise<void> {
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<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>; async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R>;
async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>; async $eval<R>(selector: string, pageFunction: FuncOn<Element, void, R>, arg?: any): Promise<R>;
async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> { async $eval<R, Arg>(selector: string, pageFunction: FuncOn<Element, Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3); 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 this._wrapApiCall(this._apiName('$eval'), async () => {
return parseResult(result.value); const result = await this._channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
});
} }
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>; async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R>;
async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>; async $$eval<R>(selector: string, pageFunction: FuncOn<Element[], void, R>, arg?: any): Promise<R>;
async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> { async $$eval<R, Arg>(selector: string, pageFunction: FuncOn<Element[], Arg, R>, arg: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3); 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 this._wrapApiCall(this._apiName('$$eval'), async () => {
return parseResult(result.value); 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<ElementHandle<Element>[]> { async $$(selector: string): Promise<ElementHandle<Element>[]> {
const result = await this._channel.querySelectorAll({ selector, isPage: this._page!._isPageCall }); return this._wrapApiCall(this._apiName('$$'), async () => {
return result.elements.map(e => ElementHandle.from(e) as ElementHandle<Element>); const result = await this._channel.querySelectorAll({ selector });
return result.elements.map(e => ElementHandle.from(e) as ElementHandle<Element>);
});
} }
async content(): Promise<string> { async content(): Promise<string> {
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<void> { async setContent(html: string, options: types.NavigateOptions = {}): Promise<void> {
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 { name(): string {
@ -184,79 +215,113 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
} }
async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string }): Promise<ElementHandle> { async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string }): Promise<ElementHandle> {
const copy = { ...options }; return this._wrapApiCall(this._apiName('addScriptTag'), async () => {
if (copy.path) { const copy = { ...options };
copy.content = (await fsReadFileAsync(copy.path)).toString(); if (copy.path) {
copy.content += '//# sourceURL=' + copy.path.replace(/\n/g, ''); 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 ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element);
});
} }
async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> { async addStyleTag(options: { url?: string; path?: string; content?: string; }): Promise<ElementHandle> {
const copy = { ...options }; return this._wrapApiCall(this._apiName('addStyleTag'), async () => {
if (copy.path) const copy = { ...options };
copy.content = (await fsReadFileAsync(copy.path)).toString(); if (copy.path)
return ElementHandle.from((await this._channel.addStyleTag({ ...options, isPage: this._page!._isPageCall })).element); 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 = {}) { 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 = {}) { 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 = {}) { 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 = {}) { 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<null|string> { async textContent(selector: string, options: types.TimeoutOptions = {}): Promise<null|string> {
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<string> { async innerText(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
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<string> { async innerHTML(selector: string, options: types.TimeoutOptions = {}): Promise<string> {
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<string | null> { async getAttribute(selector: string, name: string, options: types.TimeoutOptions = {}): Promise<string | null> {
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 = {}) { 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<string[]> { async selectOption(selector: string, values: string | ElementHandle | types.SelectOption | string[] | ElementHandle[] | types.SelectOption[] | null, options: types.NavigatingActionWaitOptions = {}): Promise<string[]> {
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<void> { async setInputFiles(selector: string, files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}): Promise<void> {
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 = {}) { 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 = {}) { 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 = {}) { 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 = {}) { 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) { async waitForTimeout(timeout: number) {
@ -266,12 +331,16 @@ export class Frame extends ChannelOwner<FrameChannel, FrameInitializer> {
async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>; async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>;
async waitForFunction<R>(pageFunction: Func1<void, R>, arg?: any, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>; async waitForFunction<R>(pageFunction: Func1<void, R>, arg?: any, options?: types.WaitForFunctionOptions): Promise<SmartHandle<R>>;
async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options: types.WaitForFunctionOptions = {}): Promise<SmartHandle<R>> { async waitForFunction<R, Arg>(pageFunction: Func1<Arg, R>, arg: Arg, options: types.WaitForFunctionOptions = {}): Promise<SmartHandle<R>> {
const result = await this._channel.waitForFunction({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), ...options, isPage: this._page!._isPageCall }); return this._wrapApiCall(this._apiName('waitForFunction'), async () => {
return JSHandle.from(result.handle) as SmartHandle<R>; const result = await this._channel.waitForFunction({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg), ...options });
return JSHandle.from(result.handle) as SmartHandle<R>;
});
} }
async title(): Promise<string> { async title(): Promise<string> {
return (await this._channel.title()).value; return this._wrapApiCall(this._apiName('title'), async () => {
return (await this._channel.title()).value;
});
} }
} }

View file

@ -391,7 +391,9 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
} }
async screenshot(options: types.ScreenshotOptions = {}): Promise<Buffer> { async screenshot(options: types.ScreenshotOptions = {}): Promise<Buffer> {
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<string> { async title(): Promise<string> {

View file

@ -19,6 +19,9 @@ import { DispatcherConnection } from './server/dispatcher';
import { Playwright } from '../server/playwright'; import { Playwright } from '../server/playwright';
import { PlaywrightDispatcher } from './server/playwrightDispatcher'; import { PlaywrightDispatcher } from './server/playwrightDispatcher';
import { Electron } from '../server/electron'; import { Electron } from '../server/electron';
import { setUseApiName } from '../progress';
setUseApiName(false);
const dispatcherConnection = new DispatcherConnection(); const dispatcherConnection = new DispatcherConnection();
const transport = new Transport(process.stdout, process.stdin); const transport = new Transport(process.stdout, process.stdin);

View file

@ -16,7 +16,7 @@
import { Frame, kAddLifecycleEvent, kRemoveLifecycleEvent } from '../../frames'; import { Frame, kAddLifecycleEvent, kRemoveLifecycleEvent } from '../../frames';
import * as types from '../../types'; 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 { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
import { convertSelectOptionValues, ElementHandleDispatcher, createHandle, convertInputFiles } from './elementHandlerDispatcher'; import { convertSelectOptionValues, ElementHandleDispatcher, createHandle, convertInputFiles } from './elementHandlerDispatcher';
import { parseArgument, serializeResult } from './jsHandleDispatcher'; import { parseArgument, serializeResult } from './jsHandleDispatcher';
@ -46,58 +46,48 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
}); });
} }
async goto(params: { url: string } & types.GotoOptions & PageAttribution): Promise<{ response: ResponseChannel | null }> { async goto(params: { url: string } & types.GotoOptions): Promise<{ response: ResponseChannel | null }> {
const target = params.isPage ? this._frame._page : this._frame; return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._frame.goto(params.url, params)) };
return { response: lookupNullableDispatcher<ResponseDispatcher>(await target.goto(params.url, params)) };
} }
async waitForNavigation(params: types.WaitForNavigationOptions & PageAttribution): Promise<{ response: ResponseChannel | null }> { async waitForNavigation(params: types.WaitForNavigationOptions): Promise<{ response: ResponseChannel | null }> {
const target = params.isPage ? this._frame._page : this._frame; return { response: lookupNullableDispatcher<ResponseDispatcher>(await this._frame.waitForNavigation(params)) };
return { response: lookupNullableDispatcher<ResponseDispatcher>(await target.waitForNavigation(params)) };
} }
async frameElement(): Promise<{ element: ElementHandleChannel }> { async frameElement(): Promise<{ element: ElementHandleChannel }> {
return { element: new ElementHandleDispatcher(this._scope, await this._frame.frameElement()) }; return { element: new ElementHandleDispatcher(this._scope, await this._frame.frameElement()) };
} }
async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any } & PageAttribution): Promise<{ value: any }> { async evaluateExpression(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
const target = params.isPage ? this._frame._page : this._frame; return { value: serializeResult(await this._frame._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))) };
return { value: serializeResult(await target._evaluateExpression(params.expression, params.isFunction, parseArgument(params.arg))) };
} }
async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any } & PageAttribution): Promise<{ handle: JSHandleChannel }> { async evaluateExpressionHandle(params: { expression: string, isFunction: boolean, arg: any }): Promise<{ handle: JSHandleChannel }> {
const target = params.isPage ? this._frame._page : this._frame; return { handle: createHandle(this._scope, await this._frame._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) };
return { handle: createHandle(this._scope, await target._evaluateExpressionHandle(params.expression, params.isFunction, parseArgument(params.arg))) };
} }
async waitForSelector(params: { selector: string } & types.WaitForElementOptions & PageAttribution): Promise<{ element: ElementHandleChannel | null }> { async waitForSelector(params: { selector: string } & types.WaitForElementOptions): Promise<{ element: ElementHandleChannel | null }> {
const target = params.isPage ? this._frame._page : this._frame; return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.waitForSelector(params.selector, params)) };
return { element: ElementHandleDispatcher.createNullable(this._scope, await target.waitForSelector(params.selector, params)) };
} }
async dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions & PageAttribution): Promise<void> { async dispatchEvent(params: { selector: string, type: string, eventInit: any } & types.TimeoutOptions): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; return this._frame.dispatchEvent(params.selector, params.type, parseArgument(params.eventInit), params);
return target.dispatchEvent(params.selector, params.type, parseArgument(params.eventInit), params);
} }
async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: any } & PageAttribution): Promise<{ value: any }> { async evalOnSelector(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
const target = params.isPage ? this._frame._page : this._frame; return { value: serializeResult(await this._frame._$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
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 } & PageAttribution): Promise<{ value: any }> { async evalOnSelectorAll(params: { selector: string, expression: string, isFunction: boolean, arg: any }): Promise<{ value: any }> {
const target = params.isPage ? this._frame._page : this._frame; return { value: serializeResult(await this._frame._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
return { value: serializeResult(await target._$$evalExpression(params.selector, params.expression, params.isFunction, parseArgument(params.arg))) };
} }
async querySelector(params: { selector: string } & PageAttribution): Promise<{ element: ElementHandleChannel | null }> { async querySelector(params: { selector: string }): Promise<{ element: ElementHandleChannel | null }> {
const target = params.isPage ? this._frame._page : this._frame; return { element: ElementHandleDispatcher.createNullable(this._scope, await this._frame.$(params.selector)) };
return { element: ElementHandleDispatcher.createNullable(this._scope, await target.$(params.selector)) };
} }
async querySelectorAll(params: { selector: string } & PageAttribution): Promise<{ elements: ElementHandleChannel[] }> { async querySelectorAll(params: { selector: string }): Promise<{ elements: ElementHandleChannel[] }> {
const target = params.isPage ? this._frame._page : this._frame; const elements = await this._frame.$$(params.selector);
const elements = await target.$$(params.selector);
return { elements: elements.map(e => new ElementHandleDispatcher(this._scope, e)) }; return { elements: elements.map(e => new ElementHandleDispatcher(this._scope, e)) };
} }
@ -105,99 +95,80 @@ export class FrameDispatcher extends Dispatcher<Frame, FrameInitializer> impleme
return { value: await this._frame.content() }; return { value: await this._frame.content() };
} }
async setContent(params: { html: string } & types.NavigateOptions & PageAttribution): Promise<void> { async setContent(params: { html: string } & types.NavigateOptions): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.setContent(params.html, params);
await target.setContent(params.html, params);
} }
async addScriptTag(params: { url?: string, content?: string, type?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }> { async addScriptTag(params: { url?: string, content?: string, type?: string }): Promise<{ element: ElementHandleChannel }> {
const target = params.isPage ? this._frame._page : this._frame; return { element: new ElementHandleDispatcher(this._scope, await this._frame.addScriptTag(params)) };
return { element: new ElementHandleDispatcher(this._scope, await target.addScriptTag(params)) };
} }
async addStyleTag(params: { url?: string, content?: string } & PageAttribution): Promise<{ element: ElementHandleChannel }> { async addStyleTag(params: { url?: string, content?: string }): Promise<{ element: ElementHandleChannel }> {
const target = params.isPage ? this._frame._page : this._frame; return { element: new ElementHandleDispatcher(this._scope, await this._frame.addStyleTag(params)) };
return { element: new ElementHandleDispatcher(this._scope, await target.addStyleTag(params)) };
} }
async click(params: { selector: string } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } & PageAttribution): Promise<void> { async click(params: { selector: string } & types.PointerActionOptions & types.MouseClickOptions & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean }): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.click(params.selector, params);
await target.click(params.selector, params);
} }
async dblclick(params: { selector: string } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean } & PageAttribution): Promise<void> { async dblclick(params: { selector: string } & types.PointerActionOptions & types.MouseMultiClickOptions & types.TimeoutOptions & { force?: boolean }): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.dblclick(params.selector, params);
await target.dblclick(params.selector, params);
} }
async fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions & PageAttribution): Promise<void> { async fill(params: { selector: string, value: string } & types.NavigatingActionWaitOptions): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.fill(params.selector, params.value, params);
await target.fill(params.selector, params.value, params);
} }
async focus(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<void> { async focus(params: { selector: string } & types.TimeoutOptions): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.focus(params.selector, params);
await target.focus(params.selector, params);
} }
async textContent(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }> { async textContent(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string | null }> {
const target = params.isPage ? this._frame._page : this._frame; return { value: await this._frame.textContent(params.selector, params) };
return { value: await target.textContent(params.selector, params) };
} }
async innerText(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }> { async innerText(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }> {
const target = params.isPage ? this._frame._page : this._frame; return { value: await this._frame.innerText(params.selector, params) };
return { value: await target.innerText(params.selector, params) };
} }
async innerHTML(params: { selector: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string }> { async innerHTML(params: { selector: string } & types.TimeoutOptions): Promise<{ value: string }> {
const target = params.isPage ? this._frame._page : this._frame; return { value: await this._frame.innerHTML(params.selector, params) };
return { value: await target.innerHTML(params.selector, params) };
} }
async getAttribute(params: { selector: string, name: string } & types.TimeoutOptions & PageAttribution): Promise<{ value: string | null }> { async getAttribute(params: { selector: string, name: string } & types.TimeoutOptions): Promise<{ value: string | null }> {
const target = params.isPage ? this._frame._page : this._frame; return { value: await this._frame.getAttribute(params.selector, params.name, params) };
return { value: await target.getAttribute(params.selector, params.name, params) };
} }
async hover(params: { selector: string } & types.PointerActionOptions & types.TimeoutOptions & { force?: boolean } & PageAttribution): Promise<void> { async hover(params: { selector: string } & types.PointerActionOptions & types.TimeoutOptions & { force?: boolean }): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.hover(params.selector, params);
await target.hover(params.selector, params);
} }
async selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions & PageAttribution): Promise<{ values: string[] }> { async selectOption(params: { selector: string, elements?: ElementHandleChannel[], options?: types.SelectOption[] } & types.NavigatingActionWaitOptions): Promise<{ values: string[] }> {
const target = params.isPage ? this._frame._page : this._frame; return { values: await this._frame.selectOption(params.selector, convertSelectOptionValues(params.elements, params.options), params) };
return { values: await target.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<void> { async setInputFiles(params: { selector: string, files: { name: string, mimeType: string, buffer: string }[] } & types.NavigatingActionWaitOptions): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.setInputFiles(params.selector, convertInputFiles(params.files), params);
await target.setInputFiles(params.selector, convertInputFiles(params.files), params);
} }
async type(params: { selector: string, text: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } & PageAttribution): Promise<void> { async type(params: { selector: string, text: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean }): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.type(params.selector, params.text, params);
await target.type(params.selector, params.text, params);
} }
async press(params: { selector: string, key: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean } & PageAttribution): Promise<void> { async press(params: { selector: string, key: string } & { delay?: number | undefined } & types.TimeoutOptions & { noWaitAfter?: boolean }): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.press(params.selector, params.key, params);
await target.press(params.selector, params.key, params);
} }
async check(params: { selector: string } & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } & PageAttribution): Promise<void> { async check(params: { selector: string } & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean }): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.check(params.selector, params);
await target.check(params.selector, params);
} }
async uncheck(params: { selector: string } & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean } & PageAttribution): Promise<void> { async uncheck(params: { selector: string } & types.TimeoutOptions & { force?: boolean } & { noWaitAfter?: boolean }): Promise<void> {
const target = params.isPage ? this._frame._page : this._frame; await this._frame.uncheck(params.selector, params);
await target.uncheck(params.selector, params);
} }
async waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions & PageAttribution): Promise<{ handle: JSHandleChannel }> { async waitForFunction(params: { expression: string, isFunction: boolean, arg: any } & types.WaitForFunctionOptions): Promise<{ handle: JSHandleChannel }> {
const target = params.isPage ? this._frame._page : this._frame; return { handle: createHandle(this._scope, await this._frame._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params)) };
return { handle: createHandle(this._scope, await target._waitForFunctionExpression(params.expression, params.isFunction, parseArgument(params.arg), params)) };
} }
async title(): Promise<{ value: string }> { async title(): Promise<{ value: string }> {

View file

@ -191,7 +191,7 @@ describe('Auto waiting', () => {
await page.setContent(`<a href="${server.PREFIX + '/frames/one-frame.html'}">click me</a>`); await page.setContent(`<a href="${server.PREFIX + '/frames/one-frame.html'}">click me</a>`);
const __testHookAfterPointerAction = () => new Promise(f => setTimeout(f, 6000)); const __testHookAfterPointerAction = () => new Promise(f => setTimeout(f, 6000));
const error = await page.click('a', { timeout: 5000, __testHookAfterPointerAction }).catch(e => e); 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('waiting for scheduled navigations to finish');
expect(error.message).toContain(`navigated to "${server.PREFIX + '/frames/one-frame.html'}"`); expect(error.message).toContain(`navigated to "${server.PREFIX + '/frames/one-frame.html'}"`);
}); });

View file

@ -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); 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. 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(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}) => { it('should click the button after navigation ', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); 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.goto(server.PREFIX + '/input/button.html');
await page.$eval('button', b => b.style.display = 'none'); await page.$eval('button', b => b.style.display = 'none');
const error = await page.click('button', { timeout: 5000 }).catch(e => e); 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('waiting for element to be visible, enabled and not moving');
expect(error.message).toContain('element is not visible - waiting'); 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.goto(server.PREFIX + '/input/button.html');
await page.$eval('button', b => b.style.visibility = 'hidden'); await page.$eval('button', b => b.style.visibility = 'hidden');
const error = await page.click('button', { timeout: 5000 }).catch(e => e); 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('waiting for element to be visible, enabled and not moving');
expect(error.message).toContain('element is not visible - waiting'); expect(error.message).toContain('element is not visible - waiting');
}); });
@ -440,7 +440,7 @@ describe('Page.click', function() {
button.style.marginLeft = '200px'; button.style.marginLeft = '200px';
}); });
const error = await button.click({ timeout: 5000 }).catch(e => e); 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('waiting for element to be visible, enabled and not moving');
expect(error.message).toContain('element is moving - waiting'); expect(error.message).toContain('element is moving - waiting');
}); });
@ -489,9 +489,9 @@ describe('Page.click', function() {
document.body.appendChild(blocker); document.body.appendChild(blocker);
}); });
const error = await button.click({ timeout: 5000 }).catch(e => e); 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('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}) => { it('should fail when obscured and not waiting for hit target', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html'); await page.goto(server.PREFIX + '/input/button.html');
@ -525,7 +525,7 @@ describe('Page.click', function() {
await page.setContent('<button onclick="javascript:window.__CLICKED=true;" disabled><span>Click target</span></button>'); await page.setContent('<button onclick="javascript:window.__CLICKED=true;" disabled><span>Click target</span></button>');
const error = await page.click('text=Click target', { timeout: 3000 }).catch(e => e); const error = await page.click('text=Click target', { timeout: 3000 }).catch(e => e);
expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined); 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'); expect(error.message).toContain('element is disabled - waiting');
}); });
it('should wait for input to be enabled', async({page, server}) => { it('should wait for input to be enabled', async({page, server}) => {
@ -720,9 +720,9 @@ describe('Page.click', function() {
const error = await promise; const error = await promise;
expect(clicked).toBe(false); expect(clicked).toBe(false);
expect(await page.evaluate(() => window.clicked)).toBe(undefined); 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('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}) => { it('should dispatch microtasks in order', async({page, server}) => {
await page.setContent(` await page.setContent(`
@ -775,7 +775,7 @@ describe('Page.click', function() {
const error = await page.dblclick('text=button1', { __testHookAfterStable, timeout: 3000 }).catch(e => e); 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.button1)).toBe(undefined);
expect(await page.evaluate(() => window.button2)).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'); 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}) => { 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); const error = await handle.click({ __testHookBeforeStable, timeout: 3000 }).catch(e => e);
expect(await page.evaluate(() => window.button1)).toBe(undefined); expect(await page.evaluate(() => window.button1)).toBe(undefined);
expect(await page.evaluate(() => window.button2)).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'); expect(error.message).toContain('element is disabled - waiting');
}); });
it('should not retarget when element changes on hover', async ({page, server}) => { 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'); const dialogPromise = page.waitForEvent('dialog');
await page.setContent(`<div onclick='window.alert(123)'>Click me</div>`); await page.setContent(`<div onclick='window.alert(123)'>Click me</div>`);
const error = await page.click('div', { timeout: 3000 }).catch(e => e); 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; const dialog = await dialogPromise;
await dialog.dismiss(); await dialog.dismiss();
}); });

View file

@ -356,7 +356,7 @@ describe('launchPersistentContext()', function() {
const userDataDir = await makeUserDataDir(); const userDataDir = await makeUserDataDir();
const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) }; const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) };
const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e); 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); await removeUserDataDir(userDataDir);
}); });
it.skip(USES_HOOKS)('should handle exception', async({browserType, defaultBrowserOptions}) => { it.skip(USES_HOOKS)('should handle exception', async({browserType, defaultBrowserOptions}) => {

View file

@ -25,6 +25,7 @@ const { DispatcherConnection } = require('../lib/rpc/server/dispatcher');
const { Connection } = require('../lib/rpc/client/connection'); const { Connection } = require('../lib/rpc/client/connection');
const { Transport } = require('../lib/rpc/transport'); const { Transport } = require('../lib/rpc/transport');
const { PlaywrightDispatcher } = require('../lib/rpc/server/playwrightDispatcher'); const { PlaywrightDispatcher } = require('../lib/rpc/server/playwrightDispatcher');
const { setUseApiName } = require('../lib/progress');
class ServerEnvironment { class ServerEnvironment {
async beforeAll(state) { async beforeAll(state) {
@ -167,6 +168,7 @@ class PlaywrightEnvironment {
async beforeAll(state) { async beforeAll(state) {
if (process.env.PWCHANNEL) { if (process.env.PWCHANNEL) {
setUseApiName(false);
const connection = new Connection(); const connection = new Connection();
if (process.env.PWCHANNEL === 'wire') { if (process.env.PWCHANNEL === 'wire') {
this.spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'rpc', 'server'), [], { this.spawnedProcess = childProcess.fork(path.join(__dirname, '..', 'lib', 'rpc', 'server'), [], {

View file

@ -611,7 +611,7 @@ describe('Frame.evaluate', function() {
const childResult = await childFrame.evaluate(() => window.__foo); const childResult = await childFrame.evaluate(() => window.__foo);
expect(childResult).toEqual({ bar: 'baz' }); expect(childResult).toEqual({ bar: 'baz' });
const error = await childFrame.evaluate(foo => foo.bar, handle).catch(e => e); 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}) => { it('should allow cross-frame element handles', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html'); await page.goto(server.PREFIX + '/frames/one-frame.html');

View file

@ -53,7 +53,7 @@ describe('Frame.frameElement', function() {
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await page.$eval('#frame1', e => e.remove()); await page.$eval('#frame1', e => e.remove());
const error = await frame1.frameElement().catch(e => e); const error = await frame1.frameElement().catch(e => e);
expect(error.message).toBe('Frame has been detached.'); expect(error.message).toContain('Frame has been detached.');
}); });
}); });

View file

@ -62,7 +62,7 @@ describe('Page.evaluateHandle', function() {
const a = { x: 1 }; const a = { x: 1 };
a.y = a; a.y = a;
const error = await page.evaluate(x => x, a).catch(e => e); 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}) => { it('should accept same handle multiple times', async({page, server}) => {
const foo = await page.evaluateHandle(() => 1); const foo = await page.evaluateHandle(() => 1);

View file

@ -48,7 +48,7 @@ describe('Playwright', function() {
const options = Object.assign({}, defaultBrowserOptions, {executablePath: path.join(__dirname, 'assets', 'dummy_bad_browser_executable.js')}); const options = Object.assign({}, defaultBrowserOptions, {executablePath: path.join(__dirname, 'assets', 'dummy_bad_browser_executable.js')});
let waitError = null; let waitError = null;
await browserType.launch(options).catch(e => waitError = e); 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}) => { it('should reject if executable path is invalid', async({browserType, defaultBrowserOptions}) => {
let waitError = null; let waitError = null;
@ -59,7 +59,7 @@ describe('Playwright', function() {
it.skip(USES_HOOKS)('should handle timeout', async({browserType, defaultBrowserOptions}) => { it.skip(USES_HOOKS)('should handle timeout', async({browserType, defaultBrowserOptions}) => {
const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) }; const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) };
const error = await browserType.launch(options).catch(e => e); 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] <launching>`); expect(error.message).toContain(`[browser] <launching>`);
expect(error.message).toContain(`[browser] <launched> pid=`); expect(error.message).toContain(`[browser] <launched> pid=`);
}); });

View file

@ -29,13 +29,8 @@ describe('Logger', function() {
await browser.close(); await browser.close();
expect(log.length > 0).toBeTruthy(); expect(log.length > 0).toBeTruthy();
expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy(); expect(log.filter(item => item.severity === 'info').length > 0).toBeTruthy();
if (CHANNEL) { expect(log.filter(item => item.message.includes('browserType.launch started')).length > 0).toBeTruthy();
expect(log.filter(item => item.message.includes('browser.newContext started')).length > 0).toBeTruthy(); expect(log.filter(item => item.message.includes('browserType.launch succeeded')).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();
}
}); });
it('should log context-level', async({browserType, defaultBrowserOptions}) => { it('should log context-level', async({browserType, defaultBrowserOptions}) => {
const log = []; const log = [];
@ -52,11 +47,7 @@ describe('Logger', function() {
await browser.close(); await browser.close();
expect(log.length > 0).toBeTruthy(); expect(log.length > 0).toBeTruthy();
if (CHANNEL) { expect(log.filter(item => item.message.includes('page.setContent')).length > 0).toBeTruthy();
expect(log.filter(item => item.message.includes('context.newPage')).length > 0).toBeTruthy(); expect(log.filter(item => item.message.includes('page.click')).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();
}
}); });
}); });

View file

@ -198,7 +198,7 @@ describe('Page.goto', function() {
server.setRoute('/empty.html', (req, res) => { }); server.setRoute('/empty.html', (req, res) => { });
let error = null; let error = null;
await page.goto(server.PREFIX + '/empty.html', {timeout: 1}).catch(e => error = e); 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.message).toContain(server.PREFIX + '/empty.html');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });
@ -209,7 +209,7 @@ describe('Page.goto', function() {
page.context().setDefaultNavigationTimeout(2); page.context().setDefaultNavigationTimeout(2);
page.setDefaultNavigationTimeout(1); page.setDefaultNavigationTimeout(1);
await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); 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.message).toContain(server.PREFIX + '/empty.html');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });
@ -219,7 +219,7 @@ describe('Page.goto', function() {
let error = null; let error = null;
page.context().setDefaultNavigationTimeout(2); page.context().setDefaultNavigationTimeout(2);
await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); 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.message).toContain(server.PREFIX + '/empty.html');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });
@ -230,7 +230,7 @@ describe('Page.goto', function() {
page.context().setDefaultTimeout(2); page.context().setDefaultTimeout(2);
page.setDefaultTimeout(1); page.setDefaultTimeout(1);
await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); 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.message).toContain(server.PREFIX + '/empty.html');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });
@ -240,7 +240,7 @@ describe('Page.goto', function() {
let error = null; let error = null;
page.context().setDefaultTimeout(2); page.context().setDefaultTimeout(2);
await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); 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.message).toContain(server.PREFIX + '/empty.html');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });
@ -251,7 +251,7 @@ describe('Page.goto', function() {
page.setDefaultTimeout(0); page.setDefaultTimeout(0);
page.setDefaultNavigationTimeout(1); page.setDefaultNavigationTimeout(1);
await page.goto(server.PREFIX + '/empty.html').catch(e => error = e); 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.message).toContain(server.PREFIX + '/empty.html');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); 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 }); const promise = page.waitForNavigation({ url: '**/frame.html', timeout: 5000 });
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const error = await promise.catch(e => e); 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('waiting for navigation to "**/frame.html" until "load"');
expect(error.message).toContain(`navigated to "${server.EMPTY_PAGE}"`); 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); server.setRoute('/one-style.css', (req, res) => response = res);
await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'});
const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e); 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}) => { it('should resolve immediately if loaded', async({page, server}) => {
await page.goto(server.PREFIX + '/one-style.html'); await page.goto(server.PREFIX + '/one-style.html');
@ -971,7 +971,7 @@ describe('Frame.goto', function() {
server.setRoute('/frames/script.js', () => {}); server.setRoute('/frames/script.js', () => {});
const url = server.PREFIX + '/frames/child-redirect.html'; const url = server.PREFIX + '/frames/child-redirect.html';
const error = await page.goto(url, { timeout: 5000, waitUntil: 'networkidle' }).catch(e => e); 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"`); expect(error.message).toContain(`navigating to "${url}", waiting until "networkidle"`);
}); });
it('should return matching responses', async({page, server}) => { it('should return matching responses', async({page, server}) => {

View file

@ -662,7 +662,7 @@ describe('Page.setContent', function() {
// stall for image // stall for image
server.setRoute(imgPath, (req, res) => {}); server.setRoute(imgPath, (req, res) => {});
const error = await page.setContent(`<img src="${server.PREFIX + imgPath}"></img>`).catch(e => e); const error = await page.setContent(`<img src="${server.PREFIX + imgPath}"></img>`).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); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });
it('should await resources to load', async({page, server}) => { it('should await resources to load', async({page, server}) => {
@ -707,7 +707,7 @@ describe('Page.addScriptTag', function() {
} catch (e) { } catch (e) {
error = 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}) => { it('should work with a url', async({page, server}) => {
@ -799,7 +799,7 @@ describe('Page.addStyleTag', function() {
} catch (e) { } catch (e) {
error = 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}) => { it('should work with a url', async({page, server}) => {

View file

@ -120,11 +120,11 @@ describe('Page.$eval', function() {
}); });
it('should throw on multiple * captures', async({page, server}) => { it('should throw on multiple * captures', async({page, server}) => {
const error = await page.$eval('*css=div >> *css=span', e => e.outerHTML).catch(e => e); 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}) => { it('should throw on malformed * capture', async({page, server}) => {
const error = await page.$eval('*=div', e => e.outerHTML).catch(e => e); 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}) => { it('should work with spaces in css attributes', async({page, server}) => {
await page.setContent('<div><input placeholder="Select date"></div>'); await page.setContent('<div><input placeholder="Select date"></div>');
@ -378,7 +378,7 @@ describe('ElementHandle.$eval', function() {
await page.setContent(htmlContent); await page.setContent(htmlContent);
const elementHandle = await page.$('#myId'); const elementHandle = await page.$('#myId');
const errorMessage = await elementHandle.$eval('.a', node => node.innerText).catch(error => error.message); 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() { describe('ElementHandle.$$eval', function() {
@ -775,7 +775,7 @@ describe('selectors.register', () => {
// Selector names are case-sensitive. // Selector names are case-sensitive.
const error = await page.$('tAG=DIV').catch(e => e); 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}) => { it('should work with path', async ({playwright, page}) => {
await utils.registerEngine(playwright, 'foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') }); 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}) => { it('should handle errors', async ({playwright, page}) => {
let error = await page.$('neverregister=ignored').catch(e => e); 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 = () => ({ const createDummySelector = () => ({
create(root, target) { create(root, target) {

View file

@ -379,7 +379,7 @@ describe.skip(ffheadful)('ElementHandle.screenshot', function() {
await page.setContent('<div style="width: 50px; height: 0"></div>'); await page.setContent('<div style="width: 50px; height: 0"></div>');
const div = await page.$('div'); const div = await page.$('div');
const error = await div.screenshot({ timeout: 3000 }).catch(e => e); 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'); expect(error.message).toContain('element is not visible');
}); });
it('should wait for visible', async({page, server, golden}) => { 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'); await page.goto(server.PREFIX + '/grid.html');
const __testHookAfterScreenshot = () => new Promise(f => setTimeout(f, 5000)); const __testHookAfterScreenshot = () => new Promise(f => setTimeout(f, 5000));
const error = await page.screenshot({ fullPage: true, __testHookAfterScreenshot, timeout: 3000 }).catch(e => e); 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 utils.verifyViewport(page, 350, 360);
await page.setViewportSize({ width: 400, height: 400 }); await page.setViewportSize({ width: 400, height: 400 });
await page.waitForTimeout(3000); // Give it some time to wrongly restore previous viewport. await page.waitForTimeout(3000); // Give it some time to wrongly restore previous viewport.

View file

@ -69,12 +69,12 @@ describe('Frame.waitForFunction', function() {
const savedCounter = counter; const savedCounter = counter;
await page.waitForTimeout(2000); // Give it some time to produce more logs. 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); expect(counter).toBe(savedCounter);
}); });
it('should throw on polling:mutation', async({page, server}) => { it('should throw on polling:mutation', async({page, server}) => {
const error = await page.waitForFunction(() => true, {}, {polling: 'mutation'}).catch(e => e); 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}) => { it('should poll on raf', async({page, server}) => {
const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'}); const watchdog = page.waitForFunction(() => window.__FOO === 'hit', {}, {polling: 'raf'});
@ -147,7 +147,7 @@ describe('Frame.waitForFunction', function() {
let error = null; let error = null;
await page.waitForFunction('false', {}, {timeout: 10}).catch(e => error = e); await page.waitForFunction('false', {}, {timeout: 10}).catch(e => error = e);
expect(error).toBeTruthy(); 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); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });
it('should respect default timeout', async({page}) => { it('should respect default timeout', async({page}) => {
@ -155,7 +155,7 @@ describe('Frame.waitForFunction', function() {
let error = null; let error = null;
await page.waitForFunction('false').catch(e => error = e); await page.waitForFunction('false').catch(e => error = e);
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); 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}) => { it('should disable timeout when its set to 0', async({page}) => {
const watchdog = page.waitForFunction(() => { const watchdog = page.waitForFunction(() => {
@ -203,7 +203,7 @@ describe('Frame.waitForSelector', function() {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
let error; let error;
await page.waitForSelector('*', { waitFor: 'attached' }).catch(e => error = e); 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}) => { it('should tolerate waitFor=visible', async({page, server}) => {
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
@ -262,7 +262,7 @@ describe('Frame.waitForSelector', function() {
await giveItTimeToLog(frame); await giveItTimeToLog(frame);
const error = await watchdog.catch(e => e); 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(`waiting for selector "div" to be visible`);
expect(error.message).toContain(`selector resolved to hidden <div id="mydiv" class="foo bar" foo="1234567890123456…>abcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvw…</div>`); expect(error.message).toContain(`selector resolved to hidden <div id="mydiv" class="foo bar" foo="1234567890123456…>abcdefghijklmnopqrstuvwyxzabcdefghijklmnopqrstuvw…</div>`);
expect(error.message).toContain(`selector did not resolve to any element`); expect(error.message).toContain(`selector did not resolve to any element`);
@ -292,7 +292,7 @@ describe('Frame.waitForSelector', function() {
await giveItTimeToLog(frame); await giveItTimeToLog(frame);
const error = await watchdog.catch(e => e); 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 hidden`); expect(error.message).toContain(`waiting for selector "div" to be hidden`);
expect(error.message).toContain(`selector resolved to visible <div id="mydiv" class="foo bar">hello</div>`); expect(error.message).toContain(`selector resolved to visible <div id="mydiv" class="foo bar">hello</div>`);
expect(error.message).toContain(`selector resolved to visible <div class="another">hello</div>`); expect(error.message).toContain(`selector resolved to visible <div class="another">hello</div>`);
@ -377,10 +377,10 @@ describe('Frame.waitForSelector', function() {
it('should not consider visible when zero-sized', async({page, server}) => { it('should not consider visible when zero-sized', async({page, server}) => {
await page.setContent(`<div style='width: 0; height: 0;'>1</div>`); await page.setContent(`<div style='width: 0; height: 0;'>1</div>`);
let error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e); 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'); await page.evaluate(() => document.querySelector('div').style.width = '10px');
error = await page.waitForSelector('div', { timeout: 1000 }).catch(e => e); 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'); await page.evaluate(() => document.querySelector('div').style.height = '10px');
expect(await page.waitForSelector('div', { timeout: 1000 })).toBeTruthy(); expect(await page.waitForSelector('div', { timeout: 1000 })).toBeTruthy();
}); });
@ -433,7 +433,7 @@ describe('Frame.waitForSelector', function() {
let error = null; let error = null;
await page.waitForSelector('div', { timeout: 3000, state: 'attached' }).catch(e => error = e); await page.waitForSelector('div', { timeout: 3000, state: 'attached' }).catch(e => error = e);
expect(error).toBeTruthy(); 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.message).toContain('waiting for selector "div"');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });
@ -442,7 +442,7 @@ describe('Frame.waitForSelector', function() {
let error = null; let error = null;
await page.waitForSelector('div', { state: 'hidden', timeout: 1000 }).catch(e => error = e); await page.waitForSelector('div', { state: 'hidden', timeout: 1000 }).catch(e => error = e);
expect(error).toBeTruthy(); 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'); expect(error.message).toContain('waiting for selector "div" to be hidden');
}); });
it('should respond to node attribute mutation', async({page, server}) => { 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}) => { it('should throw for visibility option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
const error = await page.waitForSelector('section', { visibility: 'hidden' }).catch(e => e); 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}) => { it('should throw for true state option', async({page, server}) => {
await page.setContent('<section>test</section>'); await page.setContent('<section>test</section>');
@ -523,7 +523,7 @@ describe('Frame.waitForSelector xpath', function() {
let error = null; let error = null;
await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e); await page.waitForSelector('//div', { state: 'attached', timeout: 3000 }).catch(e => error = e);
expect(error).toBeTruthy(); 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.message).toContain('waiting for selector "//div"');
expect(error).toBeInstanceOf(playwright.errors.TimeoutError); expect(error).toBeInstanceOf(playwright.errors.TimeoutError);
}); });