feat: env.PLAYWRIGHT_FORCE_TTY to control reporters' tty (#30834)

Previously, terminal reporters consulted `process.stdout.isTTY`. Now it
is possible to control the tty behavior:
- `PLAYWRIGHT_FORCE_TTY=0` or `PLAYWRIGHT_FORCE_TTY=false` to disable
TTY;
- `PLAYWRIGHT_FORCE_TTY=1` or `PLAYWRIGHT_FORCE_TTY=true` to enable TTY,
defaults to 100 columns when real columns are unavailable;
- `PLAYWRIGHT_FORCE_TTY=<number>` to force enable TTY and set the
columns.

Fixes #29422.
This commit is contained in:
Dmitry Gozman 2024-05-15 15:01:52 -07:00 committed by GitHub
parent 3370f37e7b
commit 89cdf3d56e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 42 additions and 30 deletions

View File

@ -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;

View File

@ -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) {

View File

@ -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);

View File

@ -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`);