Skip to content

Commit 7ef0427

Browse files
fix: resolve soft assertions for Cucumber steps (#14573) (#1994)
* fix: resolve soft assertions for Cucumber steps (#14573) * Update src/softAssert.ts --------- Co-authored-by: Christian Bromann <[email protected]>
1 parent 9e1898d commit 7ef0427

2 files changed

Lines changed: 37 additions & 23 deletions

File tree

src/softAssert.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ interface TestIdentifier {
1717
*/
1818
export class SoftAssertService {
1919
private static instance: SoftAssertService
20+
/**
21+
* Fallback test ID used when no test context is set (e.g., in Cucumber steps).
22+
*
23+
* NOTE: usage of this fallback ID in parallel execution environments may result
24+
* in soft assertion failures from different tests being aggregated together.
25+
* Ensure proper test context is set in hooks whenever possible.
26+
*/
27+
public static readonly GLOBAL_TEST_ID = '__global_soft_assert_context__'
2028
private failureMap: Map<string, SoftFailure[]> = new Map()
2129
private currentTest: TestIdentifier | null = null
2230

@@ -58,11 +66,17 @@ export class SoftAssertService {
5866

5967
/**
6068
* Add a soft failure for the current test
69+
* If no test context is set, failures are stored under a global fallback ID
70+
* to ensure soft assertions work even when hooks haven't set the context
6171
*/
6272
public addFailure(error: Error, matcherName: string): void {
63-
const testId = this.getCurrentTestId()
64-
if (!testId) {
65-
throw error // If no test context, throw the error immediately
73+
// Use current test ID or fallback to global ID for frameworks where
74+
// the test context might not be set (e.g., Cucumber without proper hook integration)
75+
const testId = this.getCurrentTestId() || SoftAssertService.GLOBAL_TEST_ID
76+
77+
// Ensure the failure map has an entry for this test ID
78+
if (!this.failureMap.has(testId)) {
79+
this.failureMap.set(testId, [])
6680
}
6781

6882
// Extract stack information to get file and line number
@@ -77,40 +91,34 @@ export class SoftAssertService {
7791
}
7892
}
7993

80-
const failures = this.failureMap.get(testId) || []
81-
failures.push({ error, matcherName, location })
82-
this.failureMap.set(testId, failures)
94+
// We know the entry exists from the check above
95+
this.failureMap.get(testId)!.push({ error, matcherName, location })
8396
}
8497

8598
/**
8699
* Get all failures for a specific test
100+
* Falls back to global test ID if no context is set
87101
*/
88102
public getFailures(testId?: string): SoftFailure[] {
89-
const id = testId || this.getCurrentTestId()
90-
if (!id) {
91-
return []
92-
}
103+
const id = testId || this.getCurrentTestId() || SoftAssertService.GLOBAL_TEST_ID
93104
return this.failureMap.get(id) || []
94105
}
95106

96107
/**
97108
* Clear failures for a specific test
109+
* Falls back to global test ID if no context is set
98110
*/
99111
public clearFailures(testId?: string): void {
100-
const id = testId || this.getCurrentTestId()
101-
if (id) {
102-
this.failureMap.delete(id)
103-
}
112+
const id = testId || this.getCurrentTestId() || SoftAssertService.GLOBAL_TEST_ID
113+
this.failureMap.delete(id)
104114
}
105115

106116
/**
107117
* Throw an aggregated error if there are failures for the current test
118+
* Falls back to global test ID if no context is set
108119
*/
109120
public assertNoFailures(testId?: string): void {
110-
const id = testId || this.getCurrentTestId()
111-
if (!id) {
112-
return
113-
}
121+
const id = testId || this.getCurrentTestId() || SoftAssertService.GLOBAL_TEST_ID
114122

115123
const failures = this.getFailures(id)
116124
if (failures.length === 0) {

test/softAssertions.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,20 @@ describe('Soft Assertions', () => {
225225
expect(expectWdio.getSoftFailures('isolation-test-2').length).toBe(1)
226226
})
227227

228-
it('should handle calls without test context gracefully', async () => {
228+
it('should handle calls without test context using global fallback ID', async () => {
229229
const softService = SoftAssertService.getInstance()
230230
softService.clearCurrentTest() // No test context
231231

232-
// Should throw immediately when no test context
233-
await expect(async () => {
234-
await expectWdio.soft(el).toHaveText('Expected Text')
235-
}).rejects.toThrow()
232+
// Should NOT throw - instead should store under global fallback ID
233+
await expectWdio.soft(el).toHaveText('Expected Text')
234+
235+
// Failures should be stored under the global ID
236+
const failures = expectWdio.getSoftFailures(SoftAssertService.GLOBAL_TEST_ID)
237+
expect(failures.length).toBe(1)
238+
expect(failures[0].matcherName).toBe('toHaveText')
239+
240+
// Clean up
241+
expectWdio.clearSoftFailures(SoftAssertService.GLOBAL_TEST_ID)
236242
})
237243

238244
it('should handle rapid concurrent soft assertions', async () => {

0 commit comments

Comments
 (0)