diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb25fa76..ccf49fea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber ### Changed - BREAKING CHANGE: Use appropriate module loading mechanism for configuration files ([#2334](https://github.com/cucumber/cucumber-js/pull/2334)) - BREAKING CHANGE: Use `await import()` to load all custom formatters and snippet syntaxes ([#2334](https://github.com/cucumber/cucumber-js/pull/2334)) +- BREAKING CHANGE: Use `await import()` for default support code loading ([#2337](https://github.com/cucumber/cucumber-js/pull/2337)) ### Removed - BREAKING CHANGE: Drop support for Node.js 14, 16 and 19 ([#2331](https://github.com/cucumber/cucumber-js/pull/2331)) diff --git a/docs/configuration.md b/docs/configuration.md index cf5c87f7d..8a3ceae84 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -8,40 +8,20 @@ _**You are reading the documentation in the `main` development branch, which mig You can keep your configuration in a file. Cucumber will look for one of these files in the root of your project, and use the first one it finds: -- `cucumber.js` -- `cucumber.cjs` -- `cucumber.mjs` - `cucumber.json` - `cucumber.yaml` - `cucumber.yml` +- `cucumber.js` +- `cucumber.cjs` +- `cucumber.mjs` You can also put your file somewhere else and tell Cucumber via the `--config` CLI option: ```shell -$ cucumber-js --config config/cucumber.js -``` - -Here's a concise example of a configuration file in CommonJS format: - -```js -module.exports = { - default: { - parallel: 2, - format: ['html:cucumber-report.html'] - } -} -``` - -And the same in ESM format: - -```js -export default { - parallel: 2, - format: ['html:cucumber-report.html'] -} +$ cucumber-js --config config/cucumber.json ``` -And the same in JSON format: +Here's a concise example of a configuration file in JSON format: ```json { @@ -61,6 +41,26 @@ default: - "html:cucumber-report.html" ``` +And the same in JavaScript (ESM) format: + +```js +export default { + parallel: 2, + format: ['html:cucumber-report.html'] +} +``` + +And the same in JavaScript (CommonJS) format: + +```js +module.exports = { + default: { + parallel: 2, + format: ['html:cucumber-report.html'] + } +} +``` + Cucumber also supports the configuration being a string of options in the style of the CLI, though this isn't recommended: ```js @@ -87,7 +87,7 @@ These options can be used in a configuration file (see [above](#files)) or on th | `failFast` | `boolean` | No | `--fail-fast` | Stop running tests when a test fails - see [Fail Fast](./fail_fast.md) | false | | `format` | `string[]` | Yes | `--format`, `-f` | Name/path and (optionally) output file path of each formatter to use - see [Formatters](./formatters.md) | [] | | `formatOptions` | `object` | Yes | `--format-options` | Options to be provided to formatters - see [Formatters](./formatters.md) | {} | -| `import` | `string[]` | Yes | `--import`, `-i` | Paths to where your support code is, for ESM - see [ESM](./esm.md) | [] | +| `import` | `string[]` | Yes | `--import`, `-i` | Paths to where your support code is | [] | | `language` | `string` | No | `--language` | Default language for your feature files | en | | `name` | `string` | No | `--name` | Regular expressions of which scenario names should match one of to be run - see [Filtering](./filtering.md#names) | [] | | `order` | `string` | No | `--order` | Run in the order defined, or in a random order | defined | @@ -123,15 +123,15 @@ For more granular options to control _which scenarios_ from your features should By default, Cucumber finds support code files with this logic: * If the features live in a `features` directory (at any level) - * `features/**/*.(js)` + * `features/**/*.@(js|cjs|mjs)` * Otherwise - * `/**/*.(js)` for each directory containing the selected features + * `/**/*.@(js|cjs|mjs)` for each directory containing the selected features -If your files are somewhere else, you can override this by proving your own [glob](https://github.com/isaacs/node-glob), directory or file path to the `require` configuration option: +If your files are somewhere else, you can override this by proving your own [glob](https://github.com/isaacs/node-glob), directory or file path to the `import` configuration option: -- In a configuration file `{ require: ['somewhere-else/support/*.js'] }` -- On the CLI `$ cucumber-js --require somewhere-else/support/*.js` +- In a configuration file `{ import: ['somewhere-else/support/*.js'] }` +- On the CLI `$ cucumber-js --import somewhere-else/support/*.js` -Once you specify any `require` options, the defaults described above are no longer applied. The option is repeatable, so you can provide several values and they'll be combined, meaning you can load files from multiple locations. +Once you specify any `import` options, the defaults described above are no longer applied. The option is repeatable, so you can provide several values and they'll be combined, meaning you can load files from multiple locations. -The default behaviour and the `require` option both use the [legacy CommonJS modules API](https://nodejs.org/api/modules.html) to load your files. If your files are native ES modules, you'll need to use the `import` option instead in the same way, and they'll be loaded with the [new ES modules API](https://nodejs.org/api/esm.html). See [ES Modules](./esm.md) for more on using Cucumber in an ESM project. +The default behaviour and the `import` option both use the [new ES modules API](https://nodejs.org/api/esm.html) to load your files. This should work fine for the majority of cases, but sometimes (e.g. when transpiling with the `require-module` option), you'll need to use the `require` option instead in the same way, and they'll be loaded with the [legacy CommonJS modules API](https://nodejs.org/api/modules.html). diff --git a/src/api/paths.ts b/src/api/paths.ts index 2c103cc11..fc884cfc1 100644 --- a/src/api/paths.ts +++ b/src/api/paths.ts @@ -147,9 +147,8 @@ async function deriveSupportPaths( unexpandedImportPaths.length === 0 ) { const defaultPaths = getFeatureDirectoryPaths(cwd, featurePaths) - const requirePaths = await expandPaths(cwd, defaultPaths, '.js') - const importPaths = await expandPaths(cwd, defaultPaths, '.mjs') - return { requirePaths, importPaths } + const importPaths = await expandPaths(cwd, defaultPaths, '.@(js|cjs|mjs)') + return { requirePaths: [], importPaths } } const requirePaths = unexpandedRequirePaths.length > 0 diff --git a/src/api/paths_spec.ts b/src/api/paths_spec.ts index 3135cc672..cc8cf6586 100644 --- a/src/api/paths_spec.ts +++ b/src/api/paths_spec.ts @@ -51,8 +51,8 @@ describe('resolvePaths', () => { // Assert expect(featurePaths).to.eql([featurePath]) expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath]) - expect(requirePaths).to.eql([jsSupportCodePath]) - expect(importPaths).to.eql([esmSupportCodePath]) + expect(requirePaths).to.eql([]) + expect(importPaths).to.eql([jsSupportCodePath, esmSupportCodePath]) }) it('deduplicates features based on overlapping expressions', async function () { @@ -113,7 +113,7 @@ describe('resolvePaths', () => { await fsExtra.outputFile(supportCodePath, '') // Act - const { featurePaths, unexpandedFeaturePaths, requirePaths } = + const { featurePaths, unexpandedFeaturePaths, importPaths } = await resolvePaths( new FakeLogger(), cwd, @@ -130,7 +130,7 @@ describe('resolvePaths', () => { // Assert expect(featurePaths).to.eql([featurePath]) expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath]) - expect(requirePaths).to.eql([supportCodePath]) + expect(importPaths).to.eql([supportCodePath]) }) }) @@ -145,7 +145,7 @@ describe('resolvePaths', () => { await fsExtra.outputFile(supportCodePath, '') // Act - const { featurePaths, unexpandedFeaturePaths, requirePaths } = + const { featurePaths, unexpandedFeaturePaths, importPaths } = await resolvePaths( new FakeLogger(), cwd, @@ -162,7 +162,7 @@ describe('resolvePaths', () => { // Assert expect(featurePaths).to.eql([featurePath]) expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath]) - expect(requirePaths).to.eql([supportCodePath]) + expect(importPaths).to.eql([supportCodePath]) }) it('returns the appropriate .md and support code paths', async function () { @@ -179,7 +179,7 @@ describe('resolvePaths', () => { await fsExtra.outputFile(supportCodePath, '') // Act - const { featurePaths, unexpandedFeaturePaths, requirePaths } = + const { featurePaths, unexpandedFeaturePaths, importPaths } = await resolvePaths( new FakeLogger(), cwd, @@ -196,7 +196,7 @@ describe('resolvePaths', () => { // Assert expect(featurePaths).to.eql([featurePath]) expect(unexpandedFeaturePaths).to.eql([relativeFeaturePath]) - expect(requirePaths).to.eql([supportCodePath]) + expect(importPaths).to.eql([supportCodePath]) }) }) @@ -297,8 +297,8 @@ describe('resolvePaths', () => { const relativeFeaturePath = path.join('features', 'a.feature') const featurePath = path.join(cwd, relativeFeaturePath) await fsExtra.outputFile(featurePath, '') - const jsSupportCodePath = path.join(cwd, 'features', 'a.js') - await fsExtra.outputFile(jsSupportCodePath, '') + const cjsSupportCodePath = path.join(cwd, 'features', 'a.cjs') + await fsExtra.outputFile(cjsSupportCodePath, '') const esmSupportCodePath = path.join(cwd, 'features', 'a.mjs') await fsExtra.outputFile(esmSupportCodePath, '') @@ -311,8 +311,8 @@ describe('resolvePaths', () => { }, { requireModules: [], - requirePaths: [], - importPaths: [], + requirePaths: [cjsSupportCodePath], + importPaths: [esmSupportCodePath], } ) @@ -323,7 +323,7 @@ describe('resolvePaths', () => { ) expect(logger.debug).to.have.been.calledWith( 'Found support files to load via `require` based on configuration:', - [jsSupportCodePath] + [cjsSupportCodePath] ) expect(logger.debug).to.have.been.calledWith( 'Found support files to load via `import` based on configuration:', diff --git a/src/configuration/validate_configuration.ts b/src/configuration/validate_configuration.ts index 437332acb..1f4433172 100644 --- a/src/configuration/validate_configuration.ts +++ b/src/configuration/validate_configuration.ts @@ -10,6 +10,11 @@ export function validateConfiguration( '`publishQuiet` option is no longer needed, you can remove it from your configuration; see https://github.com/cucumber/cucumber-js/blob/main/docs/deprecations.md' ) } + if (configuration.requireModule.length && !configuration.require.length) { + logger.warn( + 'Use of `require-module` option normally means you should specify your support code paths with `require`; see https://github.com/cucumber/cucumber-js/blob/main/docs/configuration.md#finding-your-code' + ) + } if (configuration.retryTagFilter && !configuration.retry) { throw new Error( 'a positive `retry` count must be specified when setting `retryTagFilter`'