From f5780be41b5aa50b8aaee53cc62468e5710cf784 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 17 Dec 2021 13:08:02 -0800 Subject: [PATCH] fix(list reporter): make sure that duration suffix survives truncation (#11002) --- .../playwright-test/src/reporters/list.ts | 50 +++++++++++-------- tests/playwright-test/reporter-list.spec.ts | 14 +++--- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/packages/playwright-test/src/reporters/list.ts b/packages/playwright-test/src/reporters/list.ts index aad8563a3f..b75a266698 100644 --- a/packages/playwright-test/src/reporters/list.ts +++ b/packages/playwright-test/src/reporters/list.ts @@ -56,7 +56,7 @@ class ListReporter extends BaseReporter { this._lastRow++; } 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++); } @@ -76,7 +76,7 @@ class ListReporter extends BaseReporter { return; if (step.category !== 'test.step') 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) { @@ -84,7 +84,7 @@ class ListReporter extends BaseReporter { return; if (step.category !== 'test.step') 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) { @@ -102,75 +102,75 @@ class ListReporter extends BaseReporter { override onTestEnd(test: TestCase, result: TestResult) { super.onTestEnd(test, result); - const duration = colors.dim(` (${milliseconds(result.duration)})`); + let duration = colors.dim(` (${milliseconds(result.duration)})`); const title = formatTestTitle(this.config, test); let text = ''; if (result.status === 'skipped') { text = colors.green(' - ') + colors.cyan(title); + duration = ''; // Do not show duration for skipped. } else { const statusMark = (' ' + (result.status === 'passed' ? POSITIVE_STATUS_MARK : NEGATIVE_STATUS_MARK)).padEnd(5); 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 - text = '\u001b[2K\u001b[0G' + colors.red(statusMark + title) + duration; + text = '\u001b[2K\u001b[0G' + colors.red(statusMark + title); } if (this._liveTerminal) { - this._updateTestLine(test, text); + this._updateTestLine(test, text, duration); } else { if (this._needNewLine) { this._needNewLine = false; process.stdout.write('\n'); } - process.stdout.write(text); + process.stdout.write(text + duration); 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) - this._updateTestLineForTest(test,line); + this._updateTestLineForTest(test, line, suffix); 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)!; // Go up if needed if (testRow !== this._lastRow) process.stdout.write(`\u001B[${this._lastRow - testRow}A`); // Erase line process.stdout.write('\u001B[2K'); - process.stdout.write(this._fitToScreen(line)); + process.stdout.write(this._fitToScreen(line, visibleLength(suffix)) + suffix); // Go down if needed. if (testRow !== this._lastRow) process.stdout.write(`\u001B[${this._lastRow - testRow}E`); } - private _fitToScreen(line: string): string { - if (!this._ttyWidth() || line.length <= this._ttyWidth()) + private _fitToScreen(line: string, suffixLength: number): string { + const ttyWidth = this._ttyWidth() - suffixLength; + if (!this._ttyWidth() || line.length <= ttyWidth) return line; - // Matches '\u001b[2K\u001b[0G' and all color codes. - const re = /\u001b\[2K\u001b\[0G|\x1B\[\d+m/g; let m; let colorLen = 0; - while ((m = re.exec(line)) !== null) { + while ((m = kColorsRe.exec(line)) !== null) { const visibleLen = m.index - colorLen; - if (visibleLen >= this._ttyWidth()) + if (visibleLen >= ttyWidth) break; colorLen += m[0].length; } // 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 { 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)!; - process.stdout.write(testRow + ' : ' + line + '\n'); + process.stdout.write(testRow + ' : ' + line + suffix + '\n'); } 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; diff --git a/tests/playwright-test/reporter-list.spec.ts b/tests/playwright-test/reporter-list.spec.ts index d79ff830ac..e86987e971 100644 --- a/tests/playwright-test/reporter-list.spec.ts +++ b/tests/playwright-test/reporter-list.spec.ts @@ -99,19 +99,19 @@ test('should truncate long test names', async ({ runInlineTest }) => { }); 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 positiveStatusMarkPrefix = process.platform === 'win32' ? 'ok' : '✓ '; const negativateStatusMarkPrefix = process.platform === 'win32' ? 'x ' : '✘ '; 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:11:7 › passes 2 l`); - expect(text).not.toContain(`${positiveStatusMarkPrefix} [foo] › a.test.ts:11:7 › passes 2 lo`); - expect(text).toContain(`- [foo] › a.test.ts:13:12 › skipped l`); - expect(text).not.toContain(`- [foo] › a.test.ts:13:12 › skipped lo`); + 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 long name (`); + expect(text).toContain(`- [foo] › a.test.ts:13:12 › skipped very long n`); + expect(text).not.toContain(`- [foo] › a.test.ts:13:12 › skipped very long na`); expect(result.exitCode).toBe(1); });