mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: migrate waitForEvent to Progress (#2483)
Drive-by: remove/simplify some helper code.
This commit is contained in:
parent
fb058ffe0d
commit
300099734c
@ -104,8 +104,12 @@ export abstract class BrowserContextBase extends ExtendedEventEmitter implements
|
|||||||
return event === Events.BrowserContext.Close ? super._abortPromiseForEvent(event) : this._closePromise;
|
return event === Events.BrowserContext.Close ? super._abortPromiseForEvent(event) : this._closePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _computeDeadline(options?: types.TimeoutOptions): number {
|
protected _getLogger(): InnerLogger {
|
||||||
return this._timeoutSettings.computeDeadline(options);
|
return this._logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _getTimeoutSettings(): TimeoutSettings {
|
||||||
|
return this._timeoutSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
_browserClosed() {
|
_browserClosed() {
|
||||||
|
|||||||
@ -15,23 +15,43 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import { helper } from './helper';
|
import { helper, RegisteredListener } from './helper';
|
||||||
import { TimeoutOptions } from './types';
|
import { ProgressController } from './progress';
|
||||||
|
import { InnerLogger } from './logger';
|
||||||
|
import { TimeoutSettings } from './timeoutSettings';
|
||||||
|
|
||||||
export class ExtendedEventEmitter extends EventEmitter {
|
export abstract class ExtendedEventEmitter extends EventEmitter {
|
||||||
protected _abortPromiseForEvent(event: string) {
|
protected _abortPromiseForEvent(event: string) {
|
||||||
return new Promise<Error>(() => void 0);
|
return new Promise<Error>(() => void 0);
|
||||||
}
|
}
|
||||||
|
protected abstract _getLogger(): InnerLogger;
|
||||||
protected _computeDeadline(options?: TimeoutOptions): number {
|
protected abstract _getTimeoutSettings(): TimeoutSettings;
|
||||||
throw new Error('unimplemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForEvent(event: string, optionsOrPredicate: Function | { predicate?: Function, timeout?: number } = {}): Promise<any> {
|
async waitForEvent(event: string, optionsOrPredicate: Function | { predicate?: Function, timeout?: number } = {}): Promise<any> {
|
||||||
const deadline = this._computeDeadline(typeof optionsOrPredicate === 'function' ? undefined : optionsOrPredicate);
|
const options = typeof optionsOrPredicate === 'function' ? { predicate: optionsOrPredicate } : optionsOrPredicate;
|
||||||
const {
|
const { predicate = () => true } = options;
|
||||||
predicate = () => true,
|
|
||||||
} = typeof optionsOrPredicate === 'function' ? {predicate: optionsOrPredicate} : optionsOrPredicate;
|
const progressController = new ProgressController(options, this._getLogger(), this._getTimeoutSettings());
|
||||||
return helper.waitForEvent(this, event, predicate, deadline, this._abortPromiseForEvent(event));
|
this._abortPromiseForEvent(event).then(error => progressController.abort(error));
|
||||||
|
|
||||||
|
return progressController.run(async progress => {
|
||||||
|
const listeners: RegisteredListener[] = [];
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
listeners.push(helper.addEventListener(this, event, eventArg => {
|
||||||
|
try {
|
||||||
|
if (!predicate(eventArg))
|
||||||
|
return;
|
||||||
|
resolve(eventArg);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
progress.cleanupWhenAborted(() => helper.removeEventListeners(listeners));
|
||||||
|
|
||||||
|
const result = await promise;
|
||||||
|
helper.removeEventListeners(listeners);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,9 +19,7 @@ import * as crypto from 'crypto';
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as util from 'util';
|
import * as util from 'util';
|
||||||
import { TimeoutError } from './errors';
|
|
||||||
import * as types from './types';
|
import * as types from './types';
|
||||||
import { ChildProcess, execSync } from 'child_process';
|
|
||||||
|
|
||||||
export type RegisteredListener = {
|
export type RegisteredListener = {
|
||||||
emitter: EventEmitter;
|
emitter: EventEmitter;
|
||||||
@ -124,59 +122,6 @@ class Helper {
|
|||||||
return typeof obj === 'boolean' || obj instanceof Boolean;
|
return typeof obj === 'boolean' || obj instanceof Boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async waitForEvent(
|
|
||||||
emitter: EventEmitter,
|
|
||||||
eventName: (string | symbol),
|
|
||||||
predicate: Function,
|
|
||||||
deadline: number,
|
|
||||||
abortPromise: Promise<Error>): Promise<any> {
|
|
||||||
let resolveCallback: (event: any) => void = () => {};
|
|
||||||
let rejectCallback: (error: any) => void = () => {};
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
|
||||||
resolveCallback = resolve;
|
|
||||||
rejectCallback = reject;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add listener.
|
|
||||||
const listener = Helper.addEventListener(emitter, eventName, event => {
|
|
||||||
try {
|
|
||||||
if (!predicate(event))
|
|
||||||
return;
|
|
||||||
resolveCallback(event);
|
|
||||||
} catch (e) {
|
|
||||||
rejectCallback(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reject upon timeout.
|
|
||||||
const eventTimeout = setTimeout(() => {
|
|
||||||
rejectCallback(new TimeoutError(`Timeout exceeded while waiting for ${String(eventName)}`));
|
|
||||||
}, helper.timeUntilDeadline(deadline));
|
|
||||||
|
|
||||||
// Reject upon abort.
|
|
||||||
abortPromise.then(rejectCallback);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await promise;
|
|
||||||
} finally {
|
|
||||||
Helper.removeEventListeners([listener]);
|
|
||||||
clearTimeout(eventTimeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async waitWithDeadline<T>(promise: Promise<T>, taskName: string, deadline: number, debugName: string): Promise<T> {
|
|
||||||
let reject: (error: Error) => void;
|
|
||||||
const timeoutError = new TimeoutError(`Waiting for ${taskName} failed: timeout exceeded. Re-run with the DEBUG=${debugName} env variable to see the debug log.`);
|
|
||||||
const timeoutPromise = new Promise<T>((resolve, x) => reject = x);
|
|
||||||
const timeoutTimer = setTimeout(() => reject(timeoutError), helper.timeUntilDeadline(deadline));
|
|
||||||
try {
|
|
||||||
return await Promise.race([promise, timeoutPromise]);
|
|
||||||
} finally {
|
|
||||||
if (timeoutTimer)
|
|
||||||
clearTimeout(timeoutTimer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static globToRegex(glob: string): RegExp {
|
static globToRegex(glob: string): RegExp {
|
||||||
const tokens = ['^'];
|
const tokens = ['^'];
|
||||||
let inGroup;
|
let inGroup;
|
||||||
@ -321,31 +266,10 @@ class Helper {
|
|||||||
return seconds * 1000 + (nanoseconds / 1000000 | 0);
|
return seconds * 1000 + (nanoseconds / 1000000 | 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static isPastDeadline(deadline: number) {
|
|
||||||
return deadline !== Number.MAX_SAFE_INTEGER && this.monotonicTime() >= deadline;
|
|
||||||
}
|
|
||||||
|
|
||||||
static timeUntilDeadline(deadline: number): number {
|
static timeUntilDeadline(deadline: number): number {
|
||||||
return Math.min(deadline - this.monotonicTime(), 2147483647); // 2^31-1 safe setTimeout in Node.
|
return Math.min(deadline - this.monotonicTime(), 2147483647); // 2^31-1 safe setTimeout in Node.
|
||||||
}
|
}
|
||||||
|
|
||||||
static optionsWithUpdatedTimeout<T extends types.TimeoutOptions>(options: T | undefined, deadline: number): T {
|
|
||||||
return { ...(options || {}) as T, timeout: this.timeUntilDeadline(deadline) };
|
|
||||||
}
|
|
||||||
|
|
||||||
static killProcess(proc: ChildProcess) {
|
|
||||||
if (proc.pid && !proc.killed) {
|
|
||||||
try {
|
|
||||||
if (process.platform === 'win32')
|
|
||||||
execSync(`taskkill /pid ${proc.pid} /T /F`);
|
|
||||||
else
|
|
||||||
process.kill(-proc.pid, 'SIGKILL');
|
|
||||||
} catch (e) {
|
|
||||||
// the process might have already stopped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static getViewportSizeFromWindowFeatures(features: string[]): types.Size | null {
|
static getViewportSizeFromWindowFeatures(features: string[]): types.Size | null {
|
||||||
const widthString = features.find(f => f.startsWith('width='));
|
const widthString = features.find(f => f.startsWith('width='));
|
||||||
const heightString = features.find(f => f.startsWith('height='));
|
const heightString = features.find(f => f.startsWith('height='));
|
||||||
|
|||||||
20
src/page.ts
20
src/page.ts
@ -140,8 +140,12 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
|
|||||||
return this._disconnectedPromise;
|
return this._disconnectedPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _computeDeadline(options?: types.TimeoutOptions): number {
|
protected _getLogger(): InnerLogger {
|
||||||
return this._timeoutSettings.computeDeadline(options);
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _getTimeoutSettings(): TimeoutSettings {
|
||||||
|
return this._timeoutSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
_didClose() {
|
_didClose() {
|
||||||
@ -314,21 +318,21 @@ export class Page extends ExtendedEventEmitter implements InnerLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async waitForRequest(urlOrPredicate: string | RegExp | ((r: network.Request) => boolean), options: types.TimeoutOptions = {}): Promise<network.Request> {
|
async waitForRequest(urlOrPredicate: string | RegExp | ((r: network.Request) => boolean), options: types.TimeoutOptions = {}): Promise<network.Request> {
|
||||||
const deadline = this._timeoutSettings.computeDeadline(options);
|
const predicate = (request: network.Request) => {
|
||||||
return helper.waitForEvent(this, Events.Page.Request, (request: network.Request) => {
|
|
||||||
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
|
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
|
||||||
return helper.urlMatches(request.url(), urlOrPredicate);
|
return helper.urlMatches(request.url(), urlOrPredicate);
|
||||||
return urlOrPredicate(request);
|
return urlOrPredicate(request);
|
||||||
}, deadline, this._disconnectedPromise);
|
};
|
||||||
|
return this.waitForEvent(Events.Page.Request, { predicate, timeout: options.timeout });
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForResponse(urlOrPredicate: string | RegExp | ((r: network.Response) => boolean), options: types.TimeoutOptions = {}): Promise<network.Response> {
|
async waitForResponse(urlOrPredicate: string | RegExp | ((r: network.Response) => boolean), options: types.TimeoutOptions = {}): Promise<network.Response> {
|
||||||
const deadline = this._timeoutSettings.computeDeadline(options);
|
const predicate = (response: network.Response) => {
|
||||||
return helper.waitForEvent(this, Events.Page.Response, (response: network.Response) => {
|
|
||||||
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
|
if (helper.isString(urlOrPredicate) || helper.isRegExp(urlOrPredicate))
|
||||||
return helper.urlMatches(response.url(), urlOrPredicate);
|
return helper.urlMatches(response.url(), urlOrPredicate);
|
||||||
return urlOrPredicate(response);
|
return urlOrPredicate(response);
|
||||||
}, deadline, this._disconnectedPromise);
|
};
|
||||||
|
return this.waitForEvent(Events.Page.Response, { predicate, timeout: options.timeout });
|
||||||
}
|
}
|
||||||
|
|
||||||
async goBack(options?: types.NavigateOptions): Promise<network.Response | null> {
|
async goBack(options?: types.NavigateOptions): Promise<network.Response | null> {
|
||||||
|
|||||||
@ -83,10 +83,16 @@ export class BrowserServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _closeOrKill(deadline: number): Promise<void> {
|
async _closeOrKill(deadline: number): Promise<void> {
|
||||||
|
let timer: NodeJS.Timer;
|
||||||
try {
|
try {
|
||||||
await helper.waitWithDeadline(this.close(), '', deadline, ''); // The error message is ignored.
|
await Promise.race([
|
||||||
|
this.close(),
|
||||||
|
new Promise((resolve, reject) => timer = setTimeout(reject, helper.timeUntilDeadline(deadline))),
|
||||||
|
]);
|
||||||
} catch (ignored) {
|
} catch (ignored) {
|
||||||
await this.kill(); // Make sure to await actual process exit.
|
await this.kill().catch(ignored => {}); // Make sure to await actual process exit.
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -153,8 +153,12 @@ export class ElectronApplication extends ExtendedEventEmitter {
|
|||||||
return this._nodeElectronHandle!.evaluateHandle(pageFunction, arg);
|
return this._nodeElectronHandle!.evaluateHandle(pageFunction, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _computeDeadline(options?: types.TimeoutOptions): number {
|
protected _getLogger(): InnerLogger {
|
||||||
return this._timeoutSettings.computeDeadline(options);
|
return this._logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _getTimeoutSettings(): TimeoutSettings {
|
||||||
|
return this._timeoutSettings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user