mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: remove the usages of raw target closed message constant (#27669)
This commit is contained in:
parent
5262e5ab35
commit
091f6883f5
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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';
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user