@elisra-devops/docgen-data-provider 1.22.0 → 1.24.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/helpers/helper.js.map +1 -1
- package/bin/helpers/test/tfs.test.d.ts +1 -0
- package/bin/helpers/test/tfs.test.js +613 -0
- package/bin/helpers/test/tfs.test.js.map +1 -0
- package/bin/helpers/tfs.js +1 -1
- package/bin/helpers/tfs.js.map +1 -1
- package/bin/modules/GitDataProvider.js.map +1 -1
- package/bin/modules/PipelinesDataProvider.js +3 -2
- package/bin/modules/PipelinesDataProvider.js.map +1 -1
- package/bin/modules/ResultDataProvider.d.ts +200 -17
- package/bin/modules/ResultDataProvider.js +628 -195
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/modules/TestDataProvider.js +7 -7
- package/bin/modules/TestDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.d.ts +1 -1
- package/bin/modules/TicketsDataProvider.js +3 -2
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/modules/test/JfrogDataProvider.test.d.ts +1 -0
- package/bin/modules/test/JfrogDataProvider.test.js +110 -0
- package/bin/modules/test/JfrogDataProvider.test.js.map +1 -0
- package/bin/modules/test/ResultDataProvider.test.d.ts +1 -0
- package/bin/modules/test/ResultDataProvider.test.js +478 -0
- package/bin/modules/test/ResultDataProvider.test.js.map +1 -0
- package/bin/modules/test/gitDataProvider.test.js +424 -120
- package/bin/modules/test/gitDataProvider.test.js.map +1 -1
- package/bin/modules/test/managmentDataProvider.test.js +283 -28
- package/bin/modules/test/managmentDataProvider.test.js.map +1 -1
- package/bin/modules/test/pipelineDataProvider.test.js +229 -45
- package/bin/modules/test/pipelineDataProvider.test.js.map +1 -1
- package/bin/modules/test/testDataProvider.test.js +225 -81
- package/bin/modules/test/testDataProvider.test.js.map +1 -1
- package/bin/modules/test/ticketsDataProvider.test.js +310 -82
- package/bin/modules/test/ticketsDataProvider.test.js.map +1 -1
- package/package.json +1 -1
- package/src/helpers/helper.ts +16 -14
- package/src/helpers/test/tfs.test.ts +748 -0
- package/src/helpers/tfs.ts +1 -1
- package/src/modules/GitDataProvider.ts +10 -10
- package/src/modules/PipelinesDataProvider.ts +2 -2
- package/src/modules/ResultDataProvider.ts +834 -260
- package/src/modules/TestDataProvider.ts +8 -8
- package/src/modules/TicketsDataProvider.ts +5 -9
- package/src/modules/test/JfrogDataProvider.test.ts +171 -0
- package/src/modules/test/ResultDataProvider.test.ts +581 -0
- package/src/modules/test/gitDataProvider.test.ts +671 -187
- package/src/modules/test/managmentDataProvider.test.ts +386 -26
- package/src/modules/test/pipelineDataProvider.test.ts +281 -52
- package/src/modules/test/testDataProvider.test.ts +307 -105
- package/src/modules/test/ticketsDataProvider.test.ts +425 -129
|
@@ -15,6 +15,28 @@ const tfs_1 = require("../helpers/tfs");
|
|
|
15
15
|
const logger_1 = require("../utils/logger");
|
|
16
16
|
const testStepParserHelper_1 = require("../utils/testStepParserHelper");
|
|
17
17
|
const pLimit = require('p-limit');
|
|
18
|
+
/**
|
|
19
|
+
* Provides methods to fetch, process, and summarize test data from Azure DevOps.
|
|
20
|
+
*
|
|
21
|
+
* This class includes functionalities for:
|
|
22
|
+
* - Fetching test suites, test points, and test cases.
|
|
23
|
+
* - Aligning test steps with iterations and generating detailed results.
|
|
24
|
+
* - Fetching result data based on work items and test runs.
|
|
25
|
+
* - Summarizing test group results, test results, and detailed results.
|
|
26
|
+
* - Fetching linked work items and open PCRs (Problem Change Requests).
|
|
27
|
+
* - Generating test logs and mapping attachments for download.
|
|
28
|
+
* - Supporting test reporter functionalities with customizable field selection and filtering.
|
|
29
|
+
*
|
|
30
|
+
* Key Features:
|
|
31
|
+
* - Hierarchical and flat test suite processing.
|
|
32
|
+
* - Step-level and test-level result alignment.
|
|
33
|
+
* - Customizable result summaries and detailed reports.
|
|
34
|
+
* - Integration with Azure DevOps APIs for fetching and processing test data.
|
|
35
|
+
* - Support for additional configurations like open PCRs, test logs, and step execution analysis.
|
|
36
|
+
*
|
|
37
|
+
* Usage:
|
|
38
|
+
* Instantiate the class with the organization URL and token, and use the provided methods to fetch and process test data.
|
|
39
|
+
*/
|
|
18
40
|
class ResultDataProvider {
|
|
19
41
|
constructor(orgUrl, token) {
|
|
20
42
|
this.orgUrl = '';
|
|
@@ -41,7 +63,200 @@ class ResultDataProvider {
|
|
|
41
63
|
this.testStepParserHelper = new testStepParserHelper_1.default(orgUrl, token);
|
|
42
64
|
}
|
|
43
65
|
/**
|
|
44
|
-
*
|
|
66
|
+
* Combines the results of test group result summary, test results summary, and detailed results summary into a single key-value pair array.
|
|
67
|
+
*/
|
|
68
|
+
async getCombinedResultsSummary(testPlanId, projectName, selectedSuiteIds, addConfiguration = false, isHierarchyGroupName = false, includeOpenPCRs = false, includeTestLog = false, stepExecution, stepAnalysis, includeHardCopyRun = false) {
|
|
69
|
+
const combinedResults = [];
|
|
70
|
+
try {
|
|
71
|
+
// Fetch test suites
|
|
72
|
+
const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, isHierarchyGroupName);
|
|
73
|
+
// Prepare test data for summaries
|
|
74
|
+
const testPointsPromises = suites.map((suite) => this.limit(() => this.fetchTestPoints(projectName, testPlanId, suite.testSuiteId)
|
|
75
|
+
.then((testPointsItems) => (Object.assign(Object.assign({}, suite), { testPointsItems })))
|
|
76
|
+
.catch((error) => {
|
|
77
|
+
logger_1.default.error(`Error occurred for suite ${suite.testSuiteId}: ${error.message}`);
|
|
78
|
+
return Object.assign(Object.assign({}, suite), { testPointsItems: [] });
|
|
79
|
+
})));
|
|
80
|
+
const testPoints = await Promise.all(testPointsPromises);
|
|
81
|
+
// 1. Calculate Test Group Result Summary
|
|
82
|
+
const summarizedResults = testPoints
|
|
83
|
+
.filter((testPoint) => testPoint.testPointsItems && testPoint.testPointsItems.length > 0)
|
|
84
|
+
.map((testPoint) => {
|
|
85
|
+
const groupResultSummary = this.calculateGroupResultSummary(testPoint.testPointsItems || [], includeHardCopyRun);
|
|
86
|
+
return Object.assign(Object.assign({}, testPoint), { groupResultSummary });
|
|
87
|
+
});
|
|
88
|
+
const totalSummary = this.calculateTotalSummary(summarizedResults, includeHardCopyRun);
|
|
89
|
+
const testGroupArray = summarizedResults.map((item) => (Object.assign({ testGroupName: item.testGroupName }, item.groupResultSummary)));
|
|
90
|
+
testGroupArray.push(Object.assign({ testGroupName: 'Total' }, totalSummary));
|
|
91
|
+
// Add test group result summary to combined results
|
|
92
|
+
combinedResults.push({
|
|
93
|
+
contentControl: 'test-group-summary-content-control',
|
|
94
|
+
data: testGroupArray,
|
|
95
|
+
skin: 'test-result-test-group-summary-table',
|
|
96
|
+
});
|
|
97
|
+
// 2. Calculate Test Results Summary
|
|
98
|
+
const flattenedTestPoints = this.flattenTestPoints(testPoints);
|
|
99
|
+
const testResultsSummary = flattenedTestPoints.map((testPoint) => this.formatTestResult(testPoint, addConfiguration, includeHardCopyRun));
|
|
100
|
+
// Add test results summary to combined results
|
|
101
|
+
combinedResults.push({
|
|
102
|
+
contentControl: 'test-result-summary-content-control',
|
|
103
|
+
data: testResultsSummary,
|
|
104
|
+
skin: 'test-result-table',
|
|
105
|
+
});
|
|
106
|
+
// 3. Calculate Detailed Results Summary
|
|
107
|
+
const testData = await this.fetchTestData(suites, projectName, testPlanId);
|
|
108
|
+
const runResults = await this.fetchAllResultData(testData, projectName);
|
|
109
|
+
const detailedStepResultsSummary = this.alignStepsWithIterations(testData, runResults);
|
|
110
|
+
//Filter out all the results with no comment
|
|
111
|
+
const filteredDetailedResults = detailedStepResultsSummary.filter((result) => result && (result.stepComments !== '' || result.stepStatus === 'Failed'));
|
|
112
|
+
// Add detailed results summary to combined results
|
|
113
|
+
combinedResults.push({
|
|
114
|
+
contentControl: 'detailed-test-result-content-control',
|
|
115
|
+
data: !includeHardCopyRun ? filteredDetailedResults : [],
|
|
116
|
+
skin: 'detailed-test-result-table',
|
|
117
|
+
});
|
|
118
|
+
if (includeOpenPCRs) {
|
|
119
|
+
//5. Open PCRs data (only if enabled)
|
|
120
|
+
await this.fetchOpenPcrData(testResultsSummary, projectName, combinedResults);
|
|
121
|
+
}
|
|
122
|
+
//6. Test Log (only if enabled)
|
|
123
|
+
if (includeTestLog) {
|
|
124
|
+
this.fetchTestLogData(flattenedTestPoints, combinedResults);
|
|
125
|
+
}
|
|
126
|
+
if (stepAnalysis && stepAnalysis.isEnabled) {
|
|
127
|
+
const mappedAnalysisData = runResults.filter((result) => {
|
|
128
|
+
var _a, _b, _c;
|
|
129
|
+
return result.comment ||
|
|
130
|
+
((_b = (_a = result.iteration) === null || _a === void 0 ? void 0 : _a.attachments) === null || _b === void 0 ? void 0 : _b.length) > 0 ||
|
|
131
|
+
((_c = result.analysisAttachments) === null || _c === void 0 ? void 0 : _c.length) > 0;
|
|
132
|
+
});
|
|
133
|
+
const mappedAnalysisResultData = stepAnalysis.generateRunAttachments.isEnabled
|
|
134
|
+
? this.mapAttachmentsUrl(mappedAnalysisData, projectName)
|
|
135
|
+
: mappedAnalysisData;
|
|
136
|
+
if ((mappedAnalysisResultData === null || mappedAnalysisResultData === void 0 ? void 0 : mappedAnalysisResultData.length) > 0) {
|
|
137
|
+
combinedResults.push({
|
|
138
|
+
contentControl: 'appendix-a-content-control',
|
|
139
|
+
data: mappedAnalysisResultData,
|
|
140
|
+
skin: 'step-analysis-appendix-skin',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (stepExecution && stepExecution.isEnabled) {
|
|
145
|
+
const mappedAnalysisData = stepExecution.generateAttachments.isEnabled &&
|
|
146
|
+
stepExecution.generateAttachments.runAttachmentMode !== 'planOnly'
|
|
147
|
+
? runResults.filter((result) => { var _a, _b; return ((_b = (_a = result.iteration) === null || _a === void 0 ? void 0 : _a.attachments) === null || _b === void 0 ? void 0 : _b.length) > 0; })
|
|
148
|
+
: [];
|
|
149
|
+
const mappedAnalysisResultData = mappedAnalysisData.length > 0 ? this.mapAttachmentsUrl(mappedAnalysisData, projectName) : [];
|
|
150
|
+
const mappedDetailedResults = this.mapStepResultsForExecutionAppendix(detailedStepResultsSummary, mappedAnalysisResultData);
|
|
151
|
+
combinedResults.push({
|
|
152
|
+
contentControl: 'appendix-b-content-control',
|
|
153
|
+
data: mappedDetailedResults,
|
|
154
|
+
skin: 'step-execution-appendix-skin',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return combinedResults;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
logger_1.default.error(`Error during getCombinedResultsSummary: ${error.message}`);
|
|
161
|
+
if (error.response) {
|
|
162
|
+
logger_1.default.error(`Response Data: ${JSON.stringify(error.response.data)}`);
|
|
163
|
+
}
|
|
164
|
+
// Ensure the error is rethrown to propagate it correctly
|
|
165
|
+
throw new Error(error.message || 'Unknown error occurred during getCombinedResultsSummary');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Fetches and processes test reporter results for a given test plan and project.
|
|
170
|
+
*
|
|
171
|
+
* @param testPlanId - The ID of the test plan to fetch results for.
|
|
172
|
+
* @param projectName - The name of the project associated with the test plan.
|
|
173
|
+
* @param selectedSuiteIds - An array of suite IDs to filter the test results.
|
|
174
|
+
* @param selectedFields - An array of field names to include in the results.
|
|
175
|
+
* @param enableRunStepStatusFilter - A flag to enable filtering out test steps with a "Not Run" status.
|
|
176
|
+
* @returns A promise that resolves to an array of test reporter results, formatted for use in a test-reporter-table.
|
|
177
|
+
*
|
|
178
|
+
* @throws Will log an error if any step in the process fails.
|
|
179
|
+
*/
|
|
180
|
+
async getTestReporterResults(testPlanId, projectName, selectedSuiteIds, selectedFields, enableRunStepStatusFilter) {
|
|
181
|
+
const fetchedTestResults = [];
|
|
182
|
+
logger_1.default.debug(`Fetching test reporter results for test plan ID: ${testPlanId}, project name: ${projectName}`);
|
|
183
|
+
logger_1.default.debug(`Selected suite IDs: ${selectedSuiteIds}`);
|
|
184
|
+
try {
|
|
185
|
+
const plan = await this.fetchTestPlanName(testPlanId, projectName);
|
|
186
|
+
const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, true);
|
|
187
|
+
const testData = await this.fetchTestData(suites, projectName, testPlanId);
|
|
188
|
+
const runResults = await this.fetchAllResultDataTestReporter(testData, projectName, selectedFields);
|
|
189
|
+
const testReporterData = this.alignStepsWithIterationsTestReporter(testData, runResults, selectedFields);
|
|
190
|
+
const isNotRunStep = (result) => {
|
|
191
|
+
return result && result.stepStatus === 'Not Run';
|
|
192
|
+
};
|
|
193
|
+
// Apply filters sequentially based on enabled flags
|
|
194
|
+
let filteredResults = testReporterData;
|
|
195
|
+
// filter: Test step run status
|
|
196
|
+
if (enableRunStepStatusFilter) {
|
|
197
|
+
filteredResults = filteredResults.filter((result) => !isNotRunStep(result));
|
|
198
|
+
}
|
|
199
|
+
// Use the final filtered results
|
|
200
|
+
fetchedTestResults.push({
|
|
201
|
+
contentControl: 'test-reporter-table',
|
|
202
|
+
customName: `Test Results for ${plan}`,
|
|
203
|
+
data: filteredResults || [],
|
|
204
|
+
skin: 'test-reporter-table',
|
|
205
|
+
});
|
|
206
|
+
return fetchedTestResults;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
logger_1.default.error(`Error during getTestReporterResults: ${error.message}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Mapping each attachment to a proper URL for downloading it
|
|
214
|
+
* @param runResults Array of run results
|
|
215
|
+
*/
|
|
216
|
+
mapAttachmentsUrl(runResults, project) {
|
|
217
|
+
return runResults.map((result) => {
|
|
218
|
+
var _a;
|
|
219
|
+
if (!result.iteration) {
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
const { iteration, analysisAttachments } = result, restResult = __rest(result, ["iteration", "analysisAttachments"]);
|
|
223
|
+
//add downloadUri field for each attachment
|
|
224
|
+
const baseDownloadUrl = `${this.orgUrl}${project}/_apis/test/runs/${result.lastRunId}/results/${result.lastResultId}/attachments`;
|
|
225
|
+
if (iteration && ((_a = iteration.attachments) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
226
|
+
const { attachments, actionResults } = iteration, restOfIteration = __rest(iteration, ["attachments", "actionResults"]);
|
|
227
|
+
const attachmentPathToIndexMap = this.CreateAttachmentPathIndexMap(actionResults);
|
|
228
|
+
const mappedAttachments = attachments.map((attachment) => (Object.assign(Object.assign({}, attachment), { stepNo: attachmentPathToIndexMap.has(attachment.actionPath)
|
|
229
|
+
? attachmentPathToIndexMap.get(attachment.actionPath)
|
|
230
|
+
: undefined, downloadUrl: `${baseDownloadUrl}/${attachment.id}/${attachment.name}` })));
|
|
231
|
+
restResult.iteration = Object.assign(Object.assign({}, restOfIteration), { attachments: mappedAttachments });
|
|
232
|
+
}
|
|
233
|
+
if (analysisAttachments && analysisAttachments.length > 0) {
|
|
234
|
+
restResult.analysisAttachments = analysisAttachments.map((attachment) => (Object.assign(Object.assign({}, attachment), { downloadUrl: `${baseDownloadUrl}/${attachment.id}/${attachment.fileName}` })));
|
|
235
|
+
}
|
|
236
|
+
return Object.assign({}, restResult);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async fetchTestPlanName(testPlanId, teamProject) {
|
|
240
|
+
try {
|
|
241
|
+
const url = `${this.orgUrl}${teamProject}/_apis/testplan/Plans/${testPlanId}?api-version=5.1`;
|
|
242
|
+
const testPlan = await tfs_1.TFSServices.getItemContent(url, this.token);
|
|
243
|
+
return testPlan.name;
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
logger_1.default.error(`Error during fetching Test Plan Name: ${error.message}`);
|
|
247
|
+
return '';
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Fetches test suites for a given test plan and project, optionally filtering by selected suite IDs.
|
|
252
|
+
*
|
|
253
|
+
* @param testPlanId - The ID of the test plan to fetch suites for.
|
|
254
|
+
* @param projectName - The name of the project containing the test plan.
|
|
255
|
+
* @param selectedSuiteIds - An optional array of suite IDs to filter the results by.
|
|
256
|
+
* @param isHierarchyGroupName - A flag indicating whether to build the test group name hierarchically. Defaults to `true`.
|
|
257
|
+
* @returns A promise that resolves to an array of objects containing `testSuiteId` and `testGroupName`.
|
|
258
|
+
* Returns an empty array if no test suites are found or an error occurs.
|
|
259
|
+
* @throws Will log an error message if fetching test suites fails.
|
|
45
260
|
*/
|
|
46
261
|
async fetchTestSuites(testPlanId, projectName, selectedSuiteIds, isHierarchyGroupName = true) {
|
|
47
262
|
try {
|
|
@@ -117,6 +332,8 @@ class ResultDataProvider {
|
|
|
117
332
|
currentSuite = parentSuite;
|
|
118
333
|
}
|
|
119
334
|
const parts = path.split('/');
|
|
335
|
+
if (parts.length - 1 === 1)
|
|
336
|
+
return parts[1];
|
|
120
337
|
return parts.length > 3
|
|
121
338
|
? `${parts[1]}/.../${parts[parts.length - 1]}`
|
|
122
339
|
: `${parts[1]}/${parts[parts.length - 1]}`;
|
|
@@ -128,7 +345,7 @@ class ResultDataProvider {
|
|
|
128
345
|
try {
|
|
129
346
|
const url = `${this.orgUrl}${projectName}/_apis/testplan/Plans/${testPlanId}/Suites/${testSuiteId}/TestPoint?includePointDetails=true`;
|
|
130
347
|
const { value: testPoints, count } = await tfs_1.TFSServices.getItemContent(url, this.token);
|
|
131
|
-
return count !== 0 ? testPoints.map(this.mapTestPoint) : [];
|
|
348
|
+
return count !== 0 ? testPoints.map((testPoint) => this.mapTestPoint(testPoint, projectName)) : [];
|
|
132
349
|
}
|
|
133
350
|
catch (error) {
|
|
134
351
|
logger_1.default.error(`Error during fetching Test Points: ${error.message}`);
|
|
@@ -138,11 +355,12 @@ class ResultDataProvider {
|
|
|
138
355
|
/**
|
|
139
356
|
* Maps raw test point data to a simplified object.
|
|
140
357
|
*/
|
|
141
|
-
mapTestPoint(testPoint) {
|
|
358
|
+
mapTestPoint(testPoint, projectName) {
|
|
142
359
|
var _a, _b, _c, _d, _e;
|
|
143
360
|
return {
|
|
144
361
|
testCaseId: testPoint.testCaseReference.id,
|
|
145
362
|
testCaseName: testPoint.testCaseReference.name,
|
|
363
|
+
testCaseUrl: `${this.orgUrl}${projectName}/_workitems/edit/${testPoint.testCaseReference.id}`,
|
|
146
364
|
configurationName: (_a = testPoint.configuration) === null || _a === void 0 ? void 0 : _a.name,
|
|
147
365
|
outcome: ((_b = testPoint.results) === null || _b === void 0 ? void 0 : _b.outcome) || 'Not Run',
|
|
148
366
|
lastRunId: (_c = testPoint.results) === null || _c === void 0 ? void 0 : _c.lastTestRunId,
|
|
@@ -159,25 +377,114 @@ class ResultDataProvider {
|
|
|
159
377
|
return testCases;
|
|
160
378
|
}
|
|
161
379
|
/**
|
|
162
|
-
* Fetches
|
|
380
|
+
* Fetches result data based on a work item base (WiBase) for a specific test run and result.
|
|
381
|
+
*
|
|
382
|
+
* @param projectName - The name of the project in Azure DevOps.
|
|
383
|
+
* @param runId - The ID of the test run.
|
|
384
|
+
* @param resultId - The ID of the test result.
|
|
385
|
+
* @param options - Optional parameters for customizing the data retrieval.
|
|
386
|
+
* @param options.expandWorkItem - If true, expands all fields of the work item.
|
|
387
|
+
* @param options.selectedFields - An array of field names to filter the work item fields.
|
|
388
|
+
* @param options.processRelatedRequirements - If true, processes related requirements linked to the work item.
|
|
389
|
+
* @param options.includeFullErrorStack - If true, includes the full error stack in the logs when an error occurs.
|
|
390
|
+
* @returns A promise that resolves to an object containing the fetched result data, including:
|
|
391
|
+
* - `stepsResultXml`: The test steps result in XML format.
|
|
392
|
+
* - `analysisAttachments`: Attachments related to the test result analysis.
|
|
393
|
+
* - `testCaseRevision`: The revision number of the test case.
|
|
394
|
+
* - `filteredFields`: The filtered fields from the work item based on the selected fields.
|
|
395
|
+
* - `relatedRequirements`: An array of related requirements with details such as ID, title, customer ID, and URL.
|
|
396
|
+
* - `relatedBugs`: An array of related bugs with details such as ID, title, and URL.
|
|
397
|
+
* If an error occurs, logs the error and returns `null`.
|
|
398
|
+
*
|
|
399
|
+
* @throws Logs an error message if the data retrieval fails.
|
|
163
400
|
*/
|
|
164
|
-
async
|
|
401
|
+
async fetchResultDataBasedOnWiBase(projectName, runId, resultId, options = {}) {
|
|
402
|
+
var _a, _b, _c, _d, _e;
|
|
165
403
|
try {
|
|
166
404
|
const url = `${this.orgUrl}${projectName}/_apis/test/runs/${runId}/results/${resultId}?detailsToInclude=Iterations`;
|
|
167
405
|
const resultData = await tfs_1.TFSServices.getItemContent(url, this.token);
|
|
168
406
|
const attachmentsUrl = `${this.orgUrl}${projectName}/_apis/test/runs/${runId}/results/${resultId}/attachments`;
|
|
169
407
|
const { value: analysisAttachments } = await tfs_1.TFSServices.getItemContent(attachmentsUrl, this.token);
|
|
170
|
-
|
|
408
|
+
// Build workItem URL with optional expand parameter
|
|
409
|
+
const expandParam = options.expandWorkItem ? '?$expand=all' : '';
|
|
410
|
+
const wiUrl = `${this.orgUrl}${projectName}/_apis/wit/workItems/${resultData.testCase.id}/revisions/${resultData.testCaseRevision}${expandParam}`;
|
|
171
411
|
const wiByRevision = await tfs_1.TFSServices.getItemContent(wiUrl, this.token);
|
|
172
|
-
|
|
412
|
+
let filteredFields = {};
|
|
413
|
+
let relatedRequirements = [];
|
|
414
|
+
let relatedBugs = [];
|
|
415
|
+
// Process selected fields if provided
|
|
416
|
+
if (((_a = options.selectedFields) === null || _a === void 0 ? void 0 : _a.length) &&
|
|
417
|
+
(options.processRelatedRequirements || options.processRelatedBugs)) {
|
|
418
|
+
const filtered = (_c = (_b = options.selectedFields) === null || _b === void 0 ? void 0 : _b.filter((field) => field.includes('@testCaseWorkItemField'))) === null || _c === void 0 ? void 0 : _c.map((field) => field.split('@')[0]);
|
|
419
|
+
const selectedFieldSet = new Set(filtered);
|
|
420
|
+
if (selectedFieldSet.size !== 0) {
|
|
421
|
+
// Process related requirements if needed
|
|
422
|
+
const { relations } = wiByRevision;
|
|
423
|
+
if (relations) {
|
|
424
|
+
for (const relation of relations) {
|
|
425
|
+
if (((_d = relation.rel) === null || _d === void 0 ? void 0 : _d.includes('System.LinkTypes.Hierarchy')) ||
|
|
426
|
+
((_e = relation.rel) === null || _e === void 0 ? void 0 : _e.includes('Microsoft.VSTS.Common.TestedBy'))) {
|
|
427
|
+
const relatedUrl = relation.url;
|
|
428
|
+
const wi = await tfs_1.TFSServices.getItemContent(relatedUrl, this.token);
|
|
429
|
+
if (wi.fields['System.WorkItemType'] === 'Requirement') {
|
|
430
|
+
const { id, fields, _links } = wi;
|
|
431
|
+
const requirementTitle = fields['System.Title'];
|
|
432
|
+
const customerFieldKey = Object.keys(fields).find((key) => key.toLowerCase().includes('customer'));
|
|
433
|
+
const customerId = customerFieldKey ? fields[customerFieldKey] : undefined;
|
|
434
|
+
const url = _links.html.href;
|
|
435
|
+
relatedRequirements.push({ id, requirementTitle, customerId, url });
|
|
436
|
+
}
|
|
437
|
+
else if (wi.fields['System.WorkItemType'] === 'Bug') {
|
|
438
|
+
const { id, fields, _links } = wi;
|
|
439
|
+
const bugTitle = fields['System.Title'];
|
|
440
|
+
const url = _links.html.href;
|
|
441
|
+
relatedBugs.push({ id, bugTitle, url });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Filter fields based on selected field set
|
|
447
|
+
filteredFields = Object.keys(wiByRevision.fields)
|
|
448
|
+
.filter((key) => selectedFieldSet.has(key))
|
|
449
|
+
.reduce((obj, key) => {
|
|
450
|
+
var _a, _b;
|
|
451
|
+
obj[key] = (_b = (_a = wiByRevision.fields[key]) === null || _a === void 0 ? void 0 : _a.displayName) !== null && _b !== void 0 ? _b : wiByRevision.fields[key];
|
|
452
|
+
return obj;
|
|
453
|
+
}, {});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return Object.assign(Object.assign({}, resultData), { stepsResultXml: wiByRevision.fields['Microsoft.VSTS.TCM.Steps'] || undefined, analysisAttachments, testCaseRevision: resultData.testCaseRevision, filteredFields,
|
|
457
|
+
relatedRequirements,
|
|
458
|
+
relatedBugs });
|
|
173
459
|
}
|
|
174
460
|
catch (error) {
|
|
175
461
|
logger_1.default.error(`Error while fetching run result: ${error.message}`);
|
|
462
|
+
if (options.includeFullErrorStack) {
|
|
463
|
+
logger_1.default.error(`Error stack: ${error.stack}`);
|
|
464
|
+
}
|
|
176
465
|
return null;
|
|
177
466
|
}
|
|
178
467
|
}
|
|
179
468
|
/**
|
|
180
|
-
*
|
|
469
|
+
* Fetches result data based on the specified work item (WI) details.
|
|
470
|
+
*
|
|
471
|
+
* @param projectName - The name of the project associated with the work item.
|
|
472
|
+
* @param runId - The unique identifier of the test run.
|
|
473
|
+
* @param resultId - The unique identifier of the test result.
|
|
474
|
+
* @returns A promise that resolves to the result data.
|
|
475
|
+
*/
|
|
476
|
+
async fetchResultDataBasedOnWi(projectName, runId, resultId) {
|
|
477
|
+
return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId);
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Converts a run status string into a human-readable format.
|
|
481
|
+
*
|
|
482
|
+
* @param status - The status string to convert. Expected values are:
|
|
483
|
+
* - `'passed'`: Indicates the run was successful.
|
|
484
|
+
* - `'failed'`: Indicates the run was unsuccessful.
|
|
485
|
+
* - `'notApplicable'`: Indicates the run is not applicable.
|
|
486
|
+
* - Any other value will default to `'Not Run'`.
|
|
487
|
+
* @returns A human-readable string representing the run status.
|
|
181
488
|
*/
|
|
182
489
|
convertRunStatus(status) {
|
|
183
490
|
switch (status) {
|
|
@@ -191,7 +498,19 @@ class ResultDataProvider {
|
|
|
191
498
|
return 'Not Run';
|
|
192
499
|
}
|
|
193
500
|
}
|
|
194
|
-
|
|
501
|
+
/**
|
|
502
|
+
* Converts the outcome of an action result based on specific conditions.
|
|
503
|
+
*
|
|
504
|
+
* - If the outcome is 'Unspecified' and the action result is a shared step title,
|
|
505
|
+
* it returns an empty string.
|
|
506
|
+
* - If the outcome is 'Unspecified' but not a shared step title, it returns 'Not Run'.
|
|
507
|
+
* - If the outcome is not 'Not Run', it returns the original outcome.
|
|
508
|
+
* - Otherwise, it returns an empty string.
|
|
509
|
+
*
|
|
510
|
+
* @param actionResult - The action result object containing the outcome and other properties.
|
|
511
|
+
* @returns A string representing the converted outcome.
|
|
512
|
+
*/
|
|
513
|
+
convertUnspecifiedRunStatus(actionResult) {
|
|
195
514
|
if (actionResult.outcome === 'Unspecified' && actionResult.isSharedStepTitle) {
|
|
196
515
|
return '';
|
|
197
516
|
}
|
|
@@ -202,13 +521,24 @@ class ResultDataProvider {
|
|
|
202
521
|
: '';
|
|
203
522
|
}
|
|
204
523
|
/**
|
|
205
|
-
* Aligns test steps with
|
|
524
|
+
* Aligns test steps with iterations and generates detailed results based on the provided options.
|
|
525
|
+
*
|
|
526
|
+
* @param testData - An array of test data objects containing test points and test cases.
|
|
527
|
+
* @param iterations - An array of iteration data used to map test cases to their respective iterations.
|
|
528
|
+
* @param options - Configuration options for processing the test data.
|
|
529
|
+
* @param options.selectedFields - An optional array of selected fields to filter step-level properties.
|
|
530
|
+
* @param options.createResultObject - A callback function to create a result object for each test or step.
|
|
531
|
+
* @param options.shouldProcessStepLevel - A callback function to determine whether to process at the step level.
|
|
532
|
+
* @returns An array of detailed result objects, either at the test level or step level, depending on the options.
|
|
206
533
|
*/
|
|
207
|
-
|
|
534
|
+
alignStepsWithIterationsBase(testData, iterations, options) {
|
|
535
|
+
var _a, _b;
|
|
208
536
|
const detailedResults = [];
|
|
209
537
|
if (!iterations || (iterations === null || iterations === void 0 ? void 0 : iterations.length) === 0) {
|
|
210
538
|
return detailedResults;
|
|
211
539
|
}
|
|
540
|
+
// Process filtered fields if available
|
|
541
|
+
const filteredFields = new Set(((_b = (_a = options.selectedFields) === null || _a === void 0 ? void 0 : _a.filter((field) => field.includes('@stepsRunProperties'))) === null || _b === void 0 ? void 0 : _b.map((field) => field.split('@')[0])) || []);
|
|
212
542
|
for (const testItem of testData) {
|
|
213
543
|
for (const point of testItem.testPointsItems) {
|
|
214
544
|
const testCase = testItem.testCasesItems.find((tc) => tc.workItem.id === point.testCaseId);
|
|
@@ -221,32 +551,77 @@ class ResultDataProvider {
|
|
|
221
551
|
}
|
|
222
552
|
if (point.lastRunId && point.lastResultId) {
|
|
223
553
|
const iterationKey = `${point.lastRunId}-${point.lastResultId}-${testCase.workItem.id}`;
|
|
224
|
-
const
|
|
225
|
-
if (!
|
|
554
|
+
const fetchedTestCase = iterationsMap[iterationKey];
|
|
555
|
+
if (!fetchedTestCase || !fetchedTestCase.iteration)
|
|
226
556
|
continue;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
stepAction: actionResults[i].action,
|
|
238
|
-
stepExpected: actionResults[i].expected,
|
|
239
|
-
isSharedStepTitle: actionResults[i].isSharedStepTitle,
|
|
240
|
-
stepStatus: this.setRunStatus(actionResults[i]),
|
|
241
|
-
stepComments: actionResults[i].errorMessage || '',
|
|
242
|
-
};
|
|
557
|
+
// Determine if we should process at step level
|
|
558
|
+
const shouldProcessSteps = options.shouldProcessStepLevel(fetchedTestCase, filteredFields);
|
|
559
|
+
if (!shouldProcessSteps) {
|
|
560
|
+
// Create a test-level result object
|
|
561
|
+
const resultObj = options.createResultObject({
|
|
562
|
+
testItem,
|
|
563
|
+
point,
|
|
564
|
+
fetchedTestCase,
|
|
565
|
+
filteredFields,
|
|
566
|
+
});
|
|
243
567
|
detailedResults.push(resultObj);
|
|
244
568
|
}
|
|
569
|
+
else {
|
|
570
|
+
// Create step-level result objects
|
|
571
|
+
const { actionResults } = fetchedTestCase === null || fetchedTestCase === void 0 ? void 0 : fetchedTestCase.iteration;
|
|
572
|
+
for (let i = 0; i < (actionResults === null || actionResults === void 0 ? void 0 : actionResults.length); i++) {
|
|
573
|
+
const resultObj = options.createResultObject({
|
|
574
|
+
testItem,
|
|
575
|
+
point,
|
|
576
|
+
fetchedTestCase,
|
|
577
|
+
actionResult: actionResults[i],
|
|
578
|
+
filteredFields,
|
|
579
|
+
});
|
|
580
|
+
detailedResults.push(resultObj);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
245
583
|
}
|
|
246
584
|
}
|
|
247
585
|
}
|
|
248
586
|
return detailedResults;
|
|
249
587
|
}
|
|
588
|
+
/**
|
|
589
|
+
* Aligns test steps with their corresponding iterations by processing the provided test data and iterations.
|
|
590
|
+
* This method utilizes a base alignment function with custom logic for processing step-level data and creating result objects.
|
|
591
|
+
*
|
|
592
|
+
* @param testData - An array of test data objects containing information about test cases and their steps.
|
|
593
|
+
* @param iterations - An array of iteration objects containing action results for each test case.
|
|
594
|
+
* @returns An array of aligned step data objects, each containing detailed information about a test step and its status.
|
|
595
|
+
*
|
|
596
|
+
* The alignment process includes:
|
|
597
|
+
* - Filtering and processing step-level data based on the presence of iteration and action results.
|
|
598
|
+
* - Creating result objects for each step, including details such as test ID, step identifier, action path, step status, and comments.
|
|
599
|
+
*/
|
|
600
|
+
alignStepsWithIterations(testData, iterations) {
|
|
601
|
+
return this.alignStepsWithIterationsBase(testData, iterations, {
|
|
602
|
+
shouldProcessStepLevel: (fetchedTestCase) => fetchedTestCase != null &&
|
|
603
|
+
fetchedTestCase.iteration != null &&
|
|
604
|
+
fetchedTestCase.iteration.actionResults != null,
|
|
605
|
+
createResultObject: ({ point, fetchedTestCase, actionResult }) => {
|
|
606
|
+
if (!actionResult)
|
|
607
|
+
return null;
|
|
608
|
+
const stepIdentifier = parseInt(actionResult.stepIdentifier, 10);
|
|
609
|
+
return {
|
|
610
|
+
testId: point.testCaseId,
|
|
611
|
+
testCaseRevision: fetchedTestCase.testCaseRevision,
|
|
612
|
+
testName: point.testCaseName,
|
|
613
|
+
stepIdentifier: stepIdentifier,
|
|
614
|
+
actionPath: actionResult.actionPath,
|
|
615
|
+
stepNo: actionResult.stepPosition,
|
|
616
|
+
stepAction: actionResult.action,
|
|
617
|
+
stepExpected: actionResult.expected,
|
|
618
|
+
isSharedStepTitle: actionResult.isSharedStepTitle,
|
|
619
|
+
stepStatus: this.convertUnspecifiedRunStatus(actionResult),
|
|
620
|
+
stepComments: actionResult.errorMessage || '',
|
|
621
|
+
};
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
}
|
|
250
625
|
/**
|
|
251
626
|
* Creates a mapping of iterations by their unique keys.
|
|
252
627
|
*/
|
|
@@ -276,12 +651,19 @@ class ResultDataProvider {
|
|
|
276
651
|
})));
|
|
277
652
|
}
|
|
278
653
|
/**
|
|
279
|
-
* Fetches result data
|
|
654
|
+
* Fetches all result data based on the provided test data, project name, and fetch strategy.
|
|
655
|
+
* This method processes the test data, filters valid test points, and sequentially fetches
|
|
656
|
+
* result data for each valid point using the provided fetch strategy.
|
|
657
|
+
*
|
|
658
|
+
* @param testData - An array of test data objects containing test suite and test points information.
|
|
659
|
+
* @param projectName - The name of the project for which the result data is being fetched.
|
|
660
|
+
* @param fetchStrategy - A function that defines the strategy for fetching result data. It takes
|
|
661
|
+
* the project name, test suite ID, a test point, and additional arguments,
|
|
662
|
+
* and returns a Promise resolving to the fetched result data.
|
|
663
|
+
* @param additionalArgs - An optional array of additional arguments to be passed to the fetch strategy.
|
|
664
|
+
* @returns A Promise that resolves to an array of fetched result data.
|
|
280
665
|
*/
|
|
281
|
-
|
|
282
|
-
* Fetches result data for all test points within the given test data, sequentially to avoid context-related errors.
|
|
283
|
-
*/
|
|
284
|
-
async fetchAllResultData(testData, projectName) {
|
|
666
|
+
async fetchAllResultDataBase(testData, projectName, fetchStrategy, additionalArgs = []) {
|
|
285
667
|
const results = [];
|
|
286
668
|
for (const item of testData) {
|
|
287
669
|
if (item.testPointsItems && item.testPointsItems.length > 0) {
|
|
@@ -290,7 +672,7 @@ class ResultDataProvider {
|
|
|
290
672
|
const validPoints = testPointsItems.filter((point) => point && point.lastRunId && point.lastResultId);
|
|
291
673
|
// Fetch results for each point sequentially
|
|
292
674
|
for (const point of validPoints) {
|
|
293
|
-
const resultData = await
|
|
675
|
+
const resultData = await fetchStrategy(projectName, testSuiteId, point, ...additionalArgs);
|
|
294
676
|
if (resultData !== null) {
|
|
295
677
|
results.push(resultData);
|
|
296
678
|
}
|
|
@@ -300,11 +682,17 @@ class ResultDataProvider {
|
|
|
300
682
|
return results;
|
|
301
683
|
}
|
|
302
684
|
/**
|
|
303
|
-
* Fetches result
|
|
685
|
+
* Fetches result data for all test points within the given test data, sequentially to avoid context-related errors.
|
|
686
|
+
*/
|
|
687
|
+
async fetchAllResultData(testData, projectName) {
|
|
688
|
+
return this.fetchAllResultDataBase(testData, projectName, (projectName, testSuiteId, point) => this.fetchResultData(projectName, testSuiteId, point));
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Base method for fetching result data for test points
|
|
304
692
|
*/
|
|
305
|
-
async
|
|
693
|
+
async fetchResultDataBase(projectName, testSuiteId, point, fetchResultMethod, createResponseObject, additionalArgs = []) {
|
|
306
694
|
const { lastRunId, lastResultId } = point;
|
|
307
|
-
const resultData = await
|
|
695
|
+
const resultData = await fetchResultMethod(projectName, lastRunId.toString(), lastResultId.toString(), ...additionalArgs);
|
|
308
696
|
const iteration = resultData.iterationDetails.length > 0
|
|
309
697
|
? resultData.iterationDetails[resultData.iterationDetails.length - 1]
|
|
310
698
|
: undefined;
|
|
@@ -338,22 +726,30 @@ class ResultDataProvider {
|
|
|
338
726
|
.sort((a, b) => this.compareActionResults(a.stepPosition, b.stepPosition));
|
|
339
727
|
}
|
|
340
728
|
return (resultData === null || resultData === void 0 ? void 0 : resultData.testCase)
|
|
341
|
-
?
|
|
342
|
-
testCaseName: `${resultData.testCase.name} - ${resultData.testCase.id}`,
|
|
343
|
-
testCaseId: resultData.testCase.id,
|
|
344
|
-
testSuiteName: `${resultData.testSuite.name}`,
|
|
345
|
-
testSuiteId,
|
|
346
|
-
lastRunId,
|
|
347
|
-
lastResultId,
|
|
348
|
-
iteration,
|
|
349
|
-
testCaseRevision: resultData.testCaseRevision,
|
|
350
|
-
failureType: resultData.failureType,
|
|
351
|
-
resolution: resultData.resolutionState,
|
|
352
|
-
comment: resultData.comment,
|
|
353
|
-
analysisAttachments: resultData.analysisAttachments,
|
|
354
|
-
}
|
|
729
|
+
? createResponseObject(resultData, testSuiteId, point, ...additionalArgs)
|
|
355
730
|
: null;
|
|
356
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* Fetches result Data for a specific test point
|
|
734
|
+
*/
|
|
735
|
+
async fetchResultData(projectName, testSuiteId, point) {
|
|
736
|
+
return this.fetchResultDataBase(projectName, testSuiteId, point, (project, runId, resultId) => this.fetchResultDataBasedOnWi(project, runId, resultId), (resultData, testSuiteId, point) => ({
|
|
737
|
+
testCaseName: `${resultData.testCase.name} - ${resultData.testCase.id}`,
|
|
738
|
+
testCaseId: resultData.testCase.id,
|
|
739
|
+
testSuiteName: `${resultData.testSuite.name}`,
|
|
740
|
+
testSuiteId,
|
|
741
|
+
lastRunId: point.lastRunId,
|
|
742
|
+
lastResultId: point.lastResultId,
|
|
743
|
+
iteration: resultData.iterationDetails.length > 0
|
|
744
|
+
? resultData.iterationDetails[resultData.iterationDetails.length - 1]
|
|
745
|
+
: undefined,
|
|
746
|
+
testCaseRevision: resultData.testCaseRevision,
|
|
747
|
+
failureType: resultData.failureType,
|
|
748
|
+
resolution: resultData.resolutionState,
|
|
749
|
+
comment: resultData.comment,
|
|
750
|
+
analysisAttachments: resultData.analysisAttachments,
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
357
753
|
/**
|
|
358
754
|
* Fetches all the linked work items (WI) for the given test case.
|
|
359
755
|
* @param project Project name
|
|
@@ -476,33 +872,6 @@ class ResultDataProvider {
|
|
|
476
872
|
});
|
|
477
873
|
}
|
|
478
874
|
}
|
|
479
|
-
/**
|
|
480
|
-
* Mapping each attachment to a proper URL for downloading it
|
|
481
|
-
* @param runResults Array of run results
|
|
482
|
-
*/
|
|
483
|
-
mapAttachmentsUrl(runResults, project) {
|
|
484
|
-
return runResults.map((result) => {
|
|
485
|
-
var _a;
|
|
486
|
-
if (!result.iteration) {
|
|
487
|
-
return result;
|
|
488
|
-
}
|
|
489
|
-
const { iteration, analysisAttachments } = result, restResult = __rest(result, ["iteration", "analysisAttachments"]);
|
|
490
|
-
//add downloadUri field for each attachment
|
|
491
|
-
const baseDownloadUrl = `${this.orgUrl}${project}/_apis/test/runs/${result.lastRunId}/results/${result.lastResultId}/attachments`;
|
|
492
|
-
if (iteration && ((_a = iteration.attachments) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
493
|
-
const { attachments, actionResults } = iteration, restOfIteration = __rest(iteration, ["attachments", "actionResults"]);
|
|
494
|
-
const attachmentPathToIndexMap = this.CreateAttachmentPathIndexMap(actionResults);
|
|
495
|
-
const mappedAttachments = attachments.map((attachment) => (Object.assign(Object.assign({}, attachment), { stepNo: attachmentPathToIndexMap.has(attachment.actionPath)
|
|
496
|
-
? attachmentPathToIndexMap.get(attachment.actionPath)
|
|
497
|
-
: undefined, downloadUrl: `${baseDownloadUrl}/${attachment.id}/${attachment.name}` })));
|
|
498
|
-
restResult.iteration = Object.assign(Object.assign({}, restOfIteration), { attachments: mappedAttachments });
|
|
499
|
-
}
|
|
500
|
-
if (analysisAttachments && analysisAttachments.length > 0) {
|
|
501
|
-
restResult.analysisAttachments = analysisAttachments.map((attachment) => (Object.assign(Object.assign({}, attachment), { downloadUrl: `${baseDownloadUrl}/${attachment.id}/${attachment.fileName}` })));
|
|
502
|
-
}
|
|
503
|
-
return Object.assign({}, restResult);
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
875
|
CreateAttachmentPathIndexMap(actionResults) {
|
|
507
876
|
const attachmentPathToIndexMap = new Map();
|
|
508
877
|
for (let i = 0; i < actionResults.length; i++) {
|
|
@@ -511,122 +880,6 @@ class ResultDataProvider {
|
|
|
511
880
|
}
|
|
512
881
|
return attachmentPathToIndexMap;
|
|
513
882
|
}
|
|
514
|
-
/**
|
|
515
|
-
* Combines the results of test group result summary, test results summary, and detailed results summary into a single key-value pair array.
|
|
516
|
-
*/
|
|
517
|
-
async getCombinedResultsSummary(testPlanId, projectName, selectedSuiteIds, addConfiguration = false, isHierarchyGroupName = false, includeOpenPCRs = false, includeTestLog = false, stepExecution, stepAnalysis, includeHardCopyRun = false) {
|
|
518
|
-
const combinedResults = [];
|
|
519
|
-
try {
|
|
520
|
-
// Fetch test suites
|
|
521
|
-
const suites = await this.fetchTestSuites(testPlanId, projectName, selectedSuiteIds, isHierarchyGroupName);
|
|
522
|
-
// Prepare test data for summaries
|
|
523
|
-
const testPointsPromises = suites.map((suite) => this.limit(() => this.fetchTestPoints(projectName, testPlanId, suite.testSuiteId)
|
|
524
|
-
.then((testPointsItems) => (Object.assign(Object.assign({}, suite), { testPointsItems })))
|
|
525
|
-
.catch((error) => {
|
|
526
|
-
logger_1.default.error(`Error occurred for suite ${suite.testSuiteId}: ${error.message}`);
|
|
527
|
-
return Object.assign(Object.assign({}, suite), { testPointsItems: [] });
|
|
528
|
-
})));
|
|
529
|
-
const testPoints = await Promise.all(testPointsPromises);
|
|
530
|
-
// 1. Calculate Test Group Result Summary
|
|
531
|
-
const summarizedResults = testPoints
|
|
532
|
-
.filter((testPoint) => testPoint.testPointsItems && testPoint.testPointsItems.length > 0)
|
|
533
|
-
.map((testPoint) => {
|
|
534
|
-
const groupResultSummary = this.calculateGroupResultSummary(testPoint.testPointsItems || [], includeHardCopyRun);
|
|
535
|
-
return Object.assign(Object.assign({}, testPoint), { groupResultSummary });
|
|
536
|
-
});
|
|
537
|
-
const totalSummary = this.calculateTotalSummary(summarizedResults, includeHardCopyRun);
|
|
538
|
-
const testGroupArray = summarizedResults.map((item) => (Object.assign({ testGroupName: item.testGroupName }, item.groupResultSummary)));
|
|
539
|
-
testGroupArray.push(Object.assign({ testGroupName: 'Total' }, totalSummary));
|
|
540
|
-
// Add test group result summary to combined results
|
|
541
|
-
combinedResults.push({
|
|
542
|
-
contentControl: 'test-group-summary-content-control',
|
|
543
|
-
data: testGroupArray,
|
|
544
|
-
skin: 'test-result-test-group-summary-table',
|
|
545
|
-
});
|
|
546
|
-
// 2. Calculate Test Results Summary
|
|
547
|
-
const flattenedTestPoints = this.flattenTestPoints(testPoints);
|
|
548
|
-
const testResultsSummary = flattenedTestPoints.map((testPoint) => this.formatTestResult(testPoint, addConfiguration, includeHardCopyRun));
|
|
549
|
-
// Add test results summary to combined results
|
|
550
|
-
combinedResults.push({
|
|
551
|
-
contentControl: 'test-result-summary-content-control',
|
|
552
|
-
data: testResultsSummary,
|
|
553
|
-
skin: 'test-result-table',
|
|
554
|
-
});
|
|
555
|
-
// 3. Calculate Detailed Results Summary
|
|
556
|
-
const testData = await this.fetchTestData(suites, projectName, testPlanId);
|
|
557
|
-
const runResults = await this.fetchAllResultData(testData, projectName);
|
|
558
|
-
const detailedStepResultsSummary = this.alignStepsWithIterations(testData, runResults);
|
|
559
|
-
//Filter out all the results with no comment
|
|
560
|
-
const filteredDetailedResults = detailedStepResultsSummary.filter((result) => result && (result.stepComments !== '' || result.stepStatus === 'Failed'));
|
|
561
|
-
// Add detailed results summary to combined results
|
|
562
|
-
combinedResults.push({
|
|
563
|
-
contentControl: 'detailed-test-result-content-control',
|
|
564
|
-
data: !includeHardCopyRun ? filteredDetailedResults : [],
|
|
565
|
-
skin: 'detailed-test-result-table',
|
|
566
|
-
});
|
|
567
|
-
if (includeOpenPCRs) {
|
|
568
|
-
//5. Open PCRs data (only if enabled)
|
|
569
|
-
await this.fetchOpenPcrData(testResultsSummary, projectName, combinedResults);
|
|
570
|
-
}
|
|
571
|
-
//6. Test Log (only if enabled)
|
|
572
|
-
if (includeTestLog) {
|
|
573
|
-
this.fetchTestLogData(flattenedTestPoints, combinedResults);
|
|
574
|
-
}
|
|
575
|
-
if (stepAnalysis && stepAnalysis.isEnabled) {
|
|
576
|
-
const mappedAnalysisData = runResults.filter((result) => {
|
|
577
|
-
var _a, _b, _c;
|
|
578
|
-
return result.comment ||
|
|
579
|
-
((_b = (_a = result.iteration) === null || _a === void 0 ? void 0 : _a.attachments) === null || _b === void 0 ? void 0 : _b.length) > 0 ||
|
|
580
|
-
((_c = result.analysisAttachments) === null || _c === void 0 ? void 0 : _c.length) > 0;
|
|
581
|
-
});
|
|
582
|
-
const mappedAnalysisResultData = stepAnalysis.generateRunAttachments.isEnabled
|
|
583
|
-
? this.mapAttachmentsUrl(mappedAnalysisData, projectName)
|
|
584
|
-
: mappedAnalysisData;
|
|
585
|
-
if ((mappedAnalysisResultData === null || mappedAnalysisResultData === void 0 ? void 0 : mappedAnalysisResultData.length) > 0) {
|
|
586
|
-
combinedResults.push({
|
|
587
|
-
contentControl: 'appendix-a-content-control',
|
|
588
|
-
data: mappedAnalysisResultData,
|
|
589
|
-
skin: 'step-analysis-appendix-skin',
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
if (stepExecution && stepExecution.isEnabled) {
|
|
594
|
-
const mappedAnalysisData = stepExecution.generateAttachments.isEnabled &&
|
|
595
|
-
stepExecution.generateAttachments.runAttachmentMode !== 'planOnly'
|
|
596
|
-
? runResults.filter((result) => { var _a, _b; return ((_b = (_a = result.iteration) === null || _a === void 0 ? void 0 : _a.attachments) === null || _b === void 0 ? void 0 : _b.length) > 0; })
|
|
597
|
-
: [];
|
|
598
|
-
const mappedAnalysisResultData = mappedAnalysisData.length > 0 ? this.mapAttachmentsUrl(mappedAnalysisData, projectName) : [];
|
|
599
|
-
const mappedDetailedResults = this.mapStepResultsForExecutionAppendix(detailedStepResultsSummary, mappedAnalysisResultData);
|
|
600
|
-
combinedResults.push({
|
|
601
|
-
contentControl: 'appendix-b-content-control',
|
|
602
|
-
data: mappedDetailedResults,
|
|
603
|
-
skin: 'step-execution-appendix-skin',
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
return combinedResults;
|
|
607
|
-
}
|
|
608
|
-
catch (error) {
|
|
609
|
-
logger_1.default.error(`Error during getCombinedResultsSummary: ${error.message}`);
|
|
610
|
-
if (error.response) {
|
|
611
|
-
logger_1.default.error(`Response Data: ${JSON.stringify(error.response.data)}`);
|
|
612
|
-
}
|
|
613
|
-
throw error;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
// private mapStepResultsForExecutionAppendix(detailedResults: any[]): any {
|
|
617
|
-
// return detailedResults?.length > 0
|
|
618
|
-
// ? detailedResults.map((result) => {
|
|
619
|
-
// return {
|
|
620
|
-
// testId: result.testId,
|
|
621
|
-
// testCaseRevision: result.testCaseRevision || undefined,
|
|
622
|
-
// stepNo: result.stepNo,
|
|
623
|
-
// stepIdentifier: result.stepIdentifier,
|
|
624
|
-
// stepStatus: result.stepStatus,
|
|
625
|
-
// stepComments: result.stepComments,
|
|
626
|
-
// };
|
|
627
|
-
// })
|
|
628
|
-
// : [];
|
|
629
|
-
// }
|
|
630
883
|
mapStepResultsForExecutionAppendix(detailedResults, runResultData) {
|
|
631
884
|
// Create maps first to avoid repeated lookups
|
|
632
885
|
const testCaseIdToStepsMap = new Map();
|
|
@@ -778,6 +1031,186 @@ class ResultDataProvider {
|
|
|
778
1031
|
}
|
|
779
1032
|
return formattedResult;
|
|
780
1033
|
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Fetches result data based on the Work Item Test Reporter.
|
|
1036
|
+
*
|
|
1037
|
+
* This method retrieves detailed result data for a specific test run and result ID,
|
|
1038
|
+
* including related work items, selected fields, and additional processing options.
|
|
1039
|
+
*
|
|
1040
|
+
* @param projectName - The name of the project containing the test run.
|
|
1041
|
+
* @param runId - The unique identifier of the test run.
|
|
1042
|
+
* @param resultId - The unique identifier of the test result.
|
|
1043
|
+
* @param selectedFields - (Optional) An array of field names to include in the result data.
|
|
1044
|
+
* @returns A promise that resolves to the fetched result data.
|
|
1045
|
+
*/
|
|
1046
|
+
async fetchResultDataBasedOnWiTestReporter(projectName, runId, resultId, selectedFields) {
|
|
1047
|
+
return this.fetchResultDataBasedOnWiBase(projectName, runId, resultId, {
|
|
1048
|
+
expandWorkItem: true,
|
|
1049
|
+
selectedFields,
|
|
1050
|
+
processRelatedRequirements: true,
|
|
1051
|
+
processRelatedBugs: true,
|
|
1052
|
+
includeFullErrorStack: true,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Fetches all result data for the test reporter by processing the provided test data.
|
|
1057
|
+
*
|
|
1058
|
+
* This method utilizes the `fetchAllResultDataBase` function to retrieve and process
|
|
1059
|
+
* result data for a specific project and test reporter. It applies a callback to fetch
|
|
1060
|
+
* result data for individual test points.
|
|
1061
|
+
*
|
|
1062
|
+
* @param testData - An array of test data objects to process.
|
|
1063
|
+
* @param projectName - The name of the project for which result data is being fetched.
|
|
1064
|
+
* @param selectedFields - An optional array of field names to include in the result data.
|
|
1065
|
+
* @returns A promise that resolves to an array of processed result data.
|
|
1066
|
+
*/
|
|
1067
|
+
async fetchAllResultDataTestReporter(testData, projectName, selectedFields) {
|
|
1068
|
+
return this.fetchAllResultDataBase(testData, projectName, (projectName, testSuiteId, point, selectedFields) => this.fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields), [selectedFields]);
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Aligns test steps with iterations for the test reporter by processing test data and iterations
|
|
1072
|
+
* and generating a structured result object based on the provided selected fields.
|
|
1073
|
+
*
|
|
1074
|
+
* @param testData - An array of test data objects to be processed.
|
|
1075
|
+
* @param iterations - An array of iteration objects to align with the test data.
|
|
1076
|
+
* @param selectedFields - An array of selected fields to determine which properties to include in the result.
|
|
1077
|
+
* @returns An array of structured result objects containing aligned test steps and iterations.
|
|
1078
|
+
*
|
|
1079
|
+
* The method uses a base alignment function and provides custom logic for:
|
|
1080
|
+
* - Determining whether step-level processing should occur based on the fetched test case and filtered fields.
|
|
1081
|
+
* - Creating a result object with properties such as suite name, test case details, priority, run information,
|
|
1082
|
+
* failure type, automation status, execution date, configuration name, state, error message, and related requirements.
|
|
1083
|
+
* - Including step-specific properties (e.g., step number, action, expected result, status, and comments) if action results are available
|
|
1084
|
+
* and the corresponding fields are selected.
|
|
1085
|
+
*/
|
|
1086
|
+
alignStepsWithIterationsTestReporter(testData, iterations, selectedFields) {
|
|
1087
|
+
return this.alignStepsWithIterationsBase(testData, iterations, {
|
|
1088
|
+
selectedFields,
|
|
1089
|
+
shouldProcessStepLevel: (fetchedTestCase, filteredFields) => fetchedTestCase != null &&
|
|
1090
|
+
fetchedTestCase.iteration != null &&
|
|
1091
|
+
fetchedTestCase.iteration.actionResults != null &&
|
|
1092
|
+
filteredFields.size > 0,
|
|
1093
|
+
createResultObject: ({ testItem, point, fetchedTestCase, actionResult, filteredFields = new Set(), }) => {
|
|
1094
|
+
var _a;
|
|
1095
|
+
const baseObj = {
|
|
1096
|
+
suiteName: testItem.testGroupName,
|
|
1097
|
+
testCase: {
|
|
1098
|
+
id: point.testCaseId,
|
|
1099
|
+
title: point.testCaseName,
|
|
1100
|
+
url: point.testCaseUrl,
|
|
1101
|
+
result: fetchedTestCase.testCaseResult,
|
|
1102
|
+
comment: (_a = fetchedTestCase.iteration) === null || _a === void 0 ? void 0 : _a.comment,
|
|
1103
|
+
},
|
|
1104
|
+
priority: fetchedTestCase.priority,
|
|
1105
|
+
runBy: fetchedTestCase.runBy,
|
|
1106
|
+
activatedBy: fetchedTestCase.activatedBy,
|
|
1107
|
+
assignedTo: fetchedTestCase.assignedTo,
|
|
1108
|
+
failureType: fetchedTestCase.failureType,
|
|
1109
|
+
automationStatus: fetchedTestCase.automationStatus,
|
|
1110
|
+
executionDate: fetchedTestCase.executionDate,
|
|
1111
|
+
configurationName: fetchedTestCase.configurationName,
|
|
1112
|
+
errorMessage: fetchedTestCase.errorMessage,
|
|
1113
|
+
relatedRequirements: fetchedTestCase.relatedRequirements,
|
|
1114
|
+
relatedBugs: fetchedTestCase.relatedBugs,
|
|
1115
|
+
};
|
|
1116
|
+
// If we have action results, add step-specific properties
|
|
1117
|
+
if (actionResult) {
|
|
1118
|
+
return Object.assign(Object.assign({}, baseObj), { stepNo: actionResult.stepPosition, stepAction: filteredFields.has('includeSteps') ? actionResult.action : undefined, stepExpected: filteredFields.has('includeSteps') ? actionResult.expected : undefined, stepStatus: filteredFields.has('stepRunStatus')
|
|
1119
|
+
? this.convertUnspecifiedRunStatus(actionResult)
|
|
1120
|
+
: undefined, stepComments: filteredFields.has('testStepComment') ? actionResult.errorMessage : undefined });
|
|
1121
|
+
}
|
|
1122
|
+
return baseObj;
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Fetches result data for a test reporter based on the provided project, test suite, and point information.
|
|
1128
|
+
* This method processes the result data and formats it according to the selected fields.
|
|
1129
|
+
*
|
|
1130
|
+
* @param projectName - The name of the project.
|
|
1131
|
+
* @param testSuiteId - The ID of the test suite.
|
|
1132
|
+
* @param point - The test point containing details such as last run ID, result ID, and configuration name.
|
|
1133
|
+
* @param selectedFields - An optional array of field names to filter and include in the response.
|
|
1134
|
+
*
|
|
1135
|
+
* @returns A promise that resolves to the formatted result data object containing details about the test case,
|
|
1136
|
+
* test suite, last run, iteration, and other selected fields.
|
|
1137
|
+
*/
|
|
1138
|
+
async fetchResultDataForTestReporter(projectName, testSuiteId, point, selectedFields) {
|
|
1139
|
+
return this.fetchResultDataBase(projectName, testSuiteId, point, (project, runId, resultId, fields) => this.fetchResultDataBasedOnWiTestReporter(project, runId, resultId, fields), (resultData, testSuiteId, point, selectedFields) => {
|
|
1140
|
+
var _a, _b;
|
|
1141
|
+
const { lastRunId, lastResultId, configurationName, lastResultDetails } = point;
|
|
1142
|
+
const iteration = resultData.iterationDetails.length > 0
|
|
1143
|
+
? resultData.iterationDetails[resultData.iterationDetails.length - 1]
|
|
1144
|
+
: undefined;
|
|
1145
|
+
const resultDataResponse = {
|
|
1146
|
+
testCaseName: `${resultData.testCase.name} - ${resultData.testCase.id}`,
|
|
1147
|
+
testCaseId: resultData.testCase.id,
|
|
1148
|
+
testSuiteName: `${resultData.testSuite.name}`,
|
|
1149
|
+
testSuiteId,
|
|
1150
|
+
lastRunId,
|
|
1151
|
+
lastResultId,
|
|
1152
|
+
iteration,
|
|
1153
|
+
testCaseRevision: resultData.testCaseRevision,
|
|
1154
|
+
resolution: resultData.resolutionState,
|
|
1155
|
+
automationStatus: resultData.filteredFields['Microsoft.VSTS.TCM.AutomationStatus'] || undefined,
|
|
1156
|
+
analysisAttachments: resultData.analysisAttachments,
|
|
1157
|
+
failureType: undefined,
|
|
1158
|
+
comment: undefined,
|
|
1159
|
+
priority: undefined,
|
|
1160
|
+
runBy: undefined,
|
|
1161
|
+
executionDate: undefined,
|
|
1162
|
+
testCaseResult: undefined,
|
|
1163
|
+
errorMessage: undefined,
|
|
1164
|
+
configurationName: undefined,
|
|
1165
|
+
relatedRequirements: undefined,
|
|
1166
|
+
relatedBugs: undefined,
|
|
1167
|
+
lastRunResult: undefined,
|
|
1168
|
+
};
|
|
1169
|
+
const filteredFields = (_a = selectedFields === null || selectedFields === void 0 ? void 0 : selectedFields.filter((field) => field.includes('@runResultField') || field.includes('@linked'))) === null || _a === void 0 ? void 0 : _a.map((field) => field.split('@')[0]);
|
|
1170
|
+
if (filteredFields && filteredFields.length > 0) {
|
|
1171
|
+
for (const field of filteredFields) {
|
|
1172
|
+
switch (field) {
|
|
1173
|
+
case 'priority':
|
|
1174
|
+
resultDataResponse.priority = resultData.priority;
|
|
1175
|
+
break;
|
|
1176
|
+
case 'testCaseResult':
|
|
1177
|
+
const outcome = (_b = resultData.iterationDetails[resultData.iterationDetails.length - 1]) === null || _b === void 0 ? void 0 : _b.outcome;
|
|
1178
|
+
resultDataResponse.testCaseResult = {
|
|
1179
|
+
resultMessage: `${outcome} in Run ${lastRunId}`,
|
|
1180
|
+
url: `${this.orgUrl}${projectName}/_testManagement/runs?runId=${lastRunId}&_a=resultSummary&resultId=${lastResultId}`,
|
|
1181
|
+
};
|
|
1182
|
+
break;
|
|
1183
|
+
case 'failureType':
|
|
1184
|
+
resultDataResponse.failureType = resultData.failureType;
|
|
1185
|
+
break;
|
|
1186
|
+
case 'testCaseComment':
|
|
1187
|
+
resultDataResponse.comment = resultData.comment || undefined;
|
|
1188
|
+
break;
|
|
1189
|
+
case 'runBy':
|
|
1190
|
+
const runBy = lastResultDetails.runBy.displayName;
|
|
1191
|
+
resultDataResponse.runBy = runBy;
|
|
1192
|
+
break;
|
|
1193
|
+
case 'executionDate':
|
|
1194
|
+
resultDataResponse.executionDate = lastResultDetails.dateCompleted;
|
|
1195
|
+
break;
|
|
1196
|
+
case 'configurationName':
|
|
1197
|
+
resultDataResponse.configurationName = configurationName;
|
|
1198
|
+
break;
|
|
1199
|
+
case 'associatedRequirement':
|
|
1200
|
+
resultDataResponse.relatedRequirements = resultData.relatedRequirements;
|
|
1201
|
+
break;
|
|
1202
|
+
case 'associatedBug':
|
|
1203
|
+
resultDataResponse.relatedBugs = resultData.relatedBugs;
|
|
1204
|
+
break;
|
|
1205
|
+
default:
|
|
1206
|
+
logger_1.default.debug(`Field ${field} not handled`);
|
|
1207
|
+
break;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
return resultDataResponse;
|
|
1212
|
+
}, [selectedFields]);
|
|
1213
|
+
}
|
|
781
1214
|
}
|
|
782
1215
|
exports.default = ResultDataProvider;
|
|
783
1216
|
//# sourceMappingURL=ResultDataProvider.js.map
|