mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: push protocol error conversion to dispatcher (#27608)
This commit is contained in:
parent
d83d9ce268
commit
b0c73b72f1
@ -26,6 +26,7 @@ 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() {
|
||||
|
||||
@ -39,6 +39,7 @@ import { RecentLogsCollector } from '../common/debugLogger';
|
||||
import type { CallMetadata } from './instrumentation';
|
||||
import { SdkObject } from './instrumentation';
|
||||
import { ManualPromise } from '../utils/manualPromise';
|
||||
import { type ProtocolError, isProtocolError } from './protocolError';
|
||||
|
||||
export const kNoXServerRunningError = 'Looks like you launched a headed browser without having a XServer running.\n' +
|
||||
'Set either \'headless: true\' or use \'xvfb-run <your-playwright-app>\' before running Playwright.\n\n<3 Playwright Team';
|
||||
@ -68,7 +69,7 @@ export abstract class BrowserType extends SdkObject {
|
||||
const seleniumHubUrl = (options as any).__testHookSeleniumRemoteURL || process.env.SELENIUM_REMOTE_URL;
|
||||
if (seleniumHubUrl)
|
||||
return this._launchWithSeleniumHub(progress, seleniumHubUrl, options);
|
||||
return this._innerLaunchWithRetries(progress, options, undefined, helper.debugProtocolLogger(protocolLogger)).catch(e => { throw this._rewriteStartupError(e); });
|
||||
return this._innerLaunchWithRetries(progress, options, undefined, helper.debugProtocolLogger(protocolLogger)).catch(e => { throw this._rewriteStartupLog(e); });
|
||||
}, TimeoutSettings.launchTimeout(options));
|
||||
return browser;
|
||||
}
|
||||
@ -79,7 +80,7 @@ export abstract class BrowserType extends SdkObject {
|
||||
const persistent: channels.BrowserNewContextParams = options;
|
||||
controller.setLogName('browser');
|
||||
const browser = await controller.run(progress => {
|
||||
return this._innerLaunchWithRetries(progress, options, persistent, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupError(e); });
|
||||
return this._innerLaunchWithRetries(progress, options, persistent, helper.debugProtocolLogger(), userDataDir).catch(e => { throw this._rewriteStartupLog(e); });
|
||||
}, TimeoutSettings.launchTimeout(options));
|
||||
return browser._defaultContext!;
|
||||
}
|
||||
@ -294,10 +295,16 @@ export abstract class BrowserType extends SdkObject {
|
||||
}
|
||||
}
|
||||
|
||||
_rewriteStartupLog(error: Error): Error {
|
||||
if (!isProtocolError(error))
|
||||
return error;
|
||||
return this._doRewriteStartupLog(error);
|
||||
}
|
||||
|
||||
abstract _defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[];
|
||||
abstract _connectToTransport(transport: ConnectionTransport, options: BrowserOptions): Promise<Browser>;
|
||||
abstract _amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env;
|
||||
abstract _rewriteStartupError(error: Error): Error;
|
||||
abstract _doRewriteStartupLog(error: ProtocolError): ProtocolError;
|
||||
abstract _attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void;
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,6 @@ import { CRBrowser } from './crBrowser';
|
||||
import type { Env } from '../../utils/processLauncher';
|
||||
import { gracefullyCloseSet } from '../../utils/processLauncher';
|
||||
import { kBrowserCloseMessageId } from './crConnection';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import { BrowserType, kNoXServerRunningError } from '../browserType';
|
||||
import type { ConnectionTransport, ProtocolRequest } from '../transport';
|
||||
import { WebSocketTransport } from '../transport';
|
||||
@ -49,6 +48,7 @@ import { registry } from '../registry';
|
||||
import { ManualPromise } from '../../utils/manualPromise';
|
||||
import { validateBrowserContextOptions } from '../browserContext';
|
||||
import { chromiumSwitches } from './chromiumSwitches';
|
||||
import type { ProtocolError } from '../protocolError';
|
||||
|
||||
const ARTIFACTS_FOLDER = path.join(os.tmpdir(), 'playwright-artifacts-');
|
||||
|
||||
@ -139,14 +139,16 @@ export class Chromium extends BrowserType {
|
||||
return CRBrowser.connect(this.attribution.playwright, transport, options, devtools);
|
||||
}
|
||||
|
||||
_rewriteStartupError(error: Error): Error {
|
||||
if (error.message.includes('Missing X server'))
|
||||
return rewriteErrorMessage(error, '\n' + wrapInASCIIBox(kNoXServerRunningError, 1));
|
||||
_doRewriteStartupLog(error: ProtocolError): ProtocolError {
|
||||
if (!error.logs)
|
||||
return error;
|
||||
if (error.logs.includes('Missing X server'))
|
||||
error.logs = '\n' + wrapInASCIIBox(kNoXServerRunningError, 1);
|
||||
// These error messages are taken from Chromium source code as of July, 2020:
|
||||
// https://github.com/chromium/chromium/blob/70565f67e79f79e17663ad1337dc6e63ee207ce9/content/browser/zygote_host/zygote_host_impl_linux.cc
|
||||
if (!error.message.includes('crbug.com/357670') && !error.message.includes('No usable sandbox!') && !error.message.includes('crbug.com/638180'))
|
||||
if (!error.logs.includes('crbug.com/357670') && !error.logs.includes('No usable sandbox!') && !error.logs.includes('crbug.com/638180'))
|
||||
return error;
|
||||
return rewriteErrorMessage(error, [
|
||||
error.logs = [
|
||||
`Chromium sandboxing failed!`,
|
||||
`================================`,
|
||||
`To workaround sandboxing issues, do either of the following:`,
|
||||
@ -154,7 +156,8 @@ export class Chromium extends BrowserType {
|
||||
` - (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option`,
|
||||
`================================`,
|
||||
``,
|
||||
].join('\n'));
|
||||
].join('\n');
|
||||
return error;
|
||||
}
|
||||
|
||||
_amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env {
|
||||
|
||||
@ -19,13 +19,11 @@ import { type RegisteredListener, assert, eventsHelper } from '../../utils';
|
||||
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||
import type { Protocol } from './protocol';
|
||||
import { EventEmitter } from 'events';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import type { RecentLogsCollector } from '../../common/debugLogger';
|
||||
import { debugLogger } from '../../common/debugLogger';
|
||||
import type { ProtocolLogger } from '../types';
|
||||
import { helper } from '../helper';
|
||||
import { ProtocolError } from '../protocolError';
|
||||
import { kTargetClosedErrorMessage } from '../../common/errors';
|
||||
|
||||
export const ConnectionEvents = {
|
||||
Disconnected: Symbol('ConnectionEvents.Disconnected')
|
||||
@ -102,7 +100,7 @@ type SessionEventListener = (method: string, params?: Object) => void;
|
||||
export class CRSession extends EventEmitter {
|
||||
private readonly _connection: CRConnection;
|
||||
private _eventListener?: SessionEventListener;
|
||||
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError, method: string}>();
|
||||
private readonly _callbacks = new Map<number, { resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError }>();
|
||||
private readonly _sessionId: string;
|
||||
private readonly _parentSession: CRSession | null;
|
||||
private _crashed: boolean = false;
|
||||
@ -138,25 +136,15 @@ export class CRSession extends EventEmitter {
|
||||
return session;
|
||||
}
|
||||
|
||||
private _closedErrorMessage() {
|
||||
if (this._crashed)
|
||||
return 'Target crashed';
|
||||
if (this._connection._browserDisconnectedLogs)
|
||||
return kTargetClosedErrorMessage + '\nBrowser logs: ' + this._connection._browserDisconnectedLogs;
|
||||
if (this._closed || this._connection._closed)
|
||||
return kTargetClosedErrorMessage;
|
||||
}
|
||||
|
||||
async send<T extends keyof Protocol.CommandParameters>(
|
||||
method: T,
|
||||
params?: Protocol.CommandParameters[T]
|
||||
): Promise<Protocol.CommandReturnValues[T]> {
|
||||
const closedErrorMessage = this._closedErrorMessage();
|
||||
if (closedErrorMessage)
|
||||
throw new ProtocolError(true, closedErrorMessage);
|
||||
if (this._crashed || this._closed || this._connection._closed || this._connection._browserDisconnectedLogs)
|
||||
throw new ProtocolError(this._crashed ? 'crashed' : 'closed', undefined, this._connection._browserDisconnectedLogs);
|
||||
const id = this._connection._rawSend(this._sessionId, method, params);
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, { resolve, reject, error: new ProtocolError(false), method });
|
||||
this._callbacks.set(id, { resolve, reject, error: new ProtocolError('error', method) });
|
||||
});
|
||||
}
|
||||
|
||||
@ -168,10 +156,12 @@ export class CRSession extends EventEmitter {
|
||||
if (object.id && this._callbacks.has(object.id)) {
|
||||
const callback = this._callbacks.get(object.id)!;
|
||||
this._callbacks.delete(object.id);
|
||||
if (object.error)
|
||||
callback.reject(createProtocolError(callback.error, callback.method, object.error));
|
||||
else
|
||||
if (object.error) {
|
||||
callback.error.setMessage(object.error.message);
|
||||
callback.reject(callback.error);
|
||||
} else {
|
||||
callback.resolve(object.result);
|
||||
}
|
||||
} else if (object.id && object.error?.code === -32001) {
|
||||
// Message to a closed session, just ignore it.
|
||||
} else {
|
||||
@ -199,10 +189,10 @@ export class CRSession extends EventEmitter {
|
||||
dispose() {
|
||||
this._closed = true;
|
||||
this._connection._sessions.delete(this._sessionId);
|
||||
const errorMessage = this._closedErrorMessage()!;
|
||||
for (const callback of this._callbacks.values()) {
|
||||
callback.error.sessionClosed = true;
|
||||
callback.reject(rewriteErrorMessage(callback.error, errorMessage));
|
||||
callback.error.type = this._crashed ? 'crashed' : 'closed';
|
||||
callback.error.logs = this._connection._browserDisconnectedLogs;
|
||||
callback.reject(callback.error);
|
||||
}
|
||||
this._callbacks.clear();
|
||||
}
|
||||
@ -247,10 +237,3 @@ export class CDPSession extends EventEmitter {
|
||||
this.emit(CDPSession.Events.Closed);
|
||||
}
|
||||
}
|
||||
|
||||
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}`;
|
||||
return rewriteErrorMessage(error, message);
|
||||
}
|
||||
|
||||
@ -18,14 +18,15 @@ import { EventEmitter } from 'events';
|
||||
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 } from '../../utils';
|
||||
import { TargetClosedError } from '../../common/errors';
|
||||
import { assert, isUnderTest, monotonicTime, rewriteErrorMessage } from '../../utils';
|
||||
import { TargetClosedError, kTargetClosedErrorMessage, kTargetCrashedErrorMessage } from '../../common/errors';
|
||||
import type { CallMetadata } from '../instrumentation';
|
||||
import { SdkObject } from '../instrumentation';
|
||||
import type { PlaywrightDispatcher } from './playwrightDispatcher';
|
||||
import { eventsHelper } from '../..//utils/eventsHelper';
|
||||
import type { RegisteredListener } from '../..//utils/eventsHelper';
|
||||
import type * as trace from '@trace/trace';
|
||||
import { isProtocolError } from '../protocolError';
|
||||
|
||||
export const dispatcherSymbol = Symbol('dispatcher');
|
||||
const metadataValidator = createMetadataValidator();
|
||||
@ -329,6 +330,12 @@ 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 (isProtocolError(e)) {
|
||||
if (e.type === 'closed')
|
||||
rewriteErrorMessage(e, kTargetClosedErrorMessage + e.browserLogMessage());
|
||||
if (e.type === 'crashed')
|
||||
rewriteErrorMessage(e, kTargetCrashedErrorMessage + e.browserLogMessage());
|
||||
}
|
||||
callMetadata.error = serializeError(e);
|
||||
} finally {
|
||||
callMetadata.endTime = monotonicTime();
|
||||
|
||||
@ -18,13 +18,11 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||
import type { Protocol } from './protocol';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import type { RecentLogsCollector } from '../../common/debugLogger';
|
||||
import { debugLogger } from '../../common/debugLogger';
|
||||
import type { ProtocolLogger } from '../types';
|
||||
import { helper } from '../helper';
|
||||
import { ProtocolError } from '../protocolError';
|
||||
import { kTargetClosedErrorMessage } from '../../common/errors';
|
||||
|
||||
export const ConnectionEvents = {
|
||||
Disconnected: Symbol('Disconnected'),
|
||||
@ -103,7 +101,7 @@ export class FFConnection extends EventEmitter {
|
||||
export class FFSession extends EventEmitter {
|
||||
_connection: FFConnection;
|
||||
_disposed = false;
|
||||
private _callbacks: Map<number, {resolve: Function, reject: Function, error: ProtocolError, method: string}>;
|
||||
private _callbacks: Map<number, { resolve: Function, reject: Function, error: ProtocolError }>;
|
||||
private _sessionId: string;
|
||||
private _rawSend: (message: any) => void;
|
||||
private _crashed: boolean = false;
|
||||
@ -132,26 +130,16 @@ export class FFSession extends EventEmitter {
|
||||
this._crashed = true;
|
||||
}
|
||||
|
||||
private _closedErrorMessage() {
|
||||
if (this._crashed)
|
||||
return 'Target crashed';
|
||||
if (this._connection._browserDisconnectedLogs)
|
||||
return kTargetClosedErrorMessage + '\nBrowser logs: ' + this._connection._browserDisconnectedLogs;
|
||||
if (this._disposed || this._connection._closed)
|
||||
return kTargetClosedErrorMessage;
|
||||
}
|
||||
|
||||
async send<T extends keyof Protocol.CommandParameters>(
|
||||
method: T,
|
||||
params?: Protocol.CommandParameters[T]
|
||||
): Promise<Protocol.CommandReturnValues[T]> {
|
||||
const closedErrorMessage = this._closedErrorMessage();
|
||||
if (closedErrorMessage)
|
||||
throw new ProtocolError(true, closedErrorMessage);
|
||||
if (this._crashed || this._disposed || this._connection._closed || this._connection._browserDisconnectedLogs)
|
||||
throw new ProtocolError(this._crashed ? 'crashed' : 'closed', undefined, this._connection._browserDisconnectedLogs);
|
||||
const id = this._connection.nextMessageId();
|
||||
this._rawSend({ method, params, id });
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, { resolve, reject, error: new ProtocolError(false), method });
|
||||
this._callbacks.set(id, { resolve, reject, error: new ProtocolError('error', method) });
|
||||
});
|
||||
}
|
||||
|
||||
@ -165,10 +153,12 @@ export class FFSession extends EventEmitter {
|
||||
// Callbacks could be all rejected if someone has called `.dispose()`.
|
||||
if (callback) {
|
||||
this._callbacks.delete(object.id);
|
||||
if (object.error)
|
||||
callback.reject(createProtocolError(callback.error, callback.method, object.error));
|
||||
else
|
||||
if (object.error) {
|
||||
callback.error.setMessage(object.error.message);
|
||||
callback.reject(callback.error);
|
||||
} else {
|
||||
callback.resolve(object.result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Promise.resolve().then(() => this.emit(object.method!, object.params));
|
||||
@ -178,18 +168,11 @@ export class FFSession extends EventEmitter {
|
||||
dispose() {
|
||||
this._disposed = true;
|
||||
this._connection._sessions.delete(this._sessionId);
|
||||
const errorMessage = this._closedErrorMessage()!;
|
||||
for (const callback of this._callbacks.values()) {
|
||||
callback.error.sessionClosed = true;
|
||||
callback.reject(rewriteErrorMessage(callback.error, errorMessage));
|
||||
callback.error.type = this._crashed ? 'crashed' : 'closed';
|
||||
callback.error.logs = this._connection._browserDisconnectedLogs;
|
||||
callback.reject(callback.error);
|
||||
}
|
||||
this._callbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
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}`;
|
||||
return rewriteErrorMessage(error, message);
|
||||
}
|
||||
|
||||
@ -24,9 +24,9 @@ import type { Env } from '../../utils/processLauncher';
|
||||
import type { ConnectionTransport } from '../transport';
|
||||
import type { BrowserOptions } from '../browser';
|
||||
import type * as types from '../types';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import { wrapInASCIIBox } from '../../utils';
|
||||
import type { SdkObject } from '../instrumentation';
|
||||
import type { ProtocolError } from '../protocolError';
|
||||
|
||||
export class Firefox extends BrowserType {
|
||||
constructor(parent: SdkObject) {
|
||||
@ -37,9 +37,11 @@ export class Firefox extends BrowserType {
|
||||
return FFBrowser.connect(this.attribution.playwright, transport, options);
|
||||
}
|
||||
|
||||
_rewriteStartupError(error: Error): Error {
|
||||
if (error.message.includes('no DISPLAY environment variable specified'))
|
||||
return rewriteErrorMessage(error, '\n' + wrapInASCIIBox(kNoXServerRunningError, 1));
|
||||
_doRewriteStartupLog(error: ProtocolError): ProtocolError {
|
||||
if (!error.logs)
|
||||
return error;
|
||||
if (error.logs.includes('no DISPLAY environment variable specified'))
|
||||
error.logs = '\n' + wrapInASCIIBox(kNoXServerRunningError, 1);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@ -14,15 +14,33 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export class ProtocolError extends Error {
|
||||
sessionClosed: boolean;
|
||||
import { rewriteErrorMessage } from '../utils/stackTrace';
|
||||
|
||||
constructor(sessionClosed: boolean, message?: string) {
|
||||
super(message);
|
||||
this.sessionClosed = sessionClosed || false;
|
||||
export class ProtocolError extends Error {
|
||||
type: 'error' | 'closed' | 'crashed';
|
||||
method: string | undefined;
|
||||
logs: string | undefined;
|
||||
|
||||
constructor(type: 'error' | 'closed' | 'crashed', method?: string, logs?: string) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.method = method;
|
||||
this.logs = logs;
|
||||
}
|
||||
|
||||
setMessage(message: string) {
|
||||
rewriteErrorMessage(this, `Protocol error (${this.method}): ${message}`);
|
||||
}
|
||||
|
||||
browserLogMessage() {
|
||||
return this.logs ? '\nBrowser logs:\n' + this.logs : '';
|
||||
}
|
||||
}
|
||||
|
||||
export function isSessionClosedError(e: Error): boolean {
|
||||
return e instanceof ProtocolError && e.sessionClosed;
|
||||
export function isProtocolError(e: Error): e is ProtocolError {
|
||||
return e instanceof ProtocolError;
|
||||
}
|
||||
|
||||
export function isSessionClosedError(e: Error): e is ProtocolError {
|
||||
return e instanceof ProtocolError && (e.type === 'closed' || e.type === 'crashed');
|
||||
}
|
||||
|
||||
@ -23,9 +23,9 @@ import { BrowserType, kNoXServerRunningError } from '../browserType';
|
||||
import type { ConnectionTransport } from '../transport';
|
||||
import type { BrowserOptions } from '../browser';
|
||||
import type * as types from '../types';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import { wrapInASCIIBox } from '../../utils';
|
||||
import type { SdkObject } from '../instrumentation';
|
||||
import type { ProtocolError } from '../protocolError';
|
||||
|
||||
export class WebKit extends BrowserType {
|
||||
constructor(parent: SdkObject) {
|
||||
@ -40,9 +40,11 @@ export class WebKit extends BrowserType {
|
||||
return { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir, 'cookiejar.db') };
|
||||
}
|
||||
|
||||
_rewriteStartupError(error: Error): Error {
|
||||
if (error.message.includes('cannot open display'))
|
||||
return rewriteErrorMessage(error, '\n' + wrapInASCIIBox(kNoXServerRunningError, 1));
|
||||
_doRewriteStartupLog(error: ProtocolError): ProtocolError {
|
||||
if (!error.logs)
|
||||
return error;
|
||||
if (error.logs.includes('cannot open display'))
|
||||
error.logs = '\n' + wrapInASCIIBox(kNoXServerRunningError, 1);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
@ -165,7 +165,7 @@ export class WKBrowser extends Browser {
|
||||
context = this._defaultContext as WKBrowserContext;
|
||||
if (!context)
|
||||
return;
|
||||
const pageProxySession = new WKSession(this._connection, pageProxyId, kTargetClosedErrorMessage, (message: any) => {
|
||||
const pageProxySession = new WKSession(this._connection, pageProxyId, (message: any) => {
|
||||
this._connection.rawSend({ ...message, pageProxyId });
|
||||
});
|
||||
const opener = event.openerId ? this._wkPages.get(event.openerId) : undefined;
|
||||
|
||||
@ -19,12 +19,10 @@ import { EventEmitter } from 'events';
|
||||
import { assert } from '../../utils';
|
||||
import type { ConnectionTransport, ProtocolRequest, ProtocolResponse } from '../transport';
|
||||
import type { Protocol } from './protocol';
|
||||
import { rewriteErrorMessage } from '../../utils/stackTrace';
|
||||
import type { RecentLogsCollector } from '../../common/debugLogger';
|
||||
import { debugLogger } from '../../common/debugLogger';
|
||||
import type { ProtocolLogger } from '../types';
|
||||
import { helper } from '../helper';
|
||||
import { kTargetClosedErrorMessage } from '../../common/errors';
|
||||
import { ProtocolError } from '../protocolError';
|
||||
|
||||
// WKPlaywright uses this special id to issue Browser.close command which we
|
||||
@ -51,7 +49,7 @@ export class WKConnection {
|
||||
this._onDisconnect = onDisconnect;
|
||||
this._protocolLogger = protocolLogger;
|
||||
this._browserLogsCollector = browserLogsCollector;
|
||||
this.browserSession = new WKSession(this, '', kTargetClosedErrorMessage, (message: any) => {
|
||||
this.browserSession = new WKSession(this, '', (message: any) => {
|
||||
this.rawSend(message);
|
||||
});
|
||||
this._transport.onmessage = this._dispatchMessage.bind(this);
|
||||
@ -101,12 +99,11 @@ export class WKConnection {
|
||||
|
||||
export class WKSession extends EventEmitter {
|
||||
connection: WKConnection;
|
||||
errorText: string;
|
||||
readonly sessionId: string;
|
||||
|
||||
private _disposed = false;
|
||||
private readonly _rawSend: (message: any) => void;
|
||||
private readonly _callbacks = new Map<number, {resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError, method: string}>();
|
||||
private readonly _callbacks = new Map<number, { resolve: (o: any) => void, reject: (e: ProtocolError) => void, error: ProtocolError }>();
|
||||
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;
|
||||
@ -115,13 +112,12 @@ export class WKSession 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: WKConnection, sessionId: string, errorText: string, rawSend: (message: any) => void) {
|
||||
constructor(connection: WKConnection, sessionId: string, rawSend: (message: any) => void) {
|
||||
super();
|
||||
this.setMaxListeners(0);
|
||||
this.connection = connection;
|
||||
this.sessionId = sessionId;
|
||||
this._rawSend = rawSend;
|
||||
this.errorText = errorText;
|
||||
|
||||
this.on = super.on;
|
||||
this.off = super.removeListener;
|
||||
@ -134,15 +130,13 @@ export class WKSession extends EventEmitter {
|
||||
method: T,
|
||||
params?: Protocol.CommandParameters[T]
|
||||
): Promise<Protocol.CommandReturnValues[T]> {
|
||||
if (this._crashed)
|
||||
throw new ProtocolError(true, 'Target crashed');
|
||||
if (this._disposed)
|
||||
throw new ProtocolError(true, kTargetClosedErrorMessage);
|
||||
if (this._crashed || this._disposed || this.connection._browserDisconnectedLogs)
|
||||
throw new ProtocolError(this._crashed ? 'crashed' : 'closed', undefined, this.connection._browserDisconnectedLogs);
|
||||
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 ProtocolError(false), method });
|
||||
this._callbacks.set(id, { resolve, reject, error: new ProtocolError('error', method) });
|
||||
});
|
||||
}
|
||||
|
||||
@ -159,11 +153,10 @@ export class WKSession extends EventEmitter {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.connection._browserDisconnectedLogs)
|
||||
this.errorText = kTargetClosedErrorMessage + '\nBrowser logs: ' + this.connection._browserDisconnectedLogs;
|
||||
for (const callback of this._callbacks.values()) {
|
||||
callback.error.sessionClosed = true;
|
||||
callback.reject(rewriteErrorMessage(callback.error, this.errorText));
|
||||
callback.error.type = this._crashed ? 'crashed' : 'closed';
|
||||
callback.error.logs = this.connection._browserDisconnectedLogs;
|
||||
callback.reject(callback.error);
|
||||
}
|
||||
this._callbacks.clear();
|
||||
this._disposed = true;
|
||||
@ -173,10 +166,12 @@ export class WKSession extends EventEmitter {
|
||||
if (object.id && this._callbacks.has(object.id)) {
|
||||
const callback = this._callbacks.get(object.id)!;
|
||||
this._callbacks.delete(object.id);
|
||||
if (object.error)
|
||||
callback.reject(createProtocolError(callback.error, callback.method, object.error));
|
||||
else
|
||||
if (object.error) {
|
||||
callback.error.setMessage(object.error.message);
|
||||
callback.reject(callback.error);
|
||||
} else {
|
||||
callback.resolve(object.result);
|
||||
}
|
||||
} else if (object.id && !object.error) {
|
||||
// Response might come after session has been disposed and rejected all callbacks.
|
||||
assert(this.isDisposed());
|
||||
@ -185,10 +180,3 @@ export class WKSession extends EventEmitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)}`;
|
||||
return rewriteErrorMessage(error, message);
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ import { WKWorkers } from './wkWorkers';
|
||||
import { debugLogger } from '../../common/debugLogger';
|
||||
import { ManualPromise } from '../../utils/manualPromise';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { TargetClosedError, kTargetClosedErrorMessage } from '../../common/errors';
|
||||
import { TargetClosedError } from '../../common/errors';
|
||||
|
||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
|
||||
|
||||
@ -304,7 +304,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, kTargetClosedErrorMessage, (message: any) => {
|
||||
const session = new WKSession(this._pageProxySession.connection, targetInfo.targetId, (message: any) => {
|
||||
this._pageProxySession.send('Target.sendMessageToTarget', {
|
||||
message: JSON.stringify(message), targetId: targetInfo.targetId
|
||||
}).catch(e => {
|
||||
|
||||
@ -38,7 +38,7 @@ export class WKWorkers {
|
||||
this._sessionListeners = [
|
||||
eventsHelper.addEventListener(session, 'Worker.workerCreated', (event: Protocol.Worker.workerCreatedPayload) => {
|
||||
const worker = new Worker(this._page, event.url);
|
||||
const workerSession = new WKSession(session.connection, event.workerId, 'Most likely the worker has been closed.', (message: any) => {
|
||||
const workerSession = new WKSession(session.connection, event.workerId, (message: any) => {
|
||||
session.send('Worker.sendMessageToWorker', {
|
||||
workerId: event.workerId,
|
||||
message: JSON.stringify(message)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user