mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-06 03:16:17 +03:00
fix(connect): include websocket close reason in the error message (#30203)
When websocket disconnects during `browserType.connect()` call, the error looks like this now: ``` browserType.connect: Custom error message received over WebSocket ``` Previously, it was a generic error: ``` browserType.connect: Target page, context or browser has been closed ```
This commit is contained in:
parent
6e799fdfa8
commit
6d56b453ff
@ -76,7 +76,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
||||
connection.on('close', closePipe);
|
||||
|
||||
let device: AndroidDevice;
|
||||
let closeError: Error | undefined;
|
||||
let closeError: string | undefined;
|
||||
const onPipeClosed = () => {
|
||||
device?._didClose();
|
||||
connection.close(closeError);
|
||||
@ -88,7 +88,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
||||
try {
|
||||
connection!.dispatch(message);
|
||||
} catch (e) {
|
||||
closeError = e;
|
||||
closeError = String(e);
|
||||
closePipe();
|
||||
}
|
||||
});
|
||||
|
@ -143,8 +143,8 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||
connection.on('close', closePipe);
|
||||
|
||||
let browser: Browser;
|
||||
let closeError: Error | undefined;
|
||||
const onPipeClosed = () => {
|
||||
let closeError: string | undefined;
|
||||
const onPipeClosed = (reason?: string) => {
|
||||
// Emulate all pages, contexts and the browser closing upon disconnect.
|
||||
for (const context of browser?.contexts() || []) {
|
||||
for (const page of context.pages())
|
||||
@ -152,16 +152,16 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||
context._onClose();
|
||||
}
|
||||
browser?._didClose();
|
||||
connection.close(closeError);
|
||||
connection.close(reason || closeError);
|
||||
};
|
||||
pipe.on('closed', onPipeClosed);
|
||||
connection.onmessage = message => pipe.send({ message }).catch(onPipeClosed);
|
||||
pipe.on('closed', params => onPipeClosed(params.reason));
|
||||
connection.onmessage = message => pipe.send({ message }).catch(() => onPipeClosed());
|
||||
|
||||
pipe.on('message', ({ message }) => {
|
||||
try {
|
||||
connection!.dispatch(message);
|
||||
} catch (e) {
|
||||
closeError = e;
|
||||
closeError = String(e);
|
||||
closePipe();
|
||||
}
|
||||
});
|
||||
|
@ -191,8 +191,8 @@ export class Connection extends EventEmitter {
|
||||
(object._channel as any).emit(method, validator(params, '', { tChannelImpl: this._tChannelImplFromWire.bind(this), binary: this._rawBuffers ? 'buffer' : 'fromBase64' }));
|
||||
}
|
||||
|
||||
close(cause?: Error) {
|
||||
this._closedError = new TargetClosedError(cause?.toString());
|
||||
close(cause?: string) {
|
||||
this._closedError = new TargetClosedError(cause);
|
||||
for (const callback of this._callbacks.values())
|
||||
callback.reject(this._closedError);
|
||||
this._callbacks.clear();
|
||||
|
@ -2586,7 +2586,7 @@ scheme.JsonPipeMessageEvent = tObject({
|
||||
message: tAny,
|
||||
});
|
||||
scheme.JsonPipeClosedEvent = tObject({
|
||||
error: tOptional(tType('SerializedError')),
|
||||
reason: tOptional(tString),
|
||||
});
|
||||
scheme.JsonPipeSendParams = tObject({
|
||||
message: tAny,
|
||||
|
@ -75,11 +75,11 @@ export class CRConnection extends EventEmitter {
|
||||
session._onMessage(message);
|
||||
}
|
||||
|
||||
_onClose() {
|
||||
_onClose(reason?: string) {
|
||||
this._closed = true;
|
||||
this._transport.onmessage = undefined;
|
||||
this._transport.onclose = undefined;
|
||||
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
|
||||
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs(), reason);
|
||||
this.rootSession.dispose();
|
||||
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
import type * as channels from '@protocol/channels';
|
||||
import { Dispatcher } from './dispatcher';
|
||||
import { createGuid } from '../../utils';
|
||||
import { serializeError } from '../errors';
|
||||
import type { LocalUtilsDispatcher } from './localUtilsDispatcher';
|
||||
|
||||
export class JsonPipeDispatcher extends Dispatcher<{ guid: string }, channels.JsonPipeChannel, LocalUtilsDispatcher> implements channels.JsonPipeChannel {
|
||||
@ -43,10 +42,9 @@ export class JsonPipeDispatcher extends Dispatcher<{ guid: string }, channels.Js
|
||||
this._dispatchEvent('message', { message });
|
||||
}
|
||||
|
||||
wasClosed(error?: Error): void {
|
||||
wasClosed(reason?: string): void {
|
||||
if (!this._disposed) {
|
||||
const params = error ? { error: serializeError(error) } : {};
|
||||
this._dispatchEvent('closed', params);
|
||||
this._dispatchEvent('closed', { reason });
|
||||
this._dispose();
|
||||
}
|
||||
}
|
||||
|
@ -230,9 +230,9 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||
pipe.on('message', message => {
|
||||
transport.send(message);
|
||||
});
|
||||
transport.onclose = () => {
|
||||
transport.onclose = (reason?: string) => {
|
||||
socksInterceptor?.cleanup();
|
||||
pipe.wasClosed();
|
||||
pipe.wasClosed(reason);
|
||||
};
|
||||
pipe.on('close', () => transport.close());
|
||||
return { pipe, headers: transport.headers };
|
||||
|
@ -77,11 +77,11 @@ export class FFConnection extends EventEmitter {
|
||||
session.dispatchMessage(message);
|
||||
}
|
||||
|
||||
_onClose() {
|
||||
_onClose(reason?: string) {
|
||||
this._closed = true;
|
||||
this._transport.onmessage = undefined;
|
||||
this._transport.onclose = undefined;
|
||||
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
|
||||
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs(), reason);
|
||||
this.rootSession.dispose();
|
||||
Promise.resolve().then(() => this.emit(ConnectionEvents.Disconnected));
|
||||
}
|
||||
|
@ -95,10 +95,10 @@ class Helper {
|
||||
};
|
||||
}
|
||||
|
||||
static formatBrowserLogs(logs: string[]) {
|
||||
if (!logs.length)
|
||||
static formatBrowserLogs(logs: string[], disconnectReason?: string) {
|
||||
if (!disconnectReason && !logs.length)
|
||||
return '';
|
||||
return '\n' + logs.join('\n');
|
||||
return '\n' + (disconnectReason ? disconnectReason + '\n' : '') + logs.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ export class PipeTransport implements ConnectionTransport {
|
||||
private _pendingBuffers: Buffer[] = [];
|
||||
private _waitForNextTask = makeWaitForNextTask();
|
||||
private _closed = false;
|
||||
private _onclose?: () => void;
|
||||
private _onclose?: (reason?: string) => void;
|
||||
|
||||
onmessage?: (message: ProtocolResponse) => void;
|
||||
|
||||
@ -47,7 +47,7 @@ export class PipeTransport implements ConnectionTransport {
|
||||
return this._onclose;
|
||||
}
|
||||
|
||||
set onclose(onclose: undefined | (() => void)) {
|
||||
set onclose(onclose: undefined | ((reason?: string) => void)) {
|
||||
this._onclose = onclose;
|
||||
if (onclose && !this._pipeRead.readable)
|
||||
onclose();
|
||||
|
@ -55,7 +55,7 @@ export interface ConnectionTransport {
|
||||
send(s: ProtocolRequest): void;
|
||||
close(): void; // Note: calling close is expected to issue onclose at some point.
|
||||
onmessage?: (message: ProtocolResponse) => void,
|
||||
onclose?: () => void,
|
||||
onclose?: (reason?: string) => void,
|
||||
}
|
||||
|
||||
export class WebSocketTransport implements ConnectionTransport {
|
||||
@ -64,7 +64,7 @@ export class WebSocketTransport implements ConnectionTransport {
|
||||
private _logUrl: string;
|
||||
|
||||
onmessage?: (message: ProtocolResponse) => void;
|
||||
onclose?: () => void;
|
||||
onclose?: (reason?: string) => void;
|
||||
readonly wsEndpoint: string;
|
||||
readonly headers: HeadersArray = [];
|
||||
|
||||
@ -175,7 +175,7 @@ export class WebSocketTransport implements ConnectionTransport {
|
||||
this._ws.addEventListener('close', event => {
|
||||
this._progress?.log(`<ws disconnected> ${logUrl} code=${event.code} reason=${event.reason}`);
|
||||
if (this.onclose)
|
||||
this.onclose.call(null);
|
||||
this.onclose.call(null, event.reason);
|
||||
});
|
||||
// Prevent Error: read ECONNRESET.
|
||||
this._ws.addEventListener('error', error => this._progress?.log(`<ws error> ${logUrl} ${error.type} ${error.message}`));
|
||||
|
@ -78,11 +78,11 @@ export class WKConnection {
|
||||
this.browserSession.dispatchMessage(message);
|
||||
}
|
||||
|
||||
_onClose() {
|
||||
_onClose(reason?: string) {
|
||||
this._closed = true;
|
||||
this._transport.onmessage = undefined;
|
||||
this._transport.onclose = undefined;
|
||||
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs());
|
||||
this._browserDisconnectedLogs = helper.formatBrowserLogs(this._browserLogsCollector.recentLogs(), reason);
|
||||
this.browserSession.dispose();
|
||||
this._onDisconnect();
|
||||
}
|
||||
|
@ -4674,7 +4674,7 @@ export type JsonPipeMessageEvent = {
|
||||
message: any,
|
||||
};
|
||||
export type JsonPipeClosedEvent = {
|
||||
error?: SerializedError,
|
||||
reason?: string,
|
||||
};
|
||||
export type JsonPipeSendParams = {
|
||||
message: any,
|
||||
|
@ -3548,4 +3548,4 @@ JsonPipe:
|
||||
|
||||
closed:
|
||||
parameters:
|
||||
error: SerializedError?
|
||||
reason: string?
|
||||
|
@ -129,6 +129,16 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
||||
expect(error.message).not.toContain('secret=MYSECRET');
|
||||
});
|
||||
|
||||
test('should print custom ws close error', async ({ connect, server }) => {
|
||||
server.onceWebSocketConnection((ws, request) => {
|
||||
ws.on('message', message => {
|
||||
ws.close(4123, 'Oh my!');
|
||||
});
|
||||
});
|
||||
const error = await connect(`ws://localhost:${server.PORT}/ws`).catch(e => e);
|
||||
expect(error.message).toContain('browserType.connect: Oh my!');
|
||||
});
|
||||
|
||||
test('should be able to reconnect to a browser', async ({ connect, startRemoteServer, server }) => {
|
||||
const remoteServer = await startRemoteServer(kind);
|
||||
{
|
||||
@ -325,7 +335,7 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
||||
]);
|
||||
expect(browser.isConnected()).toBe(false);
|
||||
const error = await page.evaluate('1 + 1').catch(e => e) as Error;
|
||||
expect(error.message).toContain('has been closed');
|
||||
expect(error.message).toContain('closed');
|
||||
});
|
||||
|
||||
test('should throw when calling waitForNavigation after disconnect', async ({ connect, startRemoteServer }) => {
|
||||
|
@ -527,3 +527,13 @@ test('setInputFiles should preserve lastModified timestamp', async ({ browserTyp
|
||||
await browserServer.close();
|
||||
}
|
||||
});
|
||||
|
||||
test('should print custom ws close error', async ({ browserType, server }) => {
|
||||
server.onceWebSocketConnection((ws, request) => {
|
||||
ws.on('message', message => {
|
||||
ws.close(4123, 'Oh my!');
|
||||
});
|
||||
});
|
||||
const error = await browserType.connectOverCDP(`ws://localhost:${server.PORT}/ws`).catch(e => e);
|
||||
expect(error.message).toContain(`Browser logs:\n\nOh my!\n`);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user