@koenvanbelle/cypress-soft-assertions 2.3.1 → 2.3.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 CHANGED
@@ -249,6 +249,36 @@ soft_it('works with automatic translation checks', () => {
249
249
  - Both plugins register hooks/events, but they can run together safely with the setup above.
250
250
  - Keep translation-checker in non-invasive mode (`failOnError: false` in auto mode) so functional assertions remain the source of test pass/fail behavior.
251
251
 
252
+ ## Force-Fail Hook (strict mode)
253
+
254
+ If your project (or another plugin) mutates test state in hooks (for example in `test:after:run`) and soft-failed tests appear as passed, enable strict mode per test:
255
+
256
+ ```typescript
257
+ soft_it.strict('fails hard when soft assertions are captured', () => {
258
+ cy.visit('/');
259
+ cy.get('#title').should('have.text', 'Wrong Title');
260
+ cy.get('#status').should('have.text', 'Ready');
261
+ });
262
+ ```
263
+
264
+ You can also use `soft_it.strict.only(...)` and `soft_it.strict.skip(...)`.
265
+
266
+ To force strict mode for all soft tests in a run, use env flags:
267
+
268
+ ```bash
269
+ cypress run --env softAssertForceFail=true
270
+ ```
271
+
272
+ You can also use:
273
+
274
+ ```bash
275
+ cypress run --env SOFT_ASSERT_FORCE_FAIL=true
276
+ ```
277
+
278
+ When strict mode is enabled (per test or via env), the plugin throws the final `SoftAssertionError` from `afterEach` on the last attempt, preventing downstream hook-based recovery from turning the test green.
279
+
280
+ Note: strict mode may abort remaining tests in the same suite after the first forced soft failure (standard Cypress hook-failure behavior).
281
+
252
282
  ## License
253
283
 
254
284
  MIT
package/dist/index.d.ts CHANGED
@@ -39,6 +39,16 @@ 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 this soft test in strict mode (force unrecoverable failure)
44
+ */
45
+ function strict(title: string, fn: Mocha.Func | Mocha.AsyncFunc): Mocha.Test;
46
+ namespace strict {
47
+ /** Run only this strict soft test */
48
+ function only(title: string, fn: Mocha.Func | Mocha.AsyncFunc): Mocha.Test;
49
+ /** Skip this strict soft test */
50
+ function skip(title: string, fn: Mocha.Func | Mocha.AsyncFunc): void;
51
+ }
42
52
  /**
43
53
  * Skip this test (like it.skip)
44
54
  */
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 forceFailCurrentSoftTest = false;
23
24
  let activeFailHandler = null;
24
25
  let originalChaiAssert = null;
25
26
  // ---------------------------------------------------------------------------
@@ -41,6 +42,26 @@ function getEffectiveTimeout() {
41
42
  return 4000;
42
43
  }
43
44
  }
45
+ function isTruthy(value) {
46
+ if (typeof value === 'boolean')
47
+ return value;
48
+ if (typeof value === 'number')
49
+ return value !== 0;
50
+ const normalized = String(value !== null && value !== void 0 ? value : '').trim().toLowerCase();
51
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
52
+ }
53
+ function shouldForceFailSoftAssertions() {
54
+ var _a;
55
+ try {
56
+ const env = (_a = Cypress.env) === null || _a === void 0 ? void 0 : _a.bind(Cypress);
57
+ if (!env)
58
+ return false;
59
+ return isTruthy(env('softAssertForceFail')) || isTruthy(env('SOFT_ASSERT_FORCE_FAIL'));
60
+ }
61
+ catch (_b) {
62
+ return false;
63
+ }
64
+ }
44
65
  // ---------------------------------------------------------------------------
45
66
  // Internal helpers (Cypress-dependent — not unit-testable in isolation)
46
67
  // ---------------------------------------------------------------------------
@@ -208,19 +229,22 @@ function buildSoftAssertionError() {
208
229
  }
209
230
  function finalizeSoftTest() {
210
231
  isInSoftTest = false;
232
+ forceFailCurrentSoftTest = false;
211
233
  restoreAssertions();
212
234
  return buildSoftAssertionError();
213
235
  }
214
236
  function abortSoftTest() {
215
237
  isInSoftTest = false;
238
+ forceFailCurrentSoftTest = false;
216
239
  restoreAssertions();
217
240
  retryAssertionFailures.clear();
218
241
  retryFirstSeen.clear();
219
242
  }
220
- function createSoftIt(baseIt) {
243
+ function createSoftIt(baseIt, options) {
221
244
  return function (title, fn) {
222
245
  return baseIt(title, function () {
223
246
  isInSoftTest = true;
247
+ forceFailCurrentSoftTest = Boolean(options === null || options === void 0 ? void 0 : options.strict) || shouldForceFailSoftAssertions();
224
248
  softAssertionErrors = [];
225
249
  retryAssertionFailures.clear();
226
250
  retryFirstSeen.clear();
@@ -257,10 +281,25 @@ globalThis.soft_it = createSoftIt(it);
257
281
  * soft_it.only - Run only this soft test
258
282
  */
259
283
  globalThis.soft_it.only = createSoftIt(it.only);
284
+ /**
285
+ * soft_it.strict - Run this soft test in strict mode.
286
+ *
287
+ * Strict mode throws the final SoftAssertionError from afterEach on the
288
+ * last attempt, making the failure unrecoverable by downstream hooks.
289
+ */
290
+ globalThis.soft_it.strict = createSoftIt(it, { strict: true });
291
+ /**
292
+ * soft_it.strict.only - Run only this strict soft test
293
+ */
294
+ globalThis.soft_it.strict.only = createSoftIt(it.only, { strict: true });
260
295
  /**
261
296
  * soft_it.skip - Skip this soft test
262
297
  */
263
298
  globalThis.soft_it.skip = it.skip;
299
+ /**
300
+ * soft_it.strict.skip - Skip this strict soft test
301
+ */
302
+ globalThis.soft_it.strict.skip = it.skip;
264
303
  // Global afterEach hook: finalize soft assertions after each test.
265
304
  // Registered at the root level so it applies to all suites.
266
305
  // For non-soft tests (isInSoftTest === false), this is a no-op.
@@ -282,6 +321,12 @@ afterEach(function () {
282
321
  // Last (or only) attempt — use runner.fail() to mark the TEST as failed
283
322
  // rather than the hook. Throwing from afterEach on the final attempt
284
323
  // would skip remaining tests in the suite.
324
+ if (forceFailCurrentSoftTest) {
325
+ // Strict mode: force an unrecoverable hook failure.
326
+ // This guarantees a failed result even when other plugins mutate
327
+ // test state in test:after:run.
328
+ throw finalError;
329
+ }
285
330
  const runner = (_c = Cypress.mocha) === null || _c === void 0 ? void 0 : _c.getRunner();
286
331
  if (runner && test) {
287
332
  runner.fail(test, finalError);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koenvanbelle/cypress-soft-assertions",
3
- "version": "2.3.1",
3
+ "version": "2.3.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",