@elisra-devops/docgen-data-provider 1.88.0 → 1.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/models/mewp-reporting.d.ts +0 -4
- package/bin/modules/ResultDataProvider.d.ts +8 -7
- package/bin/modules/ResultDataProvider.js +153 -234
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +50 -18
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/models/mewp-reporting.ts +0 -5
- package/src/modules/ResultDataProvider.ts +185 -254
- package/src/tests/modules/ResultDataProvider.test.ts +73 -19
|
@@ -296,7 +296,7 @@ class ResultDataProvider {
|
|
|
296
296
|
};
|
|
297
297
|
try {
|
|
298
298
|
const planName = await this.fetchTestPlanName(testPlanId, projectName);
|
|
299
|
-
const testData = await this.fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds
|
|
299
|
+
const testData = await this.fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds);
|
|
300
300
|
const allRequirements = await this.fetchMewpL2Requirements(projectName);
|
|
301
301
|
if (allRequirements.length === 0) {
|
|
302
302
|
return Object.assign(Object.assign({}, defaultPayload), { sheetName: this.buildMewpCoverageSheetName(planName, testPlanId) });
|
|
@@ -475,7 +475,7 @@ class ResultDataProvider {
|
|
|
475
475
|
return defaultPayload;
|
|
476
476
|
}
|
|
477
477
|
}
|
|
478
|
-
async getMewpInternalValidationFlatResults(testPlanId, projectName, selectedSuiteIds, linkedQueryRequest
|
|
478
|
+
async getMewpInternalValidationFlatResults(testPlanId, projectName, selectedSuiteIds, linkedQueryRequest) {
|
|
479
479
|
var _a, _b, _c;
|
|
480
480
|
const defaultPayload = {
|
|
481
481
|
sheetName: `MEWP Internal Validation - Plan ${testPlanId}`,
|
|
@@ -484,7 +484,7 @@ class ResultDataProvider {
|
|
|
484
484
|
};
|
|
485
485
|
try {
|
|
486
486
|
const planName = await this.fetchTestPlanName(testPlanId, projectName);
|
|
487
|
-
const testData = await this.fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds
|
|
487
|
+
const testData = await this.fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds);
|
|
488
488
|
const allRequirements = await this.fetchMewpL2Requirements(projectName);
|
|
489
489
|
const linkedRequirementsByTestCase = await this.buildLinkedRequirementsByTestCase(allRequirements, testData, projectName);
|
|
490
490
|
const scopedRequirementKeys = await this.resolveMewpRequirementScopeKeysFromQuery(linkedQueryRequest, allRequirements, linkedRequirementsByTestCase);
|
|
@@ -523,35 +523,6 @@ class ResultDataProvider {
|
|
|
523
523
|
};
|
|
524
524
|
for (const testCaseId of [...allTestCaseIds].sort((a, b) => a - b)) {
|
|
525
525
|
diagnostics.totalTestCases += 1;
|
|
526
|
-
const logList = (items, max = 12) => {
|
|
527
|
-
const values = [...items].map((item) => String(item || '').trim()).filter((item) => !!item);
|
|
528
|
-
const shown = values.slice(0, max);
|
|
529
|
-
const suffix = values.length > max ? ` ...(+${values.length - max} more)` : '';
|
|
530
|
-
return shown.join(', ') + suffix;
|
|
531
|
-
};
|
|
532
|
-
const logByFamily = (items, maxFamilies = 8, maxMembers = 10) => {
|
|
533
|
-
const map = new Map();
|
|
534
|
-
for (const item of items || []) {
|
|
535
|
-
const normalized = this.normalizeMewpRequirementCodeWithSuffix(String(item || ''));
|
|
536
|
-
if (!normalized)
|
|
537
|
-
continue;
|
|
538
|
-
const base = this.toRequirementKey(normalized) || normalized;
|
|
539
|
-
if (!map.has(base))
|
|
540
|
-
map.set(base, new Set());
|
|
541
|
-
map.get(base).add(normalized);
|
|
542
|
-
}
|
|
543
|
-
const entries = [...map.entries()]
|
|
544
|
-
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
545
|
-
.slice(0, maxFamilies)
|
|
546
|
-
.map(([base, members]) => {
|
|
547
|
-
const sortedMembers = [...members].sort((a, b) => a.localeCompare(b));
|
|
548
|
-
const shownMembers = sortedMembers.slice(0, maxMembers);
|
|
549
|
-
const membersSuffix = sortedMembers.length > maxMembers ? ` ...(+${sortedMembers.length - maxMembers} more)` : '';
|
|
550
|
-
return `${base}=[${shownMembers.join(', ')}${membersSuffix}]`;
|
|
551
|
-
});
|
|
552
|
-
const suffix = map.size > maxFamilies ? ` ...(+${map.size - maxFamilies} families)` : '';
|
|
553
|
-
return entries.join(' | ') + suffix;
|
|
554
|
-
};
|
|
555
526
|
const stepsXml = stepsXmlByTestCase.get(testCaseId) || '';
|
|
556
527
|
const parsedSteps = stepsXml && String(stepsXml).trim() !== ''
|
|
557
528
|
? await this.testStepParserHelper.parseTestSteps(stepsXml, new Map())
|
|
@@ -592,13 +563,6 @@ class ResultDataProvider {
|
|
|
592
563
|
? new Set([...linkedFullCodesRaw].filter((code) => scopedRequirementKeys.has(this.toRequirementKey(code))))
|
|
593
564
|
: linkedFullCodesRaw;
|
|
594
565
|
const linkedBaseKeys = new Set([...linkedFullCodes].map((code) => this.toRequirementKey(code)).filter((code) => !!code));
|
|
595
|
-
const mentionStepSample = mentionEntries
|
|
596
|
-
.slice(0, 8)
|
|
597
|
-
.map((entry) => `${entry.stepRef}=[${logList(entry.codes, 6)}]`)
|
|
598
|
-
.join(' | ');
|
|
599
|
-
logger_1.default.debug(`MEWP internal validation trace: testCaseId=${testCaseId} ` +
|
|
600
|
-
`mentionSteps=${mentionEntries.length} mentionStepSample='${mentionStepSample}' ` +
|
|
601
|
-
`mentionedL2ByFamily='${logByFamily(mentionedL2Only)}' linkedByFamily='${logByFamily(linkedFullCodes)}'`);
|
|
602
566
|
const mentionedCodesByBase = new Map();
|
|
603
567
|
for (const code of mentionedL2Only) {
|
|
604
568
|
const baseKey = this.toRequirementKey(code);
|
|
@@ -620,82 +584,47 @@ class ResultDataProvider {
|
|
|
620
584
|
const mentionedCodesList = [...mentionedCodes];
|
|
621
585
|
const hasBaseMention = mentionedCodesList.some((code) => !/-\d+$/.test(code));
|
|
622
586
|
const mentionedSpecificMembers = mentionedCodesList.filter((code) => /-\d+$/.test(code));
|
|
623
|
-
let familyDecision = 'no-action';
|
|
624
|
-
let familyTargetCodes = [];
|
|
625
|
-
let familyMissingCodes = [];
|
|
626
|
-
let familyLinkedCodes = [];
|
|
627
|
-
let familyAllCodes = [];
|
|
628
587
|
if (familyCodes === null || familyCodes === void 0 ? void 0 : familyCodes.size) {
|
|
629
|
-
familyAllCodes = [...familyCodes].sort((a, b) => a.localeCompare(b));
|
|
630
|
-
familyLinkedCodes = familyAllCodes.filter((code) => linkedFullCodes.has(code));
|
|
631
588
|
// Base mention ("SR0054") validates against child coverage when children exist.
|
|
632
589
|
// If no child variants exist, fallback to the single standalone requirement code.
|
|
633
590
|
if (hasBaseMention) {
|
|
634
591
|
const familyCodesList = [...familyCodes];
|
|
635
592
|
const childFamilyCodes = familyCodesList.filter((code) => /-\d+$/.test(code));
|
|
636
593
|
const targetFamilyCodes = childFamilyCodes.length > 0 ? childFamilyCodes : familyCodesList;
|
|
637
|
-
familyTargetCodes = [...targetFamilyCodes].sort((a, b) => a.localeCompare(b));
|
|
638
594
|
const missingInTargetFamily = targetFamilyCodes.filter((code) => !linkedFullCodes.has(code));
|
|
639
|
-
familyMissingCodes = [...missingInTargetFamily].sort((a, b) => a.localeCompare(b));
|
|
640
595
|
if (missingInTargetFamily.length > 0) {
|
|
641
596
|
const hasAnyLinkedInFamily = familyCodesList.some((code) => linkedFullCodes.has(code));
|
|
642
597
|
if (!hasAnyLinkedInFamily) {
|
|
643
598
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
644
|
-
familyDecision = 'base-mentioned-family-uncovered';
|
|
645
599
|
}
|
|
646
600
|
else {
|
|
647
601
|
for (const code of missingInTargetFamily) {
|
|
648
602
|
missingFamilyMembers.add(code);
|
|
649
603
|
}
|
|
650
|
-
familyDecision = 'base-mentioned-family-partial-missing-children';
|
|
651
604
|
}
|
|
652
605
|
}
|
|
653
|
-
else {
|
|
654
|
-
familyDecision = 'base-mentioned-family-fully-covered';
|
|
655
|
-
}
|
|
656
|
-
logger_1.default.debug(`MEWP internal validation family decision: testCaseId=${testCaseId} base=${baseKey} ` +
|
|
657
|
-
`mode=baseMention mentioned='${logList(mentionedCodesList)}' familyAll='${logList(familyAllCodes)}' ` +
|
|
658
|
-
`target='${logList(familyTargetCodes)}' linked='${logList(familyLinkedCodes)}' ` +
|
|
659
|
-
`missing='${logList(familyMissingCodes)}' decision=${familyDecision}`);
|
|
660
606
|
continue;
|
|
661
607
|
}
|
|
662
608
|
// Specific mention ("SR0054-1") validates as exact-match only.
|
|
663
|
-
const missingSpecificMembers = [];
|
|
664
609
|
for (const code of mentionedSpecificMembers) {
|
|
665
610
|
if (!linkedFullCodes.has(code)) {
|
|
666
611
|
missingSpecificMentionedNoFamily.add(code);
|
|
667
|
-
missingSpecificMembers.push(code);
|
|
668
612
|
}
|
|
669
613
|
}
|
|
670
|
-
familyDecision =
|
|
671
|
-
missingSpecificMembers.length > 0
|
|
672
|
-
? 'specific-mentioned-exact-missing'
|
|
673
|
-
: 'specific-mentioned-exact-covered';
|
|
674
|
-
logger_1.default.debug(`MEWP internal validation family decision: testCaseId=${testCaseId} base=${baseKey} ` +
|
|
675
|
-
`mode=specificMention mentioned='${logList(mentionedCodesList)}' familyAll='${logList(familyAllCodes)}' ` +
|
|
676
|
-
`linked='${logList(familyLinkedCodes)}' missingSpecific='${logList(missingSpecificMembers)}' ` +
|
|
677
|
-
`decision=${familyDecision}`);
|
|
678
614
|
continue;
|
|
679
615
|
}
|
|
680
616
|
// Fallback path when family data is unavailable for this base key.
|
|
681
|
-
const fallbackMissingSpecific = [];
|
|
682
|
-
let fallbackBaseMissing = false;
|
|
683
617
|
for (const code of mentionedCodes) {
|
|
684
618
|
const hasSpecificSuffix = /-\d+$/.test(code);
|
|
685
619
|
if (hasSpecificSuffix) {
|
|
686
620
|
if (!linkedFullCodes.has(code)) {
|
|
687
621
|
missingSpecificMentionedNoFamily.add(code);
|
|
688
|
-
fallbackMissingSpecific.push(code);
|
|
689
622
|
}
|
|
690
623
|
}
|
|
691
624
|
else if (!linkedBaseKeys.has(baseKey)) {
|
|
692
625
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
693
|
-
fallbackBaseMissing = true;
|
|
694
626
|
}
|
|
695
627
|
}
|
|
696
|
-
logger_1.default.debug(`MEWP internal validation family decision: testCaseId=${testCaseId} base=${baseKey} ` +
|
|
697
|
-
`mode=noFamilyData mentioned='${logList(mentionedCodesList)}' linkedBasePresent=${linkedBaseKeys.has(baseKey)} ` +
|
|
698
|
-
`missingSpecific='${logList(fallbackMissingSpecific)}' missingBase=${fallbackBaseMissing}`);
|
|
699
628
|
}
|
|
700
629
|
// Direction B is family-based: if any member of a family is mentioned in Expected Result,
|
|
701
630
|
// linked members of that same family are not considered "linked but not mentioned".
|
|
@@ -753,14 +682,8 @@ class ResultDataProvider {
|
|
|
753
682
|
const groupedRequirementList = this.formatRequirementCodesGroupedByFamily(requirementIds);
|
|
754
683
|
return `${stepRef}: ${groupedRequirementList}`;
|
|
755
684
|
})
|
|
756
|
-
.join('
|
|
685
|
+
.join('\n');
|
|
757
686
|
const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked);
|
|
758
|
-
const rawMentionedByStepForLog = [...mentionedButNotLinkedByStep.entries()]
|
|
759
|
-
.map(([stepRef, requirementIds]) => `${stepRef}=[${logList(requirementIds, 8)}]`)
|
|
760
|
-
.join(' | ');
|
|
761
|
-
logger_1.default.debug(`MEWP internal validation grouped diagnostics: testCaseId=${testCaseId} ` +
|
|
762
|
-
`rawMentionedByStep='${rawMentionedByStepForLog}' groupedMentioned='${mentionedButNotLinked}' ` +
|
|
763
|
-
`rawLinkedOnlyByFamily='${logByFamily(sortedExtraLinked)}' groupedLinkedOnly='${linkedButNotMentioned}'`);
|
|
764
687
|
const validationStatus = mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
765
688
|
if (validationStatus === 'Fail')
|
|
766
689
|
diagnostics.failingRows += 1;
|
|
@@ -1117,126 +1040,9 @@ class ResultDataProvider {
|
|
|
1117
1040
|
return 'Pass';
|
|
1118
1041
|
return (input === null || input === void 0 ? void 0 : input.hasAnyTestCase) ? 'Not Run' : 'Not Run';
|
|
1119
1042
|
}
|
|
1120
|
-
async fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
return this.fetchTestData(suites, projectName, testPlanId, false);
|
|
1124
|
-
}
|
|
1125
|
-
const selectedSuites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
|
|
1126
|
-
const selectedRel = this.resolveMaxRelNumberFromSuites(selectedSuites);
|
|
1127
|
-
if (selectedRel <= 0) {
|
|
1128
|
-
return this.fetchTestData(selectedSuites, projectName, testPlanId, false);
|
|
1129
|
-
}
|
|
1130
|
-
const allSuites = await this.fetchTestSuites(testPlanId, projectName, undefined, true);
|
|
1131
|
-
const relScopedSuites = allSuites.filter((suite) => {
|
|
1132
|
-
const rel = this.extractRelNumberFromSuite(suite);
|
|
1133
|
-
return rel > 0 && rel <= selectedRel;
|
|
1134
|
-
});
|
|
1135
|
-
const suitesForFetch = relScopedSuites.length > 0 ? relScopedSuites : selectedSuites;
|
|
1136
|
-
const rawTestData = await this.fetchTestData(suitesForFetch, projectName, testPlanId, false);
|
|
1137
|
-
return this.reduceToLatestRelRunPerTestCase(rawTestData);
|
|
1138
|
-
}
|
|
1139
|
-
extractRelNumberFromSuite(suite) {
|
|
1140
|
-
const candidates = [
|
|
1141
|
-
suite === null || suite === void 0 ? void 0 : suite.suiteName,
|
|
1142
|
-
suite === null || suite === void 0 ? void 0 : suite.parentSuiteName,
|
|
1143
|
-
suite === null || suite === void 0 ? void 0 : suite.suitePath,
|
|
1144
|
-
suite === null || suite === void 0 ? void 0 : suite.testGroupName,
|
|
1145
|
-
];
|
|
1146
|
-
const pattern = /(?:^|[^a-z0-9])rel\s*([0-9]+)/i;
|
|
1147
|
-
for (const item of candidates) {
|
|
1148
|
-
const match = pattern.exec(String(item || ''));
|
|
1149
|
-
if (!match)
|
|
1150
|
-
continue;
|
|
1151
|
-
const parsed = Number(match[1]);
|
|
1152
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
1153
|
-
return parsed;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return 0;
|
|
1157
|
-
}
|
|
1158
|
-
resolveMaxRelNumberFromSuites(suites) {
|
|
1159
|
-
let maxRel = 0;
|
|
1160
|
-
for (const suite of suites || []) {
|
|
1161
|
-
const rel = this.extractRelNumberFromSuite(suite);
|
|
1162
|
-
if (rel > maxRel)
|
|
1163
|
-
maxRel = rel;
|
|
1164
|
-
}
|
|
1165
|
-
return maxRel;
|
|
1166
|
-
}
|
|
1167
|
-
reduceToLatestRelRunPerTestCase(testData) {
|
|
1168
|
-
var _a, _b;
|
|
1169
|
-
const candidatesByTestCase = new Map();
|
|
1170
|
-
const testCaseDefinitionById = new Map();
|
|
1171
|
-
for (const suite of testData || []) {
|
|
1172
|
-
const rel = this.extractRelNumberFromSuite(suite);
|
|
1173
|
-
const testPointsItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testPointsItems) ? suite.testPointsItems : [];
|
|
1174
|
-
const testCasesItems = Array.isArray(suite === null || suite === void 0 ? void 0 : suite.testCasesItems) ? suite.testCasesItems : [];
|
|
1175
|
-
for (const testCase of testCasesItems) {
|
|
1176
|
-
const testCaseId = Number(((_a = testCase === null || testCase === void 0 ? void 0 : testCase.workItem) === null || _a === void 0 ? void 0 : _a.id) || (testCase === null || testCase === void 0 ? void 0 : testCase.testCaseId) || (testCase === null || testCase === void 0 ? void 0 : testCase.id) || 0);
|
|
1177
|
-
if (!Number.isFinite(testCaseId) || testCaseId <= 0)
|
|
1178
|
-
continue;
|
|
1179
|
-
if (!testCaseDefinitionById.has(testCaseId)) {
|
|
1180
|
-
testCaseDefinitionById.set(testCaseId, testCase);
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
for (const point of testPointsItems) {
|
|
1184
|
-
const testCaseId = Number((point === null || point === void 0 ? void 0 : point.testCaseId) || ((_b = point === null || point === void 0 ? void 0 : point.testCase) === null || _b === void 0 ? void 0 : _b.id) || 0);
|
|
1185
|
-
if (!Number.isFinite(testCaseId) || testCaseId <= 0)
|
|
1186
|
-
continue;
|
|
1187
|
-
const runId = Number((point === null || point === void 0 ? void 0 : point.lastRunId) || 0);
|
|
1188
|
-
const resultId = Number((point === null || point === void 0 ? void 0 : point.lastResultId) || 0);
|
|
1189
|
-
const hasRun = runId > 0 && resultId > 0;
|
|
1190
|
-
if (!candidatesByTestCase.has(testCaseId)) {
|
|
1191
|
-
candidatesByTestCase.set(testCaseId, []);
|
|
1192
|
-
}
|
|
1193
|
-
candidatesByTestCase.get(testCaseId).push({
|
|
1194
|
-
point,
|
|
1195
|
-
rel,
|
|
1196
|
-
runId,
|
|
1197
|
-
resultId,
|
|
1198
|
-
hasRun,
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
const selectedPoints = [];
|
|
1203
|
-
const selectedTestCaseIds = new Set();
|
|
1204
|
-
for (const [testCaseId, candidates] of candidatesByTestCase.entries()) {
|
|
1205
|
-
const sorted = [...candidates].sort((a, b) => {
|
|
1206
|
-
if (a.hasRun !== b.hasRun)
|
|
1207
|
-
return a.hasRun ? -1 : 1;
|
|
1208
|
-
if (a.rel !== b.rel)
|
|
1209
|
-
return b.rel - a.rel;
|
|
1210
|
-
if (a.runId !== b.runId)
|
|
1211
|
-
return b.runId - a.runId;
|
|
1212
|
-
return b.resultId - a.resultId;
|
|
1213
|
-
});
|
|
1214
|
-
const chosen = sorted[0];
|
|
1215
|
-
if (!(chosen === null || chosen === void 0 ? void 0 : chosen.point))
|
|
1216
|
-
continue;
|
|
1217
|
-
selectedPoints.push(chosen.point);
|
|
1218
|
-
selectedTestCaseIds.add(testCaseId);
|
|
1219
|
-
}
|
|
1220
|
-
const selectedTestCases = [];
|
|
1221
|
-
for (const testCaseId of selectedTestCaseIds) {
|
|
1222
|
-
const definition = testCaseDefinitionById.get(testCaseId);
|
|
1223
|
-
if (definition) {
|
|
1224
|
-
selectedTestCases.push(definition);
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
return [
|
|
1228
|
-
{
|
|
1229
|
-
testSuiteId: 'MEWP_REL_SCOPED',
|
|
1230
|
-
suiteId: 'MEWP_REL_SCOPED',
|
|
1231
|
-
suiteName: 'MEWP Rel Scoped',
|
|
1232
|
-
parentSuiteId: '',
|
|
1233
|
-
parentSuiteName: '',
|
|
1234
|
-
suitePath: 'MEWP Rel Scoped',
|
|
1235
|
-
testGroupName: 'MEWP Rel Scoped',
|
|
1236
|
-
testPointsItems: selectedPoints,
|
|
1237
|
-
testCasesItems: selectedTestCases,
|
|
1238
|
-
},
|
|
1239
|
-
];
|
|
1043
|
+
async fetchMewpScopedTestData(testPlanId, projectName, selectedSuiteIds) {
|
|
1044
|
+
const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
|
|
1045
|
+
return this.fetchTestData(suites, projectName, testPlanId, false);
|
|
1240
1046
|
}
|
|
1241
1047
|
async loadExternalBugsByTestCase(externalBugsFile) {
|
|
1242
1048
|
return this.mewpExternalIngestionUtils.loadExternalBugsByTestCase(externalBugsFile, {
|
|
@@ -1551,16 +1357,6 @@ class ResultDataProvider {
|
|
|
1551
1357
|
}
|
|
1552
1358
|
return out;
|
|
1553
1359
|
}
|
|
1554
|
-
extractRequirementCodesFromExpectedSteps(steps, includeSuffix) {
|
|
1555
|
-
const out = new Set();
|
|
1556
|
-
for (const step of Array.isArray(steps) ? steps : []) {
|
|
1557
|
-
if (step === null || step === void 0 ? void 0 : step.isSharedStepTitle)
|
|
1558
|
-
continue;
|
|
1559
|
-
const codes = this.extractRequirementCodesFromExpectedText((step === null || step === void 0 ? void 0 : step.expected) || '', includeSuffix);
|
|
1560
|
-
codes.forEach((code) => out.add(code));
|
|
1561
|
-
}
|
|
1562
|
-
return out;
|
|
1563
|
-
}
|
|
1564
1360
|
extractRequirementCodesFromExpectedText(text, includeSuffix) {
|
|
1565
1361
|
const out = new Set();
|
|
1566
1362
|
const source = this.normalizeRequirementStepText(text);
|
|
@@ -2370,7 +2166,7 @@ class ResultDataProvider {
|
|
|
2370
2166
|
return sortedMembers[0];
|
|
2371
2167
|
return `${baseKey}: ${sortedMembers.join(', ')}`;
|
|
2372
2168
|
})
|
|
2373
|
-
.join('
|
|
2169
|
+
.join('\n');
|
|
2374
2170
|
}
|
|
2375
2171
|
toMewpComparableText(value) {
|
|
2376
2172
|
if (value === null || value === undefined)
|
|
@@ -2653,6 +2449,120 @@ class ResultDataProvider {
|
|
|
2653
2449
|
const { value: testCases } = await tfs_1.TFSServices.getItemContent(url, this.token);
|
|
2654
2450
|
return testCases;
|
|
2655
2451
|
}
|
|
2452
|
+
attachSuiteTestCaseContextToPoints(testCasesItems, testPointsItems) {
|
|
2453
|
+
var _a;
|
|
2454
|
+
const points = Array.isArray(testPointsItems) ? testPointsItems : [];
|
|
2455
|
+
const testCases = Array.isArray(testCasesItems) ? testCasesItems : [];
|
|
2456
|
+
if (points.length === 0 || testCases.length === 0)
|
|
2457
|
+
return points;
|
|
2458
|
+
const byTestCaseId = new Map();
|
|
2459
|
+
for (const testCaseItem of testCases) {
|
|
2460
|
+
const testCaseId = Number(((_a = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _a === void 0 ? void 0 : _a.id) || (testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.testCaseId) || (testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.id) || 0);
|
|
2461
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0 || byTestCaseId.has(testCaseId))
|
|
2462
|
+
continue;
|
|
2463
|
+
byTestCaseId.set(testCaseId, testCaseItem);
|
|
2464
|
+
}
|
|
2465
|
+
return points.map((point) => {
|
|
2466
|
+
var _a;
|
|
2467
|
+
const testCaseId = Number((point === null || point === void 0 ? void 0 : point.testCaseId) || ((_a = point === null || point === void 0 ? void 0 : point.testCase) === null || _a === void 0 ? void 0 : _a.id) || 0);
|
|
2468
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0)
|
|
2469
|
+
return point;
|
|
2470
|
+
const suiteTestCase = byTestCaseId.get(testCaseId);
|
|
2471
|
+
if (!suiteTestCase)
|
|
2472
|
+
return point;
|
|
2473
|
+
return Object.assign(Object.assign({}, point), { suiteTestCase });
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
extractWorkItemFieldsMap(workItemFields) {
|
|
2477
|
+
const fields = {};
|
|
2478
|
+
if (!Array.isArray(workItemFields))
|
|
2479
|
+
return fields;
|
|
2480
|
+
for (const field of workItemFields) {
|
|
2481
|
+
const keyCandidates = [field === null || field === void 0 ? void 0 : field.key, field === null || field === void 0 ? void 0 : field.name, field === null || field === void 0 ? void 0 : field.referenceName]
|
|
2482
|
+
.map((item) => String(item || '').trim())
|
|
2483
|
+
.filter((item) => !!item);
|
|
2484
|
+
for (const key of keyCandidates) {
|
|
2485
|
+
fields[key] = field === null || field === void 0 ? void 0 : field.value;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
return fields;
|
|
2489
|
+
}
|
|
2490
|
+
resolveSuiteTestCaseRevision(testCaseItem) {
|
|
2491
|
+
var _a, _b, _c, _d, _e;
|
|
2492
|
+
const revisionCandidates = [
|
|
2493
|
+
(_a = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _a === void 0 ? void 0 : _a.rev,
|
|
2494
|
+
(_b = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _b === void 0 ? void 0 : _b.revision,
|
|
2495
|
+
(_c = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _c === void 0 ? void 0 : _c.version,
|
|
2496
|
+
(_d = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _d === void 0 ? void 0 : _d.workItemRevision,
|
|
2497
|
+
(_e = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _e === void 0 ? void 0 : _e.workItemVersion,
|
|
2498
|
+
testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.revision,
|
|
2499
|
+
testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItemRevision,
|
|
2500
|
+
testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItemVersion,
|
|
2501
|
+
];
|
|
2502
|
+
for (const candidate of revisionCandidates) {
|
|
2503
|
+
const parsed = Number(candidate || 0);
|
|
2504
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
2505
|
+
return parsed;
|
|
2506
|
+
}
|
|
2507
|
+
return 0;
|
|
2508
|
+
}
|
|
2509
|
+
buildWorkItemSnapshotFromSuiteTestCase(testCaseItem, fallbackTestCaseId, fallbackTestCaseName = '') {
|
|
2510
|
+
var _a;
|
|
2511
|
+
if (!testCaseItem)
|
|
2512
|
+
return null;
|
|
2513
|
+
const testCaseId = Number(((_a = testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) === null || _a === void 0 ? void 0 : _a.id) || (testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.testCaseId) || (testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.id) || fallbackTestCaseId || 0);
|
|
2514
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0)
|
|
2515
|
+
return null;
|
|
2516
|
+
const workItem = (testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.workItem) || {};
|
|
2517
|
+
const stepsXml = this.extractStepsXmlFromTestCaseItem(testCaseItem);
|
|
2518
|
+
const fieldsFromList = this.extractWorkItemFieldsMap(workItem === null || workItem === void 0 ? void 0 : workItem.workItemFields);
|
|
2519
|
+
const fieldsFromMap = (workItem === null || workItem === void 0 ? void 0 : workItem.fields) || {};
|
|
2520
|
+
const fields = Object.assign(Object.assign({}, fieldsFromList), fieldsFromMap);
|
|
2521
|
+
if (!fields['System.Title']) {
|
|
2522
|
+
const title = String((testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.testCaseName) || (workItem === null || workItem === void 0 ? void 0 : workItem.name) || (testCaseItem === null || testCaseItem === void 0 ? void 0 : testCaseItem.name) || fallbackTestCaseName || '').trim();
|
|
2523
|
+
if (title) {
|
|
2524
|
+
fields['System.Title'] = title;
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
if (stepsXml && !fields['Microsoft.VSTS.TCM.Steps']) {
|
|
2528
|
+
fields['Microsoft.VSTS.TCM.Steps'] = stepsXml;
|
|
2529
|
+
}
|
|
2530
|
+
return {
|
|
2531
|
+
id: testCaseId,
|
|
2532
|
+
rev: this.resolveSuiteTestCaseRevision(testCaseItem) || 1,
|
|
2533
|
+
fields,
|
|
2534
|
+
relations: Array.isArray(workItem === null || workItem === void 0 ? void 0 : workItem.relations) ? workItem.relations : [],
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
async fetchWorkItemByRevision(projectName, workItemId, revision, expandAll = false) {
|
|
2538
|
+
const id = Number(workItemId || 0);
|
|
2539
|
+
const rev = Number(revision || 0);
|
|
2540
|
+
if (!Number.isFinite(id) || id <= 0 || !Number.isFinite(rev) || rev <= 0)
|
|
2541
|
+
return null;
|
|
2542
|
+
const expandParam = expandAll ? '?$expand=all' : '';
|
|
2543
|
+
const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}/revisions/${rev}${expandParam}`;
|
|
2544
|
+
try {
|
|
2545
|
+
return await tfs_1.TFSServices.getItemContent(url, this.token);
|
|
2546
|
+
}
|
|
2547
|
+
catch (error) {
|
|
2548
|
+
logger_1.default.warn(`Failed to fetch work item ${id} by revision ${rev}: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
|
|
2549
|
+
return null;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
async fetchWorkItemLatest(projectName, workItemId, expandAll = false) {
|
|
2553
|
+
const id = Number(workItemId || 0);
|
|
2554
|
+
if (!Number.isFinite(id) || id <= 0)
|
|
2555
|
+
return null;
|
|
2556
|
+
const expandParam = expandAll ? '?$expand=all' : '';
|
|
2557
|
+
const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}${expandParam}`;
|
|
2558
|
+
try {
|
|
2559
|
+
return await tfs_1.TFSServices.getItemContent(url, this.token);
|
|
2560
|
+
}
|
|
2561
|
+
catch (error) {
|
|
2562
|
+
logger_1.default.warn(`Failed to fetch latest work item ${id}: ${(error === null || error === void 0 ? void 0 : error.message) || error}`);
|
|
2563
|
+
return null;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2656
2566
|
/**
|
|
2657
2567
|
* Fetches result data based on the Work Item Test Reporter.
|
|
2658
2568
|
*
|
|
@@ -2668,7 +2578,7 @@ class ResultDataProvider {
|
|
|
2668
2578
|
* @returns A promise that resolves to the fetched result data.
|
|
2669
2579
|
*/
|
|
2670
2580
|
async fetchResultDataBasedOnWiBase(projectName, runId, resultId, isTestReporter = false, selectedFields, isQueryMode, point, includeAllHistory = false) {
|
|
2671
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2581
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
2672
2582
|
try {
|
|
2673
2583
|
let filteredFields = {};
|
|
2674
2584
|
let relatedRequirements = [];
|
|
@@ -2679,16 +2589,29 @@ class ResultDataProvider {
|
|
|
2679
2589
|
logger_1.default.warn(`Invalid run result ${runId} or result ${resultId}`);
|
|
2680
2590
|
return null;
|
|
2681
2591
|
}
|
|
2682
|
-
const
|
|
2683
|
-
const
|
|
2592
|
+
const suiteTestCaseItem = point === null || point === void 0 ? void 0 : point.suiteTestCase;
|
|
2593
|
+
const testCaseId = Number((point === null || point === void 0 ? void 0 : point.testCaseId) || ((_a = suiteTestCaseItem === null || suiteTestCaseItem === void 0 ? void 0 : suiteTestCaseItem.workItem) === null || _a === void 0 ? void 0 : _a.id) || (suiteTestCaseItem === null || suiteTestCaseItem === void 0 ? void 0 : suiteTestCaseItem.testCaseId) || 0);
|
|
2594
|
+
const suiteTestCaseRevision = this.resolveSuiteTestCaseRevision(suiteTestCaseItem);
|
|
2595
|
+
const fallbackSnapshot = this.buildWorkItemSnapshotFromSuiteTestCase(suiteTestCaseItem, testCaseId, String((point === null || point === void 0 ? void 0 : point.testCaseName) || ''));
|
|
2596
|
+
let testCaseData = await this.fetchWorkItemByRevision(projectName, testCaseId, suiteTestCaseRevision, isTestReporter);
|
|
2597
|
+
if (!testCaseData) {
|
|
2598
|
+
testCaseData = fallbackSnapshot;
|
|
2599
|
+
}
|
|
2600
|
+
if (!testCaseData) {
|
|
2601
|
+
testCaseData = await this.fetchWorkItemLatest(projectName, testCaseId, isTestReporter);
|
|
2602
|
+
}
|
|
2603
|
+
if (!testCaseData) {
|
|
2604
|
+
logger_1.default.warn(`Could not resolve test case ${point.testCaseId} for runless point fallback.`);
|
|
2605
|
+
return null;
|
|
2606
|
+
}
|
|
2684
2607
|
const newResultData = {
|
|
2685
2608
|
id: 0,
|
|
2686
2609
|
outcome: point.outcome,
|
|
2687
|
-
revision: (testCaseData === null || testCaseData === void 0 ? void 0 : testCaseData.rev) || 1,
|
|
2688
|
-
testCase: { id:
|
|
2689
|
-
state: ((
|
|
2690
|
-
priority: ((
|
|
2691
|
-
createdDate: ((
|
|
2610
|
+
revision: Number((testCaseData === null || testCaseData === void 0 ? void 0 : testCaseData.rev) || suiteTestCaseRevision || 1),
|
|
2611
|
+
testCase: { id: String(testCaseId), name: point.testCaseName },
|
|
2612
|
+
state: ((_b = testCaseData === null || testCaseData === void 0 ? void 0 : testCaseData.fields) === null || _b === void 0 ? void 0 : _b['System.State']) || 'Active',
|
|
2613
|
+
priority: ((_c = testCaseData === null || testCaseData === void 0 ? void 0 : testCaseData.fields) === null || _c === void 0 ? void 0 : _c['Microsoft.VSTS.TCM.Priority']) || 0,
|
|
2614
|
+
createdDate: ((_d = testCaseData === null || testCaseData === void 0 ? void 0 : testCaseData.fields) === null || _d === void 0 ? void 0 : _d['System.CreatedDate']) || '0001-01-01T00:00:00',
|
|
2692
2615
|
testSuite: point.testSuite,
|
|
2693
2616
|
failureType: 'None',
|
|
2694
2617
|
};
|
|
@@ -2696,7 +2619,7 @@ class ResultDataProvider {
|
|
|
2696
2619
|
this.appendQueryRelations(point.testCaseId, relatedRequirements, relatedBugs, relatedCRs);
|
|
2697
2620
|
}
|
|
2698
2621
|
else {
|
|
2699
|
-
const filteredLinkedFields = (
|
|
2622
|
+
const filteredLinkedFields = (_e = selectedFields === null || selectedFields === void 0 ? void 0 : selectedFields.filter((field) => field.includes('@linked'))) === null || _e === void 0 ? void 0 : _e.map((field) => field.split('@')[0]);
|
|
2700
2623
|
const selectedLinkedFieldSet = new Set(filteredLinkedFields);
|
|
2701
2624
|
const { relations } = testCaseData;
|
|
2702
2625
|
if (relations) {
|
|
@@ -2704,7 +2627,7 @@ class ResultDataProvider {
|
|
|
2704
2627
|
}
|
|
2705
2628
|
selectedLinkedFieldSet.clear();
|
|
2706
2629
|
}
|
|
2707
|
-
const filteredTestCaseFields = (
|
|
2630
|
+
const filteredTestCaseFields = (_f = selectedFields === null || selectedFields === void 0 ? void 0 : selectedFields.filter((field) => field.includes('@testCaseWorkItemField'))) === null || _f === void 0 ? void 0 : _f.map((field) => field.split('@')[0]);
|
|
2708
2631
|
const selectedFieldSet = new Set(filteredTestCaseFields);
|
|
2709
2632
|
// Filter fields based on selected field set
|
|
2710
2633
|
if (selectedFieldSet.size !== 0) {
|
|
@@ -2718,7 +2641,7 @@ class ResultDataProvider {
|
|
|
2718
2641
|
}
|
|
2719
2642
|
}
|
|
2720
2643
|
selectedFieldSet.clear();
|
|
2721
|
-
return Object.assign(Object.assign({}, newResultData), { stepsResultXml: testCaseData.fields['Microsoft.VSTS.TCM.Steps'] || undefined, testCaseRevision: testCaseData.rev, filteredFields,
|
|
2644
|
+
return Object.assign(Object.assign({}, newResultData), { stepsResultXml: ((_g = testCaseData === null || testCaseData === void 0 ? void 0 : testCaseData.fields) === null || _g === void 0 ? void 0 : _g['Microsoft.VSTS.TCM.Steps']) || undefined, testCaseRevision: Number((testCaseData === null || testCaseData === void 0 ? void 0 : testCaseData.rev) || suiteTestCaseRevision || 1), filteredFields,
|
|
2722
2645
|
relatedRequirements,
|
|
2723
2646
|
relatedBugs,
|
|
2724
2647
|
relatedCRs });
|
|
@@ -2738,7 +2661,7 @@ class ResultDataProvider {
|
|
|
2738
2661
|
this.appendQueryRelations(resultData.testCase.id, relatedRequirements, relatedBugs, relatedCRs);
|
|
2739
2662
|
}
|
|
2740
2663
|
else {
|
|
2741
|
-
const filteredLinkedFields = (
|
|
2664
|
+
const filteredLinkedFields = (_h = selectedFields === null || selectedFields === void 0 ? void 0 : selectedFields.filter((field) => field.includes('@linked'))) === null || _h === void 0 ? void 0 : _h.map((field) => field.split('@')[0]);
|
|
2742
2665
|
const selectedLinkedFieldSet = new Set(filteredLinkedFields);
|
|
2743
2666
|
const { relations } = wiByRevision;
|
|
2744
2667
|
if (relations) {
|
|
@@ -2746,7 +2669,7 @@ class ResultDataProvider {
|
|
|
2746
2669
|
}
|
|
2747
2670
|
selectedLinkedFieldSet.clear();
|
|
2748
2671
|
}
|
|
2749
|
-
const filteredTestCaseFields = (
|
|
2672
|
+
const filteredTestCaseFields = (_j = selectedFields === null || selectedFields === void 0 ? void 0 : selectedFields.filter((field) => field.includes('@testCaseWorkItemField'))) === null || _j === void 0 ? void 0 : _j.map((field) => field.split('@')[0]);
|
|
2750
2673
|
const selectedFieldSet = new Set(filteredTestCaseFields);
|
|
2751
2674
|
// Filter fields based on selected field set
|
|
2752
2675
|
if (selectedFieldSet.size !== 0) {
|
|
@@ -2839,11 +2762,6 @@ class ResultDataProvider {
|
|
|
2839
2762
|
return (String((_b = (_a = process === null || process === void 0 ? void 0 : process.env) === null || _a === void 0 ? void 0 : _a.DOCGEN_VERBOSE_HISTORY_DEBUG) !== null && _b !== void 0 ? _b : '').toLowerCase() === 'true' ||
|
|
2840
2763
|
String((_d = (_c = process === null || process === void 0 ? void 0 : process.env) === null || _c === void 0 ? void 0 : _c.DOCGEN_VERBOSE_HISTORY_DEBUG) !== null && _d !== void 0 ? _d : '') === '1');
|
|
2841
2764
|
}
|
|
2842
|
-
isRunResultDebugEnabled() {
|
|
2843
|
-
var _a, _b, _c, _d;
|
|
2844
|
-
return (String((_b = (_a = process === null || process === void 0 ? void 0 : process.env) === null || _a === void 0 ? void 0 : _a.DOCGEN_DEBUG_RUNRESULT) !== null && _b !== void 0 ? _b : '').toLowerCase() === 'true' ||
|
|
2845
|
-
String((_d = (_c = process === null || process === void 0 ? void 0 : process.env) === null || _c === void 0 ? void 0 : _c.DOCGEN_DEBUG_RUNRESULT) !== null && _d !== void 0 ? _d : '') === '1');
|
|
2846
|
-
}
|
|
2847
2765
|
extractCommentText(comment) {
|
|
2848
2766
|
var _a, _b, _c, _d;
|
|
2849
2767
|
const rendered = comment === null || comment === void 0 ? void 0 : comment.renderedText;
|
|
@@ -3447,9 +3365,10 @@ class ResultDataProvider {
|
|
|
3447
3365
|
try {
|
|
3448
3366
|
const testCasesItems = await this.fetchTestCasesBySuiteId(projectName, testPlanId, suite.testSuiteId);
|
|
3449
3367
|
const testCaseIds = testCasesItems.map((testCase) => testCase.workItem.id);
|
|
3450
|
-
const
|
|
3368
|
+
const rawTestPointsItems = !fetchCrossPlans
|
|
3451
3369
|
? await this.fetchTestPoints(projectName, testPlanId, suite.testSuiteId)
|
|
3452
3370
|
: await this.fetchCrossTestPoints(projectName, testCaseIds);
|
|
3371
|
+
const testPointsItems = this.attachSuiteTestCaseContextToPoints(testCasesItems, rawTestPointsItems);
|
|
3453
3372
|
return Object.assign(Object.assign({}, suite), { testPointsItems, testCasesItems });
|
|
3454
3373
|
}
|
|
3455
3374
|
catch (error) {
|
|
@@ -4088,6 +4007,7 @@ class ResultDataProvider {
|
|
|
4088
4007
|
const iteration = ((_a = resultData.iterationDetails) === null || _a === void 0 ? void 0 : _a.length) > 0
|
|
4089
4008
|
? resultData.iterationDetails[((_b = resultData.iterationDetails) === null || _b === void 0 ? void 0 : _b.length) - 1]
|
|
4090
4009
|
: undefined;
|
|
4010
|
+
const testOutcome = this.getTestOutcome(resultData);
|
|
4091
4011
|
if (!(resultData === null || resultData === void 0 ? void 0 : resultData.testCase) || !(resultData === null || resultData === void 0 ? void 0 : resultData.testSuite)) {
|
|
4092
4012
|
logger_1.default.debug(`[RunResult] Missing testCase/testSuite for point testCaseId=${String((_c = point === null || point === void 0 ? void 0 : point.testCaseId) !== null && _c !== void 0 ? _c : 'unknown')} (lastRunId=${String(lastRunId !== null && lastRunId !== void 0 ? lastRunId : '')}, lastResultId=${String(lastResultId !== null && lastResultId !== void 0 ? lastResultId : '')}). hasTestCase=${Boolean(resultData === null || resultData === void 0 ? void 0 : resultData.testCase)} hasTestSuite=${Boolean(resultData === null || resultData === void 0 ? void 0 : resultData.testSuite)}`);
|
|
4093
4013
|
}
|
|
@@ -4126,16 +4046,15 @@ class ResultDataProvider {
|
|
|
4126
4046
|
resultDataResponse.priority = resultData.priority;
|
|
4127
4047
|
break;
|
|
4128
4048
|
case 'testCaseResult':
|
|
4129
|
-
const outcome = this.getTestOutcome(resultData);
|
|
4130
4049
|
if (lastRunId === undefined || lastResultId === undefined) {
|
|
4131
4050
|
resultDataResponse.testCaseResult = {
|
|
4132
|
-
resultMessage: `${this.convertRunStatus(
|
|
4051
|
+
resultMessage: `${this.convertRunStatus(testOutcome)}`,
|
|
4133
4052
|
url: '',
|
|
4134
4053
|
};
|
|
4135
4054
|
}
|
|
4136
4055
|
else {
|
|
4137
4056
|
resultDataResponse.testCaseResult = {
|
|
4138
|
-
resultMessage: `${this.convertRunStatus(
|
|
4057
|
+
resultMessage: `${this.convertRunStatus(testOutcome)} in Run ${lastRunId}`,
|
|
4139
4058
|
url: `${this.orgUrl}${projectName}/_testManagement/runs?runId=${lastRunId}&_a=resultSummary&resultId=${lastResultId}`,
|
|
4140
4059
|
};
|
|
4141
4060
|
}
|