Skip to content

Commit

Permalink
update config to support default definition function (#2384)
Browse files Browse the repository at this point in the history
  • Loading branch information
notaphplover committed Apr 21, 2024
1 parent a02d90e commit 66d0b1b
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
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
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
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
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
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

0 comments on commit 66d0b1b

Please sign in to comment.