chore: remove isContextDestroyedError heuristic (#8456)

This commit is contained in:
Pavel Feldman 2021-08-26 18:44:49 -07:00 committed by GitHub
parent 32fbf22add
commit 4ed976f2e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 128 additions and 102 deletions

View File

@ -23,6 +23,7 @@ import { rewriteErrorMessage } from '../../utils/stackTrace';
import { debugLogger, RecentLogsCollector } from '../../utils/debugLogger';
import { ProtocolLogger } from '../types';
import { helper } from '../helper';
import { ProtocolError } from '../common/protocolError';
export const ConnectionEvents = {
Disconnected: Symbol('ConnectionEvents.Disconnected')
@ -126,7 +127,7 @@ export const CRSessionEvents = {
export class CRSession extends EventEmitter {
_connection: CRConnection | null;
_eventListener?: (method: string, params?: Object) => void;
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError, method: string}>();
private readonly _targetType: string;
private readonly _sessionId: string;
private readonly _rootSessionId: string;
@ -164,19 +165,19 @@ export class CRSession extends EventEmitter {
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
if (this._crashed)
throw new Error('Target crashed');
throw new ProtocolError(true, 'Target crashed');
if (this._browserDisconnectedLogs !== undefined)
throw new Error(`Protocol error (${method}): Browser closed.` + this._browserDisconnectedLogs);
throw new ProtocolError(true, `Browser closed.` + this._browserDisconnectedLogs);
if (!this._connection)
throw new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`);
throw new ProtocolError(true, `Target closed`);
const id = this._connection._rawSend(this._sessionId, method, params);
return new Promise((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
this._callbacks.set(id, {resolve, reject, error: new ProtocolError(false), method});
});
}
_sendMayFail<T extends keyof Protocol.CommandParameters>(method: T, params?: Protocol.CommandParameters[T]): Promise<Protocol.CommandReturnValues[T] | void> {
return this.send(method, params).catch((error: Error) => debugLogger.log('error', error));
return this.send(method, params).catch((error: ProtocolError) => debugLogger.log('error', error));
}
_onMessage(object: ProtocolResponse) {
@ -208,16 +209,18 @@ export class CRSession extends EventEmitter {
_onClosed(browserDisconnectedLogs: string | undefined) {
this._browserDisconnectedLogs = browserDisconnectedLogs;
const errorMessage = browserDisconnectedLogs !== undefined ? 'Browser closed.' + browserDisconnectedLogs : 'Target closed.';
for (const callback of this._callbacks.values())
callback.reject(rewriteErrorMessage(callback.error, `Protocol error (${callback.method}): ` + errorMessage));
const errorMessage = browserDisconnectedLogs !== undefined ? 'Browser closed.' + browserDisconnectedLogs : 'Target closed';
for (const callback of this._callbacks.values()) {
callback.error.sessionClosed = true;
callback.reject(rewriteErrorMessage(callback.error, errorMessage));
}
this._callbacks.clear();
this._connection = null;
Promise.resolve().then(() => this.emit(CRSessionEvents.Disconnected));
}
}
function createProtocolError(error: Error, method: string, protocolError: { message: string; data: any; }): Error {
function createProtocolError(error: ProtocolError, method: string, protocolError: { message: string; data: any; }): ProtocolError {
let message = `Protocol error (${method}): ${protocolError.message}`;
if ('data' in protocolError)
message += ` ${protocolError.data}`;

View File

@ -21,6 +21,7 @@ import { Protocol } from './protocol';
import * as js from '../javascript';
import { rewriteErrorMessage } from '../../utils/stackTrace';
import { parseEvaluationResultValue } from '../common/utilityScriptSerializers';
import { isSessionClosedError } from '../common/protocolError';
export class CRExecutionContext implements js.ExecutionContextDelegate {
_client: CRSession;
@ -38,7 +39,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
returnByValue: true,
}).catch(rewriteError);
if (exceptionDetails)
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails));
return remoteObject.value;
}
@ -48,7 +49,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
contextId: this._contextId,
}).catch(rewriteError);
if (exceptionDetails)
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails));
return remoteObject.objectId!;
}
@ -76,7 +77,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
userGesture: true
}).catch(rewriteError);
if (exceptionDetails)
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails));
return returnByValue ? parseEvaluationResultValue(remoteObject.value) : utilityScript._context.createHandle(remoteObject);
}
@ -109,10 +110,10 @@ function rewriteError(error: Error): Protocol.Runtime.evaluateReturnValue {
if (error.message.includes('Object couldn\'t be returned by value'))
return {result: {type: 'undefined'}};
if (js.isContextDestroyedError(error) || error.message.endsWith('Inspected target navigated or closed'))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
if (error instanceof TypeError && error.message.startsWith('Converting circular structure to JSON'))
rewriteErrorMessage(error, error.message + ' Are you passing a nested JSHandle?');
if (!js.isJavaScriptErrorInEvaluate(error) && !isSessionClosedError(error))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export class ProtocolError extends Error {
sessionClosed: boolean;
constructor(sessionClosed: boolean, message?: string) {
super(message);
this.sessionClosed = sessionClosed || false;
}
}
export function isSessionClosedError(e: Error): boolean {
return e instanceof ProtocolError && e.sessionClosed;
}

View File

@ -104,7 +104,7 @@ export class FFBrowser extends Browser {
assert(type === 'page');
const context = browserContextId ? this._contexts.get(browserContextId)! : this._defaultContext as FFBrowserContext;
assert(context, `Unknown context id:${browserContextId}, _defaultContext: ${this._defaultContext}`);
const session = this._connection.createSession(payload.sessionId, type);
const session = this._connection.createSession(payload.sessionId);
const opener = openerId ? this._ffPages.get(openerId)! : null;
const ffPage = new FFPage(session, context, opener);
this._ffPages.set(targetId, ffPage);

View File

@ -23,6 +23,7 @@ import { rewriteErrorMessage } from '../../utils/stackTrace';
import { debugLogger, RecentLogsCollector } from '../../utils/debugLogger';
import { ProtocolLogger } from '../types';
import { helper } from '../helper';
import { ProtocolError } from '../common/protocolError';
export const ConnectionEvents = {
Disconnected: Symbol('Disconnected'),
@ -34,7 +35,7 @@ export const kBrowserCloseMessageId = -9999;
export class FFConnection extends EventEmitter {
private _lastId: number;
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
private _callbacks: Map<number, {resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError, method: string}>;
private _transport: ConnectionTransport;
private readonly _protocolLogger: ProtocolLogger;
private readonly _browserLogsCollector: RecentLogsCollector;
@ -76,7 +77,7 @@ export class FFConnection extends EventEmitter {
const id = this.nextMessageId();
this._rawSend({id, method, params});
return new Promise((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
this._callbacks.set(id, {resolve, reject, error: new ProtocolError(false), method});
});
}
@ -86,7 +87,7 @@ export class FFConnection extends EventEmitter {
_checkClosed(method: string) {
if (this._closed)
throw new Error(`Protocol error (${method}): Browser closed.` + helper.formatBrowserLogs(this._browserLogsCollector.recentLogs()));
throw new ProtocolError(true, `${method}): Browser closed.` + helper.formatBrowserLogs(this._browserLogsCollector.recentLogs()));
}
_rawSend(message: ProtocolRequest) {
@ -123,10 +124,13 @@ export class FFConnection extends EventEmitter {
this._transport.onclose = undefined;
const formattedBrowserLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
for (const session of this._sessions.values())
session.dispose(formattedBrowserLogs);
session.dispose();
this._sessions.clear();
for (const callback of this._callbacks.values())
callback.reject(rewriteErrorMessage(callback.error, `Protocol error (${callback.method}): Browser closed.` + formattedBrowserLogs));
for (const callback of this._callbacks.values()) {
const error = rewriteErrorMessage(callback.error, `Protocol error (${callback.method}): Browser closed.` + formattedBrowserLogs);
error.sessionClosed = true;
callback.reject(error);
}
this._callbacks.clear();
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
}
@ -136,8 +140,8 @@ export class FFConnection extends EventEmitter {
this._transport.close();
}
createSession(sessionId: string, type: string): FFSession {
const session = new FFSession(this, type, sessionId, message => this._rawSend({...message, sessionId}));
createSession(sessionId: string): FFSession {
const session = new FFSession(this, sessionId, message => this._rawSend({...message, sessionId}));
this._sessions.set(sessionId, session);
return session;
}
@ -150,8 +154,7 @@ export const FFSessionEvents = {
export class FFSession extends EventEmitter {
_connection: FFConnection;
_disposed = false;
private _callbacks: Map<number, {resolve: Function, reject: Function, error: Error, method: string}>;
private _targetType: string;
private _callbacks: Map<number, {resolve: Function, reject: Function, error: ProtocolError, method: string}>;
private _sessionId: string;
private _rawSend: (message: any) => void;
private _crashed: boolean = false;
@ -161,12 +164,11 @@ export class FFSession extends EventEmitter {
override removeListener: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
override once: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
constructor(connection: FFConnection, targetType: string, sessionId: string, rawSend: (message: any) => void) {
constructor(connection: FFConnection, sessionId: string, rawSend: (message: any) => void) {
super();
this.setMaxListeners(0);
this._callbacks = new Map();
this._connection = connection;
this._targetType = targetType;
this._sessionId = sessionId;
this._rawSend = rawSend;
@ -186,14 +188,14 @@ export class FFSession extends EventEmitter {
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
if (this._crashed)
throw new Error('Page crashed');
throw new ProtocolError(true, 'Target crashed');
this._connection._checkClosed(method);
if (this._disposed)
throw new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`);
throw new ProtocolError(true, 'Target closed');
const id = this._connection.nextMessageId();
this._rawSend({method, params, id});
return new Promise((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
this._callbacks.set(id, {resolve, reject, error: new ProtocolError(false), method});
});
}
@ -215,9 +217,11 @@ export class FFSession extends EventEmitter {
}
}
dispose(formattedBrowserLogs?: string) {
for (const callback of this._callbacks.values())
callback.reject(rewriteErrorMessage(callback.error, `Protocol error (${callback.method}): Target closed.` + formattedBrowserLogs));
dispose() {
for (const callback of this._callbacks.values()) {
callback.error.sessionClosed = true;
callback.reject(rewriteErrorMessage(callback.error, 'Target closed'));
}
this._callbacks.clear();
this._disposed = true;
this._connection._sessions.delete(this._sessionId);
@ -225,7 +229,7 @@ export class FFSession extends EventEmitter {
}
}
function createProtocolError(error: Error, method: string, protocolError: { message: string; data: any; }): Error {
function createProtocolError(error: ProtocolError, method: string, protocolError: { message: string; data: any; }): ProtocolError {
let message = `Protocol error (${method}): ${protocolError.message}`;
if ('data' in protocolError)
message += ` ${protocolError.data}`;

View File

@ -20,6 +20,7 @@ import { FFSession } from './ffConnection';
import { Protocol } from './protocol';
import { rewriteErrorMessage } from '../../utils/stackTrace';
import { parseEvaluationResultValue } from '../common/utilityScriptSerializers';
import { isSessionClosedError } from '../common/protocolError';
export class FFExecutionContext implements js.ExecutionContextDelegate {
_session: FFSession;
@ -103,18 +104,18 @@ function checkException(exceptionDetails?: Protocol.Runtime.ExceptionDetails) {
if (!exceptionDetails)
return;
if (exceptionDetails.value)
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
throw new js.JavaScriptErrorInEvaluate(JSON.stringify(exceptionDetails.value));
else
throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
throw new js.JavaScriptErrorInEvaluate(exceptionDetails.text + '\n' + exceptionDetails.stack);
}
function rewriteError(error: Error): (Protocol.Runtime.evaluateReturnValue | Protocol.Runtime.callFunctionReturnValue) {
if (error.message.includes('cyclic object value') || error.message.includes('Object is not serializable'))
return {result: {type: 'undefined', value: undefined}};
if (js.isContextDestroyedError(error))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
if (error instanceof TypeError && error.message.startsWith('Converting circular structure to JSON'))
rewriteErrorMessage(error, error.message + ' Are you passing a nested JSHandle?');
if (!js.isJavaScriptErrorInEvaluate(error) && !isSessionClosedError(error))
throw new Error('Execution context was destroyed, most likely because of a navigation.');
throw error;
}

View File

@ -269,7 +269,7 @@ export class FFPage implements PageDelegate {
async _onWorkerCreated(event: Protocol.Page.workerCreatedPayload) {
const workerId = event.workerId;
const worker = new Worker(this._page, event.url);
const workerSession = new FFSession(this._session._connection, 'worker', workerId, (message: any) => {
const workerSession = new FFSession(this._session._connection, workerId, (message: any) => {
this._session.send('Page.sendMessageToWorker', {
frameId: event.frameId,
workerId: workerId,

View File

@ -30,6 +30,7 @@ import { assert, constructURLBasedOnBaseURL, makeWaitForNextTask } from '../util
import { debugLogger } from '../utils/debugLogger';
import { CallMetadata, internalCallMetadata, SdkObject } from './instrumentation';
import { ElementStateWithoutStable } from './injected/injectedScript';
import { isSessionClosedError } from './common/protocolError';
type ContextData = {
contextPromise: Promise<dom.FrameExecutionContext>;
@ -728,7 +729,7 @@ export class Frame extends SdkObject {
return adopted;
} catch (e) {
// Navigated while trying to adopt the node.
if (!js.isContextDestroyedError(e) && !e.message.includes(dom.kUnableToAdoptErrorMessage))
if (js.isJavaScriptErrorInEvaluate(e))
throw e;
result.dispose();
}
@ -1350,11 +1351,12 @@ class RerunnableTask {
this._contextData.rerunnableTasks.delete(this);
this._resolve(result);
} catch (e) {
if (js.isJavaScriptErrorInEvaluate(e) || isSessionClosedError(e)) {
this._contextData.rerunnableTasks.delete(this);
this._reject(e);
}
// We will try again in the new execution context.
if (js.isContextDestroyedError(e))
return;
this._contextData.rerunnableTasks.delete(this);
this._reject(e);
}
}
}

View File

@ -280,26 +280,10 @@ export function normalizeEvaluationExpression(expression: string, isFunction: bo
return expression;
}
export const kSwappedOutErrorMessage = 'Target was swapped out.';
export function isContextDestroyedError(e: any) {
if (!e || typeof e !== 'object' || typeof e.message !== 'string')
return false;
// Evaluating in a context which was already destroyed.
if (e.message.includes('Cannot find context with specified id')
|| e.message.includes('Failed to find execution context with id')
|| e.message.includes('Missing injected script for given')
|| e.message.includes('Cannot find object with id'))
return true;
// Evaluation promise is rejected when context is gone.
if (e.message.includes('Execution context was destroyed'))
return true;
// WebKit target swap.
if (e.message.includes(kSwappedOutErrorMessage))
return true;
return false;
// Error inside the expression evaluation as opposed to a protocol error.
export class JavaScriptErrorInEvaluate extends Error {
}
export function isJavaScriptErrorInEvaluate(error: Error) {
return error instanceof JavaScriptErrorInEvaluate;
}

View File

@ -435,7 +435,7 @@ export class Page extends SdkObject {
const runBeforeUnload = !!options && !!options.runBeforeUnload;
if (this._closedState !== 'closing') {
this._closedState = 'closing';
assert(!this._disconnected, 'Protocol error: Connection closed. Most likely the page has been closed.');
assert(!this._disconnected, 'Target closed');
// This might throw if the browser context containing the page closes
// while we are trying to close the page.
await this._delegate.closePage(runBeforeUnload).catch(e => debugLogger.log('error', e));

View File

@ -153,7 +153,7 @@ export class WKBrowser extends Browser {
context = this._defaultContext as WKBrowserContext;
if (!context)
return;
const pageProxySession = new WKSession(this._connection, pageProxyId, `The page has been closed.`, (message: any) => {
const pageProxySession = new WKSession(this._connection, pageProxyId, `Target closed`, (message: any) => {
this._connection.rawSend({ ...message, pageProxyId });
});
const opener = event.openerId ? this._wkPages.get(event.openerId) : undefined;

View File

@ -24,6 +24,7 @@ import { debugLogger, RecentLogsCollector } from '../../utils/debugLogger';
import { ProtocolLogger } from '../types';
import { helper } from '../helper';
import { kBrowserClosedError } from '../../utils/errors';
import { ProtocolError } from '../common/protocolError';
// WKPlaywright uses this special id to issue Browser.close command which we
// should ignore.
@ -101,7 +102,7 @@ export class WKSession extends EventEmitter {
private _disposed = false;
private readonly _rawSend: (message: any) => void;
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: Error) => void, error: Error, method: string}>();
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError, method: string}>();
private _crashed: boolean = false;
override on: <T extends keyof Protocol.Events | symbol>(event: T, listener: (payload: T extends symbol ? any : Protocol.Events[T extends keyof Protocol.Events ? T : never]) => void) => this;
@ -130,14 +131,14 @@ export class WKSession extends EventEmitter {
params?: Protocol.CommandParameters[T]
): Promise<Protocol.CommandReturnValues[T]> {
if (this._crashed)
throw new Error('Target crashed');
throw new ProtocolError(true, 'Target crashed');
if (this._disposed)
throw new Error(`Protocol error (${method}): ${this.errorText}`);
throw new ProtocolError(true, `Target closed`);
const id = this.connection.nextMessageId();
const messageObj = { id, method, params };
this._rawSend(messageObj);
return new Promise<Protocol.CommandReturnValues[T]>((resolve, reject) => {
this._callbacks.set(id, {resolve, reject, error: new Error(), method});
this._callbacks.set(id, {resolve, reject, error: new ProtocolError(false), method});
});
}
@ -156,8 +157,10 @@ export class WKSession extends EventEmitter {
dispose(disconnected: boolean) {
if (disconnected)
this.errorText = 'Browser closed.' + helper.formatBrowserLogs(this.connection._browserLogsCollector.recentLogs());
for (const callback of this._callbacks.values())
callback.reject(rewriteErrorMessage(callback.error, `Protocol error (${callback.method}): ${this.errorText}`));
for (const callback of this._callbacks.values()) {
callback.error.sessionClosed = true;
callback.reject(rewriteErrorMessage(callback.error, this.errorText));
}
this._callbacks.clear();
this._disposed = true;
}
@ -179,7 +182,7 @@ export class WKSession extends EventEmitter {
}
}
export function createProtocolError(error: Error, method: string, protocolError: { message: string; data: any; }): Error {
export function createProtocolError(error: ProtocolError, method: string, protocolError: { message: string; data: any; }): ProtocolError {
let message = `Protocol error (${method}): ${protocolError.message}`;
if ('data' in protocolError)
message += ` ${JSON.stringify(protocolError.data)}`;

View File

@ -19,6 +19,7 @@ import { WKSession } from './wkConnection';
import { Protocol } from './protocol';
import * as js from '../javascript';
import { parseEvaluationResultValue } from '../common/utilityScriptSerializers';
import { isSessionClosedError } from '../common/protocolError';
export class WKExecutionContext implements js.ExecutionContextDelegate {
private readonly _session: WKSession;
@ -46,7 +47,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
returnByValue: true
});
if (response.wasThrown)
throw new Error('Evaluation failed: ' + response.result.description);
throw new js.JavaScriptErrorInEvaluate(response.result.description);
return response.result.value;
} catch (error) {
throw rewriteError(error);
@ -61,7 +62,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
returnByValue: false
});
if (response.wasThrown)
throw new Error('Evaluation failed: ' + response.result.description);
throw new js.JavaScriptErrorInEvaluate(response.result.description);
return response.result.objectId!;
} catch (error) {
throw rewriteError(error);
@ -81,7 +82,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
try {
const response = await Promise.race([
this._executionContextDestroyedPromise.then(() => contextDestroyedResult),
this._executionContextDestroyedPromise.then(() => { throw new Error(contextDestroyedError); }),
this._session.send('Runtime.callFunctionOn', {
functionDeclaration: expression,
objectId: utilityScript._objectId!,
@ -96,7 +97,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
})
]);
if (response.wasThrown)
throw new Error('Evaluation failed: ' + response.result.description);
throw new js.JavaScriptErrorInEvaluate(response.result.description);
if (returnByValue)
return parseEvaluationResultValue(response.result.value);
return utilityScript._context.createHandle(response.result);
@ -129,12 +130,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
}
}
const contextDestroyedResult = {
wasThrown: true,
result: {
description: 'Protocol error: Execution context was destroyed, most likely because of a navigation.'
} as Protocol.Runtime.RemoteObject
};
const contextDestroyedError = 'Execution context was destroyed.';
function potentiallyUnserializableValue(remoteObject: Protocol.Runtime.RemoteObject): any {
const value = remoteObject.value;
@ -143,7 +139,7 @@ function potentiallyUnserializableValue(remoteObject: Protocol.Runtime.RemoteObj
}
function rewriteError(error: Error): Error {
if (js.isContextDestroyedError(error))
if (!js.isJavaScriptErrorInEvaluate(error) && !isSessionClosedError(error))
return new Error('Execution context was destroyed, most likely because of a navigation.');
return error;
}

View File

@ -26,7 +26,7 @@ import * as dom from '../dom';
import * as frames from '../frames';
import { eventsHelper, RegisteredListener } from '../../utils/eventsHelper';
import { helper } from '../helper';
import { JSHandle, kSwappedOutErrorMessage } from '../javascript';
import { JSHandle } from '../javascript';
import * as network from '../network';
import { Page, PageBinding, PageDelegate } from '../page';
import { Progress } from '../progress';
@ -223,7 +223,6 @@ export class WKPage implements PageDelegate {
assert(this._provisionalPage);
assert(this._provisionalPage._session.sessionId === newTargetId, 'Unknown new target: ' + newTargetId);
assert(this._session.sessionId === oldTargetId, 'Unknown old target: ' + oldTargetId);
this._session.errorText = kSwappedOutErrorMessage;
const newSession = this._provisionalPage._session;
this._provisionalPage.commit();
this._provisionalPage.dispose();
@ -294,7 +293,7 @@ export class WKPage implements PageDelegate {
private async _onTargetCreated(event: Protocol.Target.targetCreatedPayload) {
const { targetInfo } = event;
const session = new WKSession(this._pageProxySession.connection, targetInfo.targetId, `The ${targetInfo.type} has been closed.`, (message: any) => {
const session = new WKSession(this._pageProxySession.connection, targetInfo.targetId, `Target closed`, (message: any) => {
this._pageProxySession.send('Target.sendMessageToTarget', {
message: JSON.stringify(message), targetId: targetInfo.targetId
}).catch(e => {

View File

@ -21,13 +21,12 @@ import { isUnderTest } from './utils';
const stackUtils = new StackUtils();
export function rewriteErrorMessage(e: Error, newMessage: string): Error {
if (e.stack) {
const index = e.stack.indexOf(e.message);
if (index !== -1)
e.stack = e.stack.substring(0, index) + newMessage + e.stack.substring(index + e.message.length);
}
export function rewriteErrorMessage<E extends Error>(e: E, newMessage: string): E {
const lines: string[] = (e.stack?.split('\n') || []).filter(l => l.startsWith(' at '));
e.message = newMessage;
const errorTitle = `${e.name}: ${e.message}`;
if (lines.length)
e.stack = `${errorTitle}\n${lines.join('\n')}`;
return e;
}

View File

@ -25,7 +25,8 @@ it('should reject all promises when browser is closed', async ({browserType, bro
await page.evaluate(() => new Promise(f => setTimeout(f, 0)));
await browser.close();
await neverResolves;
expect(error.message).toContain('Protocol error');
// WebKit under task-set -c 1 is giving browser, rest are giving target.
expect(error.message).toContain(' closed');
});
it('should throw if userDataDir option is passed', async ({browserType, browserOptions}) => {

View File

@ -23,7 +23,7 @@ it('should reject all promises when page is closed', async ({page}) => {
page.evaluate(() => new Promise(r => {})).catch(e => error = e),
page.close(),
]);
expect(error.message).toContain('Protocol error');
expect(error.message).toContain('Target closed');
});
it('should set the page close state', async ({page}) => {

View File

@ -45,7 +45,7 @@ it.describe('', () => {
await page.waitForEvent('crash');
const err = await page.evaluate(() => {}).then(() => null, e => e);
expect(err).toBeTruthy();
expect(err.message).toContain('crash');
expect(err.message).toContain('Target crashed');
});
it('should cancel waitForEvent when page crashes', async ({ page, toImpl, browserName, platform, mode }) => {

View File

@ -506,7 +506,7 @@ it('should not fulfill with redirect status', async ({page, server, browserName}
}
});
it('should support cors with GET', async ({page, server}) => {
it('should support cors with GET', async ({page, server, browserName}) => {
await page.goto(server.EMPTY_PAGE);
await page.route('**/cars*', async (route, request) => {
const headers = request.url().endsWith('allow') ? { 'access-control-allow-origin': '*' } : {};
@ -531,7 +531,12 @@ it('should support cors with GET', async ({page, server}) => {
const response = await fetch('https://example.com/cars?reject', { mode: 'cors' });
return response.json();
}).catch(e => e);
expect(error.message).toContain('failed');
if (browserName === 'chromium')
expect(error.message).toContain('Failed');
if (browserName === 'webkit')
expect(error.message).toContain('TypeError');
if (browserName === 'firefox')
expect(error.message).toContain('NetworkError');
}
});

View File

@ -41,7 +41,7 @@ it('should emit created and destroyed events', async function({page}) {
await page.evaluate(workerObj => workerObj.terminate(), workerObj);
expect(await workerDestroyedPromise).toBe(worker);
const error = await workerThisObj.getProperty('self').catch(error => error);
expect(error.message).toContain('Most likely the worker has been closed.');
expect(error.message).toContain('Target closed');
});
it('should report console logs', async function({page}) {