chore: remove hard-coded wrapApi names (#7347)

This commit is contained in:
Pavel Feldman 2021-06-28 13:27:38 -07:00 committed by GitHub
parent 0776cf76a2
commit a8d48a1a48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 379 additions and 358 deletions

1
.gitignore vendored
View file

@ -15,3 +15,4 @@ drivers/
.gradle/ .gradle/
nohup.out nohup.out
.trace .trace
.tmp

View file

@ -26,6 +26,7 @@ import { Page } from './page';
import { TimeoutSettings } from '../utils/timeoutSettings'; import { TimeoutSettings } from '../utils/timeoutSettings';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { ParsedStackTrace } from '../utils/stackTrace';
type Direction = 'down' | 'up' | 'left' | 'right'; type Direction = 'down' | 'up' | 'left' | 'right';
type SpeedOptions = { speed?: number }; type SpeedOptions = { speed?: number };
@ -48,7 +49,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel, channels.Andr
} }
async devices(): Promise<AndroidDevice[]> { async devices(): Promise<AndroidDevice[]> {
return this._wrapApiCall('android.devices', async (channel: channels.AndroidChannel) => { return this._wrapApiCall(async (channel: channels.AndroidChannel) => {
const { devices } = await channel.devices(); const { devices } = await channel.devices();
return devices.map(d => AndroidDevice.from(d)); return devices.map(d => AndroidDevice.from(d));
}); });
@ -114,13 +115,13 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
} }
async wait(selector: api.AndroidSelector, options?: { state?: 'gone' } & types.TimeoutOptions) { async wait(selector: api.AndroidSelector, options?: { state?: 'gone' } & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.wait', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.wait({ selector: toSelectorChannel(selector), ...options }); await channel.wait({ selector: toSelectorChannel(selector), ...options });
}); });
} }
async fill(selector: api.AndroidSelector, text: string, options?: types.TimeoutOptions) { async fill(selector: api.AndroidSelector, text: string, options?: types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.fill', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.fill({ selector: toSelectorChannel(selector), text, ...options }); await channel.fill({ selector: toSelectorChannel(selector), text, ...options });
}); });
} }
@ -131,61 +132,61 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
} }
async tap(selector: api.AndroidSelector, options?: { duration?: number } & types.TimeoutOptions) { async tap(selector: api.AndroidSelector, options?: { duration?: number } & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.tap', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.tap({ selector: toSelectorChannel(selector), ...options }); await channel.tap({ selector: toSelectorChannel(selector), ...options });
}); });
} }
async drag(selector: api.AndroidSelector, dest: types.Point, options?: SpeedOptions & types.TimeoutOptions) { async drag(selector: api.AndroidSelector, dest: types.Point, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.drag', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.drag({ selector: toSelectorChannel(selector), dest, ...options }); await channel.drag({ selector: toSelectorChannel(selector), dest, ...options });
}); });
} }
async fling(selector: api.AndroidSelector, direction: Direction, options?: SpeedOptions & types.TimeoutOptions) { async fling(selector: api.AndroidSelector, direction: Direction, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.fling', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.fling({ selector: toSelectorChannel(selector), direction, ...options }); await channel.fling({ selector: toSelectorChannel(selector), direction, ...options });
}); });
} }
async longTap(selector: api.AndroidSelector, options?: types.TimeoutOptions) { async longTap(selector: api.AndroidSelector, options?: types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.longTap', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.longTap({ selector: toSelectorChannel(selector), ...options }); await channel.longTap({ selector: toSelectorChannel(selector), ...options });
}); });
} }
async pinchClose(selector: api.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) { async pinchClose(selector: api.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.pinchClose', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.pinchClose({ selector: toSelectorChannel(selector), percent, ...options }); await channel.pinchClose({ selector: toSelectorChannel(selector), percent, ...options });
}); });
} }
async pinchOpen(selector: api.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) { async pinchOpen(selector: api.AndroidSelector, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.pinchOpen', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.pinchOpen({ selector: toSelectorChannel(selector), percent, ...options }); await channel.pinchOpen({ selector: toSelectorChannel(selector), percent, ...options });
}); });
} }
async scroll(selector: api.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) { async scroll(selector: api.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.scroll', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.scroll({ selector: toSelectorChannel(selector), direction, percent, ...options }); await channel.scroll({ selector: toSelectorChannel(selector), direction, percent, ...options });
}); });
} }
async swipe(selector: api.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) { async swipe(selector: api.AndroidSelector, direction: Direction, percent: number, options?: SpeedOptions & types.TimeoutOptions) {
await this._wrapApiCall('androidDevice.swipe', async (channel: channels.AndroidDeviceChannel) => { await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.swipe({ selector: toSelectorChannel(selector), direction, percent, ...options }); await channel.swipe({ selector: toSelectorChannel(selector), direction, percent, ...options });
}); });
} }
async info(selector: api.AndroidSelector): Promise<api.AndroidElementInfo> { async info(selector: api.AndroidSelector): Promise<api.AndroidElementInfo> {
return await this._wrapApiCall('androidDevice.info', async (channel: channels.AndroidDeviceChannel) => { return await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
return (await channel.info({ selector: toSelectorChannel(selector) })).info; return (await channel.info({ selector: toSelectorChannel(selector) })).info;
}); });
} }
async screenshot(options: { path?: string } = {}): Promise<Buffer> { async screenshot(options: { path?: string } = {}): Promise<Buffer> {
return await this._wrapApiCall('androidDevice.screenshot', async (channel: channels.AndroidDeviceChannel) => { return await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
const { binary } = await channel.screenshot(); const { binary } = await channel.screenshot();
const buffer = Buffer.from(binary, 'base64'); const buffer = Buffer.from(binary, 'base64');
if (options.path) if (options.path)
@ -195,39 +196,39 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
} }
async close() { async close() {
return this._wrapApiCall('androidDevice.close', async (channel: channels.AndroidDeviceChannel) => { return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.close(); await channel.close();
this.emit(Events.AndroidDevice.Close); this.emit(Events.AndroidDevice.Close);
}); });
} }
async shell(command: string): Promise<Buffer> { async shell(command: string): Promise<Buffer> {
return this._wrapApiCall('androidDevice.shell', async (channel: channels.AndroidDeviceChannel) => { return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
const { result } = await channel.shell({ command }); const { result } = await channel.shell({ command });
return Buffer.from(result, 'base64'); return Buffer.from(result, 'base64');
}); });
} }
async open(command: string): Promise<AndroidSocket> { async open(command: string): Promise<AndroidSocket> {
return this._wrapApiCall('androidDevice.open', async (channel: channels.AndroidDeviceChannel) => { return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
return AndroidSocket.from((await channel.open({ command })).socket); return AndroidSocket.from((await channel.open({ command })).socket);
}); });
} }
async installApk(file: string | Buffer, options?: { args: string[] }): Promise<void> { async installApk(file: string | Buffer, options?: { args: string[] }): Promise<void> {
return this._wrapApiCall('androidDevice.installApk', async (channel: channels.AndroidDeviceChannel) => { return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.installApk({ file: await loadFile(file), args: options && options.args }); await channel.installApk({ file: await loadFile(file), args: options && options.args });
}); });
} }
async push(file: string | Buffer, path: string, options?: { mode: number }): Promise<void> { async push(file: string | Buffer, path: string, options?: { mode: number }): Promise<void> {
return this._wrapApiCall('androidDevice.push', async (channel: channels.AndroidDeviceChannel) => { return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.push({ file: await loadFile(file), path, mode: options ? options.mode : undefined }); await channel.push({ file: await loadFile(file), path, mode: options ? options.mode : undefined });
}); });
} }
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> { async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> {
return this._wrapApiCall('androidDevice.launchBrowser', async (channel: channels.AndroidDeviceChannel) => { return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
const contextOptions = await prepareBrowserContextParams(options); const contextOptions = await prepareBrowserContextParams(options);
const { context } = await channel.launchBrowser(contextOptions); const { context } = await channel.launchBrowser(contextOptions);
return BrowserContext.from(context) as BrowserContext; return BrowserContext.from(context) as BrowserContext;
@ -235,15 +236,17 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
} }
async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise<any> { async waitForEvent(event: string, optionsOrPredicate: types.WaitForEventOptions = {}): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate); return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel, stackTrace: ParsedStackTrace) => {
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate; const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const waiter = Waiter.createForEvent(this, 'androidDevice', event); const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`); const waiter = Waiter.createForEvent(this, event, stackTrace);
if (event !== Events.AndroidDevice.Close) waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new Error('Device closed')); if (event !== Events.AndroidDevice.Close)
const result = await waiter.waitForEvent(this, event, predicate as any); waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new Error('Device closed'));
waiter.dispose(); const result = await waiter.waitForEvent(this, event, predicate as any);
return result; waiter.dispose();
return result;
});
} }
} }
@ -259,13 +262,13 @@ export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel, c
} }
async write(data: Buffer): Promise<void> { async write(data: Buffer): Promise<void> {
return this._wrapApiCall('androidDevice.write', async (channel: channels.AndroidSocketChannel) => { return this._wrapApiCall(async (channel: channels.AndroidSocketChannel) => {
await channel.write({ data: data.toString('base64') }); await channel.write({ data: data.toString('base64') });
}); });
} }
async close(): Promise<void> { async close(): Promise<void> {
return this._wrapApiCall('androidDevice.close', async (channel: channels.AndroidSocketChannel) => { return this._wrapApiCall(async (channel: channels.AndroidSocketChannel) => {
await channel.close(); await channel.close();
}); });
} }
@ -285,31 +288,31 @@ export class AndroidInput implements api.AndroidInput {
} }
async type(text: string) { async type(text: string) {
return this._device._wrapApiCall('androidDevice.inputType', async (channel: channels.AndroidDeviceChannel) => { return this._device._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.inputType({ text }); await channel.inputType({ text });
}); });
} }
async press(key: api.AndroidKey) { async press(key: api.AndroidKey) {
return this._device._wrapApiCall('androidDevice.inputPress', async (channel: channels.AndroidDeviceChannel) => { return this._device._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.inputPress({ key }); await channel.inputPress({ key });
}); });
} }
async tap(point: types.Point) { async tap(point: types.Point) {
return this._device._wrapApiCall('androidDevice.inputTap', async (channel: channels.AndroidDeviceChannel) => { return this._device._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.inputTap({ point }); await channel.inputTap({ point });
}); });
} }
async swipe(from: types.Point, segments: types.Point[], steps: number) { async swipe(from: types.Point, segments: types.Point[], steps: number) {
return this._device._wrapApiCall('androidDevice.inputSwipe', async (channel: channels.AndroidDeviceChannel) => { return this._device._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.inputSwipe({ segments, steps }); await channel.inputSwipe({ segments, steps });
}); });
} }
async drag(from: types.Point, to: types.Point, steps: number) { async drag(from: types.Point, to: types.Point, steps: number) {
return this._device._wrapApiCall('androidDevice.inputDragAndDrop', async (channel: channels.AndroidDeviceChannel) => { return this._device._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.inputDrag({ from, to, steps }); await channel.inputDrag({ from, to, steps });
}); });
} }
@ -391,7 +394,7 @@ export class AndroidWebView extends EventEmitter implements api.AndroidWebView {
} }
private async _fetchPage(): Promise<Page> { private async _fetchPage(): Promise<Page> {
return this._device._wrapApiCall('androidWebView.page', async (channel: channels.AndroidDeviceChannel) => { return this._device._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
const { context } = await channel.connectToWebView({ pid: this._data.pid, sdkLanguage: 'javascript' }); const { context } = await channel.connectToWebView({ pid: this._data.pid, sdkLanguage: 'javascript' });
return BrowserContext.from(context).pages()[0]; return BrowserContext.from(context).pages()[0];
}); });

View file

@ -23,7 +23,6 @@ import { Readable } from 'stream';
export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.ArtifactInitializer> { export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.ArtifactInitializer> {
_isRemote = false; _isRemote = false;
_apiName: string = '';
static from(channel: channels.ArtifactChannel): Artifact { static from(channel: channels.ArtifactChannel): Artifact {
return (channel as any)._object; return (channel as any)._object;
@ -32,13 +31,13 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.Ar
async pathAfterFinished(): Promise<string | null> { async pathAfterFinished(): Promise<string | null> {
if (this._isRemote) if (this._isRemote)
throw new Error(`Path is not available when using browserType.connect(). Use saveAs() to save a local copy.`); throw new Error(`Path is not available when using browserType.connect(). Use saveAs() to save a local copy.`);
return this._wrapApiCall(`${this._apiName}.path`, async (channel: channels.ArtifactChannel) => { return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
return (await channel.pathAfterFinished()).value || null; return (await channel.pathAfterFinished()).value || null;
}); });
} }
async saveAs(path: string): Promise<void> { async saveAs(path: string): Promise<void> {
return this._wrapApiCall(`${this._apiName}.saveAs`, async (channel: channels.ArtifactChannel) => { return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
if (!this._isRemote) { if (!this._isRemote) {
await channel.saveAs({ path }); await channel.saveAs({ path });
return; return;
@ -56,13 +55,13 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.Ar
} }
async failure(): Promise<string | null> { async failure(): Promise<string | null> {
return this._wrapApiCall(`${this._apiName}.failure`, async (channel: channels.ArtifactChannel) => { return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
return (await channel.failure()).error || null; return (await channel.failure()).error || null;
}); });
} }
async createReadStream(): Promise<Readable | null> { async createReadStream(): Promise<Readable | null> {
return this._wrapApiCall(`${this._apiName}.createReadStream`, async (channel: channels.ArtifactChannel) => { return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
const result = await channel.stream(); const result = await channel.stream();
if (!result.stream) if (!result.stream)
return null; return null;
@ -72,13 +71,13 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.Ar
} }
async cancel(): Promise<void> { async cancel(): Promise<void> {
return this._wrapApiCall(`${this._apiName}.cancel`, async (channel: channels.ArtifactChannel) => { return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
return channel.cancel(); return channel.cancel();
}); });
} }
async delete(): Promise<void> { async delete(): Promise<void> {
return this._wrapApiCall(`${this._apiName}.delete`, async (channel: channels.ArtifactChannel) => { return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
return channel.delete(); return channel.delete();
}); });
} }

View file

@ -47,7 +47,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
} }
async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> { async newContext(options: BrowserContextOptions = {}): Promise<BrowserContext> {
return this._wrapApiCall('browser.newContext', async (channel: channels.BrowserChannel) => { return this._wrapApiCall(async (channel: channels.BrowserChannel) => {
const contextOptions = await prepareBrowserContextParams(options); const contextOptions = await prepareBrowserContextParams(options);
const context = BrowserContext.from((await channel.newContext(contextOptions)).context); const context = BrowserContext.from((await channel.newContext(contextOptions)).context);
context._options = contextOptions; context._options = contextOptions;
@ -78,26 +78,26 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
} }
async newBrowserCDPSession(): Promise<api.CDPSession> { async newBrowserCDPSession(): Promise<api.CDPSession> {
return this._wrapApiCall('browser.newBrowserCDPSession', async (channel: channels.BrowserChannel) => { return this._wrapApiCall(async (channel: channels.BrowserChannel) => {
return CDPSession.from((await channel.newBrowserCDPSession()).session); return CDPSession.from((await channel.newBrowserCDPSession()).session);
}); });
} }
async startTracing(page?: Page, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) { async startTracing(page?: Page, options: { path?: string; screenshots?: boolean; categories?: string[]; } = {}) {
return this._wrapApiCall('browser.startTracing', async (channel: channels.BrowserChannel) => { return this._wrapApiCall(async (channel: channels.BrowserChannel) => {
await channel.startTracing({ ...options, page: page ? page._channel : undefined }); await channel.startTracing({ ...options, page: page ? page._channel : undefined });
}); });
} }
async stopTracing(): Promise<Buffer> { async stopTracing(): Promise<Buffer> {
return this._wrapApiCall('browser.stopTracing', async (channel: channels.BrowserChannel) => { return this._wrapApiCall(async (channel: channels.BrowserChannel) => {
return Buffer.from((await channel.stopTracing()).binary, 'base64'); return Buffer.from((await channel.stopTracing()).binary, 'base64');
}); });
} }
async close(): Promise<void> { async close(): Promise<void> {
try { try {
await this._wrapApiCall('browser.close', async (channel: channels.BrowserChannel) => { await this._wrapApiCall(async (channel: channels.BrowserChannel) => {
if (this._remoteType === 'owns-connection') if (this._remoteType === 'owns-connection')
this._connection.close(); this._connection.close();
else else

View file

@ -33,6 +33,7 @@ import * as api from '../../types/types';
import * as structs from '../../types/structs'; import * as structs from '../../types/structs';
import { CDPSession } from './cdpSession'; import { CDPSession } from './cdpSession';
import { Tracing } from './tracing'; import { Tracing } from './tracing';
import { ParsedStackTrace } from '../utils/stackTrace';
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel, channels.BrowserContextInitializer> implements api.BrowserContext { export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel, channels.BrowserContextInitializer> implements api.BrowserContext {
_pages = new Set<Page>(); _pages = new Set<Page>();
@ -162,7 +163,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
} }
async newPage(): Promise<Page> { async newPage(): Promise<Page> {
return this._wrapApiCall('browserContext.newPage', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
if (this._ownerPage) if (this._ownerPage)
throw new Error('Please use browser.newContext()'); throw new Error('Please use browser.newContext()');
return Page.from((await channel.newPage()).page); return Page.from((await channel.newPage()).page);
@ -174,50 +175,50 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
urls = []; urls = [];
if (urls && typeof urls === 'string') if (urls && typeof urls === 'string')
urls = [ urls ]; urls = [ urls ];
return this._wrapApiCall('browserContext.cookies', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
return (await channel.cookies({ urls: urls as string[] })).cookies; return (await channel.cookies({ urls: urls as string[] })).cookies;
}); });
} }
async addCookies(cookies: network.SetNetworkCookieParam[]): Promise<void> { async addCookies(cookies: network.SetNetworkCookieParam[]): Promise<void> {
return this._wrapApiCall('browserContext.addCookies', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.addCookies({ cookies }); await channel.addCookies({ cookies });
}); });
} }
async clearCookies(): Promise<void> { async clearCookies(): Promise<void> {
return this._wrapApiCall('browserContext.clearCookies', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.clearCookies(); await channel.clearCookies();
}); });
} }
async grantPermissions(permissions: string[], options?: { origin?: string }): Promise<void> { async grantPermissions(permissions: string[], options?: { origin?: string }): Promise<void> {
return this._wrapApiCall('browserContext.grantPermissions', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.grantPermissions({ permissions, ...options }); await channel.grantPermissions({ permissions, ...options });
}); });
} }
async clearPermissions(): Promise<void> { async clearPermissions(): Promise<void> {
return this._wrapApiCall('browserContext.clearPermissions', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.clearPermissions(); await channel.clearPermissions();
}); });
} }
async setGeolocation(geolocation: { longitude: number, latitude: number, accuracy?: number } | null): Promise<void> { async setGeolocation(geolocation: { longitude: number, latitude: number, accuracy?: number } | null): Promise<void> {
return this._wrapApiCall('browserContext.setGeolocation', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.setGeolocation({ geolocation: geolocation || undefined }); await channel.setGeolocation({ geolocation: geolocation || undefined });
}); });
} }
async setExtraHTTPHeaders(headers: Headers): Promise<void> { async setExtraHTTPHeaders(headers: Headers): Promise<void> {
return this._wrapApiCall('browserContext.setExtraHTTPHeaders', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
network.validateHeaders(headers); network.validateHeaders(headers);
await channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) }); await channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
}); });
} }
async setOffline(offline: boolean): Promise<void> { async setOffline(offline: boolean): Promise<void> {
return this._wrapApiCall('browserContext.setOffline', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.setOffline({ offline }); await channel.setOffline({ offline });
}); });
} }
@ -225,27 +226,27 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
async setHTTPCredentials(httpCredentials: { username: string, password: string } | null): Promise<void> { async setHTTPCredentials(httpCredentials: { username: string, password: string } | null): Promise<void> {
if (!isUnderTest()) if (!isUnderTest())
deprecate(`context.setHTTPCredentials`, `warning: method |context.setHTTPCredentials()| is deprecated. Instead of changing credentials, create another browser context with new credentials.`); deprecate(`context.setHTTPCredentials`, `warning: method |context.setHTTPCredentials()| is deprecated. Instead of changing credentials, create another browser context with new credentials.`);
return this._wrapApiCall('browserContext.setHTTPCredentials', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.setHTTPCredentials({ httpCredentials: httpCredentials || undefined }); await channel.setHTTPCredentials({ httpCredentials: httpCredentials || undefined });
}); });
} }
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void> { async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any): Promise<void> {
return this._wrapApiCall('browserContext.addInitScript', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
const source = await evaluationScript(script, arg); const source = await evaluationScript(script, arg);
await channel.addInitScript({ source }); await channel.addInitScript({ source });
}); });
} }
async exposeBinding(name: string, callback: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}): Promise<void> { async exposeBinding(name: string, callback: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}): Promise<void> {
return this._wrapApiCall('browserContext.exposeBinding', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.exposeBinding({ name, needsHandle: options.handle }); await channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, callback); this._bindings.set(name, callback);
}); });
} }
async exposeFunction(name: string, callback: Function): Promise<void> { async exposeFunction(name: string, callback: Function): Promise<void> {
return this._wrapApiCall('browserContext.exposeFunction', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.exposeBinding({ name }); await channel.exposeBinding({ name });
const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args); const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args);
this._bindings.set(name, binding); this._bindings.set(name, binding);
@ -253,7 +254,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
} }
async route(url: URLMatch, handler: network.RouteHandler): Promise<void> { async route(url: URLMatch, handler: network.RouteHandler): Promise<void> {
return this._wrapApiCall('browserContext.route', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
this._routes.push({ url, handler }); this._routes.push({ url, handler });
if (this._routes.length === 1) if (this._routes.length === 1)
await channel.setNetworkInterceptionEnabled({ enabled: true }); await channel.setNetworkInterceptionEnabled({ enabled: true });
@ -261,7 +262,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
} }
async unroute(url: URLMatch, handler?: network.RouteHandler): Promise<void> { async unroute(url: URLMatch, handler?: network.RouteHandler): Promise<void> {
return this._wrapApiCall('browserContext.unroute', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler)); this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (this._routes.length === 0) if (this._routes.length === 0)
await channel.setNetworkInterceptionEnabled({ enabled: false }); await channel.setNetworkInterceptionEnabled({ enabled: false });
@ -269,19 +270,21 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
} }
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> { async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate); return this._wrapApiCall(async (channel: channels.BrowserContextChannel, stackTrace: ParsedStackTrace) => {
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate; const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const waiter = Waiter.createForEvent(this, 'browserContext', event); const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`); const waiter = Waiter.createForEvent(this, event, stackTrace);
if (event !== Events.BrowserContext.Close) waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new Error('Context closed')); if (event !== Events.BrowserContext.Close)
const result = await waiter.waitForEvent(this, event, predicate as any); waiter.rejectOnEvent(this, Events.BrowserContext.Close, new Error('Context closed'));
waiter.dispose(); const result = await waiter.waitForEvent(this, event, predicate as any);
return result; waiter.dispose();
return result;
});
} }
async storageState(options: { path?: string } = {}): Promise<StorageState> { async storageState(options: { path?: string } = {}): Promise<StorageState> {
return await this._wrapApiCall('browserContext.storageState', async (channel: channels.BrowserContextChannel) => { return await this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
const state = await channel.storageState(); const state = await channel.storageState();
if (options.path) { if (options.path) {
await mkdirIfNeeded(options.path); await mkdirIfNeeded(options.path);
@ -300,7 +303,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
} }
async newCDPSession(page: Page): Promise<api.CDPSession> { async newCDPSession(page: Page): Promise<api.CDPSession> {
return this._wrapApiCall('browserContext.newCDPSession', async (channel: channels.BrowserContextChannel) => { return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
const result = await channel.newCDPSession({ page: page._channel }); const result = await channel.newCDPSession({ page: page._channel });
return CDPSession.from(result.session); return CDPSession.from(result.session);
}); });
@ -314,7 +317,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
async close(): Promise<void> { async close(): Promise<void> {
try { try {
await this._wrapApiCall('browserContext.close', async (channel: channels.BrowserContextChannel) => { await this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.close(); await channel.close();
await this._closedPromise; await this._closedPromise;
}); });

View file

@ -66,7 +66,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
async launch(options: LaunchOptions = {}): Promise<Browser> { async launch(options: LaunchOptions = {}): Promise<Browser> {
const logger = options.logger; const logger = options.logger;
return this._wrapApiCall('browserType.launch', async (channel: channels.BrowserTypeChannel) => { return this._wrapApiCall(async (channel: channels.BrowserTypeChannel) => {
assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); assert(!(options as any).userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
const launchOptions: channels.BrowserTypeLaunchParams = { const launchOptions: channels.BrowserTypeLaunchParams = {
@ -88,7 +88,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
} }
async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> { async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> {
return this._wrapApiCall('browserType.launchPersistentContext', async (channel: channels.BrowserTypeChannel) => { return this._wrapApiCall(async (channel: channels.BrowserTypeChannel) => {
assert(!(options as any).port, 'Cannot specify a port without launching as a server.'); assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
const contextParams = await prepareBrowserContextParams(options); const contextParams = await prepareBrowserContextParams(options);
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = { const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
@ -110,7 +110,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
async connect(params: ConnectOptions): Promise<Browser> { async connect(params: ConnectOptions): Promise<Browser> {
const logger = params.logger; const logger = params.logger;
const paramsHeaders = Object.assign({'User-Agent': getUserAgent()}, params.headers); const paramsHeaders = Object.assign({'User-Agent': getUserAgent()}, params.headers);
return this._wrapApiCall('browserType.connect', async () => { return this._wrapApiCall(async () => {
const ws = new WebSocket(params.wsEndpoint, [], { const ws = new WebSocket(params.wsEndpoint, [], {
perMessageDeflate: false, perMessageDeflate: false,
maxPayload: 256 * 1024 * 1024, // 256Mb, maxPayload: 256 * 1024 * 1024, // 256Mb,
@ -221,7 +221,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
if (this.name() !== 'chromium') if (this.name() !== 'chromium')
throw new Error('Connecting over CDP is only supported in Chromium.'); throw new Error('Connecting over CDP is only supported in Chromium.');
const logger = params.logger; const logger = params.logger;
return this._wrapApiCall('browserType.connectOverCDP', async (channel: channels.BrowserTypeChannel) => { return this._wrapApiCall(async (channel: channels.BrowserTypeChannel) => {
const paramsHeaders = Object.assign({'User-Agent': getUserAgent()}, params.headers); const paramsHeaders = Object.assign({'User-Agent': getUserAgent()}, params.headers);
const headers = paramsHeaders ? headersObjectToArray(paramsHeaders) : undefined; const headers = paramsHeaders ? headersObjectToArray(paramsHeaders) : undefined;
const result = await channel.connectOverCDP({ const result = await channel.connectOverCDP({

View file

@ -42,14 +42,14 @@ export class CDPSession extends ChannelOwner<channels.CDPSessionChannel, channel
method: T, method: T,
params?: Protocol.CommandParameters[T] params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> { ): Promise<Protocol.CommandReturnValues[T]> {
return this._wrapApiCall('cdpSession.send', async (channel: channels.CDPSessionChannel) => { return this._wrapApiCall(async (channel: channels.CDPSessionChannel) => {
const result = await channel.send({ method, params }); const result = await channel.send({ method, params });
return result.result as Protocol.CommandReturnValues[T]; return result.result as Protocol.CommandReturnValues[T];
}); });
} }
async detach() { async detach() {
return this._wrapApiCall('cdpSession.detach', async (channel: channels.CDPSessionChannel) => { return this._wrapApiCall(async (channel: channels.CDPSessionChannel) => {
return channel.detach(); return channel.detach();
}); });
} }

View file

@ -18,7 +18,8 @@ import { EventEmitter } from 'events';
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { createScheme, ValidationError, Validator } from '../protocol/validator'; import { createScheme, ValidationError, Validator } from '../protocol/validator';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { rewriteErrorMessage } from '../utils/stackTrace'; import { captureStackTrace, ParsedStackTrace } from '../utils/stackTrace';
import { isUnderTest } from '../utils/utils';
import type { Connection } from './connection'; import type { Connection } from './connection';
import type { Logger } from './types'; import type { Logger } from './types';
@ -47,7 +48,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
this._logger = this._parent._logger; this._logger = this._parent._logger;
} }
this._channel = this._createChannel(new EventEmitter(), ''); this._channel = this._createChannel(new EventEmitter(), null);
this._initializer = initializer; this._initializer = initializer;
} }
@ -70,15 +71,15 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
}; };
} }
_createChannel(base: Object, apiName: string): T { private _createChannel(base: Object, stackTrace: ParsedStackTrace | null): T {
const channel = new Proxy(base, { const channel = new Proxy(base, {
get: (obj: any, prop) => { get: (obj: any, prop) => {
if (prop === 'debugScopeState') if (prop === 'debugScopeState')
return (params: any) => this._connection.sendMessageToServer(this, prop, params, apiName); return (params: any) => this._connection.sendMessageToServer(this, prop, params, stackTrace);
if (typeof prop === 'string') { if (typeof prop === 'string') {
const validator = scheme[paramsName(this._type, prop)]; const validator = scheme[paramsName(this._type, prop)];
if (validator) if (validator)
return (params: any) => this._connection.sendMessageToServer(this, prop, validator(params, ''), apiName); return (params: any) => this._connection.sendMessageToServer(this, prop, validator(params, ''), stackTrace);
} }
return obj[prop]; return obj[prop];
}, },
@ -87,31 +88,35 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
return channel; return channel;
} }
async _wrapApiCall<R, C extends channels.Channel>(apiName: string, func: (channel: C) => Promise<R>, logger?: Logger): Promise<R> { async _wrapApiCall<R, C extends channels.Channel>(func: (channel: C, stackTrace: ParsedStackTrace) => Promise<R>, logger?: Logger): Promise<R> {
logger = logger || this._logger; logger = logger || this._logger;
const stackTrace = captureStackTrace();
const { apiName, frameTexts } = stackTrace;
const channel = this._createChannel({}, stackTrace);
try { try {
logApiCall(logger, `=> ${apiName} started`); logApiCall(logger, `=> ${apiName} started`);
const channel = this._createChannel({}, apiName); const result = await func(channel as any, stackTrace);
const result = await func(channel as any);
logApiCall(logger, `<= ${apiName} succeeded`); logApiCall(logger, `<= ${apiName} succeeded`);
return result; return result;
} catch (e) { } catch (e) {
const innerError = ((process.env.PWDEBUGIMPL || isUnderTest()) && e.stack) ? '\n<inner error>\n' + e.stack : '';
e.message = apiName + ': ' + e.message;
e.stack = e.message + '\n' + frameTexts.join('\n') + innerError;
logApiCall(logger, `<= ${apiName} failed`); logApiCall(logger, `<= ${apiName} failed`);
rewriteErrorMessage(e, `${apiName}: ` + e.message);
throw e; throw e;
} }
} }
_waitForEventInfoBefore(waitId: string, apiName: string) { _waitForEventInfoBefore(waitId: string, event: string, stackTrace: ParsedStackTrace) {
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { apiName, waitId, phase: 'before' } }, undefined).catch(() => {}); this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'before', event } }, stackTrace).catch(() => {});
} }
_waitForEventInfoAfter(waitId: string, error?: string) { _waitForEventInfoAfter(waitId: string, error: string | undefined) {
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'after', error } }, undefined).catch(() => {}); this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'after', error } }, null).catch(() => {});
} }
_waitForEventInfoLog(waitId: string, message: string) { _waitForEventInfoLog(waitId: string, message: string) {
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'log', message } }, undefined).catch(() => {}); this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'log', message } }, null).catch(() => {});
} }
private toJSON() { private toJSON() {

View file

@ -34,10 +34,9 @@ import * as channels from '../protocol/channels';
import { Stream } from './stream'; import { Stream } from './stream';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { SelectorsOwner } from './selectors'; import { SelectorsOwner } from './selectors';
import { isUnderTest } from '../utils/utils';
import { Android, AndroidSocket, AndroidDevice } from './android'; import { Android, AndroidSocket, AndroidDevice } from './android';
import { SocksSocket } from './socksSocket'; import { SocksSocket } from './socksSocket';
import { captureStackTrace } from '../utils/stackTrace'; import { ParsedStackTrace } from '../utils/stackTrace';
import { Artifact } from './artifact'; import { Artifact } from './artifact';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
@ -77,24 +76,20 @@ export class Connection extends EventEmitter {
return this._objects.get(guid)!; return this._objects.get(guid)!;
} }
async sendMessageToServer(object: ChannelOwner, method: string, params: any, apiName: string | undefined): Promise<any> { async sendMessageToServer(object: ChannelOwner, method: string, params: any, stackTrace: ParsedStackTrace | null): Promise<any> {
const guid = object._guid; const guid = object._guid;
const { stack, frames } = captureStackTrace(); const { frames, apiName }: ParsedStackTrace = stackTrace || { frameTexts: [], frames: [], apiName: '' };
const id = ++this._lastId; const id = ++this._lastId;
const converted = { id, guid, method, params }; const converted = { id, guid, method, params };
// Do not include metadata in debug logs to avoid noise. // Do not include metadata in debug logs to avoid noise.
debugLogger.log('channel:command', converted); debugLogger.log('channel:command', converted);
const metadata: channels.Metadata = { stack: frames, apiName }; const metadata: channels.Metadata = { stack: frames, apiName };
this.onmessage({ ...converted, metadata }); this.onmessage({ ...converted, metadata });
try {
if (this._disconnectedErrorMessage) if (this._disconnectedErrorMessage)
throw new Error(this._disconnectedErrorMessage); throw new Error(this._disconnectedErrorMessage);
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, metadata })); return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, metadata }));
} catch (e) {
const innerStack = ((process.env.PWDEBUGIMPL || isUnderTest()) && e.stack) ? e.stack.substring(e.stack.indexOf(e.message) + e.message.length) : '';
e.stack = e.message + innerStack + '\n' + stack;
throw e;
}
} }
_debugScopeState(): any { _debugScopeState(): any {

View file

@ -40,13 +40,13 @@ export class Dialog extends ChannelOwner<channels.DialogChannel, channels.Dialog
} }
async accept(promptText: string | undefined) { async accept(promptText: string | undefined) {
return this._wrapApiCall('dialog.accept', async (channel: channels.DialogChannel) => { return this._wrapApiCall(async (channel: channels.DialogChannel) => {
await channel.accept({ promptText }); await channel.accept({ promptText });
}); });
} }
async dismiss() { async dismiss() {
return this._wrapApiCall('dialog.dismiss', async (channel: channels.DialogChannel) => { return this._wrapApiCall(async (channel: channels.DialogChannel) => {
await channel.dismiss(); await channel.dismiss();
}); });
} }

View file

@ -18,6 +18,7 @@ import type { BrowserWindow } from 'electron';
import * as structs from '../../types/structs'; import * as structs from '../../types/structs';
import * as api from '../../types/types'; import * as api from '../../types/types';
import * as channels from '../protocol/channels'; import * as channels from '../protocol/channels';
import { ParsedStackTrace } from '../utils/stackTrace';
import { TimeoutSettings } from '../utils/timeoutSettings'; import { TimeoutSettings } from '../utils/timeoutSettings';
import { headersObjectToArray } from '../utils/utils'; import { headersObjectToArray } from '../utils/utils';
import { BrowserContext } from './browserContext'; import { BrowserContext } from './browserContext';
@ -46,7 +47,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel, channels.El
} }
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> { async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
return this._wrapApiCall('electron.launch', async (channel: channels.ElectronChannel) => { return this._wrapApiCall(async (channel: channels.ElectronChannel) => {
const params: channels.ElectronLaunchParams = { const params: channels.ElectronLaunchParams = {
sdkLanguage: 'javascript', sdkLanguage: 'javascript',
...options, ...options,
@ -88,7 +89,7 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
} }
async firstWindow(): Promise<Page> { async firstWindow(): Promise<Page> {
return this._wrapApiCall('electronApplication.firstWindow', async (channel: channels.ElectronApplicationChannel) => { return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel) => {
if (this._windows.size) if (this._windows.size)
return this._windows.values().next().value; return this._windows.values().next().value;
return this.waitForEvent('window'); return this.waitForEvent('window');
@ -100,37 +101,41 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
} }
async close() { async close() {
await this._channel.close(); return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel, stackTrace: ParsedStackTrace) => {
await channel.close();
});
} }
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> { async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate); return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel, stackTrace: ParsedStackTrace) => {
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate; const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const waiter = Waiter.createForEvent(this, 'electronApplication', event); const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`); const waiter = Waiter.createForEvent(this, event, stackTrace);
if (event !== Events.ElectronApplication.Close) waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new Error('Electron application closed')); if (event !== Events.ElectronApplication.Close)
const result = await waiter.waitForEvent(this, event, predicate as any); waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new Error('Electron application closed'));
waiter.dispose(); const result = await waiter.waitForEvent(this, event, predicate as any);
return result; waiter.dispose();
return result;
});
} }
async browserWindow(page: Page): Promise<JSHandle<BrowserWindow>> { async browserWindow(page: Page): Promise<JSHandle<BrowserWindow>> {
return this._wrapApiCall('electronApplication.browserWindow', async (channel: channels.ElectronApplicationChannel) => { return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel) => {
const result = await channel.browserWindow({ page: page._channel }); const result = await channel.browserWindow({ page: page._channel });
return JSHandle.from(result.handle); return JSHandle.from(result.handle);
}); });
} }
async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn<ElectronAppType, Arg, R>, arg: Arg): Promise<R> { async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn<ElectronAppType, Arg, R>, arg: Arg): Promise<R> {
return this._wrapApiCall('electronApplication.evaluate', async (channel: channels.ElectronApplicationChannel) => { return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel) => {
const result = await channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value); return parseResult(result.value);
}); });
} }
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunctionOn<ElectronAppType, Arg, R>, arg: Arg): Promise<structs.SmartHandle<R>> { async evaluateHandle<R, Arg>(pageFunction: structs.PageFunctionOn<ElectronAppType, Arg, R>, arg: Arg): Promise<structs.SmartHandle<R>> {
return this._wrapApiCall('electronApplication.evaluateHandle', async (channel: channels.ElectronApplicationChannel) => { return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel) => {
const result = await channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return JSHandle.from(result.handle) as any as structs.SmartHandle<R>; return JSHandle.from(result.handle) as any as structs.SmartHandle<R>;
}); });

View file

@ -47,185 +47,185 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements
} }
async ownerFrame(): Promise<Frame | null> { async ownerFrame(): Promise<Frame | null> {
return this._wrapApiCall('elementHandle.ownerFrame', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return Frame.fromNullable((await channel.ownerFrame()).frame); return Frame.fromNullable((await channel.ownerFrame()).frame);
}); });
} }
async contentFrame(): Promise<Frame | null> { async contentFrame(): Promise<Frame | null> {
return this._wrapApiCall('elementHandle.contentFrame', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return Frame.fromNullable((await channel.contentFrame()).frame); return Frame.fromNullable((await channel.contentFrame()).frame);
}); });
} }
async getAttribute(name: string): Promise<string | null> { async getAttribute(name: string): Promise<string | null> {
return this._wrapApiCall('elementHandle.getAttribute', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const value = (await channel.getAttribute({ name })).value; const value = (await channel.getAttribute({ name })).value;
return value === undefined ? null : value; return value === undefined ? null : value;
}); });
} }
async inputValue(): Promise<string> { async inputValue(): Promise<string> {
return this._wrapApiCall('elementHandle.inputValue', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.inputValue()).value; return (await channel.inputValue()).value;
}); });
} }
async textContent(): Promise<string | null> { async textContent(): Promise<string | null> {
return this._wrapApiCall('elementHandle.textContent', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const value = (await channel.textContent()).value; const value = (await channel.textContent()).value;
return value === undefined ? null : value; return value === undefined ? null : value;
}); });
} }
async innerText(): Promise<string> { async innerText(): Promise<string> {
return this._wrapApiCall('elementHandle.innerText', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.innerText()).value; return (await channel.innerText()).value;
}); });
} }
async innerHTML(): Promise<string> { async innerHTML(): Promise<string> {
return this._wrapApiCall('elementHandle.innerHTML', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.innerHTML()).value; return (await channel.innerHTML()).value;
}); });
} }
async isChecked(): Promise<boolean> { async isChecked(): Promise<boolean> {
return this._wrapApiCall('elementHandle.isChecked', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.isChecked()).value; return (await channel.isChecked()).value;
}); });
} }
async isDisabled(): Promise<boolean> { async isDisabled(): Promise<boolean> {
return this._wrapApiCall('elementHandle.isDisabled', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.isDisabled()).value; return (await channel.isDisabled()).value;
}); });
} }
async isEditable(): Promise<boolean> { async isEditable(): Promise<boolean> {
return this._wrapApiCall('elementHandle.isEditable', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.isEditable()).value; return (await channel.isEditable()).value;
}); });
} }
async isEnabled(): Promise<boolean> { async isEnabled(): Promise<boolean> {
return this._wrapApiCall('elementHandle.isEnabled', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.isEnabled()).value; return (await channel.isEnabled()).value;
}); });
} }
async isHidden(): Promise<boolean> { async isHidden(): Promise<boolean> {
return this._wrapApiCall('elementHandle.isHidden', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.isHidden()).value; return (await channel.isHidden()).value;
}); });
} }
async isVisible(): Promise<boolean> { async isVisible(): Promise<boolean> {
return this._wrapApiCall('elementHandle.isVisible', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return (await channel.isVisible()).value; return (await channel.isVisible()).value;
}); });
} }
async dispatchEvent(type: string, eventInit: Object = {}) { async dispatchEvent(type: string, eventInit: Object = {}) {
return this._wrapApiCall('elementHandle.dispatchEvent', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.dispatchEvent({ type, eventInit: serializeArgument(eventInit) }); await channel.dispatchEvent({ type, eventInit: serializeArgument(eventInit) });
}); });
} }
async scrollIntoViewIfNeeded(options: channels.ElementHandleScrollIntoViewIfNeededOptions = {}) { async scrollIntoViewIfNeeded(options: channels.ElementHandleScrollIntoViewIfNeededOptions = {}) {
return this._wrapApiCall('elementHandle.scrollIntoViewIfNeeded', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.scrollIntoViewIfNeeded(options); await channel.scrollIntoViewIfNeeded(options);
}); });
} }
async hover(options: channels.ElementHandleHoverOptions = {}): Promise<void> { async hover(options: channels.ElementHandleHoverOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.hover', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.hover(options); await channel.hover(options);
}); });
} }
async click(options: channels.ElementHandleClickOptions = {}): Promise<void> { async click(options: channels.ElementHandleClickOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.click', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return await channel.click(options); return await channel.click(options);
}); });
} }
async dblclick(options: channels.ElementHandleDblclickOptions = {}): Promise<void> { async dblclick(options: channels.ElementHandleDblclickOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.dblclick', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return await channel.dblclick(options); return await channel.dblclick(options);
}); });
} }
async tap(options: channels.ElementHandleTapOptions = {}): Promise<void> { async tap(options: channels.ElementHandleTapOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.tap', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return await channel.tap(options); return await channel.tap(options);
}); });
} }
async selectOption(values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise<string[]> { async selectOption(values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise<string[]> {
return this._wrapApiCall('elementHandle.selectOption', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const result = await channel.selectOption({ ...convertSelectOptionValues(values), ...options }); const result = await channel.selectOption({ ...convertSelectOptionValues(values), ...options });
return result.values; return result.values;
}); });
} }
async fill(value: string, options: channels.ElementHandleFillOptions = {}): Promise<void> { async fill(value: string, options: channels.ElementHandleFillOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.fill', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return await channel.fill({ value, ...options }); return await channel.fill({ value, ...options });
}); });
} }
async selectText(options: channels.ElementHandleSelectTextOptions = {}): Promise<void> { async selectText(options: channels.ElementHandleSelectTextOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.selectText', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.selectText(options); await channel.selectText(options);
}); });
} }
async setInputFiles(files: string | FilePayload | string[] | FilePayload[], options: channels.ElementHandleSetInputFilesOptions = {}) { async setInputFiles(files: string | FilePayload | string[] | FilePayload[], options: channels.ElementHandleSetInputFilesOptions = {}) {
return this._wrapApiCall('elementHandle.setInputFiles', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.setInputFiles({ files: await convertInputFiles(files), ...options }); await channel.setInputFiles({ files: await convertInputFiles(files), ...options });
}); });
} }
async focus(): Promise<void> { async focus(): Promise<void> {
return this._wrapApiCall('elementHandle.focus', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.focus(); await channel.focus();
}); });
} }
async type(text: string, options: channels.ElementHandleTypeOptions = {}): Promise<void> { async type(text: string, options: channels.ElementHandleTypeOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.type', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.type({ text, ...options }); await channel.type({ text, ...options });
}); });
} }
async press(key: string, options: channels.ElementHandlePressOptions = {}): Promise<void> { async press(key: string, options: channels.ElementHandlePressOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.press', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.press({ key, ...options }); await channel.press({ key, ...options });
}); });
} }
async check(options: channels.ElementHandleCheckOptions = {}) { async check(options: channels.ElementHandleCheckOptions = {}) {
return this._wrapApiCall('elementHandle.check', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return await channel.check(options); return await channel.check(options);
}); });
} }
async uncheck(options: channels.ElementHandleUncheckOptions = {}) { async uncheck(options: channels.ElementHandleUncheckOptions = {}) {
return this._wrapApiCall('elementHandle.uncheck', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return await channel.uncheck(options); return await channel.uncheck(options);
}); });
} }
async boundingBox(): Promise<Rect | null> { async boundingBox(): Promise<Rect | null> {
return this._wrapApiCall('elementHandle.boundingBox', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const value = (await channel.boundingBox()).value; const value = (await channel.boundingBox()).value;
return value === undefined ? null : value; return value === undefined ? null : value;
}); });
} }
async screenshot(options: channels.ElementHandleScreenshotOptions & { path?: string } = {}): Promise<Buffer> { async screenshot(options: channels.ElementHandleScreenshotOptions & { path?: string } = {}): Promise<Buffer> {
return this._wrapApiCall('elementHandle.screenshot', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const copy = { ...options }; const copy = { ...options };
if (!copy.type) if (!copy.type)
copy.type = determineScreenshotType(options); copy.type = determineScreenshotType(options);
@ -240,34 +240,34 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements
} }
async $(selector: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> { async $(selector: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this._wrapApiCall('elementHandle.$', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return ElementHandle.fromNullable((await channel.querySelector({ selector })).element) as ElementHandle<SVGElement | HTMLElement> | null; return ElementHandle.fromNullable((await channel.querySelector({ selector })).element) as ElementHandle<SVGElement | HTMLElement> | null;
}); });
} }
async $$(selector: string): Promise<ElementHandle<SVGElement | HTMLElement>[]> { async $$(selector: string): Promise<ElementHandle<SVGElement | HTMLElement>[]> {
return this._wrapApiCall('elementHandle.$$', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const result = await channel.querySelectorAll({ selector }); const result = await channel.querySelectorAll({ selector });
return result.elements.map(h => ElementHandle.from(h) as ElementHandle<SVGElement | HTMLElement>); return result.elements.map(h => ElementHandle.from(h) as ElementHandle<SVGElement | HTMLElement>);
}); });
} }
async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> { async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> {
return this._wrapApiCall('elementHandle.$eval', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const result = await channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value); return parseResult(result.value);
}); });
} }
async $$eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element[], Arg, R>, arg?: Arg): Promise<R> { async $$eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element[], Arg, R>, arg?: Arg): Promise<R> {
return this._wrapApiCall('elementHandle.$$eval', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const result = await channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value); return parseResult(result.value);
}); });
} }
async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled', options: channels.ElementHandleWaitForElementStateOptions = {}): Promise<void> { async waitForElementState(state: 'visible' | 'hidden' | 'stable' | 'enabled' | 'disabled', options: channels.ElementHandleWaitForElementStateOptions = {}): Promise<void> {
return this._wrapApiCall('elementHandle.waitForElementState', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return await channel.waitForElementState({ state, ...options }); return await channel.waitForElementState({ state, ...options });
}); });
} }
@ -275,7 +275,7 @@ export class ElementHandle<T extends Node = Node> extends JSHandle<T> implements
waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>; waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>;
waitForSelector(selector: string, options?: channels.ElementHandleWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>; waitForSelector(selector: string, options?: channels.ElementHandleWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>;
async waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions = {}): Promise<ElementHandle<SVGElement | HTMLElement> | null> { async waitForSelector(selector: string, options: channels.ElementHandleWaitForSelectorOptions = {}): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this._wrapApiCall('elementHandle.waitForSelector', async (channel: channels.ElementHandleChannel) => { return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
const result = await channel.waitForSelector({ selector, ...options }); const result = await channel.waitForSelector({ selector, ...options });
return ElementHandle.fromNullable(result.element) as ElementHandle<SVGElement | HTMLElement> | null; return ElementHandle.fromNullable(result.element) as ElementHandle<SVGElement | HTMLElement> | null;
}); });

View file

@ -44,7 +44,7 @@ export class FileChooser implements api.FileChooser {
} }
async setFiles(files: string | FilePayload | string[] | FilePayload[], options?: channels.ElementHandleSetInputFilesOptions) { async setFiles(files: string | FilePayload | string[] | FilePayload[], options?: channels.ElementHandleSetInputFilesOptions) {
return this._page._wrapApiCall('fileChooser.setFiles', async () => { return this._page._wrapApiCall(async () => {
return this._elementHandle.setInputFiles(files, options); return this._elementHandle.setInputFiles(files, options);
}); });
} }

View file

@ -30,6 +30,7 @@ import { LifecycleEvent, URLMatch, SelectOption, SelectOptionOptions, FilePayloa
import { urlMatches } from './clientHelper'; import { urlMatches } from './clientHelper';
import * as api from '../../types/types'; import * as api from '../../types/types';
import * as structs from '../../types/structs'; import * as structs from '../../types/structs';
import { ParsedStackTrace } from '../utils/stackTrace';
export type WaitForNavigationOptions = { export type WaitForNavigationOptions = {
timeout?: number, timeout?: number,
@ -82,23 +83,19 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
}); });
} }
private _apiName(method: string) {
return this._page!._isPageCall ? 'page.' + method : 'frame.' + method;
}
page(): Page { page(): Page {
return this._page!; return this._page!;
} }
async goto(url: string, options: channels.FrameGotoOptions = {}): Promise<network.Response | null> { async goto(url: string, options: channels.FrameGotoOptions = {}): Promise<network.Response | null> {
return this._wrapApiCall(this._apiName('goto'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return network.Response.fromNullable((await channel.goto({ url, ...options, waitUntil })).response); return network.Response.fromNullable((await channel.goto({ url, ...options, waitUntil })).response);
}); });
} }
private _setupNavigationWaiter(name: string, options: { timeout?: number }): Waiter { private _setupNavigationWaiter(options: { timeout?: number }, stackTrace: ParsedStackTrace): Waiter {
const waiter = new Waiter(this, name); const waiter = new Waiter(this, '', stackTrace);
if (this._page!.isClosed()) if (this._page!.isClosed())
waiter.rejectImmediately(new Error('Navigation failed because page was closed!')); waiter.rejectImmediately(new Error('Navigation failed because page was closed!'));
waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!')); waiter.rejectOnEvent(this._page!, Events.Page.Close, new Error('Navigation failed because page was closed!'));
@ -110,9 +107,9 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
} }
async waitForNavigation(options: WaitForNavigationOptions = {}): Promise<network.Response | null> { async waitForNavigation(options: WaitForNavigationOptions = {}): Promise<network.Response | null> {
return this._wrapApiCall(this._apiName('waitForNavigation'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel, stackTrace: ParsedStackTrace) => {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
const waiter = this._setupNavigationWaiter(this._apiName('waitForNavigation'), options); const waiter = this._setupNavigationWaiter(options, stackTrace);
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : ''; const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`); waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
@ -148,8 +145,8 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
state = verifyLoadState('state', state); state = verifyLoadState('state', state);
if (this._loadStates.has(state)) if (this._loadStates.has(state))
return; return;
return this._wrapApiCall(this._apiName('waitForLoadState'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel, stackTrace: ParsedStackTrace) => {
const waiter = this._setupNavigationWaiter(this._apiName('waitForLoadState'), options); const waiter = this._setupNavigationWaiter(options, stackTrace);
await waiter.waitForEvent<LifecycleEvent>(this._eventEmitter, 'loadstate', s => { await waiter.waitForEvent<LifecycleEvent>(this._eventEmitter, 'loadstate', s => {
waiter.log(` "${s}" event fired`); waiter.log(` "${s}" event fired`);
return s === state; return s === state;
@ -165,14 +162,14 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
} }
async frameElement(): Promise<ElementHandle> { async frameElement(): Promise<ElementHandle> {
return this._wrapApiCall(this._apiName('frameElement'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return ElementHandle.from((await channel.frameElement()).element); return ElementHandle.from((await channel.frameElement()).element);
}); });
} }
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> { async evaluateHandle<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> {
assertMaxArguments(arguments.length, 2); assertMaxArguments(arguments.length, 2);
return this._wrapApiCall(this._apiName('evaluateHandle'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const result = await channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return JSHandle.from(result.handle) as any as structs.SmartHandle<R>; return JSHandle.from(result.handle) as any as structs.SmartHandle<R>;
}); });
@ -180,14 +177,14 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
async evaluate<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> { async evaluate<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2); assertMaxArguments(arguments.length, 2);
return this._wrapApiCall(this._apiName('evaluate'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const result = await channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value); return parseResult(result.value);
}); });
} }
async $(selector: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> { async $(selector: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this._wrapApiCall(this._apiName('$'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const result = await channel.querySelector({ selector }); const result = await channel.querySelector({ selector });
return ElementHandle.fromNullable(result.element) as ElementHandle<SVGElement | HTMLElement> | null; return ElementHandle.fromNullable(result.element) as ElementHandle<SVGElement | HTMLElement> | null;
}); });
@ -196,7 +193,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
waitForSelector(selector: string, options: channels.FrameWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>; waitForSelector(selector: string, options: channels.FrameWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>;
waitForSelector(selector: string, options?: channels.FrameWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>; waitForSelector(selector: string, options?: channels.FrameWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>;
async waitForSelector(selector: string, options: channels.FrameWaitForSelectorOptions = {}): Promise<ElementHandle<SVGElement | HTMLElement> | null> { async waitForSelector(selector: string, options: channels.FrameWaitForSelectorOptions = {}): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this._wrapApiCall(this._apiName('waitForSelector'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
if ((options as any).visibility) if ((options as any).visibility)
throw new Error('options.visibility is not supported, did you mean options.state?'); throw new Error('options.visibility is not supported, did you mean options.state?');
if ((options as any).waitFor && (options as any).waitFor !== 'visible') if ((options as any).waitFor && (options as any).waitFor !== 'visible')
@ -207,14 +204,14 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
} }
async dispatchEvent(selector: string, type: string, eventInit?: any, options: channels.FrameDispatchEventOptions = {}): Promise<void> { async dispatchEvent(selector: string, type: string, eventInit?: any, options: channels.FrameDispatchEventOptions = {}): Promise<void> {
return this._wrapApiCall(this._apiName('dispatchEvent'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await channel.dispatchEvent({ selector, type, eventInit: serializeArgument(eventInit), ...options }); await channel.dispatchEvent({ selector, type, eventInit: serializeArgument(eventInit), ...options });
}); });
} }
async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> { async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3); assertMaxArguments(arguments.length, 3);
return this._wrapApiCall(this._apiName('$eval'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const result = await channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value); return parseResult(result.value);
}); });
@ -222,27 +219,27 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
async $$eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element[], Arg, R>, arg?: Arg): Promise<R> { async $$eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element[], Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3); assertMaxArguments(arguments.length, 3);
return this._wrapApiCall(this._apiName('$$eval'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const result = await channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value); return parseResult(result.value);
}); });
} }
async $$(selector: string): Promise<ElementHandle<SVGElement | HTMLElement>[]> { async $$(selector: string): Promise<ElementHandle<SVGElement | HTMLElement>[]> {
return this._wrapApiCall(this._apiName('$$'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const result = await channel.querySelectorAll({ selector }); const result = await channel.querySelectorAll({ selector });
return result.elements.map(e => ElementHandle.from(e) as ElementHandle<SVGElement | HTMLElement>); return result.elements.map(e => ElementHandle.from(e) as ElementHandle<SVGElement | HTMLElement>);
}); });
} }
async content(): Promise<string> { async content(): Promise<string> {
return this._wrapApiCall(this._apiName('content'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.content()).value; return (await channel.content()).value;
}); });
} }
async setContent(html: string, options: channels.FrameSetContentOptions = {}): Promise<void> { async setContent(html: string, options: channels.FrameSetContentOptions = {}): Promise<void> {
return this._wrapApiCall(this._apiName('setContent'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
await channel.setContent({ html, ...options, waitUntil }); await channel.setContent({ html, ...options, waitUntil });
}); });
@ -269,7 +266,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
} }
async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string } = {}): Promise<ElementHandle> { async addScriptTag(options: { url?: string, path?: string, content?: string, type?: string } = {}): Promise<ElementHandle> {
return this._wrapApiCall(this._apiName('addScriptTag'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const copy = { ...options }; const copy = { ...options };
if (copy.path) { if (copy.path) {
copy.content = (await fs.promises.readFile(copy.path)).toString(); copy.content = (await fs.promises.readFile(copy.path)).toString();
@ -280,7 +277,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
} }
async addStyleTag(options: { url?: string; path?: string; content?: string; } = {}): Promise<ElementHandle> { async addStyleTag(options: { url?: string; path?: string; content?: string; } = {}): Promise<ElementHandle> {
return this._wrapApiCall(this._apiName('addStyleTag'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const copy = { ...options }; const copy = { ...options };
if (copy.path) { if (copy.path) {
copy.content = (await fs.promises.readFile(copy.path)).toString(); copy.content = (await fs.promises.readFile(copy.path)).toString();
@ -291,153 +288,153 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
} }
async click(selector: string, options: channels.FrameClickOptions = {}) { async click(selector: string, options: channels.FrameClickOptions = {}) {
return this._wrapApiCall(this._apiName('click'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return await channel.click({ selector, ...options }); return await channel.click({ selector, ...options });
}); });
} }
async dblclick(selector: string, options: channels.FrameDblclickOptions = {}) { async dblclick(selector: string, options: channels.FrameDblclickOptions = {}) {
return this._wrapApiCall(this._apiName('dblclick'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return await channel.dblclick({ selector, ...options }); return await channel.dblclick({ selector, ...options });
}); });
} }
async tap(selector: string, options: channels.FrameTapOptions = {}) { async tap(selector: string, options: channels.FrameTapOptions = {}) {
return this._wrapApiCall(this._apiName('tap'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return await channel.tap({ selector, ...options }); return await channel.tap({ selector, ...options });
}); });
} }
async fill(selector: string, value: string, options: channels.FrameFillOptions = {}) { async fill(selector: string, value: string, options: channels.FrameFillOptions = {}) {
return this._wrapApiCall(this._apiName('fill'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return await channel.fill({ selector, value, ...options }); return await channel.fill({ selector, value, ...options });
}); });
} }
async focus(selector: string, options: channels.FrameFocusOptions = {}) { async focus(selector: string, options: channels.FrameFocusOptions = {}) {
return this._wrapApiCall(this._apiName('focus'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await channel.focus({ selector, ...options }); await channel.focus({ selector, ...options });
}); });
} }
async textContent(selector: string, options: channels.FrameTextContentOptions = {}): Promise<null|string> { async textContent(selector: string, options: channels.FrameTextContentOptions = {}): Promise<null|string> {
return this._wrapApiCall(this._apiName('textContent'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const value = (await channel.textContent({ selector, ...options })).value; const value = (await channel.textContent({ selector, ...options })).value;
return value === undefined ? null : value; return value === undefined ? null : value;
}); });
} }
async innerText(selector: string, options: channels.FrameInnerTextOptions = {}): Promise<string> { async innerText(selector: string, options: channels.FrameInnerTextOptions = {}): Promise<string> {
return this._wrapApiCall(this._apiName('innerText'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.innerText({ selector, ...options })).value; return (await channel.innerText({ selector, ...options })).value;
}); });
} }
async innerHTML(selector: string, options: channels.FrameInnerHTMLOptions = {}): Promise<string> { async innerHTML(selector: string, options: channels.FrameInnerHTMLOptions = {}): Promise<string> {
return this._wrapApiCall(this._apiName('innerHTML'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.innerHTML({ selector, ...options })).value; return (await channel.innerHTML({ selector, ...options })).value;
}); });
} }
async getAttribute(selector: string, name: string, options: channels.FrameGetAttributeOptions = {}): Promise<string | null> { async getAttribute(selector: string, name: string, options: channels.FrameGetAttributeOptions = {}): Promise<string | null> {
return this._wrapApiCall(this._apiName('getAttribute'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const value = (await channel.getAttribute({ selector, name, ...options })).value; const value = (await channel.getAttribute({ selector, name, ...options })).value;
return value === undefined ? null : value; return value === undefined ? null : value;
}); });
} }
async inputValue(selector: string, options: channels.FrameInputValueOptions = {}): Promise<string> { async inputValue(selector: string, options: channels.FrameInputValueOptions = {}): Promise<string> {
return this._wrapApiCall(this._apiName('inputValue'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.inputValue({ selector, ...options })).value; return (await channel.inputValue({ selector, ...options })).value;
}); });
} }
async isChecked(selector: string, options: channels.FrameIsCheckedOptions = {}): Promise<boolean> { async isChecked(selector: string, options: channels.FrameIsCheckedOptions = {}): Promise<boolean> {
return this._wrapApiCall(this._apiName('isChecked'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.isChecked({ selector, ...options })).value; return (await channel.isChecked({ selector, ...options })).value;
}); });
} }
async isDisabled(selector: string, options: channels.FrameIsDisabledOptions = {}): Promise<boolean> { async isDisabled(selector: string, options: channels.FrameIsDisabledOptions = {}): Promise<boolean> {
return this._wrapApiCall(this._apiName('isDisabled'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.isDisabled({ selector, ...options })).value; return (await channel.isDisabled({ selector, ...options })).value;
}); });
} }
async isEditable(selector: string, options: channels.FrameIsEditableOptions = {}): Promise<boolean> { async isEditable(selector: string, options: channels.FrameIsEditableOptions = {}): Promise<boolean> {
return this._wrapApiCall(this._apiName('isEditable'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.isEditable({ selector, ...options })).value; return (await channel.isEditable({ selector, ...options })).value;
}); });
} }
async isEnabled(selector: string, options: channels.FrameIsEnabledOptions = {}): Promise<boolean> { async isEnabled(selector: string, options: channels.FrameIsEnabledOptions = {}): Promise<boolean> {
return this._wrapApiCall(this._apiName('isEnabled'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.isEnabled({ selector, ...options })).value; return (await channel.isEnabled({ selector, ...options })).value;
}); });
} }
async isHidden(selector: string, options: channels.FrameIsHiddenOptions = {}): Promise<boolean> { async isHidden(selector: string, options: channels.FrameIsHiddenOptions = {}): Promise<boolean> {
return this._wrapApiCall(this._apiName('isHidden'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.isHidden({ selector, ...options })).value; return (await channel.isHidden({ selector, ...options })).value;
}); });
} }
async isVisible(selector: string, options: channels.FrameIsVisibleOptions = {}): Promise<boolean> { async isVisible(selector: string, options: channels.FrameIsVisibleOptions = {}): Promise<boolean> {
return this._wrapApiCall(this._apiName('isVisible'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.isVisible({ selector, ...options })).value; return (await channel.isVisible({ selector, ...options })).value;
}); });
} }
async hover(selector: string, options: channels.FrameHoverOptions = {}) { async hover(selector: string, options: channels.FrameHoverOptions = {}) {
return this._wrapApiCall(this._apiName('hover'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await channel.hover({ selector, ...options }); await channel.hover({ selector, ...options });
}); });
} }
async selectOption(selector: string, values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise<string[]> { async selectOption(selector: string, values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options: SelectOptionOptions = {}): Promise<string[]> {
return this._wrapApiCall(this._apiName('selectOption'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.selectOption({ selector, ...convertSelectOptionValues(values), ...options })).values; return (await channel.selectOption({ selector, ...convertSelectOptionValues(values), ...options })).values;
}); });
} }
async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options: channels.FrameSetInputFilesOptions = {}): Promise<void> { async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options: channels.FrameSetInputFilesOptions = {}): Promise<void> {
return this._wrapApiCall(this._apiName('setInputFiles'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await channel.setInputFiles({ selector, files: await convertInputFiles(files), ...options }); await channel.setInputFiles({ selector, files: await convertInputFiles(files), ...options });
}); });
} }
async type(selector: string, text: string, options: channels.FrameTypeOptions = {}) { async type(selector: string, text: string, options: channels.FrameTypeOptions = {}) {
return this._wrapApiCall(this._apiName('type'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await channel.type({ selector, text, ...options }); await channel.type({ selector, text, ...options });
}); });
} }
async press(selector: string, key: string, options: channels.FramePressOptions = {}) { async press(selector: string, key: string, options: channels.FramePressOptions = {}) {
return this._wrapApiCall(this._apiName('press'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await channel.press({ selector, key, ...options }); await channel.press({ selector, key, ...options });
}); });
} }
async check(selector: string, options: channels.FrameCheckOptions = {}) { async check(selector: string, options: channels.FrameCheckOptions = {}) {
return this._wrapApiCall(this._apiName('check'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await channel.check({ selector, ...options }); await channel.check({ selector, ...options });
}); });
} }
async uncheck(selector: string, options: channels.FrameUncheckOptions = {}) { async uncheck(selector: string, options: channels.FrameUncheckOptions = {}) {
return this._wrapApiCall(this._apiName('uncheck'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await channel.uncheck({ selector, ...options }); await channel.uncheck({ selector, ...options });
}); });
} }
async waitForTimeout(timeout: number) { async waitForTimeout(timeout: number) {
return this._wrapApiCall(this._apiName('waitForTimeout'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
await new Promise(fulfill => setTimeout(fulfill, timeout)); await new Promise(fulfill => setTimeout(fulfill, timeout));
}); });
} }
async waitForFunction<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg, options: WaitForFunctionOptions = {}): Promise<structs.SmartHandle<R>> { async waitForFunction<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg, options: WaitForFunctionOptions = {}): Promise<structs.SmartHandle<R>> {
return this._wrapApiCall(this._apiName('waitForFunction'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
if (typeof options.polling === 'string') if (typeof options.polling === 'string')
assert(options.polling === 'raf', 'Unknown polling option: ' + options.polling); assert(options.polling === 'raf', 'Unknown polling option: ' + options.polling);
const result = await channel.waitForFunction({ const result = await channel.waitForFunction({
@ -452,7 +449,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
} }
async title(): Promise<string> { async title(): Promise<string> {
return this._wrapApiCall(this._apiName('title'), async (channel: channels.FrameChannel) => { return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return (await channel.title()).value; return (await channel.title()).value;
}); });
} }

View file

@ -34,28 +34,28 @@ export class JSHandle<T = any> extends ChannelOwner<channels.JSHandleChannel, ch
} }
async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn<T, Arg, R>, arg?: Arg): Promise<R> { async evaluate<R, Arg>(pageFunction: structs.PageFunctionOn<T, Arg, R>, arg?: Arg): Promise<R> {
return this._wrapApiCall('jsHandle.evaluate', async (channel: channels.JSHandleChannel) => { return this._wrapApiCall(async (channel: channels.JSHandleChannel) => {
const result = await channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value); return parseResult(result.value);
}); });
} }
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunctionOn<T, Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> { async evaluateHandle<R, Arg>(pageFunction: structs.PageFunctionOn<T, Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> {
return this._wrapApiCall('jsHandle.evaluateHandle', async (channel: channels.JSHandleChannel) => { return this._wrapApiCall(async (channel: channels.JSHandleChannel) => {
const result = await channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return JSHandle.from(result.handle) as any as structs.SmartHandle<R>; return JSHandle.from(result.handle) as any as structs.SmartHandle<R>;
}); });
} }
async getProperty(propertyName: string): Promise<JSHandle> { async getProperty(propertyName: string): Promise<JSHandle> {
return this._wrapApiCall('jsHandle.getProperty', async (channel: channels.JSHandleChannel) => { return this._wrapApiCall(async (channel: channels.JSHandleChannel) => {
const result = await channel.getProperty({ name: propertyName }); const result = await channel.getProperty({ name: propertyName });
return JSHandle.from(result.handle); return JSHandle.from(result.handle);
}); });
} }
async getProperties(): Promise<Map<string, JSHandle>> { async getProperties(): Promise<Map<string, JSHandle>> {
return this._wrapApiCall('jsHandle.getProperties', async (channel: channels.JSHandleChannel) => { return this._wrapApiCall(async (channel: channels.JSHandleChannel) => {
const map = new Map<string, JSHandle>(); const map = new Map<string, JSHandle>();
for (const { name, value } of (await channel.getPropertyList()).properties) for (const { name, value } of (await channel.getPropertyList()).properties)
map.set(name, JSHandle.from(value)); map.set(name, JSHandle.from(value));
@ -64,7 +64,7 @@ export class JSHandle<T = any> extends ChannelOwner<channels.JSHandleChannel, ch
} }
async jsonValue(): Promise<T> { async jsonValue(): Promise<T> {
return this._wrapApiCall('jsHandle.jsonValue', async (channel: channels.JSHandleChannel) => { return this._wrapApiCall(async (channel: channels.JSHandleChannel) => {
return parseResult((await channel.jsonValue()).value); return parseResult((await channel.jsonValue()).value);
}); });
} }
@ -74,7 +74,7 @@ export class JSHandle<T = any> extends ChannelOwner<channels.JSHandleChannel, ch
} }
async dispose() { async dispose() {
return this._wrapApiCall('jsHandle.dispose', async (channel: channels.JSHandleChannel) => { return this._wrapApiCall(async (channel: channels.JSHandleChannel) => {
return await channel.dispose(); return await channel.dispose();
}); });
} }

View file

@ -26,6 +26,7 @@ import { Events } from './events';
import { Page } from './page'; import { Page } from './page';
import { Waiter } from './waiter'; import { Waiter } from './waiter';
import * as api from '../../types/types'; import * as api from '../../types/types';
import { ParsedStackTrace } from '../utils/stackTrace';
export type NetworkCookie = { export type NetworkCookie = {
name: string, name: string,
@ -132,7 +133,7 @@ export class Request extends ChannelOwner<channels.RequestChannel, channels.Requ
} }
async response(): Promise<Response | null> { async response(): Promise<Response | null> {
return this._wrapApiCall('request.response', async (channel: channels.RequestChannel) => { return this._wrapApiCall(async (channel: channels.RequestChannel) => {
return Response.fromNullable((await channel.response()).response); return Response.fromNullable((await channel.response()).response);
}); });
} }
@ -258,13 +259,13 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
} }
async abort(errorCode?: string) { async abort(errorCode?: string) {
return this._wrapApiCall('route.abort', async (channel: channels.RouteChannel) => { return this._wrapApiCall(async (channel: channels.RouteChannel) => {
await channel.abort({ errorCode }); await channel.abort({ errorCode });
}); });
} }
async fulfill(options: { status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) { async fulfill(options: { status?: number, headers?: Headers, contentType?: string, body?: string | Buffer, path?: string } = {}) {
return this._wrapApiCall('route.fulfill', async (channel: channels.RouteChannel) => { return this._wrapApiCall(async (channel: channels.RouteChannel) => {
let body = ''; let body = '';
let isBase64 = false; let isBase64 = false;
let length = 0; let length = 0;
@ -303,17 +304,17 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
} }
async intercept(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer, interceptResponse?: boolean } = {}): Promise<api.Response> { async intercept(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer, interceptResponse?: boolean } = {}): Promise<api.Response> {
return await this._continue('route.intercept', options, true); return await this._continue(options, true);
} }
async continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer } = {}) { async continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer } = {}) {
await this._continue('route.continue', options, false); await this._continue(options, false);
} }
async _continue(apiName: string, options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: NotInterceptResponse): Promise<null>; async _continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: NotInterceptResponse): Promise<null>;
async _continue(apiName: string, options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: InterceptResponse): Promise<api.Response>; async _continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: InterceptResponse): Promise<api.Response>;
async _continue(apiName: string, options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: boolean): Promise<null|api.Response> { async _continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: boolean): Promise<null|api.Response> {
return await this._wrapApiCall(apiName, async (channel: channels.RouteChannel) => { return await this._wrapApiCall(async (channel: channels.RouteChannel) => {
const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData; const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData;
const result = await channel.continue({ const result = await channel.continue({
url: options.url, url: options.url,
@ -329,7 +330,7 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
} }
async _responseBody(): Promise<Buffer> { async _responseBody(): Promise<Buffer> {
return this._wrapApiCall('response.body', async (channel: channels.RouteChannel) => { return this._wrapApiCall(async (channel: channels.RouteChannel) => {
return Buffer.from((await channel.responseBody()).binary, 'base64'); return Buffer.from((await channel.responseBody()).binary, 'base64');
}); });
} }
@ -390,14 +391,16 @@ export class Response extends ChannelOwner<channels.ResponseChannel, channels.Re
} }
async finished(): Promise<Error | null> { async finished(): Promise<Error | null> {
const result = await this._channel.finished(); return this._wrapApiCall(async (channel: channels.ResponseChannel) => {
if (result.error) const result = await channel.finished();
return new Error(result.error); if (result.error)
return null; return new Error(result.error);
return null;
});
} }
async body(): Promise<Buffer> { async body(): Promise<Buffer> {
return this._wrapApiCall('response.body', async (channel: channels.ResponseChannel) => { return this._wrapApiCall(async (channel: channels.ResponseChannel) => {
return Buffer.from((await channel.body()).binary, 'base64'); return Buffer.from((await channel.body()).binary, 'base64');
}); });
} }
@ -421,11 +424,15 @@ export class Response extends ChannelOwner<channels.ResponseChannel, channels.Re
} }
async serverAddr(): Promise<RemoteAddr|null> { async serverAddr(): Promise<RemoteAddr|null> {
return (await this._channel.serverAddr()).value || null; return this._wrapApiCall(async (channel: channels.ResponseChannel) => {
return (await channel.serverAddr()).value || null;
});
} }
async securityDetails(): Promise<SecurityDetails|null> { async securityDetails(): Promise<SecurityDetails|null> {
return (await this._channel.securityDetails()).value || null; return this._wrapApiCall(async (channel: channels.ResponseChannel) => {
return (await channel.securityDetails()).value || null;
});
} }
} }
@ -469,18 +476,20 @@ export class WebSocket extends ChannelOwner<channels.WebSocketChannel, channels.
} }
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> { async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate); return this._wrapApiCall(async (channel: channels.WebSocketChannel, stackTrace: ParsedStackTrace) => {
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate; const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const waiter = Waiter.createForEvent(this, 'webSocket', event); const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`); const waiter = Waiter.createForEvent(this, event, stackTrace);
if (event !== Events.WebSocket.Error) waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
waiter.rejectOnEvent(this, Events.WebSocket.Error, new Error('Socket error')); if (event !== Events.WebSocket.Error)
if (event !== Events.WebSocket.Close) waiter.rejectOnEvent(this, Events.WebSocket.Error, new Error('Socket error'));
waiter.rejectOnEvent(this, Events.WebSocket.Close, new Error('Socket closed')); if (event !== Events.WebSocket.Close)
waiter.rejectOnEvent(this._page, Events.Page.Close, new Error('Page closed')); waiter.rejectOnEvent(this, Events.WebSocket.Close, new Error('Socket closed'));
const result = await waiter.waitForEvent(this, event, predicate as any); waiter.rejectOnEvent(this._page, Events.Page.Close, new Error('Page closed'));
waiter.dispose(); const result = await waiter.waitForEvent(this, event, predicate as any);
return result; waiter.dispose();
return result;
});
} }
} }

View file

@ -46,6 +46,7 @@ import { isString, isRegExp, isObject, mkdirIfNeeded, headersObjectToArray } fro
import { isSafeCloseError } from '../utils/errors'; import { isSafeCloseError } from '../utils/errors';
import { Video } from './video'; import { Video } from './video';
import { Artifact } from './artifact'; import { Artifact } from './artifact';
import { ParsedStackTrace } from '../utils/stackTrace';
type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> & { type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> & {
width?: string | number, width?: string | number,
@ -80,7 +81,6 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>(); readonly _bindings = new Map<string, (source: structs.BindingSource, ...args: any[]) => any>();
readonly _timeoutSettings: TimeoutSettings; readonly _timeoutSettings: TimeoutSettings;
_isPageCall = false;
private _video: Video | null = null; private _video: Video | null = null;
readonly _opener: Page | null; readonly _opener: Page | null;
@ -121,7 +121,6 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
this._channel.on('download', ({ url, suggestedFilename, artifact }) => { this._channel.on('download', ({ url, suggestedFilename, artifact }) => {
const artifactObject = Artifact.from(artifact); const artifactObject = Artifact.from(artifact);
artifactObject._isRemote = !!this._browserContext._browser && !!this._browserContext._browser._remoteType; artifactObject._isRemote = !!this._browserContext._browser && !!this._browserContext._browser._remoteType;
artifactObject._apiName = 'download';
this.emit(Events.Page.Download, new Download(this, url, suggestedFilename, artifactObject)); this.emit(Events.Page.Download, new Download(this, url, suggestedFilename, artifactObject));
}); });
this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple))); this._channel.on('fileChooser', ({ element, isMultiple }) => this.emit(Events.Page.FileChooser, new FileChooser(this, ElementHandle.from(element), isMultiple)));
@ -132,7 +131,6 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request))); this._channel.on('route', ({ route, request }) => this._onRoute(Route.from(route), Request.from(request)));
this._channel.on('video', ({ artifact }) => { this._channel.on('video', ({ artifact }) => {
const artifactObject = Artifact.from(artifact); const artifactObject = Artifact.from(artifact);
artifactObject._apiName = 'video';
this._forceVideo()._artifactReady(artifactObject); this._forceVideo()._artifactReady(artifactObject);
}); });
this._channel.on('webSocket', ({ webSocket }) => this.emit(Events.Page.WebSocket, WebSocket.from(webSocket))); this._channel.on('webSocket', ({ webSocket }) => this.emit(Events.Page.WebSocket, WebSocket.from(webSocket)));
@ -252,58 +250,49 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
return this._forceVideo(); return this._forceVideo();
} }
private _attributeToPage<T>(func: () => T): T {
try {
this._isPageCall = true;
return func();
} finally {
this._isPageCall = false;
}
}
async $(selector: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> { async $(selector: string): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this._attributeToPage(() => this._mainFrame.$(selector)); return this._mainFrame.$(selector);
} }
waitForSelector(selector: string, options: channels.FrameWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>; waitForSelector(selector: string, options: channels.FrameWaitForSelectorOptions & { state: 'attached' | 'visible' }): Promise<ElementHandle<SVGElement | HTMLElement>>;
waitForSelector(selector: string, options?: channels.FrameWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>; waitForSelector(selector: string, options?: channels.FrameWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null>;
async waitForSelector(selector: string, options?: channels.FrameWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null> { async waitForSelector(selector: string, options?: channels.FrameWaitForSelectorOptions): Promise<ElementHandle<SVGElement | HTMLElement> | null> {
return this._attributeToPage(() => this._mainFrame.waitForSelector(selector, options)); return this._mainFrame.waitForSelector(selector, options);
} }
async dispatchEvent(selector: string, type: string, eventInit?: any, options?: channels.FrameDispatchEventOptions): Promise<void> { async dispatchEvent(selector: string, type: string, eventInit?: any, options?: channels.FrameDispatchEventOptions): Promise<void> {
return this._attributeToPage(() => this._mainFrame.dispatchEvent(selector, type, eventInit, options)); return this._mainFrame.dispatchEvent(selector, type, eventInit, options);
} }
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> { async evaluateHandle<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> {
assertMaxArguments(arguments.length, 2); assertMaxArguments(arguments.length, 2);
return this._attributeToPage(() => this._mainFrame.evaluateHandle(pageFunction, arg)); return this._mainFrame.evaluateHandle(pageFunction, arg);
} }
async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> { async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3); assertMaxArguments(arguments.length, 3);
return this._attributeToPage(() => this._mainFrame.$eval(selector, pageFunction, arg)); return this._mainFrame.$eval(selector, pageFunction, arg);
} }
async $$eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element[], Arg, R>, arg?: Arg): Promise<R> { async $$eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element[], Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 3); assertMaxArguments(arguments.length, 3);
return this._attributeToPage(() => this._mainFrame.$$eval(selector, pageFunction, arg)); return this._mainFrame.$$eval(selector, pageFunction, arg);
} }
async $$(selector: string): Promise<ElementHandle<SVGElement | HTMLElement>[]> { async $$(selector: string): Promise<ElementHandle<SVGElement | HTMLElement>[]> {
return this._attributeToPage(() => this._mainFrame.$$(selector)); return this._mainFrame.$$(selector);
} }
async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; } = {}): Promise<ElementHandle> { async addScriptTag(options: { url?: string; path?: string; content?: string; type?: string; } = {}): Promise<ElementHandle> {
return this._attributeToPage(() => this._mainFrame.addScriptTag(options)); return this._mainFrame.addScriptTag(options);
} }
async addStyleTag(options: { url?: string; path?: string; content?: string; } = {}): Promise<ElementHandle> { async addStyleTag(options: { url?: string; path?: string; content?: string; } = {}): Promise<ElementHandle> {
return this._attributeToPage(() => this._mainFrame.addStyleTag(options)); return this._mainFrame.addStyleTag(options);
} }
async exposeFunction(name: string, callback: Function) { async exposeFunction(name: string, callback: Function) {
return this._wrapApiCall('page.exposeFunction', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.exposeBinding({ name }); await channel.exposeBinding({ name });
const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args); const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args);
this._bindings.set(name, binding); this._bindings.set(name, binding);
@ -311,56 +300,56 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
} }
async exposeBinding(name: string, callback: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}) { async exposeBinding(name: string, callback: (source: structs.BindingSource, ...args: any[]) => any, options: { handle?: boolean } = {}) {
return this._wrapApiCall('page.exposeBinding', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.exposeBinding({ name, needsHandle: options.handle }); await channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, callback); this._bindings.set(name, callback);
}); });
} }
async setExtraHTTPHeaders(headers: Headers) { async setExtraHTTPHeaders(headers: Headers) {
return this._wrapApiCall('page.setExtraHTTPHeaders', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
validateHeaders(headers); validateHeaders(headers);
await channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) }); await channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
}); });
} }
url(): string { url(): string {
return this._attributeToPage(() => this._mainFrame.url()); return this._mainFrame.url();
} }
async content(): Promise<string> { async content(): Promise<string> {
return this._attributeToPage(() => this._mainFrame.content()); return this._mainFrame.content();
} }
async setContent(html: string, options?: channels.FrameSetContentOptions): Promise<void> { async setContent(html: string, options?: channels.FrameSetContentOptions): Promise<void> {
return this._attributeToPage(() => this._mainFrame.setContent(html, options)); return this._mainFrame.setContent(html, options);
} }
async goto(url: string, options?: channels.FrameGotoOptions): Promise<Response | null> { async goto(url: string, options?: channels.FrameGotoOptions): Promise<Response | null> {
return this._attributeToPage(() => this._mainFrame.goto(url, options)); return this._mainFrame.goto(url, options);
} }
async reload(options: channels.PageReloadOptions = {}): Promise<Response | null> { async reload(options: channels.PageReloadOptions = {}): Promise<Response | null> {
return this._wrapApiCall('page.reload', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return Response.fromNullable((await channel.reload({ ...options, waitUntil })).response); return Response.fromNullable((await channel.reload({ ...options, waitUntil })).response);
}); });
} }
async waitForLoadState(state?: LifecycleEvent, options?: { timeout?: number }): Promise<void> { async waitForLoadState(state?: LifecycleEvent, options?: { timeout?: number }): Promise<void> {
return this._attributeToPage(() => this._mainFrame.waitForLoadState(state, options)); return this._mainFrame.waitForLoadState(state, options);
} }
async waitForNavigation(options?: WaitForNavigationOptions): Promise<Response | null> { async waitForNavigation(options?: WaitForNavigationOptions): Promise<Response | null> {
return this._attributeToPage(() => this._mainFrame.waitForNavigation(options)); return this._mainFrame.waitForNavigation(options);
} }
async waitForURL(url: URLMatch, options?: { waitUntil?: LifecycleEvent, timeout?: number }): Promise<void> { async waitForURL(url: URLMatch, options?: { waitUntil?: LifecycleEvent, timeout?: number }): Promise<void> {
return this._attributeToPage(() => this._mainFrame.waitForURL(url, options)); return this._mainFrame.waitForURL(url, options);
} }
async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<Request> { async waitForRequest(urlOrPredicate: string | RegExp | ((r: Request) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<Request> {
return this._wrapApiCall('page.waitForRequest', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
const predicate = (request: Request) => { const predicate = (request: Request) => {
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate)) if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
return urlMatches(request.url(), urlOrPredicate); return urlMatches(request.url(), urlOrPredicate);
@ -368,12 +357,12 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}; };
const trimmedUrl = trimUrl(urlOrPredicate); const trimmedUrl = trimUrl(urlOrPredicate);
const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : undefined; const logLine = trimmedUrl ? `waiting for request ${trimmedUrl}` : undefined;
return this._waitForEvent(Events.Page.Request, { predicate, timeout: options.timeout }, logLine); return this._waitForEvent(Events.Page.Request, { predicate, timeout: options.timeout }, stackTrace, logLine);
}); });
} }
async waitForResponse(urlOrPredicate: string | RegExp | ((r: Response) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<Response> { async waitForResponse(urlOrPredicate: string | RegExp | ((r: Response) => boolean | Promise<boolean>), options: { timeout?: number } = {}): Promise<Response> {
return this._wrapApiCall('page.waitForResponse', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
const predicate = (response: Response) => { const predicate = (response: Response) => {
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate)) if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
return urlMatches(response.url(), urlOrPredicate); return urlMatches(response.url(), urlOrPredicate);
@ -381,20 +370,20 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}; };
const trimmedUrl = trimUrl(urlOrPredicate); const trimmedUrl = trimUrl(urlOrPredicate);
const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : undefined; const logLine = trimmedUrl ? `waiting for response ${trimmedUrl}` : undefined;
return this._waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout }, logLine); return this._waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout }, stackTrace, logLine);
}); });
} }
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> { async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
return this._wrapApiCall('page.waitForEvent', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
return this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`); return this._waitForEvent(event, optionsOrPredicate, stackTrace, `waiting for event "${event}"`);
}); });
} }
private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, logLine?: string): Promise<any> { private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, stackTrace: ParsedStackTrace, logLine?: string): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate); const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate; const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, 'page', event); const waiter = Waiter.createForEvent(this, event, stackTrace);
if (logLine) if (logLine)
waiter.log(logLine); waiter.log(logLine);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`); waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
@ -408,21 +397,21 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
} }
async goBack(options: channels.PageGoBackOptions = {}): Promise<Response | null> { async goBack(options: channels.PageGoBackOptions = {}): Promise<Response | null> {
return this._wrapApiCall('page.goBack', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return Response.fromNullable((await channel.goBack({ ...options, waitUntil })).response); return Response.fromNullable((await channel.goBack({ ...options, waitUntil })).response);
}); });
} }
async goForward(options: channels.PageGoForwardOptions = {}): Promise<Response | null> { async goForward(options: channels.PageGoForwardOptions = {}): Promise<Response | null> {
return this._wrapApiCall('page.goForward', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil); const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return Response.fromNullable((await channel.goForward({ ...options, waitUntil })).response); return Response.fromNullable((await channel.goForward({ ...options, waitUntil })).response);
}); });
} }
async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null } = {}) { async emulateMedia(options: { media?: 'screen' | 'print' | null, colorScheme?: 'dark' | 'light' | 'no-preference' | null, reducedMotion?: 'reduce' | 'no-preference' | null } = {}) {
return this._wrapApiCall('page.emulateMedia', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.emulateMedia({ await channel.emulateMedia({
media: options.media === null ? 'null' : options.media, media: options.media === null ? 'null' : options.media,
colorScheme: options.colorScheme === null ? 'null' : options.colorScheme, colorScheme: options.colorScheme === null ? 'null' : options.colorScheme,
@ -432,7 +421,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
} }
async setViewportSize(viewportSize: Size) { async setViewportSize(viewportSize: Size) {
return this._wrapApiCall('page.setViewportSize', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
this._viewportSize = viewportSize; this._viewportSize = viewportSize;
await channel.setViewportSize({ viewportSize }); await channel.setViewportSize({ viewportSize });
}); });
@ -444,18 +433,18 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
async evaluate<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> { async evaluate<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2); assertMaxArguments(arguments.length, 2);
return this._attributeToPage(() => this._mainFrame.evaluate(pageFunction, arg)); return this._mainFrame.evaluate(pageFunction, arg);
} }
async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) { async addInitScript(script: Function | string | { path?: string, content?: string }, arg?: any) {
return this._wrapApiCall('page.addInitScript', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
const source = await evaluationScript(script, arg); const source = await evaluationScript(script, arg);
await channel.addInitScript({ source }); await channel.addInitScript({ source });
}); });
} }
async route(url: URLMatch, handler: RouteHandler): Promise<void> { async route(url: URLMatch, handler: RouteHandler): Promise<void> {
return this._wrapApiCall('page.route', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
this._routes.push({ url, handler }); this._routes.push({ url, handler });
if (this._routes.length === 1) if (this._routes.length === 1)
await channel.setNetworkInterceptionEnabled({ enabled: true }); await channel.setNetworkInterceptionEnabled({ enabled: true });
@ -463,7 +452,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
} }
async unroute(url: URLMatch, handler?: RouteHandler): Promise<void> { async unroute(url: URLMatch, handler?: RouteHandler): Promise<void> {
return this._wrapApiCall('page.unroute', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler)); this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (this._routes.length === 0) if (this._routes.length === 0)
await channel.setNetworkInterceptionEnabled({ enabled: false }); await channel.setNetworkInterceptionEnabled({ enabled: false });
@ -471,7 +460,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
} }
async screenshot(options: channels.PageScreenshotOptions & { path?: string } = {}): Promise<Buffer> { async screenshot(options: channels.PageScreenshotOptions & { path?: string } = {}): Promise<Buffer> {
return this._wrapApiCall('page.screenshot', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
const copy = { ...options }; const copy = { ...options };
if (!copy.type) if (!copy.type)
copy.type = determineScreenshotType(options); copy.type = determineScreenshotType(options);
@ -486,18 +475,18 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
} }
async title(): Promise<string> { async title(): Promise<string> {
return this._attributeToPage(() => this._mainFrame.title()); return this._mainFrame.title();
} }
async bringToFront(): Promise<void> { async bringToFront(): Promise<void> {
return this._wrapApiCall('page.bringToFront', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.bringToFront(); await channel.bringToFront();
}); });
} }
async close(options: { runBeforeUnload?: boolean } = {runBeforeUnload: undefined}) { async close(options: { runBeforeUnload?: boolean } = {runBeforeUnload: undefined}) {
try { try {
await this._wrapApiCall('page.close', async (channel: channels.PageChannel) => { await this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.close(options); await channel.close(options);
if (this._ownedContext) if (this._ownedContext)
await this._ownedContext.close(); await this._ownedContext.close();
@ -514,103 +503,103 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
} }
async click(selector: string, options?: channels.FrameClickOptions) { async click(selector: string, options?: channels.FrameClickOptions) {
return this._attributeToPage(() => this._mainFrame.click(selector, options)); return this._mainFrame.click(selector, options);
} }
async dblclick(selector: string, options?: channels.FrameDblclickOptions) { async dblclick(selector: string, options?: channels.FrameDblclickOptions) {
return this._attributeToPage(() => this._mainFrame.dblclick(selector, options)); return this._mainFrame.dblclick(selector, options);
} }
async tap(selector: string, options?: channels.FrameTapOptions) { async tap(selector: string, options?: channels.FrameTapOptions) {
return this._attributeToPage(() => this._mainFrame.tap(selector, options)); return this._mainFrame.tap(selector, options);
} }
async fill(selector: string, value: string, options?: channels.FrameFillOptions) { async fill(selector: string, value: string, options?: channels.FrameFillOptions) {
return this._attributeToPage(() => this._mainFrame.fill(selector, value, options)); return this._mainFrame.fill(selector, value, options);
} }
async focus(selector: string, options?: channels.FrameFocusOptions) { async focus(selector: string, options?: channels.FrameFocusOptions) {
return this._attributeToPage(() => this._mainFrame.focus(selector, options)); return this._mainFrame.focus(selector, options);
} }
async textContent(selector: string, options?: channels.FrameTextContentOptions): Promise<null|string> { async textContent(selector: string, options?: channels.FrameTextContentOptions): Promise<null|string> {
return this._attributeToPage(() => this._mainFrame.textContent(selector, options)); return this._mainFrame.textContent(selector, options);
} }
async innerText(selector: string, options?: channels.FrameInnerTextOptions): Promise<string> { async innerText(selector: string, options?: channels.FrameInnerTextOptions): Promise<string> {
return this._attributeToPage(() => this._mainFrame.innerText(selector, options)); return this._mainFrame.innerText(selector, options);
} }
async innerHTML(selector: string, options?: channels.FrameInnerHTMLOptions): Promise<string> { async innerHTML(selector: string, options?: channels.FrameInnerHTMLOptions): Promise<string> {
return this._attributeToPage(() => this._mainFrame.innerHTML(selector, options)); return this._mainFrame.innerHTML(selector, options);
} }
async getAttribute(selector: string, name: string, options?: channels.FrameGetAttributeOptions): Promise<string | null> { async getAttribute(selector: string, name: string, options?: channels.FrameGetAttributeOptions): Promise<string | null> {
return this._attributeToPage(() => this._mainFrame.getAttribute(selector, name, options)); return this._mainFrame.getAttribute(selector, name, options);
} }
async inputValue(selector: string, options?: channels.FrameInputValueOptions): Promise<string> { async inputValue(selector: string, options?: channels.FrameInputValueOptions): Promise<string> {
return this._attributeToPage(() => this._mainFrame.inputValue(selector, options)); return this._mainFrame.inputValue(selector, options);
} }
async isChecked(selector: string, options?: channels.FrameIsCheckedOptions): Promise<boolean> { async isChecked(selector: string, options?: channels.FrameIsCheckedOptions): Promise<boolean> {
return this._attributeToPage(() => this._mainFrame.isChecked(selector, options)); return this._mainFrame.isChecked(selector, options);
} }
async isDisabled(selector: string, options?: channels.FrameIsDisabledOptions): Promise<boolean> { async isDisabled(selector: string, options?: channels.FrameIsDisabledOptions): Promise<boolean> {
return this._attributeToPage(() => this._mainFrame.isDisabled(selector, options)); return this._mainFrame.isDisabled(selector, options);
} }
async isEditable(selector: string, options?: channels.FrameIsEditableOptions): Promise<boolean> { async isEditable(selector: string, options?: channels.FrameIsEditableOptions): Promise<boolean> {
return this._attributeToPage(() => this._mainFrame.isEditable(selector, options)); return this._mainFrame.isEditable(selector, options);
} }
async isEnabled(selector: string, options?: channels.FrameIsEnabledOptions): Promise<boolean> { async isEnabled(selector: string, options?: channels.FrameIsEnabledOptions): Promise<boolean> {
return this._attributeToPage(() => this._mainFrame.isEnabled(selector, options)); return this._mainFrame.isEnabled(selector, options);
} }
async isHidden(selector: string, options?: channels.FrameIsHiddenOptions): Promise<boolean> { async isHidden(selector: string, options?: channels.FrameIsHiddenOptions): Promise<boolean> {
return this._attributeToPage(() => this._mainFrame.isHidden(selector, options)); return this._mainFrame.isHidden(selector, options);
} }
async isVisible(selector: string, options?: channels.FrameIsVisibleOptions): Promise<boolean> { async isVisible(selector: string, options?: channels.FrameIsVisibleOptions): Promise<boolean> {
return this._attributeToPage(() => this._mainFrame.isVisible(selector, options)); return this._mainFrame.isVisible(selector, options);
} }
async hover(selector: string, options?: channels.FrameHoverOptions) { async hover(selector: string, options?: channels.FrameHoverOptions) {
return this._attributeToPage(() => this._mainFrame.hover(selector, options)); return this._mainFrame.hover(selector, options);
} }
async selectOption(selector: string, values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options?: SelectOptionOptions): Promise<string[]> { async selectOption(selector: string, values: string | api.ElementHandle | SelectOption | string[] | api.ElementHandle[] | SelectOption[] | null, options?: SelectOptionOptions): Promise<string[]> {
return this._attributeToPage(() => this._mainFrame.selectOption(selector, values, options)); return this._mainFrame.selectOption(selector, values, options);
} }
async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options?: channels.FrameSetInputFilesOptions): Promise<void> { async setInputFiles(selector: string, files: string | FilePayload | string[] | FilePayload[], options?: channels.FrameSetInputFilesOptions): Promise<void> {
return this._attributeToPage(() => this._mainFrame.setInputFiles(selector, files, options)); return this._mainFrame.setInputFiles(selector, files, options);
} }
async type(selector: string, text: string, options?: channels.FrameTypeOptions) { async type(selector: string, text: string, options?: channels.FrameTypeOptions) {
return this._attributeToPage(() => this._mainFrame.type(selector, text, options)); return this._mainFrame.type(selector, text, options);
} }
async press(selector: string, key: string, options?: channels.FramePressOptions) { async press(selector: string, key: string, options?: channels.FramePressOptions) {
return this._attributeToPage(() => this._mainFrame.press(selector, key, options)); return this._mainFrame.press(selector, key, options);
} }
async check(selector: string, options?: channels.FrameCheckOptions) { async check(selector: string, options?: channels.FrameCheckOptions) {
return this._attributeToPage(() => this._mainFrame.check(selector, options)); return this._mainFrame.check(selector, options);
} }
async uncheck(selector: string, options?: channels.FrameUncheckOptions) { async uncheck(selector: string, options?: channels.FrameUncheckOptions) {
return this._attributeToPage(() => this._mainFrame.uncheck(selector, options)); return this._mainFrame.uncheck(selector, options);
} }
async waitForTimeout(timeout: number) { async waitForTimeout(timeout: number) {
return this._attributeToPage(() => this._mainFrame.waitForTimeout(timeout)); return this._mainFrame.waitForTimeout(timeout);
} }
async waitForFunction<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg, options?: WaitForFunctionOptions): Promise<structs.SmartHandle<R>> { async waitForFunction<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg, options?: WaitForFunctionOptions): Promise<structs.SmartHandle<R>> {
return this._attributeToPage(() => this._mainFrame.waitForFunction(pageFunction, arg, options)); return this._mainFrame.waitForFunction(pageFunction, arg, options);
} }
workers(): Worker[] { workers(): Worker[] {
@ -646,13 +635,13 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
} }
async pause() { async pause() {
return this.context()._wrapApiCall('page.pause', async (channel: channels.BrowserContextChannel) => { return this.context()._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.pause(); await channel.pause();
}); });
} }
async pdf(options: PDFOptions = {}): Promise<Buffer> { async pdf(options: PDFOptions = {}): Promise<Buffer> {
return this._wrapApiCall('page.pdf', async (channel: channels.PageChannel) => { return this._wrapApiCall(async (channel: channels.PageChannel) => {
const transportOptions: channels.PagePdfParams = { ...options } as channels.PagePdfParams; const transportOptions: channels.PagePdfParams = { ...options } as channels.PagePdfParams;
if (transportOptions.margin) if (transportOptions.margin)
transportOptions.margin = { ...transportOptions.margin }; transportOptions.margin = { ...transportOptions.margin };

View file

@ -27,18 +27,17 @@ export class Tracing implements api.Tracing {
} }
async start(options: { name?: string, snapshots?: boolean, screenshots?: boolean } = {}) { async start(options: { name?: string, snapshots?: boolean, screenshots?: boolean } = {}) {
await this._context._wrapApiCall('tracing.start', async (channel: channels.BrowserContextChannel) => { await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
return await channel.tracingStart(options); return await channel.tracingStart(options);
}); });
} }
async stop(options: { path?: string } = {}) { async stop(options: { path?: string } = {}) {
await this._context._wrapApiCall('tracing.stop', async (channel: channels.BrowserContextChannel) => { await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.tracingStop(); await channel.tracingStop();
if (options.path) { if (options.path) {
const result = await channel.tracingExport(); const result = await channel.tracingExport();
const artifact = Artifact.from(result.artifact); const artifact = Artifact.from(result.artifact);
artifact._apiName = 'tracing';
if (this._context.browser()?._remoteType) if (this._context.browser()?._remoteType)
artifact._isRemote = true; artifact._isRemote = true;
await artifact.saveAs(options.path); await artifact.saveAs(options.path);

View file

@ -15,7 +15,7 @@
*/ */
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { rewriteErrorMessage } from '../utils/stackTrace'; import { ParsedStackTrace, rewriteErrorMessage } from '../utils/stackTrace';
import { TimeoutError } from '../utils/errors'; import { TimeoutError } from '../utils/errors';
import { createGuid } from '../utils/utils'; import { createGuid } from '../utils/utils';
import { ChannelOwner } from './channelOwner'; import { ChannelOwner } from './channelOwner';
@ -30,17 +30,17 @@ export class Waiter {
private _waitId: string; private _waitId: string;
private _error: string | undefined; private _error: string | undefined;
constructor(channelOwner: ChannelOwner, apiName: string) { constructor(channelOwner: ChannelOwner, event: string, stackTrace: ParsedStackTrace) {
this._waitId = createGuid(); this._waitId = createGuid();
this._channelOwner = channelOwner; this._channelOwner = channelOwner;
this._channelOwner._waitForEventInfoBefore(this._waitId, apiName); this._channelOwner._waitForEventInfoBefore(this._waitId, event, stackTrace);
this._dispose = [ this._dispose = [
() => this._channelOwner._waitForEventInfoAfter(this._waitId, this._error) () => this._channelOwner._waitForEventInfoAfter(this._waitId, this._error)
]; ];
} }
static createForEvent(channelOwner: ChannelOwner, target: string, event: string) { static createForEvent(channelOwner: ChannelOwner, event: string, stackTrace: ParsedStackTrace) {
return new Waiter(channelOwner, `${target}.waitForEvent(${event})`); return new Waiter(channelOwner, event, stackTrace);
} }
async waitForEvent<T = void>(emitter: EventEmitter, event: string, predicate?: (arg: T) => boolean | Promise<boolean>): Promise<T> { async waitForEvent<T = void>(emitter: EventEmitter, event: string, predicate?: (arg: T) => boolean | Promise<boolean>): Promise<T> {
@ -82,7 +82,7 @@ export class Waiter {
dispose(); dispose();
this._error = e.message; this._error = e.message;
this.dispose(); this.dispose();
rewriteErrorMessage(e, e.message + formatLogRecording(this._logs) + kLoggingNote); rewriteErrorMessage(e, e.message + formatLogRecording(this._logs));
throw e; throw e;
} }
} }
@ -126,8 +126,6 @@ function waitForTimeout(timeout: number): { promise: Promise<void>, dispose: ()
return { promise, dispose }; return { promise, dispose };
} }
const kLoggingNote = `\nNote: use DEBUG=pw:api environment variable to capture Playwright logs.`;
function formatLogRecording(log: string[]): string { function formatLogRecording(log: string[]): string {
if (!log.length) if (!log.length)
return ''; return '';

View file

@ -48,7 +48,7 @@ export class Worker extends ChannelOwner<channels.WorkerChannel, channels.Worker
async evaluate<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> { async evaluate<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<R> {
assertMaxArguments(arguments.length, 2); assertMaxArguments(arguments.length, 2);
return this._wrapApiCall('worker.evaluate', async (channel: channels.WorkerChannel) => { return this._wrapApiCall(async (channel: channels.WorkerChannel) => {
const result = await channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return parseResult(result.value); return parseResult(result.value);
}); });
@ -56,7 +56,7 @@ export class Worker extends ChannelOwner<channels.WorkerChannel, channels.Worker
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> { async evaluateHandle<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> {
assertMaxArguments(arguments.length, 2); assertMaxArguments(arguments.length, 2);
return this._wrapApiCall('worker.evaluateHandle', async (channel: channels.WorkerChannel) => { return this._wrapApiCall(async (channel: channels.WorkerChannel) => {
const result = await channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) }); const result = await channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === 'function', arg: serializeArgument(arg) });
return JSHandle.from(result.handle) as any as structs.SmartHandle<R>; return JSHandle.from(result.handle) as any as structs.SmartHandle<R>;
}); });

View file

@ -237,7 +237,6 @@ export class DispatcherConnection {
const info = params.info; const info = params.info;
switch (info.phase) { switch (info.phase) {
case 'before': { case 'before': {
callMetadata.apiName = info.apiName;
this._waitOperations.set(info.waitId, callMetadata); this._waitOperations.set(info.waitId, callMetadata);
await sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata); await sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata);
return; return;
@ -267,7 +266,7 @@ export class DispatcherConnection {
// Dispatching error // Dispatching error
callMetadata.error = e.message; callMetadata.error = e.message;
if (callMetadata.log.length) if (callMetadata.log.length)
rewriteErrorMessage(e, e.message + formatLogRecording(callMetadata.log) + kLoggingNote); rewriteErrorMessage(e, e.message + formatLogRecording(callMetadata.log));
error = serializeError(e); error = serializeError(e);
} finally { } finally {
callMetadata.endTime = monotonicTime(); callMetadata.endTime = monotonicTime();
@ -297,8 +296,6 @@ export class DispatcherConnection {
} }
} }
const kLoggingNote = `\nNote: use DEBUG=pw:api environment variable to capture Playwright logs.`;
function formatLogRecording(log: string[]): string { function formatLogRecording(log: string[]): string {
if (!log.length) if (!log.length)
return ''; return '';

View file

@ -38,7 +38,7 @@ export type Metadata = {
export type WaitForEventInfo = { export type WaitForEventInfo = {
waitId: string, waitId: string,
phase: 'before' | 'after' | 'log', phase: 'before' | 'after' | 'log',
apiName?: string, event?: string,
message?: string, message?: string,
error?: string, error?: string,
}; };

View file

@ -41,7 +41,7 @@ WaitForEventInfo:
- before - before
- after - after
- log - log
apiName: string? event: string?
message: string? message: string?
error: string? error: string?

View file

@ -46,7 +46,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
scheme.WaitForEventInfo = tObject({ scheme.WaitForEventInfo = tObject({
waitId: tString, waitId: tString,
phase: tEnum(['before', 'after', 'log']), phase: tEnum(['before', 'after', 'log']),
apiName: tOptional(tString), event: tOptional(tString),
message: tOptional(tString), message: tOptional(tString),
error: tOptional(tString), error: tOptional(tString),
}); });

View file

@ -18,7 +18,9 @@ import { CallMetadata } from '../../instrumentation';
import { CallLog, CallLogStatus } from './recorderTypes'; import { CallLog, CallLogStatus } from './recorderTypes';
export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog { export function metadataToCallLog(metadata: CallMetadata, status: CallLogStatus): CallLog {
const title = metadata.apiName || metadata.method; let title = metadata.apiName || metadata.method;
if (metadata.method === 'waitForEventInfo')
title += `(${metadata.params.info.event})`;
if (metadata.error) if (metadata.error)
status = 'error'; status = 'error';
const params = { const params = {

View file

@ -37,28 +37,44 @@ const PW_LIB_DIRS = [
'playwright-firefox', 'playwright-firefox',
'playwright-webkit', 'playwright-webkit',
path.join('@playwright', 'test'), path.join('@playwright', 'test'),
].map(packageName => path.sep + path.join(packageName, 'lib')); ].map(packageName => path.sep + packageName);
export function captureStackTrace(): { stack: string, frames: StackFrame[] } { const runnerLib = path.join('@playwright', 'test', 'lib', 'test');
const runnerSrc = path.join('src', 'test');
export type ParsedStackTrace = {
frames: StackFrame[];
frameTexts: string[];
apiName: string;
};
export function captureStackTrace(): ParsedStackTrace {
const stackTraceLimit = Error.stackTraceLimit; const stackTraceLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 30; Error.stackTraceLimit = 30;
const stack = new Error().stack!; const error = new Error();
const stack = error.stack!;
Error.stackTraceLimit = stackTraceLimit; Error.stackTraceLimit = stackTraceLimit;
const frames: StackFrame[] = []; const frames: StackFrame[] = [];
for (const line of stack.split('\n')) { const frameTexts: string[] = [];
const lines = stack.split('\n').reverse();
let apiName = '';
const isTesting = process.env.PWTEST_CLI_ALLOW_TEST_COMMAND || isUnderTest();
for (const line of lines) {
const frame = stackUtils.parseLine(line); const frame = stackUtils.parseLine(line);
if (!frame || !frame.file) if (!frame || !frame.file)
continue; continue;
if (frame.file.startsWith('internal')) if (frame.file.startsWith('internal'))
continue; continue;
const fileName = path.resolve(process.cwd(), frame.file); const fileName = path.resolve(process.cwd(), frame.file);
if (PW_LIB_DIRS.some(libDir => fileName.includes(libDir)))
continue;
const isTesting = process.env.PWTEST_CLI_ALLOW_TEST_COMMAND || isUnderTest();
if (isTesting && fileName.includes(path.join('playwright', 'src')))
continue;
if (isTesting && fileName.includes(path.join('playwright', 'tests', 'config', 'coverage.js'))) if (isTesting && fileName.includes(path.join('playwright', 'tests', 'config', 'coverage.js')))
continue; continue;
if (!fileName.includes(runnerLib) && !(isTesting && fileName.includes(runnerSrc)) && PW_LIB_DIRS.map(p => path.join(p, isTesting ? 'src' : 'lib')).some(libDir => fileName.includes(libDir))) {
apiName = frame.function ? frame.function[0].toLowerCase() + frame.function.slice(1) : '';
break;
}
frameTexts.push(line);
frames.push({ frames.push({
file: fileName, file: fileName,
line: frame.line, line: frame.line,
@ -66,7 +82,9 @@ export function captureStackTrace(): { stack: string, frames: StackFrame[] } {
function: frame.function, function: frame.function,
}); });
} }
return { stack, frames }; frames.reverse();
frameTexts.reverse();
return { frames, frameTexts, apiName };
} }
export function splitErrorMessage(message: string): { name: string, message: string } { export function splitErrorMessage(message: string): { name: string, message: string } {

View file

@ -36,6 +36,7 @@ it('should respect timeout', async ({page, server}) => {
await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'}); await page.goto(server.PREFIX + '/one-style.html', {waitUntil: 'domcontentloaded'});
const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e); const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e);
expect(error.message).toContain('page.waitForLoadState: Timeout 1ms exceeded.'); expect(error.message).toContain('page.waitForLoadState: Timeout 1ms exceeded.');
expect(error.stack.split('\n')[1]).toContain(__filename);
}); });
it('should resolve immediately if loaded', async ({page, server}) => { it('should resolve immediately if loaded', async ({page, server}) => {

View file

@ -27,7 +27,7 @@ it('should respect timeout', async ({page, server}) => {
const promise = page.waitForURL('**/frame.html', { timeout: 2500 }).catch(e => e); const promise = page.waitForURL('**/frame.html', { timeout: 2500 }).catch(e => e);
await page.goto(server.EMPTY_PAGE); await page.goto(server.EMPTY_PAGE);
const error = await promise; const error = await promise;
expect(error.message).toContain('page.waitForNavigation: Timeout 2500ms exceeded.'); expect(error.message).toContain('page.waitForURL: Timeout 2500ms exceeded.');
}); });
it('should work with both domcontentloaded and load', async ({page, server}) => { it('should work with both domcontentloaded and load', async ({page, server}) => {