chore: an iteration on test server (#29589)

This commit is contained in:
Pavel Feldman 2024-02-21 09:27:48 -08:00 committed by GitHub
parent 7c9ca0c7ef
commit ba72f7e429
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 115 additions and 53 deletions

View File

@ -83,6 +83,14 @@ export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: R
return taskRunner;
}
export function createTaskRunnerForTestServer(config: FullConfigInternal, reporter: ReporterV2): TaskRunner<TestRun> {
const taskRunner = new TaskRunner<TestRun>(reporter, 0);
addGlobalSetupTasks(taskRunner, config);
taskRunner.addTask('load tests', createLoadTask('out-of-process', { filterOnly: true, failOnLoadErrors: false, doNotRunTestsOutsideProjectFilter: true }));
addRunTasks(taskRunner, config);
return taskRunner;
}
function addGlobalSetupTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal) {
if (!config.configCLIOverrides.preserveOutputDir && !process.env.PW_TEST_NO_REMOVE_OUTPUT_DIRS)
taskRunner.addTask('clear output', createRemoveOutputDirsTask());

View File

@ -24,7 +24,7 @@ import { loadConfigFromFile } from '../common/configLoader';
import { InternalReporter } from '../reporters/internalReporter';
import { Multiplexer } from '../reporters/multiplexer';
import { createReporters } from './reporters';
import { TestRun, createTaskRunnerForList, createTaskRunnerForWatch } from './tasks';
import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer } from './tasks';
type PlaywrightTestOptions = {
headed?: boolean,
@ -44,13 +44,18 @@ export async function runTestServer(configFile: string) {
if (!config)
return;
const dispatcher = new Dispatcher(config);
const wss = new WSServer({
onConnection(request: http.IncomingMessage, url: URL, ws: WebSocket, id: string) {
const dispatcher = new Dispatcher(config, ws);
ws.on('message', async message => {
const { id, method, params } = JSON.parse(message.toString());
const result = await (dispatcher as any)[method](params);
ws.send(JSON.stringify({ id, result }));
try {
const result = await (dispatcher as any)[method](params);
ws.send(JSON.stringify({ id, result }));
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
});
return {
async close() {}
@ -64,62 +69,111 @@ export async function runTestServer(configFile: string) {
class Dispatcher {
private _config: FullConfigInternal;
private _testRun: { run: Promise<FullResult['status']>, stop: ManualPromise<void> } | undefined;
private _ws: WebSocket;
constructor(config: FullConfigInternal) {
constructor(config: FullConfigInternal, ws: WebSocket) {
this._config = config;
this._ws = ws;
process.stdout.write = ((chunk: string | Buffer, cb?: Buffer | Function, cb2?: Function) => {
this._dispatchEvent('stdio', chunkToPayload('stdout', chunk));
if (typeof cb === 'function')
(cb as any)();
if (typeof cb2 === 'function')
(cb2 as any)();
return true;
}) as any;
process.stderr.write = ((chunk: string | Buffer, cb?: Buffer | Function, cb2?: Function) => {
this._dispatchEvent('stdio', chunkToPayload('stderr', chunk));
if (typeof cb === 'function')
(cb as any)();
if (typeof cb2 === 'function')
(cb2 as any)();
return true;
}) as any;
}
async test(params: { mode: 'list' | 'run', locations: string[], options: PlaywrightTestOptions, reporter: string, env: NodeJS.ProcessEnv }) {
async list(params: { locations: string[], reporter: string, env: NodeJS.ProcessEnv }) {
for (const name in params.env)
process.env[name] = params.env[name];
if (params.mode === 'list')
await listTests(this._config, params.reporter, params.locations);
if (params.mode === 'run')
await runTests(this._config, params.reporter, params.locations, params.options);
await this._listTests(params.reporter, params.locations);
}
async test(params: { locations: string[], options: PlaywrightTestOptions, reporter: string, env: NodeJS.ProcessEnv }) {
for (const name in params.env)
process.env[name] = params.env[name];
await this._runTests(params.reporter, params.locations, params.options);
}
async stop() {
await this._stopTests();
}
private async _listTests(reporterPath: string, locations: string[] | undefined) {
this._config.cliArgs = [...(locations || []), '--reporter=null'];
const reporter = new InternalReporter(new Multiplexer(await createReporters(this._config, 'list', [[reporterPath]])));
const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process', { failOnLoadErrors: true });
const testRun = new TestRun(this._config, reporter);
reporter.onConfigure(this._config.config);
const taskStatus = await taskRunner.run(testRun, 0);
let status: FullResult['status'] = testRun.failureTracker.result();
if (status === 'passed' && taskStatus !== 'passed')
status = taskStatus;
const modifiedResult = await reporter.onEnd({ status });
if (modifiedResult && modifiedResult.status)
status = modifiedResult.status;
await reporter.onExit();
}
private async _runTests(reporterPath: string, locations: string[] | undefined, options: PlaywrightTestOptions) {
await this._stopTests();
this._config.cliListOnly = false;
this._config.cliArgs = locations || [];
this._config.cliGrep = options.grep;
this._config.cliProjectFilter = options.projects?.length ? options.projects : undefined;
this._config.configCLIOverrides.reporter = [[reporterPath]];
this._config.configCLIOverrides.repeatEach = 1;
this._config.configCLIOverrides.retries = 0;
this._config.configCLIOverrides.preserveOutputDir = true;
this._config.configCLIOverrides.use = {
trace: options.trace,
headless: options.headed ? false : undefined,
_optionContextReuseMode: options.reuseContext ? 'when-possible' : undefined,
_optionConnectOptions: options.connectWsEndpoint ? { wsEndpoint: options.connectWsEndpoint } : undefined,
};
// Too late to adjust via overrides for this one.
if (options.oneWorker)
this._config.config.workers = 1;
const reporter = new InternalReporter(new Multiplexer(await createReporters(this._config, 'run')));
const taskRunner = createTaskRunnerForTestServer(this._config, reporter);
const testRun = new TestRun(this._config, reporter);
reporter.onConfigure(this._config.config);
const stop = new ManualPromise();
const run = taskRunner.run(testRun, 0, stop).then(async status => {
await reporter.onEnd({ status });
await reporter.onExit();
this._testRun = undefined;
return status;
});
this._testRun = { run, stop };
await run;
}
private async _stopTests() {
this._testRun?.stop?.resolve();
await this._testRun?.run;
}
private _dispatchEvent(method: string, params: any) {
this._ws.send(JSON.stringify({ method, params }));
}
}
async function listTests(config: FullConfigInternal, reporterPath: string, locations: string[] | undefined) {
config.cliArgs = [...(locations || []), '--reporter=null'];
const reporter = new InternalReporter(new Multiplexer(await createReporters(config, 'list', [[reporterPath]])));
const taskRunner = createTaskRunnerForList(config, reporter, 'out-of-process', { failOnLoadErrors: false });
const testRun = new TestRun(config, reporter);
reporter.onConfigure(config.config);
const taskStatus = await taskRunner.run(testRun, 0);
let status: FullResult['status'] = testRun.failureTracker.result();
if (status === 'passed' && taskStatus !== 'passed')
status = taskStatus;
const modifiedResult = await reporter.onEnd({ status });
if (modifiedResult && modifiedResult.status)
status = modifiedResult.status;
await reporter.onExit();
}
async function runTests(config: FullConfigInternal, reporterPath: string, locations: string[] | undefined, options: PlaywrightTestOptions) {
config.cliArgs = locations || [];
config.cliGrep = options.grep;
config.cliProjectFilter = options.projects;
config.configCLIOverrides.reporter = [[reporterPath]];
config.configCLIOverrides.repeatEach = 1;
config.configCLIOverrides.retries = 0;
config.configCLIOverrides.workers = options.oneWorker ? 1 : undefined;
config.configCLIOverrides.preserveOutputDir = true;
config.configCLIOverrides.use = {
trace: options.trace,
headless: options.headed ? false : undefined,
_optionContextReuseMode: options.reuseContext ? 'when-possible' : undefined,
_optionConnectOptions: options.connectWsEndpoint ? { wsEndpoint: options.connectWsEndpoint } : undefined,
};
const reporter = new InternalReporter(new Multiplexer(await createReporters(config, 'run')));
const taskRunner = createTaskRunnerForWatch(config, reporter);
const testRun = new TestRun(config, reporter);
reporter.onConfigure(config.config);
const stop = new ManualPromise();
const status = await taskRunner.run(testRun, 0, stop);
await reporter.onEnd({ status });
await reporter.onExit();
function chunkToPayload(type: 'stdout' | 'stderr', chunk: Buffer | string) {
if (chunk instanceof Buffer)
return { type, buffer: chunk.toString('base64') };
return { type, text: chunk };
}