feat(test runner): do not mock tty in the worker process (#30107)
This was historically done to make `console.log()` have colors. However, this makes any other code that checks `process.stdout.isTTY` incorrectly assume real TTY support. Node18 and Node20 now respect `FORCE_COLOR=1` in console, so our default behavior of forcing colors in the worker process just works out of the box. See https://github.com/nodejs/node/pull/48034.
This commit is contained in:
parent
4b3c596874
commit
911d8effb9
|
|
@ -46,15 +46,7 @@ export type SerializedConfig = {
|
||||||
compilationCache?: SerializedCompilationCache;
|
compilationCache?: SerializedCompilationCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TtyParams = {
|
|
||||||
rows: number | undefined;
|
|
||||||
columns: number | undefined;
|
|
||||||
colorDepth: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ProcessInitParams = {
|
export type ProcessInitParams = {
|
||||||
stdoutParams: TtyParams;
|
|
||||||
stderrParams: TtyParams;
|
|
||||||
processName: string;
|
processName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { WriteStream } from 'tty';
|
import type { EnvProducedPayload, ProcessInitParams } from './ipc';
|
||||||
import type { EnvProducedPayload, ProcessInitParams, TtyParams } from './ipc';
|
|
||||||
import { startProfiling, stopProfiling } from 'playwright-core/lib/utils';
|
import { startProfiling, stopProfiling } from 'playwright-core/lib/utils';
|
||||||
import type { TestInfoError } from '../../types/test';
|
import type { TestInfoError } from '../../types/test';
|
||||||
import { serializeError } from '../util';
|
import { serializeError } from '../util';
|
||||||
|
|
@ -67,8 +66,6 @@ const startingEnv = { ...process.env };
|
||||||
process.on('message', async (message: any) => {
|
process.on('message', async (message: any) => {
|
||||||
if (message.method === '__init__') {
|
if (message.method === '__init__') {
|
||||||
const { processParams, runnerParams, runnerScript } = message.params as { processParams: ProcessInitParams, runnerParams: any, runnerScript: string };
|
const { processParams, runnerParams, runnerScript } = message.params as { processParams: ProcessInitParams, runnerParams: any, runnerScript: string };
|
||||||
setTtyParams(process.stdout, processParams.stdoutParams);
|
|
||||||
setTtyParams(process.stderr, processParams.stderrParams);
|
|
||||||
void startProfiling();
|
void startProfiling();
|
||||||
const { create } = require(runnerScript);
|
const { create } = require(runnerScript);
|
||||||
processRunner = create(runnerParams) as ProcessRunner;
|
processRunner = create(runnerParams) as ProcessRunner;
|
||||||
|
|
@ -117,40 +114,3 @@ function sendMessageToParent(message: { method: string, params?: any }) {
|
||||||
// Can throw when closing.
|
// 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];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -112,16 +112,6 @@ export class ProcessHost extends EventEmitter {
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
const processParams: ProcessInitParams = {
|
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
|
processName: this._processName
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,14 @@ test('should ignore stdio when quiet', async ({ runInlineTest }) => {
|
||||||
expect(result.output).not.toContain('%%');
|
expect(result.output).not.toContain('%%');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should support console colors', async ({ runInlineTest }) => {
|
test('should support console colors but not tty', {
|
||||||
|
annotation: [
|
||||||
|
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/15366' },
|
||||||
|
{ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29839' },
|
||||||
|
],
|
||||||
|
}, async ({ runInlineTest, nodeVersion }) => {
|
||||||
|
test.skip(nodeVersion.major < 18, 'Node16 does not respect FORCE_COLOR in onsole');
|
||||||
|
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.spec.js': `
|
'a.spec.js': `
|
||||||
import { test, expect } from '@playwright/test';
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
@ -90,31 +97,13 @@ test('should support console colors', async ({ runInlineTest }) => {
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
});
|
});
|
||||||
expect(result.output).toContain(`process.stdout.isTTY = true`);
|
expect(result.output).toContain(`process.stdout.isTTY = undefined`);
|
||||||
expect(result.output).toContain(`process.stderr.isTTY = true`);
|
expect(result.output).toContain(`process.stderr.isTTY = undefined`);
|
||||||
// The output should have colors.
|
// The output should have colors.
|
||||||
expect(result.rawOutput).toContain(`{ b: \x1b[33mtrue\x1b[39m, n: \x1b[33m123\x1b[39m, s: \x1b[32m'abc'\x1b[39m }`);
|
expect(result.rawOutput).toContain(`{ b: \x1b[33mtrue\x1b[39m, n: \x1b[33m123\x1b[39m, s: \x1b[32m'abc'\x1b[39m }`);
|
||||||
expect(result.rawOutput).toContain(`{ b: \x1b[33mfalse\x1b[39m, n: \x1b[33m123\x1b[39m, s: \x1b[32m'abc'\x1b[39m }`);
|
expect(result.rawOutput).toContain(`{ b: \x1b[33mfalse\x1b[39m, n: \x1b[33m123\x1b[39m, s: \x1b[32m'abc'\x1b[39m }`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should override hasColors and getColorDepth', async ({ runInlineTest }) => {
|
|
||||||
const result = await 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(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`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not throw type error when using assert', async ({ runInlineTest }) => {
|
test('should not throw type error when using assert', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.spec.js': `
|
'a.spec.js': `
|
||||||
|
|
@ -128,30 +117,3 @@ test('should not throw type error when using assert', async ({ runInlineTest })
|
||||||
expect(result.output).not.toContain(`TypeError: process.stderr.hasColors is not a function`);
|
expect(result.output).not.toContain(`TypeError: process.stderr.hasColors is not a function`);
|
||||||
expect(result.output).toContain(`AssertionError`);
|
expect(result.output).toContain(`AssertionError`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should provide stubs for tty.WriteStream methods on process.stdout/stderr', async ({ runInlineTest }) => {
|
|
||||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29839' });
|
|
||||||
const result = await runInlineTest({
|
|
||||||
'a.spec.ts': `
|
|
||||||
import { test, expect } from '@playwright/test';
|
|
||||||
import type * as tty from 'tty';
|
|
||||||
async function checkMethods(stream: tty.WriteStream) {
|
|
||||||
expect(stream.isTTY).toBe(true);
|
|
||||||
await new Promise<void>(r => stream.clearLine(-1, r));;
|
|
||||||
await new Promise<void>(r => stream.clearScreenDown(r));;
|
|
||||||
await new Promise<void>(r => stream.cursorTo(0, 0, r));;
|
|
||||||
await new Promise<void>(r => stream.cursorTo(0, r));;
|
|
||||||
await new Promise<void>(r => stream.moveCursor(1, 1, r));;
|
|
||||||
expect(stream.getWindowSize()).toEqual([stream.columns, stream.rows]);
|
|
||||||
// getColorDepth() and hasColors() are covered in other tests.
|
|
||||||
}
|
|
||||||
test('process.stdout implementd tty.WriteStream methods', () => {
|
|
||||||
checkMethods(process.stdout);
|
|
||||||
});
|
|
||||||
test('process.stderr implementd tty.WriteStream methods', () => {
|
|
||||||
checkMethods(process.stderr);
|
|
||||||
});
|
|
||||||
`
|
|
||||||
});
|
|
||||||
expect(result.exitCode).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue