@elisra-devops/docgen-data-provider 1.87.0 → 1.89.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 +2 -2
- package/bin/modules/ResultDataProvider.d.ts +7 -5
- package/bin/modules/ResultDataProvider.js +240 -164
- 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 +2 -2
- package/src/modules/ResultDataProvider.ts +281 -167
- package/src/tests/modules/ResultDataProvider.test.ts +73 -19
package/package.json
CHANGED
|
@@ -13,13 +13,13 @@ export interface MewpExternalFileRef {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface MewpCoverageRequestOptions {
|
|
16
|
-
useRelFallback?: boolean;
|
|
17
16
|
externalBugsFile?: MewpExternalFileRef | null;
|
|
18
17
|
externalL3L4File?: MewpExternalFileRef | null;
|
|
18
|
+
debugMode?: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export interface MewpInternalValidationRequestOptions {
|
|
22
|
-
|
|
22
|
+
debugMode?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface MewpRequirementStepSummary {
|
|
@@ -446,8 +446,7 @@ export default class ResultDataProvider {
|
|
|
446
446
|
const testData = await this.fetchMewpScopedTestData(
|
|
447
447
|
testPlanId,
|
|
448
448
|
projectName,
|
|
449
|
-
selectedSuiteIds
|
|
450
|
-
!!options?.useRelFallback
|
|
449
|
+
selectedSuiteIds
|
|
451
450
|
);
|
|
452
451
|
|
|
453
452
|
const allRequirements = await this.fetchMewpL2Requirements(projectName);
|
|
@@ -549,6 +548,9 @@ export default class ResultDataProvider {
|
|
|
549
548
|
const parsedDefinitionStepsByTestCase = new Map<number, TestSteps[]>();
|
|
550
549
|
const testCaseStepsXmlMap = this.buildTestCaseStepsXmlMap(testData);
|
|
551
550
|
const runResults = await this.fetchAllResultDataTestReporter(testData, projectName, [], false, false);
|
|
551
|
+
if (options?.debugMode) {
|
|
552
|
+
this.logMewpRunScenarioDebugMatrix(runResults, `coverage plan=${testPlanId}`);
|
|
553
|
+
}
|
|
552
554
|
for (const runResult of runResults) {
|
|
553
555
|
const testCaseId = this.extractMewpTestCaseId(runResult);
|
|
554
556
|
const rawActionResults = Array.isArray(runResult?.iteration?.actionResults)
|
|
@@ -748,8 +750,7 @@ export default class ResultDataProvider {
|
|
|
748
750
|
const testData = await this.fetchMewpScopedTestData(
|
|
749
751
|
testPlanId,
|
|
750
752
|
projectName,
|
|
751
|
-
selectedSuiteIds
|
|
752
|
-
!!options?.useRelFallback
|
|
753
|
+
selectedSuiteIds
|
|
753
754
|
);
|
|
754
755
|
const allRequirements = await this.fetchMewpL2Requirements(projectName);
|
|
755
756
|
const linkedRequirementsByTestCase = await this.buildLinkedRequirementsByTestCase(
|
|
@@ -795,6 +796,16 @@ export default class ResultDataProvider {
|
|
|
795
796
|
`fromSuitePayload=${preloadedStepXmlCount} fromWorkItemFallback=${fallbackStepLoadStats.loadedFromFallback} ` +
|
|
796
797
|
`stepsXmlAvailable=${stepsXmlByTestCase.size} unresolved=${fallbackStepLoadStats.unresolvedCount}`
|
|
797
798
|
);
|
|
799
|
+
if (options?.debugMode) {
|
|
800
|
+
const debugRunResults = await this.fetchAllResultDataTestReporter(
|
|
801
|
+
testData,
|
|
802
|
+
projectName,
|
|
803
|
+
[],
|
|
804
|
+
false,
|
|
805
|
+
false
|
|
806
|
+
);
|
|
807
|
+
this.logMewpRunScenarioDebugMatrix(debugRunResults, `internal-validation plan=${testPlanId}`);
|
|
808
|
+
}
|
|
798
809
|
|
|
799
810
|
const validL2BaseKeys = new Set<string>([...requirementFamilies.keys()]);
|
|
800
811
|
const diagnostics = {
|
|
@@ -917,7 +928,9 @@ export default class ResultDataProvider {
|
|
|
917
928
|
for (const code of mentionedCodes) {
|
|
918
929
|
const hasSpecificSuffix = /-\d+$/.test(code);
|
|
919
930
|
if (hasSpecificSuffix) {
|
|
920
|
-
if (!linkedFullCodes.has(code))
|
|
931
|
+
if (!linkedFullCodes.has(code)) {
|
|
932
|
+
missingSpecificMentionedNoFamily.add(code);
|
|
933
|
+
}
|
|
921
934
|
} else if (!linkedBaseKeys.has(baseKey)) {
|
|
922
935
|
missingBaseWhenFamilyUncovered.add(baseKey);
|
|
923
936
|
}
|
|
@@ -984,7 +997,7 @@ export default class ResultDataProvider {
|
|
|
984
997
|
const groupedRequirementList = this.formatRequirementCodesGroupedByFamily(requirementIds);
|
|
985
998
|
return `${stepRef}: ${groupedRequirementList}`;
|
|
986
999
|
})
|
|
987
|
-
.join('
|
|
1000
|
+
.join('\n');
|
|
988
1001
|
const linkedButNotMentioned = this.formatRequirementCodesGroupedByFamily(sortedExtraLinked);
|
|
989
1002
|
const validationStatus: 'Pass' | 'Fail' =
|
|
990
1003
|
mentionedButNotLinked || linkedButNotMentioned ? 'Fail' : 'Pass';
|
|
@@ -1447,139 +1460,10 @@ export default class ResultDataProvider {
|
|
|
1447
1460
|
private async fetchMewpScopedTestData(
|
|
1448
1461
|
testPlanId: string,
|
|
1449
1462
|
projectName: string,
|
|
1450
|
-
selectedSuiteIds: number[] | undefined
|
|
1451
|
-
useRelFallback: boolean
|
|
1463
|
+
selectedSuiteIds: number[] | undefined
|
|
1452
1464
|
): Promise<any[]> {
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
return this.fetchTestData(suites, projectName, testPlanId, false);
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
const selectedSuites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
|
|
1459
|
-
const selectedRel = this.resolveMaxRelNumberFromSuites(selectedSuites);
|
|
1460
|
-
if (selectedRel <= 0) {
|
|
1461
|
-
return this.fetchTestData(selectedSuites, projectName, testPlanId, false);
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
const allSuites = await this.fetchTestSuites(testPlanId, projectName, undefined, true);
|
|
1465
|
-
const relScopedSuites = allSuites.filter((suite) => {
|
|
1466
|
-
const rel = this.extractRelNumberFromSuite(suite);
|
|
1467
|
-
return rel > 0 && rel <= selectedRel;
|
|
1468
|
-
});
|
|
1469
|
-
const suitesForFetch = relScopedSuites.length > 0 ? relScopedSuites : selectedSuites;
|
|
1470
|
-
const rawTestData = await this.fetchTestData(suitesForFetch, projectName, testPlanId, false);
|
|
1471
|
-
return this.reduceToLatestRelRunPerTestCase(rawTestData);
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
private extractRelNumberFromSuite(suite: any): number {
|
|
1475
|
-
const candidates = [
|
|
1476
|
-
suite?.suiteName,
|
|
1477
|
-
suite?.parentSuiteName,
|
|
1478
|
-
suite?.suitePath,
|
|
1479
|
-
suite?.testGroupName,
|
|
1480
|
-
];
|
|
1481
|
-
const pattern = /(?:^|[^a-z0-9])rel\s*([0-9]+)/i;
|
|
1482
|
-
for (const item of candidates) {
|
|
1483
|
-
const match = pattern.exec(String(item || ''));
|
|
1484
|
-
if (!match) continue;
|
|
1485
|
-
const parsed = Number(match[1]);
|
|
1486
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
1487
|
-
return parsed;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
return 0;
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
private resolveMaxRelNumberFromSuites(suites: any[]): number {
|
|
1494
|
-
let maxRel = 0;
|
|
1495
|
-
for (const suite of suites || []) {
|
|
1496
|
-
const rel = this.extractRelNumberFromSuite(suite);
|
|
1497
|
-
if (rel > maxRel) maxRel = rel;
|
|
1498
|
-
}
|
|
1499
|
-
return maxRel;
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
private reduceToLatestRelRunPerTestCase(testData: any[]): any[] {
|
|
1503
|
-
type Candidate = {
|
|
1504
|
-
point: any;
|
|
1505
|
-
rel: number;
|
|
1506
|
-
runId: number;
|
|
1507
|
-
resultId: number;
|
|
1508
|
-
hasRun: boolean;
|
|
1509
|
-
};
|
|
1510
|
-
|
|
1511
|
-
const candidatesByTestCase = new Map<number, Candidate[]>();
|
|
1512
|
-
const testCaseDefinitionById = new Map<number, any>();
|
|
1513
|
-
|
|
1514
|
-
for (const suite of testData || []) {
|
|
1515
|
-
const rel = this.extractRelNumberFromSuite(suite);
|
|
1516
|
-
const testPointsItems = Array.isArray(suite?.testPointsItems) ? suite.testPointsItems : [];
|
|
1517
|
-
const testCasesItems = Array.isArray(suite?.testCasesItems) ? suite.testCasesItems : [];
|
|
1518
|
-
|
|
1519
|
-
for (const testCase of testCasesItems) {
|
|
1520
|
-
const testCaseId = Number(testCase?.workItem?.id || testCase?.testCaseId || testCase?.id || 0);
|
|
1521
|
-
if (!Number.isFinite(testCaseId) || testCaseId <= 0) continue;
|
|
1522
|
-
if (!testCaseDefinitionById.has(testCaseId)) {
|
|
1523
|
-
testCaseDefinitionById.set(testCaseId, testCase);
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
|
|
1527
|
-
for (const point of testPointsItems) {
|
|
1528
|
-
const testCaseId = Number(point?.testCaseId || point?.testCase?.id || 0);
|
|
1529
|
-
if (!Number.isFinite(testCaseId) || testCaseId <= 0) continue;
|
|
1530
|
-
|
|
1531
|
-
const runId = Number(point?.lastRunId || 0);
|
|
1532
|
-
const resultId = Number(point?.lastResultId || 0);
|
|
1533
|
-
const hasRun = runId > 0 && resultId > 0;
|
|
1534
|
-
if (!candidatesByTestCase.has(testCaseId)) {
|
|
1535
|
-
candidatesByTestCase.set(testCaseId, []);
|
|
1536
|
-
}
|
|
1537
|
-
candidatesByTestCase.get(testCaseId)!.push({
|
|
1538
|
-
point,
|
|
1539
|
-
rel,
|
|
1540
|
-
runId,
|
|
1541
|
-
resultId,
|
|
1542
|
-
hasRun,
|
|
1543
|
-
});
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
|
|
1547
|
-
const selectedPoints: any[] = [];
|
|
1548
|
-
const selectedTestCaseIds = new Set<number>();
|
|
1549
|
-
for (const [testCaseId, candidates] of candidatesByTestCase.entries()) {
|
|
1550
|
-
const sorted = [...candidates].sort((a, b) => {
|
|
1551
|
-
if (a.hasRun !== b.hasRun) return a.hasRun ? -1 : 1;
|
|
1552
|
-
if (a.rel !== b.rel) return b.rel - a.rel;
|
|
1553
|
-
if (a.runId !== b.runId) return b.runId - a.runId;
|
|
1554
|
-
return b.resultId - a.resultId;
|
|
1555
|
-
});
|
|
1556
|
-
const chosen = sorted[0];
|
|
1557
|
-
if (!chosen?.point) continue;
|
|
1558
|
-
selectedPoints.push(chosen.point);
|
|
1559
|
-
selectedTestCaseIds.add(testCaseId);
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
const selectedTestCases: any[] = [];
|
|
1563
|
-
for (const testCaseId of selectedTestCaseIds) {
|
|
1564
|
-
const definition = testCaseDefinitionById.get(testCaseId);
|
|
1565
|
-
if (definition) {
|
|
1566
|
-
selectedTestCases.push(definition);
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
|
|
1570
|
-
return [
|
|
1571
|
-
{
|
|
1572
|
-
testSuiteId: 'MEWP_REL_SCOPED',
|
|
1573
|
-
suiteId: 'MEWP_REL_SCOPED',
|
|
1574
|
-
suiteName: 'MEWP Rel Scoped',
|
|
1575
|
-
parentSuiteId: '',
|
|
1576
|
-
parentSuiteName: '',
|
|
1577
|
-
suitePath: 'MEWP Rel Scoped',
|
|
1578
|
-
testGroupName: 'MEWP Rel Scoped',
|
|
1579
|
-
testPointsItems: selectedPoints,
|
|
1580
|
-
testCasesItems: selectedTestCases,
|
|
1581
|
-
},
|
|
1582
|
-
];
|
|
1465
|
+
const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
|
|
1466
|
+
return this.fetchTestData(suites, projectName, testPlanId, false);
|
|
1583
1467
|
}
|
|
1584
1468
|
|
|
1585
1469
|
private async loadExternalBugsByTestCase(
|
|
@@ -1923,16 +1807,6 @@ export default class ResultDataProvider {
|
|
|
1923
1807
|
return out;
|
|
1924
1808
|
}
|
|
1925
1809
|
|
|
1926
|
-
private extractRequirementCodesFromExpectedSteps(steps: TestSteps[], includeSuffix: boolean): Set<string> {
|
|
1927
|
-
const out = new Set<string>();
|
|
1928
|
-
for (const step of Array.isArray(steps) ? steps : []) {
|
|
1929
|
-
if (step?.isSharedStepTitle) continue;
|
|
1930
|
-
const codes = this.extractRequirementCodesFromExpectedText(step?.expected || '', includeSuffix);
|
|
1931
|
-
codes.forEach((code) => out.add(code));
|
|
1932
|
-
}
|
|
1933
|
-
return out;
|
|
1934
|
-
}
|
|
1935
|
-
|
|
1936
1810
|
private extractRequirementCodesFromExpectedText(text: string, includeSuffix: boolean): Set<string> {
|
|
1937
1811
|
const out = new Set<string>();
|
|
1938
1812
|
const source = this.normalizeRequirementStepText(text);
|
|
@@ -2775,7 +2649,7 @@ export default class ResultDataProvider {
|
|
|
2775
2649
|
if (sortedMembers.length <= 1) return sortedMembers[0];
|
|
2776
2650
|
return `${baseKey}: ${sortedMembers.join(', ')}`;
|
|
2777
2651
|
})
|
|
2778
|
-
.join('
|
|
2652
|
+
.join('\n');
|
|
2779
2653
|
}
|
|
2780
2654
|
|
|
2781
2655
|
private toMewpComparableText(value: any): string {
|
|
@@ -3096,6 +2970,140 @@ export default class ResultDataProvider {
|
|
|
3096
2970
|
return testCases;
|
|
3097
2971
|
}
|
|
3098
2972
|
|
|
2973
|
+
private attachSuiteTestCaseContextToPoints(testCasesItems: any[], testPointsItems: any[]): any[] {
|
|
2974
|
+
const points = Array.isArray(testPointsItems) ? testPointsItems : [];
|
|
2975
|
+
const testCases = Array.isArray(testCasesItems) ? testCasesItems : [];
|
|
2976
|
+
if (points.length === 0 || testCases.length === 0) return points;
|
|
2977
|
+
|
|
2978
|
+
const byTestCaseId = new Map<number, any>();
|
|
2979
|
+
for (const testCaseItem of testCases) {
|
|
2980
|
+
const testCaseId = Number(
|
|
2981
|
+
testCaseItem?.workItem?.id || testCaseItem?.testCaseId || testCaseItem?.id || 0
|
|
2982
|
+
);
|
|
2983
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0 || byTestCaseId.has(testCaseId)) continue;
|
|
2984
|
+
byTestCaseId.set(testCaseId, testCaseItem);
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
return points.map((point: any) => {
|
|
2988
|
+
const testCaseId = Number(point?.testCaseId || point?.testCase?.id || 0);
|
|
2989
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0) return point;
|
|
2990
|
+
const suiteTestCase = byTestCaseId.get(testCaseId);
|
|
2991
|
+
if (!suiteTestCase) return point;
|
|
2992
|
+
return { ...point, suiteTestCase };
|
|
2993
|
+
});
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
private extractWorkItemFieldsMap(workItemFields: any): Record<string, any> {
|
|
2997
|
+
const fields: Record<string, any> = {};
|
|
2998
|
+
if (!Array.isArray(workItemFields)) return fields;
|
|
2999
|
+
for (const field of workItemFields) {
|
|
3000
|
+
const keyCandidates = [field?.key, field?.name, field?.referenceName]
|
|
3001
|
+
.map((item) => String(item || '').trim())
|
|
3002
|
+
.filter((item) => !!item);
|
|
3003
|
+
for (const key of keyCandidates) {
|
|
3004
|
+
fields[key] = field?.value;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
return fields;
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
private resolveSuiteTestCaseRevision(testCaseItem: any): number {
|
|
3011
|
+
const revisionCandidates = [
|
|
3012
|
+
testCaseItem?.workItem?.rev,
|
|
3013
|
+
testCaseItem?.workItem?.revision,
|
|
3014
|
+
testCaseItem?.workItem?.version,
|
|
3015
|
+
testCaseItem?.workItem?.workItemRevision,
|
|
3016
|
+
testCaseItem?.workItem?.workItemVersion,
|
|
3017
|
+
testCaseItem?.revision,
|
|
3018
|
+
testCaseItem?.workItemRevision,
|
|
3019
|
+
testCaseItem?.workItemVersion,
|
|
3020
|
+
];
|
|
3021
|
+
for (const candidate of revisionCandidates) {
|
|
3022
|
+
const parsed = Number(candidate || 0);
|
|
3023
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
3024
|
+
}
|
|
3025
|
+
return 0;
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
private buildWorkItemSnapshotFromSuiteTestCase(
|
|
3029
|
+
testCaseItem: any,
|
|
3030
|
+
fallbackTestCaseId: number,
|
|
3031
|
+
fallbackTestCaseName: string = ''
|
|
3032
|
+
): any | null {
|
|
3033
|
+
if (!testCaseItem) return null;
|
|
3034
|
+
|
|
3035
|
+
const testCaseId = Number(
|
|
3036
|
+
testCaseItem?.workItem?.id || testCaseItem?.testCaseId || testCaseItem?.id || fallbackTestCaseId || 0
|
|
3037
|
+
);
|
|
3038
|
+
if (!Number.isFinite(testCaseId) || testCaseId <= 0) return null;
|
|
3039
|
+
|
|
3040
|
+
const workItem = testCaseItem?.workItem || {};
|
|
3041
|
+
const stepsXml = this.extractStepsXmlFromTestCaseItem(testCaseItem);
|
|
3042
|
+
const fieldsFromList = this.extractWorkItemFieldsMap(workItem?.workItemFields);
|
|
3043
|
+
const fieldsFromMap = workItem?.fields || {};
|
|
3044
|
+
const fields = {
|
|
3045
|
+
...fieldsFromList,
|
|
3046
|
+
...fieldsFromMap,
|
|
3047
|
+
};
|
|
3048
|
+
|
|
3049
|
+
if (!fields['System.Title']) {
|
|
3050
|
+
const title = String(
|
|
3051
|
+
testCaseItem?.testCaseName || workItem?.name || testCaseItem?.name || fallbackTestCaseName || ''
|
|
3052
|
+
).trim();
|
|
3053
|
+
if (title) {
|
|
3054
|
+
fields['System.Title'] = title;
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
if (stepsXml && !fields['Microsoft.VSTS.TCM.Steps']) {
|
|
3058
|
+
fields['Microsoft.VSTS.TCM.Steps'] = stepsXml;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
return {
|
|
3062
|
+
id: testCaseId,
|
|
3063
|
+
rev: this.resolveSuiteTestCaseRevision(testCaseItem) || 1,
|
|
3064
|
+
fields,
|
|
3065
|
+
relations: Array.isArray(workItem?.relations) ? workItem.relations : [],
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
private async fetchWorkItemByRevision(
|
|
3070
|
+
projectName: string,
|
|
3071
|
+
workItemId: number,
|
|
3072
|
+
revision: number,
|
|
3073
|
+
expandAll: boolean = false
|
|
3074
|
+
): Promise<any | null> {
|
|
3075
|
+
const id = Number(workItemId || 0);
|
|
3076
|
+
const rev = Number(revision || 0);
|
|
3077
|
+
if (!Number.isFinite(id) || id <= 0 || !Number.isFinite(rev) || rev <= 0) return null;
|
|
3078
|
+
|
|
3079
|
+
const expandParam = expandAll ? '?$expand=all' : '';
|
|
3080
|
+
const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}/revisions/${rev}${expandParam}`;
|
|
3081
|
+
try {
|
|
3082
|
+
return await TFSServices.getItemContent(url, this.token);
|
|
3083
|
+
} catch (error: any) {
|
|
3084
|
+
logger.warn(`Failed to fetch work item ${id} by revision ${rev}: ${error?.message || error}`);
|
|
3085
|
+
return null;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
private async fetchWorkItemLatest(
|
|
3090
|
+
projectName: string,
|
|
3091
|
+
workItemId: number,
|
|
3092
|
+
expandAll: boolean = false
|
|
3093
|
+
): Promise<any | null> {
|
|
3094
|
+
const id = Number(workItemId || 0);
|
|
3095
|
+
if (!Number.isFinite(id) || id <= 0) return null;
|
|
3096
|
+
|
|
3097
|
+
const expandParam = expandAll ? '?$expand=all' : '';
|
|
3098
|
+
const url = `${this.orgUrl}${projectName}/_apis/wit/workItems/${id}${expandParam}`;
|
|
3099
|
+
try {
|
|
3100
|
+
return await TFSServices.getItemContent(url, this.token);
|
|
3101
|
+
} catch (error: any) {
|
|
3102
|
+
logger.warn(`Failed to fetch latest work item ${id}: ${error?.message || error}`);
|
|
3103
|
+
return null;
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3099
3107
|
/**
|
|
3100
3108
|
* Fetches result data based on the Work Item Test Reporter.
|
|
3101
3109
|
*
|
|
@@ -3130,13 +3138,37 @@ export default class ResultDataProvider {
|
|
|
3130
3138
|
logger.warn(`Invalid run result ${runId} or result ${resultId}`);
|
|
3131
3139
|
return null;
|
|
3132
3140
|
}
|
|
3133
|
-
const
|
|
3134
|
-
const
|
|
3141
|
+
const suiteTestCaseItem = point?.suiteTestCase;
|
|
3142
|
+
const testCaseId = Number(
|
|
3143
|
+
point?.testCaseId || suiteTestCaseItem?.workItem?.id || suiteTestCaseItem?.testCaseId || 0
|
|
3144
|
+
);
|
|
3145
|
+
const suiteTestCaseRevision = this.resolveSuiteTestCaseRevision(suiteTestCaseItem);
|
|
3146
|
+
const fallbackSnapshot = this.buildWorkItemSnapshotFromSuiteTestCase(
|
|
3147
|
+
suiteTestCaseItem,
|
|
3148
|
+
testCaseId,
|
|
3149
|
+
String(point?.testCaseName || '')
|
|
3150
|
+
);
|
|
3151
|
+
let testCaseData = await this.fetchWorkItemByRevision(
|
|
3152
|
+
projectName,
|
|
3153
|
+
testCaseId,
|
|
3154
|
+
suiteTestCaseRevision,
|
|
3155
|
+
isTestReporter
|
|
3156
|
+
);
|
|
3157
|
+
if (!testCaseData) {
|
|
3158
|
+
testCaseData = fallbackSnapshot;
|
|
3159
|
+
}
|
|
3160
|
+
if (!testCaseData) {
|
|
3161
|
+
testCaseData = await this.fetchWorkItemLatest(projectName, testCaseId, isTestReporter);
|
|
3162
|
+
}
|
|
3163
|
+
if (!testCaseData) {
|
|
3164
|
+
logger.warn(`Could not resolve test case ${point.testCaseId} for runless point fallback.`);
|
|
3165
|
+
return null;
|
|
3166
|
+
}
|
|
3135
3167
|
const newResultData: PlainTestResult = {
|
|
3136
3168
|
id: 0,
|
|
3137
3169
|
outcome: point.outcome,
|
|
3138
|
-
revision: testCaseData?.rev || 1,
|
|
3139
|
-
testCase: { id:
|
|
3170
|
+
revision: Number(testCaseData?.rev || suiteTestCaseRevision || 1),
|
|
3171
|
+
testCase: { id: String(testCaseId), name: point.testCaseName },
|
|
3140
3172
|
state: testCaseData?.fields?.['System.State'] || 'Active',
|
|
3141
3173
|
priority: testCaseData?.fields?.['Microsoft.VSTS.TCM.Priority'] || 0,
|
|
3142
3174
|
createdDate: testCaseData?.fields?.['System.CreatedDate'] || '0001-01-01T00:00:00',
|
|
@@ -3184,8 +3216,8 @@ export default class ResultDataProvider {
|
|
|
3184
3216
|
selectedFieldSet.clear();
|
|
3185
3217
|
return {
|
|
3186
3218
|
...newResultData,
|
|
3187
|
-
stepsResultXml: testCaseData
|
|
3188
|
-
testCaseRevision: testCaseData
|
|
3219
|
+
stepsResultXml: testCaseData?.fields?.['Microsoft.VSTS.TCM.Steps'] || undefined,
|
|
3220
|
+
testCaseRevision: Number(testCaseData?.rev || suiteTestCaseRevision || 1),
|
|
3189
3221
|
filteredFields,
|
|
3190
3222
|
relatedRequirements,
|
|
3191
3223
|
relatedBugs,
|
|
@@ -3355,13 +3387,6 @@ export default class ResultDataProvider {
|
|
|
3355
3387
|
);
|
|
3356
3388
|
}
|
|
3357
3389
|
|
|
3358
|
-
private isRunResultDebugEnabled(): boolean {
|
|
3359
|
-
return (
|
|
3360
|
-
String(process?.env?.DOCGEN_DEBUG_RUNRESULT ?? '').toLowerCase() === 'true' ||
|
|
3361
|
-
String(process?.env?.DOCGEN_DEBUG_RUNRESULT ?? '') === '1'
|
|
3362
|
-
);
|
|
3363
|
-
}
|
|
3364
|
-
|
|
3365
3390
|
private extractCommentText(comment: AdoWorkItemComment): string {
|
|
3366
3391
|
const rendered = comment?.renderedText;
|
|
3367
3392
|
// In Azure DevOps the `renderedText` field can be present but empty ("") even when `text` is populated.
|
|
@@ -3848,6 +3873,79 @@ export default class ResultDataProvider {
|
|
|
3848
3873
|
return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId);
|
|
3849
3874
|
}
|
|
3850
3875
|
|
|
3876
|
+
private logMewpRunScenarioDebugMatrix(runResults: any[], contextLabel: string): void {
|
|
3877
|
+
const results = Array.isArray(runResults) ? runResults : [];
|
|
3878
|
+
const matrix = {
|
|
3879
|
+
total: results.length,
|
|
3880
|
+
passOrFailWithActionResults: 0,
|
|
3881
|
+
runWithNoActionResults: 0,
|
|
3882
|
+
notApplicable: 0,
|
|
3883
|
+
noRunHistoryActive: 0,
|
|
3884
|
+
other: 0,
|
|
3885
|
+
};
|
|
3886
|
+
const samples = {
|
|
3887
|
+
passOrFailWithActionResults: [] as number[],
|
|
3888
|
+
runWithNoActionResults: [] as number[],
|
|
3889
|
+
notApplicable: [] as number[],
|
|
3890
|
+
noRunHistoryActive: [] as number[],
|
|
3891
|
+
other: [] as number[],
|
|
3892
|
+
};
|
|
3893
|
+
|
|
3894
|
+
const pushSample = (bucket: keyof typeof samples, id: number) => {
|
|
3895
|
+
if (!Number.isFinite(id) || id <= 0) return;
|
|
3896
|
+
if (samples[bucket].length >= 5) return;
|
|
3897
|
+
samples[bucket].push(id);
|
|
3898
|
+
};
|
|
3899
|
+
|
|
3900
|
+
for (const item of results) {
|
|
3901
|
+
const testCaseId = Number(item?.testCaseId || item?.testCase?.id || 0);
|
|
3902
|
+
const hasRun = Number(item?.lastRunId || 0) > 0 && Number(item?.lastResultId || 0) > 0;
|
|
3903
|
+
const rawOutcome = String(item?._debugTestOutcome || '').trim().toLowerCase();
|
|
3904
|
+
const rawState = String(item?._debugTestCaseState || '').trim().toLowerCase();
|
|
3905
|
+
const originalActionResultsCount = Number(item?._debugOriginalActionResultsCount ?? -1);
|
|
3906
|
+
|
|
3907
|
+
if (rawOutcome === 'notapplicable' || rawOutcome === 'not applicable') {
|
|
3908
|
+
matrix.notApplicable += 1;
|
|
3909
|
+
pushSample('notApplicable', testCaseId);
|
|
3910
|
+
continue;
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
if (hasRun && (rawOutcome === 'passed' || rawOutcome === 'failed') && originalActionResultsCount > 0) {
|
|
3914
|
+
matrix.passOrFailWithActionResults += 1;
|
|
3915
|
+
pushSample('passOrFailWithActionResults', testCaseId);
|
|
3916
|
+
continue;
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
if (hasRun && originalActionResultsCount === 0) {
|
|
3920
|
+
matrix.runWithNoActionResults += 1;
|
|
3921
|
+
pushSample('runWithNoActionResults', testCaseId);
|
|
3922
|
+
continue;
|
|
3923
|
+
}
|
|
3924
|
+
|
|
3925
|
+
if (!hasRun && rawState === 'active') {
|
|
3926
|
+
matrix.noRunHistoryActive += 1;
|
|
3927
|
+
pushSample('noRunHistoryActive', testCaseId);
|
|
3928
|
+
continue;
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
matrix.other += 1;
|
|
3932
|
+
pushSample('other', testCaseId);
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
logger.info(
|
|
3936
|
+
`MEWP run debug matrix (${contextLabel}): total=${matrix.total}; ` +
|
|
3937
|
+
`passOrFailWithActionResults=${matrix.passOrFailWithActionResults}; ` +
|
|
3938
|
+
`runWithNoActionResults=${matrix.runWithNoActionResults}; ` +
|
|
3939
|
+
`notApplicable=${matrix.notApplicable}; ` +
|
|
3940
|
+
`noRunHistoryActive=${matrix.noRunHistoryActive}; other=${matrix.other}; ` +
|
|
3941
|
+
`samplePassFail=${samples.passOrFailWithActionResults.join(',') || '-'}; ` +
|
|
3942
|
+
`sampleNoAction=${samples.runWithNoActionResults.join(',') || '-'}; ` +
|
|
3943
|
+
`sampleNA=${samples.notApplicable.join(',') || '-'}; ` +
|
|
3944
|
+
`sampleNoRunActive=${samples.noRunHistoryActive.join(',') || '-'}; ` +
|
|
3945
|
+
`sampleOther=${samples.other.join(',') || '-'}`
|
|
3946
|
+
);
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3851
3949
|
/**
|
|
3852
3950
|
* Converts a run status string into a human-readable format.
|
|
3853
3951
|
*
|
|
@@ -4123,9 +4221,17 @@ export default class ResultDataProvider {
|
|
|
4123
4221
|
suite.testSuiteId
|
|
4124
4222
|
);
|
|
4125
4223
|
const testCaseIds = testCasesItems.map((testCase: any) => testCase.workItem.id);
|
|
4126
|
-
const
|
|
4127
|
-
? await this.fetchTestPoints(
|
|
4224
|
+
const rawTestPointsItems = !fetchCrossPlans
|
|
4225
|
+
? await this.fetchTestPoints(
|
|
4226
|
+
projectName,
|
|
4227
|
+
testPlanId,
|
|
4228
|
+
suite.testSuiteId
|
|
4229
|
+
)
|
|
4128
4230
|
: await this.fetchCrossTestPoints(projectName, testCaseIds);
|
|
4231
|
+
const testPointsItems = this.attachSuiteTestCaseContextToPoints(
|
|
4232
|
+
testCasesItems,
|
|
4233
|
+
rawTestPointsItems
|
|
4234
|
+
);
|
|
4129
4235
|
|
|
4130
4236
|
return { ...suite, testPointsItems, testCasesItems };
|
|
4131
4237
|
} catch (error: any) {
|
|
@@ -4275,6 +4381,11 @@ export default class ResultDataProvider {
|
|
|
4275
4381
|
resultData.iterationDetails.push(iteration);
|
|
4276
4382
|
}
|
|
4277
4383
|
|
|
4384
|
+
const originalActionResultsCount = Array.isArray(iteration?.actionResults)
|
|
4385
|
+
? iteration.actionResults.length
|
|
4386
|
+
: 0;
|
|
4387
|
+
resultData._debugOriginalActionResultsCount = originalActionResultsCount;
|
|
4388
|
+
|
|
4278
4389
|
if (resultData.stepsResultXml && iteration) {
|
|
4279
4390
|
const actionResults = Array.isArray(iteration.actionResults) ? iteration.actionResults : [];
|
|
4280
4391
|
const actionResultsWithSharedModels = actionResults.filter(
|
|
@@ -4987,6 +5098,7 @@ export default class ResultDataProvider {
|
|
|
4987
5098
|
resultData.iterationDetails?.length > 0
|
|
4988
5099
|
? resultData.iterationDetails[resultData.iterationDetails?.length - 1]
|
|
4989
5100
|
: undefined;
|
|
5101
|
+
const debugOutcome = this.getTestOutcome(resultData);
|
|
4990
5102
|
|
|
4991
5103
|
if (!resultData?.testCase || !resultData?.testSuite) {
|
|
4992
5104
|
logger.debug(
|
|
@@ -5020,6 +5132,9 @@ export default class ResultDataProvider {
|
|
|
5020
5132
|
relatedCRs: resultData.relatedCRs || undefined,
|
|
5021
5133
|
lastRunResult: undefined as any,
|
|
5022
5134
|
customFields: {}, // Create an object to store custom fields
|
|
5135
|
+
_debugTestOutcome: debugOutcome,
|
|
5136
|
+
_debugTestCaseState: String(resultData?.state || ''),
|
|
5137
|
+
_debugOriginalActionResultsCount: Number(resultData?._debugOriginalActionResultsCount ?? -1),
|
|
5023
5138
|
};
|
|
5024
5139
|
|
|
5025
5140
|
// Process all custom fields from resultData.filteredFields
|
|
@@ -5039,15 +5154,14 @@ export default class ResultDataProvider {
|
|
|
5039
5154
|
resultDataResponse.priority = resultData.priority;
|
|
5040
5155
|
break;
|
|
5041
5156
|
case 'testCaseResult':
|
|
5042
|
-
const outcome = this.getTestOutcome(resultData);
|
|
5043
5157
|
if (lastRunId === undefined || lastResultId === undefined) {
|
|
5044
5158
|
resultDataResponse.testCaseResult = {
|
|
5045
|
-
resultMessage: `${this.convertRunStatus(
|
|
5159
|
+
resultMessage: `${this.convertRunStatus(debugOutcome)}`,
|
|
5046
5160
|
url: '',
|
|
5047
5161
|
};
|
|
5048
5162
|
} else {
|
|
5049
5163
|
resultDataResponse.testCaseResult = {
|
|
5050
|
-
resultMessage: `${this.convertRunStatus(
|
|
5164
|
+
resultMessage: `${this.convertRunStatus(debugOutcome)} in Run ${lastRunId}`,
|
|
5051
5165
|
url: `${this.orgUrl}${projectName}/_testManagement/runs?runId=${lastRunId}&_a=resultSummary&resultId=${lastResultId}`,
|
|
5052
5166
|
};
|
|
5053
5167
|
}
|