Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- Add pending and skipped via exceptions ([#238](https://github.com/cucumber/compatibility-kit/pull/238))

## [28.0.1] - 2026-02-01
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion devkit/samples/all-statuses/all-statuses.ndjson
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
{"testStepStarted":{"testCaseStartedId":"81","testStepId":"64","timestamp":{"seconds":0,"nanos":18000000}}}
{"testStepFinished":{"testCaseStartedId":"81","testStepId":"64","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}}
{"testStepStarted":{"testCaseStartedId":"81","testStepId":"65","timestamp":{"seconds":0,"nanos":20000000}}}
{"testStepFinished":{"testCaseStartedId":"81","testStepId":"65","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}}
{"testStepFinished":{"testCaseStartedId":"81","testStepId":"65","testStepResult":{"status":"PENDING","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}}
{"testStepStarted":{"testCaseStartedId":"81","testStepId":"66","timestamp":{"seconds":0,"nanos":22000000}}}
{"testStepFinished":{"testCaseStartedId":"81","testStepId":"66","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":23000000}}}
{"testCaseFinished":{"testCaseStartedId":"81","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}}
Expand Down
6 changes: 6 additions & 0 deletions devkit/samples/pending-exception/pending-exception.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Feature: Pending steps via exception
Some implementations support marking a step pending via throwing
an exception, which can include a message

Scenario: Pending via an exception
Given an unimplemented pending step
12 changes: 12 additions & 0 deletions devkit/samples/pending-exception/pending-exception.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{"meta":{"protocolVersion":"31.1.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Pending steps via exception\n Some implementations support marking a step pending via throwing\n an exception, which can include a message\n\n Scenario: Pending via an exception\n Given an unimplemented pending step\n","uri":"samples/pending-exception/pending-exception.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Pending steps via exception","description":" Some implementations support marking a step pending via throwing\n an exception, which can include a message","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"Pending via an exception","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"Given ","keywordType":"Context","text":"an unimplemented pending step"}],"examples":[]}}]},"comments":[],"uri":"samples/pending-exception/pending-exception.feature"}}
{"pickle":{"id":"3","uri":"samples/pending-exception/pending-exception.feature","location":{"line":5,"column":3},"astNodeIds":["1"],"tags":[],"name":"Pending via an exception","language":"en","steps":[{"id":"2","text":"an unimplemented pending step","type":"Context","astNodeIds":["0"]}]}}
{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an unimplemented pending step"},"sourceReference":{"uri":"samples/pending-exception/pending-exception.ts","location":{"line":3}}}}
{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}}
{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"5"}}
{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}}
{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"message":"TODO","exception":{"type":"PendingException","message":"TODO","stackTrace":"PendingException: TODO\nsamples/pending-exception/pending-exception.feature:6"},"status":"PENDING","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":false}}
5 changes: 5 additions & 0 deletions devkit/samples/pending-exception/pending-exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Given, PendingException } from '@cucumber/fake-cucumber'

Given('an unimplemented pending step', function () {
throw new PendingException('TODO')
})
6 changes: 3 additions & 3 deletions devkit/samples/pending/pending.ndjson
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@
{"testCase":{"id":"25","pickleId":"15","testSteps":[{"id":"26","pickleStepId":"13","stepDefinitionIds":["18"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"27","pickleStepId":"14","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"19"}}
{"testCaseStarted":{"id":"28","testCaseId":"20","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
{"testStepStarted":{"testCaseStartedId":"28","testStepId":"21","timestamp":{"seconds":0,"nanos":2000000}}}
{"testStepFinished":{"testCaseStartedId":"28","testStepId":"21","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
{"testStepFinished":{"testCaseStartedId":"28","testStepId":"21","testStepResult":{"status":"PENDING","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
{"testCaseFinished":{"testCaseStartedId":"28","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
{"testCaseStarted":{"id":"29","testCaseId":"22","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}}
{"testStepStarted":{"testCaseStartedId":"29","testStepId":"23","timestamp":{"seconds":0,"nanos":6000000}}}
{"testStepFinished":{"testCaseStartedId":"29","testStepId":"23","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}}
{"testStepStarted":{"testCaseStartedId":"29","testStepId":"24","timestamp":{"seconds":0,"nanos":8000000}}}
{"testStepFinished":{"testCaseStartedId":"29","testStepId":"24","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}}
{"testStepFinished":{"testCaseStartedId":"29","testStepId":"24","testStepResult":{"status":"PENDING","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}}
{"testCaseFinished":{"testCaseStartedId":"29","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}}
{"testCaseStarted":{"id":"30","testCaseId":"25","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}}
{"testStepStarted":{"testCaseStartedId":"30","testStepId":"26","timestamp":{"seconds":0,"nanos":12000000}}}
{"testStepFinished":{"testCaseStartedId":"30","testStepId":"26","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}}
{"testStepFinished":{"testCaseStartedId":"30","testStepId":"26","testStepResult":{"status":"PENDING","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}}
{"testStepStarted":{"testCaseStartedId":"30","testStepId":"27","timestamp":{"seconds":0,"nanos":14000000}}}
{"testStepFinished":{"testCaseStartedId":"30","testStepId":"27","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":15000000}}}
{"testCaseFinished":{"testCaseStartedId":"30","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}}
Expand Down
2 changes: 1 addition & 1 deletion devkit/samples/retry-pending/retry-pending.ndjson
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"5"}}
{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}}
{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"PENDING","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":false}}
7 changes: 7 additions & 0 deletions devkit/samples/skipped-exception/skipped-exception.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Feature: Skipping scenarios via exception

Some implementations support marking a step skipped via throwing
an exception, which can include a message

Scenario: Skipping via an exception
Given I skip a step
12 changes: 12 additions & 0 deletions devkit/samples/skipped-exception/skipped-exception.ndjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{"meta":{"protocolVersion":"31.1.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}}
{"source":{"data":"Feature: Skipping scenarios via exception\n\n Some implementations support marking a step skipped via throwing\n an exception, which can include a message\n\n Scenario: Skipping via an exception\n Given I skip a step\n","uri":"samples/skipped-exception/skipped-exception.feature","mediaType":"text/x.cucumber.gherkin+plain"}}
{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Skipping scenarios via exception","description":" Some implementations support marking a step skipped via throwing\n an exception, which can include a message","children":[{"scenario":{"id":"1","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Skipping via an exception","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"I skip a step"}],"examples":[]}}]},"comments":[],"uri":"samples/skipped-exception/skipped-exception.feature"}}
{"pickle":{"id":"3","uri":"samples/skipped-exception/skipped-exception.feature","location":{"line":6,"column":3},"astNodeIds":["1"],"tags":[],"name":"Skipping via an exception","language":"en","steps":[{"id":"2","text":"I skip a step","type":"Context","astNodeIds":["0"]}]}}
{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I skip a step"},"sourceReference":{"uri":"samples/skipped-exception/skipped-exception.ts","location":{"line":3}}}}
{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}}
{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"5"}}
{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}}
{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}}
{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"message":"skipping","exception":{"type":"SkippedException","message":"skipping","stackTrace":"SkippedException: skipping\nsamples/skipped-exception/skipped-exception.feature:7"},"status":"SKIPPED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}}
{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}}
{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":true}}
5 changes: 5 additions & 0 deletions devkit/samples/skipped-exception/skipped-exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Given, SkippedException } from '@cucumber/fake-cucumber'

Given('I skip a step', function () {
throw new SkippedException('skipping')
})
1 change: 1 addition & 0 deletions devkit/src/PendingException.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class PendingException extends Error {}
15 changes: 10 additions & 5 deletions devkit/src/Runner.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import {
AmbiguousError,
AssembledTestCase,
AssembledTestStep,
DataTable,
DefinedTestRunHook,
makeTestPlan,
SupportCodeLibrary,
UndefinedError,
} from '@cucumber/core'
import {
Envelope,
Expand Down Expand Up @@ -337,7 +335,6 @@ export class Runner {
if (returned === 'pending') {
mostOfResult = {
status: TestStepResultStatus.PENDING,
message: 'TODO',
}
} else if (returned === 'skipped') {
mostOfResult = {
Expand All @@ -347,7 +344,14 @@ export class Runner {
} catch (error: unknown) {
mostOfResult = {
...this.formatError(error as Error, testStep.sourceReference),
status: TestStepResultStatus.FAILED,
status: TestStepResultStatus.UNKNOWN,
}
if (mostOfResult.exception?.type === 'PendingException') {
mostOfResult.status = TestStepResultStatus.PENDING
} else if (mostOfResult.exception?.type === 'SkippedException') {
mostOfResult.status = TestStepResultStatus.SKIPPED
} else {
mostOfResult.status = TestStepResultStatus.FAILED
}
}
const endTime = this.stopwatch.now()
Expand All @@ -361,7 +365,8 @@ export class Runner {
const sourceFrame = sourceReference
? `${sourceReference.uri}:${sourceReference.location?.line}`
: '<unknown>'
const type = error.name || 'Error'

const type = error.constructor.name || 'Error'
const message = error.message
const stackTrace = type + ': ' + message + '\n' + sourceFrame
return {
Expand Down
1 change: 1 addition & 0 deletions devkit/src/SkippedException.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class SkippedException extends Error {}
2 changes: 2 additions & 0 deletions devkit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type HookFunction = (this: World) => any | Promise<any>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type StepFunction = (this: World, ...args: any[]) => any | Promise<any>

export * from './PendingException.js'
export * from './SkippedException.js'
export { DataTable } from '@cucumber/core'

export function ParameterType(
Expand Down