@koenvanbelle/cypress-soft-assertions 2.0.0 → 2.0.1

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.
Files changed (3) hide show
  1. package/README.md +9 -0
  2. package/dist/index.js +36 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -158,6 +158,15 @@ SOFT ASSERTION FAILURES (3 failed):
158
158
  - **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
159
  - **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
160
  - **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 for up to 75% of the effective timeout before being captured as soft failures. For slow applications, increase the timeout to give assertions more time to pass:
162
+ ```typescript
163
+ // Global: set in cypress.config.ts
164
+ e2e: { defaultCommandTimeout: 10000 }
165
+
166
+ // Per-command: override on individual commands
167
+ cy.get('.slow-element', { timeout: 15000 }).should('be.visible');
168
+ ```
169
+ - **Cypress Studio / Command Log**: When a soft assertion fails definitively, the plugin swallows the error so the test can continue. Cypress treats the command as resolved, which means it may not appear as a failed step in the Cypress Studio command log or may look like the assertion was skipped. The assertions **do** execute and failures **are** captured — they just don't show visually in the command log. Check the final `SoftAssertionError` report for the complete list of failures.
161
170
 
162
171
  ## License
163
172
 
package/dist/index.js CHANGED
@@ -9,15 +9,26 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  let softAssertionErrors = [];
11
11
  let retryAssertionFailures = new Map();
12
- let retryAttemptCount = new Map();
12
+ let retryFirstSeen = new Map();
13
13
  let isInSoftTest = false;
14
14
  let activeFailHandler = null;
15
15
  let originalChaiAssert = null;
16
- // After this many consecutive assertion failures on the same token, stop
17
- // rethrowing (which would cause Cypress to retry) and swallow instead.
18
- // This gives Cypress enough retry cycles for assertions that will eventually
19
- // pass, while bounding the time spent on definitively failing assertions.
20
- const MAX_RETHROWS = 10;
16
+ function getEffectiveTimeout() {
17
+ var _a;
18
+ try {
19
+ const current = cy.state('current');
20
+ const perCommand = (_a = current === null || current === void 0 ? void 0 : current.get) === null || _a === void 0 ? void 0 : _a.call(current, 'timeout');
21
+ if (typeof perCommand === 'number')
22
+ return perCommand;
23
+ }
24
+ catch ( /* ignore */_b) { /* ignore */ }
25
+ try {
26
+ return Cypress.config('defaultCommandTimeout') || 4000;
27
+ }
28
+ catch (_c) {
29
+ return 4000;
30
+ }
31
+ }
21
32
  function captureSoftAssertion(error) {
22
33
  const message = (error === null || error === void 0 ? void 0 : error.message) || String(error);
23
34
  const stack = error === null || error === void 0 ? void 0 : error.stack;
@@ -81,7 +92,7 @@ function patchedAssertionAssert(...args) {
81
92
  const token = getAssertionToken(this, args);
82
93
  if (token) {
83
94
  retryAssertionFailures.delete(token);
84
- retryAttemptCount.delete(token);
95
+ retryFirstSeen.delete(token);
85
96
  }
86
97
  }
87
98
  return result;
@@ -97,17 +108,23 @@ function patchedAssertionAssert(...args) {
97
108
  message: String((error === null || error === void 0 ? void 0 : error.message) || error),
98
109
  stack: error === null || error === void 0 ? void 0 : error.stack,
99
110
  });
100
- const attempts = (retryAttemptCount.get(token) || 0) + 1;
101
- retryAttemptCount.set(token, attempts);
102
- if (attempts <= MAX_RETHROWS) {
103
- // Rethrow to let Cypress retry the assertion. This gives retriable
104
- // commands (should/and, retried from get/contains/etc.) a window
105
- // to eventually pass.
111
+ const now = Date.now();
112
+ if (!retryFirstSeen.has(token)) {
113
+ retryFirstSeen.set(token, now);
114
+ }
115
+ const elapsed = now - retryFirstSeen.get(token);
116
+ const timeout = getEffectiveTimeout();
117
+ // Use 75% of the timeout as the retry window. This ensures the plugin
118
+ // swallows the error before Cypress's own timeout fires (which would
119
+ // route through the fail handler and prevent finalization).
120
+ if (elapsed < timeout * 0.75) {
121
+ // Still within the command timeout window — rethrow to let Cypress
122
+ // retry the assertion. If it eventually passes, the token is cleared.
106
123
  throw error;
107
124
  }
108
- // Past the retry budget — swallow so the command "succeeds" and Cypress
109
- // moves on to the next queued command. The token stays in the Map and
110
- // will be promoted to softAssertionErrors at finalization.
125
+ // Past the timeout budget — swallow so the command "succeeds" and
126
+ // Cypress moves on to the next queued command. The token stays in the
127
+ // Map and will be promoted to softAssertionErrors at finalization.
111
128
  return;
112
129
  }
113
130
  // No identifiable subject — capture directly (e.g. bare expect() calls
@@ -150,7 +167,7 @@ function buildSoftAssertionError() {
150
167
  captureSoftAssertion(entry);
151
168
  }
152
169
  retryAssertionFailures.clear();
153
- retryAttemptCount.clear();
170
+ retryFirstSeen.clear();
154
171
  if (softAssertionErrors.length > 0) {
155
172
  const errorMessages = softAssertionErrors
156
173
  .map((entry, index) => ` ${index + 1}. ${entry.message}`)
@@ -180,7 +197,7 @@ function abortSoftTest() {
180
197
  isInSoftTest = false;
181
198
  restoreAssertions();
182
199
  retryAssertionFailures.clear();
183
- retryAttemptCount.clear();
200
+ retryFirstSeen.clear();
184
201
  }
185
202
  function createSoftIt(baseIt) {
186
203
  return function (title, fn) {
@@ -188,7 +205,7 @@ function createSoftIt(baseIt) {
188
205
  isInSoftTest = true;
189
206
  softAssertionErrors = [];
190
207
  retryAssertionFailures.clear();
191
- retryAttemptCount.clear();
208
+ retryFirstSeen.clear();
192
209
  setupSoftAssertions();
193
210
  let result;
194
211
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koenvanbelle/cypress-soft-assertions",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
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",