@testcollab/cli 1.6.0 → 1.8.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 +83 -4
- 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
|
@@ -775,6 +775,18 @@ class TcApiClient {
|
|
|
775
775
|
return false;
|
|
776
776
|
}
|
|
777
777
|
}
|
|
778
|
+
|
|
779
|
+
async bulkSkipTestCases(data) {
|
|
780
|
+
try {
|
|
781
|
+
const result = await this.request('/testplantestcases/bulkAction', {
|
|
782
|
+
method: 'POST',
|
|
783
|
+
body: data
|
|
784
|
+
});
|
|
785
|
+
return result;
|
|
786
|
+
} catch {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
778
790
|
}
|
|
779
791
|
|
|
780
792
|
function findMatchingExecutedCase(casesAssigned, runRecord, hasConfig, configId) {
|
|
@@ -834,7 +846,8 @@ async function uploadUsingReporterFlow({
|
|
|
834
846
|
apiUrl,
|
|
835
847
|
hasConfig,
|
|
836
848
|
resultsToUpload,
|
|
837
|
-
unresolvedIds
|
|
849
|
+
unresolvedIds,
|
|
850
|
+
skipMissing = false
|
|
838
851
|
}) {
|
|
839
852
|
const tcApiInstance = new TcApiClient({
|
|
840
853
|
accessToken: apiKey,
|
|
@@ -883,9 +896,11 @@ async function uploadUsingReporterFlow({
|
|
|
883
896
|
|
|
884
897
|
const unmatchedCaseIds = new Set();
|
|
885
898
|
const unmatchedConfigIds = new Set();
|
|
899
|
+
const matchedExecCaseIds = new Set();
|
|
886
900
|
let matched = 0;
|
|
887
901
|
let updated = 0;
|
|
888
902
|
let errors = 0;
|
|
903
|
+
let skippedMissing = 0;
|
|
889
904
|
|
|
890
905
|
const configIds = Object.keys(resultsToUpload);
|
|
891
906
|
|
|
@@ -916,6 +931,7 @@ async function uploadUsingReporterFlow({
|
|
|
916
931
|
}
|
|
917
932
|
|
|
918
933
|
matched += 1;
|
|
934
|
+
matchedExecCaseIds.add(execCase.id);
|
|
919
935
|
|
|
920
936
|
const updatePayload = buildUpdatePayload({
|
|
921
937
|
execCase,
|
|
@@ -955,10 +971,67 @@ async function uploadUsingReporterFlow({
|
|
|
955
971
|
}
|
|
956
972
|
}
|
|
957
973
|
|
|
974
|
+
// --skip-missing: mark all assigned cases not present in the result file as skipped
|
|
975
|
+
if (skipMissing) {
|
|
976
|
+
const missingCases = casesAssigned.filter(
|
|
977
|
+
(assignedCase) => assignedCase && assignedCase.id && !matchedExecCaseIds.has(assignedCase.id)
|
|
978
|
+
);
|
|
979
|
+
|
|
980
|
+
if (missingCases.length) {
|
|
981
|
+
console.log(`\n⏭️ --skip-missing: marking ${missingCases.length} unmatched test case(s) as skipped...`);
|
|
982
|
+
|
|
983
|
+
// Group missing cases by config ID — the bulkAction endpoint requires
|
|
984
|
+
// test_plan_config when configs exist, so we need one call per config
|
|
985
|
+
const missingByConfig = {};
|
|
986
|
+
for (const c of missingCases) {
|
|
987
|
+
const tptc = c.test_plan_test_case;
|
|
988
|
+
const testCaseId = tptc && typeof tptc === 'object' ? tptc.test_case : null;
|
|
989
|
+
if (testCaseId === null || testCaseId === undefined) {
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
const configId = c.test_plan_config && typeof c.test_plan_config === 'object'
|
|
993
|
+
? String(c.test_plan_config.id)
|
|
994
|
+
: c.test_plan_config ? String(c.test_plan_config) : '0';
|
|
995
|
+
if (!missingByConfig[configId]) {
|
|
996
|
+
missingByConfig[configId] = [];
|
|
997
|
+
}
|
|
998
|
+
missingByConfig[configId].push(testCaseId);
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
for (const [configId, testCaseIds] of Object.entries(missingByConfig)) {
|
|
1002
|
+
if (!testCaseIds.length) {
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const bulkPayload = {
|
|
1007
|
+
actionType: 'skip',
|
|
1008
|
+
testcases: testCaseIds,
|
|
1009
|
+
project: projectId,
|
|
1010
|
+
testplan: testPlanId
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
if (configId && configId !== '0') {
|
|
1014
|
+
bulkPayload.test_plan_config = Number(configId);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const bulkResult = await tcApiInstance.bulkSkipTestCases(bulkPayload);
|
|
1018
|
+
|
|
1019
|
+
if (bulkResult && bulkResult.status === true) {
|
|
1020
|
+
skippedMissing += bulkResult.affected || testCaseIds.length;
|
|
1021
|
+
} else {
|
|
1022
|
+
const errMsg = (bulkResult && bulkResult.message) || 'Unknown error';
|
|
1023
|
+
console.warn(`⚠️ Bulk skip failed for config ${configId}: ${errMsg}`);
|
|
1024
|
+
errors += testCaseIds.length;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
958
1030
|
return {
|
|
959
1031
|
matched,
|
|
960
1032
|
updated,
|
|
961
1033
|
errors,
|
|
1034
|
+
skippedMissing,
|
|
962
1035
|
unresolvedIds: unique(unresolvedIds || []),
|
|
963
1036
|
unmatchedCaseIds: unique([...unmatchedCaseIds]),
|
|
964
1037
|
unmatchedConfigIds: unique([...unmatchedConfigIds])
|
|
@@ -1001,6 +1074,9 @@ function validateRequiredOptions({ apiKey, project, testPlanId }) {
|
|
|
1001
1074
|
function logUploadSummary(formatLabel, summary) {
|
|
1002
1075
|
console.log(`✅ ${formatLabel} report processed (${summary.matched || 0} matched, ${summary.updated || 0} updated)`);
|
|
1003
1076
|
|
|
1077
|
+
if (summary.skippedMissing) {
|
|
1078
|
+
console.log(`⏭️ ${summary.skippedMissing} test case(s) not in result file marked as skipped`);
|
|
1079
|
+
}
|
|
1004
1080
|
if (summary.unresolvedIds?.length) {
|
|
1005
1081
|
console.warn(`⚠️ ${summary.unresolvedIds.length} testcase(s) missing TestCollab ID`);
|
|
1006
1082
|
}
|
|
@@ -1029,7 +1105,8 @@ export async function report(options) {
|
|
|
1029
1105
|
testPlanId,
|
|
1030
1106
|
format,
|
|
1031
1107
|
resultFile,
|
|
1032
|
-
apiUrl
|
|
1108
|
+
apiUrl,
|
|
1109
|
+
skipMissing
|
|
1033
1110
|
} = options;
|
|
1034
1111
|
|
|
1035
1112
|
// Resolve API key: --api-key flag takes precedence, then TESTCOLLAB_TOKEN env var
|
|
@@ -1076,7 +1153,8 @@ export async function report(options) {
|
|
|
1076
1153
|
apiUrl,
|
|
1077
1154
|
hasConfig: parsedReport.hasConfig,
|
|
1078
1155
|
resultsToUpload: parsedReport.resultsToUpload,
|
|
1079
|
-
unresolvedIds: parsedReport.unresolvedIds
|
|
1156
|
+
unresolvedIds: parsedReport.unresolvedIds,
|
|
1157
|
+
skipMissing: Boolean(skipMissing)
|
|
1080
1158
|
});
|
|
1081
1159
|
|
|
1082
1160
|
logUploadSummary('JUnit', summary);
|
|
@@ -1105,7 +1183,8 @@ export async function report(options) {
|
|
|
1105
1183
|
apiUrl,
|
|
1106
1184
|
hasConfig: parsedReport.hasConfig,
|
|
1107
1185
|
resultsToUpload: parsedReport.resultsToUpload,
|
|
1108
|
-
unresolvedIds: parsedReport.unresolvedIds
|
|
1186
|
+
unresolvedIds: parsedReport.unresolvedIds,
|
|
1187
|
+
skipMissing: Boolean(skipMissing)
|
|
1109
1188
|
});
|
|
1110
1189
|
|
|
1111
1190
|
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
|