chore: implement server-based list files (#29633)

This commit is contained in:
Pavel Feldman 2024-02-23 08:29:44 -08:00 committed by GitHub
parent 2ca45ff948
commit a3aea813bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 103 additions and 71 deletions

View File

@ -27,7 +27,7 @@ import { createMergedReport } from './reporters/merge';
import { loadConfigFromFileRestartIfNeeded, loadEmptyConfigForMergeReports } from './common/configLoader';
import type { ConfigCLIOverrides } from './common/ipc';
import type { FullResult, TestError } from '../types/testReporter';
import type { FullConfig, TraceMode } from '../types/test';
import type { TraceMode } from '../types/test';
import { builtInReporters, defaultReporter, defaultTimeout } from './common/config';
import { program } from 'playwright-core/lib/cli/program';
export { program } from 'playwright-core/lib/cli/program';
@ -176,7 +176,7 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
gracefullyProcessExitDoNotHang(exitCode);
}
export async function withRunnerAndMutedWrite(configFile: string | undefined, callback: (runner: Runner, config: FullConfig, configDir: string) => Promise<any>) {
export async function withRunnerAndMutedWrite(configFile: string | undefined, callback: (runner: Runner) => Promise<any>) {
// Redefine process.stdout.write in case config decides to pollute stdio.
const stdoutWrite = process.stdout.write.bind(process.stdout);
process.stdout.write = ((a: any, b: any, c: any) => process.stderr.write(a, b, c)) as any;
@ -185,7 +185,7 @@ export async function withRunnerAndMutedWrite(configFile: string | undefined, ca
if (!config)
return;
const runner = new Runner(config);
const result = await callback(runner, config.config, config.configDir);
const result = await callback(runner);
stdoutWrite(JSON.stringify(result, undefined, 2), () => {
gracefullyProcessExitDoNotHang(0);
});
@ -199,9 +199,8 @@ export async function withRunnerAndMutedWrite(configFile: string | undefined, ca
}
async function listTestFiles(opts: { [key: string]: any }) {
await withRunnerAndMutedWrite(opts.config, async (runner, config) => {
const frameworkPackage = (config as any)['@playwright/test']?.['packageJSON'];
return await runner.listTestFiles(frameworkPackage, opts.project);
await withRunnerAndMutedWrite(opts.config, async runner => {
return await runner.listTestFiles();
});
}

View File

@ -57,8 +57,9 @@ export class Runner {
this._config = config;
}
async listTestFiles(frameworkPackage: string | undefined, projectNames: string[] | undefined): Promise<ConfigListFilesReport> {
const projects = filterProjects(this._config.projects, projectNames);
async listTestFiles(): Promise<ConfigListFilesReport> {
const frameworkPackage = (this._config.config as any)['@playwright/test']?.['packageJSON'];
const projects = filterProjects(this._config.projects);
const report: ConfigListFilesReport = {
projects: [],
cliEntryPoint: frameworkPackage ? path.join(path.dirname(frameworkPackage), 'cli.js') : undefined,

View File

@ -29,6 +29,9 @@ import type { ConfigCLIOverrides } from '../common/ipc';
import { Runner } from './runner';
import type { FindRelatedTestFilesReport } from './runner';
import type { FullConfigInternal } from '../common/config';
import type { TestServerInterface } from './testServerInterface';
import { serializeError } from '../util';
import { prepareErrorStack } from '../reporters/base';
export async function runTestServer() {
if (restartWithExperimentalTsEsm(undefined, true))
@ -60,44 +63,6 @@ export async function runTestServer() {
process.stdin.on('close', () => gracefullyProcessExitDoNotHang(0));
}
export interface TestServerInterface {
list(params: {
configFile: string;
locations: string[];
reporter: string;
env: NodeJS.ProcessEnv;
}): Promise<void>;
test(params: {
configFile: string;
locations: string[];
reporter: string;
env: NodeJS.ProcessEnv;
headed?: boolean;
oneWorker?: boolean;
trace?: 'on' | 'off';
projects?: string[];
grep?: string;
reuseContext?: boolean;
connectWsEndpoint?: string;
}): Promise<void>;
findRelatedTestFiles(params: {
configFile: string;
files: string[];
}): Promise<{ testFiles: string[]; errors?: TestError[]; }>;
stop(params: {
configFile: string;
}): Promise<void>;
closeGracefully(): Promise<void>;
}
export interface TestServerEvents {
on(event: 'stdio', listener: (params: { type: 'stdout' | 'stderr', text?: string, buffer?: string }) => void): void;
}
class Dispatcher implements TestServerInterface {
private _testRun: { run: Promise<FullResult['status']>, stop: ManualPromise<void> } | undefined;
private _ws: WebSocket;
@ -123,7 +88,30 @@ class Dispatcher implements TestServerInterface {
}) as any;
}
async list(params: {
async listFiles(params: {
configFile: string;
}): Promise<{
projects: {
name: string;
testDir: string;
use: { testIdAttribute?: string };
files: string[];
}[];
cliEntryPoint?: string;
error?: TestError;
}> {
try {
const config = await this._loadConfig(params.configFile);
const runner = new Runner(config);
return runner.listTestFiles();
} catch (e) {
const error: TestError = serializeError(e);
error.location = prepareErrorStack(e.stack).location;
return { projects: [], error };
}
}
async listTests(params: {
configFile: string;
locations: string[];
reporter: string;

View File

@ -0,0 +1,68 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { TestError } from '../../types/testReporter';
export interface TestServerInterface {
listFiles(params: {
configFile: string;
}): Promise<{
projects: {
name: string;
testDir: string;
use: { testIdAttribute?: string };
files: string[];
}[];
cliEntryPoint?: string;
error?: TestError;
}>;
listTests(params: {
configFile: string;
locations: string[];
reporter: string;
env: NodeJS.ProcessEnv;
}): Promise<void>;
test(params: {
configFile: string;
locations: string[];
reporter: string;
env: NodeJS.ProcessEnv;
headed?: boolean;
oneWorker?: boolean;
trace?: 'on' | 'off';
projects?: string[];
grep?: string;
reuseContext?: boolean;
connectWsEndpoint?: string;
}): Promise<void>;
findRelatedTestFiles(params: {
configFile: string;
files: string[];
}): Promise<{ testFiles: string[]; errors?: TestError[]; }>;
stop(params: {
configFile: string;
}): Promise<void>;
closeGracefully(): Promise<void>;
}
export interface TestServerEvents {
on(event: 'stdio', listener: (params: { type: 'stdout' | 'stderr', text?: string, buffer?: string }) => void): void;
}

View File

@ -48,30 +48,6 @@ test('should list files', async ({ runCLICommand }) => {
});
});
test('should support wildcard list files', async ({ runCLICommand }) => {
const result = await runCLICommand({
'playwright.config.ts': `
module.exports = { projects: [{ name: 'foo' }, { name: 'bar' }] };
`,
'a.test.js': ``
}, 'list-files', ['--project', 'f*o']);
expect(result.exitCode).toBe(0);
const data = JSON.parse(result.stdout);
expect(data).toEqual({
projects: [
{
name: 'foo',
testDir: expect.stringContaining('list-files-should-support-wildcard-list-files-playwright-test'),
use: {},
files: [
expect.stringContaining('a.test.js')
]
}
]
});
});
test('should include testIdAttribute', async ({ runCLICommand }) => {
const result = await runCLICommand({
'playwright.config.ts': `