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;
while (progress.isRunning()) {
progress.logger.info(`${first ? 'attempting' : 'retrying'} ${progress.apiName} action`);
const result = await this._performPointerAction(progress, action, options);
progress.logger.info(`${first ? 'attempting' : 'retrying'} ${actionName} action`);
const result = await this._performPointerAction(progress, actionName, action, options);
first = false;
if (result === 'error:notvisible') {
if (options.force)
@ -316,7 +316,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
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;
if ((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;
if (options && options.modifiers)
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
progress.logger.info(` performing ${progress.apiName} action`);
progress.logger.info(` performing ${actionName} action`);
await action(point);
progress.logger.info(` ${progress.apiName} action done`);
progress.logger.info(` ${actionName} action done`);
progress.logger.info(' waiting for scheduled navigations to finish');
if ((options as any).__testHookAfterPointerAction)
await (options as any).__testHookAfterPointerAction();
@ -379,7 +379,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
}
_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> {
@ -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'> {
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> {
@ -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'> {
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[]> {

View File

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

View File

@ -20,7 +20,6 @@ import { assert } from './helper';
import { rewriteErrorMessage } from './utils/stackTrace';
export interface Progress {
readonly apiName: string;
readonly aborted: Promise<void>;
readonly logger: Logger;
timeUntilDeadline(): number;
@ -34,6 +33,11 @@ export async function runAbortableTask<T>(task: (progress: Progress) => Promise<
return controller.run(task);
}
let useApiName = true;
export function setUseApiName(value: boolean) {
useApiName = value;
}
export class ProgressController {
// Promise and callback that forcefully abort the progress.
// This promise always rejects.
@ -70,10 +74,9 @@ export class ProgressController {
assert(this._state === 'before');
this._state = 'running';
const loggerScope = this._logger.createScope(this._apiName, true);
const loggerScope = this._logger.createScope(useApiName ? this._apiName : undefined, true);
const progress: Progress = {
apiName: this._apiName,
aborted: this._abortedPromise,
logger: loggerScope,
timeUntilDeadline: () => this._deadline ? this._deadline - monotonicTime() : 2147483647, // 2^31-1 safe setTimeout in Node.
@ -90,7 +93,7 @@ export class ProgressController {
},
};
const timeoutError = new TimeoutError(`Timeout ${this._timeout}ms exceeded during ${this._apiName}.`);
const timeoutError = new TimeoutError(`Timeout ${this._timeout}ms exceeded.`);
const timer = setTimeout(() => this._forceAbort(timeoutError), progress.timeUntilDeadline());
try {
const promise = task(progress);
@ -101,7 +104,11 @@ export class ProgressController {
return result;
} catch (e) {
this._aborted();
rewriteErrorMessage(e, e.message + formatLogRecording(loggerScope.recording(), this._apiName) + kLoggingNote);
rewriteErrorMessage(e,
(useApiName ? `${this._apiName}: ` : '') +
e.message +
formatLogRecording(loggerScope.recording()) +
kLoggingNote);
clearTimeout(timer);
this._state = 'aborted';
loggerScope.endScope(`failed`);
@ -124,14 +131,14 @@ async function runCleanup(cleanup: () => any) {
const kLoggingNote = `\nNote: use DEBUG=pw:api environment variable and rerun to capture Playwright logs.`;
function formatLogRecording(log: string[], name: string): string {
function formatLogRecording(log: string[]): string {
if (!log.length)
return '';
name = ` ${name} logs `;
const header = ` logs `;
const headerLength = 60;
const leftLength = (headerLength - name.length) / 2;
const rightLength = headerLength - name.length - leftLength;
return `\n${'='.repeat(leftLength)}${name}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
const leftLength = (headerLength - header.length) / 2;
const rightLength = headerLength - header.length - leftLength;
return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
}
function monotonicTime(): number {

View File

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

View File

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

View File

@ -19,6 +19,7 @@ import { Channel } from '../channels';
import { Connection } from './connection';
import { assert } from '../../helper';
import { LoggerSink } from '../../loggerSink';
import { rewriteErrorMessage } from '../../utils/stackTrace';
export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}> extends EventEmitter {
private _connection: Connection;
@ -65,23 +66,7 @@ export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}
return obj.addListener;
if (prop === 'removeEventListener')
return obj.removeListener;
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;
}
};
return (params: any) => this._connection.sendMessageToServer({ guid, method: String(prop), params });
},
});
(this._channel as any)._object = this;
@ -112,4 +97,21 @@ export abstract class ChannelOwner<T extends Channel = Channel, Initializer = {}
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> {
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> {
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> {
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> {
return (await this._elementChannel.textContent()).value;
return this._wrapApiCall('elementHandle.textContent', async () => {
return (await this._elementChannel.textContent()).value;
});
}
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> {
return (await this._elementChannel.innerHTML()).value;
return this._wrapApiCall('elementHandle.innerHTML', async () => {
return (await this._elementChannel.innerHTML()).value;
});
}
async dispatchEvent(type: string, eventInit: Object = {}) {
await this._elementChannel.dispatchEvent({ type, eventInit });
return this._wrapApiCall('elementHandle.dispatchEvent', async () => {
await this._elementChannel.dispatchEvent({ type, eventInit });
});
}
async scrollIntoViewIfNeeded(options: types.TimeoutOptions = {}) {
await this._elementChannel.scrollIntoViewIfNeeded(options);
return this._wrapApiCall('elementHandle.scrollIntoViewIfNeeded', async () => {
await this._elementChannel.scrollIntoViewIfNeeded(options);
});
}
async hover(options: types.PointerActionOptions & types.PointerActionWaitOptions = {}): Promise<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> {
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> {
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[]> {
const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options });
return result.values;
return this._wrapApiCall('elementHandle.selectOption', async () => {
const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options });
return result.values;
});
}
async fill(value: string, options: types.NavigatingActionWaitOptions = {}): Promise<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> {
await this._elementChannel.selectText(options);
return this._wrapApiCall('elementHandle.selectText', async () => {
await this._elementChannel.selectText(options);
});
}
async setInputFiles(files: string | types.FilePayload | string[] | types.FilePayload[], options: types.NavigatingActionWaitOptions = {}) {
await this._elementChannel.setInputFiles({ files: await convertInputFiles(files), ...options });
return this._wrapApiCall('elementHandle.setInputFiles', async () => {
await this._elementChannel.setInputFiles({ files: await convertInputFiles(files), ...options });
});
}
async focus(): Promise<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> {
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> {
await this._elementChannel.press({ key, ...options });
return this._wrapApiCall('elementHandle.press', async () => {
await this._elementChannel.press({ key, ...options });
});
}
async check(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return await this._elementChannel.check(options);
return this._wrapApiCall('elementHandle.check', async () => {
return await this._elementChannel.check(options);
});
}
async uncheck(options: types.PointerActionWaitOptions & types.NavigatingActionWaitOptions = {}) {
return await this._elementChannel.uncheck(options);
return this._wrapApiCall('elementHandle.uncheck', async () => {
return await this._elementChannel.uncheck(options);
});
}
async boundingBox(): Promise<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> {
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> {
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>[]> {
const result = await this._elementChannel.querySelectorAll({ selector });
return result.elements.map(h => ElementHandle.from(h) as ElementHandle<Element>);
return this._wrapApiCall('elementHandle.$$', async () => {
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>(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> {
const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
return this._wrapApiCall('elementHandle.$eval', async () => {
const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
});
}
async $$eval<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, 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 parseResult(result.value);
return this._wrapApiCall('elementHandle.$$eval', async () => {
const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value);
});
}
}

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

View File

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

View File

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

View File

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

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);
await page.waitForTimeout(5000); // Give it some time to click after the test hook is done waiting.
expect(await page.evaluate(() => result)).toBe('Was not clicked');
expect(error.message).toContain('Timeout 2000ms exceeded during page.click.');
expect(error.message).toContain('page.click: Timeout 2000ms exceeded.');
});
it('should click the button after navigation ', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
@ -188,7 +188,7 @@ describe('Page.click', function() {
await page.goto(server.PREFIX + '/input/button.html');
await page.$eval('button', b => b.style.display = 'none');
const error = await page.click('button', { timeout: 5000 }).catch(e => e);
expect(error.message).toContain('Timeout 5000ms exceeded during page.click.');
expect(error.message).toContain('page.click: Timeout 5000ms exceeded.');
expect(error.message).toContain('waiting for element to be visible, enabled and not moving');
expect(error.message).toContain('element is not visible - waiting');
});
@ -196,7 +196,7 @@ describe('Page.click', function() {
await page.goto(server.PREFIX + '/input/button.html');
await page.$eval('button', b => b.style.visibility = 'hidden');
const error = await page.click('button', { timeout: 5000 }).catch(e => e);
expect(error.message).toContain('Timeout 5000ms exceeded during page.click.');
expect(error.message).toContain('page.click: Timeout 5000ms exceeded.');
expect(error.message).toContain('waiting for element to be visible, enabled and not moving');
expect(error.message).toContain('element is not visible - waiting');
});
@ -440,7 +440,7 @@ describe('Page.click', function() {
button.style.marginLeft = '200px';
});
const error = await button.click({ timeout: 5000 }).catch(e => e);
expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.');
expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.');
expect(error.message).toContain('waiting for element to be visible, enabled and not moving');
expect(error.message).toContain('element is moving - waiting');
});
@ -489,9 +489,9 @@ describe('Page.click', function() {
document.body.appendChild(blocker);
});
const error = await button.click({ timeout: 5000 }).catch(e => e);
expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.');
expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.');
expect(error.message).toContain('element does not receive pointer events');
expect(error.message).toContain('retrying elementHandle.click action');
expect(error.message).toContain('retrying click action');
});
it('should fail when obscured and not waiting for hit target', async({page, server}) => {
await page.goto(server.PREFIX + '/input/button.html');
@ -525,7 +525,7 @@ describe('Page.click', function() {
await page.setContent('<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);
expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined);
expect(error.message).toContain('Timeout 3000ms exceeded during page.click.');
expect(error.message).toContain('page.click: Timeout 3000ms exceeded.');
expect(error.message).toContain('element is disabled - waiting');
});
it('should wait for input to be enabled', async({page, server}) => {
@ -720,9 +720,9 @@ describe('Page.click', function() {
const error = await promise;
expect(clicked).toBe(false);
expect(await page.evaluate(() => window.clicked)).toBe(undefined);
expect(error.message).toContain('Timeout 5000ms exceeded during elementHandle.click.');
expect(error.message).toContain('elementHandle.click: Timeout 5000ms exceeded.');
expect(error.message).toContain('element does not receive pointer events');
expect(error.message).toContain('retrying elementHandle.click action');
expect(error.message).toContain('retrying click action');
});
it('should dispatch microtasks in order', async({page, server}) => {
await page.setContent(`
@ -775,7 +775,7 @@ describe('Page.click', function() {
const error = await page.dblclick('text=button1', { __testHookAfterStable, timeout: 3000 }).catch(e => e);
expect(await page.evaluate(() => window.button1)).toBe(undefined);
expect(await page.evaluate(() => window.button2)).toBe(undefined);
expect(error.message).toContain('Timeout 3000ms exceeded during page.dblclick.');
expect(error.message).toContain('page.dblclick: Timeout 3000ms exceeded.');
expect(error.message).toContain('element does not match the selector anymore');
});
it.skip(USES_HOOKS).fail(true)('should retarget when element is recycled before enabled check', async ({page, server}) => {
@ -806,7 +806,7 @@ describe('Page.click', function() {
const error = await handle.click({ __testHookBeforeStable, timeout: 3000 }).catch(e => e);
expect(await page.evaluate(() => window.button1)).toBe(undefined);
expect(await page.evaluate(() => window.button2)).toBe(undefined);
expect(error.message).toContain('Timeout 3000ms exceeded during elementHandle.click.');
expect(error.message).toContain('elementHandle.click: Timeout 3000ms exceeded.');
expect(error.message).toContain('element is disabled - waiting');
});
it('should not retarget when element changes on hover', async ({page, server}) => {
@ -840,7 +840,7 @@ describe('Page.click', function() {
const dialogPromise = page.waitForEvent('dialog');
await page.setContent(`<div onclick='window.alert(123)'>Click me</div>`);
const error = await page.click('div', { timeout: 3000 }).catch(e => e);
expect(error.message).toContain('Timeout 3000ms exceeded during page.click.');
expect(error.message).toContain('page.click: Timeout 3000ms exceeded.');
const dialog = await dialogPromise;
await dialog.dismiss();
});

View File

@ -356,7 +356,7 @@ describe('launchPersistentContext()', function() {
const userDataDir = await makeUserDataDir();
const options = { ...defaultBrowserOptions, timeout: 5000, __testHookBeforeCreateBrowser: () => new Promise(f => setTimeout(f, 6000)) };
const error = await browserType.launchPersistentContext(userDataDir, options).catch(e => e);
expect(error.message).toContain(`Timeout 5000ms exceeded during browserType.launchPersistentContext.`);
expect(error.message).toContain(`browserType.launchPersistentContext: Timeout 5000ms exceeded.`);
await removeUserDataDir(userDataDir);
});
it.skip(USES_HOOKS)('should handle exception', async({browserType, defaultBrowserOptions}) => {

View File

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

View File

@ -611,7 +611,7 @@ describe('Frame.evaluate', function() {
const childResult = await childFrame.evaluate(() => window.__foo);
expect(childResult).toEqual({ bar: 'baz' });
const error = await childFrame.evaluate(foo => foo.bar, handle).catch(e => e);
expect(error.message).toBe('JSHandles can be evaluated only in the context they were created!');
expect(error.message).toContain('JSHandles can be evaluated only in the context they were created!');
});
it('should allow cross-frame element handles', async({page, server}) => {
await page.goto(server.PREFIX + '/frames/one-frame.html');

View File

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

View File

@ -62,7 +62,7 @@ describe('Page.evaluateHandle', function() {
const a = { x: 1 };
a.y = a;
const error = await page.evaluate(x => x, a).catch(e => e);
expect(error.message).toBe('Argument is a circular structure');
expect(error.message).toContain('Argument is a circular structure');
});
it('should accept same handle multiple times', async({page, server}) => {
const foo = await page.evaluateHandle(() => 1);

View File

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

View File

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

View File

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

View File

@ -662,7 +662,7 @@ describe('Page.setContent', function() {
// stall for image
server.setRoute(imgPath, (req, res) => {});
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);
});
it('should await resources to load', async({page, server}) => {
@ -707,7 +707,7 @@ describe('Page.addScriptTag', function() {
} catch (e) {
error = e;
}
expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property');
expect(error.message).toContain('Provide an object with a `url`, `path` or `content` property');
});
it('should work with a url', async({page, server}) => {
@ -799,7 +799,7 @@ describe('Page.addStyleTag', function() {
} catch (e) {
error = e;
}
expect(error.message).toBe('Provide an object with a `url`, `path` or `content` property');
expect(error.message).toContain('Provide an object with a `url`, `path` or `content` property');
});
it('should work with a url', async({page, server}) => {

View File

@ -120,11 +120,11 @@ describe('Page.$eval', function() {
});
it('should throw on multiple * captures', async({page, server}) => {
const error = await page.$eval('*css=div >> *css=span', e => e.outerHTML).catch(e => e);
expect(error.message).toBe('Only one of the selectors can capture using * modifier');
expect(error.message).toContain('Only one of the selectors can capture using * modifier');
});
it('should throw on malformed * capture', async({page, server}) => {
const error = await page.$eval('*=div', e => e.outerHTML).catch(e => e);
expect(error.message).toBe('Unknown engine "" while parsing selector *=div');
expect(error.message).toContain('Unknown engine "" while parsing selector *=div');
});
it('should work with spaces in css attributes', async({page, server}) => {
await page.setContent('<div><input placeholder="Select date"></div>');
@ -378,7 +378,7 @@ describe('ElementHandle.$eval', function() {
await page.setContent(htmlContent);
const elementHandle = await page.$('#myId');
const errorMessage = await elementHandle.$eval('.a', node => node.innerText).catch(error => error.message);
expect(errorMessage).toBe(`Error: failed to find element matching selector ".a"`);
expect(errorMessage).toContain(`Error: failed to find element matching selector ".a"`);
});
});
describe('ElementHandle.$$eval', function() {
@ -775,7 +775,7 @@ describe('selectors.register', () => {
// Selector names are case-sensitive.
const error = await page.$('tAG=DIV').catch(e => e);
expect(error.message).toBe('Unknown engine "tAG" while parsing selector tAG=DIV');
expect(error.message).toContain('Unknown engine "tAG" while parsing selector tAG=DIV');
});
it('should work with path', async ({playwright, page}) => {
await utils.registerEngine(playwright, 'foo', { path: path.join(__dirname, 'assets/sectionselectorengine.js') });
@ -815,7 +815,7 @@ describe('selectors.register', () => {
});
it('should handle errors', async ({playwright, page}) => {
let error = await page.$('neverregister=ignored').catch(e => e);
expect(error.message).toBe('Unknown engine "neverregister" while parsing selector neverregister=ignored');
expect(error.message).toContain('Unknown engine "neverregister" while parsing selector neverregister=ignored');
const createDummySelector = () => ({
create(root, target) {

View File

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

View File

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