fix(@wdio/cucumber-framework): report step results in parallel mode as in sequential mode by plum117 · Pull Request #16161 · webdriverio/webdriverio · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
edb0278
refactor(@wdio/cucumber-framework): one step result rule for sequenti…
plum117 Oct 21, 2026
8318a88
fix(@wdio/cucumber-framework): report undefined, ambiguous and failed…
plum117 Oct 21, 2026
28ca545
fix(@wdio/cucumber-framework): use cucumberOpts.order in parallel mode
plum117 Oct 21, 2026
cd920a0
fix(@wdio/cucumber-framework): give Cucumber hooks their sequential t…
plum117 Oct 21, 2026
85741ac
fix(@wdio/cucumber-framework): report undefined and ambiguous scenari…
plum117 Oct 21, 2026
af3765e
perf(@wdio/cucumber-framework): build the feature stack only for an e…
plum117 Oct 21, 2026
f104b29
fix(@wdio/cucumber-framework): give features their sequential title i…
plum117 Oct 21, 2026
7b584f0
fix(@wdio/cucumber-framework): report the scenarios after a failFast …
plum117 Oct 21, 2026
1c8f70c
fix(@wdio/cucumber-framework): count the scenarios after a failFast s…
plum117 Oct 21, 2026
193d101
fix(@wdio/cucumber-framework): run a spec with line numbers in parall…
plum117 Oct 21, 2026
0c373a4
docs: list how Cucumber parallel runs differ from sequential runs
plum117 Oct 21, 2026
0ede749
fix(@wdio/cucumber-framework): mark a pending step as not passed in p…
plum117 Oct 21, 2026
34bf42f
refactor(@wdio/cucumber-framework): one ambiguous step message rule f…
plum117 Oct 21, 2026
2feef08
docs(@wdio/cucumber-framework): correct the cucumberOpts docs
plum117 Oct 21, 2026
de81b36
refactor(@wdio/cucumber-framework): remove the dead retry check of th…
plum117 Oct 21, 2026
90483e4
fix(@wdio/cucumber-framework): use one random order seed and log it once
plum117 Oct 21, 2026
97c1dfa
docs(@wdio/spec-reporter): say that Cucumber parallel mode is not sup…
plum117 Oct 21, 2026
b2bbd29
docs(@wdio/spec-reporter): say that Mocha parallel mode also nests su…
plum117 Oct 21, 2026
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
76 changes: 63 additions & 13 deletions packages/wdio-cucumber-framework/README.md
87 changes: 15 additions & 72 deletions packages/wdio-cucumber-framework/src/cucumberFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path from 'node:path'
import logger from '@wdio/logger'

import type { Payload } from './types.js'
import { convertStatus, getAmbiguousStepMessage } from './utils.js'
import { getFeatureStack, getStepResult, withAmbiguousMessage } from './utils.js'

const log = logger('CucumberFormatter')

Expand All @@ -24,7 +24,6 @@ import type {
TestStepStarted,
TestStepFinished,
TestStepResult,
TestCaseFinished,
Hook,
StepDefinition
} from '@cucumber/messages'
Expand Down Expand Up @@ -223,53 +222,21 @@ export default class CucumberFormatter extends Formatter {
step: PickleStep | undefined,
result: TestStepResult
) {
let state = convertStatus(result.status)
let error = result.message ? new Error(result.message) : undefined
const title = step ? step?.text : this.getTitle(scenario)
let titleSuffix = ''

if (result.status === Status.UNDEFINED) {
if (this.ignoreUndefinedDefinitions) {
/**
* mark test as pending
*/
state = 'pending'
titleSuffix = ' (undefined step)'
} else {
/**
* mark test as failed
*/
state = 'fail'
this.failedCount++

const err = new Error(
(step ? `Step "${title}" is not defined. ` : `Scenario ${title} has undefined steps. `) +
'You can ignore this error by setting cucumberOpts.ignoreUndefinedDefinitions as true.'
)

err.stack = `${err.message}\n${this.getFeatureStack(uri, feature, scenario, step)}`
error = err
const { state, error, pendingReason, titleSuffix, failed } = getStepResult(
result,
{
title,
isStep: Boolean(step),
ignoreUndefinedDefinitions: this.ignoreUndefinedDefinitions,
failAmbiguousDefinitions: this.failAmbiguousDefinitions,
featureStack: () => getFeatureStack(uri, feature, scenario, step),
}
} else if (result.status === Status.FAILED && !(result as unknown as TestCaseFinished).willBeRetried) {
state = 'fail'
this.failedCount++
error = new Error(result.message?.split('\n')[0])
error.stack = result.message as string
} else if (result.status === Status.AMBIGUOUS && this.failAmbiguousDefinitions) {
state = 'fail'
)
if (failed) {
this.failedCount++
const message = result.message || (
(step
? `Step "${title}" matches multiple step definitions. `
: `Scenario "${title}" contains a step matching multiple step definitions. `) +
'Make the step definitions unique by removing duplicate or overlapping expressions.'
)
error = new Error(message.split('\n')[0])
error.stack = `${message}\n${this.getFeatureStack(uri, feature, scenario, step)}`
}

// reporters show the reason of a skipped test from `pendingReason`, not from its error
const pendingReason = result.status === Status.AMBIGUOUS && state === 'skip' ? result.message : undefined
const common = {
title: title,
state,
Expand Down Expand Up @@ -305,25 +272,6 @@ export default class CucumberFormatter extends Formatter {
this.emit('test:' + state, payload)
}

/**
* stack lines with the location of the scenario and the step in the feature file
*/
private getFeatureStack(uri: string, feature: Feature, scenario: Pickle, step?: PickleStep) {
let stack = `\tat Feature(${uri}):1:1\n`
const children = feature.children?.flatMap((child) => child.rule ? child.rule.children : [child]) ?? []
const featScenario = children.find(c => scenario.astNodeIds && c.scenario?.id === scenario.astNodeIds[0])?.scenario
if (featScenario) {
stack += `\tat Scenario(${featScenario.name}):${featScenario.location?.line}:${featScenario.location?.column}\n`

const steps = [...children.flatMap((c) => c.background?.steps ?? []), ...(featScenario.steps ?? [])]
const featStep = steps.find(s => step?.astNodeIds && s.id === step.astNodeIds[0])
if (featStep) {
stack += `\tat Step(${featStep.text}):${featStep.location?.line}:${featStep.location?.column}\n`
}
}
return stack
}

onGherkinDocument(gherkinDocEvent: GherkinDocument) {
this.updateCurrentPickle({
uri: gherkinDocEvent.uri,
Expand Down Expand Up @@ -538,17 +486,12 @@ export default class CucumberFormatter extends Formatter {
*/
private withAmbiguousMessage(event: TestStepFinished): TestStepFinished {
const result = event.testStepResult
if (result?.status !== Status.AMBIGUOUS || result.message) {
return event
}
const testStep = this._testCases
const stepDefinitionIds = () => this._testCases
.find((tc) => tc.id === this._currentTestCase?.testCaseId)
?.testSteps.find((step) => step.id === event.testStepId)
const stepDefinitions = (testStep?.stepDefinitionIds ?? [])
.map((id) => this._stepDefinitions.get(id))
.filter((definition): definition is StepDefinition => Boolean(definition))
const message = getAmbiguousStepMessage(stepDefinitions)
return message ? { ...event, testStepResult: { ...result, message } } : event
?.stepDefinitionIds ?? []
const withMessage = result && withAmbiguousMessage(result, stepDefinitionIds, this._stepDefinitions)
return withMessage === result ? event : { ...event, testStepResult: withMessage }
}

onTestStepFinished(testStepFinishedEvent: TestStepFinished) {
Expand Down
13 changes: 11 additions & 2 deletions packages/wdio-cucumber-framework/src/index.ts
Loading
Loading