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/
nohup.out
.trace
.tmp

View File

@ -26,6 +26,7 @@ import { Page } from './page';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { Waiter } from './waiter';
import { EventEmitter } from 'events';
import { ParsedStackTrace } from '../utils/stackTrace';
type Direction = 'down' | 'up' | 'left' | 'right';
type SpeedOptions = { speed?: number };
@ -48,7 +49,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel, channels.Andr
}
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();
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) {
await this._wrapApiCall('androidDevice.wait', async (channel: channels.AndroidDeviceChannel) => {
await this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.wait({ selector: toSelectorChannel(selector), ...options });
});
}
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 });
});
}
@ -131,61 +132,61 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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;
});
}
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 buffer = Buffer.from(binary, 'base64');
if (options.path)
@ -195,39 +196,39 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel, c
}
async close() {
return this._wrapApiCall('androidDevice.close', async (channel: channels.AndroidDeviceChannel) => {
return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel) => {
await channel.close();
this.emit(Events.AndroidDevice.Close);
});
}
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 });
return Buffer.from(result, 'base64');
});
}
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);
});
}
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 });
});
}
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 });
});
}
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 { context } = await channel.launchBrowser(contextOptions);
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> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, 'androidDevice', event);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.AndroidDevice.Close)
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new Error('Device closed'));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
return this._wrapApiCall(async (channel: channels.AndroidDeviceChannel, stackTrace: ParsedStackTrace) => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event, stackTrace);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.AndroidDevice.Close)
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new Error('Device closed'));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
});
}
}
@ -259,13 +262,13 @@ export class AndroidSocket extends ChannelOwner<channels.AndroidSocketChannel, c
}
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') });
});
}
async close(): Promise<void> {
return this._wrapApiCall('androidDevice.close', async (channel: channels.AndroidSocketChannel) => {
return this._wrapApiCall(async (channel: channels.AndroidSocketChannel) => {
await channel.close();
});
}
@ -285,31 +288,31 @@ export class AndroidInput implements api.AndroidInput {
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
@ -391,7 +394,7 @@ export class AndroidWebView extends EventEmitter implements api.AndroidWebView {
}
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' });
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> {
_isRemote = false;
_apiName: string = '';
static from(channel: channels.ArtifactChannel): Artifact {
return (channel as any)._object;
@ -32,13 +31,13 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.Ar
async pathAfterFinished(): Promise<string | null> {
if (this._isRemote)
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;
});
}
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) {
await channel.saveAs({ path });
return;
@ -56,13 +55,13 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.Ar
}
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;
});
}
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();
if (!result.stream)
return null;
@ -72,13 +71,13 @@ export class Artifact extends ChannelOwner<channels.ArtifactChannel, channels.Ar
}
async cancel(): Promise<void> {
return this._wrapApiCall(`${this._apiName}.cancel`, async (channel: channels.ArtifactChannel) => {
return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
return channel.cancel();
});
}
async delete(): Promise<void> {
return this._wrapApiCall(`${this._apiName}.delete`, async (channel: channels.ArtifactChannel) => {
return this._wrapApiCall(async (channel: channels.ArtifactChannel) => {
return channel.delete();
});
}

View File

@ -47,7 +47,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
}
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 context = BrowserContext.from((await channel.newContext(contextOptions)).context);
context._options = contextOptions;
@ -78,26 +78,26 @@ export class Browser extends ChannelOwner<channels.BrowserChannel, channels.Brow
}
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);
});
}
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 });
});
}
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');
});
}
async close(): Promise<void> {
try {
await this._wrapApiCall('browser.close', async (channel: channels.BrowserChannel) => {
await this._wrapApiCall(async (channel: channels.BrowserChannel) => {
if (this._remoteType === 'owns-connection')
this._connection.close();
else

View File

@ -33,6 +33,7 @@ import * as api from '../../types/types';
import * as structs from '../../types/structs';
import { CDPSession } from './cdpSession';
import { Tracing } from './tracing';
import { ParsedStackTrace } from '../utils/stackTrace';
export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel, channels.BrowserContextInitializer> implements api.BrowserContext {
_pages = new Set<Page>();
@ -162,7 +163,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
}
async newPage(): Promise<Page> {
return this._wrapApiCall('browserContext.newPage', async (channel: channels.BrowserContextChannel) => {
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
if (this._ownerPage)
throw new Error('Please use browser.newContext()');
return Page.from((await channel.newPage()).page);
@ -174,50 +175,50 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
urls = [];
if (urls && typeof urls === 'string')
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;
});
}
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 });
});
}
async clearCookies(): Promise<void> {
return this._wrapApiCall('browserContext.clearCookies', async (channel: channels.BrowserContextChannel) => {
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.clearCookies();
});
}
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 });
});
}
async clearPermissions(): Promise<void> {
return this._wrapApiCall('browserContext.clearPermissions', async (channel: channels.BrowserContextChannel) => {
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.clearPermissions();
});
}
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 });
});
}
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);
await channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
});
}
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 });
});
}
@ -225,27 +226,27 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
async setHTTPCredentials(httpCredentials: { username: string, password: string } | null): Promise<void> {
if (!isUnderTest())
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 });
});
}
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);
await channel.addInitScript({ source });
});
}
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 });
this._bindings.set(name, callback);
});
}
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 });
const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args);
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> {
return this._wrapApiCall('browserContext.route', async (channel: channels.BrowserContextChannel) => {
return this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
this._routes.push({ url, handler });
if (this._routes.length === 1)
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> {
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));
if (this._routes.length === 0)
await channel.setNetworkInterceptionEnabled({ enabled: false });
@ -269,19 +270,21 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
}
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, 'browserContext', event);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.BrowserContext.Close)
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new Error('Context closed'));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
return this._wrapApiCall(async (channel: channels.BrowserContextChannel, stackTrace: ParsedStackTrace) => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event, stackTrace);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.BrowserContext.Close)
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new Error('Context closed'));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
});
}
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();
if (options.path) {
await mkdirIfNeeded(options.path);
@ -300,7 +303,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
}
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 });
return CDPSession.from(result.session);
});
@ -314,7 +317,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel,
async close(): Promise<void> {
try {
await this._wrapApiCall('browserContext.close', async (channel: channels.BrowserContextChannel) => {
await this._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.close();
await this._closedPromise;
});

View File

@ -66,7 +66,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
async launch(options: LaunchOptions = {}): Promise<Browser> {
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).port, 'Cannot specify a port without launching as a server.');
const launchOptions: channels.BrowserTypeLaunchParams = {
@ -88,7 +88,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
}
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.');
const contextParams = await prepareBrowserContextParams(options);
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
@ -110,7 +110,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
async connect(params: ConnectOptions): Promise<Browser> {
const logger = params.logger;
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, [], {
perMessageDeflate: false,
maxPayload: 256 * 1024 * 1024, // 256Mb,
@ -221,7 +221,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel, chann
if (this.name() !== 'chromium')
throw new Error('Connecting over CDP is only supported in Chromium.');
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 headers = paramsHeaders ? headersObjectToArray(paramsHeaders) : undefined;
const result = await channel.connectOverCDP({

View File

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

View File

@ -18,7 +18,8 @@ import { EventEmitter } from 'events';
import * as channels from '../protocol/channels';
import { createScheme, ValidationError, Validator } from '../protocol/validator';
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 { Logger } from './types';
@ -47,7 +48,7 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.Channel
this._logger = this._parent._logger;
}
this._channel = this._createChannel(new EventEmitter(), '');
this._channel = this._createChannel(new EventEmitter(), null);
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, {
get: (obj: any, prop) => {
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') {
const validator = scheme[paramsName(this._type, prop)];
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];
},
@ -87,31 +88,35 @@ export abstract class ChannelOwner<T extends channels.Channel = channels.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;
const stackTrace = captureStackTrace();
const { apiName, frameTexts } = stackTrace;
const channel = this._createChannel({}, stackTrace);
try {
logApiCall(logger, `=> ${apiName} started`);
const channel = this._createChannel({}, apiName);
const result = await func(channel as any);
const result = await func(channel as any, stackTrace);
logApiCall(logger, `<= ${apiName} succeeded`);
return result;
} 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`);
rewriteErrorMessage(e, `${apiName}: ` + e.message);
throw e;
}
}
_waitForEventInfoBefore(waitId: string, apiName: string) {
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { apiName, waitId, phase: 'before' } }, undefined).catch(() => {});
_waitForEventInfoBefore(waitId: string, event: string, stackTrace: ParsedStackTrace) {
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'before', event } }, stackTrace).catch(() => {});
}
_waitForEventInfoAfter(waitId: string, error?: string) {
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'after', error } }, undefined).catch(() => {});
_waitForEventInfoAfter(waitId: string, error: string | undefined) {
this._connection.sendMessageToServer(this, 'waitForEventInfo', { info: { waitId, phase: 'after', error } }, null).catch(() => {});
}
_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() {

View File

@ -34,10 +34,9 @@ import * as channels from '../protocol/channels';
import { Stream } from './stream';
import { debugLogger } from '../utils/debugLogger';
import { SelectorsOwner } from './selectors';
import { isUnderTest } from '../utils/utils';
import { Android, AndroidSocket, AndroidDevice } from './android';
import { SocksSocket } from './socksSocket';
import { captureStackTrace } from '../utils/stackTrace';
import { ParsedStackTrace } from '../utils/stackTrace';
import { Artifact } from './artifact';
import { EventEmitter } from 'events';
@ -77,24 +76,20 @@ export class Connection extends EventEmitter {
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 { stack, frames } = captureStackTrace();
const { frames, apiName }: ParsedStackTrace = stackTrace || { frameTexts: [], frames: [], apiName: '' };
const id = ++this._lastId;
const converted = { id, guid, method, params };
// Do not include metadata in debug logs to avoid noise.
debugLogger.log('channel:command', converted);
const metadata: channels.Metadata = { stack: frames, apiName };
this.onmessage({ ...converted, metadata });
try {
if (this._disconnectedErrorMessage)
throw new Error(this._disconnectedErrorMessage);
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;
}
if (this._disconnectedErrorMessage)
throw new Error(this._disconnectedErrorMessage);
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, metadata }));
}
_debugScopeState(): any {

View File

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

View File

@ -18,6 +18,7 @@ import type { BrowserWindow } from 'electron';
import * as structs from '../../types/structs';
import * as api from '../../types/types';
import * as channels from '../protocol/channels';
import { ParsedStackTrace } from '../utils/stackTrace';
import { TimeoutSettings } from '../utils/timeoutSettings';
import { headersObjectToArray } from '../utils/utils';
import { BrowserContext } from './browserContext';
@ -46,7 +47,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel, channels.El
}
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 = {
sdkLanguage: 'javascript',
...options,
@ -88,7 +89,7 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
}
async firstWindow(): Promise<Page> {
return this._wrapApiCall('electronApplication.firstWindow', async (channel: channels.ElectronApplicationChannel) => {
return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel) => {
if (this._windows.size)
return this._windows.values().next().value;
return this.waitForEvent('window');
@ -100,37 +101,41 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
}
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> {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, 'electronApplication', event);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.ElectronApplication.Close)
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new Error('Electron application closed'));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
return this._wrapApiCall(async (channel: channels.ElectronApplicationChannel, stackTrace: ParsedStackTrace) => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event, stackTrace);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.ElectronApplication.Close)
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new Error('Electron application closed'));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
});
}
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 });
return JSHandle.from(result.handle);
});
}
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) });
return parseResult(result.value);
});
}
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) });
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> {
return this._wrapApiCall('elementHandle.ownerFrame', async (channel: channels.ElementHandleChannel) => {
return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
return Frame.fromNullable((await channel.ownerFrame()).frame);
});
}
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);
});
}
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;
return value === undefined ? null : value;
});
}
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;
});
}
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;
return value === undefined ? null : value;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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) });
});
}
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);
});
}
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);
});
}
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);
});
}
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);
});
}
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);
});
}
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 });
return result.values;
});
}
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 });
});
}
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);
});
}
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 });
});
}
async focus(): Promise<void> {
return this._wrapApiCall('elementHandle.focus', async (channel: channels.ElementHandleChannel) => {
return this._wrapApiCall(async (channel: channels.ElementHandleChannel) => {
await channel.focus();
});
}
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 });
});
}
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 });
});
}
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);
});
}
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);
});
}
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;
return value === undefined ? null : value;
});
}
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 };
if (!copy.type)
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> {
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;
});
}
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 });
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> {
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) });
return parseResult(result.value);
});
}
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) });
return parseResult(result.value);
});
}
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 });
});
}
@ -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): 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 });
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) {
return this._page._wrapApiCall('fileChooser.setFiles', async () => {
return this._page._wrapApiCall(async () => {
return this._elementHandle.setInputFiles(files, options);
});
}

View File

@ -30,6 +30,7 @@ import { LifecycleEvent, URLMatch, SelectOption, SelectOptionOptions, FilePayloa
import { urlMatches } from './clientHelper';
import * as api from '../../types/types';
import * as structs from '../../types/structs';
import { ParsedStackTrace } from '../utils/stackTrace';
export type WaitForNavigationOptions = {
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 {
return this._page!;
}
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);
return network.Response.fromNullable((await channel.goto({ url, ...options, waitUntil })).response);
});
}
private _setupNavigationWaiter(name: string, options: { timeout?: number }): Waiter {
const waiter = new Waiter(this, name);
private _setupNavigationWaiter(options: { timeout?: number }, stackTrace: ParsedStackTrace): Waiter {
const waiter = new Waiter(this, '', stackTrace);
if (this._page!.isClosed())
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!'));
@ -110,9 +107,9 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
}
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 waiter = this._setupNavigationWaiter(this._apiName('waitForNavigation'), options);
const waiter = this._setupNavigationWaiter(options, stackTrace);
const toUrl = typeof options.url === 'string' ? ` to "${options.url}"` : '';
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);
if (this._loadStates.has(state))
return;
return this._wrapApiCall(this._apiName('waitForLoadState'), async (channel: channels.FrameChannel) => {
const waiter = this._setupNavigationWaiter(this._apiName('waitForLoadState'), options);
return this._wrapApiCall(async (channel: channels.FrameChannel, stackTrace: ParsedStackTrace) => {
const waiter = this._setupNavigationWaiter(options, stackTrace);
await waiter.waitForEvent<LifecycleEvent>(this._eventEmitter, 'loadstate', s => {
waiter.log(` "${s}" event fired`);
return s === state;
@ -165,14 +162,14 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
}
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);
});
}
async evaluateHandle<R, Arg>(pageFunction: structs.PageFunction<Arg, R>, arg?: Arg): Promise<structs.SmartHandle<R>> {
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) });
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> {
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) });
return parseResult(result.value);
});
}
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 });
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): 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)
throw new Error('options.visibility is not supported, did you mean options.state?');
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> {
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 });
});
}
async $eval<R, Arg>(selector: string, pageFunction: structs.PageFunctionOn<Element, Arg, R>, arg?: Arg): Promise<R> {
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) });
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> {
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) });
return parseResult(result.value);
});
}
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 });
return result.elements.map(e => ElementHandle.from(e) as ElementHandle<SVGElement | HTMLElement>);
});
}
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;
});
}
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);
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> {
return this._wrapApiCall(this._apiName('addScriptTag'), async (channel: channels.FrameChannel) => {
return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const copy = { ...options };
if (copy.path) {
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> {
return this._wrapApiCall(this._apiName('addStyleTag'), async (channel: channels.FrameChannel) => {
return this._wrapApiCall(async (channel: channels.FrameChannel) => {
const copy = { ...options };
if (copy.path) {
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 = {}) {
return this._wrapApiCall(this._apiName('click'), async (channel: channels.FrameChannel) => {
return this._wrapApiCall(async (channel: channels.FrameChannel) => {
return await channel.click({ selector, ...options });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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;
return value === undefined ? null : value;
});
}
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;
});
}
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;
});
}
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;
return value === undefined ? null : value;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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;
});
}
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 });
});
}
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;
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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 });
});
}
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));
});
}
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')
assert(options.polling === 'raf', 'Unknown polling option: ' + options.polling);
const result = await channel.waitForFunction({
@ -452,7 +449,7 @@ export class Frame extends ChannelOwner<channels.FrameChannel, channels.FrameIni
}
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;
});
}

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> {
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) });
return parseResult(result.value);
});
}
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) });
return JSHandle.from(result.handle) as any as structs.SmartHandle<R>;
});
}
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 });
return JSHandle.from(result.handle);
});
}
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>();
for (const { name, value } of (await channel.getPropertyList()).properties)
map.set(name, JSHandle.from(value));
@ -64,7 +64,7 @@ export class JSHandle<T = any> extends ChannelOwner<channels.JSHandleChannel, ch
}
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);
});
}
@ -74,7 +74,7 @@ export class JSHandle<T = any> extends ChannelOwner<channels.JSHandleChannel, ch
}
async dispose() {
return this._wrapApiCall('jsHandle.dispose', async (channel: channels.JSHandleChannel) => {
return this._wrapApiCall(async (channel: channels.JSHandleChannel) => {
return await channel.dispose();
});
}

View File

@ -26,6 +26,7 @@ import { Events } from './events';
import { Page } from './page';
import { Waiter } from './waiter';
import * as api from '../../types/types';
import { ParsedStackTrace } from '../utils/stackTrace';
export type NetworkCookie = {
name: string,
@ -132,7 +133,7 @@ export class Request extends ChannelOwner<channels.RequestChannel, channels.Requ
}
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);
});
}
@ -258,13 +259,13 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
}
async abort(errorCode?: string) {
return this._wrapApiCall('route.abort', async (channel: channels.RouteChannel) => {
return this._wrapApiCall(async (channel: channels.RouteChannel) => {
await channel.abort({ errorCode });
});
}
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 isBase64 = false;
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> {
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 } = {}) {
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(apiName: string, 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> {
return await this._wrapApiCall(apiName, async (channel: channels.RouteChannel) => {
async _continue(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: InterceptResponse): Promise<api.Response>;
async _continue(options: { url?: string, method?: string, headers?: Headers, postData?: string | Buffer }, interceptResponse: boolean): Promise<null|api.Response> {
return await this._wrapApiCall(async (channel: channels.RouteChannel) => {
const postDataBuffer = isString(options.postData) ? Buffer.from(options.postData, 'utf8') : options.postData;
const result = await channel.continue({
url: options.url,
@ -329,7 +330,7 @@ export class Route extends ChannelOwner<channels.RouteChannel, channels.RouteIni
}
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');
});
}
@ -390,14 +391,16 @@ export class Response extends ChannelOwner<channels.ResponseChannel, channels.Re
}
async finished(): Promise<Error | null> {
const result = await this._channel.finished();
if (result.error)
return new Error(result.error);
return null;
return this._wrapApiCall(async (channel: channels.ResponseChannel) => {
const result = await channel.finished();
if (result.error)
return new Error(result.error);
return null;
});
}
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');
});
}
@ -421,11 +424,15 @@ export class Response extends ChannelOwner<channels.ResponseChannel, channels.Re
}
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> {
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> {
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, 'webSocket', event);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.WebSocket.Error)
waiter.rejectOnEvent(this, Events.WebSocket.Error, new Error('Socket error'));
if (event !== Events.WebSocket.Close)
waiter.rejectOnEvent(this, Events.WebSocket.Close, new Error('Socket closed'));
waiter.rejectOnEvent(this._page, Events.Page.Close, new Error('Page closed'));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
return this._wrapApiCall(async (channel: channels.WebSocketChannel, stackTrace: ParsedStackTrace) => {
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, event, stackTrace);
waiter.rejectOnTimeout(timeout, `Timeout while waiting for event "${event}"`);
if (event !== Events.WebSocket.Error)
waiter.rejectOnEvent(this, Events.WebSocket.Error, new Error('Socket error'));
if (event !== Events.WebSocket.Close)
waiter.rejectOnEvent(this, Events.WebSocket.Close, new Error('Socket closed'));
waiter.rejectOnEvent(this._page, Events.Page.Close, new Error('Page closed'));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
});
}
}

View File

@ -46,6 +46,7 @@ import { isString, isRegExp, isObject, mkdirIfNeeded, headersObjectToArray } fro
import { isSafeCloseError } from '../utils/errors';
import { Video } from './video';
import { Artifact } from './artifact';
import { ParsedStackTrace } from '../utils/stackTrace';
type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> & {
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 _timeoutSettings: TimeoutSettings;
_isPageCall = false;
private _video: Video | null = 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 }) => {
const artifactObject = Artifact.from(artifact);
artifactObject._isRemote = !!this._browserContext._browser && !!this._browserContext._browser._remoteType;
artifactObject._apiName = 'download';
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)));
@ -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('video', ({ artifact }) => {
const artifactObject = Artifact.from(artifact);
artifactObject._apiName = 'video';
this._forceVideo()._artifactReady(artifactObject);
});
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();
}
private _attributeToPage<T>(func: () => T): T {
try {
this._isPageCall = true;
return func();
} finally {
this._isPageCall = false;
}
}
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): 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> {
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>> {
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> {
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> {
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>[]> {
return this._attributeToPage(() => this._mainFrame.$$(selector));
return this._mainFrame.$$(selector);
}
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> {
return this._attributeToPage(() => this._mainFrame.addStyleTag(options));
return this._mainFrame.addStyleTag(options);
}
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 });
const binding = (source: structs.BindingSource, ...args: any[]) => callback(...args);
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 } = {}) {
return this._wrapApiCall('page.exposeBinding', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, callback);
});
}
async setExtraHTTPHeaders(headers: Headers) {
return this._wrapApiCall('page.setExtraHTTPHeaders', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
validateHeaders(headers);
await channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
});
}
url(): string {
return this._attributeToPage(() => this._mainFrame.url());
return this._mainFrame.url();
}
async content(): Promise<string> {
return this._attributeToPage(() => this._mainFrame.content());
return this._mainFrame.content();
}
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> {
return this._attributeToPage(() => this._mainFrame.goto(url, options));
return this._mainFrame.goto(url, options);
}
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);
return Response.fromNullable((await channel.reload({ ...options, waitUntil })).response);
});
}
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> {
return this._attributeToPage(() => this._mainFrame.waitForNavigation(options));
return this._mainFrame.waitForNavigation(options);
}
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> {
return this._wrapApiCall('page.waitForRequest', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
const predicate = (request: Request) => {
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
return urlMatches(request.url(), urlOrPredicate);
@ -368,12 +357,12 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
};
const trimmedUrl = trimUrl(urlOrPredicate);
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> {
return this._wrapApiCall('page.waitForResponse', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
const predicate = (response: Response) => {
if (isString(urlOrPredicate) || isRegExp(urlOrPredicate))
return urlMatches(response.url(), urlOrPredicate);
@ -381,20 +370,20 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
};
const trimmedUrl = trimUrl(urlOrPredicate);
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> {
return this._wrapApiCall('page.waitForEvent', async (channel: channels.PageChannel) => {
return this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`);
return this._wrapApiCall(async (channel: channels.PageChannel, stackTrace: ParsedStackTrace) => {
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 predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = Waiter.createForEvent(this, 'page', event);
const waiter = Waiter.createForEvent(this, event, stackTrace);
if (logLine)
waiter.log(logLine);
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> {
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);
return Response.fromNullable((await channel.goBack({ ...options, waitUntil })).response);
});
}
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);
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 } = {}) {
return this._wrapApiCall('page.emulateMedia', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.emulateMedia({
media: options.media === null ? 'null' : options.media,
colorScheme: options.colorScheme === null ? 'null' : options.colorScheme,
@ -432,7 +421,7 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}
async setViewportSize(viewportSize: Size) {
return this._wrapApiCall('page.setViewportSize', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
this._viewportSize = 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> {
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) {
return this._wrapApiCall('page.addInitScript', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
const source = await evaluationScript(script, arg);
await channel.addInitScript({ source });
});
}
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 });
if (this._routes.length === 1)
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> {
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));
if (this._routes.length === 0)
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> {
return this._wrapApiCall('page.screenshot', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
const copy = { ...options };
if (!copy.type)
copy.type = determineScreenshotType(options);
@ -486,18 +475,18 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}
async title(): Promise<string> {
return this._attributeToPage(() => this._mainFrame.title());
return this._mainFrame.title();
}
async bringToFront(): Promise<void> {
return this._wrapApiCall('page.bringToFront', async (channel: channels.PageChannel) => {
return this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.bringToFront();
});
}
async close(options: { runBeforeUnload?: boolean } = {runBeforeUnload: undefined}) {
try {
await this._wrapApiCall('page.close', async (channel: channels.PageChannel) => {
await this._wrapApiCall(async (channel: channels.PageChannel) => {
await channel.close(options);
if (this._ownedContext)
await this._ownedContext.close();
@ -514,103 +503,103 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}
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) {
return this._attributeToPage(() => this._mainFrame.dblclick(selector, options));
return this._mainFrame.dblclick(selector, options);
}
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) {
return this._attributeToPage(() => this._mainFrame.fill(selector, value, options));
return this._mainFrame.fill(selector, value, options);
}
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> {
return this._attributeToPage(() => this._mainFrame.textContent(selector, options));
return this._mainFrame.textContent(selector, options);
}
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> {
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> {
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> {
return this._attributeToPage(() => this._mainFrame.inputValue(selector, options));
return this._mainFrame.inputValue(selector, options);
}
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> {
return this._attributeToPage(() => this._mainFrame.isDisabled(selector, options));
return this._mainFrame.isDisabled(selector, options);
}
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> {
return this._attributeToPage(() => this._mainFrame.isEnabled(selector, options));
return this._mainFrame.isEnabled(selector, options);
}
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> {
return this._attributeToPage(() => this._mainFrame.isVisible(selector, options));
return this._mainFrame.isVisible(selector, options);
}
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[]> {
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> {
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) {
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) {
return this._attributeToPage(() => this._mainFrame.press(selector, key, options));
return this._mainFrame.press(selector, key, options);
}
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) {
return this._attributeToPage(() => this._mainFrame.uncheck(selector, options));
return this._mainFrame.uncheck(selector, options);
}
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>> {
return this._attributeToPage(() => this._mainFrame.waitForFunction(pageFunction, arg, options));
return this._mainFrame.waitForFunction(pageFunction, arg, options);
}
workers(): Worker[] {
@ -646,13 +635,13 @@ export class Page extends ChannelOwner<channels.PageChannel, channels.PageInitia
}
async pause() {
return this.context()._wrapApiCall('page.pause', async (channel: channels.BrowserContextChannel) => {
return this.context()._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
await channel.pause();
});
}
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;
if (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 } = {}) {
await this._context._wrapApiCall('tracing.start', async (channel: channels.BrowserContextChannel) => {
await this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => {
return await channel.tracingStart(options);
});
}
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();
if (options.path) {
const result = await channel.tracingExport();
const artifact = Artifact.from(result.artifact);
artifact._apiName = 'tracing';
if (this._context.browser()?._remoteType)
artifact._isRemote = true;
await artifact.saveAs(options.path);

View File

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

View File

@ -237,7 +237,6 @@ export class DispatcherConnection {
const info = params.info;
switch (info.phase) {
case 'before': {
callMetadata.apiName = info.apiName;
this._waitOperations.set(info.waitId, callMetadata);
await sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata);
return;
@ -267,7 +266,7 @@ export class DispatcherConnection {
// Dispatching error
callMetadata.error = e.message;
if (callMetadata.log.length)
rewriteErrorMessage(e, e.message + formatLogRecording(callMetadata.log) + kLoggingNote);
rewriteErrorMessage(e, e.message + formatLogRecording(callMetadata.log));
error = serializeError(e);
} finally {
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 {
if (!log.length)
return '';

View File

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

View File

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

View File

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

View File

@ -18,7 +18,9 @@ import { CallMetadata } from '../../instrumentation';
import { CallLog, CallLogStatus } from './recorderTypes';
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)
status = 'error';
const params = {

View File

@ -37,28 +37,44 @@ const PW_LIB_DIRS = [
'playwright-firefox',
'playwright-webkit',
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;
Error.stackTraceLimit = 30;
const stack = new Error().stack!;
const error = new Error();
const stack = error.stack!;
Error.stackTraceLimit = stackTraceLimit;
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);
if (!frame || !frame.file)
continue;
if (frame.file.startsWith('internal'))
continue;
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')))
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({
file: fileName,
line: frame.line,
@ -66,7 +82,9 @@ export function captureStackTrace(): { stack: string, frames: StackFrame[] } {
function: frame.function,
});
}
return { stack, frames };
frames.reverse();
frameTexts.reverse();
return { frames, frameTexts, apiName };
}
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'});
const error = await page.waitForLoadState('load', { timeout: 1 }).catch(e => e);
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}) => {

View File

@ -27,7 +27,7 @@ it('should respect timeout', async ({page, server}) => {
const promise = page.waitForURL('**/frame.html', { timeout: 2500 }).catch(e => e);
await page.goto(server.EMPTY_PAGE);
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}) => {