fix(list reporter): make sure that duration suffix survives truncation (#11002)

This commit is contained in:
Dmitry Gozman 2021-12-17 13:08:02 -08:00 committed by GitHub
parent 037baf0945
commit f5780be41b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 35 additions and 29 deletions

View file

@ -56,7 +56,7 @@ class ListReporter extends BaseReporter {
this._lastRow++; this._lastRow++;
} }
const line = ' ' + colors.gray(formatTestTitle(this.config, test)); const line = ' ' + colors.gray(formatTestTitle(this.config, test));
process.stdout.write(this._fitToScreen(line) + '\n'); process.stdout.write(this._fitToScreen(line, 0) + '\n');
} }
this._testRows.set(test, this._lastRow++); this._testRows.set(test, this._lastRow++);
} }
@ -76,7 +76,7 @@ class ListReporter extends BaseReporter {
return; return;
if (step.category !== 'test.step') if (step.category !== 'test.step')
return; return;
this._updateTestLine(test, ' ' + colors.gray(formatTestTitle(this.config, test, step))); this._updateTestLine(test, ' ' + colors.gray(formatTestTitle(this.config, test, step)), '');
} }
onStepEnd(test: TestCase, result: TestResult, step: TestStep) { onStepEnd(test: TestCase, result: TestResult, step: TestStep) {
@ -84,7 +84,7 @@ class ListReporter extends BaseReporter {
return; return;
if (step.category !== 'test.step') if (step.category !== 'test.step')
return; return;
this._updateTestLine(test, ' ' + colors.gray(formatTestTitle(this.config, test, step.parent))); this._updateTestLine(test, ' ' + colors.gray(formatTestTitle(this.config, test, step.parent)), '');
} }
private _dumpToStdio(test: TestCase | undefined, chunk: string | Buffer, stream: NodeJS.WriteStream) { private _dumpToStdio(test: TestCase | undefined, chunk: string | Buffer, stream: NodeJS.WriteStream) {
@ -102,75 +102,75 @@ class ListReporter extends BaseReporter {
override onTestEnd(test: TestCase, result: TestResult) { override onTestEnd(test: TestCase, result: TestResult) {
super.onTestEnd(test, result); super.onTestEnd(test, result);
const duration = colors.dim(` (${milliseconds(result.duration)})`); let duration = colors.dim(` (${milliseconds(result.duration)})`);
const title = formatTestTitle(this.config, test); const title = formatTestTitle(this.config, test);
let text = ''; let text = '';
if (result.status === 'skipped') { if (result.status === 'skipped') {
text = colors.green(' - ') + colors.cyan(title); text = colors.green(' - ') + colors.cyan(title);
duration = ''; // Do not show duration for skipped.
} else { } else {
const statusMark = (' ' + (result.status === 'passed' ? POSITIVE_STATUS_MARK : NEGATIVE_STATUS_MARK)).padEnd(5); const statusMark = (' ' + (result.status === 'passed' ? POSITIVE_STATUS_MARK : NEGATIVE_STATUS_MARK)).padEnd(5);
if (result.status === test.expectedStatus) if (result.status === test.expectedStatus)
text = '\u001b[2K\u001b[0G' + colors.green(statusMark) + colors.gray(title) + duration; text = '\u001b[2K\u001b[0G' + colors.green(statusMark) + colors.gray(title);
else else
text = '\u001b[2K\u001b[0G' + colors.red(statusMark + title) + duration; text = '\u001b[2K\u001b[0G' + colors.red(statusMark + title);
} }
if (this._liveTerminal) { if (this._liveTerminal) {
this._updateTestLine(test, text); this._updateTestLine(test, text, duration);
} else { } else {
if (this._needNewLine) { if (this._needNewLine) {
this._needNewLine = false; this._needNewLine = false;
process.stdout.write('\n'); process.stdout.write('\n');
} }
process.stdout.write(text); process.stdout.write(text + duration);
process.stdout.write('\n'); process.stdout.write('\n');
} }
} }
private _updateTestLine(test: TestCase, line: string) { private _updateTestLine(test: TestCase, line: string, suffix: string) {
if (process.env.PWTEST_SKIP_TEST_OUTPUT) if (process.env.PWTEST_SKIP_TEST_OUTPUT)
this._updateTestLineForTest(test,line); this._updateTestLineForTest(test, line, suffix);
else else
this._updateTestLineForTTY(test,line); this._updateTestLineForTTY(test, line, suffix);
} }
private _updateTestLineForTTY(test: TestCase, line: string) { private _updateTestLineForTTY(test: TestCase, line: string, suffix: string) {
const testRow = this._testRows.get(test)!; const testRow = this._testRows.get(test)!;
// Go up if needed // Go up if needed
if (testRow !== this._lastRow) if (testRow !== this._lastRow)
process.stdout.write(`\u001B[${this._lastRow - testRow}A`); process.stdout.write(`\u001B[${this._lastRow - testRow}A`);
// Erase line // Erase line
process.stdout.write('\u001B[2K'); process.stdout.write('\u001B[2K');
process.stdout.write(this._fitToScreen(line)); process.stdout.write(this._fitToScreen(line, visibleLength(suffix)) + suffix);
// Go down if needed. // Go down if needed.
if (testRow !== this._lastRow) if (testRow !== this._lastRow)
process.stdout.write(`\u001B[${this._lastRow - testRow}E`); process.stdout.write(`\u001B[${this._lastRow - testRow}E`);
} }
private _fitToScreen(line: string): string { private _fitToScreen(line: string, suffixLength: number): string {
if (!this._ttyWidth() || line.length <= this._ttyWidth()) const ttyWidth = this._ttyWidth() - suffixLength;
if (!this._ttyWidth() || line.length <= ttyWidth)
return line; return line;
// Matches '\u001b[2K\u001b[0G' and all color codes.
const re = /\u001b\[2K\u001b\[0G|\x1B\[\d+m/g;
let m; let m;
let colorLen = 0; let colorLen = 0;
while ((m = re.exec(line)) !== null) { while ((m = kColorsRe.exec(line)) !== null) {
const visibleLen = m.index - colorLen; const visibleLen = m.index - colorLen;
if (visibleLen >= this._ttyWidth()) if (visibleLen >= ttyWidth)
break; break;
colorLen += m[0].length; colorLen += m[0].length;
} }
// Truncate and reset all colors. // Truncate and reset all colors.
return line.substr(0, this._ttyWidth() + colorLen) + '\u001b[0m'; return line.substr(0, ttyWidth + colorLen) + '\u001b[0m';
} }
private _ttyWidth(): number { private _ttyWidth(): number {
return this._ttyWidthForTest || process.stdout.columns || 0; return this._ttyWidthForTest || process.stdout.columns || 0;
} }
private _updateTestLineForTest(test: TestCase, line: string) { private _updateTestLineForTest(test: TestCase, line: string, suffix: string) {
const testRow = this._testRows.get(test)!; const testRow = this._testRows.get(test)!;
process.stdout.write(testRow + ' : ' + line + '\n'); process.stdout.write(testRow + ' : ' + line + suffix + '\n');
} }
override async onEnd(result: FullResult) { override async onEnd(result: FullResult) {
@ -180,4 +180,10 @@ class ListReporter extends BaseReporter {
} }
} }
// Matches '\u001b[2K\u001b[0G' and all color codes.
const kColorsRe = /\u001b\[2K\u001b\[0G|\x1B\[\d+m/g;
function visibleLength(s: string): number {
return s.replace(kColorsRe, '').length;
}
export default ListReporter; export default ListReporter;

View file

@ -99,19 +99,19 @@ test('should truncate long test names', async ({ runInlineTest }) => {
}); });
test('passes 2 long name', async () => { test('passes 2 long name', async () => {
}); });
test.skip('skipped long name', async () => { test.skip('skipped very long name', async () => {
}); });
`, `,
}, { reporter: 'list', retries: 0 }, { PWTEST_TTY_WIDTH: 40, PWTEST_SKIP_TEST_OUTPUT: undefined }); }, { reporter: 'list', retries: 0 }, { PWTEST_TTY_WIDTH: 50, PWTEST_SKIP_TEST_OUTPUT: undefined });
const text = stripAscii(result.output); const text = stripAscii(result.output);
const positiveStatusMarkPrefix = process.platform === 'win32' ? 'ok' : '✓ '; const positiveStatusMarkPrefix = process.platform === 'win32' ? 'ok' : '✓ ';
const negativateStatusMarkPrefix = process.platform === 'win32' ? 'x ' : '✘ '; const negativateStatusMarkPrefix = process.platform === 'win32' ? 'x ' : '✘ ';
expect(text).toContain(`${negativateStatusMarkPrefix} [foo] a.test.ts:6:7 fails long`); expect(text).toContain(`${negativateStatusMarkPrefix} [foo] a.test.ts:6:7 fails long`);
expect(text).not.toContain(`${negativateStatusMarkPrefix} [foo] a.test.ts:6:7 fails long n`); expect(text).not.toContain(`${negativateStatusMarkPrefix} [foo] a.test.ts:6:7 fails long name (`);
expect(text).toContain(`${positiveStatusMarkPrefix} [foo] a.test.ts:9:7 passes (`); expect(text).toContain(`${positiveStatusMarkPrefix} [foo] a.test.ts:9:7 passes (`);
expect(text).toContain(`${positiveStatusMarkPrefix} [foo] a.test.ts:11:7 passes 2 l`); expect(text).toContain(`${positiveStatusMarkPrefix} [foo] a.test.ts:11:7 passes 2 long`);
expect(text).not.toContain(`${positiveStatusMarkPrefix} [foo] a.test.ts:11:7 passes 2 lo`); expect(text).not.toContain(`${positiveStatusMarkPrefix} [foo] a.test.ts:11:7 passes 2 long name (`);
expect(text).toContain(`- [foo] a.test.ts:13:12 skipped l`); expect(text).toContain(`- [foo] a.test.ts:13:12 skipped very long n`);
expect(text).not.toContain(`- [foo] a.test.ts:13:12 skipped lo`); expect(text).not.toContain(`- [foo] a.test.ts:13:12 skipped very long na`);
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
}); });