fix(list): avoid overwriting stdio logs from tests when writing status (#36219)

This commit is contained in:
Adam Gastineau 2025-06-16 04:38:15 -07:00 committed by GitHub
parent a02722a2f6
commit 6caf3442fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 144 additions and 12 deletions

View File

@ -102,7 +102,7 @@ List report supports the following configuration options and environment variabl
| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_LIST_PRINT_STEPS` | `printSteps` | Whether to print each step on its own line. | `false`
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. If a number is specified, it will also be used as the terminal width. | `true` when terminal is in TTY mode, `false` otherwise.
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
@ -140,7 +140,7 @@ Line report supports the following configuration options and environment variabl
| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. If a number is specified, it will also be used as the terminal width. | `true` when terminal is in TTY mode, `false` otherwise.
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
@ -182,7 +182,7 @@ Dot report supports the following configuration options and environment variable
| Environment Variable Name | Reporter Config Option| Description | Default
|---|---|---|---|
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. If a number is specified, it will also be used as the terminal width. | `true` when terminal is in TTY mode, `false` otherwise.
| `PLAYWRIGHT_FORCE_TTY` | | Whether to produce output suitable for a live terminal. Supports `true`, `1`, `false`, `0`, `[WIDTH]`, and `[WIDTH]x[HEIGHT]`. `[WIDTH]` and `[WIDTH]x[HEIGHT]` specifies the TTY dimensions. | `true` when terminal is in TTY mode, `false` otherwise.
| `FORCE_COLOR` | | Whether to produce colored output. | `true` when terminal is in TTY mode, `false` otherwise.
### HTML reporter

View File

@ -58,23 +58,39 @@ export type Screen = {
colors: Colors;
isTTY: boolean;
ttyWidth: number;
ttyHeight: number;
};
const DEFAULT_TTY_WIDTH = 100;
const DEFAULT_TTY_HEIGHT = 40;
// Output goes to terminal.
export const terminalScreen: Screen = (() => {
let isTTY = !!process.stdout.isTTY;
let ttyWidth = process.stdout.columns || 0;
let ttyHeight = process.stdout.rows || 0;
if (process.env.PLAYWRIGHT_FORCE_TTY === 'false' || process.env.PLAYWRIGHT_FORCE_TTY === '0') {
isTTY = false;
ttyWidth = 0;
ttyHeight = 0;
} else if (process.env.PLAYWRIGHT_FORCE_TTY === 'true' || process.env.PLAYWRIGHT_FORCE_TTY === '1') {
isTTY = true;
ttyWidth = process.stdout.columns || 100;
ttyWidth = process.stdout.columns || DEFAULT_TTY_WIDTH;
ttyHeight = process.stdout.rows || DEFAULT_TTY_HEIGHT;
} else if (process.env.PLAYWRIGHT_FORCE_TTY) {
isTTY = true;
ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY;
const sizeMatch = process.env.PLAYWRIGHT_FORCE_TTY.match(/^(\d+)x(\d+)$/);
if (sizeMatch) {
ttyWidth = +sizeMatch[1];
ttyHeight = +sizeMatch[2];
} else {
ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY;
ttyHeight = DEFAULT_TTY_HEIGHT;
}
if (isNaN(ttyWidth))
ttyWidth = 100;
ttyWidth = DEFAULT_TTY_WIDTH;
if (isNaN(ttyHeight))
ttyHeight = DEFAULT_TTY_HEIGHT;
}
let useColors = isTTY;
@ -89,6 +105,7 @@ export const terminalScreen: Screen = (() => {
resolveFiles: 'cwd',
isTTY,
ttyWidth,
ttyHeight,
colors
};
})();
@ -98,6 +115,7 @@ export const nonTerminalScreen: Screen = {
colors: terminalScreen.colors,
isTTY: false,
ttyWidth: 0,
ttyHeight: 0,
resolveFiles: 'rootDir',
};
@ -106,6 +124,7 @@ export const internalScreen: Screen = {
colors: realColors,
isTTY: false,
ttyWidth: 0,
ttyHeight: 0,
resolveFiles: 'rootDir',
};

View File

@ -102,7 +102,7 @@ class ListReporter extends TerminalReporter {
const line = test.title + this.screen.colors.dim(stepSuffix(step));
this._appendLine(line, prefix);
} else {
this._updateLine(this._testRows.get(test)!, this.screen.colors.dim(this.formatTestTitle(test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, ''));
this._updateOrAppendLine(this._testRows, test, this.screen.colors.dim(this.formatTestTitle(test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, ''));
}
}
@ -113,7 +113,7 @@ class ListReporter extends TerminalReporter {
const testIndex = this._resultIndex.get(result) || '';
if (!this._printSteps) {
if (this.screen.isTTY)
this._updateLine(this._testRows.get(test)!, this.screen.colors.dim(this.formatTestTitle(test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, ''));
this._updateOrAppendLine(this._testRows, test, this.screen.colors.dim(this.formatTestTitle(test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, ''));
return;
}
@ -127,7 +127,7 @@ class ListReporter extends TerminalReporter {
text = title;
text += this.screen.colors.dim(` (${milliseconds(step.duration)})`);
this._updateOrAppendLine(this._stepRows.get(step)!, text, prefix);
this._updateOrAppendLine(this._stepRows, step, text, prefix);
}
private _maybeWriteNewLine() {
@ -196,14 +196,17 @@ class ListReporter extends TerminalReporter {
text += this._retrySuffix(result) + this.screen.colors.dim(` (${milliseconds(result.duration)})`);
}
this._updateOrAppendLine(this._testRows.get(test)!, text, prefix);
this._updateOrAppendLine(this._testRows, test, text, prefix);
}
private _updateOrAppendLine(row: number, text: string, prefix: string) {
if (this.screen.isTTY) {
private _updateOrAppendLine<T>(entityRowNumbers: Map<T, number>, entity: T, text: string, prefix: string) {
const row = entityRowNumbers.get(entity);
// Only update the line if we assume that the line is still on the screen
if (row !== undefined && this.screen.isTTY && this._lastRow - row < this.screen.ttyHeight) {
this._updateLine(row, text, prefix);
} else {
this._maybeWriteNewLine();
entityRowNumbers.set(entity, this._lastRow);
this._appendLine(text, prefix);
}
}

View File

@ -303,6 +303,116 @@ for (const useIntermediateMergeReport of [false, true] as const) {
for (let i = 0; i < expected.length; ++i)
expect(lines[firstIndex + i]).toContain(expected[i]);
});
test('should update test status row only when TTY has not scrolled', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('A', async ({}) => {
for (let i = 0; i < 20; ++i) {
console.log('line ' + i);
}
});
test('B', async ({}) => {
// Go past end of the screen
for (let i = 20; i < 60; ++i) {
console.log('line ' + i);
}
// Should create new line
await test.step('First step', async () => {
console.log('step 1');
});
for (let i = 60; i < 80; ++i) {
console.log('line ' + i);
}
// Should update the new (not original) line
await test.step('Second step', async () => {
console.log('step 2');
});
});
`,
}, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_FORCE_TTY: '80' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(2);
const expected = [
'#0 : 1 a.test.ts:3:15 A',
];
for (let i = 0; i < 20; ++i)
expected.push(`line ${i}`);
// Update to initial test status row
expected.push(`#0 : ${POSITIVE_STATUS_MARK} 1 a.test.ts:3:15 A`);
expected.push(`#21 : 2 a.test.ts:9:15 B`);
for (let i = 20; i < 60; ++i)
expected.push(`line ${i}`);
expected.push(`#62 : 2 a.test.ts:9:15 B First step`);
expected.push(`step 1`);
expected.push(`#62 : 2 a.test.ts:9:15 B`);
for (let i = 60; i < 80; ++i)
expected.push(`line ${i}`);
expected.push(`#62 : 2 a.test.ts:9:15 B Second step`);
expected.push(`step 2`);
expected.push(`#62 : 2 a.test.ts:9:15 B`);
expected.push(`#62 : ${POSITIVE_STATUS_MARK} 2 a.test.ts:9:15 B`);
const lines = result.output.split('\n');
const firstIndex = lines.indexOf(expected[0]);
expect(firstIndex, 'first line should be there').not.toBe(-1);
for (let i = 0; i < expected.length; ++i)
expect(lines[firstIndex + i]).toContain(expected[i]);
});
test('should update test status row only within configured TTY height', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('A', async ({}) => {
// No scroll
for (let i = 0; i < 60; ++i) {
console.log('line ' + i);
}
// Update original line
await test.step('First step', async () => {
console.log('step 1');
});
for (let i = 60; i < 120; ++i) {
console.log('line ' + i);
}
// Should create new line
await test.step('Second step', async () => {
console.log('step 2');
});
});
`,
}, { reporter: 'list' }, { PW_TEST_DEBUG_REPORTERS: '1', PLAYWRIGHT_FORCE_TTY: '80x80' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
const expected = [
'#0 : 1 a.test.ts:3:15 A',
];
for (let i = 0; i < 60; ++i)
expected.push(`line ${i}`);
// Update to initial test status row
expected.push(`#0 : 1 a.test.ts:3:15 A First step`);
expected.push(`step 1`);
expected.push(`#0 : 1 a.test.ts:3:15 A`);
for (let i = 60; i < 120; ++i)
expected.push(`line ${i}`);
expected.push(`#122 : 1 a.test.ts:3:15 A Second step`);
expected.push(`step 2`);
expected.push(`#122 : 1 a.test.ts:3:15 A`);
expected.push(`#122 : ${POSITIVE_STATUS_MARK} 1 a.test.ts:3:15 A`);
const lines = result.output.split('\n');
const firstIndex = lines.indexOf(expected[0]);
expect(firstIndex, 'first line should be there').not.toBe(-1);
for (let i = 0; i < expected.length; ++i)
expect(lines[firstIndex + i]).toContain(expected[i]);
});
});
}