diff --git a/CHANGELOG.md b/CHANGELOG.md index 318547841..ac429c554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) on how to contribute to Cucumber. ## [Unreleased] +### Added +- Add support for YAML as a configuration file format ([#2199](https://github.com/cucumber/cucumber-js/pull/2199)) ## [8.9.1] - 2022-12-16 ### Fixed diff --git a/docs/configuration.md b/docs/configuration.md index ccbce08c9..678801631 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,6 +12,8 @@ You can keep your configuration in a file. Cucumber will look for one of these f - `cucumber.cjs` - `cucumber.mjs` - `cucumber.json` +- `cucumber.yaml` +- `cucumber.yml` You can also put your file somewhere else and tell Cucumber via the `--config` CLI option: @@ -45,11 +47,20 @@ And the same in JSON format: { "default": { "parallel": 2, - "format": ["html:cucumber-report.html"] + "format": ["html:cucumber-report.html"] } } ``` +And the same in YAML format: + +```yaml +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 diff --git a/features/profiles.feature b/features/profiles.feature index ba0771843..ce057271e 100644 --- a/features/profiles.feature +++ b/features/profiles.feature @@ -117,3 +117,19 @@ Feature: default command line arguments 1 step (1 skipped) """ + + Scenario: using a YAML file + Given a file named ".cucumber-rc.yaml" with: + """ + default: + dryRun: true + """ + When I run cucumber-js with `--config .cucumber-rc.yaml` + Then it outputs the text: + """ + - + + 1 scenario (1 skipped) + 1 step (1 skipped) + + """ diff --git a/package-lock.json b/package-lock.json index f16b0bb60..15f260be6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "util-arity": "^1.1.0", "verror": "^1.10.0", "xmlbuilder": "^15.1.1", + "yaml": "1.10.2", "yup": "^0.32.11" }, "bin": { @@ -7909,6 +7910,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -14065,6 +14074,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index 3ddf02a6f..374fcf80e 100644 --- a/package.json +++ b/package.json @@ -234,6 +234,7 @@ "util-arity": "^1.1.0", "verror": "^1.10.0", "xmlbuilder": "^15.1.1", + "yaml": "1.10.2", "yup": "^0.32.11" }, "devDependencies": { diff --git a/src/configuration/from_file.ts b/src/configuration/from_file.ts index 9e4c41701..d95ab51e1 100644 --- a/src/configuration/from_file.ts +++ b/src/configuration/from_file.ts @@ -1,5 +1,8 @@ import stringArgv from 'string-argv' +import fs from 'fs' import path from 'path' +import YAML from 'yaml' +import { promisify } from 'util' import { pathToFileURL } from 'url' import { IConfiguration } from './types' import { mergeConfigurations } from './merge_configurations' @@ -44,16 +47,31 @@ async function loadFile( file: string ): Promise> { const filePath: string = path.join(cwd, file) + const extension = path.extname(filePath) let definitions - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - definitions = require(filePath) - } catch (error) { - if (error.code === 'ERR_REQUIRE_ESM') { - definitions = await importer(pathToFileURL(filePath)) - } else { - throw error - } + switch (extension) { + case '.json': + definitions = JSON.parse( + await promisify(fs.readFile)(filePath, { encoding: 'utf-8' }) + ) + break + case '.yaml': + case '.yml': + definitions = YAML.parse( + await promisify(fs.readFile)(filePath, { encoding: 'utf-8' }) + ) + break + default: + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + definitions = require(filePath) + } catch (error) { + if (error.code === 'ERR_REQUIRE_ESM') { + definitions = await importer(pathToFileURL(filePath)) + } else { + throw error + } + } } if (typeof definitions !== 'object') { throw new Error(`Configuration file ${filePath} does not export an object`) diff --git a/src/configuration/from_file_spec.ts b/src/configuration/from_file_spec.ts index a785b1806..7b75557e8 100644 --- a/src/configuration/from_file_spec.ts +++ b/src/configuration/from_file_spec.ts @@ -99,5 +99,20 @@ describe('fromFile', () => { const result = await fromFile(logger, cwd, 'cucumber.json', ['p1']) expect(result).to.deep.eq({ paths: ['other/path/*.feature'] }) }) + + it('should work with yaml', async () => { + const { logger, cwd } = await setup( + 'cucumber.yaml', + `default: + +p1: + paths: + - "other/path/*.feature" +` + ) + + const result = await fromFile(logger, cwd, 'cucumber.yaml', ['p1']) + expect(result).to.deep.eq({ paths: ['other/path/*.feature'] }) + }) }) }) diff --git a/src/configuration/locate_file.ts b/src/configuration/locate_file.ts index e4897aecf..36c8011e8 100644 --- a/src/configuration/locate_file.ts +++ b/src/configuration/locate_file.ts @@ -6,6 +6,8 @@ const DEFAULT_FILENAMES = [ 'cucumber.cjs', 'cucumber.mjs', 'cucumber.json', + 'cucumber.yaml', + 'cucumber.yml', ] export function locateFile(cwd: string): string | undefined {