chore: remove the usages of raw target closed message constant (#27669)

This commit is contained in:
Pavel Feldman 2023-10-17 15:35:41 -07:00 committed by GitHub
parent 5262e5ab35
commit 091f6883f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 74 additions and 54 deletions

View File

@ -281,7 +281,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
const waiter = Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== Events.AndroidDevice.Close)
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, new TargetClosedError());
waiter.rejectOnEvent(this, Events.AndroidDevice.Close, () => new TargetClosedError());
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;

View File

@ -40,6 +40,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
// Used from @playwright/test fixtures.
_connectHeaders?: HeadersArray;
_closeReason: string | undefined;
static from(browser: channels.BrowserChannel): Browser {
return (browser as any)._object;
@ -131,6 +132,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
}
async close(options: { reason?: string } = {}): Promise<void> {
this._closeReason = options.reason;
try {
if (this._shouldCloseConnectionOnClose)
this._connection.close();

View File

@ -64,6 +64,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
readonly _isChromium: boolean;
private _harRecorders = new Map<string, { path: string, content: 'embed' | 'attach' | 'omit' | undefined }>();
private _closeWasCalled = false;
private _closeReason: string | undefined;
static from(context: channels.BrowserContextChannel): BrowserContext {
return (context as any)._object;
@ -337,6 +338,10 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
await this._channel.setNetworkInterceptionPatterns({ patterns });
}
_effectiveCloseReason(): string | undefined {
return this._closeReason || this._browser?._closeReason;
}
async waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions = {}): Promise<any> {
return this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
@ -344,7 +349,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
const waiter = Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== Events.BrowserContext.Close)
waiter.rejectOnEvent(this, Events.BrowserContext.Close, new TargetClosedError());
waiter.rejectOnEvent(this, Events.BrowserContext.Close, () => new TargetClosedError(this._effectiveCloseReason()));
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
@ -386,6 +391,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
async close(options: { reason?: string } = {}): Promise<void> {
if (this._closeWasCalled)
return;
this._closeReason = options.reason;
this._closeWasCalled = true;
await this._wrapApiCall(async () => {
await this._browserType?._willCloseContext(this);

View File

@ -184,9 +184,7 @@ export class Connection extends EventEmitter {
}
close(cause?: Error) {
this._closedError = new TargetClosedError();
if (cause)
rewriteErrorMessage(this._closedError, this._closedError.message + '\nCaused by: ' + cause.toString());
this._closedError = new TargetClosedError(cause?.toString());
for (const callback of this._callbacks.values())
callback.reject(this._closedError);
this._callbacks.clear();

View File

@ -121,7 +121,7 @@ export class ElectronApplication extends ChannelOwner<channels.ElectronApplicati
const waiter = Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== Events.ElectronApplication.Close)
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, new TargetClosedError());
waiter.rejectOnEvent(this, Events.ElectronApplication.Close, () => new TargetClosedError());
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;

View File

@ -36,7 +36,6 @@ import { urlMatches } from '../utils/network';
import type * as api from '../../types/types';
import type * as structs from '../../types/structs';
import { debugLogger } from '../common/debugLogger';
import { TargetClosedError } from '../common/errors';
export type WaitForNavigationOptions = {
timeout?: number,
@ -105,8 +104,8 @@ export class Frame extends ChannelOwner<channels.FrameChannel> implements api.Fr
private _setupNavigationWaiter(options: { timeout?: number }): Waiter {
const waiter = new Waiter(this._page!, '');
if (this._page!.isClosed())
waiter.rejectImmediately(new TargetClosedError());
waiter.rejectOnEvent(this._page!, Events.Page.Close, new TargetClosedError());
waiter.rejectImmediately(this._page!._closeErrorWithReason());
waiter.rejectOnEvent(this._page!, Events.Page.Close, () => this._page!._closeErrorWithReason());
waiter.rejectOnEvent(this._page!, Events.Page.Crash, new Error('Navigation failed because page crashed!'));
waiter.rejectOnEvent<Frame>(this._page!, Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this);
const timeout = this._page!._timeoutSettings.navigationTimeout(options);

View File

@ -34,7 +34,6 @@ import { MultiMap } from '../utils/multimap';
import { APIResponse } from './fetch';
import type { Serializable } from '../../types/structs';
import type { BrowserContext } from './browserContext';
import { TargetClosedError } from '../common/errors';
export type NetworkCookie = {
name: string,
@ -611,7 +610,7 @@ export class WebSocket extends ChannelOwner<channels.WebSocketChannel> implement
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 TargetClosedError());
waiter.rejectOnEvent(this._page, Events.Page.Close, () => this._page._closeErrorWithReason());
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;

View File

@ -19,7 +19,7 @@ import fs from 'fs';
import path from 'path';
import type * as structs from '../../types/structs';
import type * as api from '../../types/types';
import { isTargetClosedError, TargetClosedError, kTargetClosedErrorMessage } from '../common/errors';
import { isTargetClosedError, TargetClosedError } from '../common/errors';
import { urlMatches } from '../utils/network';
import { TimeoutSettings } from '../common/timeoutSettings';
import type * as channels from '@protocol/channels';
@ -93,6 +93,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
readonly _timeoutSettings: TimeoutSettings;
private _video: Video | null = null;
readonly _opener: Page | null;
private _closeReason: string | undefined;
static from(page: channels.PageChannel): Page {
return (page as any)._object;
@ -140,8 +141,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
this.coverage = new Coverage(this._channel);
this.once(Events.Page.Close, () => this._closedOrCrashedScope.close(kTargetClosedErrorMessage));
this.once(Events.Page.Crash, () => this._closedOrCrashedScope.close(kTargetClosedErrorMessage));
this.once(Events.Page.Close, () => this._closedOrCrashedScope.close(this._closeErrorWithReason()));
this.once(Events.Page.Crash, () => this._closedOrCrashedScope.close(new TargetClosedError()));
this._setEventToSubscriptionMapping(new Map<string, channels.PageUpdateSubscriptionParams['event']>([
[Events.Page.Console, 'console'],
@ -387,6 +388,10 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
return this._waitForEvent(event, optionsOrPredicate, `waiting for event "${event}"`);
}
_closeErrorWithReason(): TargetClosedError {
return new TargetClosedError(this._closeReason || this._browserContext._effectiveCloseReason());
}
private async _waitForEvent(event: string, optionsOrPredicate: WaitForEventOptions, logLine?: string): Promise<any> {
return this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
@ -398,7 +403,7 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
if (event !== Events.Page.Crash)
waiter.rejectOnEvent(this, Events.Page.Crash, new Error('Page crashed'));
if (event !== Events.Page.Close)
waiter.rejectOnEvent(this, Events.Page.Close, new TargetClosedError());
waiter.rejectOnEvent(this, Events.Page.Close, () => this._closeErrorWithReason());
const result = await waiter.waitForEvent(this, event, predicate as any);
waiter.dispose();
return result;
@ -513,7 +518,8 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
await this._channel.bringToFront();
}
async close(options: { runBeforeUnload?: boolean } = { runBeforeUnload: undefined }) {
async close(options: { runBeforeUnload?: boolean, reason?: string } = {}) {
this._closeReason = options.reason;
try {
if (this._ownedContext)
await this._ownedContext.close();

View File

@ -50,9 +50,9 @@ export class Waiter {
return this.waitForPromise(promise, dispose);
}
rejectOnEvent<T = void>(emitter: EventEmitter, event: string, error: Error, predicate?: (arg: T) => boolean | Promise<boolean>) {
rejectOnEvent<T = void>(emitter: EventEmitter, event: string, error: Error | (() => Error), predicate?: (arg: T) => boolean | Promise<boolean>) {
const { promise, dispose } = waitForEvent(emitter, event, predicate);
this._rejectOn(promise.then(() => { throw error; }), dispose);
this._rejectOn(promise.then(() => { throw (typeof error === 'function' ? error() : error); }), dispose);
}
rejectOnTimeout(timeout: number, message: string) {

View File

@ -23,7 +23,7 @@ import type { BrowserContext } from './browserContext';
import type * as api from '../../types/types';
import type * as structs from '../../types/structs';
import { LongStandingScope } from '../utils';
import { kTargetClosedErrorMessage } from '../common/errors';
import { TargetClosedError } from '../common/errors';
export class Worker extends ChannelOwner<channels.WorkerChannel> implements api.Worker {
_page: Page | undefined; // Set for web workers.
@ -43,7 +43,7 @@ export class Worker extends ChannelOwner<channels.WorkerChannel> implements api.
this._context._serviceWorkers.delete(this);
this.emit(Events.Worker.Close, this);
});
this.once(Events.Worker.Close, () => this._closedScope.close(kTargetClosedErrorMessage));
this.once(Events.Worker.Close, () => this._closedScope.close(this._page?._closeErrorWithReason() || new TargetClosedError()));
}
url(): string {

View File

@ -25,16 +25,13 @@ class CustomError extends Error {
export class TimeoutError extends CustomError {}
export const kTargetClosedErrorMessage = 'Target page, context or browser has been closed';
export const kTargetCrashedErrorMessage = 'Target crashed';
export class TargetClosedError extends Error {
constructor() {
super(kTargetClosedErrorMessage);
constructor(cause?: string, logs?: string) {
super((cause || 'Target page, context or browser has been closed') + (logs || ''));
this.name = this.constructor.name;
}
}
export function isTargetClosedError(error: Error) {
return error instanceof TargetClosedError || error.message.includes(kTargetClosedErrorMessage);
return error instanceof TargetClosedError || error.name === 'TargetClosedError';
}

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
import { TimeoutError } from '../common/errors';
import { TargetClosedError, TimeoutError } from '../common/errors';
import type { SerializedError, SerializedValue } from '@protocol/channels';
export function serializeError(e: any): SerializedError {
@ -34,6 +34,11 @@ export function parseError(error: SerializedError): Error {
e.stack = error.error.stack || '';
return e;
}
if (error.error.name === 'TargetClosedError') {
const e = new TargetClosedError(error.error.message);
e.stack = error.error.stack || '';
return e;
}
const e = new Error(error.error.message);
e.stack = error.error.stack || '';
e.name = error.error.name;

View File

@ -19,7 +19,7 @@ import type * as channels from '@protocol/channels';
import { serializeError } from '../../protocol/serializers';
import { findValidator, ValidationError, createMetadataValidator, type ValidatorContext } from '../../protocol/validator';
import { assert, isUnderTest, monotonicTime, rewriteErrorMessage } from '../../utils';
import { TargetClosedError, isTargetClosedError, kTargetClosedErrorMessage, kTargetCrashedErrorMessage } from '../../common/errors';
import { TargetClosedError, isTargetClosedError } from '../../common/errors';
import type { CallMetadata } from '../instrumentation';
import { SdkObject } from '../instrumentation';
import type { PlaywrightDispatcher } from './playwrightDispatcher';
@ -330,15 +330,17 @@ export class DispatcherConnection {
const validator = findValidator(dispatcher._type, method, 'Result');
callMetadata.result = validator(result, '', { tChannelImpl: this._tChannelImplToWire.bind(this), binary: this._isLocal ? 'buffer' : 'toBase64' });
} catch (e) {
if (isTargetClosedError(e) && sdkObject)
rewriteErrorMessage(e, closeReason(sdkObject));
if (isProtocolError(e)) {
if (isTargetClosedError(e) && sdkObject) {
const reason = closeReason(sdkObject);
if (reason)
rewriteErrorMessage(e, reason);
} else if (isProtocolError(e)) {
if (e.type === 'closed') {
const closedReason = sdkObject ? closeReason(sdkObject) : kTargetClosedErrorMessage;
rewriteErrorMessage(e, closedReason + e.browserLogMessage());
const reason = sdkObject ? closeReason(sdkObject) : undefined;
e = new TargetClosedError(reason, e.browserLogMessage());
} else if (e.type === 'crashed') {
rewriteErrorMessage(e, 'Target crashed ' + e.browserLogMessage());
}
if (e.type === 'crashed')
rewriteErrorMessage(e, kTargetCrashedErrorMessage + e.browserLogMessage());
}
callMetadata.error = serializeError(e);
} finally {
@ -357,8 +359,8 @@ export class DispatcherConnection {
}
}
function closeReason(sdkObject: SdkObject) {
function closeReason(sdkObject: SdkObject): string | undefined {
return sdkObject.attribution.page?._closeReason ||
sdkObject.attribution.context?._closeReason ||
sdkObject.attribution.browser?._closeReason || kTargetClosedErrorMessage;
sdkObject.attribution.browser?._closeReason;
}

View File

@ -1532,7 +1532,7 @@ export class Frame extends SdkObject {
_onDetached() {
this._stopNetworkIdleTimer();
this._detachedScope.close('Frame was detached');
this._detachedScope.close(new Error('Frame was detached'));
for (const data of this._contextData.values()) {
if (data.context)
data.context.contextDestroyed('Frame was detached');

View File

@ -73,7 +73,7 @@ export class ExecutionContext extends SdkObject {
}
contextDestroyed(reason: string) {
this._contextDestroyedScope.close(reason);
this._contextDestroyedScope.close(new Error(reason));
}
async _raceAgainstContextDestroyed<T>(promise: Promise<T>): Promise<T> {

View File

@ -43,7 +43,7 @@ import type { TimeoutOptions } from '../common/types';
import { isInvalidSelectorError } from '../utils/isomorphic/selectorParser';
import { parseEvaluationResultValue, source } from './isomorphic/utilityScriptSerializers';
import type { SerializedValue } from './isomorphic/utilityScriptSerializers';
import { kTargetClosedErrorMessage } from '../common/errors';
import { TargetClosedError } from '../common/errors';
export interface PageDelegate {
readonly rawMouse: input.RawMouse;
@ -277,7 +277,7 @@ export class Page extends SdkObject {
this.emit(Page.Events.Close);
this._closedPromise.resolve();
this.instrumentation.onPageClose(this);
this.openScope.close(kTargetClosedErrorMessage);
this.openScope.close(new TargetClosedError());
}
_didCrash() {
@ -286,7 +286,7 @@ export class Page extends SdkObject {
this.emit(Page.Events.Crash);
this._crashed = true;
this.instrumentation.onPageClose(this);
this.openScope.close('Page crashed');
this.openScope.close(new Error('Page crashed'));
}
async _onFileChooserOpened(handle: dom.ElementHandle) {
@ -733,7 +733,7 @@ export class Worker extends SdkObject {
if (this._existingExecutionContext)
this._existingExecutionContext.contextDestroyed('Worker was closed');
this.emit(Worker.Events.Close, this);
this.openScope.close('Worker closed');
this.openScope.close(new Error('Worker closed'));
}
async evaluateExpression(expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {

View File

@ -58,7 +58,7 @@ export class ManualPromise<T = void> extends Promise<T> {
export class LongStandingScope {
private _terminateError: Error | undefined;
private _terminateErrorMessage: string | undefined;
private _closeError: Error | undefined;
private _terminatePromises = new Map<ManualPromise<Error>, string[]>();
private _isClosed = false;
@ -69,14 +69,11 @@ export class LongStandingScope {
p.resolve(error);
}
close(errorMessage: string) {
close(error: Error) {
this._isClosed = true;
this._terminateErrorMessage = errorMessage;
for (const [p, frames] of this._terminatePromises) {
const error = new Error(errorMessage);
error.stack = [error.name + ':' + errorMessage, ...frames].join('\n');
p.resolve(error);
}
this._closeError = error;
for (const [p, frames] of this._terminatePromises)
p.resolve(cloneError(error, frames));
}
isClosed() {
@ -97,11 +94,12 @@ export class LongStandingScope {
private async _race(promises: Promise<any>[], safe: boolean, defaultValue?: any): Promise<any> {
const terminatePromise = new ManualPromise<Error>();
const frames = captureRawStack();
if (this._terminateError)
terminatePromise.resolve(this._terminateError);
if (this._terminateErrorMessage)
terminatePromise.resolve(new Error(this._terminateErrorMessage));
this._terminatePromises.set(terminatePromise, captureRawStack());
if (this._closeError)
terminatePromise.resolve(cloneError(this._closeError, frames));
this._terminatePromises.set(terminatePromise, frames);
try {
return await Promise.race([
terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)),
@ -112,3 +110,11 @@ export class LongStandingScope {
}
}
}
function cloneError(error: Error, frames: string[]) {
const clone = new Error();
clone.name = error.name;
clone.message = error.message;
clone.stack = [error.name + ':' + error.message, ...frames].join('\n');
return clone;
}