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:
parent
3370f37e7b
commit
89cdf3d56e
|
|
@ -45,27 +45,41 @@ type TestSummary = {
|
||||||
fatalErrors: TestError[];
|
fatalErrors: TestError[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isTTY = !!process.env.PWTEST_TTY_WIDTH || process.stdout.isTTY;
|
export const { isTTY, ttyWidth, colors } = (() => {
|
||||||
export const ttyWidth = process.env.PWTEST_TTY_WIDTH ? parseInt(process.env.PWTEST_TTY_WIDTH, 10) : process.stdout.columns || 0;
|
let isTTY = !!process.stdout.isTTY;
|
||||||
let useColors = isTTY;
|
let ttyWidth = process.stdout.columns || 0;
|
||||||
if (process.env.DEBUG_COLORS === '0'
|
if (process.env.PLAYWRIGHT_FORCE_TTY === 'false' || process.env.PLAYWRIGHT_FORCE_TTY === '0') {
|
||||||
|| process.env.DEBUG_COLORS === 'false'
|
isTTY = false;
|
||||||
|| process.env.FORCE_COLOR === '0'
|
ttyWidth = 0;
|
||||||
|| process.env.FORCE_COLOR === 'false')
|
} else if (process.env.PLAYWRIGHT_FORCE_TTY === 'true' || process.env.PLAYWRIGHT_FORCE_TTY === '1') {
|
||||||
useColors = false;
|
isTTY = true;
|
||||||
else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
|
ttyWidth = process.stdout.columns || 100;
|
||||||
useColors = true;
|
} 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 : {
|
let useColors = isTTY;
|
||||||
bold: (t: string) => t,
|
if (process.env.DEBUG_COLORS === '0' || process.env.DEBUG_COLORS === 'false' ||
|
||||||
cyan: (t: string) => t,
|
process.env.FORCE_COLOR === '0' || process.env.FORCE_COLOR === 'false')
|
||||||
dim: (t: string) => t,
|
useColors = false;
|
||||||
gray: (t: string) => t,
|
else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
|
||||||
green: (t: string) => t,
|
useColors = true;
|
||||||
red: (t: string) => t,
|
|
||||||
yellow: (t: string) => t,
|
const colors = useColors ? realColors : {
|
||||||
enabled: false,
|
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 {
|
export class BaseReporter implements ReporterV2 {
|
||||||
config!: FullConfig;
|
config!: FullConfig;
|
||||||
|
|
|
||||||
|
|
@ -229,8 +229,6 @@ class ListReporter extends BaseReporter {
|
||||||
// Go down if needed.
|
// Go down if needed.
|
||||||
if (row !== this._lastRow)
|
if (row !== this._lastRow)
|
||||||
process.stdout.write(`\u001B[${this._lastRow - row}E`);
|
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) {
|
private _testPrefix(index: string, statusMark: string) {
|
||||||
|
|
|
||||||
|
|
@ -441,7 +441,7 @@ test('merge into list report by default', async ({ runInlineTest, mergeReports }
|
||||||
const reportFiles = await fs.promises.readdir(reportDir);
|
const reportFiles = await fs.promises.readdir(reportDir);
|
||||||
reportFiles.sort();
|
reportFiles.sort();
|
||||||
expect(reportFiles).toEqual(['report-1.zip', 'report-2.zip', 'report-3.zip']);
|
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);
|
expect(exitCode).toBe(0);
|
||||||
|
|
||||||
const text = stripAnsi(output);
|
const text = stripAnsi(output);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* 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 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' : '✓ ';
|
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 text = result.output;
|
||||||
const lines = text.split('\n').filter(l => l.match(/^\d :/)).map(l => l.replace(/[.\d]+m?s/, 'Xms'));
|
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.
|
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 () => {});
|
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 text = result.output;
|
||||||
const lines = text.split('\n').filter(l => l.match(/^\d :/)).map(l => l.replace(/[.\d]+m?s/, 'Xms'));
|
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.
|
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));
|
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);
|
const renderedText = simpleAnsiRenderer(result.rawOutput, TTY_WIDTH);
|
||||||
if (process.platform === 'win32')
|
if (process.platform === 'win32')
|
||||||
|
|
@ -154,7 +154,7 @@ for (const useIntermediateMergeReport of [false, true] as const) {
|
||||||
expect(testInfo.retry).toBe(1);
|
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 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'));
|
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 () => {
|
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);
|
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.every(line => line.length <= 50)).toBe(true);
|
||||||
|
|
||||||
expect(lines[0]).toBe(` 1 …a.test.ts:3:15 › failure in very long name`);
|
expect(lines[0]).toBe(` 1 …a.test.ts:3:15 › failure in very long name`);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue