Programmatic API for running cucumber-js #2041
Replies: 71 comments 1 reply
-
Paging for initial feedback: @aslakhellesoy @charlierudolph @aurelien-reeves @mattwynne @nicojs @jan-molak |
Beta Was this translation helpful? Give feedback.
-
I love this proposal! We already an API like this in fake-cucumber's runCucumber |
Beta Was this translation helpful? Give feedback.
-
@davidjgoss - sounds great! For your reference, here's how Serenity/JS invokes Cucumber at the moment - Being able to provide an options object instead of argv would be much nicer 👍🏻 |
Beta Was this translation helpful? Give feedback.
-
Love it! |
Beta Was this translation helpful? Give feedback.
-
While specifying that new public API, we may also consider what recently happened with issue #1489 and think about providing public APIs to have more and better interaction with the filters and the resulting features under test |
Beta Was this translation helpful? Give feedback.
-
And to add to that - let's make something that would make it easier for Cucumber-Electron too: |
Beta Was this translation helpful? Give feedback.
-
Having a public API is better than nothing, so go ahead 👍! Preferably I would also have an API to load profiles using the same rules as loadProfiles(directory = process.cwd()): Record<string, Profile> StrykerJS will also rely heavily on the custom_formatters API and the events published by the |
Beta Was this translation helpful? Give feedback.
-
This is a good point. Profiles (in their current form at least) are fundamentally coupled to the CLI so it feels right to keep them on that side of the boundary, but we could still expose a function to load them and generate a partial options object. |
Beta Was this translation helpful? Give feedback.
-
I think we could include an option for providing a custom pickle filter when calling the API (in addition to the names, tags etc that drive the built-in filtering). |
Beta Was this translation helpful? Give feedback.
-
The current syntax for profiles if very command-line-y. I would ❤️❤️❤️ to be able to specify profiles in a more generic format such as JSON, JavaScript, YAML or environment variables. In JSON it could look like this:
{
"default": {
"requireModule": ["ts-node/register"],
"require": ["support/**/*./ts"],
"worldParameters": {
"appUrl": "http://localhost:3000/",
},
"format": ["progress-bar", "html:./cucumber-report.html"]
},
"ci": {
"requireModule": ["ts-node/register"],
"require": ["support/**/*./ts"],
"worldParameters": {
"appUrl": "http://localhost:3000/",
},
"format": ["html:./cucumber-report.html"],
"publish": true
}
} Or, using JavaScript
const common = {
"requireModule": ["ts-node/register"],
"require": ["support/**/*./ts"],
"worldParameters": {
"appUrl": "http://localhost:3000/",
}
}
module.exports = {
default: {
...common,
"format": ["progress-bar", "html:./cucumber-report.html"]
},
ci: {
...common,
"format": ["html:./cucumber-report.html"],
"publish": true
}
} Or even with environment variables (for example loaded with a tool like dotenv):
CUCUMBER_PROFILE_DEFAULT_REQUIREMODULE=ts-node/register
CUCUMBER_PROFILE_DEFAULT_REQUIRE=ts-node/register
CUCUMBER_PROFILE_DEFAULT_WORLDPARAMETERS_APPURL=http://localhost:3000/
CUCUMBER_PROFILE_DEFAULT_FORMAT=progress-bar,html:./cucumber-report.html
CUCUMBER_PROFILE_CI_REQUIREMODULE=ts-node/register
CUCUMBER_PROFILE_CI_REQUIRE=ts-node/register
CUCUMBER_PROFILE_CI_WORLDPARAMETERS_APPURL=http://localhost:3000/
CUCUMBER_PROFILE_CI_FORMAT=progress-bar,html:./cucumber-report.html
CUCUMBER_PROFILE_CI_PUBLISH=true In fact, the config library does exactly this. We never ended up integrating it in Cucumber-JVM because other stuff got in the way, but maybe we could give it a go with a JavaScript implementation? |
Beta Was this translation helpful? Give feedback.
-
@aslakhellesoy agree that would be awesome! I'll try and get a POC going for this proposal so we have something a bit more concrete to talk around, and would love to make profiles right as part of it (4.5 years and counting on #751 😄) |
Beta Was this translation helpful? Give feedback.
-
Refs. #1004 |
Beta Was this translation helpful? Give feedback.
-
Yes, that would be awesome and much appreciated from the point of view of plugin creators.
That sounds great! And is also exactly the reason I would appreciate an API to load them the same way as cucumberJS does. Loading a single |
Beta Was this translation helpful? Give feedback.
-
Q: Would I be able to run We want to load the environment and run the tests multiple times in quick succession while changing a global variable in order to switch the active mutant. Right now, we're using the Pseudo code of our use case: const profiles = await loadProfiles();
const options = {
...profiles.default,
formatter: require.resolve('./our-awesomely-crafted-formatter'),
some: 'other options we want to override',
}
const cucumber = new Cucumber(options);
// Allow cucumber to load the step definitions once.
// This is `async`, so support for esm can be added without a breaking change
await cucumber.initialize();
// Initial test run ("dry run"), without mutants active
await cucumber.run();
collectMutantCoveragePerTestFromFormatter();
// Start mutation testing:
global.activeMutant = 1;
await cucumber.run({ features: ['features/a.feature:24']);
collectResultsFromFormatterToDetermineKilledOrSurvivedMutant()
global.activeMutant = 2;
await cucumber.run({ features: ['features/b.feature:24:25:26', 'features/c.feature:12']);
collectResultsFromFormatterToDetermineKilledOrSurvivedMutant()
// etc |
Beta Was this translation helpful? Give feedback.
-
@nicojs definitely agree we need this, it's come up a few times before with e.g. people wanting to run cucumber in a lambda, and I'd also like to add an interactive mode which would need it too. What I had sketched out so far was in more of a functional style but same fundamental concept I think: const runnerOptions = {
support: {
require: ['features/support/**/*.js']
}
}
// initial run returns support code library
const { support } = await runCucumber(runnerOptions)
// subsequent run reuses support code library
await runCucumber({
...runnerOptions,
support
}) |
Beta Was this translation helpful? Give feedback.
-
I might be too late to the party to give input, but the API still seems fairly similar to running cucumber from the CLI, and I'm having some trouble understanding how to apply this for my use case. I want to write a test file that contains all of the steps, then run those steps on one or more associated feature-files. I'm using TS, and I want to be able to compile the test and then run it repeatedly as I develop the feature file without having to re-do the transpilation step repeatedly, which slows down the development loop. It's already been mentioned that I can use |
Beta Was this translation helpful? Give feedback.
-
Thanks @davidjgoss that was really helpful to get It looks like the update from rc.2 to rc.3 may have broken our Have you seen this error before:
app_scan_steps.js is here I've created a diff where the only difference is rc.2 vs rc.3 which has confirmed that this error doesn't exist with rc.2 and is introduced with rc.3. Thanks. |
Beta Was this translation helpful? Give feedback.
-
There doesn't appear to be a lib/api/support.js in the root of the cucumber repo, but I found a src/api/support.ts which appears to be new in rc.3 and it appears to be So it seems that cucumber in rc.2 supports ESM but rc.3 has regressed to only supporting CJS? |
Beta Was this translation helpful? Give feedback.
-
@binarymist it’s still there but uses a specfic option for import vs require now - previously it was inferred but the heuristics were too fuzzy so we changed it to be explicit: |
Beta Was this translation helpful? Give feedback.
-
(We should add better messaging for that, thanks for raising.) |
Beta Was this translation helpful? Give feedback.
-
@garfieldnate just to make sure I understand, you want to:
If so, I think you could implement this now. You can't create the support code library inline, but you can load it once and use it multiple times. Something like: import {loadConfiguration, loadSupport, runCucumber} from '@cucumber/cucumber/api'
const {runConfiguration} = await loadConfiguration()
const support = await loadSupport(runConfiguration)
const merged = {...runConfiguration, support}
// do this bit as many times as you want e.g. in a loop or callback
await runCucumber(merged) |
Beta Was this translation helpful? Give feedback.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
-
Just dropping back in here to note that we'll soon be putting out a minor release with these deprecations:
The idea is that the new API ( The deprecations will be fairly long-running - it's going to be a year or more before we actually remove these things, and it will be in a major version - and in the meantime I'm happy to hear any feedback around gaps/flaws in the API functionality and help out with migrations. Relatedly, there's a couple more things currently on the main entry point which would be good to see if we can serve better with the API, this time around support code:
From what I can see looking at usages in public projects, there is at least:
Thoughts welcome @binarymist @christian-bromann @jan-molak @nicojs |
Beta Was this translation helpful? Give feedback.
-
@davidjgoss -- is there way for Programmatically rerun failed scenarios. how the @rerun.txt has to be passed to runCucumber |
Beta Was this translation helpful? Give feedback.
-
Closing this off now as the API has been around a while. Feel free to raise an issue if needed! |
Beta Was this translation helpful? Give feedback.
-
Problem
Currently we don't have a good way to programmatically run cucumber-js. The need is from several angles:
As-is
What tends to happen at the moment is a new instance of
Cli
is created with strung-together argv input. It's obviously very unweildy and also isn't on the public API.Sometimes (possibly due to perceived fragility of the above) frameworks will just rely on the cucumber-js CLI but struggle to find ways to integrate and have their own options.
The
Runtime
class is currently part of the public API but it's not useful in these contexts, depending on the pickles and support code to be provided by the caller.Proposal
Two components in the project:
runCucumber
New async function that executes a test run in-process. Responsibilities:
This would be part of the public API and we'd encourage framework maintainers to use it when "wrapping" cucumber-js. We'd also use it for our own testing.
As much as possible, it would avoid direct interaction with
process
, instead accepting normalised options and stream interfaces for output, and leaving it to the caller to decide how to exit based on the result or an unhandled error.Also
Runtime
should come off the public API as it's really an internal thing.CLI
Effectively a "client" of
runCucumber
. Responsibilities:runCucumber
with the resolved optionsThis would continue to not be on the public API. Also it would only use functions/interfaces that are on the public API, such that we could easily break it out into its own package at some point, as is a common pattern now with projects like Jest.
This decoupling also paves the way for some interesting new CLI features without having them bleed into the internals, e.g.:
--gui
for the cucumber-electron stuff--interactive
for quick targeted reruns when TDD'ingetc
We would also expose functions (consumable by the CLI and by others) for:
i18nKeywords
andi18nLanguages
Timescale
We'd target this at the upcoming 8.0.0 release.
Beta Was this translation helpful? Give feedback.
All reactions