feat(connect): print debug log when remote connection failed (#23069)
This commit is contained in:
parent
3cd21cb6d4
commit
fec5059fee
|
|
@ -28,7 +28,6 @@ import type * as api from '../../types/types';
|
||||||
import { kBrowserClosedError } from '../common/errors';
|
import { kBrowserClosedError } from '../common/errors';
|
||||||
import { raceAgainstTimeout } from '../utils/timeoutRunner';
|
import { raceAgainstTimeout } from '../utils/timeoutRunner';
|
||||||
import type { Playwright } from './playwright';
|
import type { Playwright } from './playwright';
|
||||||
import { debugLogger } from '../common/debugLogger';
|
|
||||||
|
|
||||||
export interface BrowserServerLauncher {
|
export interface BrowserServerLauncher {
|
||||||
launchServer(options?: LaunchServerOptions): Promise<api.BrowserServer>;
|
launchServer(options?: LaunchServerOptions): Promise<api.BrowserServer>;
|
||||||
|
|
@ -200,10 +199,6 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||||
this._didLaunchBrowser(browser, {}, logger);
|
this._didLaunchBrowser(browser, {}, logger);
|
||||||
browser._shouldCloseConnectionOnClose = true;
|
browser._shouldCloseConnectionOnClose = true;
|
||||||
browser._connectHeaders = connectHeaders;
|
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);
|
browser.on(Events.Browser.Disconnected, closePipe);
|
||||||
return browser;
|
return browser;
|
||||||
}, deadline ? deadline - monotonicTime() : 0);
|
}, deadline ? deadline - monotonicTime() : 0);
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ export class LocalUtilsDispatcher extends Dispatcher<{ guid: string }, channels.
|
||||||
};
|
};
|
||||||
const wsEndpoint = await urlToWSEndpoint(progress, params.wsEndpoint);
|
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 socksInterceptor = new SocksInterceptor(transport, params.exposeNetwork, params.socksProxyRedirectPortForTest);
|
||||||
const pipe = new JsonPipeDispatcher(this);
|
const pipe = new JsonPipeDispatcher(this);
|
||||||
transport.onmessage = json => {
|
transport.onmessage = json => {
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@ export class WebSocketTransport implements ConnectionTransport {
|
||||||
readonly wsEndpoint: string;
|
readonly wsEndpoint: string;
|
||||||
readonly headers: HeadersArray = [];
|
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);
|
const logUrl = stripQueryParams(url);
|
||||||
progress?.log(`<ws connecting> ${logUrl}`);
|
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;
|
let success = false;
|
||||||
progress?.cleanupWhenAborted(async () => {
|
progress?.cleanupWhenAborted(async () => {
|
||||||
if (!success)
|
if (!success)
|
||||||
|
|
@ -78,6 +78,10 @@ export class WebSocketTransport implements ConnectionTransport {
|
||||||
transport._ws.close();
|
transport._ws.close();
|
||||||
});
|
});
|
||||||
transport._ws.on('unexpected-response', (request: ClientRequest, response: IncomingMessage) => {
|
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 chunks: Buffer[] = [];
|
||||||
const errorPrefix = `${logUrl} ${response.statusCode} ${response.statusMessage}`;
|
const errorPrefix = `${logUrl} ${response.statusCode} ${response.statusMessage}`;
|
||||||
response.on('data', chunk => chunks.push(chunk));
|
response.on('data', chunk => chunks.push(chunk));
|
||||||
|
|
@ -93,7 +97,7 @@ export class WebSocketTransport implements ConnectionTransport {
|
||||||
return transport;
|
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.wsEndpoint = url;
|
||||||
this._logUrl = logUrl;
|
this._logUrl = logUrl;
|
||||||
this._ws = new ws(url, [], {
|
this._ws = new ws(url, [], {
|
||||||
|
|
@ -105,9 +109,12 @@ export class WebSocketTransport implements ConnectionTransport {
|
||||||
followRedirects,
|
followRedirects,
|
||||||
agent: (/^(https|wss):\/\//.test(url)) ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent
|
agent: (/^(https|wss):\/\//.test(url)) ? httpsHappyEyeballsAgent : httpHappyEyeballsAgent
|
||||||
});
|
});
|
||||||
this._ws.on('upgrade', request => {
|
this._ws.on('upgrade', response => {
|
||||||
for (let i = 0; i < request.rawHeaders.length; i += 2)
|
for (let i = 0; i < response.rawHeaders.length; i += 2) {
|
||||||
this.headers.push({ name: request.rawHeaders[i], value: request.rawHeaders[i + 1] });
|
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;
|
this._progress = progress;
|
||||||
// The 'ws' module in node sometimes sends us multiple messages in a single task.
|
// The 'ws' module in node sometimes sends us multiple messages in a single task.
|
||||||
|
|
|
||||||
|
|
@ -117,3 +117,44 @@ test('should respect connectOptions.timeout', async ({ runInlineTest }) => {
|
||||||
expect(result.passed).toBe(0);
|
expect(result.passed).toBe(0);
|
||||||
expect(result.output).toContain('browserType.launch: Timeout 1ms exceeded.');
|
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([]);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue