From 22f73ca34ff27e422606f3a4eb914896ef63804a Mon Sep 17 00:00:00 2001 From: Mrunal chaudhari Date: Thu, 15 Jan 2026 22:28:58 +0530 Subject: [PATCH 1/2] fix: resolve soft assertions for Cucumber steps (#14573) --- src/softAssert.ts | 44 ++++++++++++++++++++++--------------- test/softAssertions.test.ts | 16 +++++++++----- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/softAssert.ts b/src/softAssert.ts index 73f1a9903..d9cc2a2fa 100644 --- a/src/softAssert.ts +++ b/src/softAssert.ts @@ -17,6 +17,14 @@ interface TestIdentifier { */ export class SoftAssertService { private static instance: SoftAssertService + /** + * Fallback test ID used when no test context is set (e.g., in Cucumber steps). + * + * NOTE: usage of this fallback ID in parallel execution environments may result + * in soft assertion failures from different tests being aggregated together. + * Ensure proper test context is set in hooks whenever possible. + */ + public static readonly GLOBAL_TEST_ID = '__global_soft_assert_context__' private failureMap: Map = new Map() private currentTest: TestIdentifier | null = null @@ -58,11 +66,17 @@ export class SoftAssertService { /** * Add a soft failure for the current test + * If no test context is set, failures are stored under a global fallback ID + * to ensure soft assertions work even when hooks haven't set the context */ public addFailure(error: Error, matcherName: string): void { - const testId = this.getCurrentTestId() - if (!testId) { - throw error // If no test context, throw the error immediately + // Use current test ID or fallback to global ID for frameworks where + // the test context might not be set (e.g., Cucumber without proper hook integration) + const testId = this.getCurrentTestId() || SoftAssertService.GLOBAL_TEST_ID + + // Ensure the failure map has an entry for this test ID + if (!this.failureMap.has(testId)) { + this.failureMap.set(testId, []) } // Extract stack information to get file and line number @@ -77,40 +91,34 @@ export class SoftAssertService { } } - const failures = this.failureMap.get(testId) || [] - failures.push({ error, matcherName, location }) - this.failureMap.set(testId, failures) + // We know the entry exists from the check above + this.failureMap.get(testId)!.push({ error, matcherName, location }) } /** * Get all failures for a specific test + * Falls back to global test ID if no context is set */ public getFailures(testId?: string): SoftFailure[] { - const id = testId || this.getCurrentTestId() - if (!id) { - return [] - } + const id = testId || this.getCurrentTestId() || SoftAssertService.GLOBAL_TEST_ID return this.failureMap.get(id) || [] } /** * Clear failures for a specific test + * Falls back to global test ID if no context is set */ public clearFailures(testId?: string): void { - const id = testId || this.getCurrentTestId() - if (id) { - this.failureMap.delete(id) - } + const id = testId || this.getCurrentTestId() || SoftAssertService.GLOBAL_TEST_ID + this.failureMap.delete(id) } /** * Throw an aggregated error if there are failures for the current test + * Falls back to global test ID if no context is set */ public assertNoFailures(testId?: string): void { - const id = testId || this.getCurrentTestId() - if (!id) { - return - } + const id = testId || this.getCurrentTestId() || SoftAssertService.GLOBAL_TEST_ID const failures = this.getFailures(id) if (failures.length === 0) { diff --git a/test/softAssertions.test.ts b/test/softAssertions.test.ts index 69e07d1c0..d524252e5 100644 --- a/test/softAssertions.test.ts +++ b/test/softAssertions.test.ts @@ -225,14 +225,20 @@ describe('Soft Assertions', () => { expect(expectWdio.getSoftFailures('isolation-test-2').length).toBe(1) }) - it('should handle calls without test context gracefully', async () => { + it('should handle calls without test context using global fallback ID', async () => { const softService = SoftAssertService.getInstance() softService.clearCurrentTest() // No test context - // Should throw immediately when no test context - await expect(async () => { - await expectWdio.soft(el).toHaveText('Expected Text') - }).rejects.toThrow() + // Should NOT throw - instead should store under global fallback ID + await expectWdio.soft(el).toHaveText('Expected Text') + + // Failures should be stored under the global ID + const failures = expectWdio.getSoftFailures(SoftAssertService.GLOBAL_TEST_ID) + expect(failures.length).toBe(1) + expect(failures[0].matcherName).toBe('toHaveText') + + // Clean up + expectWdio.clearSoftFailures(SoftAssertService.GLOBAL_TEST_ID) }) it('should handle rapid concurrent soft assertions', async () => { From 15594652c24436633785517d6accc588fb5eeb5f Mon Sep 17 00:00:00 2001 From: Christian Bromann Date: Sat, 17 Jan 2026 10:26:58 -0800 Subject: [PATCH 2/2] Update src/softAssert.ts --- src/softAssert.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/softAssert.ts b/src/softAssert.ts index d9cc2a2fa..d696f337a 100644 --- a/src/softAssert.ts +++ b/src/softAssert.ts @@ -19,7 +19,7 @@ export class SoftAssertService { private static instance: SoftAssertService /** * Fallback test ID used when no test context is set (e.g., in Cucumber steps). - * + * * NOTE: usage of this fallback ID in parallel execution environments may result * in soft assertion failures from different tests being aggregated together. * Ensure proper test context is set in hooks whenever possible.