@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testcollab/cli",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Command-line interface for TestCollab operations",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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
- return extractTestCaseIdFromTitle(testTitle) || extractTestCaseIdFromTitle(fullTitle);
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