Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
44 changes: 26 additions & 18 deletions src/softAssert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@
*/
export class SoftAssertService {
private static instance: SoftAssertService
/**
* Fallback test ID used when no test context is set (e.g., in Cucumber steps).
*

Check failure on line 22 in src/softAssert.ts

View workflow job for this annotation

GitHub Actions / linux (22.x)

Trailing spaces not allowed
Comment thread
christian-bromann marked this conversation as resolved.
Outdated
* 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<string, SoftFailure[]> = new Map()
private currentTest: TestIdentifier | null = null

Expand Down Expand Up @@ -58,11 +66,17 @@

/**
* 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
Expand All @@ -77,40 +91,34 @@
}
}

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) {
Expand Down
16 changes: 11 additions & 5 deletions test/softAssertions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
Loading