chore: do not use ansi outsite of TTY (#27979)

Fixes https://github.com/microsoft/playwright/issues/27891
This commit is contained in:
Pavel Feldman 2023-11-07 14:09:40 -08:00 committed by GitHub
parent b9aaa38d3b
commit cb14de7a5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 42 deletions

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { colors, ms as milliseconds, parseStackTraceLine } from 'playwright-core/lib/utilsBundle'; import { colors as realColors, ms as milliseconds, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
import path from 'path'; import path from 'path';
import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location } from '../../types/testReporter'; import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location } from '../../types/testReporter';
import type { SuitePrivate } from '../../types/reporterPrivate'; import type { SuitePrivate } from '../../types/reporterPrivate';
@ -45,6 +45,28 @@ type TestSummary = {
fatalErrors: TestError[]; fatalErrors: TestError[];
}; };
export const isTTY = !!process.env.PWTEST_TTY_WIDTH || process.stdout.isTTY;
export const ttyWidth = process.env.PWTEST_TTY_WIDTH ? parseInt(process.env.PWTEST_TTY_WIDTH, 10) : process.stdout.columns || 0;
let useColors = isTTY;
if (process.env.DEBUG_COLORS === '0'
|| process.env.DEBUG_COLORS === 'false'
|| process.env.FORCE_COLOR === '0'
|| process.env.FORCE_COLOR === 'false')
useColors = false;
else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
useColors = true;
export const colors = useColors ? realColors : {
bold: (t: string) => t,
cyan: (t: string) => t,
dim: (t: string) => t,
gray: (t: string) => t,
green: (t: string) => t,
red: (t: string) => t,
yellow: (t: string) => t,
enabled: false,
};
export class BaseReporter implements ReporterV2 { export class BaseReporter implements ReporterV2 {
config!: FullConfig; config!: FullConfig;
suite!: Suite; suite!: Suite;
@ -52,13 +74,11 @@ export class BaseReporter implements ReporterV2 {
result!: FullResult; result!: FullResult;
private fileDurations = new Map<string, number>(); private fileDurations = new Map<string, number>();
private _omitFailures: boolean; private _omitFailures: boolean;
private readonly _ttyWidthForTest: number;
private _fatalErrors: TestError[] = []; private _fatalErrors: TestError[] = [];
private _failureCount: number = 0; private _failureCount: number = 0;
constructor(options: { omitFailures?: boolean } = {}) { constructor(options: { omitFailures?: boolean } = {}) {
this._omitFailures = options.omitFailures || false; this._omitFailures = options.omitFailures || false;
this._ttyWidthForTest = parseInt(process.env.PWTEST_TTY_WIDTH || '', 10);
} }
version(): 'v2' { version(): 'v2' {
@ -128,12 +148,7 @@ export class BaseReporter implements ReporterV2 {
return true; return true;
} }
protected ttyWidth() {
return this._ttyWidthForTest || process.stdout.columns || 0;
}
protected fitToScreen(line: string, prefix?: string): string { protected fitToScreen(line: string, prefix?: string): string {
const ttyWidth = this.ttyWidth();
if (!ttyWidth) { if (!ttyWidth) {
// Guard against the case where we cannot determine available width. // Guard against the case where we cannot determine available width.
return line; return line;
@ -473,7 +488,7 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta
export function separator(text: string = ''): string { export function separator(text: string = ''): string {
if (text) if (text)
text += ' '; text += ' ';
const columns = Math.min(100, process.stdout?.columns || 100); const columns = Math.min(100, ttyWidth || 100);
return text + colors.dim('─'.repeat(Math.max(0, columns - text.length))); return text + colors.dim('─'.repeat(Math.max(0, columns - text.length)));
} }

View file

@ -14,8 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { colors } from 'playwright-core/lib/utilsBundle'; import { colors, BaseReporter, formatError } from './base';
import { BaseReporter, formatError } from './base';
import type { FullResult, TestCase, TestResult, Suite, TestError } from '../../types/testReporter'; import type { FullResult, TestCase, TestResult, Suite, TestError } from '../../types/testReporter';
class DotReporter extends BaseReporter { class DotReporter extends BaseReporter {

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { colors, open } from 'playwright-core/lib/utilsBundle'; import { open } from 'playwright-core/lib/utilsBundle';
import { MultiMap, getPackageManagerExecCommand } from 'playwright-core/lib/utils'; import { MultiMap, getPackageManagerExecCommand } from 'playwright-core/lib/utils';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
@ -25,7 +25,7 @@ import { codeFrameColumns } from '../transform/babelBundle';
import type { FullResult, FullConfig, Location, Suite, TestCase as TestCasePublic, TestResult as TestResultPublic, TestStep as TestStepPublic, TestError } from '../../types/testReporter'; import type { FullResult, FullConfig, Location, Suite, TestCase as TestCasePublic, TestResult as TestResultPublic, TestStep as TestStepPublic, TestError } from '../../types/testReporter';
import type { SuitePrivate } from '../../types/reporterPrivate'; import type { SuitePrivate } from '../../types/reporterPrivate';
import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath } from 'playwright-core/lib/utils'; import { HttpServer, assert, calculateSha1, copyFileAndMakeWritable, gracefullyProcessExitDoNotHang, removeFolders, sanitizeForFilePath } from 'playwright-core/lib/utils';
import { formatError, formatResultFailure, stripAnsiEscapes } from './base'; import { colors, formatError, formatResultFailure, stripAnsiEscapes } from './base';
import { resolveReporterOutputPath } from '../util'; import { resolveReporterOutputPath } from '../util';
import type { Metadata } from '../../types/test'; import type { Metadata } from '../../types/test';
import type { ZipFile } from 'playwright-core/lib/zipBundle'; import type { ZipFile } from 'playwright-core/lib/zipBundle';

View file

@ -15,11 +15,10 @@
*/ */
import fs from 'fs'; import fs from 'fs';
import { colors } from 'playwright-core/lib/utilsBundle';
import { codeFrameColumns } from '../transform/babelBundle'; import { codeFrameColumns } from '../transform/babelBundle';
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep } from '../../types/testReporter'; import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep } from '../../types/testReporter';
import { Suite } from '../common/test'; import { Suite } from '../common/test';
import { prepareErrorStack, relativeFilePath } from './base'; import { colors, prepareErrorStack, relativeFilePath } from './base';
import type { ReporterV2 } from './reporterV2'; import type { ReporterV2 } from './reporterV2';
import { monotonicTime } from 'playwright-core/lib/utils'; import { monotonicTime } from 'playwright-core/lib/utils';

View file

@ -14,8 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { colors } from 'playwright-core/lib/utilsBundle'; import { colors, BaseReporter, formatError, formatFailure, formatTestTitle } from './base';
import { BaseReporter, formatError, formatFailure, formatTestTitle } from './base';
import type { TestCase, Suite, TestResult, FullResult, TestStep, TestError } from '../../types/testReporter'; import type { TestCase, Suite, TestResult, FullResult, TestStep, TestError } from '../../types/testReporter';
class LineReporter extends BaseReporter { class LineReporter extends BaseReporter {

View file

@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { colors, ms as milliseconds } from 'playwright-core/lib/utilsBundle'; import { ms as milliseconds } from 'playwright-core/lib/utilsBundle';
import { BaseReporter, formatError, formatTestTitle, stepSuffix, stripAnsiEscapes } from './base'; import { colors, BaseReporter, formatError, formatTestTitle, isTTY, stepSuffix, stripAnsiEscapes, ttyWidth } from './base';
import type { FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter'; import type { FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter';
// Allow it in the Visual Studio Code Terminal and the new Windows Terminal // Allow it in the Visual Studio Code Terminal and the new Windows Terminal
@ -31,13 +31,11 @@ class ListReporter extends BaseReporter {
private _resultIndex = new Map<TestResult, string>(); private _resultIndex = new Map<TestResult, string>();
private _stepIndex = new Map<TestStep, string>(); private _stepIndex = new Map<TestStep, string>();
private _needNewLine = false; private _needNewLine = false;
private readonly _liveTerminal: string | boolean | undefined;
private _printSteps: boolean; private _printSteps: boolean;
constructor(options: { omitFailures?: boolean, printSteps?: boolean } = {}) { constructor(options: { omitFailures?: boolean, printSteps?: boolean } = {}) {
super(options); super(options);
this._printSteps = options.printSteps || !!process.env.PW_TEST_DEBUG_REPORTERS_PRINT_STEPS; this._printSteps = isTTY && (options.printSteps || !!process.env.PW_TEST_DEBUG_REPORTERS_PRINT_STEPS);
this._liveTerminal = process.stdout.isTTY || !!process.env.PWTEST_TTY_WIDTH;
} }
override printsToStdio() { override printsToStdio() {
@ -55,16 +53,15 @@ class ListReporter extends BaseReporter {
override onTestBegin(test: TestCase, result: TestResult) { override onTestBegin(test: TestCase, result: TestResult) {
super.onTestBegin(test, result); super.onTestBegin(test, result);
if (this._liveTerminal) if (!isTTY)
this._maybeWriteNewLine(); return;
this._resultIndex.set(result, String(this._resultIndex.size + 1)); this._maybeWriteNewLine();
if (this._liveTerminal) { const index = String(this._resultIndex.size + 1);
this._testRows.set(test, this._lastRow); this._resultIndex.set(result, index);
const index = this._resultIndex.get(result)!; this._testRows.set(test, this._lastRow);
const prefix = this._testPrefix(index, ''); const prefix = this._testPrefix(index, '');
const line = colors.dim(formatTestTitle(this.config, test)) + this._retrySuffix(result); const line = colors.dim(formatTestTitle(this.config, test)) + this._retrySuffix(result);
this._appendLine(line, prefix); this._appendLine(line, prefix);
}
} }
override onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) { override onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult) {
@ -81,9 +78,9 @@ class ListReporter extends BaseReporter {
super.onStepBegin(test, result, step); super.onStepBegin(test, result, step);
if (step.category !== 'test.step') if (step.category !== 'test.step')
return; return;
const testIndex = this._resultIndex.get(result)!; const testIndex = this._resultIndex.get(result) || '';
if (!this._printSteps) { if (!this._printSteps) {
if (this._liveTerminal) if (isTTY)
this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, ''));
return; return;
} }
@ -93,9 +90,8 @@ class ListReporter extends BaseReporter {
const stepIndex = `${testIndex}.${ordinal}`; const stepIndex = `${testIndex}.${ordinal}`;
this._stepIndex.set(step, stepIndex); this._stepIndex.set(step, stepIndex);
if (this._liveTerminal) if (isTTY) {
this._maybeWriteNewLine(); this._maybeWriteNewLine();
if (this._liveTerminal) {
this._stepRows.set(step, this._lastRow); this._stepRows.set(step, this._lastRow);
const prefix = this._testPrefix(stepIndex, ''); const prefix = this._testPrefix(stepIndex, '');
const line = test.title + colors.dim(stepSuffix(step)); const line = test.title + colors.dim(stepSuffix(step));
@ -108,9 +104,9 @@ class ListReporter extends BaseReporter {
if (step.category !== 'test.step') if (step.category !== 'test.step')
return; return;
const testIndex = this._resultIndex.get(result)!; const testIndex = this._resultIndex.get(result) || '';
if (!this._printSteps) { if (!this._printSteps) {
if (this._liveTerminal) if (isTTY)
this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, ''));
return; return;
} }
@ -137,8 +133,7 @@ class ListReporter extends BaseReporter {
private _updateLineCountAndNewLineFlagForOutput(text: string) { private _updateLineCountAndNewLineFlagForOutput(text: string) {
this._needNewLine = text[text.length - 1] !== '\n'; this._needNewLine = text[text.length - 1] !== '\n';
const ttyWidth = this.ttyWidth(); if (!ttyWidth)
if (!this._liveTerminal || ttyWidth === 0)
return; return;
for (const ch of text) { for (const ch of text) {
if (ch === '\n') { if (ch === '\n') {
@ -168,7 +163,15 @@ class ListReporter extends BaseReporter {
const title = formatTestTitle(this.config, test); const title = formatTestTitle(this.config, test);
let prefix = ''; let prefix = '';
let text = ''; let text = '';
const index = this._resultIndex.get(result)!;
// In TTY mode test index is incremented in onTestStart
// and in non-TTY mode it is incremented onTestEnd.
let index = this._resultIndex.get(result);
if (!index) {
index = String(this._resultIndex.size + 1);
this._resultIndex.set(result, index);
}
if (result.status === 'skipped') { if (result.status === 'skipped') {
prefix = this._testPrefix(index, colors.green('-')); prefix = this._testPrefix(index, colors.green('-'));
// Do not show duration for skipped. // Do not show duration for skipped.
@ -189,7 +192,7 @@ class ListReporter extends BaseReporter {
} }
private _updateOrAppendLine(row: number, text: string, prefix: string) { private _updateOrAppendLine(row: number, text: string, prefix: string) {
if (this._liveTerminal) { if (isTTY) {
this._updateLine(row, text, prefix); this._updateLine(row, text, prefix);
} else { } else {
this._maybeWriteNewLine(); this._maybeWriteNewLine();