test: make debugp collect IO (take 2) (#1493)

This commit is contained in:
Pavel Feldman 2020-03-23 15:08:02 -07:00 committed by GitHub
parent afbc2f272a
commit c0c9b7f137
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 52 additions and 38 deletions

View file

@ -44,6 +44,7 @@ export class CRConnection extends platform.EventEmitter {
this.rootSession = new CRSession(this, '', 'browser', ''); this.rootSession = new CRSession(this, '', 'browser', '');
this._sessions.set('', this.rootSession); this._sessions.set('', this.rootSession);
this._debugProtocol = platform.debug('pw:protocol'); this._debugProtocol = platform.debug('pw:protocol');
(this._debugProtocol as any).color = '34';
} }
static fromSession(session: CRSession): CRConnection { static fromSession(session: CRSession): CRConnection {

View file

@ -58,6 +58,7 @@ export class FFConnection extends platform.EventEmitter {
this.off = super.removeListener; this.off = super.removeListener;
this.removeListener = super.removeListener; this.removeListener = super.removeListener;
this.once = super.once; this.once = super.once;
(this._debugProtocol as any).color = '34';
} }
async send<T extends keyof Protocol.CommandParameters>( async send<T extends keyof Protocol.CommandParameters>(

View file

@ -23,7 +23,6 @@ import * as readline from 'readline';
import { TimeoutError } from '../errors'; import { TimeoutError } from '../errors';
import * as platform from '../platform'; import * as platform from '../platform';
const debugLauncher = platform.debug('pw:launcher');
const removeFolderAsync = platform.promisify(removeFolder); const removeFolderAsync = platform.promisify(removeFolder);
export type LaunchProcessOptions = { export type LaunchProcessOptions = {
@ -49,6 +48,12 @@ let lastLaunchedId = 0;
export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> { export async function launchProcess(options: LaunchProcessOptions): Promise<LaunchResult> {
const id = ++lastLaunchedId; const id = ++lastLaunchedId;
const debugBrowser = platform.debug(`pw:browser:proc:[${id}]`);
const debugBrowserOut = platform.debug(`pw:browser:out:[${id}]`);
const debugBrowserErr = platform.debug(`pw:browser:err:[${id}]`);
(debugBrowser as any).color = '33';
(debugBrowserOut as any).color = '178';
(debugBrowserErr as any).color = '160';
const stdio: ('ignore' | 'pipe')[] = options.pipe ? ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe']; const stdio: ('ignore' | 'pipe')[] = options.pipe ? ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'];
const spawnedProcess = childProcess.spawn( const spawnedProcess = childProcess.spawn(
options.executablePath, options.executablePath,
@ -62,7 +67,7 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
stdio stdio
} }
); );
debugLauncher(`[${id}] <launching> ${options.executablePath} ${options.args.join(' ')}`); debugBrowser(`<launching> ${options.executablePath} ${options.args.join(' ')}`);
if (!spawnedProcess.pid) { if (!spawnedProcess.pid) {
let reject: (e: Error) => void; let reject: (e: Error) => void;
@ -73,18 +78,24 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
return result; return result;
} }
if (options.dumpio) { const stdout = readline.createInterface({ input: spawnedProcess.stdout });
spawnedProcess.stdout.pipe(process.stdout); stdout.on('line', (data: string) => {
spawnedProcess.stderr.pipe(process.stderr); debugBrowserOut(data);
} else { if (options.dumpio)
spawnedProcess.stderr.on('data', () => {}); console.log(`\x1b[33m[out]\x1b[0m ${data}`); // eslint-disable-line no-console
spawnedProcess.stdout.on('data', () => {}); });
}
const stderr = readline.createInterface({ input: spawnedProcess.stderr });
stderr.on('line', (data: string) => {
debugBrowserErr(data);
if (options.dumpio)
console.log(`\x1b[31m[err]\x1b[0m ${data}`); // eslint-disable-line no-console
});
let processClosed = false; let processClosed = false;
const waitForProcessToClose = new Promise((fulfill, reject) => { const waitForProcessToClose = new Promise((fulfill, reject) => {
spawnedProcess.once('exit', (exitCode, signal) => { spawnedProcess.once('exit', (exitCode, signal) => {
debugLauncher(`[${id}] <process did exit ${exitCode}, ${signal}>`); debugBrowser(`<process did exit ${exitCode}, ${signal}>`);
processClosed = true; processClosed = true;
helper.removeEventListeners(listeners); helper.removeEventListeners(listeners);
options.onkill(exitCode, signal); options.onkill(exitCode, signal);
@ -117,20 +128,20 @@ export async function launchProcess(options: LaunchProcessOptions): Promise<Laun
// reentrancy to this function, for example user sends SIGINT second time. // reentrancy to this function, for example user sends SIGINT second time.
// In this case, let's forcefully kill the process. // In this case, let's forcefully kill the process.
if (gracefullyClosing) { if (gracefullyClosing) {
debugLauncher(`[${id}] <forecefully close>`); debugBrowser(`<forecefully close>`);
killProcess(); killProcess();
return; return;
} }
gracefullyClosing = true; gracefullyClosing = true;
debugLauncher(`[${id}] <gracefully close start>`); debugBrowser(`<gracefully close start>`);
options.attemptToGracefullyClose().catch(() => killProcess()); options.attemptToGracefullyClose().catch(() => killProcess());
await waitForProcessToClose; await waitForProcessToClose;
debugLauncher(`[${id}] <gracefully close end>`); debugBrowser(`<gracefully close end>`);
} }
// This method has to be sync to be used as 'exit' event handler. // This method has to be sync to be used as 'exit' event handler.
function killProcess() { function killProcess() {
debugLauncher(`[${id}] <kill>`); debugBrowser(`<kill>`);
helper.removeEventListeners(listeners); helper.removeEventListeners(listeners);
if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) { if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) {
// Force kill the browser. // Force kill the browser.

View file

@ -187,7 +187,7 @@ export class WKBrowser extends platform.EventEmitter implements Browser {
} }
_setDebugFunction(debugFunction: (message: string) => void) { _setDebugFunction(debugFunction: (message: string) => void) {
this._connection._debugFunction = debugFunction; this._connection._debugProtocol = debugFunction;
} }
} }

View file

@ -34,7 +34,7 @@ export class WKConnection {
private readonly _onDisconnect: () => void; private readonly _onDisconnect: () => void;
private _lastId = 0; private _lastId = 0;
private _closed = false; private _closed = false;
_debugFunction: (message: string) => void = platform.debug('pw:protocol'); _debugProtocol: (message: string) => void = platform.debug('pw:protocol');
readonly browserSession: WKSession; readonly browserSession: WKSession;
@ -46,6 +46,7 @@ export class WKConnection {
this.browserSession = new WKSession(this, '', 'Browser has been closed.', (message: any) => { this.browserSession = new WKSession(this, '', 'Browser has been closed.', (message: any) => {
this.rawSend(message); this.rawSend(message);
}); });
(this._debugProtocol as any).color = '34';
} }
nextMessageId(): number { nextMessageId(): number {
@ -54,12 +55,12 @@ export class WKConnection {
rawSend(message: any) { rawSend(message: any) {
const data = JSON.stringify(message); const data = JSON.stringify(message);
this._debugFunction('SEND ► ' + (rewriteInjectedScriptEvaluationLog(message) || data)); this._debugProtocol('SEND ► ' + (rewriteInjectedScriptEvaluationLog(message) || data));
this._transport.send(data); this._transport.send(data);
} }
private _dispatchMessage(message: string) { private _dispatchMessage(message: string) {
this._debugFunction('◀ RECV ' + message); this._debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message); const object = JSON.parse(message);
if (object.id === kBrowserCloseMessageId) if (object.id === kBrowserCloseMessageId)
return; return;

View file

@ -72,14 +72,14 @@ module.exports.describe = function({testRunner, expect, product, browserType, pl
it.slow()('should dump browser process stderr', async({server}) => { it.slow()('should dump browser process stderr', async({server}) => {
let dumpioData = ''; let dumpioData = '';
const res = spawn('node', [path.join(__dirname, 'fixtures', 'dumpio.js'), playwrightPath, product, 'usewebsocket']); const res = spawn('node', [path.join(__dirname, 'fixtures', 'dumpio.js'), playwrightPath, product, 'usewebsocket']);
res.stderr.on('data', data => dumpioData += data.toString('utf8')); res.stdout.on('data', data => dumpioData += data.toString('utf8'));
await new Promise(resolve => res.on('close', resolve)); await new Promise(resolve => res.on('close', resolve));
expect(dumpioData).toContain('message from dumpio'); expect(dumpioData).toContain('message from dumpio');
}); });
it.slow()('should dump browser process stderr', async({server}) => { it.slow()('should dump browser process stderr', async({server}) => {
let dumpioData = ''; let dumpioData = '';
const res = spawn('node', [path.join(__dirname, 'fixtures', 'dumpio.js'), playwrightPath, product]); const res = spawn('node', [path.join(__dirname, 'fixtures', 'dumpio.js'), playwrightPath, product]);
res.stderr.on('data', data => dumpioData += data.toString('utf8')); res.stdout.on('data', data => dumpioData += data.toString('utf8'));
await new Promise(resolve => res.on('close', resolve)); await new Promise(resolve => res.on('close', resolve));
expect(dumpioData).toContain('message from dumpio'); expect(dumpioData).toContain('message from dumpio');
}); });

View file

@ -20,6 +20,7 @@ const os = require('os');
const rm = require('rimraf').sync; const rm = require('rimraf').sync;
const GoldenUtils = require('./golden-utils'); const GoldenUtils = require('./golden-utils');
const {Matchers} = require('../utils/testrunner/'); const {Matchers} = require('../utils/testrunner/');
const readline = require('readline');
const YELLOW_COLOR = '\x1b[33m'; const YELLOW_COLOR = '\x1b[33m';
const RESET_COLOR = '\x1b[0m'; const RESET_COLOR = '\x1b[0m';
@ -61,7 +62,6 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
executablePath, executablePath,
slowMo, slowMo,
headless, headless,
dumpio: !!process.env.DUMPIO,
}; };
if (defaultBrowserOptions.executablePath) { if (defaultBrowserOptions.executablePath) {
@ -103,31 +103,29 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
beforeAll(async state => { beforeAll(async state => {
state.browser = await browserType.launch(defaultBrowserOptions); state.browser = await browserType.launch(defaultBrowserOptions);
state.browserServer = state.browser.__server__; state.browserServer = state.browser.__server__;
state._stdout = readline.createInterface({ input: state.browserServer.process().stdout });
state._stderr = readline.createInterface({ input: state.browserServer.process().stderr });
}); });
afterAll(async state => { afterAll(async state => {
await state.browserServer.close(); await state.browserServer.close();
state.browser = null; state.browser = null;
state.browserServer = null; state.browserServer = null;
}); state._stdout.close();
state._stderr.close();
});
beforeEach(async(state, test) => { beforeEach(async(state, test) => {
const onLine = (line) => test.output += line + '\n'; test.output = [];
const dumpout = data => test.output.push(`\x1b[33m[pw:stdio:out]\x1b[0m ${data}`);
const dumperr = data => test.output.push(`\x1b[31m[pw:stdio:err]\x1b[0m ${data}`);
state._stdout.on('line', dumpout);
state._stderr.on('line', dumperr);
if (dumpProtocolOnFailure) if (dumpProtocolOnFailure)
state.browser._setDebugFunction(onLine); state.browser._setDebugFunction(data => test.output.push(`\x1b[32m[pw:protocol]\x1b[0m ${data}`));
let rl;
if (state.browserServer.process().stderr) {
rl = require('readline').createInterface({ input: state.browserServer.process().stderr });
test.output = '';
rl.on('line', onLine);
}
state.tearDown = async () => { state.tearDown = async () => {
if (rl) { state._stdout.off('line', dumpout);
rl.removeListener('line', onLine); state._stderr.off('line', dumperr);
rl.close();
}
if (dumpProtocolOnFailure) if (dumpProtocolOnFailure)
state.browser._setDebugFunction(() => void 0); state.browser._setDebugFunction(() => void 0);
}; };

View file

@ -207,7 +207,8 @@ class Reporter {
console.log(`${prefix} ${colors.red(`[TIMEOUT ${test.timeout}ms]`)} ${test.fullName} (${formatLocation(test.location)})`); console.log(`${prefix} ${colors.red(`[TIMEOUT ${test.timeout}ms]`)} ${test.fullName} (${formatLocation(test.location)})`);
if (test.output) { if (test.output) {
console.log(' Output:'); console.log(' Output:');
console.log(padLines(test.output, 4)); for (const line of test.output)
console.log(' ' + line);
} }
} else if (test.result === 'failed') { } else if (test.result === 'failed') {
console.log(`${prefix} ${colors.red('[FAIL]')} ${test.fullName} (${formatLocation(test.location)})`); console.log(`${prefix} ${colors.red('[FAIL]')} ${test.fullName} (${formatLocation(test.location)})`);
@ -253,7 +254,8 @@ class Reporter {
} }
if (test.output) { if (test.output) {
console.log(' Output:'); console.log(' Output:');
console.log(padLines(test.output, 4)); for (const line of test.output)
console.log(' ' + line);
} }
} }
} }