From fc915b68c67a7f0d1c7ada383987001ccc85268f Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Sat, 23 Mar 2024 19:04:09 -0700 Subject: [PATCH] feat(test runner): allow overriding TTY params - Move all tty-related craft into `tty.ts`. - Inherit `isTTY` from the runner process into worker process, instead of always pretending `isTTY === true` in the worker. - Respect `env.PLAYWRIGHT_TTY` that overrides tty. Supported: - `0` and `false` to disable tty; - `80` to set columns; - `80x25` to set columns and rows; - `80x25x8` to set columns, rows and color depth. --- packages/playwright/src/common/ipc.ts | 11 +- packages/playwright/src/common/process.ts | 45 +------ packages/playwright/src/common/tty.ts | 121 ++++++++++++++++++ packages/playwright/src/reporters/base.ts | 33 +---- packages/playwright/src/reporters/list.ts | 21 ++- packages/playwright/src/runner/processHost.ts | 16 +-- packages/playwright/src/runner/testServer.ts | 6 +- tests/playwright-test/reporter-blob.spec.ts | 2 +- tests/playwright-test/reporter-list.spec.ts | 14 +- tests/playwright-test/stdio.spec.ts | 46 +++++-- 10 files changed, 192 insertions(+), 123 deletions(-) create mode 100644 packages/playwright/src/common/tty.ts diff --git a/packages/playwright/src/common/ipc.ts b/packages/playwright/src/common/ipc.ts index 3c3ad40f02..8726ec2639 100644 --- a/packages/playwright/src/common/ipc.ts +++ b/packages/playwright/src/common/ipc.ts @@ -18,6 +18,7 @@ import util from 'util'; import { type SerializedCompilationCache, serializeCompilationCache } from '../transform/compilationCache'; import type { ConfigLocation, FullConfigInternal } from './config'; import type { ReporterDescription, TestInfoError, TestStatus } from '../../types/test'; +import type { TTYParams } from './tty'; export type ConfigCLIOverrides = { forbidOnly?: boolean; @@ -46,15 +47,9 @@ export type SerializedConfig = { compilationCache?: SerializedCompilationCache; }; -export type TtyParams = { - rows: number | undefined; - columns: number | undefined; - colorDepth: number; -}; - export type ProcessInitParams = { - stdoutParams: TtyParams; - stderrParams: TtyParams; + stdoutTTY: TTYParams | undefined; + stderrTTY: TTYParams | undefined; processName: string; }; diff --git a/packages/playwright/src/common/process.ts b/packages/playwright/src/common/process.ts index 23de620649..fa2cb656c8 100644 --- a/packages/playwright/src/common/process.ts +++ b/packages/playwright/src/common/process.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import type { WriteStream } from 'tty'; -import type { EnvProducedPayload, ProcessInitParams, TtyParams } from './ipc'; +import type { EnvProducedPayload, ProcessInitParams } from './ipc'; import { startProfiling, stopProfiling } from 'playwright-core/lib/utils'; import type { TestInfoError } from '../../types/test'; import { serializeError } from '../util'; import { registerESMLoader } from './esmLoaderHost'; import { execArgvWithoutExperimentalLoaderOptions } from '../transform/esmUtils'; +import { setTTYParams } from './tty'; export type ProtocolRequest = { id: number; @@ -67,8 +67,8 @@ const startingEnv = { ...process.env }; process.on('message', async (message: any) => { if (message.method === '__init__') { const { processParams, runnerParams, runnerScript } = message.params as { processParams: ProcessInitParams, runnerParams: any, runnerScript: string }; - setTtyParams(process.stdout, processParams.stdoutParams); - setTtyParams(process.stderr, processParams.stderrParams); + setTTYParams(process.stdout, processParams.stdoutTTY); + setTTYParams(process.stderr, processParams.stderrTTY); void startProfiling(); const { create } = require(runnerScript); processRunner = create(runnerParams) as ProcessRunner; @@ -117,40 +117,3 @@ function sendMessageToParent(message: { method: string, params?: any }) { // Can throw when closing. } } - -function setTtyParams(stream: WriteStream, params: TtyParams) { - stream.isTTY = true; - if (params.rows) - stream.rows = params.rows; - if (params.columns) - stream.columns = params.columns; - stream.getColorDepth = () => params.colorDepth; - stream.hasColors = ((count = 16) => { - // count is optional and the first argument may actually be env. - if (typeof count !== 'number') - count = 16; - return count <= 2 ** params.colorDepth; - })as any; - - // Stubs for the rest of the methods to avoid exceptions in user code. - stream.clearLine = (dir: any, callback?: () => void) => { - callback?.(); - return true; - }; - stream.clearScreenDown = (callback?: () => void) => { - callback?.(); - return true; - }; - (stream as any).cursorTo = (x: number, y?: number | (() => void), callback?: () => void) => { - if (callback) - callback(); - else if (y instanceof Function) - y(); - return true; - }; - stream.moveCursor = (dx: number, dy: number, callback?: () => void) => { - callback?.(); - return true; - }; - stream.getWindowSize = () => [stream.columns, stream.rows]; -} diff --git a/packages/playwright/src/common/tty.ts b/packages/playwright/src/common/tty.ts new file mode 100644 index 0000000000..927fad34d5 --- /dev/null +++ b/packages/playwright/src/common/tty.ts @@ -0,0 +1,121 @@ +/** +* Copyright Microsoft Corporation. All rights reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import type { WriteStream } from 'tty'; +import { colors as realColors } from 'playwright-core/lib/utilsBundle'; + +export type TTYParams = { + rows: number; + columns: number; + colorDepth: number; +}; + +function getTTYParams(stream: WriteStream): TTYParams | undefined { + // Explicitly disabled. + if (process.env.PLAYWRIGHT_TTY === 'false' || process.env.PLAYWRIGHT_TTY === '0') + return; + + // Format is COLUMNS[xROWS[xDEPTH]] similar to xvfb screen. + const match = (process.env.PLAYWRIGHT_TTY || '').match(/^(\d+)(?:x(\d+))?(?:x(\d+))?$/); + if (!match && !stream.isTTY) + return; + + // Use an override from PLAYWRIGHT_TTY or the real value for each. + return { + columns: match?.[1] ? +match[1] : (stream.isTTY ? stream.columns : 0), + rows: match?.[2] ? +match[2] : (stream.isTTY ? stream.rows : 0), + colorDepth: match?.[3] ? +match[3] : (stream.isTTY ? (stream.getColorDepth?.() || 8) : 8), + }; +} + +export const stdoutTTY = getTTYParams(process.stdout); +export const stderrTTY = getTTYParams(process.stderr); + +let useColors = !!stdoutTTY; +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 function setTTYParams(stream: WriteStream, params: TTYParams | undefined) { + if (!params) { + stream.isTTY = false; + stream.rows = 0; + stream.columns = 0; + stream.getColorDepth = () => 8; + return; + } + + stream.isTTY = true; + if (params.rows) + stream.rows = params.rows; + if (params.columns) + stream.columns = params.columns; + stream.getColorDepth = () => params.colorDepth; + stream.hasColors = ((count = 16) => { + // count is optional and the first argument may actually be env. + if (typeof count !== 'number') + count = 16; + return count <= 2 ** params.colorDepth; + })as any; + + // Stubs for the rest of the methods to avoid exceptions in user code. + stream.clearLine = (dir: any, callback?: () => void) => { + callback?.(); + return true; + }; + stream.clearScreenDown = (callback?: () => void) => { + callback?.(); + return true; + }; + (stream as any).cursorTo = (x: number, y?: number | (() => void), callback?: () => void) => { + if (callback) + callback(); + else if (y instanceof Function) + y(); + return true; + }; + stream.moveCursor = (dx: number, dy: number, callback?: () => void) => { + callback?.(); + return true; + }; + stream.getWindowSize = () => [stream.columns, stream.rows]; +} + +export function resizeTTY(columns: number, rows: number) { + if (stdoutTTY) { + process.stdout.columns = stdoutTTY.columns = columns; + process.stdout.rows = stdoutTTY.rows = rows; + } + if (stderrTTY) { + process.stderr.columns = stderrTTY.rows = rows; + process.stderr.rows = stderrTTY.columns = columns; + } +} diff --git a/packages/playwright/src/reporters/base.ts b/packages/playwright/src/reporters/base.ts index fff2703208..0e995cc570 100644 --- a/packages/playwright/src/reporters/base.ts +++ b/packages/playwright/src/reporters/base.ts @@ -14,11 +14,14 @@ * limitations under the License. */ -import { colors as realColors, ms as milliseconds, parseStackTraceLine } from 'playwright-core/lib/utilsBundle'; +import { ms as milliseconds, parseStackTraceLine } from 'playwright-core/lib/utilsBundle'; import path from 'path'; import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location } from '../../types/testReporter'; import { getPackageManagerExecCommand } from 'playwright-core/lib/utils'; import type { ReporterV2 } from './reporterV2'; +import { colors, stdoutTTY } from '../common/tty'; + +export { colors } from '../common/tty'; export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' }; export const kOutputSymbol = Symbol('output'); @@ -44,28 +47,6 @@ 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 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 { config!: FullConfig; suite!: Suite; @@ -145,11 +126,11 @@ export class BaseReporter implements ReporterV2 { } protected fitToScreen(line: string, prefix?: string): string { - if (!ttyWidth) { + if (!stdoutTTY?.columns) { // Guard against the case where we cannot determine available width. return line; } - return fitToWidth(line, ttyWidth, prefix); + return fitToWidth(line, stdoutTTY.columns, prefix); } protected generateStartingMessage() { @@ -491,7 +472,7 @@ export function formatError(error: TestError, highlightCode: boolean): ErrorDeta export function separator(text: string = ''): string { if (text) text += ' '; - const columns = Math.min(100, ttyWidth || 100); + const columns = Math.min(100, stdoutTTY?.columns || 100); return text + colors.dim('─'.repeat(Math.max(0, columns - text.length))); } diff --git a/packages/playwright/src/reporters/list.ts b/packages/playwright/src/reporters/list.ts index 11f6a8b755..2fe83d5e88 100644 --- a/packages/playwright/src/reporters/list.ts +++ b/packages/playwright/src/reporters/list.ts @@ -15,8 +15,9 @@ */ import { ms as milliseconds } from 'playwright-core/lib/utilsBundle'; -import { colors, BaseReporter, formatError, formatTestTitle, isTTY, stepSuffix, stripAnsiEscapes, ttyWidth } from './base'; +import { colors, BaseReporter, formatError, formatTestTitle, stepSuffix, stripAnsiEscapes } from './base'; import type { FullResult, Suite, TestCase, TestError, TestResult, TestStep } from '../../types/testReporter'; +import { stdoutTTY } from '../common/tty'; // Allow it in the Visual Studio Code Terminal and the new Windows Terminal const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === 'win32' && process.env.TERM_PROGRAM !== 'vscode' && !process.env.WT_SESSION; @@ -35,7 +36,7 @@ class ListReporter extends BaseReporter { constructor(options: { omitFailures?: boolean, printSteps?: boolean } = {}) { super(options); - this._printSteps = isTTY && (options.printSteps || !!process.env.PW_TEST_DEBUG_REPORTERS_PRINT_STEPS); + this._printSteps = !!stdoutTTY && (options.printSteps || !!process.env.PW_TEST_DEBUG_REPORTERS_PRINT_STEPS); } override printsToStdio() { @@ -53,7 +54,7 @@ class ListReporter extends BaseReporter { override onTestBegin(test: TestCase, result: TestResult) { super.onTestBegin(test, result); - if (!isTTY) + if (!stdoutTTY) return; this._maybeWriteNewLine(); const index = String(this._resultIndex.size + 1); @@ -80,7 +81,7 @@ class ListReporter extends BaseReporter { return; const testIndex = this._resultIndex.get(result) || ''; if (!this._printSteps) { - if (isTTY) + if (stdoutTTY) this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); return; } @@ -90,7 +91,7 @@ class ListReporter extends BaseReporter { const stepIndex = `${testIndex}.${ordinal}`; this._stepIndex.set(step, stepIndex); - if (isTTY) { + if (stdoutTTY) { this._maybeWriteNewLine(); this._stepRows.set(step, this._lastRow); const prefix = this._testPrefix(stepIndex, ''); @@ -106,7 +107,7 @@ class ListReporter extends BaseReporter { const testIndex = this._resultIndex.get(result) || ''; if (!this._printSteps) { - if (isTTY) + if (stdoutTTY) this._updateLine(this._testRows.get(test)!, colors.dim(formatTestTitle(this.config, test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, '')); return; } @@ -133,7 +134,7 @@ class ListReporter extends BaseReporter { private _updateLineCountAndNewLineFlagForOutput(text: string) { this._needNewLine = text[text.length - 1] !== '\n'; - if (!ttyWidth) + if (!stdoutTTY?.columns) return; for (const ch of text) { if (ch === '\n') { @@ -142,7 +143,7 @@ class ListReporter extends BaseReporter { continue; } ++this._lastColumn; - if (this._lastColumn > ttyWidth) { + if (this._lastColumn > stdoutTTY.columns) { this._lastColumn = 0; ++this._lastRow; } @@ -192,7 +193,7 @@ class ListReporter extends BaseReporter { } private _updateOrAppendLine(row: number, text: string, prefix: string) { - if (isTTY) { + if (stdoutTTY) { this._updateLine(row, text, prefix); } else { this._maybeWriteNewLine(); @@ -229,8 +230,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/packages/playwright/src/runner/processHost.ts b/packages/playwright/src/runner/processHost.ts index 2a3e00aa20..186cdc2126 100644 --- a/packages/playwright/src/runner/processHost.ts +++ b/packages/playwright/src/runner/processHost.ts @@ -22,6 +22,7 @@ import type { ProtocolResponse } from '../common/process'; import { execArgvWithExperimentalLoaderOptions } from '../transform/esmUtils'; import { assert } from 'playwright-core/lib/utils'; import { esmLoaderRegistered } from '../common/esmLoaderHost'; +import { stderrTTY, stdoutTTY } from '../common/tty'; export type ProcessExitData = { unexpectedly: boolean; @@ -111,20 +112,7 @@ export class ProcessHost extends EventEmitter { if (error) return error; - const processParams: ProcessInitParams = { - stdoutParams: { - rows: process.stdout.rows, - columns: process.stdout.columns, - colorDepth: process.stdout.getColorDepth?.() || 8 - }, - stderrParams: { - rows: process.stderr.rows, - columns: process.stderr.columns, - colorDepth: process.stderr.getColorDepth?.() || 8 - }, - processName: this._processName - }; - + const processParams: ProcessInitParams = { stdoutTTY, stderrTTY, processName: this._processName }; this.send({ method: '__init__', params: { processParams, diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index d100e04983..ee1b55d3a2 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -39,6 +39,7 @@ import { loadConfig, resolveConfigFile, restartWithExperimentalTsEsm } from '../ import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import type { TraceViewerRedirectOptions, TraceViewerServerOptions } from 'playwright-core/lib/server/trace/viewer/traceViewer'; import type { TestRunnerPluginRegistration } from '../plugins'; +import { resizeTTY } from '../common/tty'; class TestServer { private _configFile: string | undefined; @@ -120,10 +121,7 @@ class TestServerDispatcher implements TestServerInterface { } async resizeTerminal(params: Parameters[0]): ReturnType { - process.stdout.columns = params.cols; - process.stdout.rows = params.rows; - process.stderr.columns = params.cols; - process.stderr.columns = params.rows; + resizeTTY(params.cols, params.rows); } async checkBrowsers(): Promise<{ hasBrowsers: boolean; }> { diff --git a/tests/playwright-test/reporter-blob.spec.ts b/tests/playwright-test/reporter-blob.spec.ts index e12ec3c069..18ba4420ec 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_TTY: '80x25x8' }, { 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..0fcb963b47 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_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_TTY: '80x25' }); 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_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_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_TTY: '50x20' }); 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`); diff --git a/tests/playwright-test/stdio.spec.ts b/tests/playwright-test/stdio.spec.ts index e0609b1ac6..2334f0c8e6 100644 --- a/tests/playwright-test/stdio.spec.ts +++ b/tests/playwright-test/stdio.spec.ts @@ -89,7 +89,7 @@ test('should support console colors', async ({ runInlineTest }) => { console.error({ b: false, n: 123, s: 'abc' }); }); ` - }); + }, {}, { PLAYWRIGHT_TTY: '80' }); expect(result.output).toContain(`process.stdout.isTTY = true`); expect(result.output).toContain(`process.stderr.isTTY = true`); // The output should have colors. @@ -102,17 +102,22 @@ test('should override hasColors and getColorDepth', async ({ runInlineTest }) => 'a.spec.js': ` import { test, expect } from '@playwright/test'; test('console log', () => { - console.log('process.stdout.hasColors(1) = ' + process.stdout.hasColors(1)); - console.log('process.stderr.hasColors(1) = ' + process.stderr.hasColors(1)); - console.log('process.stdout.getColorDepth() > 0 = ' + (process.stdout.getColorDepth() > 0)); - console.log('process.stderr.getColorDepth() > 0 = ' + (process.stderr.getColorDepth() > 0)); + expect(process.stdout.hasColors(1)).toBe(true); + expect(process.stdout.isTTY).toBe(true); + expect(process.stdout.columns).toBe(60); + expect(process.stdout.rows).toBe(15); + expect(process.stdout.getColorDepth()).toBe(16); + + expect(process.stderr.hasColors(1)).toBe(true); + expect(process.stderr.isTTY).toBe(true); + expect(process.stderr.columns).toBe(60); + expect(process.stderr.rows).toBe(15); + expect(process.stderr.getColorDepth()).toBe(16); }); ` - }); - expect(result.output).toContain(`process.stdout.hasColors(1) = true`); - expect(result.output).toContain(`process.stderr.hasColors(1) = true`); - expect(result.output).toContain(`process.stdout.getColorDepth() > 0 = true`); - expect(result.output).toContain(`process.stderr.getColorDepth() > 0 = true`); + }, {}, { PLAYWRIGHT_TTY: '60x15x16' }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); }); test('should not throw type error when using assert', async ({ runInlineTest }) => { @@ -152,6 +157,25 @@ test('should provide stubs for tty.WriteStream methods on process.stdout/stderr' checkMethods(process.stderr); }); ` - }); + }, {}, { PLAYWRIGHT_TTY: '80' }); + expect(result.exitCode).toBe(0); +}); + +test('should respect PLAYWRIGHT_TTY=0', async ({ runInlineTest }) => { + test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29422' }); + + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('passes', () => { + expect(process.stdout.isTTY).toBe(false); + expect(process.stdout.columns).toBe(0); + expect(process.stdout.rows).toBe(0); + expect(process.stderr.isTTY).toBe(false); + expect(process.stderr.columns).toBe(0); + expect(process.stderr.rows).toBe(0); + }); + ` + }, {}, { PLAYWRIGHT_TTY: '0' }); expect(result.exitCode).toBe(0); });