diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index 0c023e8761..f418baeea4 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -45,27 +45,41 @@ type TestSummary = { 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 { isTTY, ttyWidth, colors } = (() => { + let isTTY = !!process.stdout.isTTY; + let ttyWidth = process.stdout.columns || 0; + if (process.env.PLAYWRIGHT_FORCE_TTY === 'false' || process.env.PLAYWRIGHT_FORCE_TTY === '0') { + isTTY = false; + ttyWidth = 0; + } else if (process.env.PLAYWRIGHT_FORCE_TTY === 'true' || process.env.PLAYWRIGHT_FORCE_TTY === '1') { + isTTY = true; + ttyWidth = process.stdout.columns || 100; + } else if (process.env.PLAYWRIGHT_FORCE_TTY) { + isTTY = true; + ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY; + if (isNaN(ttyWidth)) + ttyWidth = 100; + } -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, -}; + 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; + + 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, + }; + return { isTTY, ttyWidth, colors }; +})(); export class BaseReporter implements ReporterV2 { config!: FullConfig; diff --git a/packages/playwright/src/reporters/list.ts b/packages/playwright/src/reporters/list.ts index 11f6a8b755..65057e16f9 100644 --- a/packages/playwright/src/reporters/list.ts +++ b/packages/playwright/src/reporters/list.ts @@ -229,8 +229,6 @@ class ListReporter extends BaseReporter { // Go down if needed. if (row !== this._lastRow) process.stdout.write(`\u001B[${this._lastRow - row}E`); - if (process.env.PWTEST_TTY_WIDTH) - process.stdout.write('\n'); // For testing. } private _testPrefix(index: string, statusMark: string) { diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index 2e0e39a337..dbd91049d1 100644 --- a/tests/playwright-test/reporter-blob.spec.ts +++ b/tests/playwright-test/reporter-blob.spec.ts @@ -441,7 +441,7 @@ test('merge into list report by default', async ({ runInlineTest, mergeReports } const reportFiles = await fs.promises.readdir(reportDir); reportFiles.sort(); expect(reportFiles).toEqual(['report-1.zip', 'report-2.zip', 'report-3.zip']); - const { exitCode, output } = await mergeReports(reportDir, { PW_TEST_DEBUG_REPORTERS: '1', PW_TEST_DEBUG_REPORTERS_PRINT_STEPS: '1', PWTEST_TTY_WIDTH: '80' }, { additionalArgs: ['--reporter', 'list'] }); + const { exitCode, output } = await mergeReports(reportDir, { PW_TEST_DEBUG_REPORTERS: '1', PW_TEST_DEBUG_REPORTERS_PRINT_STEPS: '1', PLAYWRIGHT_FORCE_TTY: '80' }, { additionalArgs: ['--reporter', 'list'] }); expect(exitCode).toBe(0); const text = stripAnsi(output); diff --git a/tests/playwright-test/reporter-list.spec.ts b/tests/playwright-test/reporter-list.spec.ts index 245b0e26d3..2b924e5986 100644 --- a/tests/playwright-test/reporter-list.spec.ts +++ b/tests/playwright-test/reporter-list.spec.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { test, expect } from './playwright-test-fixtures'; +import { test, expect, stripAnsi } from './playwright-test-fixtures'; const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && process.env.TERM_PROGRAM !== 'vscode' && !process.env.WT_SESSION; const POSITIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? 'ok' : '✓ '; @@ -70,7 +70,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { }); }); `, - }, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PW_TEST_DEBUG_REPORTERS_PRINT_STEPS: '1', PWTEST_TTY_WIDTH: '80' }); + }, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PW_TEST_DEBUG_REPORTERS_PRINT_STEPS: '1', PLAYWRIGHT_FORCE_TTY: '80' }); const text = result.output; const lines = text.split('\n').filter(l => l.match(/^\d :/)).map(l => l.replace(/[.\d]+m?s/, 'Xms')); lines.pop(); // Remove last item that contains [v] and time in ms. @@ -105,7 +105,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { await test.step('inner 2.2', async () => {}); }); });`, - }, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PWTEST_TTY_WIDTH: '80' }); + }, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_FORCE_TTY: '80' }); const text = result.output; const lines = text.split('\n').filter(l => l.match(/^\d :/)).map(l => l.replace(/[.\d]+m?s/, 'Xms')); lines.pop(); // Remove last item that contains [v] and time in ms. @@ -135,7 +135,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { console.log('a'.repeat(80) + 'b'.repeat(20)); }); `, - }, { reporter: 'list' }, { PWTEST_TTY_WIDTH: TTY_WIDTH + '' }); + }, { reporter: 'list' }, { PLAYWRIGHT_FORCE_TTY: TTY_WIDTH + '' }); const renderedText = simpleAnsiRenderer(result.rawOutput, TTY_WIDTH); if (process.platform === 'win32') @@ -154,7 +154,7 @@ for (const useIntermediateMergeReport of [false, true] as const) { expect(testInfo.retry).toBe(1); }); `, - }, { reporter: 'list', retries: '1' }, { PW_TEST_DEBUG_REPORTERS: '1', PWTEST_TTY_WIDTH: '80' }); + }, { reporter: 'list', retries: '1' }, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_FORCE_TTY: '80' }); const text = result.output; const lines = text.split('\n').filter(l => l.startsWith('0 :') || l.startsWith('1 :')).map(l => l.replace(/\d+(\.\d+)?m?s/, 'XXms')); @@ -185,10 +185,10 @@ for (const useIntermediateMergeReport of [false, true] as const) { test.skip('skipped very long name', async () => { }); `, - }, { reporter: 'list', retries: 0 }, { PWTEST_TTY_WIDTH: '50' }); + }, { reporter: 'list', retries: 0 }, { PLAYWRIGHT_FORCE_TTY: '50' }); expect(result.exitCode).toBe(1); - const lines = result.output.split('\n').slice(3, 11); + const lines = result.rawOutput.split('\n').map(line => line.split('\x1B[22m\x1B[1E')).flat().map(line => stripAnsi(line)).filter(line => line.trim()).slice(1, 9); expect(lines.every(line => line.length <= 50)).toBe(true); expect(lines[0]).toBe(` 1 …a.test.ts:3:15 › failure in very long name`);