@autometa/test-builder 0.4.2 → 1.0.0-rc.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/README.md +1 -1
- package/dist/build-test-plan.d.ts +2 -0
- package/dist/index.cjs +1133 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -161
- package/dist/index.js +1047 -649
- package/dist/index.js.map +1 -1
- package/dist/internal/nodes.d.ts +69 -0
- package/dist/internal/scope-resolution.d.ts +4 -0
- package/dist/internal/summaries.d.ts +6 -0
- package/dist/internal/test-plan-builder.d.ts +55 -0
- package/dist/internal/utils.d.ts +18 -0
- package/dist/types.d.ts +113 -0
- package/package.json +29 -31
- package/.eslintignore +0 -3
- package/.eslintrc.cjs +0 -4
- package/.turbo/turbo-coverage.log +0 -36
- package/.turbo/turbo-lint$colon$fix.log +0 -4
- package/.turbo/turbo-lint.log +0 -4
- package/.turbo/turbo-lint:fix.log +0 -4
- package/.turbo/turbo-prettify.log +0 -6
- package/.turbo/turbo-test.log +0 -21
- package/CHANGELOG.md +0 -654
- package/dist/esm/index.js +0 -700
- package/dist/esm/index.js.map +0 -1
- package/dist/index.d.cts +0 -161
- package/tsup.config.ts +0 -14
package/dist/index.js
CHANGED
|
@@ -1,729 +1,1127 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
20
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
21
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
22
|
-
if (decorator = decorators[i])
|
|
23
|
-
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
24
|
-
if (kind && result)
|
|
25
|
-
__defProp(target, key, result);
|
|
26
|
-
return result;
|
|
27
|
-
};
|
|
28
|
-
var __accessCheck = (obj, member, msg) => {
|
|
29
|
-
if (!member.has(obj))
|
|
30
|
-
throw TypeError("Cannot " + msg);
|
|
31
|
-
};
|
|
32
|
-
var __privateAdd = (obj, member, value) => {
|
|
33
|
-
if (member.has(obj))
|
|
34
|
-
throw TypeError("Cannot add the same private member more than once");
|
|
35
|
-
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
36
|
-
};
|
|
37
|
-
var __privateMethod = (obj, member, method) => {
|
|
38
|
-
__accessCheck(obj, member, "access private method");
|
|
39
|
-
return method;
|
|
40
|
-
};
|
|
1
|
+
import { RegularExpression, CucumberExpression, ParameterTypeRegistry } from '@cucumber/cucumber-expressions';
|
|
2
|
+
import { createDefaultParameterTypes } from '@autometa/cucumber-expressions';
|
|
3
|
+
import path from 'node:path';
|
|
41
4
|
|
|
42
|
-
// src/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
5
|
+
// src/internal/test-plan-builder.ts
|
|
6
|
+
function normalizeName(value) {
|
|
7
|
+
return value.trim();
|
|
8
|
+
}
|
|
9
|
+
function normalizeKeyword(keyword) {
|
|
10
|
+
return keyword.trim();
|
|
11
|
+
}
|
|
12
|
+
function normalizeUri(uri) {
|
|
13
|
+
const cleaned = uri.replace(/^file:/, "");
|
|
14
|
+
const normalized = path.normalize(cleaned);
|
|
15
|
+
return normalized.replace(/[\\/]+/g, "/");
|
|
16
|
+
}
|
|
17
|
+
function buildScopeSuffix(id) {
|
|
18
|
+
return ` [${id}]`;
|
|
19
|
+
}
|
|
20
|
+
function buildExampleSuffix(exampleId, index) {
|
|
21
|
+
return ` [${exampleId}#${index + 1}]`;
|
|
22
|
+
}
|
|
23
|
+
function buildQualifiedName(segments) {
|
|
24
|
+
return segments.map((segment) => {
|
|
25
|
+
const keyword = segment.keyword.trim();
|
|
26
|
+
const name = segment.name?.trim();
|
|
27
|
+
const suffix = segment.suffix ?? "";
|
|
28
|
+
if (!name || name.length === 0) {
|
|
29
|
+
return `${keyword}${suffix}`;
|
|
30
|
+
}
|
|
31
|
+
return `${keyword}: ${name}${suffix}`;
|
|
32
|
+
}).join(" > ");
|
|
33
|
+
}
|
|
34
|
+
function collectTags(...sources) {
|
|
35
|
+
const result = [];
|
|
36
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37
|
+
for (const source of sources) {
|
|
38
|
+
if (!source) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
for (const tag of source) {
|
|
42
|
+
if (!seen.has(tag)) {
|
|
43
|
+
seen.add(tag);
|
|
44
|
+
result.push(tag);
|
|
80
45
|
}
|
|
81
|
-
}
|
|
82
|
-
this.data = {
|
|
83
|
-
scope: scope2,
|
|
84
|
-
gherkin: new nullNode()
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
var FeatureBridge = class extends GherkinCodeBridge {
|
|
89
|
-
constructor() {
|
|
90
|
-
super(...arguments);
|
|
91
|
-
this.scenarios = [];
|
|
92
|
-
this.rules = [];
|
|
93
|
-
this.steps = [];
|
|
94
|
-
}
|
|
95
|
-
accumulateTags() {
|
|
96
|
-
const scenarioTags = this.scenarios.map((scenario) => scenario.accumulateTags()).flat();
|
|
97
|
-
const ruleTags = this.rules.map((rule) => rule.accumulateTags()).flat();
|
|
98
|
-
const allTags = [...this.data.gherkin.tags, ...scenarioTags, ...ruleTags];
|
|
99
|
-
return [...new Set(allTags)];
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
var BackgroundBridge = class extends GherkinCodeBridge {
|
|
103
|
-
constructor() {
|
|
104
|
-
super(...arguments);
|
|
105
|
-
this.steps = [];
|
|
46
|
+
}
|
|
106
47
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function combineSteps(featureSteps, ruleSteps, scenarioSteps) {
|
|
51
|
+
return [
|
|
52
|
+
...featureSteps ?? [],
|
|
53
|
+
...ruleSteps ?? [],
|
|
54
|
+
...scenarioSteps ?? []
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
function cloneData(data) {
|
|
58
|
+
if (!data) {
|
|
59
|
+
return void 0;
|
|
113
60
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
61
|
+
return { ...data };
|
|
62
|
+
}
|
|
63
|
+
function mergeData(base, extra) {
|
|
64
|
+
if (!base && !extra) {
|
|
65
|
+
return void 0;
|
|
118
66
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
67
|
+
return {
|
|
68
|
+
...base ?? {},
|
|
69
|
+
...extra ?? {}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function createExampleData(group, compiled) {
|
|
73
|
+
const headers = group.tableHeader ?? [];
|
|
74
|
+
const rows = group.tableBody ?? [];
|
|
75
|
+
const row = rows[compiled.exampleIndex];
|
|
76
|
+
if (!row) {
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
const values = {};
|
|
80
|
+
headers.forEach((header, index) => {
|
|
81
|
+
const key = header.trim();
|
|
82
|
+
if (key.length === 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
values[key] = row[index] ?? "";
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
example: {
|
|
89
|
+
group: {
|
|
90
|
+
id: group.id,
|
|
91
|
+
name: group.name,
|
|
92
|
+
tags: [...group.tags]
|
|
93
|
+
},
|
|
94
|
+
index: compiled.exampleIndex,
|
|
95
|
+
values
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function normalizeError(error) {
|
|
100
|
+
if (error instanceof Error) {
|
|
101
|
+
return error;
|
|
125
102
|
}
|
|
126
|
-
|
|
127
|
-
return
|
|
103
|
+
if (typeof error === "string") {
|
|
104
|
+
return new Error(error);
|
|
128
105
|
}
|
|
129
|
-
|
|
130
|
-
|
|
106
|
+
return new Error(JSON.stringify(error));
|
|
107
|
+
}
|
|
108
|
+
function groupCompiledScenarios(compiled) {
|
|
109
|
+
const map = /* @__PURE__ */ new Map();
|
|
110
|
+
for (const scenario of compiled ?? []) {
|
|
111
|
+
const list = map.get(scenario.exampleGroupId) ?? [];
|
|
112
|
+
list.push(scenario);
|
|
113
|
+
map.set(scenario.exampleGroupId, list);
|
|
114
|
+
}
|
|
115
|
+
return map;
|
|
116
|
+
}
|
|
117
|
+
function isRule(element) {
|
|
118
|
+
return "elements" in element && Array.isArray(element.elements);
|
|
119
|
+
}
|
|
120
|
+
function isScenarioOutline(element) {
|
|
121
|
+
return "exampleGroups" in element;
|
|
122
|
+
}
|
|
123
|
+
function isScenario(element) {
|
|
124
|
+
return "steps" in element && !("exampleGroups" in element) && !("elements" in element);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/internal/nodes.ts
|
|
128
|
+
function createFeatureNode(init) {
|
|
129
|
+
return new FeatureNodeImpl(init);
|
|
130
|
+
}
|
|
131
|
+
function createRuleNode(init) {
|
|
132
|
+
return new RuleNodeImpl(init);
|
|
133
|
+
}
|
|
134
|
+
function createScenarioNode(init) {
|
|
135
|
+
return new ScenarioNodeImpl(init);
|
|
136
|
+
}
|
|
137
|
+
function createScenarioOutlineNode(init) {
|
|
138
|
+
return new ScenarioOutlineNodeImpl(init);
|
|
139
|
+
}
|
|
140
|
+
function createScenarioOutlineExample(init) {
|
|
141
|
+
return new ScenarioOutlineExampleImpl(init);
|
|
142
|
+
}
|
|
143
|
+
var FeatureNodeImpl = class {
|
|
144
|
+
constructor(init) {
|
|
145
|
+
this.type = "feature";
|
|
146
|
+
this.feature = init.feature;
|
|
147
|
+
this.scope = init.scope;
|
|
148
|
+
this.executions = init.executions;
|
|
149
|
+
this.scenarios = init.scenarios;
|
|
150
|
+
this.scenarioOutlines = init.outlines;
|
|
151
|
+
this.rules = init.rules;
|
|
152
|
+
this.name = init.feature.name;
|
|
153
|
+
this.keyword = normalizeKeyword(init.feature.keyword ?? "Feature");
|
|
154
|
+
if (init.feature.background) {
|
|
155
|
+
this.background = init.feature.background;
|
|
156
|
+
}
|
|
131
157
|
}
|
|
132
|
-
|
|
133
|
-
return this.
|
|
158
|
+
listExecutables() {
|
|
159
|
+
return this.executions;
|
|
134
160
|
}
|
|
135
161
|
};
|
|
136
|
-
var
|
|
137
|
-
constructor() {
|
|
138
|
-
|
|
139
|
-
this.
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return this.tags;
|
|
162
|
+
var RuleNodeImpl = class {
|
|
163
|
+
constructor(init) {
|
|
164
|
+
this.type = "rule";
|
|
165
|
+
this.rule = init.rule;
|
|
166
|
+
this.scope = init.scope;
|
|
167
|
+
this.qualifiedName = init.qualifiedName;
|
|
168
|
+
this.name = init.rule.name;
|
|
169
|
+
this.keyword = normalizeKeyword(init.rule.keyword ?? "Rule");
|
|
170
|
+
if (init.rule.background) {
|
|
171
|
+
this.background = init.rule.background;
|
|
172
|
+
}
|
|
173
|
+
this.scenarios = init.scenarios;
|
|
174
|
+
this.scenarioOutlines = init.outlines;
|
|
150
175
|
}
|
|
151
176
|
};
|
|
152
|
-
var
|
|
153
|
-
constructor() {
|
|
154
|
-
|
|
155
|
-
this.
|
|
156
|
-
this.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
177
|
+
var ScenarioExecutionBase = class {
|
|
178
|
+
constructor(type, init) {
|
|
179
|
+
this.resultState = { status: "pending" };
|
|
180
|
+
this.type = type;
|
|
181
|
+
this.id = init.id;
|
|
182
|
+
this.name = init.name;
|
|
183
|
+
this.keyword = normalizeKeyword(init.keyword);
|
|
184
|
+
this.qualifiedName = init.qualifiedName;
|
|
185
|
+
this.tags = [...init.tags];
|
|
186
|
+
this.mode = init.mode;
|
|
187
|
+
this.pending = init.pending;
|
|
188
|
+
if (init.pendingReason !== void 0) {
|
|
189
|
+
this.pendingReason = init.pendingReason;
|
|
190
|
+
}
|
|
191
|
+
if (init.timeout !== void 0) {
|
|
192
|
+
this.timeout = init.timeout;
|
|
193
|
+
}
|
|
194
|
+
const data = cloneData(init.data);
|
|
195
|
+
if (data !== void 0) {
|
|
196
|
+
this.data = data;
|
|
197
|
+
}
|
|
198
|
+
this.feature = init.feature;
|
|
199
|
+
if (init.rule) {
|
|
200
|
+
this.rule = init.rule;
|
|
201
|
+
}
|
|
202
|
+
if (init.outline) {
|
|
203
|
+
this.outline = init.outline;
|
|
204
|
+
}
|
|
205
|
+
this.scope = init.scope;
|
|
206
|
+
this.summary = init.summary;
|
|
207
|
+
this.gherkin = init.gherkin;
|
|
208
|
+
this.gherkinSteps = [...init.gherkinSteps];
|
|
209
|
+
this.steps = [...init.steps];
|
|
210
|
+
this.ancestors = [...init.ancestors];
|
|
211
|
+
}
|
|
212
|
+
get result() {
|
|
213
|
+
return this.resultState;
|
|
214
|
+
}
|
|
215
|
+
markPassed() {
|
|
216
|
+
const startedAt = this.resultState.startedAt ?? Date.now();
|
|
217
|
+
this.resultState = {
|
|
218
|
+
status: "passed",
|
|
219
|
+
startedAt,
|
|
220
|
+
completedAt: Date.now()
|
|
221
|
+
};
|
|
160
222
|
}
|
|
161
|
-
|
|
162
|
-
|
|
223
|
+
markFailed(error) {
|
|
224
|
+
const startedAt = this.resultState.startedAt ?? Date.now();
|
|
225
|
+
this.resultState = {
|
|
226
|
+
status: "failed",
|
|
227
|
+
error: normalizeError(error),
|
|
228
|
+
startedAt,
|
|
229
|
+
completedAt: Date.now()
|
|
230
|
+
};
|
|
163
231
|
}
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
232
|
+
markSkipped(reason) {
|
|
233
|
+
const timestamp = Date.now();
|
|
234
|
+
this.resultState = {
|
|
235
|
+
status: "skipped",
|
|
236
|
+
startedAt: this.resultState.startedAt ?? timestamp,
|
|
237
|
+
completedAt: timestamp,
|
|
238
|
+
...reason !== void 0 ? { reason } : {}
|
|
239
|
+
};
|
|
168
240
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
241
|
+
markPending(reason) {
|
|
242
|
+
const timestamp = Date.now();
|
|
243
|
+
this.resultState = {
|
|
244
|
+
status: "pending",
|
|
245
|
+
startedAt: this.resultState.startedAt ?? timestamp,
|
|
246
|
+
completedAt: timestamp,
|
|
247
|
+
...reason !== void 0 ? { reason } : {}
|
|
248
|
+
};
|
|
175
249
|
}
|
|
176
|
-
|
|
177
|
-
|
|
250
|
+
reset() {
|
|
251
|
+
this.resultState = { status: "pending" };
|
|
178
252
|
}
|
|
179
|
-
|
|
180
|
-
|
|
253
|
+
};
|
|
254
|
+
var ScenarioNodeImpl = class extends ScenarioExecutionBase {
|
|
255
|
+
constructor(init) {
|
|
256
|
+
super("scenario", init);
|
|
181
257
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
258
|
+
};
|
|
259
|
+
var ScenarioOutlineExampleImpl = class extends ScenarioExecutionBase {
|
|
260
|
+
constructor(init) {
|
|
261
|
+
super("example", init);
|
|
262
|
+
this.outline = init.outline;
|
|
263
|
+
this.exampleGroup = init.exampleGroup;
|
|
264
|
+
this.compiled = init.gherkin;
|
|
265
|
+
this.exampleIndex = init.exampleIndex;
|
|
186
266
|
}
|
|
187
267
|
};
|
|
188
|
-
var
|
|
189
|
-
|
|
190
|
-
|
|
268
|
+
var ScenarioOutlineNodeImpl = class {
|
|
269
|
+
constructor(init) {
|
|
270
|
+
this.type = "scenarioOutline";
|
|
271
|
+
this.outline = init.outline;
|
|
272
|
+
this.summary = init.summary;
|
|
273
|
+
this.scope = init.scope;
|
|
274
|
+
this.keyword = normalizeKeyword(init.keyword);
|
|
275
|
+
this.name = init.name;
|
|
276
|
+
this.qualifiedName = init.qualifiedName;
|
|
277
|
+
this.tags = [...init.tags];
|
|
278
|
+
this.mode = init.mode;
|
|
279
|
+
this.pending = init.pending;
|
|
280
|
+
if (init.pendingReason !== void 0) {
|
|
281
|
+
this.pendingReason = init.pendingReason;
|
|
282
|
+
}
|
|
283
|
+
if (init.timeout !== void 0) {
|
|
284
|
+
this.timeout = init.timeout;
|
|
285
|
+
}
|
|
286
|
+
const data = cloneData(init.data);
|
|
287
|
+
if (data !== void 0) {
|
|
288
|
+
this.data = data;
|
|
289
|
+
}
|
|
290
|
+
this.ancestors = [...init.ancestors];
|
|
291
|
+
if (init.rule) {
|
|
292
|
+
this.rule = init.rule;
|
|
293
|
+
}
|
|
294
|
+
this.feature = init.feature;
|
|
295
|
+
this.mutableExamples = init.examples;
|
|
191
296
|
}
|
|
192
|
-
get
|
|
193
|
-
return this.
|
|
297
|
+
get examples() {
|
|
298
|
+
return this.mutableExamples;
|
|
194
299
|
}
|
|
195
300
|
};
|
|
196
301
|
|
|
197
|
-
// src/
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
302
|
+
// src/internal/summaries.ts
|
|
303
|
+
function bucketScenarioSummaries(summaries) {
|
|
304
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
305
|
+
for (const summary of summaries) {
|
|
306
|
+
const key = createSummaryKey(
|
|
307
|
+
summary.scenario.kind,
|
|
308
|
+
summary.scenario.name,
|
|
309
|
+
summary.rule?.id
|
|
310
|
+
);
|
|
311
|
+
const existing = buckets.get(key) ?? [];
|
|
312
|
+
existing.push(summary);
|
|
313
|
+
buckets.set(key, existing);
|
|
205
314
|
}
|
|
206
|
-
|
|
207
|
-
|
|
315
|
+
return buckets;
|
|
316
|
+
}
|
|
317
|
+
function createSummaryKey(kind, scenarioName, parentScopeId) {
|
|
318
|
+
return [
|
|
319
|
+
kind,
|
|
320
|
+
normalizeName(scenarioName),
|
|
321
|
+
parentScopeId ?? "root"
|
|
322
|
+
].join("::");
|
|
323
|
+
}
|
|
324
|
+
function describeSummary(summary) {
|
|
325
|
+
const parts = [summary.scenario.name];
|
|
326
|
+
if (summary.rule) {
|
|
327
|
+
parts.push(`in rule '${summary.rule.name}'`);
|
|
208
328
|
}
|
|
209
|
-
|
|
329
|
+
return `${summary.scenario.kind} '${parts.join(" ")}'`;
|
|
210
330
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
331
|
+
|
|
332
|
+
// src/internal/scope-resolution.ts
|
|
333
|
+
function resolveFeatureScope(adapter, feature) {
|
|
334
|
+
const normalizedName = normalizeName(feature.name);
|
|
335
|
+
const featureUri = feature.uri ? normalizeUri(feature.uri) : void 0;
|
|
336
|
+
const matches = adapter.features.filter((scope) => {
|
|
337
|
+
if (scope.kind !== "feature") {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
const scopeName = normalizeName(scope.name);
|
|
341
|
+
if (scopeName !== normalizedName) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
if (!featureUri) {
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
const scopeFile = typeof scope.data?.file === "string" ? normalizeUri(scope.data.file) : void 0;
|
|
348
|
+
return scopeFile ? scopeFile === featureUri : true;
|
|
349
|
+
});
|
|
350
|
+
if (matches.length === 1) {
|
|
351
|
+
const [match] = matches;
|
|
352
|
+
if (!match) {
|
|
353
|
+
throw new Error("Unexpected empty feature scope match");
|
|
354
|
+
}
|
|
355
|
+
return match;
|
|
216
356
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
357
|
+
if (matches.length === 0) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`No feature scope registered for feature '${feature.name}'. Provide featureScope explicitly to resolve the association.`
|
|
360
|
+
);
|
|
220
361
|
}
|
|
362
|
+
throw new Error(
|
|
363
|
+
`Multiple feature scopes match feature '${feature.name}'. Provide featureScope explicitly to disambiguate.`
|
|
364
|
+
);
|
|
221
365
|
}
|
|
222
|
-
function
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
366
|
+
function findChildScope(parent, kind, name) {
|
|
367
|
+
const normalizedName = normalizeName(name);
|
|
368
|
+
const matches = parent.children.filter(
|
|
369
|
+
(child) => child.kind === kind && normalizeName(child.name) === normalizedName
|
|
370
|
+
);
|
|
371
|
+
if (matches.length === 1) {
|
|
372
|
+
const [match] = matches;
|
|
373
|
+
if (!match) {
|
|
374
|
+
throw new Error("Unexpected empty child scope match");
|
|
375
|
+
}
|
|
376
|
+
return match;
|
|
227
377
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
378
|
+
if (matches.length === 0) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Could not find ${kind} scope named '${name}' beneath '${parent.name}'`
|
|
381
|
+
);
|
|
231
382
|
}
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Multiple ${kind} scopes named '${name}' were found beneath '${parent.name}'. Names must be unique within the same parent scope.`
|
|
385
|
+
);
|
|
232
386
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
387
|
+
|
|
388
|
+
// src/internal/test-plan-builder.ts
|
|
389
|
+
var FEATURE_SEGMENT_KEY = "feature";
|
|
390
|
+
var RULE_SEGMENT_KEY = "rule";
|
|
391
|
+
var EXAMPLE_SEGMENT_KEY = "Example";
|
|
392
|
+
var _TestPlanBuilder = class _TestPlanBuilder {
|
|
393
|
+
constructor(feature, featureScope, adapter) {
|
|
394
|
+
this.feature = feature;
|
|
395
|
+
this.featureScope = featureScope;
|
|
396
|
+
this.adapter = adapter;
|
|
397
|
+
this.executions = [];
|
|
398
|
+
this.byId = /* @__PURE__ */ new Map();
|
|
399
|
+
this.byQualifiedName = /* @__PURE__ */ new Map();
|
|
400
|
+
this.featureScenarios = [];
|
|
401
|
+
this.featureScenarioOutlines = [];
|
|
402
|
+
this.featureRules = [];
|
|
403
|
+
this.ruleScenarioMap = /* @__PURE__ */ new Map();
|
|
404
|
+
this.ruleOutlineMap = /* @__PURE__ */ new Map();
|
|
405
|
+
this.outlineExamplesMap = /* @__PURE__ */ new Map();
|
|
406
|
+
const summaries = adapter.listScenarios().filter((summary) => summary.feature.id === featureScope.id);
|
|
407
|
+
this.summaryBuckets = bucketScenarioSummaries(summaries);
|
|
408
|
+
this.featureSegment = {
|
|
409
|
+
keyword: normalizeKeyword(feature.keyword ?? FEATURE_SEGMENT_KEY),
|
|
410
|
+
name: feature.name,
|
|
411
|
+
suffix: buildScopeSuffix(featureScope.id)
|
|
412
|
+
};
|
|
413
|
+
this.parameterRegistry = resolveParameterRegistry(adapter.getParameterRegistry?.());
|
|
414
|
+
createDefaultParameterTypes()(this.parameterRegistry);
|
|
415
|
+
}
|
|
416
|
+
build() {
|
|
417
|
+
this.featureNode = createFeatureNode({
|
|
418
|
+
feature: this.feature,
|
|
419
|
+
scope: this.featureScope,
|
|
420
|
+
executions: this.executions,
|
|
421
|
+
scenarios: this.featureScenarios,
|
|
422
|
+
outlines: this.featureScenarioOutlines,
|
|
423
|
+
rules: this.featureRules
|
|
424
|
+
});
|
|
425
|
+
for (const element of this.feature.elements ?? []) {
|
|
426
|
+
if (isRule(element)) {
|
|
427
|
+
this.processRule(element);
|
|
428
|
+
} else if (isScenarioOutline(element)) {
|
|
429
|
+
this.processScenarioOutline(element, void 0);
|
|
430
|
+
} else if (isScenario(element)) {
|
|
431
|
+
this.processScenario(element, void 0);
|
|
239
432
|
}
|
|
240
433
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
434
|
+
this.ensureAllSummariesConsumed();
|
|
435
|
+
return new TestPlanImpl(
|
|
436
|
+
this.featureNode,
|
|
437
|
+
this.executions,
|
|
438
|
+
this.byId,
|
|
439
|
+
this.byQualifiedName
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
processRule(rule) {
|
|
443
|
+
const ruleScope = findChildScope(this.featureScope, "rule", rule.name);
|
|
444
|
+
const qualifiedName = buildQualifiedName([
|
|
445
|
+
this.featureSegment,
|
|
446
|
+
{
|
|
447
|
+
keyword: normalizeKeyword(rule.keyword ?? RULE_SEGMENT_KEY),
|
|
448
|
+
name: rule.name,
|
|
449
|
+
suffix: buildScopeSuffix(ruleScope.id)
|
|
245
450
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
451
|
+
]);
|
|
452
|
+
const ruleScenarios = [];
|
|
453
|
+
const ruleOutlines = [];
|
|
454
|
+
const ruleNode = createRuleNode({
|
|
455
|
+
rule,
|
|
456
|
+
scope: ruleScope,
|
|
457
|
+
qualifiedName,
|
|
458
|
+
scenarios: ruleScenarios,
|
|
459
|
+
outlines: ruleOutlines
|
|
460
|
+
});
|
|
461
|
+
this.ruleScenarioMap.set(ruleNode, ruleScenarios);
|
|
462
|
+
this.ruleOutlineMap.set(ruleNode, ruleOutlines);
|
|
463
|
+
this.featureRules.push(ruleNode);
|
|
464
|
+
for (const element of rule.elements ?? []) {
|
|
465
|
+
if (isScenarioOutline(element)) {
|
|
466
|
+
this.processScenarioOutline(element, ruleNode);
|
|
467
|
+
} else if (isScenario(element)) {
|
|
468
|
+
this.processScenario(element, ruleNode);
|
|
251
469
|
}
|
|
252
470
|
}
|
|
253
471
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
472
|
+
processScenario(gherkinScenario, ruleNode) {
|
|
473
|
+
const ruleScope = ruleNode?.scope;
|
|
474
|
+
const summary = this.consumeSummary(
|
|
475
|
+
"scenario",
|
|
476
|
+
gherkinScenario.name,
|
|
477
|
+
ruleScope
|
|
478
|
+
);
|
|
479
|
+
if (summary.scenario.kind !== "scenario") {
|
|
480
|
+
throw new Error(
|
|
481
|
+
`Scope mismatch: expected scenario kind 'scenario' but received '${summary.scenario.kind}' for '${gherkinScenario.name}'`
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
const qualifiedName = buildQualifiedName([
|
|
485
|
+
this.featureSegment,
|
|
486
|
+
...ruleNode ? [
|
|
487
|
+
{
|
|
488
|
+
keyword: normalizeKeyword(ruleNode.keyword),
|
|
489
|
+
name: ruleNode.name,
|
|
490
|
+
suffix: buildScopeSuffix(ruleNode.scope.id)
|
|
491
|
+
}
|
|
492
|
+
] : [],
|
|
493
|
+
{
|
|
494
|
+
keyword: normalizeKeyword(gherkinScenario.keyword ?? "Scenario"),
|
|
495
|
+
name: gherkinScenario.name,
|
|
496
|
+
suffix: buildScopeSuffix(summary.scenario.id)
|
|
497
|
+
}
|
|
498
|
+
]);
|
|
499
|
+
const gherkinSteps = combineSteps(
|
|
500
|
+
this.feature.background?.steps,
|
|
501
|
+
ruleNode?.background?.steps,
|
|
502
|
+
gherkinScenario.steps
|
|
503
|
+
);
|
|
504
|
+
const resolvedSteps = this.resolveStepDefinitions(summary, gherkinSteps, {
|
|
505
|
+
scenario: gherkinScenario.name,
|
|
506
|
+
...ruleNode ? { rule: ruleNode.name } : {}
|
|
507
|
+
});
|
|
508
|
+
const scenarioData = cloneData(summary.scenario.data);
|
|
509
|
+
const scenarioNode = createScenarioNode({
|
|
510
|
+
id: summary.id,
|
|
511
|
+
feature: this.featureNode,
|
|
512
|
+
name: gherkinScenario.name,
|
|
513
|
+
keyword: gherkinScenario.keyword ?? "Scenario",
|
|
514
|
+
qualifiedName,
|
|
515
|
+
tags: collectTags(
|
|
516
|
+
this.feature.tags,
|
|
517
|
+
ruleNode?.rule.tags,
|
|
518
|
+
gherkinScenario.tags,
|
|
519
|
+
summary.feature.tags,
|
|
520
|
+
summary.scenario.tags
|
|
521
|
+
),
|
|
522
|
+
mode: summary.scenario.mode,
|
|
523
|
+
pending: summary.scenario.pending,
|
|
524
|
+
...summary.scenario.pendingReason ? { pendingReason: summary.scenario.pendingReason } : {},
|
|
525
|
+
scope: summary.scenario,
|
|
526
|
+
summary,
|
|
527
|
+
gherkin: gherkinScenario,
|
|
528
|
+
gherkinSteps,
|
|
529
|
+
steps: resolvedSteps,
|
|
530
|
+
ancestors: summary.ancestors,
|
|
531
|
+
...ruleNode ? { rule: ruleNode } : {},
|
|
532
|
+
...summary.scenario.timeout !== void 0 ? { timeout: summary.scenario.timeout } : {},
|
|
533
|
+
...scenarioData ? { data: scenarioData } : {}
|
|
534
|
+
});
|
|
535
|
+
if (ruleNode) {
|
|
536
|
+
this.getRuleScenarios(ruleNode).push(scenarioNode);
|
|
537
|
+
} else {
|
|
538
|
+
this.featureScenarios.push(scenarioNode);
|
|
539
|
+
}
|
|
540
|
+
this.registerExecution(scenarioNode);
|
|
541
|
+
}
|
|
542
|
+
processScenarioOutline(outline, ruleNode) {
|
|
543
|
+
const ruleScope = ruleNode?.scope;
|
|
544
|
+
const summary = this.consumeSummary(
|
|
545
|
+
"scenarioOutline",
|
|
546
|
+
outline.name,
|
|
547
|
+
ruleScope
|
|
548
|
+
);
|
|
549
|
+
if (summary.scenario.kind !== "scenarioOutline") {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`Scope mismatch: expected scenario kind 'scenarioOutline' but received '${summary.scenario.kind}' for '${outline.name}'`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
const outlineQualifiedName = buildQualifiedName([
|
|
555
|
+
this.featureSegment,
|
|
556
|
+
...ruleNode ? [
|
|
557
|
+
{
|
|
558
|
+
keyword: normalizeKeyword(ruleNode.keyword),
|
|
559
|
+
name: ruleNode.name,
|
|
560
|
+
suffix: buildScopeSuffix(ruleNode.scope.id)
|
|
561
|
+
}
|
|
562
|
+
] : [],
|
|
563
|
+
{
|
|
564
|
+
keyword: normalizeKeyword(outline.keyword ?? "Scenario Outline"),
|
|
565
|
+
name: outline.name,
|
|
566
|
+
suffix: buildScopeSuffix(summary.scenario.id)
|
|
567
|
+
}
|
|
568
|
+
]);
|
|
569
|
+
const outlineData = cloneData(summary.scenario.data);
|
|
570
|
+
const outlineExamples = [];
|
|
571
|
+
const outlineNode = createScenarioOutlineNode({
|
|
572
|
+
outline,
|
|
573
|
+
summary,
|
|
574
|
+
scope: summary.scenario,
|
|
575
|
+
keyword: outline.keyword ?? "Scenario Outline",
|
|
576
|
+
name: outline.name,
|
|
577
|
+
qualifiedName: outlineQualifiedName,
|
|
578
|
+
tags: collectTags(
|
|
579
|
+
this.feature.tags,
|
|
580
|
+
ruleNode?.rule.tags,
|
|
581
|
+
outline.tags,
|
|
582
|
+
summary.feature.tags,
|
|
583
|
+
summary.scenario.tags
|
|
584
|
+
),
|
|
585
|
+
mode: summary.scenario.mode,
|
|
586
|
+
pending: summary.scenario.pending,
|
|
587
|
+
...summary.scenario.pendingReason ? { pendingReason: summary.scenario.pendingReason } : {},
|
|
588
|
+
ancestors: summary.ancestors,
|
|
589
|
+
feature: this.featureNode,
|
|
590
|
+
...summary.scenario.timeout !== void 0 ? { timeout: summary.scenario.timeout } : {},
|
|
591
|
+
...outlineData ? { data: outlineData } : {},
|
|
592
|
+
...ruleNode ? { rule: ruleNode } : {},
|
|
593
|
+
examples: outlineExamples
|
|
594
|
+
});
|
|
595
|
+
this.outlineExamplesMap.set(outlineNode, outlineExamples);
|
|
596
|
+
if (ruleNode) {
|
|
597
|
+
this.getRuleOutlines(ruleNode).push(outlineNode);
|
|
598
|
+
} else {
|
|
599
|
+
this.featureScenarioOutlines.push(outlineNode);
|
|
600
|
+
}
|
|
601
|
+
const compiledByGroup = groupCompiledScenarios(outline.compiledScenarios);
|
|
602
|
+
for (const group of outline.exampleGroups ?? []) {
|
|
603
|
+
const compiledForGroup = compiledByGroup.get(group.id) ?? [];
|
|
604
|
+
compiledForGroup.sort((a, b) => a.exampleIndex - b.exampleIndex);
|
|
605
|
+
for (const compiled of compiledForGroup) {
|
|
606
|
+
const qualifiedName = buildQualifiedName([
|
|
607
|
+
this.featureSegment,
|
|
608
|
+
...ruleNode ? [
|
|
609
|
+
{
|
|
610
|
+
keyword: normalizeKeyword(ruleNode.keyword),
|
|
611
|
+
name: ruleNode.name,
|
|
612
|
+
suffix: buildScopeSuffix(ruleNode.scope.id)
|
|
613
|
+
}
|
|
614
|
+
] : [],
|
|
615
|
+
{
|
|
616
|
+
keyword: normalizeKeyword(outline.keyword ?? "Scenario Outline"),
|
|
617
|
+
name: outline.name,
|
|
618
|
+
suffix: buildScopeSuffix(summary.scenario.id)
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
keyword: EXAMPLE_SEGMENT_KEY,
|
|
622
|
+
name: compiled.name,
|
|
623
|
+
suffix: buildExampleSuffix(compiled.id, compiled.exampleIndex)
|
|
624
|
+
}
|
|
625
|
+
]);
|
|
626
|
+
const gherkinSteps = combineSteps(
|
|
627
|
+
this.feature.background?.steps,
|
|
628
|
+
ruleNode?.background?.steps,
|
|
629
|
+
compiled.steps
|
|
630
|
+
);
|
|
631
|
+
const resolvedSteps = this.resolveStepDefinitions(summary, gherkinSteps, {
|
|
632
|
+
scenario: compiled.name,
|
|
633
|
+
outline: outline.name,
|
|
634
|
+
...ruleNode ? { rule: ruleNode.name } : {}
|
|
635
|
+
});
|
|
636
|
+
const exampleData = mergeData(
|
|
637
|
+
cloneData(summary.scenario.data),
|
|
638
|
+
createExampleData(group, compiled)
|
|
639
|
+
);
|
|
640
|
+
const exampleExecution = createScenarioOutlineExample({
|
|
641
|
+
id: compiled.id,
|
|
642
|
+
feature: this.featureNode,
|
|
643
|
+
outline: outlineNode,
|
|
644
|
+
name: compiled.name,
|
|
645
|
+
keyword: compiled.keyword ?? outline.keyword ?? "Scenario Outline",
|
|
646
|
+
qualifiedName,
|
|
647
|
+
tags: collectTags(
|
|
648
|
+
this.feature.tags,
|
|
649
|
+
ruleNode?.rule.tags,
|
|
650
|
+
outline.tags,
|
|
651
|
+
group.tags,
|
|
652
|
+
compiled.tags,
|
|
653
|
+
summary.feature.tags,
|
|
654
|
+
summary.scenario.tags
|
|
655
|
+
),
|
|
656
|
+
mode: summary.scenario.mode,
|
|
657
|
+
pending: summary.scenario.pending,
|
|
658
|
+
...summary.scenario.pendingReason ? { pendingReason: summary.scenario.pendingReason } : {},
|
|
659
|
+
scope: summary.scenario,
|
|
660
|
+
summary,
|
|
661
|
+
gherkin: compiled,
|
|
662
|
+
gherkinSteps,
|
|
663
|
+
steps: resolvedSteps,
|
|
664
|
+
ancestors: summary.ancestors,
|
|
665
|
+
exampleGroup: group,
|
|
666
|
+
exampleIndex: compiled.exampleIndex,
|
|
667
|
+
...ruleNode ? { rule: ruleNode } : {},
|
|
668
|
+
...summary.scenario.timeout !== void 0 ? { timeout: summary.scenario.timeout } : {},
|
|
669
|
+
...exampleData ? { data: exampleData } : {}
|
|
670
|
+
});
|
|
671
|
+
this.getOutlineExamples(outlineNode).push(exampleExecution);
|
|
672
|
+
this.registerExecution(exampleExecution);
|
|
673
|
+
}
|
|
260
674
|
}
|
|
261
675
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
676
|
+
consumeSummary(kind, scenarioName, parentScope) {
|
|
677
|
+
const key = createSummaryKey(kind, scenarioName, parentScope?.id);
|
|
678
|
+
const bucket = this.summaryBuckets.get(key);
|
|
679
|
+
if (!bucket || bucket.length === 0) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`Could not find a registered ${kind} scope for '${scenarioName}'${parentScope ? ` within '${parentScope.name}'` : ""}`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
const summary = bucket.shift();
|
|
685
|
+
if (bucket.length === 0) {
|
|
686
|
+
this.summaryBuckets.delete(key);
|
|
687
|
+
}
|
|
688
|
+
return summary;
|
|
270
689
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
690
|
+
registerExecution(execution) {
|
|
691
|
+
if (this.byId.has(execution.id)) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
`Duplicate scenario identifier detected: '${execution.id}' for '${execution.qualifiedName}'`
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
if (this.byQualifiedName.has(execution.qualifiedName)) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
`Duplicate qualified scenario name detected: '${execution.qualifiedName}'`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
this.executions.push(execution);
|
|
702
|
+
this.byId.set(execution.id, execution);
|
|
703
|
+
this.byQualifiedName.set(execution.qualifiedName, execution);
|
|
704
|
+
}
|
|
705
|
+
ensureAllSummariesConsumed() {
|
|
706
|
+
const leftovers = [];
|
|
707
|
+
for (const bucket of this.summaryBuckets.values()) {
|
|
708
|
+
for (const summary of bucket) {
|
|
709
|
+
leftovers.push(describeSummary(summary));
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (leftovers.length > 0) {
|
|
713
|
+
throw new Error(
|
|
714
|
+
`The following scope scenarios were not matched to Gherkin nodes for feature '${this.feature.name}': ${leftovers.join(", ")}`
|
|
715
|
+
);
|
|
275
716
|
}
|
|
276
717
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
} = outline;
|
|
284
|
-
const title = scope2.title(gherkin);
|
|
285
|
-
if (testName === title) {
|
|
286
|
-
return outline;
|
|
718
|
+
getRuleScenarios(rule) {
|
|
719
|
+
const list = this.ruleScenarioMap.get(rule);
|
|
720
|
+
if (!list) {
|
|
721
|
+
throw new Error(`Rule '${rule.name}' has no associated scenario registry`);
|
|
722
|
+
}
|
|
723
|
+
return list;
|
|
287
724
|
}
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
if (
|
|
291
|
-
|
|
725
|
+
getRuleOutlines(rule) {
|
|
726
|
+
const list = this.ruleOutlineMap.get(rule);
|
|
727
|
+
if (!list) {
|
|
728
|
+
throw new Error(`Rule '${rule.name}' has no associated scenario outline registry`);
|
|
292
729
|
}
|
|
730
|
+
return list;
|
|
293
731
|
}
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
732
|
+
getOutlineExamples(outline) {
|
|
733
|
+
const list = this.outlineExamplesMap.get(outline);
|
|
734
|
+
if (!list) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
`Scenario outline '${outline.name}' has no associated example registry`
|
|
737
|
+
);
|
|
299
738
|
}
|
|
739
|
+
return list;
|
|
300
740
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
741
|
+
resolveStepDefinitions(summary, gherkinSteps, context) {
|
|
742
|
+
if (summary.steps.length === 0) {
|
|
743
|
+
if (gherkinSteps.length === 0) {
|
|
744
|
+
return [];
|
|
745
|
+
}
|
|
746
|
+
return gherkinSteps.map(
|
|
747
|
+
(step, index) => this.createMissingStepDefinition(summary, context, step, index)
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
const remaining = new Set(summary.steps);
|
|
751
|
+
const ordered = [];
|
|
752
|
+
const matchers = /* @__PURE__ */ new Map();
|
|
753
|
+
let encounteredMissing = false;
|
|
754
|
+
for (const step of gherkinSteps) {
|
|
755
|
+
const matched = this.findMatchingStepDefinition(
|
|
756
|
+
step,
|
|
757
|
+
summary.steps,
|
|
758
|
+
remaining,
|
|
759
|
+
matchers
|
|
760
|
+
);
|
|
761
|
+
if (!matched) {
|
|
762
|
+
ordered.push(
|
|
763
|
+
this.createMissingStepDefinition(
|
|
764
|
+
summary,
|
|
765
|
+
context,
|
|
766
|
+
step,
|
|
767
|
+
ordered.length
|
|
768
|
+
)
|
|
769
|
+
);
|
|
770
|
+
encounteredMissing = true;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
ordered.push(matched);
|
|
774
|
+
remaining.delete(matched);
|
|
775
|
+
}
|
|
776
|
+
if (encounteredMissing) {
|
|
777
|
+
return ordered;
|
|
778
|
+
}
|
|
779
|
+
return ordered;
|
|
780
|
+
}
|
|
781
|
+
findMatchingStepDefinition(step, definitions, remaining, matchers) {
|
|
782
|
+
const rawKeyword = normalizeKeyword(step.keyword ?? "");
|
|
783
|
+
const wildcard = isFlexibleKeyword(rawKeyword);
|
|
784
|
+
const keyword = wildcard ? void 0 : normalizeGherkinStepKeyword(rawKeyword);
|
|
785
|
+
const candidates = definitions.filter((definition) => {
|
|
786
|
+
if (!remaining.has(definition)) {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
if (wildcard) {
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
return keyword ? definition.keyword === keyword : false;
|
|
793
|
+
});
|
|
794
|
+
for (const definition of candidates) {
|
|
795
|
+
if (this.matchesStepExpression(definition, step.text, matchers)) {
|
|
796
|
+
return definition;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return void 0;
|
|
312
800
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
801
|
+
matchesStepExpression(definition, text, matchers) {
|
|
802
|
+
let matcher = matchers.get(definition);
|
|
803
|
+
if (!matcher) {
|
|
804
|
+
matcher = this.createMatcher(definition.expression);
|
|
805
|
+
matchers.set(definition, matcher);
|
|
317
806
|
}
|
|
807
|
+
return matcher(text);
|
|
318
808
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
return
|
|
809
|
+
createMatcher(expression) {
|
|
810
|
+
if (expression instanceof RegExp) {
|
|
811
|
+
const regex = new RegExp(expression.source, expression.flags);
|
|
812
|
+
const evaluator = new RegularExpression(regex, this.parameterRegistry);
|
|
813
|
+
return (text) => evaluator.match(text) !== null;
|
|
814
|
+
}
|
|
815
|
+
try {
|
|
816
|
+
const cucumberExpression = new CucumberExpression(
|
|
817
|
+
expression,
|
|
818
|
+
this.parameterRegistry
|
|
819
|
+
);
|
|
820
|
+
return (text) => cucumberExpression.match(text) !== null;
|
|
821
|
+
} catch {
|
|
822
|
+
const literal = expression;
|
|
823
|
+
return (text) => text === literal;
|
|
324
824
|
}
|
|
325
825
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
826
|
+
buildMissingStepDefinitionMessage(context, step, definitions) {
|
|
827
|
+
const keyword = (step.keyword ?? "").trim();
|
|
828
|
+
const display = keyword.length > 0 ? `${keyword} ${step.text}` : step.text;
|
|
829
|
+
const lines = [
|
|
830
|
+
"No step definition matched:",
|
|
831
|
+
"",
|
|
832
|
+
`'${display}'`,
|
|
833
|
+
"",
|
|
834
|
+
this.buildMissingStepContextLine(context)
|
|
835
|
+
];
|
|
836
|
+
const suggestions = this.resolveClosestStepDefinitionSuggestions(step, definitions);
|
|
837
|
+
if (suggestions.sameType.length === 0 && suggestions.differentType.length === 0) {
|
|
838
|
+
return lines.join("\n");
|
|
839
|
+
}
|
|
840
|
+
lines.push("", "Some close matches were found:");
|
|
841
|
+
if (suggestions.sameType.length > 0) {
|
|
842
|
+
lines.push(" Close matches with the same step type:");
|
|
843
|
+
for (const suggestion of suggestions.sameType) {
|
|
844
|
+
lines.push(` - ${suggestion}`);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (suggestions.differentType.length > 0) {
|
|
848
|
+
lines.push(" Close matches with different step type:");
|
|
849
|
+
for (const suggestion of suggestions.differentType) {
|
|
850
|
+
lines.push(` - ${suggestion}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return lines.join("\n");
|
|
334
854
|
}
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
if (
|
|
338
|
-
|
|
855
|
+
buildMissingStepContextLine(context) {
|
|
856
|
+
const parts = [`in scenario '${context.scenario}'`];
|
|
857
|
+
if (context.outline) {
|
|
858
|
+
parts.push(`of outline '${context.outline}'`);
|
|
859
|
+
}
|
|
860
|
+
if (context.rule) {
|
|
861
|
+
parts.push(`within rule '${context.rule}'`);
|
|
339
862
|
}
|
|
863
|
+
parts.push(`for feature '${this.feature.name}'.`);
|
|
864
|
+
return parts.join(" ");
|
|
865
|
+
}
|
|
866
|
+
buildUnusedStepDefinitionsMessage(context, extras) {
|
|
867
|
+
const segments = [
|
|
868
|
+
`The following step definitions were not matched to Gherkin steps in scenario '${context.scenario}'`
|
|
869
|
+
];
|
|
870
|
+
if (context.outline) {
|
|
871
|
+
segments.push(`of outline '${context.outline}'`);
|
|
872
|
+
}
|
|
873
|
+
if (context.rule) {
|
|
874
|
+
segments.push(`within rule '${context.rule}'`);
|
|
875
|
+
}
|
|
876
|
+
segments.push(`for feature '${this.feature.name}': ${extras.join(", ")}`);
|
|
877
|
+
return segments.join(" ");
|
|
340
878
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
879
|
+
describeStepDefinition(definition) {
|
|
880
|
+
return `${definition.keyword} ${formatExpression(definition.expression)}`;
|
|
881
|
+
}
|
|
882
|
+
createMissingStepDefinition(summary, context, step, index) {
|
|
883
|
+
const message = this.buildMissingStepDefinitionMessage(
|
|
884
|
+
context,
|
|
885
|
+
step,
|
|
886
|
+
summary.steps
|
|
887
|
+
);
|
|
888
|
+
return {
|
|
889
|
+
id: `${summary.id}:missing-step:${index}`,
|
|
890
|
+
keyword: this.resolveStepKeyword(step),
|
|
891
|
+
expression: step.text,
|
|
892
|
+
handler: () => {
|
|
893
|
+
throw new Error(message);
|
|
894
|
+
},
|
|
895
|
+
options: this.createFallbackStepOptions(summary.scenario.mode)
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
resolveStepKeyword(step) {
|
|
899
|
+
const raw = normalizeKeyword(step.keyword ?? "");
|
|
900
|
+
if (isFlexibleKeyword(raw)) {
|
|
901
|
+
return "And";
|
|
902
|
+
}
|
|
903
|
+
try {
|
|
904
|
+
return normalizeGherkinStepKeyword(raw);
|
|
905
|
+
} catch {
|
|
906
|
+
return "Given";
|
|
369
907
|
}
|
|
370
908
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
909
|
+
createFallbackStepOptions(mode) {
|
|
910
|
+
return {
|
|
911
|
+
tags: [],
|
|
912
|
+
mode
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
resolveClosestStepDefinitionSuggestions(step, definitions) {
|
|
916
|
+
if (definitions.length === 0) {
|
|
917
|
+
return { sameType: [], differentType: [] };
|
|
918
|
+
}
|
|
919
|
+
const desiredKeyword = this.tryNormalizeKeyword(step.keyword);
|
|
920
|
+
const target = this.normalizeForDistance(step.text);
|
|
921
|
+
const candidates = definitions.map((definition) => {
|
|
922
|
+
const candidateText = this.normalizeForDistance(
|
|
923
|
+
formatExpression(definition.expression)
|
|
924
|
+
);
|
|
925
|
+
const distance = this.computeEditDistance(target, candidateText);
|
|
926
|
+
const similarity = this.computeSimilarity(target, candidateText, distance);
|
|
927
|
+
return {
|
|
928
|
+
definition,
|
|
929
|
+
description: this.describeStepDefinition(definition),
|
|
930
|
+
distance,
|
|
931
|
+
similarity
|
|
932
|
+
};
|
|
933
|
+
});
|
|
934
|
+
candidates.sort((a, b) => a.distance - b.distance);
|
|
935
|
+
const sameType = [];
|
|
936
|
+
const differentType = [];
|
|
937
|
+
for (const candidate of candidates) {
|
|
938
|
+
const isSameType = !desiredKeyword || candidate.definition.keyword === desiredKeyword;
|
|
939
|
+
if (isSameType) {
|
|
940
|
+
if (candidate.similarity < _TestPlanBuilder.SUGGESTION_MIN_SIMILARITY_SAME_TYPE) {
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (sameType.length < _TestPlanBuilder.SUGGESTION_LIMIT_PER_GROUP) {
|
|
944
|
+
sameType.push(candidate.description);
|
|
945
|
+
}
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
if (candidate.similarity < _TestPlanBuilder.SUGGESTION_MIN_SIMILARITY_DIFFERENT_TYPE) {
|
|
949
|
+
continue;
|
|
950
|
+
}
|
|
951
|
+
if (differentType.length < _TestPlanBuilder.SUGGESTION_LIMIT_PER_GROUP) {
|
|
952
|
+
differentType.push(candidate.description);
|
|
953
|
+
}
|
|
954
|
+
if (sameType.length >= _TestPlanBuilder.SUGGESTION_LIMIT_PER_GROUP && differentType.length >= _TestPlanBuilder.SUGGESTION_LIMIT_PER_GROUP) {
|
|
955
|
+
break;
|
|
381
956
|
}
|
|
382
957
|
}
|
|
958
|
+
return { sameType, differentType };
|
|
383
959
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
function gherkinToTestNames(node, path = "", accumulator = []) {
|
|
387
|
-
if (!("name" in node) || node instanceof import_gherkin2.Background) {
|
|
388
|
-
return;
|
|
960
|
+
normalizeForDistance(value) {
|
|
961
|
+
return value.toLowerCase().replace(/\s+/g, " ").trim();
|
|
389
962
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
963
|
+
computeSimilarity(a, b, distance) {
|
|
964
|
+
const maxLen = Math.max(a.length, b.length);
|
|
965
|
+
if (maxLen === 0) {
|
|
966
|
+
return 1;
|
|
967
|
+
}
|
|
968
|
+
const normalized = 1 - distance / maxLen;
|
|
969
|
+
return Math.max(0, Math.min(1, normalized));
|
|
395
970
|
}
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
971
|
+
computeEditDistance(a, b) {
|
|
972
|
+
if (a === b) {
|
|
973
|
+
return 0;
|
|
974
|
+
}
|
|
975
|
+
const rows = a.length + 1;
|
|
976
|
+
const cols = b.length + 1;
|
|
977
|
+
const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
|
|
978
|
+
for (let i = 0; i < rows; i++) {
|
|
979
|
+
const row = matrix[i];
|
|
980
|
+
if (!row) {
|
|
981
|
+
throw new Error("Internal error: matrix row missing");
|
|
982
|
+
}
|
|
983
|
+
row[0] = i;
|
|
984
|
+
}
|
|
985
|
+
const row0 = matrix[0];
|
|
986
|
+
if (!row0) {
|
|
987
|
+
throw new Error("Internal error: matrix[0] missing");
|
|
988
|
+
}
|
|
989
|
+
for (let j = 0; j < cols; j++) {
|
|
990
|
+
row0[j] = j;
|
|
991
|
+
}
|
|
992
|
+
for (let i = 1; i < rows; i++) {
|
|
993
|
+
const row = matrix[i];
|
|
994
|
+
const prevRow = matrix[i - 1];
|
|
995
|
+
if (!row || !prevRow) {
|
|
996
|
+
throw new Error("Internal error: matrix row missing");
|
|
997
|
+
}
|
|
998
|
+
for (let j = 1; j < cols; j++) {
|
|
999
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1000
|
+
const deletion = (prevRow[j] ?? 0) + 1;
|
|
1001
|
+
const insertion = (row[j - 1] ?? 0) + 1;
|
|
1002
|
+
const substitution = (prevRow[j - 1] ?? 0) + cost;
|
|
1003
|
+
row[j] = Math.min(deletion, insertion, substitution);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const lastRow = matrix[rows - 1];
|
|
1007
|
+
if (!lastRow) {
|
|
1008
|
+
throw new Error("Internal error: last matrix row missing");
|
|
1009
|
+
}
|
|
1010
|
+
const result = lastRow[cols - 1];
|
|
1011
|
+
if (result === void 0) {
|
|
1012
|
+
throw new Error("Internal error: matrix result missing");
|
|
399
1013
|
}
|
|
400
|
-
|
|
1014
|
+
return result;
|
|
401
1015
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
var import_gherkin4 = require("@autometa/gherkin");
|
|
415
|
-
var _walkNode, walkNode_fn, _walkChildren, walkChildren_fn;
|
|
416
|
-
var GherkinWalker = class {
|
|
417
|
-
static walk(walkFunction, childNode, accumulator, parentNode) {
|
|
418
|
-
if (accumulator === void 0) {
|
|
419
|
-
throw new import_errors2.AutomationError(
|
|
420
|
-
`An accumulator must be defined to continue the walker from ${childNode.constructor.name}${JSON.stringify(childNode)}`
|
|
421
|
-
);
|
|
1016
|
+
tryNormalizeKeyword(keyword) {
|
|
1017
|
+
if (!keyword) {
|
|
1018
|
+
return void 0;
|
|
1019
|
+
}
|
|
1020
|
+
const raw = normalizeKeyword(keyword);
|
|
1021
|
+
if (isFlexibleKeyword(raw)) {
|
|
1022
|
+
return void 0;
|
|
1023
|
+
}
|
|
1024
|
+
try {
|
|
1025
|
+
return normalizeGherkinStepKeyword(raw);
|
|
1026
|
+
} catch {
|
|
1027
|
+
return void 0;
|
|
422
1028
|
}
|
|
423
|
-
__privateMethod(this, _walkNode, walkNode_fn).call(this, childNode, accumulator, walkFunction, parentNode);
|
|
424
|
-
return accumulator;
|
|
425
1029
|
}
|
|
426
1030
|
};
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return
|
|
444
|
-
}
|
|
445
|
-
if (child instanceof import_gherkin3.Scenario) {
|
|
446
|
-
const acc = walkFunction.onScenario?.(child, accumulator, lastNode);
|
|
447
|
-
return __privateMethod(this, _walkChildren, walkChildren_fn).call(this, child, acc, walkFunction);
|
|
448
|
-
}
|
|
449
|
-
if (child instanceof import_gherkin3.ScenarioOutline) {
|
|
450
|
-
const acc = walkFunction.onScenarioOutline?.(
|
|
451
|
-
child,
|
|
452
|
-
accumulator,
|
|
453
|
-
lastNode
|
|
454
|
-
);
|
|
455
|
-
return __privateMethod(this, _walkChildren, walkChildren_fn).call(this, child, acc, walkFunction);
|
|
1031
|
+
_TestPlanBuilder.SUGGESTION_LIMIT_PER_GROUP = 3;
|
|
1032
|
+
/**
|
|
1033
|
+
* Similarity threshold in [0, 1].
|
|
1034
|
+
* 1.0 = identical, 0.0 = completely different.
|
|
1035
|
+
*/
|
|
1036
|
+
_TestPlanBuilder.SUGGESTION_MIN_SIMILARITY_SAME_TYPE = 0.6;
|
|
1037
|
+
_TestPlanBuilder.SUGGESTION_MIN_SIMILARITY_DIFFERENT_TYPE = 0.85;
|
|
1038
|
+
var TestPlanBuilder = _TestPlanBuilder;
|
|
1039
|
+
var TestPlanImpl = class {
|
|
1040
|
+
constructor(feature, executions, byId, byQualifiedName) {
|
|
1041
|
+
this.feature = feature;
|
|
1042
|
+
this.executions = executions;
|
|
1043
|
+
this.byId = byId;
|
|
1044
|
+
this.byQualifiedName = byQualifiedName;
|
|
1045
|
+
}
|
|
1046
|
+
listExecutables() {
|
|
1047
|
+
return this.executions;
|
|
456
1048
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
return __privateMethod(this, _walkChildren, walkChildren_fn).call(this, child, acc, walkFunction);
|
|
1049
|
+
listFailed() {
|
|
1050
|
+
return this.executions.filter((execution) => execution.result.status === "failed");
|
|
460
1051
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
1052
|
+
findById(id) {
|
|
1053
|
+
return this.byId.get(id);
|
|
1054
|
+
}
|
|
1055
|
+
findByQualifiedName(name) {
|
|
1056
|
+
return this.byQualifiedName.get(name);
|
|
464
1057
|
}
|
|
465
1058
|
};
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
1059
|
+
function resolveParameterRegistry(parameterRegistry) {
|
|
1060
|
+
if (!parameterRegistry) {
|
|
1061
|
+
return new ParameterTypeRegistry();
|
|
1062
|
+
}
|
|
1063
|
+
if (isParameterTypeRegistry(parameterRegistry)) {
|
|
1064
|
+
return parameterRegistry;
|
|
1065
|
+
}
|
|
1066
|
+
const candidate = parameterRegistry.registry;
|
|
1067
|
+
if (isParameterTypeRegistry(candidate)) {
|
|
1068
|
+
return candidate;
|
|
1069
|
+
}
|
|
1070
|
+
return new ParameterTypeRegistry();
|
|
1071
|
+
}
|
|
1072
|
+
function isParameterTypeRegistry(value) {
|
|
1073
|
+
if (!value || typeof value !== "object") {
|
|
1074
|
+
return false;
|
|
470
1075
|
}
|
|
1076
|
+
if (value instanceof ParameterTypeRegistry) {
|
|
1077
|
+
return true;
|
|
1078
|
+
}
|
|
1079
|
+
const registry = value;
|
|
1080
|
+
return typeof registry.lookupByTypeName === "function" && typeof registry.defineParameterType === "function";
|
|
1081
|
+
}
|
|
1082
|
+
function normalizeGherkinStepKeyword(keyword) {
|
|
1083
|
+
const trimmed = normalizeKeyword(keyword).replace(/:$/, "");
|
|
1084
|
+
const mapped = STEP_KEYWORD_MAP[trimmed.toLowerCase()];
|
|
1085
|
+
if (!mapped) {
|
|
1086
|
+
throw new Error(`Unsupported Gherkin step keyword '${keyword}'`);
|
|
1087
|
+
}
|
|
1088
|
+
return mapped;
|
|
1089
|
+
}
|
|
1090
|
+
function isFlexibleKeyword(keyword) {
|
|
1091
|
+
const normalized = normalizeKeyword(keyword).replace(/:$/, "").toLowerCase();
|
|
1092
|
+
return FLEXIBLE_KEYWORDS.has(normalized);
|
|
1093
|
+
}
|
|
1094
|
+
function formatExpression(expression) {
|
|
1095
|
+
return typeof expression === "string" ? expression : expression.toString();
|
|
1096
|
+
}
|
|
1097
|
+
var STEP_KEYWORD_MAP = {
|
|
1098
|
+
given: "Given",
|
|
1099
|
+
when: "When",
|
|
1100
|
+
then: "Then",
|
|
1101
|
+
and: "And",
|
|
1102
|
+
but: "But"
|
|
471
1103
|
};
|
|
472
|
-
|
|
473
|
-
__privateAdd(GherkinWalker, _walkChildren);
|
|
1104
|
+
var FLEXIBLE_KEYWORDS = /* @__PURE__ */ new Set(["and", "but", "*"]);
|
|
474
1105
|
|
|
475
|
-
// src/
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const found = value.closedScopes.find((child) => {
|
|
483
|
-
return child instanceof import_scopes.RuleScope && child.name === name;
|
|
484
|
-
});
|
|
485
|
-
if (!found) {
|
|
486
|
-
const rule = new import_scopes.RuleScope(
|
|
487
|
-
name,
|
|
488
|
-
import_scopes2.Empty_Function,
|
|
489
|
-
void 0,
|
|
490
|
-
value.hooks,
|
|
491
|
-
value.steps
|
|
492
|
-
);
|
|
493
|
-
value.attach(rule);
|
|
494
|
-
return rule;
|
|
495
|
-
}
|
|
496
|
-
return found;
|
|
497
|
-
},
|
|
498
|
-
findScenario: (name) => {
|
|
499
|
-
const found = value.closedScopes.find((child) => {
|
|
500
|
-
return child instanceof import_scopes.ScenarioScope && child.name === name && !(child instanceof import_scopes.ScenarioOutlineScope);
|
|
501
|
-
});
|
|
502
|
-
if (!found) {
|
|
503
|
-
const scenario = new import_scopes.ScenarioScope(
|
|
504
|
-
name,
|
|
505
|
-
import_scopes2.Empty_Function,
|
|
506
|
-
void 0,
|
|
507
|
-
value.hooks,
|
|
508
|
-
value.steps
|
|
509
|
-
);
|
|
510
|
-
value.attach(scenario);
|
|
511
|
-
return scenario;
|
|
512
|
-
}
|
|
513
|
-
return found;
|
|
514
|
-
},
|
|
515
|
-
findBackground: ({ name }) => {
|
|
516
|
-
const found = value.closedScopes.find((child) => {
|
|
517
|
-
return child instanceof import_scopes.BackgroundScope;
|
|
518
|
-
});
|
|
519
|
-
if (found && found.name !== name) {
|
|
520
|
-
throw new import_errors3.AutomationError(
|
|
521
|
-
`Could not find background matching ${name} but found ${found?.name}`
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
if (found) {
|
|
525
|
-
return found;
|
|
526
|
-
}
|
|
527
|
-
const bgScope = new import_scopes.BackgroundScope(
|
|
528
|
-
name,
|
|
529
|
-
import_scopes2.Empty_Function,
|
|
530
|
-
value.hooks,
|
|
531
|
-
value.steps
|
|
532
|
-
);
|
|
533
|
-
value.attach(bgScope);
|
|
534
|
-
return bgScope;
|
|
535
|
-
},
|
|
536
|
-
findScenarioOutline: (name) => {
|
|
537
|
-
const found = value.closedScopes.find((child) => {
|
|
538
|
-
return child instanceof import_scopes.ScenarioOutlineScope && child.name === name;
|
|
539
|
-
});
|
|
540
|
-
if (!found) {
|
|
541
|
-
const scenarioOutline = new import_scopes.ScenarioOutlineScope(
|
|
542
|
-
name,
|
|
543
|
-
import_scopes2.Empty_Function,
|
|
544
|
-
void 0,
|
|
545
|
-
value.hooks,
|
|
546
|
-
value.steps
|
|
547
|
-
);
|
|
548
|
-
value.attach(scenarioOutline);
|
|
549
|
-
return scenarioOutline;
|
|
550
|
-
}
|
|
551
|
-
return found;
|
|
552
|
-
},
|
|
553
|
-
findExample(name) {
|
|
554
|
-
const found = value.closedScopes.find((child) => {
|
|
555
|
-
if (!(child instanceof import_scopes.ScenarioScope)) {
|
|
556
|
-
return false;
|
|
557
|
-
}
|
|
558
|
-
return child.name === name;
|
|
559
|
-
});
|
|
560
|
-
if (!found) {
|
|
561
|
-
const scenario = new import_scopes.ScenarioScope(
|
|
562
|
-
name,
|
|
563
|
-
import_scopes2.Empty_Function,
|
|
564
|
-
void 0,
|
|
565
|
-
value.hooks,
|
|
566
|
-
value.steps
|
|
567
|
-
);
|
|
568
|
-
value.attach(scenario);
|
|
569
|
-
return scenario;
|
|
570
|
-
}
|
|
571
|
-
return found;
|
|
572
|
-
},
|
|
573
|
-
findStep: (keywordType, keyword, name) => {
|
|
574
|
-
return value.steps.find(keywordType, keyword, name);
|
|
575
|
-
}
|
|
576
|
-
};
|
|
1106
|
+
// src/build-test-plan.ts
|
|
1107
|
+
function buildTestPlan(options) {
|
|
1108
|
+
const { feature, adapter } = options;
|
|
1109
|
+
assertFeature(feature);
|
|
1110
|
+
assertAdapter(adapter);
|
|
1111
|
+
const featureScope = options.featureScope ?? resolveFeatureScope(adapter, feature);
|
|
1112
|
+
return new TestPlanBuilder(feature, featureScope, adapter).build();
|
|
577
1113
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
var import_errors4 = require("@autometa/errors");
|
|
582
|
-
var TestBuilder = class {
|
|
583
|
-
constructor(feature) {
|
|
584
|
-
this.feature = feature;
|
|
1114
|
+
function assertFeature(feature) {
|
|
1115
|
+
if (!feature) {
|
|
1116
|
+
throw new Error("A Gherkin feature is required to build a test plan");
|
|
585
1117
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
onFeature: (feature, accumulator) => {
|
|
591
|
-
accumulator.data = { gherkin: feature, scope: featureScope };
|
|
592
|
-
return accumulator;
|
|
593
|
-
},
|
|
594
|
-
onRule: (rule, accumulator) => {
|
|
595
|
-
const ruleScope = scope(featureScope).findRule(
|
|
596
|
-
rule.name
|
|
597
|
-
);
|
|
598
|
-
const bridge2 = new RuleBridge();
|
|
599
|
-
bridge2.data = { gherkin: rule, scope: ruleScope };
|
|
600
|
-
accumulator.rules.push(bridge2);
|
|
601
|
-
return bridge2;
|
|
602
|
-
},
|
|
603
|
-
onScenario: (gherkin, accumulator) => {
|
|
604
|
-
const scenarioScope = scope(accumulator.data.scope).findScenario(
|
|
605
|
-
gherkin.name
|
|
606
|
-
);
|
|
607
|
-
const bridge2 = new ScenarioBridge();
|
|
608
|
-
bridge2.data = { gherkin, scope: scenarioScope };
|
|
609
|
-
accumulator.scenarios.push(bridge2);
|
|
610
|
-
return bridge2;
|
|
611
|
-
},
|
|
612
|
-
onScenarioOutline: (gherkin, accumulator) => {
|
|
613
|
-
const outlineScope = scope(
|
|
614
|
-
accumulator.data.scope
|
|
615
|
-
).findScenarioOutline(gherkin.name);
|
|
616
|
-
const bridge2 = new ScenarioOutlineBridge();
|
|
617
|
-
bridge2.data = { gherkin, scope: outlineScope };
|
|
618
|
-
accumulator.scenarios.push(bridge2);
|
|
619
|
-
return bridge2;
|
|
620
|
-
},
|
|
621
|
-
onExamples: (gherkin, accumulator) => {
|
|
622
|
-
const outlineScope = accumulator.data.scope;
|
|
623
|
-
const bridge2 = new ExamplesBridge();
|
|
624
|
-
bridge2.data = { gherkin, scope: outlineScope };
|
|
625
|
-
accumulator.examples.push(bridge2);
|
|
626
|
-
return bridge2;
|
|
627
|
-
},
|
|
628
|
-
onExample(gherkin, accumulator) {
|
|
629
|
-
if (gherkin.table === void 0) {
|
|
630
|
-
(0, import_errors4.raise)(
|
|
631
|
-
`Example ${gherkin.name} has no Example Table data. A Row of data is required.`
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
const title = gherkin.name;
|
|
635
|
-
const exampleScope = scope(accumulator.data.scope).findExample(title);
|
|
636
|
-
const bridge2 = new ExampleBridge();
|
|
637
|
-
bridge2.data = { gherkin, scope: exampleScope };
|
|
638
|
-
const acc = accumulator;
|
|
639
|
-
acc.scenarios.push(bridge2);
|
|
640
|
-
return bridge2;
|
|
641
|
-
},
|
|
642
|
-
onBackground(gherkin, accumulator) {
|
|
643
|
-
const backgroundScope = scope(accumulator.data.scope).findBackground({
|
|
644
|
-
name: gherkin.name
|
|
645
|
-
});
|
|
646
|
-
const bridge2 = new BackgroundBridge();
|
|
647
|
-
bridge2.data = { gherkin, scope: backgroundScope };
|
|
648
|
-
const acc = accumulator;
|
|
649
|
-
acc.background = bridge2;
|
|
650
|
-
return bridge2;
|
|
651
|
-
},
|
|
652
|
-
onStep: (step, accumulator) => {
|
|
653
|
-
const {
|
|
654
|
-
data: { scope: parentScope, gherkin }
|
|
655
|
-
} = accumulator;
|
|
656
|
-
const { keyword, keywordType, text } = step;
|
|
657
|
-
const cache = parentScope.steps;
|
|
658
|
-
const existing = getStep(
|
|
659
|
-
accumulator,
|
|
660
|
-
gherkin,
|
|
661
|
-
cache,
|
|
662
|
-
keywordType,
|
|
663
|
-
keyword,
|
|
664
|
-
text
|
|
665
|
-
);
|
|
666
|
-
const bridge2 = new StepBridge();
|
|
667
|
-
const acc = accumulator;
|
|
668
|
-
if (existing) {
|
|
669
|
-
bridge2.data = {
|
|
670
|
-
gherkin: step,
|
|
671
|
-
scope: existing.step,
|
|
672
|
-
args: existing.args
|
|
673
|
-
};
|
|
674
|
-
} else {
|
|
675
|
-
(0, import_errors4.raise)(`No step definition matching ${step.keyword} ${step.text}`);
|
|
676
|
-
}
|
|
677
|
-
acc.steps.push(bridge2);
|
|
678
|
-
return accumulator;
|
|
679
|
-
}
|
|
680
|
-
},
|
|
681
|
-
this.feature,
|
|
682
|
-
bridge
|
|
683
|
-
);
|
|
684
|
-
return bridge;
|
|
1118
|
+
}
|
|
1119
|
+
function assertAdapter(adapter) {
|
|
1120
|
+
if (!adapter) {
|
|
1121
|
+
throw new Error("A scope execution adapter is required to build a test plan");
|
|
685
1122
|
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
function getStep(accumulator, gherkin, cache, keywordType, keyword, text) {
|
|
691
|
-
try {
|
|
692
|
-
if (accumulator instanceof ExampleBridge) {
|
|
693
|
-
const scenario = gherkin;
|
|
694
|
-
if (scenario.table) {
|
|
695
|
-
return cache.findByExample(keywordType, keyword, text, scenario.table);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
return cache.find(keywordType, keyword, text);
|
|
699
|
-
} catch (e) {
|
|
700
|
-
const cause = e;
|
|
701
|
-
const { title } = gherkin;
|
|
702
|
-
(0, import_errors4.raise)(`'${title}' could not find a step definition`, { cause });
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
706
|
-
0 && (module.exports = {
|
|
707
|
-
BackgroundBridge,
|
|
708
|
-
ExampleBridge,
|
|
709
|
-
ExamplesBridge,
|
|
710
|
-
FeatureBridge,
|
|
711
|
-
GherkinCodeBridge,
|
|
712
|
-
GherkinWalker,
|
|
713
|
-
GlobalBridge,
|
|
714
|
-
Query,
|
|
715
|
-
RuleBridge,
|
|
716
|
-
ScenarioBridge,
|
|
717
|
-
ScenarioOutlineBridge,
|
|
718
|
-
StepBridge,
|
|
719
|
-
TestBuilder,
|
|
720
|
-
find,
|
|
721
|
-
findExamplesOrChild,
|
|
722
|
-
findRuleOrChild,
|
|
723
|
-
findRuleTypes,
|
|
724
|
-
findScenario,
|
|
725
|
-
findScenarioOutlineOrChild,
|
|
726
|
-
findTestTypes,
|
|
727
|
-
scope
|
|
728
|
-
});
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
export { buildTestPlan };
|
|
1126
|
+
//# sourceMappingURL=out.js.map
|
|
729
1127
|
//# sourceMappingURL=index.js.map
|