@qualflare/cucumberjs 0.4.0 → 0.5.0
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 +16 -1
- package/dist/formatter/index.cjs +41 -3
- package/dist/formatter/index.cjs.map +1 -1
- package/dist/formatter/index.js +41 -3
- package/dist/formatter/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
|
|
7
7
|
A native CucumberJS reporter for [Qualflare](https://qualflare.com) — captures test results directly
|
|
8
|
-
from your `cucumber-js` run: Feature/Scenario status,
|
|
8
|
+
from your `cucumber-js` run: Feature/Scenario status, per-attempt retry history, screenshots, videos,
|
|
9
9
|
Given/When/Then step traces, Scenario Outline rows, and author-facing metadata (labels, links, tags,
|
|
10
10
|
custom attachments).
|
|
11
11
|
|
|
@@ -125,6 +125,21 @@ wrong value cannot fail at run time — this package makes no network calls —
|
|
|
125
125
|
(workaround, not a first-class rendering).
|
|
126
126
|
- **`BeforeStep`/`AfterStep` hooks are off by default** (`includeStepHooks`) — noisy for suites with
|
|
127
127
|
global per-step instrumentation.
|
|
128
|
+
- **`BeforeAll`/`AfterAll` attachments need the hook to fail** — a failed global hook becomes a
|
|
129
|
+
synthetic Case and its attachments land there; a passing one produces no Case, so they are
|
|
130
|
+
dropped.
|
|
131
|
+
- **`parameter()` outside a step is not masked** — `masked` is a display hint for the UI; the
|
|
132
|
+
server never redacts the value, so never put a real secret in one. See
|
|
133
|
+
[`docs/LIMITATIONS.md`](./docs/LIMITATIONS.md#qualflareparameter-outside-a-step-has-no-masking).
|
|
134
|
+
- **Attachment caps are two budgets, not one pool** — `maxAttachmentBytes` bounds a single
|
|
135
|
+
attachment and `maxTotalAttachmentBytes` the whole run; anything over either is dropped
|
|
136
|
+
outright rather than truncated. Raising them is the easiest way to push a request past
|
|
137
|
+
`/collect`'s body limit. See
|
|
138
|
+
[`docs/LIMITATIONS.md`](./docs/LIMITATIONS.md#per-caseper-attachment-caps-are-independent-not-pooled).
|
|
139
|
+
- **Retries carry per-attempt errors, but everything else is the final attempt** — `Case.attempts`
|
|
140
|
+
records each attempt's status, duration and error; steps, labels, links, tags, priority,
|
|
141
|
+
properties and attachments come from the last attempt only, so an abandoned attempt's step trace
|
|
142
|
+
is discarded rather than replayed alongside the final one.
|
|
128
143
|
|
|
129
144
|
Full details in [`docs/LIMITATIONS.md`](./docs/LIMITATIONS.md).
|
|
130
145
|
|
package/dist/formatter/index.cjs
CHANGED
|
@@ -1038,7 +1038,7 @@ function timestampMs(ts) {
|
|
|
1038
1038
|
var os = __toESM(require("os"), 1);
|
|
1039
1039
|
|
|
1040
1040
|
// src/config/version.ts
|
|
1041
|
-
var PACKAGE_VERSION = "0.
|
|
1041
|
+
var PACKAGE_VERSION = "0.5.0";
|
|
1042
1042
|
|
|
1043
1043
|
// src/formatter/collect-builder.ts
|
|
1044
1044
|
function resolveOs(config) {
|
|
@@ -1165,23 +1165,52 @@ function buildHookIndex(supportCodeLibrary) {
|
|
|
1165
1165
|
|
|
1166
1166
|
// src/formatter/run-hook-tracker.ts
|
|
1167
1167
|
var RunHookTracker = class {
|
|
1168
|
+
constructor(config, budget) {
|
|
1169
|
+
this.config = config;
|
|
1170
|
+
this.budget = budget;
|
|
1171
|
+
}
|
|
1172
|
+
config;
|
|
1173
|
+
budget;
|
|
1168
1174
|
/** `testRunHookStartedId` -> the hook it started, so `finish()` can look
|
|
1169
1175
|
* up its kind/name. `TestRunHookFinished.result.duration` already gives
|
|
1170
1176
|
* an accurate duration directly — no start/finish timestamp delta needed. */
|
|
1171
1177
|
started = /* @__PURE__ */ new Map();
|
|
1172
1178
|
failed = [];
|
|
1179
|
+
/** `testRunHookStartedId` -> attachments made during that hook, unresolved. */
|
|
1180
|
+
pending = /* @__PURE__ */ new Map();
|
|
1173
1181
|
start(e) {
|
|
1174
1182
|
this.started.set(e.id, e.hookId);
|
|
1175
1183
|
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Buffers one attachment made from inside a `BeforeAll`/`AfterAll`. Returns
|
|
1186
|
+
* whether it was claimed, so the caller can tell a run-hook attachment from
|
|
1187
|
+
* one that simply has no home.
|
|
1188
|
+
*
|
|
1189
|
+
* `testRunHookStartedId` is optional in the message schema and only populated
|
|
1190
|
+
* by newer cucumber-js; when it is absent this returns false and the
|
|
1191
|
+
* attachment is dropped exactly as it was before.
|
|
1192
|
+
*/
|
|
1193
|
+
attachment(e) {
|
|
1194
|
+
if (!e.testRunHookStartedId) {
|
|
1195
|
+
return false;
|
|
1196
|
+
}
|
|
1197
|
+
const content = e.contentEncoding === "BASE64" ? e.body : Buffer.from(e.body, "utf8").toString("base64");
|
|
1198
|
+
const list = this.pending.get(e.testRunHookStartedId) ?? [];
|
|
1199
|
+
list.push({ name: e.fileName || "attachment", mimeType: e.mediaType, content });
|
|
1200
|
+
this.pending.set(e.testRunHookStartedId, list);
|
|
1201
|
+
return true;
|
|
1202
|
+
}
|
|
1176
1203
|
finish(e, hookIndex) {
|
|
1177
1204
|
const hookId = this.started.get(e.testRunHookStartedId);
|
|
1178
1205
|
this.started.delete(e.testRunHookStartedId);
|
|
1179
1206
|
const status = mapStatus(e.result.status);
|
|
1180
1207
|
if (status === "passed" || status === "skipped") {
|
|
1208
|
+
this.pending.delete(e.testRunHookStartedId);
|
|
1181
1209
|
return;
|
|
1182
1210
|
}
|
|
1183
1211
|
const hook = hookId ? hookIndex.get(hookId) : void 0;
|
|
1184
1212
|
const label = hook?.kind === "afterAll" ? "AfterAll hook" : "BeforeAll hook";
|
|
1213
|
+
const attachments = this.takeAttachments(e.testRunHookStartedId);
|
|
1185
1214
|
this.failed.push({
|
|
1186
1215
|
id: `global-hook:${e.testRunHookStartedId}`,
|
|
1187
1216
|
name: hook?.name || label,
|
|
@@ -1190,9 +1219,17 @@ var RunHookTracker = class {
|
|
|
1190
1219
|
// `result.message` first — see `step-mapper.ts`'s `formatError()` doc
|
|
1191
1220
|
// comment for why (verified empirically to be the version-safe field
|
|
1192
1221
|
// across the peer-dependency range; `exception.stackTrace` is not).
|
|
1193
|
-
error: e.result.message || e.result.exception?.stackTrace || e.result.exception?.message
|
|
1222
|
+
error: e.result.message || e.result.exception?.stackTrace || e.result.exception?.message,
|
|
1223
|
+
...attachments.length > 0 ? { attachments } : {}
|
|
1194
1224
|
});
|
|
1195
1225
|
}
|
|
1226
|
+
/** Resolves and clears the buffer for one hook. Called only for a FAILED
|
|
1227
|
+
* hook, so the budget is never spent on a passing one's attachments. */
|
|
1228
|
+
takeAttachments(testRunHookStartedId) {
|
|
1229
|
+
const buffered = this.pending.get(testRunHookStartedId) ?? [];
|
|
1230
|
+
this.pending.delete(testRunHookStartedId);
|
|
1231
|
+
return buffered.map((pending) => resolvePendingAttachment(pending, this.config, this.budget)).filter((a) => a !== void 0);
|
|
1232
|
+
}
|
|
1196
1233
|
/** Returns `undefined` if no run-hook failed — see the class doc comment
|
|
1197
1234
|
* for why a passing BeforeAll/AfterAll produces no Case at all. */
|
|
1198
1235
|
buildSuite() {
|
|
@@ -1264,7 +1301,7 @@ var QualflareCucumberFormatter = class extends import_cucumber.Formatter {
|
|
|
1264
1301
|
testCaseIndex = /* @__PURE__ */ new Map();
|
|
1265
1302
|
attachmentBudget;
|
|
1266
1303
|
attemptTracker;
|
|
1267
|
-
runHookTracker
|
|
1304
|
+
runHookTracker;
|
|
1268
1305
|
finishedCases = [];
|
|
1269
1306
|
/** One promise per `testCaseFinished` envelope, resolving once that
|
|
1270
1307
|
* scenario's `AttemptTracker.finish()` (which itself awaits any pending
|
|
@@ -1279,6 +1316,7 @@ var QualflareCucumberFormatter = class extends import_cucumber.Formatter {
|
|
|
1279
1316
|
this.config = resolveConfig(options.parsedArgvOptions);
|
|
1280
1317
|
this.hookIndex = buildHookIndex(options.supportCodeLibrary);
|
|
1281
1318
|
this.attachmentBudget = new AttachmentBudget(this.config.maxTotalAttachmentBytes);
|
|
1319
|
+
this.runHookTracker = new RunHookTracker(this.config, this.attachmentBudget);
|
|
1282
1320
|
this.attemptTracker = new AttemptTracker(
|
|
1283
1321
|
this.hookIndex,
|
|
1284
1322
|
this.gherkin,
|