diff --git a/packages/playwright-core/src/cli/innerCli.ts b/packages/playwright-core/src/cli/innerCli.ts index 99495293d6..988f6cc36c 100644 --- a/packages/playwright-core/src/cli/innerCli.ts +++ b/packages/playwright-core/src/cli/innerCli.ts @@ -246,6 +246,7 @@ if (!process.env.PW_CLI_TARGET_LANG) { if (playwrightTestPackagePath) { require(playwrightTestPackagePath).addTestCommand(program); require(playwrightTestPackagePath).addShowReportCommand(program); + require(playwrightTestPackagePath).addListTestsCommand(program); } else { { const command = program.command('test').allowUnknownOption(true); diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index 699807a53c..9818ebca82 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -73,6 +73,21 @@ Examples: $ npx playwright test --browser=webkit`); } +export function addListTestsCommand(program: Command) { + const command = program.command('list-tests [test-filter...]', { hidden: true }); + command.description('List tests with Playwright Test'); + command.option('-c, --config ', `Configuration file, or a test directory with optional ${kDefaultConfigFiles.map(file => `"${file}"`).join('/')}`); + command.option('--project ', `Only run tests from the specified list of projects (default: list all projects)`); + command.action(async (args, opts) => { + try { + await listTests(opts); + } catch (e) { + console.error(e); + process.exit(1); + } + }); +} + export function addShowReportCommand(program: Command) { const command = program.command('show-report [report]'); command.description('show HTML report'); @@ -151,6 +166,16 @@ async function runTests(args: string[], opts: { [key: string]: any }) { process.exit(result.status === 'passed' ? 0 : 1); } + +async function listTests(opts: { [key: string]: any }) { + const configFile = opts.config ? path.resolve(process.cwd(), opts.config) : process.cwd(); + const runner = new Runner({}, { defaultConfig: {} }); + await runner.loadConfigFromFile(configFile); + const report = await runner.listAllTestFiles(opts.project); + process.stdout.write(JSON.stringify(report)); + process.exit(0); +} + function forceRegExp(pattern: string): RegExp { const match = pattern.match(/^\/(.*)\/([gi]*)$/); if (match) diff --git a/packages/playwright-test/src/runner.ts b/packages/playwright-test/src/runner.ts index f1e92758c9..db6d63a84b 100644 --- a/packages/playwright-test/src/runner.ts +++ b/packages/playwright-test/src/runner.ts @@ -163,10 +163,27 @@ export class Runner { } } - async _run(list: boolean, testFileReFilters: FilePatternFilter[], projectNames?: string[]): Promise { - const testFileFilter = testFileReFilters.length ? createFileMatcher(testFileReFilters.map(e => e.re)) : () => true; - const config = this._loader.fullConfig(); + async listAllTestFiles(projectNames: string[] | undefined): Promise { + const filesByProject = await this._collectFiles([], projectNames); + const report: any = { + projects: [] + }; + for (const [project, files] of filesByProject) { + report.projects.push({ + name: project.config.name, + files: files + }); + } + return report; + } + private async _run(list: boolean, testFileReFilters: FilePatternFilter[], projectNames?: string[]): Promise { + const filesByProject = await this._collectFiles(testFileReFilters, projectNames); + return await this._runFiles(list, filesByProject, testFileReFilters); + } + + private async _collectFiles(testFileReFilters: FilePatternFilter[], projectNames?: string[]): Promise> { + const testFileFilter = testFileReFilters.length ? createFileMatcher(testFileReFilters.map(e => e.re)) : () => true; let projectsToFind: Set | undefined; let unknownProjects: Map | undefined; if (projectNames) { @@ -194,7 +211,6 @@ export class Runner { } const files = new Map(); - const allTestFiles = new Set(); for (const project of projects) { const testDir = project.config.testDir; if (!fs.existsSync(testDir)) @@ -208,9 +224,16 @@ export class Runner { const testFileExtension = (file: string) => extensions.includes(path.extname(file)); const testFiles = allFiles.filter(file => !testIgnore(file) && testMatch(file) && testFileFilter(file) && testFileExtension(file)); files.set(project, testFiles); - testFiles.forEach(file => allTestFiles.add(file)); } + return files; + } + private async _runFiles(list: boolean, filesByProject: Map, testFileReFilters: FilePatternFilter[]): Promise { + const allTestFiles = new Set(); + for (const files of filesByProject.values()) + files.forEach(file => allTestFiles.add(file)); + + const config = this._loader.fullConfig(); const internalGlobalTeardowns: (() => Promise)[] = []; if (!list) { for (const internalGlobalSetup of this._internalGlobalSetups) @@ -257,11 +280,11 @@ export class Runner { const grepMatcher = createTitleMatcher(config.grep); const grepInvertMatcher = config.grepInvert ? createTitleMatcher(config.grepInvert) : null; const rootSuite = new Suite(''); - for (const project of projects) { + for (const [project, files] of filesByProject) { const projectSuite = new Suite(project.config.name); projectSuite._projectConfig = project.config; rootSuite._addSuite(projectSuite); - for (const file of files.get(project)!) { + for (const file of files) { const fileSuite = fileSuites.get(file); if (!fileSuite) continue;