@koenvanbelle/cypress-soft-assertions 2.0.1 → 2.1.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.
- package/dist/index.js +67 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -64,6 +64,29 @@ function toTokenPart(value) {
|
|
|
64
64
|
return String(value);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
function getRetryableCommandId() {
|
|
68
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
69
|
+
try {
|
|
70
|
+
const current = cy.state('current');
|
|
71
|
+
if (!current)
|
|
72
|
+
return '';
|
|
73
|
+
// In Cypress 15, inside a .should()/.and() callback, cy.state('current')
|
|
74
|
+
// points to the parent command (e.g. 'wrap', 'window', 'get'). The
|
|
75
|
+
// 'followedByShouldCallback' attribute is set to true, and
|
|
76
|
+
// 'currentAssertionCommand' points to the should/and command object.
|
|
77
|
+
const assertionCmd = (_a = current.get) === null || _a === void 0 ? void 0 : _a.call(current, 'currentAssertionCommand');
|
|
78
|
+
if (assertionCmd) {
|
|
79
|
+
const id = (_d = (_c = (_b = assertionCmd.get) === null || _b === void 0 ? void 0 : _b.call(assertionCmd, 'id')) !== null && _c !== void 0 ? _c : assertionCmd.id) !== null && _d !== void 0 ? _d : '';
|
|
80
|
+
return id ? String(id) : '';
|
|
81
|
+
}
|
|
82
|
+
if ((_e = current.get) === null || _e === void 0 ? void 0 : _e.call(current, 'followedByShouldCallback')) {
|
|
83
|
+
const id = (_g = (_f = current.get) === null || _f === void 0 ? void 0 : _f.call(current, 'id')) !== null && _g !== void 0 ? _g : '';
|
|
84
|
+
return id ? String(id) : '';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch ( /* ignore */_h) { /* ignore */ }
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
67
90
|
function getAssertionToken(assertionContext, args) {
|
|
68
91
|
const subjectKey = getSubjectKey(assertionContext);
|
|
69
92
|
if (!subjectKey)
|
|
@@ -94,6 +117,13 @@ function patchedAssertionAssert(...args) {
|
|
|
94
117
|
retryAssertionFailures.delete(token);
|
|
95
118
|
retryFirstSeen.delete(token);
|
|
96
119
|
}
|
|
120
|
+
// Also clear command-based fallback tokens for this command
|
|
121
|
+
const retryableCid = getRetryableCommandId();
|
|
122
|
+
if (retryableCid) {
|
|
123
|
+
const fallbackToken = `__cmd__|${retryableCid}|${toTokenPart(args === null || args === void 0 ? void 0 : args[3])}`;
|
|
124
|
+
retryAssertionFailures.delete(fallbackToken);
|
|
125
|
+
retryFirstSeen.delete(fallbackToken);
|
|
126
|
+
}
|
|
97
127
|
}
|
|
98
128
|
return result;
|
|
99
129
|
}
|
|
@@ -108,27 +138,31 @@ function patchedAssertionAssert(...args) {
|
|
|
108
138
|
message: String((error === null || error === void 0 ? void 0 : error.message) || error),
|
|
109
139
|
stack: error === null || error === void 0 ? void 0 : error.stack,
|
|
110
140
|
});
|
|
111
|
-
const now = Date.now();
|
|
112
141
|
if (!retryFirstSeen.has(token)) {
|
|
113
|
-
retryFirstSeen.set(token, now);
|
|
142
|
+
retryFirstSeen.set(token, Date.now());
|
|
114
143
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
144
|
+
// Always rethrow to let Cypress use its full retry/timeout window.
|
|
145
|
+
// When the timeout expires, Cypress fires the fail event; our fail
|
|
146
|
+
// handler captures it and clears the staged entry to avoid duplicates.
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
// No identifiable subject from the DOM. If we're inside a retryable
|
|
150
|
+
// command (.should() / .and()), derive a token from the command ID so the
|
|
151
|
+
// retry-window logic still applies (e.g. window property assertions).
|
|
152
|
+
const retryableCid = getRetryableCommandId();
|
|
153
|
+
if (retryableCid) {
|
|
154
|
+
const fallbackToken = `__cmd__|${retryableCid}|${toTokenPart(args === null || args === void 0 ? void 0 : args[3])}`;
|
|
155
|
+
retryAssertionFailures.set(fallbackToken, {
|
|
156
|
+
message: String((error === null || error === void 0 ? void 0 : error.message) || error),
|
|
157
|
+
stack: error === null || error === void 0 ? void 0 : error.stack,
|
|
158
|
+
});
|
|
159
|
+
if (!retryFirstSeen.has(fallbackToken)) {
|
|
160
|
+
retryFirstSeen.set(fallbackToken, Date.now());
|
|
124
161
|
}
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
// Map and will be promoted to softAssertionErrors at finalization.
|
|
128
|
-
return;
|
|
162
|
+
// Always rethrow to let Cypress use its full retry/timeout window.
|
|
163
|
+
throw error;
|
|
129
164
|
}
|
|
130
|
-
//
|
|
131
|
-
// in .then() callbacks).
|
|
165
|
+
// Bare expect() in .then() callbacks — capture directly.
|
|
132
166
|
captureSoftAssertion(error);
|
|
133
167
|
}
|
|
134
168
|
}
|
|
@@ -144,6 +178,16 @@ function setupSoftAssertions() {
|
|
|
144
178
|
// Non-assertion command failures (e.g. element not found timeouts)
|
|
145
179
|
// are captured as soft failures.
|
|
146
180
|
captureSoftAssertion(error);
|
|
181
|
+
// Clear any matching retry-tracked entry to prevent double-counting
|
|
182
|
+
// at finalization (the fail handler is now the authoritative capture).
|
|
183
|
+
const errorMsg = (error === null || error === void 0 ? void 0 : error.message) || String(error);
|
|
184
|
+
for (const [token, entry] of retryAssertionFailures.entries()) {
|
|
185
|
+
if (entry.message === errorMsg) {
|
|
186
|
+
retryAssertionFailures.delete(token);
|
|
187
|
+
retryFirstSeen.delete(token);
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
147
191
|
return false;
|
|
148
192
|
};
|
|
149
193
|
Cypress.on('fail', activeFailHandler);
|
|
@@ -162,9 +206,13 @@ function restoreAssertions() {
|
|
|
162
206
|
}
|
|
163
207
|
}
|
|
164
208
|
function buildSoftAssertionError() {
|
|
165
|
-
// Promote any remaining retry-tracked failures
|
|
209
|
+
// Promote any remaining retry-tracked failures that weren't already
|
|
210
|
+
// captured by the fail handler (dedup by message).
|
|
166
211
|
for (const entry of retryAssertionFailures.values()) {
|
|
167
|
-
|
|
212
|
+
const isDuplicate = softAssertionErrors.some(e => e.message === entry.message);
|
|
213
|
+
if (!isDuplicate) {
|
|
214
|
+
softAssertionErrors.push(entry);
|
|
215
|
+
}
|
|
168
216
|
}
|
|
169
217
|
retryAssertionFailures.clear();
|
|
170
218
|
retryFirstSeen.clear();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koenvanbelle/cypress-soft-assertions",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.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",
|