Skip to content

Commit 66d0b1b

Browse files
authored
update config to support default definition function (#2384)
1 parent a02d90e commit 66d0b1b

File tree

5 files changed

+156
-3
lines changed

5 files changed

+156
-3
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
88
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber.
99

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

1315
## [10.4.0] - 2024-04-07
1416
### Added

docs/profiles.md

+62-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ The short tag is `-p`
1414
cucumber-js -p my_profile
1515
```
1616

17-
## Simple Example
17+
## Default profiles
18+
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.
19+
20+
## Examples
21+
22+
### Simple Example
1823

1924
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:
2025

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

75+
### Example using a default function
76+
77+
```javascript
78+
module.exports = {
79+
default: function buildProfiles() {
80+
const common = {
81+
requireModule: ['ts-node/register'],
82+
require: ['support/**/*.ts'],
83+
worldParameters: {
84+
appUrl: process.env.MY_APP_URL || 'http://localhost:3000/'
85+
}
86+
}
87+
88+
return {
89+
default: {
90+
...common,
91+
format: ['progress-bar', 'html:cucumber-report.html'],
92+
},
93+
ci: {
94+
...common,
95+
format: ['html:cucumber-report.html'],
96+
publish: true
97+
}
98+
}
99+
}
100+
}
101+
```
102+
103+
or its `esm` version:
104+
105+
```javascript
106+
export default function buildProfiles() {
107+
const common = {
108+
requireModule: ['ts-node/register'],
109+
require: ['support/**/*.ts'],
110+
worldParameters: {
111+
appUrl: process.env.MY_APP_URL || 'http://localhost:3000/'
112+
}
113+
}
114+
115+
return {
116+
default: {
117+
...common,
118+
format: ['progress-bar', 'html:cucumber-report.html'],
119+
},
120+
ci: {
121+
...common,
122+
format: ['html:cucumber-report.html'],
123+
publish: true
124+
}
125+
}
126+
}
127+
```
128+
129+
This way the `buildProfiles` function will be invoked to discover profiles.
130+
70131
## Using Profiles for Arguments
71132

72133
Cucumber doesn't allow custom command line arguments. For example:

features/profiles.feature

+19
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,25 @@ Feature: default command line arguments
9595
| -c |
9696
| --config |
9797

98+
Scenario Outline: specifying a esm configuration file with default function profile
99+
Given a file named ".cucumber-rc.mjs" with:
100+
"""
101+
export default function buildProfiles() {
102+
return {
103+
default: '--dry-run'
104+
}
105+
}
106+
"""
107+
When I run cucumber-js with `-c .cucumber-rc.mjs`
108+
Then it outputs the text:
109+
"""
110+
-
111+
112+
1 scenario (1 skipped)
113+
1 step (1 skipped)
114+
<duration-stat>
115+
"""
116+
98117
Scenario: specifying a configuration file that doesn't exist
99118
When I run cucumber-js with `--config doesntexist.js`
100119
Then it fails

src/configuration/from_file.ts

+33-2
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,28 @@ export async function fromFile(
1515
file: string,
1616
profiles: string[] = []
1717
): Promise<Partial<IConfiguration>> {
18-
const definitions = await loadFile(logger, cwd, file)
19-
if (!definitions.default) {
18+
let definitions = await loadFile(logger, cwd, file)
19+
20+
const defaultDefinition: unknown = definitions.default
21+
22+
if (defaultDefinition) {
23+
if (typeof defaultDefinition === 'function') {
24+
logger.debug('Default function found; loading profiles')
25+
definitions = await handleDefaultFunctionDefinition(
26+
definitions,
27+
defaultDefinition
28+
)
29+
}
30+
} else {
2031
logger.debug('No default profile defined in configuration file')
2132
definitions.default = {}
2233
}
34+
2335
if (profiles.length < 1) {
2436
logger.debug('No profiles specified; using default profile')
2537
profiles = ['default']
2638
}
39+
2740
const definedKeys = Object.keys(definitions)
2841
profiles.forEach((profileKey) => {
2942
if (!definedKeys.includes(profileKey)) {
@@ -42,6 +55,24 @@ export async function fromFile(
4255
)
4356
}
4457

58+
async function handleDefaultFunctionDefinition(
59+
definitions: Record<string, any>,
60+
defaultDefinition: Function
61+
): Promise<Record<string, any>> {
62+
if (Object.keys(definitions).length > 1) {
63+
throw new Error(
64+
'Invalid profiles specified: if a default function definition is provided, no other static profiles should be specified'
65+
)
66+
}
67+
68+
const definitionsFromDefault = await defaultDefinition()
69+
70+
return {
71+
default: {},
72+
...definitionsFromDefault,
73+
}
74+
}
75+
4576
async function loadFile(
4677
logger: ILogger,
4778
cwd: string,

src/configuration/from_file_spec.ts

+40
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,46 @@ describe('fromFile', () => {
9898
expect(result).to.deep.eq({ paths: ['other/path/*.feature'] })
9999
})
100100

101+
it('should work with .mjs with default function', async () => {
102+
const { logger, cwd } = await setup(
103+
'cucumber.mjs',
104+
`export default async function() {
105+
return {
106+
default: { paths: ['default/path/*.feature'] },
107+
p1: { paths: ['p1/path/*.feature'] }
108+
};
109+
};`
110+
)
111+
112+
const defaultResult = await fromFile(logger, cwd, 'cucumber.mjs', [
113+
'default',
114+
])
115+
expect(defaultResult).to.deep.eq({ paths: ['default/path/*.feature'] })
116+
})
117+
118+
it('should throw with .mjs with default function and additional static profiles', async () => {
119+
const { logger, cwd } = await setup(
120+
'cucumber.mjs',
121+
`export default async function() {
122+
return {
123+
default: { paths: ['default/path/*.feature'] },
124+
p1: { paths: ['p1/path/*.feature'] }
125+
};
126+
};
127+
export const p1 = { paths: ['other/p1/path/*.feature'] };
128+
export const p2 = { paths: ['p2/path/*.feature'] };`
129+
)
130+
131+
try {
132+
await fromFile(logger, cwd, 'cucumber.mjs', ['default'])
133+
expect.fail('should have thrown')
134+
} catch (error) {
135+
expect(error.message).to.eq(
136+
'Invalid profiles specified: if a default function definition is provided, no other static profiles should be specified'
137+
)
138+
}
139+
})
140+
101141
it('should work with .cjs', async () => {
102142
const { logger, cwd } = await setup(
103143
'cucumber.cjs',

0 commit comments

Comments
 (0)