chore: allow calling spawnSync on Node.js file inside test (#24539)
Fixes https://github.com/microsoft/playwright/issues/24516 Relates https://github.com/microsoft/playwright/pull/16733
This commit is contained in:
parent
8e2f33673b
commit
2193903d03
|
|
@ -21,7 +21,7 @@ import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { Runner } from './runner/runner';
|
import { Runner } from './runner/runner';
|
||||||
import { stopProfiling, startProfiling, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils';
|
import { stopProfiling, startProfiling, gracefullyProcessExitDoNotHang } from 'playwright-core/lib/utils';
|
||||||
import { experimentalLoaderOption, fileIsModule, serializeError } from './util';
|
import { execArgvWithoutExperimentalLoaderOptions, execArgvWithExperimentalLoaderOptions, fileIsModule, serializeError } from './util';
|
||||||
import { showHTMLReport } from './reporters/html';
|
import { showHTMLReport } from './reporters/html';
|
||||||
import { createMergedReport } from './reporters/merge';
|
import { createMergedReport } from './reporters/merge';
|
||||||
import { ConfigLoader, resolveConfigFile } from './common/configLoader';
|
import { ConfigLoader, resolveConfigFile } from './common/configLoader';
|
||||||
|
|
@ -275,17 +275,19 @@ function restartWithExperimentalTsEsm(configFile: string | null): boolean {
|
||||||
return false;
|
return false;
|
||||||
if (process.env.PW_DISABLE_TS_ESM)
|
if (process.env.PW_DISABLE_TS_ESM)
|
||||||
return false;
|
return false;
|
||||||
if (process.env.PW_TS_ESM_ON)
|
if (process.env.PW_TS_ESM_ON) {
|
||||||
|
// clear execArgv after restart, so that childProcess.fork in user code does not inherit our loader.
|
||||||
|
process.execArgv = execArgvWithoutExperimentalLoaderOptions();
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
if (!fileIsModule(configFile))
|
if (!fileIsModule(configFile))
|
||||||
return false;
|
return false;
|
||||||
const NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + experimentalLoaderOption();
|
const innerProcess = (require('child_process') as typeof import('child_process')).fork(require.resolve('./cli'), process.argv.slice(2), {
|
||||||
const innerProcess = require('child_process').fork(require.resolve('./cli'), process.argv.slice(2), {
|
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
NODE_OPTIONS,
|
|
||||||
PW_TS_ESM_ON: '1',
|
PW_TS_ESM_ON: '1',
|
||||||
}
|
},
|
||||||
|
execArgv: execArgvWithExperimentalLoaderOptions(),
|
||||||
});
|
});
|
||||||
|
|
||||||
innerProcess.on('close', (code: number | null) => {
|
innerProcess.on('close', (code: number | null) => {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import type { WriteStream } from 'tty';
|
||||||
import type { EnvProducedPayload, ProcessInitParams, TtyParams } 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 { execArgvWithoutExperimentalLoaderOptions, serializeError } from '../util';
|
||||||
|
|
||||||
export type ProtocolRequest = {
|
export type ProtocolRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -51,6 +51,9 @@ process.on('disconnect', gracefullyCloseAndExit);
|
||||||
process.on('SIGINT', () => {});
|
process.on('SIGINT', () => {});
|
||||||
process.on('SIGTERM', () => {});
|
process.on('SIGTERM', () => {});
|
||||||
|
|
||||||
|
// Clear execArgv immediately, so that the user-code does not inherit our loader.
|
||||||
|
process.execArgv = execArgvWithoutExperimentalLoaderOptions();
|
||||||
|
|
||||||
let processRunner: ProcessRunner;
|
let processRunner: ProcessRunner;
|
||||||
let processName: string;
|
let processName: string;
|
||||||
const startingEnv = { ...process.env };
|
const startingEnv = { ...process.env };
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import { raceAgainstDeadline, launchProcess, httpRequest, monotonicTime } from '
|
||||||
import type { FullConfig } from '../../types/testReporter';
|
import type { FullConfig } from '../../types/testReporter';
|
||||||
import type { TestRunnerPlugin } from '.';
|
import type { TestRunnerPlugin } from '.';
|
||||||
import type { FullConfigInternal } from '../common/config';
|
import type { FullConfigInternal } from '../common/config';
|
||||||
import { envWithoutExperimentalLoaderOptions } from '../util';
|
|
||||||
import type { ReporterV2 } from '../reporters/reporterV2';
|
import type { ReporterV2 } from '../reporters/reporterV2';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -93,7 +92,6 @@ export class WebServerPlugin implements TestRunnerPlugin {
|
||||||
command: this._options.command,
|
command: this._options.command,
|
||||||
env: {
|
env: {
|
||||||
...DEFAULT_ENVIRONMENT_VARIABLES,
|
...DEFAULT_ENVIRONMENT_VARIABLES,
|
||||||
...envWithoutExperimentalLoaderOptions(),
|
|
||||||
...this._options.env,
|
...this._options.env,
|
||||||
},
|
},
|
||||||
cwd: this._options.cwd,
|
cwd: this._options.cwd,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { EventEmitter } from 'events';
|
||||||
import { debug } from 'playwright-core/lib/utilsBundle';
|
import { debug } from 'playwright-core/lib/utilsBundle';
|
||||||
import type { EnvProducedPayload, ProcessInitParams } from '../common/ipc';
|
import type { EnvProducedPayload, ProcessInitParams } from '../common/ipc';
|
||||||
import type { ProtocolResponse } from '../common/process';
|
import type { ProtocolResponse } from '../common/process';
|
||||||
|
import { execArgvWithExperimentalLoaderOptions } from '../util';
|
||||||
|
|
||||||
export type ProcessExitData = {
|
export type ProcessExitData = {
|
||||||
unexpectedly: boolean;
|
unexpectedly: boolean;
|
||||||
|
|
@ -50,6 +51,7 @@ export class ProcessHost extends EventEmitter {
|
||||||
detached: false,
|
detached: false,
|
||||||
env: { ...process.env, ...this._extraEnv },
|
env: { ...process.env, ...this._extraEnv },
|
||||||
stdio: inheritStdio ? ['ignore', 'inherit', 'inherit', 'ipc'] : ['ignore', 'ignore', process.env.PW_RUNNER_DEBUG ? 'inherit' : 'ignore', 'ipc'],
|
stdio: inheritStdio ? ['ignore', 'inherit', 'inherit', 'ipc'] : ['ignore', 'ignore', process.env.PW_RUNNER_DEBUG ? 'inherit' : 'ignore', 'ipc'],
|
||||||
|
...(process.env.PW_TS_ESM_ON ? { execArgv: execArgvWithExperimentalLoaderOptions() } : {}),
|
||||||
});
|
});
|
||||||
this.process.on('exit', (code, signal) => {
|
this.process.on('exit', (code, signal) => {
|
||||||
this.didExit = true;
|
this.didExit = true;
|
||||||
|
|
|
||||||
|
|
@ -303,16 +303,20 @@ function folderIsModule(folder: string): boolean {
|
||||||
return require(packageJsonPath).type === 'module';
|
return require(packageJsonPath).type === 'module';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function experimentalLoaderOption() {
|
const kExperimentalLoaderOptions = [
|
||||||
return ` --no-warnings --experimental-loader=${url.pathToFileURL(require.resolve('@playwright/test/lib/transform/esmLoader')).toString()}`;
|
'--no-warnings',
|
||||||
|
`--experimental-loader=${url.pathToFileURL(require.resolve('@playwright/test/lib/transform/esmLoader')).toString()}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
export function execArgvWithExperimentalLoaderOptions() {
|
||||||
|
return [
|
||||||
|
...process.execArgv,
|
||||||
|
...kExperimentalLoaderOptions,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function envWithoutExperimentalLoaderOptions(): NodeJS.ProcessEnv {
|
export function execArgvWithoutExperimentalLoaderOptions() {
|
||||||
const substring = experimentalLoaderOption();
|
return process.execArgv.filter(arg => !kExperimentalLoaderOptions.includes(arg));
|
||||||
const result = { ...process.env };
|
|
||||||
if (result.NODE_OPTIONS)
|
|
||||||
result.NODE_OPTIONS = result.NODE_OPTIONS.replace(substring, '').trim() || undefined;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This follows the --moduleResolution=bundler strategy from tsc.
|
// This follows the --moduleResolution=bundler strategy from tsc.
|
||||||
|
|
|
||||||
|
|
@ -547,3 +547,88 @@ test('should disallow ESM when config is cjs', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(1);
|
expect(result.exitCode).toBe(1);
|
||||||
expect(result.output).toContain('Unknown file extension ".ts"');
|
expect(result.output).toContain('Unknown file extension ".ts"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should be able to use use execSync with a Node.js file inside a spec', async ({ runInlineTest }) => {
|
||||||
|
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/24516' });
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'global-setup.ts': `
|
||||||
|
import { execSync, spawnSync, fork } from 'child_process';
|
||||||
|
console.log('%%global-setup import level');
|
||||||
|
console.log('%%execSync: ' + execSync('node hello.js').toString());
|
||||||
|
console.log('%%spawnSync: ' + spawnSync('node', ['hello.js']).stdout.toString());
|
||||||
|
export default async () => {
|
||||||
|
console.log('%%global-setup export level');
|
||||||
|
console.log('%%execSync: ' + execSync('node hello.js').toString());
|
||||||
|
console.log('%%spawnSync: ' + spawnSync('node', ['hello.js']).stdout.toString());
|
||||||
|
const child = fork('hellofork.js');
|
||||||
|
child.on('message', (m) => console.log('%%fork: ' + m));
|
||||||
|
await new Promise((resolve) => child.on('exit', (code) => resolve(code)));
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'global-teardown.ts': `
|
||||||
|
import { execSync, spawnSync, fork } from 'child_process';
|
||||||
|
console.log('%%global-teardown import level');
|
||||||
|
console.log('%%execSync: ' + execSync('node hello.js').toString());
|
||||||
|
console.log('%%spawnSync: ' + spawnSync('node', ['hello.js']).stdout.toString());
|
||||||
|
export default async () => {
|
||||||
|
console.log('%%global-teardown export level');
|
||||||
|
console.log('%%execSync: ' + execSync('node hello.js').toString());
|
||||||
|
console.log('%%spawnSync: ' + spawnSync('node', ['hello.js']).stdout.toString());
|
||||||
|
const child = fork('hellofork.js');
|
||||||
|
child.on('message', (m) => console.log('%%fork: ' + m));
|
||||||
|
await new Promise((resolve) => child.on('exit', (code) => resolve(code)));
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'package.json': `{ "type": "module" }`,
|
||||||
|
'playwright.config.ts': `export default {
|
||||||
|
projects: [{name: 'foo'}],
|
||||||
|
globalSetup: './global-setup.ts',
|
||||||
|
globalTeardown: './global-teardown.ts',
|
||||||
|
};`,
|
||||||
|
'hello.js': `console.log('hello from hello.js');`,
|
||||||
|
'hellofork.js': `process.send('hello from hellofork.js');`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { execSync, spawnSync, fork } from 'child_process';
|
||||||
|
console.log('%%inside test file');
|
||||||
|
console.log('%%execSync: ' + execSync('node hello.js').toString());
|
||||||
|
console.log('%%spawnSync: ' + spawnSync('node', ['hello.js']).stdout.toString());
|
||||||
|
test('check project name', async ({}) => {
|
||||||
|
console.log('%%inside test');
|
||||||
|
console.log('%%execSync: ' + execSync('node hello.js').toString());
|
||||||
|
console.log('%%spawnSync: ' + spawnSync('node', ['hello.js']).stdout.toString());
|
||||||
|
const child = fork('hellofork.js');
|
||||||
|
child.on('message', (m) => console.log('%%fork: ' + m));
|
||||||
|
await new Promise((resolve) => child.on('exit', (code) => resolve(code)));
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.outputLines).toEqual([
|
||||||
|
'global-setup import level',
|
||||||
|
'execSync: hello from hello.js',
|
||||||
|
'spawnSync: hello from hello.js',
|
||||||
|
'global-teardown import level',
|
||||||
|
'execSync: hello from hello.js',
|
||||||
|
'spawnSync: hello from hello.js',
|
||||||
|
'global-setup export level',
|
||||||
|
'execSync: hello from hello.js',
|
||||||
|
'spawnSync: hello from hello.js',
|
||||||
|
'fork: hello from hellofork.js',
|
||||||
|
'inside test file',
|
||||||
|
'execSync: hello from hello.js',
|
||||||
|
'spawnSync: hello from hello.js',
|
||||||
|
'inside test file',
|
||||||
|
'execSync: hello from hello.js',
|
||||||
|
'spawnSync: hello from hello.js',
|
||||||
|
'inside test',
|
||||||
|
'execSync: hello from hello.js',
|
||||||
|
'spawnSync: hello from hello.js',
|
||||||
|
'fork: hello from hellofork.js',
|
||||||
|
'global-teardown export level',
|
||||||
|
'execSync: hello from hello.js',
|
||||||
|
'spawnSync: hello from hello.js',
|
||||||
|
'fork: hello from hellofork.js',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue