@testspectra/cli 1.1.8-rc.2 → 1.1.8-rc.20

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.
Files changed (205) hide show
  1. package/README.md +47 -0
  2. package/bin/spectra.js +0 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/commands/__tests__/run-e2e.test.js +49 -49
  5. package/dist/commands/add.js +2 -1
  6. package/dist/commands/devices.js +1 -0
  7. package/dist/commands/doctor.js +1 -0
  8. package/dist/commands/init.js +150 -149
  9. package/dist/commands/install.js +2 -0
  10. package/dist/commands/license.js +1 -0
  11. package/dist/commands/test.js +5 -3
  12. package/dist/generator/__tests__/tsconfig-isolation.test.js +2 -1
  13. package/dist/generator/__tests__/type-generator.test.js +38 -0
  14. package/dist/generator/tsconfig-generator.js +1 -1
  15. package/dist/generator/type-generator.d.ts +5 -2
  16. package/dist/generator/type-generator.js +14 -17
  17. package/dist/index.js +11 -11
  18. package/dist/linter/__tests__/component-test-imports.test.js +51 -51
  19. package/dist/linter/__tests__/shared-lib-boundary.test.js +15 -15
  20. package/dist/linter/__tests__/spec-schema.test.js +167 -167
  21. package/dist/linter/discovery.d.ts +3 -3
  22. package/dist/linter/discovery.js +5 -5
  23. package/dist/linter/schemas/spec.schema.d.ts +2 -2
  24. package/dist/linter/schemas/suite.schema.d.ts +4 -4
  25. package/dist/runner/bridge.js +53 -4
  26. package/package.json +34 -29
  27. package/templates/default/CLAUDE.md +16 -16
  28. package/templates/default/global-hooks/after/mobile.hook.ts +3 -3
  29. package/templates/default/global-hooks/after/web.hook.ts +3 -3
  30. package/templates/default/global-hooks/before/mobile.hook.ts +3 -3
  31. package/templates/default/global-hooks/before/web.hook.ts +3 -3
  32. package/templates/default/package.json +2 -2
  33. package/templates/default/page-objects/AuthPage/mobile.ts +5 -5
  34. package/templates/default/page-objects/AuthPage/web.ts +10 -10
  35. package/templates/default/page-objects/MatchersPage/mobile.ts +70 -70
  36. package/templates/default/page-objects/MatchersPage/web.ts +43 -43
  37. package/templates/default/page-objects/NavigationPage/mobile.ts +14 -14
  38. package/templates/default/page-objects/NavigationPage/web.ts +8 -8
  39. package/templates/default/page-objects/PlaygroundPage/mobile.ts +42 -42
  40. package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
  41. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -53
  42. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -19
  43. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -31
  44. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -3
  45. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -3
  46. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -27
  47. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -19
  48. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -26
  49. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -30
  50. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -22
  51. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -27
  52. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -20
  53. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -24
  54. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -26
  55. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -37
  56. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -21
  57. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -29
  58. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -16
  59. package/templates/default/specs/ApiInterception/suite.md +10 -10
  60. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -30
  61. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -23
  62. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -16
  63. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -28
  64. package/templates/default/specs/ApiSeeding/suite.md +9 -9
  65. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -7
  66. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -19
  67. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -9
  68. package/templates/default/specs/Authentication/suite.md +9 -9
  69. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +9 -9
  70. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +17 -17
  71. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  72. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -17
  73. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -28
  74. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -26
  75. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -26
  76. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -19
  77. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +11 -11
  78. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +17 -17
  79. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  80. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -14
  81. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -21
  82. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -28
  83. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -29
  84. package/templates/default/specs/DeepLink/suite.md +9 -9
  85. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +11 -11
  86. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +17 -17
  87. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  88. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +29 -29
  89. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +19 -19
  90. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  91. package/templates/default/specs/ElementMatchers/suite.md +10 -10
  92. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  93. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  94. package/templates/default/specs/EnvironmentConfig/suite.md +9 -9
  95. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -26
  96. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -22
  97. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -13
  98. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -20
  99. package/templates/default/specs/PermissionRationale/suite.md +9 -9
  100. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +6 -6
  101. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +18 -18
  102. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  103. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +6 -6
  104. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +18 -18
  105. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  106. package/templates/default/spectra.config.ts +52 -52
  107. package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -4
  108. package/templates/default/support/actions/fillCredentials/web.action.ts +4 -4
  109. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -9
  110. package/templates/default/support/actions/seedSession/web.action.ts +13 -13
  111. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +5 -5
  112. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  113. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +6 -6
  114. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  115. package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm} +0 -0
  116. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  117. package/templates/nx/.nx/workspace-data/d/daemon.log +6269 -1577
  118. package/templates/nx/.nx/workspace-data/file-map.json +129 -129
  119. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -1
  120. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  121. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +535 -535
  122. package/templates/nx/.nx/workspace-data/project-graph.json +589 -590
  123. package/templates/nx/CLAUDE.md +10 -10
  124. package/templates/nx/package.json +2 -2
  125. package/templates/nx/packages/matchers/e2e/project.json +2 -2
  126. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -27
  127. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -27
  128. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -24
  129. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -24
  130. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -3
  131. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  132. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -3
  133. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +17 -17
  134. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -3
  135. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  136. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -3
  137. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  138. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -3
  139. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  140. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  141. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  142. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -9
  143. package/templates/nx/packages/playground/e2e/project.json +2 -2
  144. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -3
  145. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  146. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -3
  147. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  148. package/templates/nx/pnpm-lock.yaml +6663 -6663
  149. package/templates/nx/pnpm-workspace.yaml +9 -9
  150. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +33 -33
  151. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +91 -91
  152. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -5
  153. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -8
  154. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -26
  155. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +31 -31
  156. package/templates/nx/shared/testing/project.json +18 -19
  157. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -4
  158. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -4
  159. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -3
  160. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  161. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -4
  162. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  163. package/templates/nx/spectra.config.ts +52 -52
  164. package/templates/react-component/.cursorrules +17 -17
  165. package/templates/react-component/.vscode/extensions.json +5 -5
  166. package/templates/react-component/CLAUDE.md +14 -14
  167. package/templates/react-component/README.md +69 -69
  168. package/templates/react-component/fixtures/component-mock.json +12 -12
  169. package/templates/react-component/global-hooks/after/web.hook.ts +3 -3
  170. package/templates/react-component/global-hooks/before/web.hook.ts +3 -3
  171. package/templates/react-component/package.json +27 -27
  172. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -23
  173. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -23
  174. package/templates/react-component/page-objects/InputComponent/web.ts +13 -13
  175. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -18
  176. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -11
  177. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -18
  178. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -10
  179. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -18
  180. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -16
  181. package/templates/react-component/specs/BadgeComponent/suite.md +12 -12
  182. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -19
  183. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -28
  184. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -19
  185. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -22
  186. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -18
  187. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -8
  188. package/templates/react-component/specs/ButtonComponent/suite.md +12 -12
  189. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -20
  190. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -28
  191. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -18
  192. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -9
  193. package/templates/react-component/specs/InputComponent/suite.md +12 -12
  194. package/templates/react-component/spectra.config.ts +27 -27
  195. package/templates/react-component/src/components/Badge/Badge.tsx +60 -60
  196. package/templates/react-component/src/components/Button/Button.tsx +57 -57
  197. package/templates/react-component/src/components/Input/Input.tsx +41 -41
  198. package/templates/react-component/src/test-utils.tsx +23 -23
  199. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -9
  200. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -12
  201. package/bin/.testspectra-runner.hash +0 -1
  202. package/bin/testspectra-runner.exe +0 -0
  203. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal +0 -0
  204. package/templates/nx/.nx/workspace-data/d/server-process.json +0 -3
  205. /package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db} +0 -0
@@ -19,23 +19,23 @@ describe('Spec Schema Linter (Stage 1 & Stage 2)', () => {
19
19
  describe('Spec File Validation (spec.md)', () => {
20
20
  it('passes validation for a well-formed spec.md with frontmatter, H1 title, and Test Steps', () => {
21
21
  const specPath = path.join(tempDir, 'spec.md');
22
- fs.writeFileSync(specPath, `---
23
- id: TC-0001-valid
24
- title: Verify user authentication
25
- priority: High
26
- caseType: Positive
27
- status: ready-for-automation
28
- coverage:
29
- web: automated
30
- mobile: automated
31
- tags:
32
- - auth
33
- ---
34
-
35
- # Verify user authentication
36
-
37
- ## Test Steps
38
- 1. Open login page and submit credentials
22
+ fs.writeFileSync(specPath, `---
23
+ id: TC-0001-valid
24
+ title: Verify user authentication
25
+ priority: High
26
+ caseType: Positive
27
+ status: ready-for-automation
28
+ coverage:
29
+ web: automated
30
+ mobile: automated
31
+ tags:
32
+ - auth
33
+ ---
34
+
35
+ # Verify user authentication
36
+
37
+ ## Test Steps
38
+ 1. Open login page and submit credentials
39
39
  `);
40
40
  const result = SpecLinter.validateSpecFile(specPath);
41
41
  expect(result.diagnostics).toHaveLength(0);
@@ -43,16 +43,16 @@ tags:
43
43
  });
44
44
  it('reports testspectra/missing-frontmatter when required frontmatter attributes are missing', () => {
45
45
  const specPath = path.join(tempDir, 'spec.md');
46
- fs.writeFileSync(specPath, `---
47
- id: TC-0002-missing-attrs
48
- title: Missing priority and caseType
49
- status: draft
50
- ---
51
-
52
- # Missing priority and caseType
53
-
54
- ## Test Steps
55
- 1. Step one
46
+ fs.writeFileSync(specPath, `---
47
+ id: TC-0002-missing-attrs
48
+ title: Missing priority and caseType
49
+ status: draft
50
+ ---
51
+
52
+ # Missing priority and caseType
53
+
54
+ ## Test Steps
55
+ 1. Step one
56
56
  `);
57
57
  const result = SpecLinter.validateSpecFile(specPath);
58
58
  const codes = result.diagnostics.map((d) => d.code);
@@ -69,17 +69,17 @@ status: draft
69
69
  it('differentiates missing status (testspectra/missing-frontmatter) from invalid status value (testspectra/invalid-frontmatter-enum)', () => {
70
70
  // 1. Missing status -> testspectra/missing-frontmatter
71
71
  const missingSpecPath = path.join(tempDir, 'missing-status.spec.md');
72
- fs.writeFileSync(missingSpecPath, `---
73
- id: TC-0002-missing-status
74
- title: Missing Status Spec
75
- priority: High
76
- caseType: Positive
77
- ---
78
-
79
- # Missing Status Spec
80
-
81
- ## Test Steps
82
- 1. Step one
72
+ fs.writeFileSync(missingSpecPath, `---
73
+ id: TC-0002-missing-status
74
+ title: Missing Status Spec
75
+ priority: High
76
+ caseType: Positive
77
+ ---
78
+
79
+ # Missing Status Spec
80
+
81
+ ## Test Steps
82
+ 1. Step one
83
83
  `);
84
84
  const missingResult = SpecLinter.validateSpecFile(missingSpecPath);
85
85
  const missingStatusDiag = missingResult.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' &&
@@ -89,18 +89,18 @@ caseType: Positive
89
89
  expect(missingResult.diagnostics.some((d) => d.code === 'testspectra/invalid-frontmatter-enum')).toBe(false);
90
90
  // 2. Invalid status -> testspectra/invalid-frontmatter-enum
91
91
  const invalidSpecPath = path.join(tempDir, 'invalid-status.spec.md');
92
- fs.writeFileSync(invalidSpecPath, `---
93
- id: TC-0002-invalid-status
94
- title: Invalid Status Spec
95
- priority: High
96
- caseType: Positive
97
- status: not-a-valid-status
98
- ---
99
-
100
- # Invalid Status Spec
101
-
102
- ## Test Steps
103
- 1. Step one
92
+ fs.writeFileSync(invalidSpecPath, `---
93
+ id: TC-0002-invalid-status
94
+ title: Invalid Status Spec
95
+ priority: High
96
+ caseType: Positive
97
+ status: not-a-valid-status
98
+ ---
99
+
100
+ # Invalid Status Spec
101
+
102
+ ## Test Steps
103
+ 1. Step one
104
104
  `);
105
105
  const invalidResult = SpecLinter.validateSpecFile(invalidSpecPath);
106
106
  const invalidStatusDiag = invalidResult.diagnostics.find((d) => d.code === 'testspectra/invalid-frontmatter-enum' &&
@@ -111,18 +111,18 @@ status: not-a-valid-status
111
111
  });
112
112
  it('reports testspectra/invalid-frontmatter-enum when enum attributes have invalid values', () => {
113
113
  const specPath = path.join(tempDir, 'spec.md');
114
- fs.writeFileSync(specPath, `---
115
- id: TC-0003-invalid-enum
116
- title: Invalid priority value
117
- priority: SuperHigh
118
- caseType: MaybePositive
119
- status: unknown-status
120
- ---
121
-
122
- # Invalid priority value
123
-
124
- ## Test Steps
125
- 1. Step one
114
+ fs.writeFileSync(specPath, `---
115
+ id: TC-0003-invalid-enum
116
+ title: Invalid priority value
117
+ priority: SuperHigh
118
+ caseType: MaybePositive
119
+ status: unknown-status
120
+ ---
121
+
122
+ # Invalid priority value
123
+
124
+ ## Test Steps
125
+ 1. Step one
126
126
  `);
127
127
  const result = SpecLinter.validateSpecFile(specPath);
128
128
  const enumErrors = result.diagnostics.filter((d) => d.code === 'testspectra/invalid-frontmatter-enum');
@@ -133,16 +133,16 @@ status: unknown-status
133
133
  });
134
134
  it('reports testspectra/missing-frontmatter when required H1 title section is missing in spec.md body', () => {
135
135
  const specPath = path.join(tempDir, 'spec.md');
136
- fs.writeFileSync(specPath, `---
137
- id: TC-0004-no-h1
138
- title: No Heading Section
139
- priority: High
140
- caseType: Positive
141
- status: draft
142
- ---
143
-
144
- ## Test Steps
145
- 1. Step without H1
136
+ fs.writeFileSync(specPath, `---
137
+ id: TC-0004-no-h1
138
+ title: No Heading Section
139
+ priority: High
140
+ caseType: Positive
141
+ status: draft
142
+ ---
143
+
144
+ ## Test Steps
145
+ 1. Step without H1
146
146
  `);
147
147
  const result = SpecLinter.validateSpecFile(specPath);
148
148
  const missingHeading = result.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' && d.message.includes('expected top-level test title'));
@@ -151,17 +151,17 @@ status: draft
151
151
  });
152
152
  it('reports testspectra/missing-frontmatter when required ## Test Steps section is missing in spec.md body', () => {
153
153
  const specPath = path.join(tempDir, 'spec.md');
154
- fs.writeFileSync(specPath, `---
155
- id: TC-0005-no-steps
156
- title: No Steps Heading
157
- priority: High
158
- caseType: Positive
159
- status: draft
160
- ---
161
-
162
- # No Steps Heading
163
-
164
- Some narrative without Test Steps section.
154
+ fs.writeFileSync(specPath, `---
155
+ id: TC-0005-no-steps
156
+ title: No Steps Heading
157
+ priority: High
158
+ caseType: Positive
159
+ status: draft
160
+ ---
161
+
162
+ # No Steps Heading
163
+
164
+ Some narrative without Test Steps section.
165
165
  `);
166
166
  const result = SpecLinter.validateSpecFile(specPath);
167
167
  const missingSteps = result.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' && d.message.includes('expected "## Test Steps"'));
@@ -170,20 +170,20 @@ Some narrative without Test Steps section.
170
170
  });
171
171
  it('warns when multiple H1 headings are declared in spec.md body', () => {
172
172
  const specPath = path.join(tempDir, 'spec.md');
173
- fs.writeFileSync(specPath, `---
174
- id: TC-0006-multiple-h1
175
- title: Multiple Headings
176
- priority: High
177
- caseType: Positive
178
- status: draft
179
- ---
180
-
181
- # First Title
182
-
183
- # Second Title
184
-
185
- ## Test Steps
186
- 1. Step
173
+ fs.writeFileSync(specPath, `---
174
+ id: TC-0006-multiple-h1
175
+ title: Multiple Headings
176
+ priority: High
177
+ caseType: Positive
178
+ status: draft
179
+ ---
180
+
181
+ # First Title
182
+
183
+ # Second Title
184
+
185
+ ## Test Steps
186
+ 1. Step
187
187
  `);
188
188
  const result = SpecLinter.validateSpecFile(specPath);
189
189
  const multipleH1 = result.diagnostics.find((d) => d.severity === 'warning' && d.message.includes('Multiple top-level H1 headings'));
@@ -191,10 +191,10 @@ status: draft
191
191
  });
192
192
  it('reports testspectra/parse-error on malformed YAML syntax', () => {
193
193
  const specPath = path.join(tempDir, 'spec.md');
194
- fs.writeFileSync(specPath, `---
195
- id: [broken yaml
196
- title: Test
197
- ---
194
+ fs.writeFileSync(specPath, `---
195
+ id: [broken yaml
196
+ title: Test
197
+ ---
198
198
  `);
199
199
  const result = SpecLinter.validateSpecFile(specPath);
200
200
  expect(result.diagnostics.some((d) => d.code === 'testspectra/parse-error')).toBe(true);
@@ -203,16 +203,16 @@ title: Test
203
203
  describe('Suite File Validation (suite.md)', () => {
204
204
  it('passes validation for a well-formed suite.md with H1 suite heading', () => {
205
205
  const suitePath = path.join(tempDir, 'suite.md');
206
- fs.writeFileSync(suitePath, `---
207
- id: suite-auth
208
- name: Authentication
209
- description: User authentication flows
210
- executionOrder: 1.0
211
- parallel: true
212
- status: active
213
- ---
214
-
215
- # Suite: Authentication
206
+ fs.writeFileSync(suitePath, `---
207
+ id: suite-auth
208
+ name: Authentication
209
+ description: User authentication flows
210
+ executionOrder: 1.0
211
+ parallel: true
212
+ status: active
213
+ ---
214
+
215
+ # Suite: Authentication
216
216
  `);
217
217
  const result = SpecLinter.validateSuiteFile(suitePath);
218
218
  expect(result.diagnostics).toHaveLength(0);
@@ -220,24 +220,24 @@ status: active
220
220
  });
221
221
  it('reports testspectra/missing-frontmatter when suite id or title/name is missing', () => {
222
222
  const suitePath = path.join(tempDir, 'suite.md');
223
- fs.writeFileSync(suitePath, `---
224
- description: No id and no name
225
- ---
226
-
227
- # Suite: Incomplete
223
+ fs.writeFileSync(suitePath, `---
224
+ description: No id and no name
225
+ ---
226
+
227
+ # Suite: Incomplete
228
228
  `);
229
229
  const result = SpecLinter.validateSuiteFile(suitePath);
230
230
  expect(result.diagnostics.some((d) => d.code === 'testspectra/missing-frontmatter')).toBe(true);
231
231
  });
232
232
  it('reports testspectra/invalid-frontmatter-enum when suite status has invalid enum value', () => {
233
233
  const suitePath = path.join(tempDir, 'suite.md');
234
- fs.writeFileSync(suitePath, `---
235
- id: suite-invalid-status
236
- name: Invalid Status Suite
237
- status: unknown-status
238
- ---
239
-
240
- # Suite: Invalid Status Suite
234
+ fs.writeFileSync(suitePath, `---
235
+ id: suite-invalid-status
236
+ name: Invalid Status Suite
237
+ status: unknown-status
238
+ ---
239
+
240
+ # Suite: Invalid Status Suite
241
241
  `);
242
242
  const result = SpecLinter.validateSuiteFile(suitePath);
243
243
  const enumErr = result.diagnostics.find((d) => d.code === 'testspectra/invalid-frontmatter-enum');
@@ -246,13 +246,13 @@ status: unknown-status
246
246
  });
247
247
  it('supports in-memory content override for suite.md without writing to disk', () => {
248
248
  const suitePath = path.join(tempDir, 'non-existent-suite.md');
249
- const inMemoryContent = `---
250
- id: suite-in-memory
251
- title: In Memory Suite
252
- status: active
253
- ---
254
-
255
- # Suite: In Memory Suite
249
+ const inMemoryContent = `---
250
+ id: suite-in-memory
251
+ title: In Memory Suite
252
+ status: active
253
+ ---
254
+
255
+ # Suite: In Memory Suite
256
256
  `;
257
257
  const result = SpecLinter.validateSuiteFile(suitePath, inMemoryContent);
258
258
  expect(result.diagnostics).toHaveLength(0);
@@ -260,12 +260,12 @@ status: active
260
260
  });
261
261
  it('reports testspectra/missing-frontmatter when suite H1 title heading is missing', () => {
262
262
  const suitePath = path.join(tempDir, 'suite.md');
263
- fs.writeFileSync(suitePath, `---
264
- id: suite-no-heading
265
- name: No Heading
266
- ---
267
-
268
- Body without H1 suite heading.
263
+ fs.writeFileSync(suitePath, `---
264
+ id: suite-no-heading
265
+ name: No Heading
266
+ ---
267
+
268
+ Body without H1 suite heading.
269
269
  `);
270
270
  const result = SpecLinter.validateSuiteFile(suitePath);
271
271
  const headingErr = result.diagnostics.find((d) => d.code === 'testspectra/missing-frontmatter' && d.message.includes('expected top-level suite title'));
@@ -278,18 +278,18 @@ Body without H1 suite heading.
278
278
  const tc2Dir = path.join(tempDir, 'TC-2');
279
279
  fs.mkdirSync(tc1Dir, { recursive: true });
280
280
  fs.mkdirSync(tc2Dir, { recursive: true });
281
- const specContent = `---
282
- id: TC-DUPLICATE-001
283
- title: Duplicate test case
284
- priority: High
285
- caseType: Positive
286
- status: automated
287
- ---
288
-
289
- # Duplicate test case
290
-
291
- ## Test Steps
292
- 1. Duplicate step
281
+ const specContent = `---
282
+ id: TC-DUPLICATE-001
283
+ title: Duplicate test case
284
+ priority: High
285
+ caseType: Positive
286
+ status: automated
287
+ ---
288
+
289
+ # Duplicate test case
290
+
291
+ ## Test Steps
292
+ 1. Duplicate step
293
293
  `;
294
294
  fs.writeFileSync(path.join(tc1Dir, 'spec.md'), specContent);
295
295
  fs.writeFileSync(path.join(tc2Dir, 'spec.md'), specContent);
@@ -303,26 +303,26 @@ status: automated
303
303
  const validSuiteDir = path.join(tempDir, 'ValidSuite');
304
304
  const validTcDir = path.join(validSuiteDir, 'TC-001');
305
305
  fs.mkdirSync(validTcDir, { recursive: true });
306
- fs.writeFileSync(path.join(validSuiteDir, 'suite.md'), `---
307
- id: suite-valid
308
- name: Valid Suite
309
- status: active
310
- ---
311
-
312
- # Suite: Valid Suite
306
+ fs.writeFileSync(path.join(validSuiteDir, 'suite.md'), `---
307
+ id: suite-valid
308
+ name: Valid Suite
309
+ status: active
310
+ ---
311
+
312
+ # Suite: Valid Suite
313
313
  `);
314
- fs.writeFileSync(path.join(validTcDir, 'spec.md'), `---
315
- id: TC-VALID-001
316
- title: Valid Test Case
317
- priority: High
318
- caseType: Positive
319
- status: automated
320
- ---
321
-
322
- # Valid Test Case
323
-
324
- ## Test Steps
325
- 1. Open page
314
+ fs.writeFileSync(path.join(validTcDir, 'spec.md'), `---
315
+ id: TC-VALID-001
316
+ title: Valid Test Case
317
+ priority: High
318
+ caseType: Positive
319
+ status: automated
320
+ ---
321
+
322
+ # Valid Test Case
323
+
324
+ ## Test Steps
325
+ 1. Open page
326
326
  `);
327
327
  fs.writeFileSync(path.join(validTcDir, 'web.test.ts'), `it('Valid Test Case', async () => {});\n`);
328
328
  const result = SpecLinter.validateWorkspace(tempDir);
@@ -4,14 +4,14 @@ export interface WorkspaceAssets {
4
4
  fixtures: Set<string>;
5
5
  }
6
6
  /**
7
- * Scans a workspace directory for all `project.json` files that have both
8
- * "scope:shared" and "type:shared" tags, and returns their parent folder paths.
7
+ * Scans a workspace directory for all `project.json` files that have the
8
+ * "testspectra:shared" tag, and returns their parent folder paths.
9
9
  */
10
10
  export declare function findSharedLibraryRoots(workspaceDir: string): string[];
11
11
  /**
12
12
  * Resolves the root directory of a shared testing library if the file path is inside one.
13
13
  * Determines shared library status based on `project.json` containing
14
- * both "scope:shared" and "type:shared" tags in ancestor directories,
14
+ * the "testspectra:shared" tag in ancestor directories,
15
15
  * taking the parent folder of that `project.json` as the shared library root.
16
16
  */
17
17
  export declare function getSharedLibraryRoot(filePath: string, workspaceDir?: string): string | null;
@@ -1,8 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  /**
4
- * Scans a workspace directory for all `project.json` files that have both
5
- * "scope:shared" and "type:shared" tags, and returns their parent folder paths.
4
+ * Scans a workspace directory for all `project.json` files that have the
5
+ * "testspectra:shared" tag, and returns their parent folder paths.
6
6
  */
7
7
  export function findSharedLibraryRoots(workspaceDir) {
8
8
  const sharedRoots = [];
@@ -23,7 +23,7 @@ export function findSharedLibraryRoots(workspaceDir) {
23
23
  const raw = fs.readFileSync(path.join(dir, entry.name), 'utf-8');
24
24
  const parsed = JSON.parse(raw);
25
25
  const tags = Array.isArray(parsed.tags) ? parsed.tags : [];
26
- if (tags.includes('scope:shared') && tags.includes('type:shared')) {
26
+ if (tags.includes('testspectra:shared')) {
27
27
  sharedRoots.push(dir);
28
28
  }
29
29
  }
@@ -39,7 +39,7 @@ export function findSharedLibraryRoots(workspaceDir) {
39
39
  /**
40
40
  * Resolves the root directory of a shared testing library if the file path is inside one.
41
41
  * Determines shared library status based on `project.json` containing
42
- * both "scope:shared" and "type:shared" tags in ancestor directories,
42
+ * the "testspectra:shared" tag in ancestor directories,
43
43
  * taking the parent folder of that `project.json` as the shared library root.
44
44
  */
45
45
  export function getSharedLibraryRoot(filePath, workspaceDir) {
@@ -67,7 +67,7 @@ export function getSharedLibraryRoot(filePath, workspaceDir) {
67
67
  const raw = fs.readFileSync(projectJsonPath, 'utf-8');
68
68
  const projectConfig = JSON.parse(raw);
69
69
  const tags = Array.isArray(projectConfig.tags) ? projectConfig.tags : [];
70
- if (tags.includes('scope:shared') && tags.includes('type:shared')) {
70
+ if (tags.includes('testspectra:shared')) {
71
71
  return current;
72
72
  }
73
73
  return null;
@@ -36,8 +36,8 @@ export declare const SpecFrontmatterSchema: z.ZodObject<{
36
36
  tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
37
37
  }, "strip", z.ZodTypeAny, {
38
38
  id: string;
39
- status: "draft" | "ready-for-automation" | "automated" | "deprecated";
40
39
  title: string;
40
+ status: "draft" | "ready-for-automation" | "automated" | "deprecated";
41
41
  priority: "Critical" | "High" | "Medium" | "Low";
42
42
  caseType: "Positive" | "Negative" | "Edge";
43
43
  coverage?: {
@@ -47,8 +47,8 @@ export declare const SpecFrontmatterSchema: z.ZodObject<{
47
47
  tags?: string[] | undefined;
48
48
  }, {
49
49
  id: string;
50
- status: "draft" | "ready-for-automation" | "automated" | "deprecated";
51
50
  title: string;
51
+ status: "draft" | "ready-for-automation" | "automated" | "deprecated";
52
52
  priority: "Critical" | "High" | "Medium" | "Low";
53
53
  caseType: "Positive" | "Negative" | "Edge";
54
54
  coverage?: {
@@ -15,8 +15,8 @@ export declare const SuiteFrontmatterSchema: z.ZodEffects<z.ZodObject<{
15
15
  }, "strip", z.ZodTypeAny, {
16
16
  id: string;
17
17
  name?: string | undefined;
18
- status?: "active" | "draft" | "deprecated" | undefined;
19
18
  title?: string | undefined;
19
+ status?: "draft" | "deprecated" | "active" | undefined;
20
20
  tags?: string[] | undefined;
21
21
  description?: string | undefined;
22
22
  executionOrder?: number | undefined;
@@ -24,8 +24,8 @@ export declare const SuiteFrontmatterSchema: z.ZodEffects<z.ZodObject<{
24
24
  }, {
25
25
  id: string;
26
26
  name?: string | undefined;
27
- status?: "active" | "draft" | "deprecated" | undefined;
28
27
  title?: string | undefined;
28
+ status?: "draft" | "deprecated" | "active" | undefined;
29
29
  tags?: string[] | undefined;
30
30
  description?: string | undefined;
31
31
  executionOrder?: number | undefined;
@@ -33,8 +33,8 @@ export declare const SuiteFrontmatterSchema: z.ZodEffects<z.ZodObject<{
33
33
  }>, {
34
34
  id: string;
35
35
  name?: string | undefined;
36
- status?: "active" | "draft" | "deprecated" | undefined;
37
36
  title?: string | undefined;
37
+ status?: "draft" | "deprecated" | "active" | undefined;
38
38
  tags?: string[] | undefined;
39
39
  description?: string | undefined;
40
40
  executionOrder?: number | undefined;
@@ -42,8 +42,8 @@ export declare const SuiteFrontmatterSchema: z.ZodEffects<z.ZodObject<{
42
42
  }, {
43
43
  id: string;
44
44
  name?: string | undefined;
45
- status?: "active" | "draft" | "deprecated" | undefined;
46
45
  title?: string | undefined;
46
+ status?: "draft" | "deprecated" | "active" | undefined;
47
47
  tags?: string[] | undefined;
48
48
  description?: string | undefined;
49
49
  executionOrder?: number | undefined;
@@ -1,15 +1,60 @@
1
1
  import { spawn } from 'child_process';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
+ import { createRequire } from 'module';
4
5
  import { fileURLToPath } from 'url';
5
6
  const __filename = fileURLToPath(import.meta.url);
6
7
  const __dirname = path.dirname(__filename);
8
+ const require = createRequire(import.meta.url);
9
+ // Maps process.platform/process.arch to the optionalDependency package that carries the
10
+ // prebuilt native binary for that platform (see cli/npm/*). Keep in sync with the
11
+ // optionalDependencies list in cli/package.json and the packages built by
12
+ // scripts/release-cli-local.sh.
13
+ function platformPackageName() {
14
+ const { platform, arch } = process;
15
+ if (platform === 'darwin' && arch === 'arm64')
16
+ return '@testspectra/cli-darwin-arm64';
17
+ if (platform === 'linux' && arch === 'x64')
18
+ return '@testspectra/cli-linux-x64';
19
+ if (platform === 'win32' && arch === 'arm64')
20
+ return '@testspectra/cli-win32-arm64';
21
+ if (platform === 'win32' && arch === 'x64')
22
+ return '@testspectra/cli-win32-x64';
23
+ return null;
24
+ }
7
25
  export class RustCoreBridge {
8
26
  static resolveBinaryPath() {
9
- // 1. Look for precompiled release/debug binary in workspace target
10
- const workspaceRoot = path.resolve(__dirname, '../../..');
11
27
  const isWindows = process.platform === 'win32';
12
28
  const binaryName = isWindows ? 'testspectra-runner.exe' : 'testspectra-runner';
29
+ // 1. Production install: resolve the prebuilt binary from the platform-specific
30
+ // optionalDependency package (e.g. @testspectra/cli-darwin-arm64). This is how the
31
+ // published npm package ships native binaries for every supported platform without
32
+ // bundling all of them into @testspectra/cli itself.
33
+ const pkgName = platformPackageName();
34
+ if (pkgName) {
35
+ try {
36
+ const pkgJsonPath = require.resolve(`${pkgName}/package.json`);
37
+ const pkgBinPath = path.join(path.dirname(pkgJsonPath), 'bin', binaryName);
38
+ if (fs.existsSync(pkgBinPath)) {
39
+ if (process.platform !== 'win32') {
40
+ try {
41
+ const stat = fs.statSync(pkgBinPath);
42
+ if ((stat.mode & 0o111) === 0) {
43
+ fs.chmodSync(pkgBinPath, 0o755);
44
+ }
45
+ }
46
+ catch { }
47
+ }
48
+ return pkgBinPath;
49
+ }
50
+ }
51
+ catch {
52
+ // Optional dependency not installed — fall through to the workspace dev paths below.
53
+ }
54
+ }
55
+ // 2. Monorepo dev fallback: look for a precompiled release/debug binary in the workspace
56
+ // target, as produced by scripts/ensure-runner-binary.js during local development.
57
+ const workspaceRoot = path.resolve(__dirname, '../../..');
13
58
  const candidatePaths = [
14
59
  path.join(workspaceRoot, 'core/target/release', binaryName),
15
60
  path.join(workspaceRoot, 'target/release', binaryName),
@@ -29,8 +74,11 @@ export class RustCoreBridge {
29
74
  return `${p}.exe`;
30
75
  }
31
76
  }
32
- throw new Error("[TestSpectra] Native core runner binary 'testspectra-runner' not found.\n" +
33
- "Please build the project first using 'pnpm run build' or 'pnpm run build:core'.");
77
+ throw new Error(pkgName
78
+ ? `[TestSpectra] Native core runner binary not found for ${process.platform}/${process.arch}.\n` +
79
+ `Expected the optional dependency '${pkgName}' to provide it. Try reinstalling ` +
80
+ "('npm install' / 'pnpm install') without --no-optional, or build locally with 'pnpm run build:core'."
81
+ : `[TestSpectra] ${process.platform}/${process.arch} is not currently supported by @testspectra/cli.`);
34
82
  }
35
83
  static async run(options, reporter) {
36
84
  const binPath = this.resolveBinaryPath();
@@ -66,6 +114,7 @@ export class RustCoreBridge {
66
114
  return new Promise((resolve, reject) => {
67
115
  const child = spawn(binPath, [payload], {
68
116
  env: runnerEnv,
117
+ windowsHide: true,
69
118
  });
70
119
  let runStatus = 'failed';
71
120
  let runDuration = '0s';