feat(connect): print debug log when remote connection failed (#23069)

This commit is contained in:
Dmitry Gozman 2023-05-16 16:46:02 -07:00 committed by GitHub
parent 3cd21cb6d4
commit fec5059fee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 55 additions and 12 deletions

View file

@ -28,7 +28,6 @@ import type * as api from '../../types/types';
import { kBrowserClosedError } from '../common/errors';
import { raceAgainstTimeout } from '../utils/timeoutRunner';
import type { Playwright } from './playwright';
import { debugLogger } from '../common/debugLogger';
export interface BrowserServerLauncher {
launchServer(options?: LaunchServerOptions): Promise<api.BrowserServer>;
@ -200,10 +199,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
this._didLaunchBrowser(browser, {}, logger);
browser._shouldCloseConnectionOnClose = true;
browser._connectHeaders = connectHeaders;
for (const header of connectHeaders) {
if (header.name === 'x-playwright-debug-log')
debugLogger.log('browser', header.value);
}
browser.on(Events.Browser.Disconnected, closePipe);
return browser;
}, deadline ? deadline - monotonicTime() : 0);

View file

@ -202,7 +202,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
};
const wsEndpoint = await urlToWSEndpoint(progress, params.wsEndpoint);
const transport = await WebSocketTransport.connect(progress, wsEndpoint, wsHeaders, true);
const transport = await WebSocketTransport.connect(progress, wsEndpoint, wsHeaders, true, 'x-playwright-debug-log');
const socksInterceptor = new SocksInterceptor(transport, params.exposeNetwork, params.socksProxyRedirectPortForTest);
const pipe = new JsonPipeDispatcher(this);
transport.onmessage = json => {

View file

@ -58,10 +58,10 @@ export class WebSocketTransport implements ConnectionTransport {
readonly wsEndpoint: string;
readonly headers: HeadersArray = [];
static async connect(progress: (Progress|undefined), url: string, headers?: { [key: string]: string; }, followRedirects?: boolean): Promise<WebSocketTransport> {
static async connect(progress: (Progress|undefined), url: string, headers?: { [key: string]: string; }, followRedirects?: boolean, debugLogHeader?: string): Promise<WebSocketTransport> {
const logUrl = stripQueryParams(url);
progress?.log(`<ws connecting> ${logUrl}`);
const transport = new WebSocketTransport(progress, url, logUrl, headers, followRedirects);
const transport = new WebSocketTransport(progress, url, logUrl, headers, followRedirects, debugLogHeader);
let success = false;
progress?.cleanupWhenAborted(async () => {
if (!success)
@ -78,6 +78,10 @@ export class WebSocketTransport implements ConnectionTransport {
transport._ws.close();
});
transport._ws.on('unexpected-response', (request: ClientRequest, response: IncomingMessage) => {
for (let i = 0; i < response.rawHeaders.length; i += 2) {
if (debugLogHeader && response.rawHeaders[i] === debugLogHeader)
progress?.log(response.rawHeaders[i + 1]);
}
const chunks: Buffer[] = [];
const errorPrefix = `${logUrl} ${response.statusCode} ${response.statusMessage}`;
response.on('data', chunk => chunks.push(chunk));
@ -93,7 +97,7 @@ export class WebSocketTransport implements ConnectionTransport {
return transport;
}
constructor(progress: Progress|undefined, url: string, logUrl: string, headers?: { [key: string]: string; }, followRedirects?: boolean) {
constructor(progress: Progress|undefined, url: string, logUrl: string, headers?: { [key: string]: string; }, followRedirects?: boolean, debugLogHeader?: string) {
this.wsEndpoint = url;
this._logUrl = logUrl;
this._ws = new ws(url, [], {
@ -105,9 +109,12 @@ export class WebSocketTransport implements ConnectionTransport {
followRedirects,
agent: (/^(https|wss):\/\//.test(url)) ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent
});
this._ws.on('upgrade', request => {
for (let i = 0; i < request.rawHeaders.length; i += 2)
this.headers.push({ name: request.rawHeaders[i], value: request.rawHeaders[i + 1] });
this._ws.on('upgrade', response => {
for (let i = 0; i < response.rawHeaders.length; i += 2) {
this.headers.push({ name: response.rawHeaders[i], value: response.rawHeaders[i + 1] });
if (debugLogHeader && response.rawHeaders[i] === debugLogHeader)
progress?.log(response.rawHeaders[i + 1]);
}
});
this._progress = progress;
// The 'ws' module in node sometimes sends us multiple messages in a single task.

View file

@ -117,3 +117,44 @@ test('should respect connectOptions.timeout', async ({ runInlineTest }) => {
expect(result.passed).toBe(0);
expect(result.output).toContain('browserType.launch: Timeout 1ms exceeded.');
});
test('should print debug log when failed to connect', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.js': `
module.exports = {
globalSetup: './global-setup',
use: {
connectOptions: {
wsEndpoint: process.env.CONNECT_WS_ENDPOINT,
},
},
};
`,
'global-setup.ts': `
import { chromium } from '@playwright/test';
import ws from 'ws';
import http from 'http';
module.exports = async () => {
const server = http.createServer(() => {});
server.on('upgrade', async (request, socket, head) => {
socket.write('HTTP/1.1 401 Unauthorized\\r\\nx-playwright-debug-log: b-debug-log-string\\r\\n\\r\\nUnauthorized body');
socket.destroy();
});
server.listen(0);
await new Promise(f => server.once('listening', f));
process.env.CONNECT_WS_ENDPOINT = 'ws://localhost:' + server.address().port;
return () => new Promise(f => server.close(f));
};
`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('fail', async ({ page }) => {
await page.setContent('<div>FAIL</div>');
});
`,
});
expect(result.exitCode).toBe(1);
expect(result.failed).toBe(1);
expect(result.output).toContain('b-debug-log-string');
expect(result.results[0].attachments).toEqual([]);
});