@koenvanbelle/cypress-soft-assertions 2.3.1 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +37 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -130,6 +130,23 @@ describe('Test Suite', () => {
|
|
|
130
130
|
});
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
### `soft_it.expectFailure`
|
|
134
|
+
|
|
135
|
+
Use `soft_it.expectFailure()` for browser behavior specs that intentionally trigger
|
|
136
|
+
soft failures but should still finish green overall.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
soft_it.expectFailure('captures a known soft failure without failing the spec', () => {
|
|
140
|
+
cy.get('#title').should('have.text', 'Wrong Title');
|
|
141
|
+
cy.get('#status').should('have.text', 'Ready');
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
This mode still verifies that a final `SoftAssertionError` was produced. If the
|
|
146
|
+
test completes without any soft failure, the test fails.
|
|
147
|
+
|
|
148
|
+
`soft_it.expectFailure.only(...)` is also available.
|
|
149
|
+
|
|
133
150
|
## How it works
|
|
134
151
|
|
|
135
152
|
1. `soft_it()` patches Chai's assertion mechanism for the duration of the test.
|
|
@@ -158,7 +175,7 @@ SOFT ASSERTION FAILURES (3 failed):
|
|
|
158
175
|
- **Retriable assertions are retried**: `.should()` and `.and()` assertions are retried by Cypress before being captured as soft failures. Assertions that eventually pass are not reported.
|
|
159
176
|
- **Non-assertion errors still stop execution**: Network errors, visit timeouts, and other command errors are captured as soft failures but may prevent subsequent commands from running.
|
|
160
177
|
- **State resets between tests**: Each `soft_it()` test starts with a clean slate — failures from one test never leak into another.
|
|
161
|
-
- **Timeout-aware retries**: The plugin respects Cypress's `defaultCommandTimeout` and per-command `{ timeout }` overrides. Retriable assertions (`.should()`, `.and()`) are retried
|
|
178
|
+
- **Timeout-aware retries**: The plugin respects Cypress's `defaultCommandTimeout` and per-command `{ timeout }` overrides. Retriable assertions (`.should()`, `.and()`) are retried until just before the effective timeout expires, using `max(timeout - 100ms, timeout * 0.9)` as the swallow threshold. For slow applications, increase the timeout to give assertions more time to pass:
|
|
162
179
|
```typescript
|
|
163
180
|
// Global: set in cypress.config.ts
|
|
164
181
|
e2e: { defaultCommandTimeout: 10000 }
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,14 @@ declare global {
|
|
|
39
39
|
* Run only this test (like it.only)
|
|
40
40
|
*/
|
|
41
41
|
function only(title: string, fn: Mocha.Func | Mocha.AsyncFunc): Mocha.Test;
|
|
42
|
+
/**
|
|
43
|
+
* Run a soft test that is expected to finish with a SoftAssertionError.
|
|
44
|
+
*/
|
|
45
|
+
function expectFailure(title: string, fn: Mocha.Func | Mocha.AsyncFunc): Mocha.Test;
|
|
46
|
+
namespace expectFailure {
|
|
47
|
+
/** Run only this expected-failure soft test */
|
|
48
|
+
function only(title: string, fn: Mocha.Func | Mocha.AsyncFunc): Mocha.Test;
|
|
49
|
+
}
|
|
42
50
|
/**
|
|
43
51
|
* Skip this test (like it.skip)
|
|
44
52
|
*/
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,7 @@ let softAssertionErrors = [];
|
|
|
20
20
|
let retryAssertionFailures = new Map();
|
|
21
21
|
let retryFirstSeen = new Map();
|
|
22
22
|
let isInSoftTest = false;
|
|
23
|
+
let expectSoftFailureCurrentSoftTest = false;
|
|
23
24
|
let activeFailHandler = null;
|
|
24
25
|
let originalChaiAssert = null;
|
|
25
26
|
// ---------------------------------------------------------------------------
|
|
@@ -208,19 +209,22 @@ function buildSoftAssertionError() {
|
|
|
208
209
|
}
|
|
209
210
|
function finalizeSoftTest() {
|
|
210
211
|
isInSoftTest = false;
|
|
212
|
+
expectSoftFailureCurrentSoftTest = false;
|
|
211
213
|
restoreAssertions();
|
|
212
214
|
return buildSoftAssertionError();
|
|
213
215
|
}
|
|
214
216
|
function abortSoftTest() {
|
|
215
217
|
isInSoftTest = false;
|
|
218
|
+
expectSoftFailureCurrentSoftTest = false;
|
|
216
219
|
restoreAssertions();
|
|
217
220
|
retryAssertionFailures.clear();
|
|
218
221
|
retryFirstSeen.clear();
|
|
219
222
|
}
|
|
220
|
-
function createSoftIt(baseIt) {
|
|
223
|
+
function createSoftIt(baseIt, options) {
|
|
221
224
|
return function (title, fn) {
|
|
222
225
|
return baseIt(title, function () {
|
|
223
226
|
isInSoftTest = true;
|
|
227
|
+
expectSoftFailureCurrentSoftTest = Boolean(options === null || options === void 0 ? void 0 : options.expectFailure);
|
|
224
228
|
softAssertionErrors = [];
|
|
225
229
|
retryAssertionFailures.clear();
|
|
226
230
|
retryFirstSeen.clear();
|
|
@@ -257,6 +261,15 @@ globalThis.soft_it = createSoftIt(it);
|
|
|
257
261
|
* soft_it.only - Run only this soft test
|
|
258
262
|
*/
|
|
259
263
|
globalThis.soft_it.only = createSoftIt(it.only);
|
|
264
|
+
/**
|
|
265
|
+
* soft_it.expectFailure - Run a soft test that is expected to aggregate
|
|
266
|
+
* into a final SoftAssertionError without failing the enclosing behavior spec.
|
|
267
|
+
*/
|
|
268
|
+
globalThis.soft_it.expectFailure = createSoftIt(it, { expectFailure: true });
|
|
269
|
+
/**
|
|
270
|
+
* soft_it.expectFailure.only - Run only this expected-failure soft test.
|
|
271
|
+
*/
|
|
272
|
+
globalThis.soft_it.expectFailure.only = createSoftIt(it.only, { expectFailure: true });
|
|
260
273
|
/**
|
|
261
274
|
* soft_it.skip - Skip this soft test
|
|
262
275
|
*/
|
|
@@ -265,29 +278,34 @@ globalThis.soft_it.skip = it.skip;
|
|
|
265
278
|
// Registered at the root level so it applies to all suites.
|
|
266
279
|
// For non-soft tests (isInSoftTest === false), this is a no-op.
|
|
267
280
|
afterEach(function () {
|
|
268
|
-
var _a, _b, _c;
|
|
269
281
|
if (!isInSoftTest)
|
|
270
282
|
return;
|
|
283
|
+
const expectsSoftFailure = expectSoftFailureCurrentSoftTest;
|
|
271
284
|
const finalError = finalizeSoftTest();
|
|
285
|
+
if (expectsSoftFailure) {
|
|
286
|
+
if (!finalError) {
|
|
287
|
+
throw new Error('Expected SoftAssertionError but soft_it.expectFailure test completed without one.');
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
272
291
|
if (finalError) {
|
|
273
292
|
const test = this.currentTest;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
throw finalError;
|
|
293
|
+
if (test) {
|
|
294
|
+
test.err = finalError;
|
|
295
|
+
test._cypressTestStatusInfo = {
|
|
296
|
+
outerStatus: 'failed',
|
|
297
|
+
shouldAttemptsContinue: false,
|
|
298
|
+
attempts: typeof test.currentRetry === 'function' ? test.currentRetry() + 1 : 1,
|
|
299
|
+
strategy: 'detect-flake-and-pass-on-threshold',
|
|
300
|
+
};
|
|
301
|
+
const prevAttempts = Array.isArray(test.prevAttempts) ? test.prevAttempts : [];
|
|
302
|
+
if (!prevAttempts.some((attempt) => (attempt === null || attempt === void 0 ? void 0 : attempt.state) === 'failed' && (attempt === null || attempt === void 0 ? void 0 : attempt.err))) {
|
|
303
|
+
prevAttempts.unshift({
|
|
304
|
+
state: 'failed',
|
|
305
|
+
err: finalError,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
test.prevAttempts = prevAttempts;
|
|
291
309
|
}
|
|
292
310
|
}
|
|
293
311
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koenvanbelle/cypress-soft-assertions",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "A Cypress plugin that provides soft_it() for soft assertions - all assertions continue on failure and are reported together",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|