@testcollab/cli 1.5.0 → 1.7.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/README.md +14 -0
- package/package.json +1 -1
- package/src/commands/report.js +66 -5
- package/src/index.js +1 -0
package/README.md
CHANGED
|
@@ -85,6 +85,20 @@ tc report \
|
|
|
85
85
|
| `--result-file <path>` | Yes | Path to the result file |
|
|
86
86
|
| `--api-key <key>` | No | TestCollab API key (or set `TESTCOLLAB_TOKEN` env var) |
|
|
87
87
|
| `--api-url <url>` | No | API base URL override (default: `https://api.testcollab.io`). Use `https://api-eu.testcollab.io` for EU region. |
|
|
88
|
+
| `--skip-missing` | No | Mark test cases in the test plan but not in the result file as **skipped** |
|
|
89
|
+
|
|
90
|
+
#### `--skip-missing`
|
|
91
|
+
|
|
92
|
+
By default, test cases in the test plan that don't appear in the result file are left untouched. When `--skip-missing` is passed, these unmatched cases are automatically marked as **skipped**. This is useful when your result file only contains the tests that actually ran, and you want the full test plan status to reflect that anything not executed was skipped.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
tc report \
|
|
96
|
+
--project 123 \
|
|
97
|
+
--test-plan-id 555 \
|
|
98
|
+
--format junit \
|
|
99
|
+
--result-file ./results.xml \
|
|
100
|
+
--skip-missing
|
|
101
|
+
```
|
|
88
102
|
|
|
89
103
|
#### Mapping test cases
|
|
90
104
|
|
package/package.json
CHANGED
package/src/commands/report.js
CHANGED
|
@@ -216,7 +216,8 @@ export function extractTestCaseIdFromTitle(title) {
|
|
|
216
216
|
function extractTestCaseIdFromMochawesomeTest(testData) {
|
|
217
217
|
const testTitle = String(testData?.title || '').trim();
|
|
218
218
|
const fullTitle = String(testData?.fullTitle || '').trim();
|
|
219
|
-
|
|
219
|
+
// Prefer fullTitle because some reporters shorten `title` and keep the canonical ID in fullTitle.
|
|
220
|
+
return extractTestCaseIdFromTitle(fullTitle) || extractTestCaseIdFromTitle(testTitle);
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
function prepareMochawesomeRunRecord(testData) {
|
|
@@ -527,6 +528,7 @@ class TcApiClient {
|
|
|
527
528
|
buildUrl(endpoint) {
|
|
528
529
|
const normalized = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
529
530
|
const separator = normalized.includes('?') ? '&' : '?';
|
|
531
|
+
// console.log("build url", `${this.baseApiUrl}${normalized}${separator}token=${encodeURIComponent(this.accessToken)}`);
|
|
530
532
|
return `${this.baseApiUrl}${normalized}${separator}token=${encodeURIComponent(this.accessToken)}`;
|
|
531
533
|
}
|
|
532
534
|
|
|
@@ -832,7 +834,8 @@ async function uploadUsingReporterFlow({
|
|
|
832
834
|
apiUrl,
|
|
833
835
|
hasConfig,
|
|
834
836
|
resultsToUpload,
|
|
835
|
-
unresolvedIds
|
|
837
|
+
unresolvedIds,
|
|
838
|
+
skipMissing = false
|
|
836
839
|
}) {
|
|
837
840
|
const tcApiInstance = new TcApiClient({
|
|
838
841
|
accessToken: apiKey,
|
|
@@ -871,14 +874,21 @@ async function uploadUsingReporterFlow({
|
|
|
871
874
|
|
|
872
875
|
await tcApiInstance.getTestplanConfigs();
|
|
873
876
|
|
|
877
|
+
const userData = await tcApiInstance.getUserInfo();
|
|
878
|
+
if (!userData || !userData.id) {
|
|
879
|
+
throw new Error('User could not be fetched for this API key.');
|
|
880
|
+
}
|
|
881
|
+
|
|
874
882
|
const casesAssigned = await tcApiInstance.getAssignedCases();
|
|
875
883
|
console.log({ 'Total assigned cases found': Array.isArray(casesAssigned) ? casesAssigned.length : 0 });
|
|
876
884
|
|
|
877
885
|
const unmatchedCaseIds = new Set();
|
|
878
886
|
const unmatchedConfigIds = new Set();
|
|
887
|
+
const matchedExecCaseIds = new Set();
|
|
879
888
|
let matched = 0;
|
|
880
889
|
let updated = 0;
|
|
881
890
|
let errors = 0;
|
|
891
|
+
let skippedMissing = 0;
|
|
882
892
|
|
|
883
893
|
const configIds = Object.keys(resultsToUpload);
|
|
884
894
|
|
|
@@ -909,6 +919,7 @@ async function uploadUsingReporterFlow({
|
|
|
909
919
|
}
|
|
910
920
|
|
|
911
921
|
matched += 1;
|
|
922
|
+
matchedExecCaseIds.add(execCase.id);
|
|
912
923
|
|
|
913
924
|
const updatePayload = buildUpdatePayload({
|
|
914
925
|
execCase,
|
|
@@ -948,10 +959,54 @@ async function uploadUsingReporterFlow({
|
|
|
948
959
|
}
|
|
949
960
|
}
|
|
950
961
|
|
|
962
|
+
// --skip-missing: mark all assigned cases not present in the result file as skipped
|
|
963
|
+
if (skipMissing) {
|
|
964
|
+
const missingCases = casesAssigned.filter(
|
|
965
|
+
(assignedCase) => assignedCase && assignedCase.id && !matchedExecCaseIds.has(assignedCase.id)
|
|
966
|
+
);
|
|
967
|
+
|
|
968
|
+
if (missingCases.length) {
|
|
969
|
+
console.log(`\n⏭️ --skip-missing: marking ${missingCases.length} unmatched test case(s) as skipped...`);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
for (const execCase of missingCases) {
|
|
973
|
+
try {
|
|
974
|
+
const skipPayload = {
|
|
975
|
+
id: execCase.id,
|
|
976
|
+
test_plan_test_case: execCase.test_plan_test_case?.id || execCase.test_plan_test_case,
|
|
977
|
+
project: projectId,
|
|
978
|
+
status: RUN_RESULT_MAP.skip,
|
|
979
|
+
test_plan: testPlanId
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
if (execCase.test_plan_config && execCase.test_plan_config.id) {
|
|
983
|
+
skipPayload.test_plan_config = execCase.test_plan_config.id;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (Array.isArray(execCase?.test_case_revision?.steps) && execCase.test_case_revision.steps.length) {
|
|
987
|
+
skipPayload.step_wise_result = execCase.test_case_revision.steps.map((step) => ({
|
|
988
|
+
...step,
|
|
989
|
+
status: RUN_RESULT_MAP.skip
|
|
990
|
+
}));
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
const updateResult = await tcApiInstance.updateCaseRunResult(execCase.id, skipPayload);
|
|
994
|
+
if (updateResult && updateResult.id) {
|
|
995
|
+
skippedMissing += 1;
|
|
996
|
+
} else {
|
|
997
|
+
errors += 1;
|
|
998
|
+
}
|
|
999
|
+
} catch {
|
|
1000
|
+
errors += 1;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
951
1005
|
return {
|
|
952
1006
|
matched,
|
|
953
1007
|
updated,
|
|
954
1008
|
errors,
|
|
1009
|
+
skippedMissing,
|
|
955
1010
|
unresolvedIds: unique(unresolvedIds || []),
|
|
956
1011
|
unmatchedCaseIds: unique([...unmatchedCaseIds]),
|
|
957
1012
|
unmatchedConfigIds: unique([...unmatchedConfigIds])
|
|
@@ -994,6 +1049,9 @@ function validateRequiredOptions({ apiKey, project, testPlanId }) {
|
|
|
994
1049
|
function logUploadSummary(formatLabel, summary) {
|
|
995
1050
|
console.log(`✅ ${formatLabel} report processed (${summary.matched || 0} matched, ${summary.updated || 0} updated)`);
|
|
996
1051
|
|
|
1052
|
+
if (summary.skippedMissing) {
|
|
1053
|
+
console.log(`⏭️ ${summary.skippedMissing} test case(s) not in result file marked as skipped`);
|
|
1054
|
+
}
|
|
997
1055
|
if (summary.unresolvedIds?.length) {
|
|
998
1056
|
console.warn(`⚠️ ${summary.unresolvedIds.length} testcase(s) missing TestCollab ID`);
|
|
999
1057
|
}
|
|
@@ -1022,7 +1080,8 @@ export async function report(options) {
|
|
|
1022
1080
|
testPlanId,
|
|
1023
1081
|
format,
|
|
1024
1082
|
resultFile,
|
|
1025
|
-
apiUrl
|
|
1083
|
+
apiUrl,
|
|
1084
|
+
skipMissing
|
|
1026
1085
|
} = options;
|
|
1027
1086
|
|
|
1028
1087
|
// Resolve API key: --api-key flag takes precedence, then TESTCOLLAB_TOKEN env var
|
|
@@ -1069,7 +1128,8 @@ export async function report(options) {
|
|
|
1069
1128
|
apiUrl,
|
|
1070
1129
|
hasConfig: parsedReport.hasConfig,
|
|
1071
1130
|
resultsToUpload: parsedReport.resultsToUpload,
|
|
1072
|
-
unresolvedIds: parsedReport.unresolvedIds
|
|
1131
|
+
unresolvedIds: parsedReport.unresolvedIds,
|
|
1132
|
+
skipMissing: Boolean(skipMissing)
|
|
1073
1133
|
});
|
|
1074
1134
|
|
|
1075
1135
|
logUploadSummary('JUnit', summary);
|
|
@@ -1098,7 +1158,8 @@ export async function report(options) {
|
|
|
1098
1158
|
apiUrl,
|
|
1099
1159
|
hasConfig: parsedReport.hasConfig,
|
|
1100
1160
|
resultsToUpload: parsedReport.resultsToUpload,
|
|
1101
|
-
unresolvedIds: parsedReport.unresolvedIds
|
|
1161
|
+
unresolvedIds: parsedReport.unresolvedIds,
|
|
1162
|
+
skipMissing: Boolean(skipMissing)
|
|
1102
1163
|
});
|
|
1103
1164
|
|
|
1104
1165
|
logUploadSummary('Mochawesome', summary);
|
package/src/index.js
CHANGED
|
@@ -51,6 +51,7 @@ program
|
|
|
51
51
|
.requiredOption('--format <type>', 'Result format: mochawesome or junit')
|
|
52
52
|
.requiredOption('--result-file <path>', 'Path to test result file')
|
|
53
53
|
.option('--api-url <url>', 'TestCollab API base URL override', 'https://api.testcollab.io')
|
|
54
|
+
.option('--skip-missing', 'Mark test cases in the test plan but not in the result file as skipped', false)
|
|
54
55
|
.action(report);
|
|
55
56
|
|
|
56
57
|
// Add specgen command
|