Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update config to support default definition function #2384

Merged
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber.

## [Unreleased]
### Added
- Add error message for pending steps ([#2392](https://github.com/cucumber/cucumber-js/pull/2393))
- Updated profiles to allow defining a default function profile to be used as profile builder ([#2384](https://github.com/cucumber/cucumber-js/pull/2384))

## [10.4.0] - 2024-04-07
### Added
Expand Down
63 changes: 62 additions & 1 deletion docs/profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ The short tag is `-p`
cucumber-js -p my_profile
```

## Simple Example
## Default profiles
If defined, a `default` profile is used in case no profiles are specified at runtime. A default profile is either a profile or a function that returns either a profiles object or a `Promise` of profiles object. If defined this way, no other profile shall be defined.

## Examples

### Simple Example

Let's take the common case of having some things a bit different locally than on a continuous integration server. Here's the configuration we've been running locally:

Expand Down Expand Up @@ -67,6 +72,62 @@ Now, if we just run `cucumber-js` with no arguments, it will pick up our profile
cucumber-js -p ci
```

### Example using a default function

```javascript
module.exports = {
default: function buildProfiles() {
const common = {
requireModule: ['ts-node/register'],
require: ['support/**/*.ts'],
worldParameters: {
appUrl: process.env.MY_APP_URL || 'http://localhost:3000/'
}
}

return {
default: {
...common,
format: ['progress-bar', 'html:cucumber-report.html'],
},
ci: {
...common,
format: ['html:cucumber-report.html'],
publish: true
}
}
}
}
```

or its `esm` version:

```javascript
export default function buildProfiles() {
const common = {
requireModule: ['ts-node/register'],
require: ['support/**/*.ts'],
worldParameters: {
appUrl: process.env.MY_APP_URL || 'http://localhost:3000/'
}
}

return {
default: {
...common,
format: ['progress-bar', 'html:cucumber-report.html'],
},
ci: {
...common,
format: ['html:cucumber-report.html'],
publish: true
}
}
}
```

This way the `buildProfiles` function will be invoked to discover profiles.

## Using Profiles for Arguments

Cucumber doesn't allow custom command line arguments. For example:
Expand Down
19 changes: 19 additions & 0 deletions features/profiles.feature
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,25 @@ Feature: default command line arguments
| -c |
| --config |

Scenario Outline: specifying a esm configuration file with default function profile
Given a file named ".cucumber-rc.mjs" with:
"""
export default function buildProfiles() {
return {
default: '--dry-run'
}
}
"""
When I run cucumber-js with `-c .cucumber-rc.mjs`
Then it outputs the text:
"""
-

1 scenario (1 skipped)
1 step (1 skipped)
<duration-stat>
"""

Scenario: specifying a configuration file that doesn't exist
When I run cucumber-js with `--config doesntexist.js`
Then it fails
Expand Down
35 changes: 33 additions & 2 deletions src/configuration/from_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,28 @@ export async function fromFile(
file: string,
profiles: string[] = []
): Promise<Partial<IConfiguration>> {
const definitions = await loadFile(logger, cwd, file)
if (!definitions.default) {
let definitions = await loadFile(logger, cwd, file)

const defaultDefinition: unknown = definitions.default

if (defaultDefinition) {
if (typeof defaultDefinition === 'function') {
logger.debug('Default function found; loading profiles')
definitions = await handleDefaultFunctionDefinition(
definitions,
defaultDefinition
)
}
} else {
logger.debug('No default profile defined in configuration file')
definitions.default = {}
}

if (profiles.length < 1) {
logger.debug('No profiles specified; using default profile')
profiles = ['default']
}

const definedKeys = Object.keys(definitions)
profiles.forEach((profileKey) => {
if (!definedKeys.includes(profileKey)) {
Expand All @@ -42,6 +55,24 @@ export async function fromFile(
)
}

async function handleDefaultFunctionDefinition(
definitions: Record<string, any>,
defaultDefinition: Function
): Promise<Record<string, any>> {
if (Object.keys(definitions).length > 1) {
throw new Error(
'Invalid profiles specified: if a default function definition is provided, no other static profiles should be specified'
)
}

const definitionsFromDefault = await defaultDefinition()

return {
default: {},
...definitionsFromDefault,
}
}

async function loadFile(
logger: ILogger,
cwd: string,
Expand Down
40 changes: 40 additions & 0 deletions src/configuration/from_file_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,46 @@ describe('fromFile', () => {
expect(result).to.deep.eq({ paths: ['other/path/*.feature'] })
})

it('should work with .mjs with default function', async () => {
const { logger, cwd } = await setup(
'cucumber.mjs',
`export default async function() {
return {
default: { paths: ['default/path/*.feature'] },
p1: { paths: ['p1/path/*.feature'] }
};
};`
)

const defaultResult = await fromFile(logger, cwd, 'cucumber.mjs', [
'default',
])
expect(defaultResult).to.deep.eq({ paths: ['default/path/*.feature'] })
})

it('should throw with .mjs with default function and additional static profiles', async () => {
const { logger, cwd } = await setup(
'cucumber.mjs',
`export default async function() {
return {
default: { paths: ['default/path/*.feature'] },
p1: { paths: ['p1/path/*.feature'] }
};
};
export const p1 = { paths: ['other/p1/path/*.feature'] };
export const p2 = { paths: ['p2/path/*.feature'] };`
)

try {
await fromFile(logger, cwd, 'cucumber.mjs', ['default'])
expect.fail('should have thrown')
} catch (error) {
expect(error.message).to.eq(
'Invalid profiles specified: if a default function definition is provided, no other static profiles should be specified'
)
}
})

it('should work with .cjs', async () => {
const { logger, cwd } = await setup(
'cucumber.cjs',
Expand Down