Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 48 additions & 7 deletions packages/playwright/src/reporters/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
* limitations under the License.
*/

import { StringDecoder } from 'string_decoder';

import { msToString } from '@isomorphic/formatUtils';
import { getAsBooleanFromENV } from '@utils/env';

Expand Down Expand Up @@ -42,6 +44,7 @@ class ListReporter extends TerminalReporter {
private _printWorkerIndex: boolean;
private _failureIndex = new Map<TestCase, number>();
private _paused = new Set<TestResult>();
private _stringDecoders = new Map<string, StringDecoder>();

constructor(options?: ListReporterOptions & CommonReporterOptions & TerminalReporterOptions) {
const printFailuresInline = getAsBooleanFromENV('PLAYWRIGHT_LIST_PRINT_FAILURES_INLINE', options?.printFailuresInline);
Expand Down Expand Up @@ -167,11 +170,28 @@ class ListReporter extends TerminalReporter {
private _dumpToStdio(chunk: string | Buffer, stream: NodeJS.WriteStream, result: TestResult | undefined) {
if (this.config.quiet)
return;
let text = chunk.toString('utf-8');
if (result)
text = this._prefixLines(text, this._workerPrefix(result), !this._needNewLine);
this._updateLineCountAndNewLineFlagForOutput(text);
stream.write(text);
if (chunk.length === 0) {
this._updateLineCountAndNewLineFlagForOutput('');
return;
}
const decoder = this._getStringDecoder(stream, result);
const text = decoder.write(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
if (!text)
return;
const prefix = result ? this._workerPrefix(result) : '';
const prefixedText = this._prefixLines(text, prefix, !this._needNewLine);
this._updateLineCountAndNewLineFlagForOutput(prefixedText);
stream.write(prefixedText);
}

private _getStringDecoder(stream: NodeJS.WriteStream, result: TestResult | undefined): StringDecoder {
const key = `${result?.workerIndex ?? -1}:${stream === this.screen.stdout ? 'out' : 'err'}`;
let decoder = this._stringDecoders.get(key);
if (!decoder) {
decoder = new StringDecoder('utf-8');
this._stringDecoders.set(key, decoder);
}
return decoder;
}

private _prefixLines(text: string, prefix: string, atLineStart: boolean) {
Expand Down Expand Up @@ -305,10 +325,14 @@ class ListReporter extends TerminalReporter {
}

private _workerPrefix(result: TestResult) {
return this._workerPrefixFromIndex(result.workerIndex);
}

private _workerPrefixFromIndex(workerIndex: number | undefined) {
if (!this._printWorkerIndex)
return '';
const workerIndex = result.workerIndex >= 0 ? String(result.workerIndex) : ' ';
return this.screen.colors.dim(`[${workerIndex}]`) + ' ';
const indexStr = workerIndex !== undefined && workerIndex >= 0 ? String(workerIndex) : ' ';
return this.screen.colors.dim(`[${indexStr}]`) + ' ';
}

private _testPrefix(result: TestResult, index: string, statusMark: string) {
Expand All @@ -330,6 +354,23 @@ class ListReporter extends TerminalReporter {
}

override async onEnd(result: FullResult) {
for (const [key, decoder] of this._stringDecoders) {
const remaining = decoder.end();
if (remaining) {
const stream = key.endsWith(':out') ? this.screen.stdout : this.screen.stderr;
if (this._printWorkerIndex) {
const workerIndex = Number(key.split(':')[0]);
const prefix = this._workerPrefixFromIndex(workerIndex);
const prefixed = this._prefixLines(remaining, prefix, !this._needNewLine);
this._updateLineCountAndNewLineFlagForOutput(prefixed);
stream.write(prefixed);
} else {
this._updateLineCountAndNewLineFlagForOutput(remaining);
stream.write(remaining);
}
}
}
this._stringDecoders.clear();
await super.onEnd(result);
this.screen.stdout.write('\n');
this.epilogue(!this._printFailuresInline);
Expand Down
35 changes: 35 additions & 0 deletions tests/playwright-test/reporter-list.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,41 @@ for (const useIntermediateMergeReport of [false, true] as const) {
expect(lines[firstIndex + i]).toContain(expected[i]);
});

test('print stdio with split multi-byte characters', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test } from '@playwright/test';
test('passes', async ({}) => {
await new Promise(resolve => process.stdout.write(Buffer.from([0xe2, 0x82]), () => resolve()));
await new Promise(resolve => process.stdout.write(Buffer.from([0xac, 0x0a]), () => resolve()));
});
`,
}, { reporter: 'list' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
expect(result.output).toContain('€');
expect(result.output).not.toContain('�');
});

test('print worker index with split multi-byte characters', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': `
module.exports = { reporter: [['list', { printWorkerIndex: true }]] };
`,
'a.test.ts': `
import { test } from '@playwright/test';
test('passes', async ({}) => {
await new Promise(resolve => process.stdout.write(Buffer.from([0xe2, 0x82]), () => resolve()));
await new Promise(resolve => process.stdout.write(Buffer.from([0xac, 0x0a]), () => resolve()));
});
`,
}, { workers: 1 });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
expect(result.output).toContain('[0] €');
expect(result.output).not.toContain('�');
});

test('should update test status row only when TTY has not scrolled', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
Expand Down