@step-forge/step-forge 0.0.20 → 0.0.22
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 +2 -0
- package/RUNTIME.md +242 -0
- package/dist/{analyzer-DJyJbU_V.js → analyzer-byS8yRrY.js} +202 -34
- package/dist/analyzer-byS8yRrY.js.map +1 -0
- package/dist/analyzer-cli.js +1 -1
- package/dist/analyzer.d.ts +2 -0
- package/dist/analyzer.js +1 -2
- package/dist/cli.cjs +525 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +526 -0
- package/dist/cli.js.map +1 -0
- package/dist/{hooks-Dar49TtT.d.ts → config-C7PCYgYy.d.cts} +65 -16
- package/dist/{hooks-Dar49TtT.d.cts → config-C7PCYgYy.d.ts} +65 -16
- package/dist/engine-DPVLEHBi.js +163 -0
- package/dist/engine-DPVLEHBi.js.map +1 -0
- package/dist/engine-vqA-eL_T.cjs +186 -0
- package/dist/engine-vqA-eL_T.cjs.map +1 -0
- package/dist/gherkinParser-BT40q_i3.cjs +338 -0
- package/dist/gherkinParser-BT40q_i3.cjs.map +1 -0
- package/dist/gherkinParser-NcttZgN4.js +259 -0
- package/dist/gherkinParser-NcttZgN4.js.map +1 -0
- package/dist/hooks-BDCMKeNq.js +71 -0
- package/dist/{hooks-CywugMQQ.js.map → hooks-BDCMKeNq.js.map} +1 -1
- package/dist/{hooks-CGYzwDOv.cjs → hooks-Be0cjULN.cjs} +20 -31
- package/dist/{hooks-CGYzwDOv.cjs.map → hooks-Be0cjULN.cjs.map} +1 -1
- package/dist/runtime.cjs +7 -162
- package/dist/runtime.d.cts +44 -8
- package/dist/runtime.d.ts +44 -8
- package/dist/runtime.js +3 -159
- package/dist/step-forge.cjs +73 -216
- package/dist/step-forge.cjs.map +1 -1
- package/dist/step-forge.d.cts +19 -10
- package/dist/step-forge.d.ts +19 -10
- package/dist/step-forge.js +67 -185
- package/dist/step-forge.js.map +1 -1
- package/package.json +12 -18
- package/dist/analyzer-DJyJbU_V.js.map +0 -1
- package/dist/gherkinParser-Dp2d7JNr.js +0 -116
- package/dist/gherkinParser-Dp2d7JNr.js.map +0 -1
- package/dist/hooks-CywugMQQ.js +0 -82
- package/dist/runtime.cjs.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/vitest.d.ts +0 -74
- package/dist/vitest.js +0 -136
- package/dist/vitest.js.map +0 -1
package/dist/step-forge.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as globalHookRegistry, o as globalRegistry } from "./hooks-BDCMKeNq.js";
|
|
2
|
+
import { a as captureDefinitionSite, i as BasicWorld, n as parseFeatureFiles, r as globFiles, t as parseFeatureContent } from "./gherkinParser-NcttZgN4.js";
|
|
2
3
|
import _ from "lodash";
|
|
3
|
-
import { glob } from "node:fs/promises";
|
|
4
|
-
import * as path from "node:path";
|
|
5
4
|
import ts from "typescript";
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
|
-
import { AstBuilder, GherkinClassicTokenMatcher, Parser } from "@cucumber/gherkin";
|
|
8
|
-
import * as messages from "@cucumber/messages";
|
|
9
5
|
//#region src/builderTypeUtils.ts
|
|
10
6
|
const isString = (statement) => typeof statement === "string";
|
|
11
7
|
//#endregion
|
|
@@ -118,11 +114,13 @@ const addStep = (statement, stepType, dependencies = {
|
|
|
118
114
|
});
|
|
119
115
|
world[stepType].merge({ ...result });
|
|
120
116
|
};
|
|
117
|
+
const source = captureDefinitionSite();
|
|
121
118
|
globalRegistry.add({
|
|
122
119
|
stepType,
|
|
123
120
|
expression,
|
|
124
121
|
parsers,
|
|
125
|
-
execute
|
|
122
|
+
execute,
|
|
123
|
+
source
|
|
126
124
|
});
|
|
127
125
|
return {
|
|
128
126
|
statement,
|
|
@@ -196,42 +194,6 @@ const thenStatement = () => (statement) => {
|
|
|
196
194
|
};
|
|
197
195
|
const thenBuilder = () => ({ statement: thenStatement() });
|
|
198
196
|
//#endregion
|
|
199
|
-
//#region src/world.ts
|
|
200
|
-
function mergeCustomizer(objValue, srcValue) {
|
|
201
|
-
if (_.isArray(objValue)) return objValue.concat(srcValue);
|
|
202
|
-
else if (objValue && !_.isPlainObject(objValue) && objValue !== srcValue) throw new Error(`Merge would have destroyed previous value ${objValue} with ${srcValue}`);
|
|
203
|
-
return objValue;
|
|
204
|
-
}
|
|
205
|
-
var BasicWorld = class {
|
|
206
|
-
givenState = {};
|
|
207
|
-
whenState = {};
|
|
208
|
-
thenState = {};
|
|
209
|
-
get given() {
|
|
210
|
-
return {
|
|
211
|
-
...this.givenState,
|
|
212
|
-
merge: (newState) => {
|
|
213
|
-
this.givenState = _.merge({ ...this.givenState }, newState, mergeCustomizer);
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
get when() {
|
|
218
|
-
return {
|
|
219
|
-
...this.whenState,
|
|
220
|
-
merge: (newState) => {
|
|
221
|
-
this.whenState = _.merge({ ...this.whenState }, newState, mergeCustomizer);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
get then() {
|
|
226
|
-
return {
|
|
227
|
-
...this.thenState,
|
|
228
|
-
merge: (newState) => {
|
|
229
|
-
this.thenState = _.merge({ ...this.thenState }, newState, mergeCustomizer);
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
//#endregion
|
|
235
197
|
//#region src/analyzer/stepExtractor.ts
|
|
236
198
|
const BUILDER_NAMES = {
|
|
237
199
|
givenBuilder: "given",
|
|
@@ -239,6 +201,21 @@ const BUILDER_NAMES = {
|
|
|
239
201
|
thenBuilder: "then"
|
|
240
202
|
};
|
|
241
203
|
function extractStepDefinitions(filePaths, tsConfigPath) {
|
|
204
|
+
const fast = extractWithSources(filePaths.map(parseSourceFile).filter((s) => s !== null), null);
|
|
205
|
+
if (!fast.needsChecker) return fast.results;
|
|
206
|
+
const program = ts.createProgram(filePaths, resolveCompilerOptions(tsConfigPath));
|
|
207
|
+
const checker = program.getTypeChecker();
|
|
208
|
+
return extractWithSources(filePaths.map((fp) => program.getSourceFile(fp)).filter((s) => s !== void 0), checker).results;
|
|
209
|
+
}
|
|
210
|
+
/** Parse a single file into an AST with no type resolution. */
|
|
211
|
+
function parseSourceFile(filePath) {
|
|
212
|
+
const text = ts.sys.readFile(filePath);
|
|
213
|
+
if (text === void 0) return null;
|
|
214
|
+
const scriptKind = filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
215
|
+
return ts.createSourceFile(filePath, text, ts.ScriptTarget.ESNext, true, scriptKind);
|
|
216
|
+
}
|
|
217
|
+
/** Resolve compiler options from tsconfig (fallback to sane defaults). */
|
|
218
|
+
function resolveCompilerOptions(tsConfigPath) {
|
|
242
219
|
const configPath = tsConfigPath ?? ts.findConfigFile(process.cwd(), ts.sys.fileExists);
|
|
243
220
|
let compilerOptions = {
|
|
244
221
|
target: ts.ScriptTarget.ESNext,
|
|
@@ -252,22 +229,25 @@ function extractStepDefinitions(filePaths, tsConfigPath) {
|
|
|
252
229
|
if (!configFile.error) compilerOptions = ts.parseJsonConfigFileContent(configFile.config, ts.sys, configPath.replace(/[/\\][^/\\]+$/, "")).options;
|
|
253
230
|
}
|
|
254
231
|
compilerOptions.noEmit = true;
|
|
255
|
-
|
|
256
|
-
|
|
232
|
+
return compilerOptions;
|
|
233
|
+
}
|
|
234
|
+
function extractWithSources(sources, checker) {
|
|
235
|
+
const ctx = {
|
|
236
|
+
checker,
|
|
237
|
+
needsChecker: false
|
|
238
|
+
};
|
|
257
239
|
const results = [];
|
|
258
|
-
for (const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
return results;
|
|
240
|
+
for (const sourceFile of sources) results.push(...extractFromSourceFile(sourceFile, ctx));
|
|
241
|
+
return {
|
|
242
|
+
results,
|
|
243
|
+
needsChecker: ctx.needsChecker
|
|
244
|
+
};
|
|
265
245
|
}
|
|
266
|
-
function extractFromSourceFile(sourceFile,
|
|
246
|
+
function extractFromSourceFile(sourceFile, ctx) {
|
|
267
247
|
const results = [];
|
|
268
248
|
function visit(node) {
|
|
269
249
|
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "step") {
|
|
270
|
-
const meta = extractFromRegisterCall(node, sourceFile,
|
|
250
|
+
const meta = extractFromRegisterCall(node, sourceFile, ctx);
|
|
271
251
|
if (meta) results.push(meta);
|
|
272
252
|
}
|
|
273
253
|
ts.forEachChild(node, visit);
|
|
@@ -275,7 +255,7 @@ function extractFromSourceFile(sourceFile, checker) {
|
|
|
275
255
|
visit(sourceFile);
|
|
276
256
|
return results;
|
|
277
257
|
}
|
|
278
|
-
function extractFromRegisterCall(registerCall, sourceFile,
|
|
258
|
+
function extractFromRegisterCall(registerCall, sourceFile, ctx) {
|
|
279
259
|
const chain = collectCallChain(registerCall);
|
|
280
260
|
let stepType = null;
|
|
281
261
|
let expression = null;
|
|
@@ -290,7 +270,7 @@ function extractFromRegisterCall(registerCall, sourceFile, checker) {
|
|
|
290
270
|
if (!name) continue;
|
|
291
271
|
if (name === "register") continue;
|
|
292
272
|
if (name === "step") {
|
|
293
|
-
produces = extractProducedKeys(link,
|
|
273
|
+
produces = extractProducedKeys(link, ctx);
|
|
294
274
|
continue;
|
|
295
275
|
}
|
|
296
276
|
if (name === "dependencies") {
|
|
@@ -307,7 +287,7 @@ function extractFromRegisterCall(registerCall, sourceFile, checker) {
|
|
|
307
287
|
}
|
|
308
288
|
}
|
|
309
289
|
if (!stepType || !expression) {
|
|
310
|
-
const reExport = resolveReExportedCall(chain,
|
|
290
|
+
const reExport = resolveReExportedCall(chain, ctx);
|
|
311
291
|
if (reExport) {
|
|
312
292
|
if (!stepType) stepType = reExport.stepType;
|
|
313
293
|
if (!expression) expression = reExport.expression;
|
|
@@ -394,33 +374,37 @@ function extractDependencies(depsCall) {
|
|
|
394
374
|
}
|
|
395
375
|
return deps;
|
|
396
376
|
}
|
|
397
|
-
function extractProducedKeys(stepCall,
|
|
377
|
+
function extractProducedKeys(stepCall, ctx) {
|
|
398
378
|
const callback = stepCall.arguments[0];
|
|
399
379
|
if (!callback) return [];
|
|
400
|
-
if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) return extractProducedKeysFromCallback(callback,
|
|
380
|
+
if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) return extractProducedKeysFromCallback(callback, ctx);
|
|
401
381
|
return [];
|
|
402
382
|
}
|
|
403
|
-
function extractProducedKeysFromCallback(callback,
|
|
383
|
+
function extractProducedKeysFromCallback(callback, ctx) {
|
|
404
384
|
const body = callback.body;
|
|
405
|
-
if (!ts.isBlock(body)) return extractKeysFromExpression(body,
|
|
385
|
+
if (!ts.isBlock(body)) return extractKeysFromExpression(body, ctx);
|
|
406
386
|
const keys = /* @__PURE__ */ new Set();
|
|
407
387
|
function visitReturn(node) {
|
|
408
|
-
if (ts.isReturnStatement(node) && node.expression) for (const key of extractKeysFromExpression(node.expression,
|
|
388
|
+
if (ts.isReturnStatement(node) && node.expression) for (const key of extractKeysFromExpression(node.expression, ctx)) keys.add(key);
|
|
409
389
|
ts.forEachChild(node, visitReturn);
|
|
410
390
|
}
|
|
411
391
|
visitReturn(body);
|
|
412
392
|
return [...keys];
|
|
413
393
|
}
|
|
414
|
-
function extractKeysFromExpression(expr,
|
|
394
|
+
function extractKeysFromExpression(expr, ctx) {
|
|
415
395
|
while (ts.isParenthesizedExpression(expr)) expr = expr.expression;
|
|
416
396
|
if (ts.isObjectLiteralExpression(expr)) return expr.properties.filter((p) => ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)).map((p) => p.name.getText()).filter(Boolean);
|
|
397
|
+
if (!ctx.checker) {
|
|
398
|
+
ctx.needsChecker = true;
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
417
401
|
try {
|
|
418
|
-
return checker.getTypeAtLocation(expr).getProperties().map((p) => p.name).filter((n) => n !== "merge");
|
|
402
|
+
return ctx.checker.getTypeAtLocation(expr).getProperties().map((p) => p.name).filter((n) => n !== "merge");
|
|
419
403
|
} catch {
|
|
420
404
|
return [];
|
|
421
405
|
}
|
|
422
406
|
}
|
|
423
|
-
function resolveReExportedCall(chain,
|
|
407
|
+
function resolveReExportedCall(chain, ctx) {
|
|
424
408
|
const lastCall = chain[chain.length - 1];
|
|
425
409
|
if (!lastCall) return null;
|
|
426
410
|
const expr = lastCall.expression;
|
|
@@ -428,7 +412,11 @@ function resolveReExportedCall(chain, checker) {
|
|
|
428
412
|
if (ts.isIdentifier(expr)) identifier = expr;
|
|
429
413
|
else if (ts.isPropertyAccessExpression(expr) && ts.isIdentifier(expr.expression)) identifier = expr.expression;
|
|
430
414
|
if (!identifier) return null;
|
|
431
|
-
|
|
415
|
+
if (!ctx.checker) {
|
|
416
|
+
ctx.needsChecker = true;
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
const symbol = ctx.checker.getSymbolAtLocation(identifier);
|
|
432
420
|
if (!symbol) return null;
|
|
433
421
|
const decl = symbol.valueDeclaration;
|
|
434
422
|
if (!decl || !ts.isVariableDeclaration(decl) || !decl.initializer) return null;
|
|
@@ -449,116 +437,6 @@ function resolveReExportedCall(chain, checker) {
|
|
|
449
437
|
return null;
|
|
450
438
|
}
|
|
451
439
|
//#endregion
|
|
452
|
-
//#region src/analyzer/gherkinParser.ts
|
|
453
|
-
function parseFeatureFiles(filePaths) {
|
|
454
|
-
const scenarios = [];
|
|
455
|
-
for (const filePath of filePaths) {
|
|
456
|
-
const parsed = parseFeatureContent(fs.readFileSync(filePath, "utf-8"), filePath);
|
|
457
|
-
scenarios.push(...parsed);
|
|
458
|
-
}
|
|
459
|
-
return scenarios;
|
|
460
|
-
}
|
|
461
|
-
function parseFeatureContent(content, filePath) {
|
|
462
|
-
const feature = new Parser(new AstBuilder(messages.IdGenerator.uuid()), new GherkinClassicTokenMatcher()).parse(content).feature;
|
|
463
|
-
if (!feature) return [];
|
|
464
|
-
const featureBackground = [];
|
|
465
|
-
const scenarios = [];
|
|
466
|
-
const featureTags = tagNames(feature.tags);
|
|
467
|
-
for (const child of feature.children) {
|
|
468
|
-
if (child.background) featureBackground.push(...child.background.steps);
|
|
469
|
-
if (child.scenario) scenarios.push(...expandScenario(child.scenario, featureBackground, filePath, featureTags));
|
|
470
|
-
if (child.rule) {
|
|
471
|
-
const ruleBackground = [...featureBackground];
|
|
472
|
-
const ruleTags = [...featureTags, ...tagNames(child.rule.tags)];
|
|
473
|
-
for (const ruleChild of child.rule.children) {
|
|
474
|
-
if (ruleChild.background) ruleBackground.push(...ruleChild.background.steps);
|
|
475
|
-
if (ruleChild.scenario) scenarios.push(...expandScenario(ruleChild.scenario, ruleBackground, filePath, ruleTags));
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return scenarios;
|
|
480
|
-
}
|
|
481
|
-
/** Extract tag names (each keeping its leading `@`), deduped in order. */
|
|
482
|
-
function tagNames(tags) {
|
|
483
|
-
return [...new Set((tags ?? []).map((t) => t.name))];
|
|
484
|
-
}
|
|
485
|
-
function expandScenario(scenario, backgroundSteps, filePath, inheritedTags) {
|
|
486
|
-
const scenarioTags = [...inheritedTags, ...tagNames(scenario.tags)];
|
|
487
|
-
if (!(scenario.examples.length > 0 && scenario.examples.some((e) => e.tableBody.length > 0))) {
|
|
488
|
-
const bgParsed = convertSteps(backgroundSteps);
|
|
489
|
-
const scenarioParsed = convertSteps(scenario.steps);
|
|
490
|
-
const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioParsed]);
|
|
491
|
-
return [{
|
|
492
|
-
name: scenario.name,
|
|
493
|
-
file: filePath,
|
|
494
|
-
steps: allSteps,
|
|
495
|
-
tags: scenarioTags
|
|
496
|
-
}];
|
|
497
|
-
}
|
|
498
|
-
const results = [];
|
|
499
|
-
for (const example of scenario.examples) {
|
|
500
|
-
if (!example.tableHeader || example.tableBody.length === 0) continue;
|
|
501
|
-
const headers = example.tableHeader.cells.map((c) => c.value);
|
|
502
|
-
const exampleTags = [...scenarioTags, ...tagNames(example.tags)];
|
|
503
|
-
for (const row of example.tableBody) {
|
|
504
|
-
const values = row.cells.map((c) => c.value);
|
|
505
|
-
const substitution = {};
|
|
506
|
-
headers.forEach((h, i) => {
|
|
507
|
-
substitution[h] = values[i];
|
|
508
|
-
});
|
|
509
|
-
const bgParsed = convertSteps(backgroundSteps);
|
|
510
|
-
const scenarioSteps = convertSteps(scenario.steps).map((step) => ({
|
|
511
|
-
...step,
|
|
512
|
-
text: substituteExampleValues(step.text, substitution)
|
|
513
|
-
}));
|
|
514
|
-
const allSteps = resolveEffectiveKeywords([...bgParsed, ...scenarioSteps]);
|
|
515
|
-
results.push({
|
|
516
|
-
name: headers.map((h, i) => `${h}=${values[i]}`).join(", "),
|
|
517
|
-
file: filePath,
|
|
518
|
-
steps: allSteps,
|
|
519
|
-
tags: exampleTags,
|
|
520
|
-
outline: { name: scenario.name }
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
return results;
|
|
525
|
-
}
|
|
526
|
-
function convertSteps(steps) {
|
|
527
|
-
return steps.map((step) => ({
|
|
528
|
-
keyword: normalizeKeyword(step.keyword),
|
|
529
|
-
text: step.text,
|
|
530
|
-
line: step.location.line,
|
|
531
|
-
column: (step.location.column ?? 1) + step.keyword.length
|
|
532
|
-
}));
|
|
533
|
-
}
|
|
534
|
-
function normalizeKeyword(keyword) {
|
|
535
|
-
const trimmed = keyword.trim();
|
|
536
|
-
if (trimmed === "Given") return "Given";
|
|
537
|
-
if (trimmed === "When") return "When";
|
|
538
|
-
if (trimmed === "Then") return "Then";
|
|
539
|
-
if (trimmed === "And") return "And";
|
|
540
|
-
if (trimmed === "But") return "But";
|
|
541
|
-
return "Given";
|
|
542
|
-
}
|
|
543
|
-
function resolveEffectiveKeywords(steps) {
|
|
544
|
-
let lastEffective = "Given";
|
|
545
|
-
return steps.map((step) => {
|
|
546
|
-
let effectiveKeyword;
|
|
547
|
-
if (step.keyword === "And" || step.keyword === "But") effectiveKeyword = lastEffective;
|
|
548
|
-
else effectiveKeyword = step.keyword;
|
|
549
|
-
lastEffective = effectiveKeyword;
|
|
550
|
-
return {
|
|
551
|
-
...step,
|
|
552
|
-
effectiveKeyword
|
|
553
|
-
};
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
function substituteExampleValues(text, substitution) {
|
|
557
|
-
let result = text;
|
|
558
|
-
for (const [key, value] of Object.entries(substitution)) result = result.replace(new RegExp(`<${key}>`, "g"), value);
|
|
559
|
-
return result;
|
|
560
|
-
}
|
|
561
|
-
//#endregion
|
|
562
440
|
//#region src/analyzer/stepMatcher.ts
|
|
563
441
|
function compileDefinitions(definitions) {
|
|
564
442
|
const compiled = [];
|
|
@@ -693,8 +571,8 @@ function runRules(rules, scenario, matchedSteps) {
|
|
|
693
571
|
//#region src/analyzer/index.ts
|
|
694
572
|
async function analyze(config, options) {
|
|
695
573
|
const rules = options?.rules ?? defaultRules;
|
|
696
|
-
const stepFilePaths = await
|
|
697
|
-
const featureFilePaths = await
|
|
574
|
+
const stepFilePaths = await globFiles(config.stepFiles);
|
|
575
|
+
const featureFilePaths = await globFiles(config.featureFiles);
|
|
698
576
|
if (stepFilePaths.length === 0) return [];
|
|
699
577
|
if (featureFilePaths.length === 0) return [];
|
|
700
578
|
const stepDefinitions = extractStepDefinitions(stepFilePaths, config.tsConfigPath);
|
|
@@ -706,11 +584,6 @@ async function analyze(config, options) {
|
|
|
706
584
|
}
|
|
707
585
|
return diagnostics;
|
|
708
586
|
}
|
|
709
|
-
async function resolveGlobs(patterns) {
|
|
710
|
-
const files = [];
|
|
711
|
-
for (const pattern of patterns) for await (const file of glob(pattern)) files.push(path.resolve(file));
|
|
712
|
-
return [...new Set(files)];
|
|
713
|
-
}
|
|
714
587
|
//#endregion
|
|
715
588
|
//#region src/init.ts
|
|
716
589
|
const createBuilders = () => {
|
|
@@ -763,7 +636,12 @@ function afterFeature(fn) {
|
|
|
763
636
|
fn
|
|
764
637
|
});
|
|
765
638
|
}
|
|
766
|
-
/**
|
|
639
|
+
/**
|
|
640
|
+
* Run once before the entire run, ahead of any scenario (and before
|
|
641
|
+
* concurrency starts). Multiple `beforeAll` hooks run **in parallel** with no
|
|
642
|
+
* ordering between them — if a step of setup must precede another, sequence both
|
|
643
|
+
* inside a single hook.
|
|
644
|
+
*/
|
|
767
645
|
function beforeAll(fn) {
|
|
768
646
|
globalHookRegistry.add({
|
|
769
647
|
scope: "global",
|
|
@@ -771,7 +649,11 @@ function beforeAll(fn) {
|
|
|
771
649
|
fn
|
|
772
650
|
});
|
|
773
651
|
}
|
|
774
|
-
/**
|
|
652
|
+
/**
|
|
653
|
+
* Run once after the entire run, once every scenario is done. Multiple
|
|
654
|
+
* `afterAll` hooks run **in parallel** with no ordering between them — sequence
|
|
655
|
+
* dependent teardown inside a single hook.
|
|
656
|
+
*/
|
|
775
657
|
function afterAll(fn) {
|
|
776
658
|
globalHookRegistry.add({
|
|
777
659
|
scope: "global",
|