feat(test runner): support .cjs and .cts files (#22971)

Fixes #22579.
This commit is contained in:
Dmitry Gozman 2023-05-11 15:41:50 -07:00 committed by GitHub
parent d7f5f1f5fe
commit 44a934c160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 82 additions and 21 deletions

View File

@ -492,7 +492,7 @@ export default defineConfig({
Only the files matching one of these patterns are executed as test files. Matching is performed against the absolute file path. Strings are treated as glob patterns.
By default, Playwright looks for files matching the following glob pattern: `**/*.@(spec|test).?(m)[jt]s?(x)`. This means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example `login-screen.wrong-credentials.spec.ts`.
By default, Playwright looks for files matching the following glob pattern: `**/*.@(spec|test).?(c|m)[jt]s?(x)`. This means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example `login-screen.wrong-credentials.spec.ts`.
**Usage**

View File

@ -305,7 +305,7 @@ Use [`property: TestConfig.testIgnore`] to change this option for all projects.
Only the files matching one of these patterns are executed as test files. Matching is performed against the absolute file path. Strings are treated as glob patterns.
By default, Playwright looks for files matching the following glob pattern: `**/*.@(spec|test).?(m)[jt]s?(x)`. This means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example `login-screen.wrong-credentials.spec.ts`.
By default, Playwright looks for files matching the following glob pattern: `**/*.@(spec|test).?(c|m)[jt]s?(x)`. This means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example `login-screen.wrong-credentials.spec.ts`.
Use [`property: TestConfig.testMatch`] to change this option for all projects.

View File

@ -24,7 +24,7 @@ import { stopProfiling, startProfiling } from 'playwright-core/lib/utils';
import { experimentalLoaderOption, fileIsModule, serializeError } from './util';
import { showHTMLReport } from './reporters/html';
import { createMergedReport } from './reporters/merge';
import { ConfigLoader, kDefaultConfigFiles, resolveConfigFile } from './common/configLoader';
import { ConfigLoader, resolveConfigFile } from './common/configLoader';
import type { ConfigCLIOverrides } from './common/ipc';
import type { FullResult, TestError } from '../reporter';
import type { TraceMode } from '../types/test';
@ -61,7 +61,7 @@ Examples:
function addListFilesCommand(program: Command) {
const command = program.command('list-files [file-filter...]', { hidden: true });
command.description('List files with Playwright Test tests');
command.option('-c, --config <file>', `Configuration file, or a test directory with optional ${kDefaultConfigFiles.map(file => `"${file}"`).join('/')}`);
command.option('-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
command.option('--project <project-name...>', `Only run tests from the specified list of projects (default: list all projects)`);
command.action(async (args, opts) => {
try {
@ -297,7 +297,7 @@ const kTraceModes: TraceMode[] = ['on', 'off', 'on-first-retry', 'on-all-retries
const testOptions: [string, string][] = [
['--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`],
['-c, --config <file>', `Configuration file, or a test directory with optional ${kDefaultConfigFiles.map(file => `"${file}"`).join('/')}`],
['-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`],
['--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options`],
['--forbid-only', `Fail if test.only is called (default: false)`],
['--fully-parallel', `Run all tests in parallel (default: false)`],

View File

@ -173,7 +173,7 @@ export class FullProjectInternal {
testDir,
snapshotDir: takeFirst(pathResolve(configDir, projectConfig.snapshotDir), pathResolve(configDir, config.snapshotDir), testDir),
testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []),
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, '**/*.@(spec|test).?(m)[jt]s?(x)'),
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, '**/*.@(spec|test).?(c|m)[jt]s?(x)'),
timeout: takeFirst(configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
use: mergeObjects(config.use, projectConfig.use, configCLIOverrides.use),
dependencies: projectConfig.dependencies || [],

View File

@ -258,8 +258,6 @@ function validateProject(file: string, project: Project, title: string) {
}
}
export const kDefaultConfigFiles = ['playwright.config.ts', 'playwright.config.js', 'playwright.config.mjs'];
export function resolveConfigFile(configFileOrDirectory: string): string | null {
const resolveConfig = (configFile: string) => {
if (fs.existsSync(configFile))
@ -267,8 +265,8 @@ export function resolveConfigFile(configFileOrDirectory: string): string | null
};
const resolveConfigFileFromDirectory = (directory: string) => {
for (const configName of kDefaultConfigFiles) {
const configFile = resolveConfig(path.resolve(directory, configName));
for (const ext of ['.ts', '.js', '.mts', '.mjs', '.cts', '.cjs']) {
const configFile = resolveConfig(path.resolve(directory, 'playwright.config' + ext));
if (configFile)
return configFile;
}

View File

@ -102,8 +102,8 @@ export function buildDependentProjects(forProject: FullProjectInternal, projects
}
export async function collectFilesForProject(project: FullProjectInternal, fsCache = new Map<string, string[]>()): Promise<string[]> {
const extensions = ['.js', '.ts', '.mjs', '.tsx', '.jsx'];
const testFileExtension = (file: string) => extensions.includes(path.extname(file));
const extensions = new Set(['.js', '.ts', '.mjs', '.mts', '.cjs', '.cts', '.jsx', '.tsx', '.mjsx', '.mtsx', '.cjsx', '.ctsx']);
const testFileExtension = (file: string) => extensions.has(path.extname(file));
const allFiles = await cachedCollectFiles(project.project.testDir, project.respectGitIgnore, fsCache);
const testMatch = createFileMatcher(project.project.testMatch);
const testIgnore = createFileMatcher(project.project.testIgnore);

View File

@ -280,9 +280,10 @@ export async function normalizeAndSaveAttachment(outputPath: string, name: strin
}
export function fileIsModule(file: string): boolean {
if (file.endsWith('.mjs'))
if (file.endsWith('.mjs') || file.endsWith('.mts'))
return true;
if (file.endsWith('.cjs') || file.endsWith('.cts'))
return false;
const folder = path.dirname(file);
return folderIsModule(folder);
}

View File

@ -395,8 +395,8 @@ export interface FullProject<TestArgs = {}, WorkerArgs = {}> {
* Only the files matching one of these patterns are executed as test files. Matching is performed against the
* absolute file path. Strings are treated as glob patterns.
*
* By default, Playwright looks for files matching the following glob pattern: `**\/*.@(spec|test).?(m)[jt]s?(x)`. This
* means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example
* By default, Playwright looks for files matching the following glob pattern: `**\/*.@(spec|test).?(c|m)[jt]s?(x)`.
* This means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example
* `login-screen.wrong-credentials.spec.ts`.
*
* Use [testConfig.testMatch](https://playwright.dev/docs/api/class-testconfig#test-config-test-match) to change this
@ -1240,8 +1240,8 @@ interface TestConfig {
* Only the files matching one of these patterns are executed as test files. Matching is performed against the
* absolute file path. Strings are treated as glob patterns.
*
* By default, Playwright looks for files matching the following glob pattern: `**\/*.@(spec|test).?(m)[jt]s?(x)`. This
* means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example
* By default, Playwright looks for files matching the following glob pattern: `**\/*.@(spec|test).?(c|m)[jt]s?(x)`.
* This means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example
* `login-screen.wrong-credentials.spec.ts`.
*
* **Usage**
@ -6439,8 +6439,8 @@ interface TestProject {
* Only the files matching one of these patterns are executed as test files. Matching is performed against the
* absolute file path. Strings are treated as glob patterns.
*
* By default, Playwright looks for files matching the following glob pattern: `**\/*.@(spec|test).?(m)[jt]s?(x)`. This
* means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example
* By default, Playwright looks for files matching the following glob pattern: `**\/*.@(spec|test).?(c|m)[jt]s?(x)`.
* This means JavaScript or TypeScript files with `".test"` or `".spec"` suffix, for example
* `login-screen.wrong-credentials.spec.ts`.
*
* Use [testConfig.testMatch](https://playwright.dev/docs/api/class-testconfig#test-config-test-match) to change this

View File

@ -392,3 +392,47 @@ test('should resolve .js import to .tsx file in ESM mode for components', async
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});
test('should load cjs config and test in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.cjs': `
const fs = require('fs');
module.exports = { projects: [{name: 'foo'}] };
`,
'a.test.js': `
import { test, expect } from '@playwright/test';
test('check project name', ({}, testInfo) => {
expect(testInfo.project.name).toBe('foo');
});
`,
'b.spec.cjs': `
const { test, expect } = require('@playwright/test');
test('check project name', ({}, testInfo) => {
expect(testInfo.project.name).toBe('foo');
});
`,
});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(2);
});
test('should disallow ESM when config is cjs', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.cjs': `
const fs = require('fs');
module.exports = { projects: [{name: 'foo'}] };
`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('check project name', ({}, testInfo) => {
expect(testInfo.project.name).toBe('foo');
});
`,
});
expect(result.exitCode).toBe(1);
expect(result.output).toContain('Unknown file extension ".ts"');
});

View File

@ -224,7 +224,7 @@ test('should load esm when package.json has type module', async ({ runInlineTest
expect(result.passed).toBe(1);
});
test('should load esm config files', async ({ runInlineTest }) => {
test('should load mjs config file', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.mjs': `
import * as fs from 'fs';
@ -242,6 +242,24 @@ test('should load esm config files', async ({ runInlineTest }) => {
expect(result.passed).toBe(1);
});
test('should load mts config file', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.mts': `
import * as fs from 'fs';
export default { projects: [{name: 'foo'}] };
`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
test('check project name', ({}, testInfo) => {
expect(testInfo.project.name).toBe('foo');
});
`
});
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
});
test('should load ts from esm when package.json has type module', async ({ runInlineTest, nodeVersion }) => {
// We only support experimental esm mode on Node 16+
test.skip(nodeVersion.major < 16);