@fromeroc9/testform 1.0.3 → 1.0.4
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/action/index.js +1 -1
- package/dist/action.js +60 -0
- package/dist/adapters/github.js +467 -0
- package/dist/adapters/resources.js +363 -0
- package/dist/cli/index.js +3 -3
- package/dist/commands/apply.js +390 -0
- package/dist/commands/destroy.js +85 -0
- package/dist/commands/diff.js +131 -0
- package/dist/commands/fmt.js +166 -0
- package/dist/commands/force-unlock.js +55 -0
- package/dist/commands/generate.js +143 -0
- package/dist/commands/graph.js +159 -0
- package/dist/commands/import.js +222 -0
- package/dist/commands/init.js +167 -0
- package/dist/commands/login.js +71 -0
- package/dist/commands/logout.js +20 -0
- package/dist/commands/plan.js +250 -0
- package/dist/commands/refresh.js +165 -0
- package/dist/commands/report.js +724 -0
- package/dist/commands/show.js +61 -0
- package/dist/commands/state.js +197 -0
- package/dist/commands/taint.js +49 -0
- package/dist/commands/validate.js +128 -0
- package/dist/commands/workspace.js +102 -0
- package/dist/const.js +105 -0
- package/dist/core/backends/azurerm.js +201 -0
- package/dist/core/backends/backend.js +2 -0
- package/dist/core/backends/gcs.js +200 -0
- package/dist/core/backends/local.js +162 -0
- package/dist/core/backends/s3.js +224 -0
- package/dist/core/command-context.js +59 -0
- package/dist/core/config.js +131 -0
- package/dist/core/credentials.js +53 -0
- package/dist/core/parser.js +62 -0
- package/dist/core/parsers/base-parser.js +215 -0
- package/dist/core/parsers/testcase-parser.js +115 -0
- package/dist/core/parsers/testplan-parser.js +41 -0
- package/dist/core/parsers/testrun-parser.js +43 -0
- package/dist/core/policy.js +341 -0
- package/dist/core/prompt.js +109 -0
- package/dist/core/state.js +185 -0
- package/dist/core/utils.js +94 -0
- package/dist/core/variables.js +108 -0
- package/dist/core/workspace.js +56 -0
- package/dist/help.js +797 -0
- package/dist/index.js +650 -0
- package/dist/logger.js +134 -0
- package/dist/notify.js +36 -0
- package/dist/types.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Base Gherkin parser.
|
|
4
|
+
*
|
|
5
|
+
* Handles file reading, tokenization, AST building, and base DSL field extraction.
|
|
6
|
+
* Specialized parsers (testcase, testrun, testplan) extend this class to implement
|
|
7
|
+
* their specific filtering and data enrichment logic.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.BaseParser = exports.FIELD_PATTERN = void 0;
|
|
11
|
+
const gherkin_1 = require("@cucumber/gherkin");
|
|
12
|
+
const messages_1 = require("@cucumber/messages");
|
|
13
|
+
const fs_1 = require("fs");
|
|
14
|
+
const path_1 = require("path");
|
|
15
|
+
exports.FIELD_PATTERN = /^(?:field)\s+([A-Za-z0-9_.\- ]+?)\s*=\s*(.+)$/i;
|
|
16
|
+
class BaseParser {
|
|
17
|
+
dir;
|
|
18
|
+
variables;
|
|
19
|
+
constructor(dir, variables) {
|
|
20
|
+
this.dir = dir;
|
|
21
|
+
this.variables = variables;
|
|
22
|
+
}
|
|
23
|
+
find(targetPath) {
|
|
24
|
+
try {
|
|
25
|
+
const stat = (0, fs_1.statSync)(targetPath);
|
|
26
|
+
if (stat.isFile() && targetPath.endsWith('.feature')) {
|
|
27
|
+
return [targetPath];
|
|
28
|
+
}
|
|
29
|
+
if (stat.isDirectory()) {
|
|
30
|
+
const entries = (0, fs_1.readdirSync)(targetPath, { withFileTypes: true });
|
|
31
|
+
const result = [];
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const filePath = (0, path_1.join)(targetPath, entry.name);
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
result.push(...this.find(filePath));
|
|
36
|
+
}
|
|
37
|
+
else if (entry.name.endsWith('.feature')) {
|
|
38
|
+
result.push(filePath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
format(document) {
|
|
50
|
+
const feature = document.feature;
|
|
51
|
+
const uri = document.uri ?? "(unknown)";
|
|
52
|
+
const toStep = (s) => ({
|
|
53
|
+
keyword: s.keyword,
|
|
54
|
+
keywordType: s.keywordType,
|
|
55
|
+
text: s.text,
|
|
56
|
+
});
|
|
57
|
+
const toBackground = (bg) => ({
|
|
58
|
+
keyword: bg.keyword,
|
|
59
|
+
name: bg.name,
|
|
60
|
+
steps: (bg.steps ?? []).map(toStep),
|
|
61
|
+
});
|
|
62
|
+
const interpolate = (text, headers, values) => headers.reduce((acc, h, i) => acc.split(`<${h}>`).join(values[i]), text);
|
|
63
|
+
const processChildren = (children, parentBackground, rule) => {
|
|
64
|
+
const result = [];
|
|
65
|
+
const localBg = children[0]?.background ? toBackground(children[0].background) : parentBackground;
|
|
66
|
+
for (const child of children) {
|
|
67
|
+
if (child.background)
|
|
68
|
+
continue;
|
|
69
|
+
if (child.scenario) {
|
|
70
|
+
const s = child.scenario;
|
|
71
|
+
const featureTags = (feature?.tags ?? []).map((t) => t.name);
|
|
72
|
+
const tags = [...featureTags, ...(s.tags ?? []).map((t) => t.name)];
|
|
73
|
+
const isUnique = tags.includes("@unique");
|
|
74
|
+
const hasTemplate = (s.name).includes("<") || (s.steps ?? []).some((st) => (st.text).includes("<")) || tags.some((t) => t.includes("<"));
|
|
75
|
+
const featureObj = {
|
|
76
|
+
tags: (feature?.tags ?? []).map((t) => t.name),
|
|
77
|
+
keyword: feature?.keyword ?? "",
|
|
78
|
+
name: feature?.name ?? "",
|
|
79
|
+
description: feature?.description ?? "",
|
|
80
|
+
location: feature?.location.line ?? 0
|
|
81
|
+
};
|
|
82
|
+
if (isUnique && hasTemplate && (s.examples ?? []).length > 0) {
|
|
83
|
+
for (const example of s.examples) {
|
|
84
|
+
const headers = (example.tableHeader?.cells ?? []).map((c) => c.value);
|
|
85
|
+
for (const row of example.tableBody ?? []) {
|
|
86
|
+
const values = (row.cells ?? []).map((c) => c.value);
|
|
87
|
+
result.push({
|
|
88
|
+
uri,
|
|
89
|
+
feature: featureObj,
|
|
90
|
+
location: s.location.line,
|
|
91
|
+
keyword: s.keyword,
|
|
92
|
+
name: interpolate(s.name, headers, values),
|
|
93
|
+
description: s.description,
|
|
94
|
+
tags: tags.map((t) => interpolate(t, headers, values)),
|
|
95
|
+
steps: (s.steps ?? []).map((step) => ({
|
|
96
|
+
keyword: step.keyword,
|
|
97
|
+
keywordType: step.keywordType,
|
|
98
|
+
text: interpolate(step.text, headers, values),
|
|
99
|
+
})),
|
|
100
|
+
...(localBg ? { background: JSON.parse(JSON.stringify(localBg)) } : {}),
|
|
101
|
+
...(rule ? { rule } : {}),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
result.push({
|
|
108
|
+
uri,
|
|
109
|
+
feature: featureObj,
|
|
110
|
+
location: s.location.line,
|
|
111
|
+
keyword: s.keyword,
|
|
112
|
+
name: s.name,
|
|
113
|
+
description: s.description,
|
|
114
|
+
tags,
|
|
115
|
+
steps: (s.steps ?? []).map(toStep),
|
|
116
|
+
...(localBg ? { background: JSON.parse(JSON.stringify(localBg)) } : {}),
|
|
117
|
+
...(rule ? { rule } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if ("rule" in child && child.rule) {
|
|
122
|
+
const r = child.rule;
|
|
123
|
+
const ruleObj = {
|
|
124
|
+
keyword: r.keyword,
|
|
125
|
+
name: r.name,
|
|
126
|
+
description: r.description ?? '',
|
|
127
|
+
};
|
|
128
|
+
const ruleScenarios = processChildren(r.children ?? [], localBg, ruleObj);
|
|
129
|
+
if (ruleScenarios.length === 0) {
|
|
130
|
+
result.push({
|
|
131
|
+
uri,
|
|
132
|
+
feature: {
|
|
133
|
+
keyword: feature?.keyword ?? "",
|
|
134
|
+
name: feature?.name ?? "",
|
|
135
|
+
description: feature?.description ?? "",
|
|
136
|
+
tags: (feature?.tags ?? []).map((t) => t.name),
|
|
137
|
+
location: feature?.location?.line ?? 0
|
|
138
|
+
},
|
|
139
|
+
location: r.location.line,
|
|
140
|
+
keyword: r.keyword,
|
|
141
|
+
name: '*',
|
|
142
|
+
description: r.description,
|
|
143
|
+
tags: [...(feature?.tags ?? []).map((t) => t.name)],
|
|
144
|
+
steps: [],
|
|
145
|
+
...(localBg ? { background: JSON.parse(JSON.stringify(localBg)) } : {}),
|
|
146
|
+
rule: ruleObj
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
result.push(...ruleScenarios);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
};
|
|
156
|
+
if (!feature)
|
|
157
|
+
return [];
|
|
158
|
+
return processChildren(feature.children ?? []);
|
|
159
|
+
}
|
|
160
|
+
content() {
|
|
161
|
+
const featureFiles = this.find(this.dir);
|
|
162
|
+
const scenarios = [];
|
|
163
|
+
for (const file of featureFiles) {
|
|
164
|
+
let content = (0, fs_1.readFileSync)(file, "utf-8");
|
|
165
|
+
if (this.variables) {
|
|
166
|
+
content = this.variables.applyToTemplate(content);
|
|
167
|
+
}
|
|
168
|
+
const parser = new gherkin_1.Parser(new gherkin_1.AstBuilder(messages_1.IdGenerator.uuid()), new gherkin_1.GherkinClassicTokenMatcher());
|
|
169
|
+
const rawDoc = parser.parse(content);
|
|
170
|
+
rawDoc.uri = (0, path_1.relative)(this.dir, file);
|
|
171
|
+
scenarios.push(...this.format(rawDoc));
|
|
172
|
+
}
|
|
173
|
+
return scenarios;
|
|
174
|
+
}
|
|
175
|
+
dslFields(fields) {
|
|
176
|
+
const keywordFields = new Map();
|
|
177
|
+
const tagFields = new Map();
|
|
178
|
+
for (const field of fields) {
|
|
179
|
+
if (field.type === "keywords") {
|
|
180
|
+
keywordFields.set(field.name.toLowerCase(), field);
|
|
181
|
+
}
|
|
182
|
+
if (field.type === "tags") {
|
|
183
|
+
tagFields.set(field.name.toLowerCase(), field);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { keywordFields, tagFields };
|
|
187
|
+
}
|
|
188
|
+
compileIdentityMatcher(pattern) {
|
|
189
|
+
if (!pattern)
|
|
190
|
+
return null;
|
|
191
|
+
const normalized = pattern.trim().replace(/^@/, "");
|
|
192
|
+
if (normalized.includes("*")) {
|
|
193
|
+
const parts = normalized.split('*');
|
|
194
|
+
return { type: 'wildcard', prefix: parts[0], suffix: parts[1] || '' };
|
|
195
|
+
}
|
|
196
|
+
return { type: 'exact', value: normalized };
|
|
197
|
+
}
|
|
198
|
+
matchIdentity(tags, matcher) {
|
|
199
|
+
if (matcher.type === 'wildcard') {
|
|
200
|
+
for (const tag of tags) {
|
|
201
|
+
const normalizedTag = tag.replace(/^@/, "");
|
|
202
|
+
if (normalizedTag.startsWith(matcher.prefix) && normalizedTag.endsWith(matcher.suffix)) {
|
|
203
|
+
return { identityValue: tag, tagToRemove: tag };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
const matched = tags.find((tag) => tag.replace(/^@/, "") === matcher.value);
|
|
209
|
+
if (matched) {
|
|
210
|
+
return { identityValue: matched, tagToRemove: matched };
|
|
211
|
+
}
|
|
212
|
+
return {};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.BaseParser = BaseParser;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Testcase parser.
|
|
4
|
+
*
|
|
5
|
+
* Specializes in extracting individual testcases from feature files.
|
|
6
|
+
* Handles DSL field extraction from tags and steps, and removes consumed elements.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.TestcaseParser = void 0;
|
|
10
|
+
const base_parser_1 = require("./base-parser");
|
|
11
|
+
class TestcaseParser extends base_parser_1.BaseParser {
|
|
12
|
+
filter(scenarios, test, scope) {
|
|
13
|
+
const identityPattern = test.identity;
|
|
14
|
+
const fields = test.fields || [];
|
|
15
|
+
const { keywordFields, tagFields } = this.dslFields(fields);
|
|
16
|
+
const identityMatcher = this.compileIdentityMatcher(identityPattern);
|
|
17
|
+
for (const s of scenarios) {
|
|
18
|
+
const customFields = {};
|
|
19
|
+
if (scope) {
|
|
20
|
+
const scopeTag = `@${scope}`;
|
|
21
|
+
if (!s.tags)
|
|
22
|
+
s.tags = [];
|
|
23
|
+
if (!s.tags.includes(scopeTag)) {
|
|
24
|
+
s.tags.push(scopeTag);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const { identityValue, tagToRemove } = identityMatcher
|
|
28
|
+
? this.matchIdentity(s.tags, identityMatcher)
|
|
29
|
+
: {};
|
|
30
|
+
s.tags = s.tags.filter((tag) => tag !== "@unique");
|
|
31
|
+
if (tagToRemove) {
|
|
32
|
+
s.tags = s.tags.filter((tag) => tag !== tagToRemove);
|
|
33
|
+
}
|
|
34
|
+
if (s.background) {
|
|
35
|
+
s.background.steps = s.background.steps.filter((step) => {
|
|
36
|
+
const match = step.text.match(base_parser_1.FIELD_PATTERN);
|
|
37
|
+
if (!match)
|
|
38
|
+
return true;
|
|
39
|
+
const rawName = match[1]?.trim().toLowerCase();
|
|
40
|
+
const rawValue = match[2]?.trim();
|
|
41
|
+
if (!rawName || rawValue === undefined)
|
|
42
|
+
return true;
|
|
43
|
+
const fieldDef = keywordFields.get(rawName);
|
|
44
|
+
if (!fieldDef) {
|
|
45
|
+
(s.custom ??= {}).policy ??= [];
|
|
46
|
+
s.custom.policy.push({ type: 'undeclared-field', field: rawName });
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
customFields[fieldDef.name] = rawValue;
|
|
50
|
+
return false;
|
|
51
|
+
});
|
|
52
|
+
s.steps = [...s.background.steps, ...s.steps];
|
|
53
|
+
s.background.steps = [];
|
|
54
|
+
}
|
|
55
|
+
s.steps = s.steps.filter((step) => {
|
|
56
|
+
const match = step.text.match(base_parser_1.FIELD_PATTERN);
|
|
57
|
+
if (!match)
|
|
58
|
+
return true;
|
|
59
|
+
const rawName = match[1]?.trim().toLowerCase();
|
|
60
|
+
const rawValue = match[2]?.trim();
|
|
61
|
+
if (!rawName || rawValue === undefined)
|
|
62
|
+
return true;
|
|
63
|
+
const fieldDef = keywordFields.get(rawName);
|
|
64
|
+
if (!fieldDef) {
|
|
65
|
+
(s.custom ??= {}).policy ??= [];
|
|
66
|
+
s.custom.policy.push({ type: 'undeclared-field', field: rawName });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
customFields[fieldDef.name] = rawValue;
|
|
70
|
+
return false;
|
|
71
|
+
});
|
|
72
|
+
const tagFilter = (tag) => {
|
|
73
|
+
for (const fieldDef of tagFields.values()) {
|
|
74
|
+
const values = Array.isArray(fieldDef.values)
|
|
75
|
+
? fieldDef.values
|
|
76
|
+
: typeof fieldDef.values === "string"
|
|
77
|
+
? [fieldDef.values]
|
|
78
|
+
: [];
|
|
79
|
+
if (values.includes(tag)) {
|
|
80
|
+
customFields[fieldDef.name] = tag;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
s.tags = s.tags.filter(tagFilter);
|
|
87
|
+
if (s.feature && s.feature.tags) {
|
|
88
|
+
s.feature.tags = s.feature.tags.filter(tagFilter);
|
|
89
|
+
}
|
|
90
|
+
for (const fieldDef of fields) {
|
|
91
|
+
if (customFields[fieldDef.name] === undefined && fieldDef.default !== undefined) {
|
|
92
|
+
const defaultVal = Array.isArray(fieldDef.default) ? fieldDef.default[0] : fieldDef.default;
|
|
93
|
+
if (defaultVal !== undefined) {
|
|
94
|
+
customFields[fieldDef.name] = defaultVal;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (fieldDef.required && !customFields[fieldDef.name]) {
|
|
98
|
+
(s.custom ??= {}).policy ??= [];
|
|
99
|
+
s.custom.policy.push({ type: 'required-field', field: fieldDef.name });
|
|
100
|
+
customFields[fieldDef.name] = "";
|
|
101
|
+
}
|
|
102
|
+
else if (customFields[fieldDef.name] === undefined) {
|
|
103
|
+
customFields[fieldDef.name] = "";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
s.custom = {
|
|
107
|
+
...(s.custom ?? {}),
|
|
108
|
+
identity: identityValue,
|
|
109
|
+
fields: customFields,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return scenarios;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.TestcaseParser = TestcaseParser;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Testplan parser.
|
|
4
|
+
*
|
|
5
|
+
* Extracts testplan data by grouping all scenarios in a file into a single
|
|
6
|
+
* aggregate scenario. Also extracts embedded testrun links from Rules.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.TestplanParser = void 0;
|
|
10
|
+
const testcase_parser_1 = require("./testcase-parser");
|
|
11
|
+
class TestplanParser extends testcase_parser_1.TestcaseParser {
|
|
12
|
+
filter(scenarios, test, scope) {
|
|
13
|
+
// First, apply standard DSL field extraction
|
|
14
|
+
const filtered = super.filter(scenarios, test, scope);
|
|
15
|
+
// Group by URI (1 feature file = 1 testplan)
|
|
16
|
+
const groups = {};
|
|
17
|
+
for (const s of filtered) {
|
|
18
|
+
(groups[s.uri] ??= []).push(s);
|
|
19
|
+
}
|
|
20
|
+
// Transform each group into a single plan scenario
|
|
21
|
+
return Object.entries(groups).map(([uri, groupScenarios]) => {
|
|
22
|
+
const aggregated = JSON.parse(JSON.stringify(groupScenarios[0]));
|
|
23
|
+
aggregated.custom ??= {};
|
|
24
|
+
// If the base parser extracted an identity from tags, preserve it, otherwise fallback to uri
|
|
25
|
+
const firstScenarioWithIdentity = groupScenarios.find(s => s.custom && s.custom.identity);
|
|
26
|
+
if (firstScenarioWithIdentity && firstScenarioWithIdentity.custom?.identity) {
|
|
27
|
+
aggregated.custom.identity = firstScenarioWithIdentity.custom.identity;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
aggregated.custom.identity = uri;
|
|
31
|
+
}
|
|
32
|
+
// Extract all testruns belonging to this plan
|
|
33
|
+
// Testruns are listed as Rules in the plan feature file
|
|
34
|
+
aggregated.custom.testruns = groupScenarios
|
|
35
|
+
.filter(e => e.rule)
|
|
36
|
+
.map(e => e.rule.name);
|
|
37
|
+
return aggregated;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.TestplanParser = TestplanParser;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Testrun parser.
|
|
4
|
+
*
|
|
5
|
+
* Extracts testrun data by grouping all scenarios in a file into a single
|
|
6
|
+
* aggregate scenario. Also extracts embedded testcase links from Rules.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.TestrunParser = void 0;
|
|
10
|
+
const testcase_parser_1 = require("./testcase-parser");
|
|
11
|
+
class TestrunParser extends testcase_parser_1.TestcaseParser {
|
|
12
|
+
filter(scenarios, test, scope) {
|
|
13
|
+
// First, apply standard DSL field extraction
|
|
14
|
+
const filtered = super.filter(scenarios, test, scope);
|
|
15
|
+
// Group by URI (1 feature file = 1 testrun)
|
|
16
|
+
const groups = {};
|
|
17
|
+
for (const s of filtered) {
|
|
18
|
+
(groups[s.uri] ??= []).push(s);
|
|
19
|
+
}
|
|
20
|
+
// Transform each group into a single run scenario
|
|
21
|
+
return Object.entries(groups).map(([uri, groupScenarios]) => {
|
|
22
|
+
const aggregated = JSON.parse(JSON.stringify(groupScenarios[0]));
|
|
23
|
+
aggregated.custom ??= {};
|
|
24
|
+
// If the base parser extracted an identity from tags, preserve it, otherwise fallback to uri
|
|
25
|
+
const firstScenarioWithIdentity = groupScenarios.find(s => s.custom && s.custom.identity);
|
|
26
|
+
if (firstScenarioWithIdentity && firstScenarioWithIdentity.custom?.identity) {
|
|
27
|
+
aggregated.custom.identity = firstScenarioWithIdentity.custom.identity;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
aggregated.custom.identity = uri;
|
|
31
|
+
}
|
|
32
|
+
// Extract all testcases belonging to this run
|
|
33
|
+
// Format: "RuleName::ScenarioName"
|
|
34
|
+
aggregated.custom.testcases = groupScenarios
|
|
35
|
+
.filter(e => e.rule && e.name)
|
|
36
|
+
.map(e => `${e.rule?.name}::${e.name}`);
|
|
37
|
+
// Also keep the raw group scenarios so we can extract their status fields later
|
|
38
|
+
aggregated.custom.groupScenarios = groupScenarios;
|
|
39
|
+
return aggregated;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.TestrunParser = TestrunParser;
|