@testspectra/cli 1.1.8-rc.3 → 1.1.8-rc.30

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 (247) hide show
  1. package/README.md +50 -0
  2. package/bin/.testspectra-runner.hash +1 -1
  3. package/bin/spectra.js +0 -0
  4. package/dist/commands/__tests__/doctor-filter.test.js +37 -0
  5. package/dist/commands/__tests__/doctor-scope.test.d.ts +1 -0
  6. package/dist/commands/__tests__/doctor-scope.test.js +24 -0
  7. package/dist/commands/__tests__/run-e2e.test.js +49 -49
  8. package/dist/commands/__tests__/test-error-streaming.test.d.ts +1 -0
  9. package/dist/commands/__tests__/test-error-streaming.test.js +85 -0
  10. package/dist/commands/__tests__/test-scaffolding.test.d.ts +1 -0
  11. package/dist/commands/__tests__/test-scaffolding.test.js +32 -0
  12. package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
  13. package/dist/commands/__tests__/upgrade.test.js +95 -0
  14. package/dist/commands/add.js +5 -4
  15. package/dist/commands/devices.js +1 -0
  16. package/dist/commands/docs.d.ts +4 -0
  17. package/dist/commands/docs.js +17 -0
  18. package/dist/commands/doctor.d.ts +9 -1
  19. package/dist/commands/doctor.js +17 -3
  20. package/dist/commands/init.js +7 -160
  21. package/dist/commands/install.js +5 -0
  22. package/dist/commands/license.js +1 -0
  23. package/dist/commands/test.d.ts +13 -0
  24. package/dist/commands/test.js +115 -21
  25. package/dist/commands/upgrade.d.ts +12 -0
  26. package/dist/commands/upgrade.js +197 -0
  27. package/dist/generator/__tests__/tsconfig-isolation.test.js +2 -1
  28. package/dist/generator/__tests__/type-generator.test.js +38 -0
  29. package/dist/generator/architecture-doc-generator.d.ts +14 -0
  30. package/dist/generator/architecture-doc-generator.js +80 -0
  31. package/dist/generator/index.d.ts +1 -0
  32. package/dist/generator/index.js +1 -0
  33. package/dist/generator/tsconfig-generator.js +1 -1
  34. package/dist/generator/type-generator.d.ts +5 -2
  35. package/dist/generator/type-generator.js +14 -17
  36. package/dist/index.js +54 -26
  37. package/dist/linter/__tests__/component-test-imports.test.js +51 -51
  38. package/dist/linter/__tests__/shared-lib-boundary.test.js +15 -15
  39. package/dist/linter/__tests__/spec-schema.test.js +167 -167
  40. package/dist/linter/discovery.d.ts +3 -3
  41. package/dist/linter/discovery.js +7 -7
  42. package/dist/linter/schemas/spec.schema.d.ts +8 -8
  43. package/dist/linter/schemas/suite.schema.d.ts +4 -4
  44. package/dist/runner/bridge.d.ts +1 -1
  45. package/dist/runner/bridge.js +101 -11
  46. package/dist/runner/reporter.d.ts +1 -0
  47. package/dist/runner/reporter.js +12 -6
  48. package/dist/utils/background-update-check.d.ts +1 -0
  49. package/dist/utils/background-update-check.js +43 -0
  50. package/dist/utils/dependency-updates.d.ts +15 -0
  51. package/dist/utils/dependency-updates.js +107 -0
  52. package/dist/utils/update-notifier.d.ts +2 -0
  53. package/dist/utils/update-notifier.js +67 -0
  54. package/package.json +34 -30
  55. package/templates/default/CLAUDE.md +16 -16
  56. package/templates/default/global-hooks/after/mobile.hook.ts +3 -3
  57. package/templates/default/global-hooks/after/web.hook.ts +3 -3
  58. package/templates/default/global-hooks/before/mobile.hook.ts +3 -3
  59. package/templates/default/global-hooks/before/web.hook.ts +3 -3
  60. package/templates/default/package.json +2 -2
  61. package/templates/default/page-objects/AuthPage/mobile.ts +5 -5
  62. package/templates/default/page-objects/AuthPage/web.ts +10 -10
  63. package/templates/default/page-objects/MatchersPage/mobile.ts +70 -70
  64. package/templates/default/page-objects/MatchersPage/web.ts +43 -43
  65. package/templates/default/page-objects/NavigationPage/mobile.ts +14 -14
  66. package/templates/default/page-objects/NavigationPage/web.ts +8 -8
  67. package/templates/default/page-objects/PlaygroundPage/mobile.ts +42 -42
  68. package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
  69. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -53
  70. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -19
  71. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -31
  72. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -3
  73. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -3
  74. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -27
  75. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -19
  76. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -26
  77. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -30
  78. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -22
  79. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -27
  80. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -20
  81. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -24
  82. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -26
  83. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -37
  84. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -21
  85. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -29
  86. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -16
  87. package/templates/default/specs/ApiInterception/suite.md +10 -10
  88. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -30
  89. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -23
  90. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -16
  91. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -28
  92. package/templates/default/specs/ApiSeeding/suite.md +9 -9
  93. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -7
  94. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -19
  95. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -9
  96. package/templates/default/specs/Authentication/suite.md +9 -9
  97. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +9 -9
  98. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +17 -17
  99. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  100. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -17
  101. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -28
  102. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -26
  103. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -26
  104. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -19
  105. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +11 -11
  106. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +17 -17
  107. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  108. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -14
  109. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -21
  110. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -28
  111. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -29
  112. package/templates/default/specs/DeepLink/suite.md +9 -9
  113. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +11 -11
  114. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +17 -17
  115. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  116. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +29 -29
  117. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +19 -19
  118. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  119. package/templates/default/specs/ElementMatchers/suite.md +10 -10
  120. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  121. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  122. package/templates/default/specs/EnvironmentConfig/suite.md +9 -9
  123. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -26
  124. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -22
  125. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -13
  126. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -20
  127. package/templates/default/specs/PermissionRationale/suite.md +9 -9
  128. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +6 -6
  129. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +18 -18
  130. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  131. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +6 -6
  132. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +18 -18
  133. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  134. package/templates/default/spectra.config.ts +52 -52
  135. package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -4
  136. package/templates/default/support/actions/fillCredentials/web.action.ts +4 -4
  137. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -9
  138. package/templates/default/support/actions/seedSession/web.action.ts +13 -13
  139. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +5 -5
  140. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  141. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +6 -6
  142. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  143. package/templates/docs/ARCHITECTURE.default.md +26 -0
  144. package/templates/docs/ARCHITECTURE.nx.md +119 -0
  145. package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db} +0 -0
  146. package/templates/nx/.nx/workspace-data/d/daemon.log +1012 -1612
  147. package/templates/nx/.nx/workspace-data/file-map.json +123 -137
  148. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -1
  149. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  150. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +535 -535
  151. package/templates/nx/.nx/workspace-data/project-graph.json +592 -591
  152. package/templates/nx/.nx/workspace-data/source-maps.json +336 -0
  153. package/templates/nx/CLAUDE.md +10 -10
  154. package/templates/nx/package.json +3 -3
  155. package/templates/nx/packages/matchers/e2e/project.json +2 -2
  156. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -27
  157. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -27
  158. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -24
  159. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -24
  160. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -3
  161. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  162. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -3
  163. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +17 -17
  164. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -3
  165. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  166. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -3
  167. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  168. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -3
  169. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  170. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  171. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  172. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -9
  173. package/templates/nx/packages/playground/e2e/project.json +2 -2
  174. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -3
  175. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  176. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -3
  177. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  178. package/templates/nx/pnpm-lock.yaml +6663 -6663
  179. package/templates/nx/pnpm-workspace.yaml +9 -9
  180. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +33 -33
  181. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +91 -91
  182. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -5
  183. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -8
  184. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -26
  185. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +31 -31
  186. package/templates/nx/shared/testing/project.json +18 -19
  187. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -4
  188. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -4
  189. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -3
  190. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  191. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -4
  192. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  193. package/templates/nx/spectra.config.ts +52 -52
  194. package/templates/react-component/.cursorrules +17 -17
  195. package/templates/react-component/.vscode/extensions.json +5 -5
  196. package/templates/react-component/CLAUDE.md +14 -14
  197. package/templates/react-component/README.md +69 -69
  198. package/templates/react-component/fixtures/component-mock.json +12 -12
  199. package/templates/react-component/global-hooks/after/web.hook.ts +3 -3
  200. package/templates/react-component/global-hooks/before/web.hook.ts +3 -3
  201. package/templates/react-component/package.json +27 -27
  202. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -23
  203. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -23
  204. package/templates/react-component/page-objects/InputComponent/web.ts +13 -13
  205. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -18
  206. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -11
  207. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -18
  208. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -10
  209. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -18
  210. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -16
  211. package/templates/react-component/specs/BadgeComponent/suite.md +12 -12
  212. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -19
  213. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +25 -28
  214. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -19
  215. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +13 -22
  216. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -18
  217. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -8
  218. package/templates/react-component/specs/ButtonComponent/suite.md +12 -12
  219. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -20
  220. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +24 -28
  221. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -18
  222. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -9
  223. package/templates/react-component/specs/InputComponent/suite.md +12 -12
  224. package/templates/react-component/spectra.config.ts +17 -27
  225. package/templates/react-component/src/components/Badge/Badge.tsx +60 -60
  226. package/templates/react-component/src/components/Button/Button.tsx +59 -57
  227. package/templates/react-component/src/components/Input/Input.tsx +40 -41
  228. package/templates/react-component/src/test-utils.tsx +23 -23
  229. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -9
  230. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -12
  231. package/bin/testspectra-runner.exe +0 -0
  232. package/dist/.tsbuildinfo +0 -1
  233. package/dist/commands/update.d.ts +0 -3
  234. package/dist/commands/update.js +0 -109
  235. package/dist/lifecycle/__tests__/lifecycle-engine.test.js +0 -290
  236. package/dist/lifecycle/hook-discovery.d.ts +0 -24
  237. package/dist/lifecycle/hook-discovery.js +0 -60
  238. package/dist/lifecycle/hook-dispatcher.d.ts +0 -34
  239. package/dist/lifecycle/hook-dispatcher.js +0 -190
  240. package/dist/lifecycle/index.d.ts +0 -3
  241. package/dist/lifecycle/index.js +0 -3
  242. package/dist/lifecycle/parallel-grouping.d.ts +0 -27
  243. package/dist/lifecycle/parallel-grouping.js +0 -151
  244. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db +0 -0
  245. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal +0 -0
  246. package/templates/nx/.nx/workspace-data/d/server-process.json +0 -3
  247. /package/dist/{lifecycle/__tests__/lifecycle-engine.test.d.ts → commands/__tests__/doctor-filter.test.d.ts} +0 -0
@@ -41,7 +41,8 @@ describe('Platform TSConfigs & Type Isolation (docs/v2/cli/tsconfig-platform-iso
41
41
  expect(rootConfig.references).toEqual([{ path: './.testspectra/tsconfig.json' }]);
42
42
  // Verify dynamic master solution references all 5 platform solution tsconfigs in .testspectra/tsconfig/
43
43
  const solutionConfig = JSON.parse(fs.readFileSync(path.join(tmpDir, '.testspectra', 'tsconfig.json'), 'utf-8'));
44
- expect(solutionConfig.compilerOptions.noEmit).toBe(true);
44
+ expect(solutionConfig.compilerOptions.composite).toBe(true);
45
+ expect(solutionConfig.compilerOptions.noEmit).toBeUndefined();
45
46
  expect(solutionConfig.references).toEqual([
46
47
  { path: './tsconfig/web.json' },
47
48
  { path: './tsconfig/mobile.json' },
@@ -383,4 +383,42 @@ async function verifyZeroImport() {
383
383
  expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'))).toBe(false);
384
384
  expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'common.d.ts'))).toBe(false);
385
385
  });
386
+ it('nx monorepo: does NOT treat feature library as shared library and discovers nested e2e subproject', () => {
387
+ // 1. Shared library with testspectra:shared
388
+ const sharedDir = path.join(tmpDir, 'shared', 'testing');
389
+ fs.mkdirSync(path.join(sharedDir, 'page-objects', 'SharedNav'), { recursive: true });
390
+ fs.writeFileSync(path.join(sharedDir, 'project.json'), JSON.stringify({ name: 'shared-testing', projectType: 'library', tags: ['testspectra:shared'] }));
391
+ fs.writeFileSync(path.join(sharedDir, 'page-objects', 'SharedNav', 'common.ts'), 'export default class SharedNav {}');
392
+ // 2. Nx Feature library (projectType: "library", standard Nx tags, NO testspectra tags)
393
+ const featureLibDir = path.join(tmpDir, 'packages', 'features', 'auth');
394
+ fs.mkdirSync(featureLibDir, { recursive: true });
395
+ fs.writeFileSync(path.join(featureLibDir, 'project.json'), JSON.stringify({
396
+ name: '@tagsamurai/feature-auth',
397
+ projectType: 'library',
398
+ sourceRoot: 'packages/features/auth',
399
+ targets: {},
400
+ tags: ['type:feature', 'scope:feature-auth'],
401
+ }));
402
+ // 3. Nested E2E subproject inside feature library
403
+ const featureE2eDir = path.join(featureLibDir, 'e2e');
404
+ fs.mkdirSync(path.join(featureE2eDir, 'specs', 'AuthSuite', 'TC-001'), { recursive: true });
405
+ fs.writeFileSync(path.join(featureE2eDir, 'project.json'), JSON.stringify({
406
+ name: 'feature-auth-e2e',
407
+ projectType: 'application',
408
+ tags: ['testspectra:e2e', 'testspectra:scope:feature-auth'],
409
+ }));
410
+ fs.writeFileSync(path.join(featureE2eDir, 'specs', 'AuthSuite', 'TC-001', 'web.test.ts'), 'it("auth test", async () => {});');
411
+ const scopes = TypeGenerator.getEntityScopes(tmpDir);
412
+ expect(scopes).toHaveLength(2);
413
+ const sharedScope = scopes.find((s) => s.isShared);
414
+ expect(sharedScope).toBeDefined();
415
+ expect(sharedScope?.name).toBe('shared');
416
+ expect(sharedScope?.dir).toBe(path.resolve(sharedDir));
417
+ const featureScope = scopes.find((s) => !s.isShared);
418
+ expect(featureScope).toBeDefined();
419
+ expect(featureScope?.name).toBe('feature-auth');
420
+ expect(featureScope?.dir).toBe(path.resolve(featureE2eDir));
421
+ // Verify packages/features/auth is NOT in scopes
422
+ expect(scopes.some((s) => s.dir === path.resolve(featureLibDir))).toBe(false);
423
+ });
386
424
  });
@@ -0,0 +1,14 @@
1
+ export interface ArchitectureDocResult {
2
+ mode: 'nx' | 'default';
3
+ path: string;
4
+ sharedTestingRelPath?: string;
5
+ featureCount?: number;
6
+ }
7
+ export declare function buildFeatureModulesSection(cwd: string, featureE2eDirs: string[]): string;
8
+ /**
9
+ * (Re)generates `ARCHITECTURE.md` from the current on-disk workspace state — no prompts, no other
10
+ * scaffolding side effects. Nx/monorepo mode is auto-detected via `TsConfigGenerator.getEntityScopes`
11
+ * (the same shared/feature discovery `sync-types` already relies on), so this stays accurate after
12
+ * features are added/removed/renamed without needing to re-run `spectra init`.
13
+ */
14
+ export declare function generateArchitectureDoc(cwd: string): ArchitectureDocResult;
@@ -0,0 +1,80 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { TsConfigGenerator } from './tsconfig-generator.js';
5
+ function resolveDocsTemplateDir() {
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const candidates = [
9
+ path.resolve(__dirname, '../templates/docs'),
10
+ path.resolve(__dirname, '../../templates/docs'),
11
+ path.resolve(__dirname, '../../../templates/docs'),
12
+ ];
13
+ return candidates.find((d) => fs.existsSync(d)) ?? candidates[candidates.length - 1];
14
+ }
15
+ function renderArchitectureDoc(templatePath, replacements) {
16
+ let content = fs.readFileSync(templatePath, 'utf-8');
17
+ for (const [token, value] of Object.entries(replacements)) {
18
+ content = content.replaceAll(token, value);
19
+ }
20
+ return content;
21
+ }
22
+ /** Reads a feature e2e dir's project.json for its Nx project name, falling back to `<parentDirName>-<e2eDirName>`. */
23
+ function resolveNxProjectName(featureDir) {
24
+ const parentName = path.basename(path.dirname(featureDir));
25
+ const fallback = `${parentName}-${path.basename(featureDir)}`;
26
+ const projJsonPath = path.join(featureDir, 'project.json');
27
+ if (!fs.existsSync(projJsonPath))
28
+ return fallback;
29
+ try {
30
+ const pObj = JSON.parse(fs.readFileSync(projJsonPath, 'utf-8'));
31
+ return typeof pObj.name === 'string' && pObj.name ? pObj.name : fallback;
32
+ }
33
+ catch {
34
+ return fallback;
35
+ }
36
+ }
37
+ export function buildFeatureModulesSection(cwd, featureE2eDirs) {
38
+ return featureE2eDirs
39
+ .map((d) => {
40
+ const rel = path.relative(cwd, d).replace(/\\/g, '/');
41
+ const featureName = path.basename(path.dirname(d));
42
+ const e2eName = resolveNxProjectName(d);
43
+ return `- \`./${rel}\` (**${featureName}**, Nx Project: \`${e2eName}\`):
44
+ - \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
45
+ - \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
46
+ - \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
47
+ })
48
+ .join('\n');
49
+ }
50
+ /**
51
+ * (Re)generates `ARCHITECTURE.md` from the current on-disk workspace state — no prompts, no other
52
+ * scaffolding side effects. Nx/monorepo mode is auto-detected via `TsConfigGenerator.getEntityScopes`
53
+ * (the same shared/feature discovery `sync-types` already relies on), so this stays accurate after
54
+ * features are added/removed/renamed without needing to re-run `spectra init`.
55
+ */
56
+ export function generateArchitectureDoc(cwd) {
57
+ const docsTemplateDir = resolveDocsTemplateDir();
58
+ const destPath = path.join(cwd, 'ARCHITECTURE.md');
59
+ const scopes = TsConfigGenerator.getEntityScopes(cwd);
60
+ const isNxMode = scopes.length > 0 || fs.existsSync(path.join(cwd, 'nx.json'));
61
+ if (!isNxMode) {
62
+ fs.copyFileSync(path.join(docsTemplateDir, 'ARCHITECTURE.default.md'), destPath);
63
+ return { mode: 'default', path: destPath };
64
+ }
65
+ const sharedScope = scopes.find((s) => s.isShared);
66
+ const featureE2eDirs = scopes.filter((s) => !s.isShared).map((s) => s.dir);
67
+ const sharedTestingRelPath = sharedScope ? path.relative(cwd, sharedScope.dir).replace(/\\/g, '/') : 'shared/testing';
68
+ const e2eFolderName = featureE2eDirs.length > 0 ? path.basename(featureE2eDirs[0]) : 'e2e';
69
+ const content = renderArchitectureDoc(path.join(docsTemplateDir, 'ARCHITECTURE.nx.md'), {
70
+ __SHARED_TESTING_REL_PATH__: sharedTestingRelPath,
71
+ __FEATURE_COUNT__: String(featureE2eDirs.length),
72
+ __FEATURE_MODULES_SECTION__: buildFeatureModulesSection(cwd, featureE2eDirs),
73
+ __E2E_FOLDER_NAME__: e2eFolderName,
74
+ __SAMPLE_FEATURE_E2E_PATH__: featureE2eDirs.length > 0
75
+ ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/')
76
+ : `modules/auth/${e2eFolderName}`,
77
+ });
78
+ fs.writeFileSync(destPath, content, 'utf-8');
79
+ return { mode: 'nx', path: destPath, sharedTestingRelPath, featureCount: featureE2eDirs.length };
80
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './type-generator.js';
2
2
  export * from './tsconfig-generator.js';
3
+ export * from './architecture-doc-generator.js';
@@ -1,2 +1,3 @@
1
1
  export * from './type-generator.js';
2
2
  export * from './tsconfig-generator.js';
3
+ export * from './architecture-doc-generator.js';
@@ -16,7 +16,7 @@ export class TsConfigGenerator {
16
16
  moduleResolution: 'NodeNext',
17
17
  skipLibCheck: true,
18
18
  strict: true,
19
- noEmit: true,
19
+ composite: true,
20
20
  },
21
21
  files: [],
22
22
  references: references || [
@@ -9,8 +9,11 @@ export interface EntityScope {
9
9
  * Discovers all feature directories and shared library directories.
10
10
  * Standard discovery hierarchy:
11
11
  * 1. Nx / Monorepo Project Descriptors (`project.json`):
12
- * - projectType: "library" OR tags: ["type:shared", "type:library", "scope:shared"] => Shared Library Scope (isShared: true)
13
- * - projectType: "application" OR tags: ["type:e2e", "type:app"] => Feature Scope (isShared: false)
12
+ * - tags: ["testspectra:shared"] => Shared Library Scope (isShared: true)
13
+ * - tags: ["testspectra:e2e"] or ["testspectra:scope:<name>"] => Feature Scope (isShared: false)
14
+ * - Generic Nx libraries/apps without testspectra tags are not TestSpectra scopes and recursion
15
+ * continues into their subdirectories so nested e2e projects (e.g. `packages/features/auth/e2e`)
16
+ * are discovered.
14
17
  * 2. Explicit User Configuration (`tsconfig.spectra.shared.json`):
15
18
  * - Custom include path for shared testing utilities
16
19
  * 3. Dynamic Workspace Filesystem Discovery (Fallback for non-Nx / custom layouts):
@@ -13,8 +13,11 @@ export const PLATFORM_HIERARCHY = {
13
13
  * Discovers all feature directories and shared library directories.
14
14
  * Standard discovery hierarchy:
15
15
  * 1. Nx / Monorepo Project Descriptors (`project.json`):
16
- * - projectType: "library" OR tags: ["type:shared", "type:library", "scope:shared"] => Shared Library Scope (isShared: true)
17
- * - projectType: "application" OR tags: ["type:e2e", "type:app"] => Feature Scope (isShared: false)
16
+ * - tags: ["testspectra:shared"] => Shared Library Scope (isShared: true)
17
+ * - tags: ["testspectra:e2e"] or ["testspectra:scope:<name>"] => Feature Scope (isShared: false)
18
+ * - Generic Nx libraries/apps without testspectra tags are not TestSpectra scopes and recursion
19
+ * continues into their subdirectories so nested e2e projects (e.g. `packages/features/auth/e2e`)
20
+ * are discovered.
18
21
  * 2. Explicit User Configuration (`tsconfig.spectra.shared.json`):
19
22
  * - Custom include path for shared testing utilities
20
23
  * 3. Dynamic Workspace Filesystem Discovery (Fallback for non-Nx / custom layouts):
@@ -48,16 +51,9 @@ export function discoverEntityScopes(cwd) {
48
51
  if (fs.existsSync(projectJsonPath)) {
49
52
  try {
50
53
  const pObj = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
51
- const projectType = pObj.projectType;
52
54
  const tags = Array.isArray(pObj.tags) ? pObj.tags : [];
53
- const isSharedLib = projectType === 'library' ||
54
- tags.includes('type:shared') ||
55
- tags.includes('type:library') ||
56
- tags.includes('scope:shared');
57
- const isE2eApp = projectType === 'application' ||
58
- tags.includes('type:e2e') ||
59
- tags.includes('type:app') ||
60
- tags.some((t) => t.startsWith('scope:') && t !== 'scope:shared');
55
+ const isSharedLib = tags.includes('testspectra:shared');
56
+ const isE2eApp = tags.includes('testspectra:e2e') || tags.some((t) => t.startsWith('testspectra:scope:'));
61
57
  if (isSharedLib) {
62
58
  if (!scopes.some((s) => s.dir === dir)) {
63
59
  scopes.push({ name: 'shared', dir, isShared: true });
@@ -65,9 +61,9 @@ export function discoverEntityScopes(cwd) {
65
61
  return; // Do not recurse into subdirectories of an identified project
66
62
  }
67
63
  else if (isE2eApp) {
68
- // Extract scope name from tags (e.g. "scope:matchers" => "matchers") or project name
69
- const scopeTag = tags.find((t) => t.startsWith('scope:') && t !== 'scope:shared');
70
- let featureName = scopeTag ? scopeTag.replace('scope:', '') : '';
64
+ // Extract scope name from tags (e.g. "testspectra:scope:matchers" => "matchers") or project name
65
+ const scopeTag = tags.find((t) => t.startsWith('testspectra:scope:'));
66
+ let featureName = scopeTag ? scopeTag.replace('testspectra:scope:', '') : '';
71
67
  if (!featureName && pObj.name) {
72
68
  featureName = pObj.name.replace(/-e2e$/, '').replace(/^e2e-/, '');
73
69
  }
@@ -329,6 +325,9 @@ export class TypeGenerator {
329
325
  const hierarchy = PLATFORM_HIERARCHY[platform];
330
326
  let content = `// Auto-generated ambient declarations for TestSpectra [${platform.toUpperCase()}]\n`;
331
327
  content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
328
+ content += `/// <reference types="@testspectra/matchers" />\n`;
329
+ content += `/// <reference path="./fixtures.d.ts" />\n`;
330
+ content += `/// <reference path="${envRefPath}" />\n\n`;
332
331
  // TestSpectra's Rust-native orchestrator worker (core/orchestrator/src/worker/bdd_runner.ts)
333
332
  // registers its own `it`/`test`/lifecycle-hook globals directly — there is no WDIO or Mocha
334
333
  // runtime involved at all (not just for component testing; this is true across every
@@ -346,9 +345,7 @@ export class TypeGenerator {
346
345
  // — the Rust orchestrator's BDD runtime has no `expect` global at all.
347
346
  content += `declare const expect: import('@testspectra/react').ExpectStatic;\n`;
348
347
  }
349
- content += `/// <reference types="@testspectra/matchers" />\n`;
350
- content += `/// <reference path="./fixtures.d.ts" />\n`;
351
- content += `/// <reference path="${envRefPath}" />\n\n`;
348
+ content += `\n`;
352
349
  // 1. Page Objects Global Declarations
353
350
  const declaredPOMs = new Set();
354
351
  for (const poDir of poDirs) {
package/dist/index.js CHANGED
@@ -7,12 +7,13 @@ import { initCommand } from './commands/init.js';
7
7
  import { installCommand } from './commands/install.js';
8
8
  import { runCommand } from './commands/run.js';
9
9
  import { testCommand } from './commands/test.js';
10
- import { updateCommand } from './commands/update.js';
10
+ import { upgradeCommand } from './commands/upgrade.js';
11
11
  import { watchCommand } from './commands/watch.js';
12
12
  import { syncTypesCommand } from './commands/sync-types.js';
13
13
  import { refactorCommand } from './commands/refactor.js';
14
14
  import { lintCommand } from './commands/lint.js';
15
15
  import { licenseCommand } from './commands/license.js';
16
+ import { docsUpdateCommand } from './commands/docs.js';
16
17
  import { skillsInitCommand, skillsListCommand, skillsAddCommand, skillsUpdateCommand, skillsRemoveCommand, } from './commands/skills.js';
17
18
  export * from './config/schema.js';
18
19
  export * from './config/loader.js';
@@ -24,10 +25,10 @@ export * from './types/generator.js';
24
25
  export * from '@testspectra/matchers';
25
26
  export function createCliProgram() {
26
27
  const program = new Command();
27
- program.name('spectra').description('TestSpectra Zero-Config Cross-Platform Test Runner CLI').version('1.0.0');
28
+ program.name('spectra').description('TestSpectra Cross-Platform Test Runner CLI').version('1.0.0');
28
29
  program
29
30
  .command('init')
30
- .description('Initialize testspectra.config.json and project directories')
31
+ .description('Initialize spectra.config.ts and project directories')
31
32
  .option('-t, --template <template>', 'Project template: default, nx, react-component')
32
33
  .option('-f, --force', 'Overwrite existing configuration if present')
33
34
  .action(initCommand);
@@ -40,15 +41,16 @@ export function createCliProgram() {
40
41
  program
41
42
  .command('install [dependency]')
42
43
  .description('Install missing testing dependencies, browser drivers, and workspace packages')
43
- .option('-f, --force', 'Force re-installation of dependencies')
44
- .addHelpText('after', `
45
- Examples:
46
- $ spectra install Install all missing required dependencies automatically
47
- $ spectra install chrome Install Google Chrome for Testing & matching Chromedriver
48
- $ spectra install adb Install Google Android SDK Platform Tools (ADB)
49
- $ spectra install bun Install Bun runtime
50
- $ spectra install android-driver Install Spectra Android Native Driver & WDIO Service
51
- $ spectra install wdio Install WebDriverIO core packages and matchers`)
44
+ .option('-f, --force', 'Remove existing artifacts and perform a clean reinstall of the dependency')
45
+ .addHelpText('after', `
46
+ Examples:
47
+ $ spectra install Install all missing required dependencies automatically
48
+ $ spectra install chrome Install Google Chrome for Testing & matching Chromedriver
49
+ $ spectra install adb Install Google Android SDK Platform Tools (ADB)
50
+ $ spectra install bun Install Bun runtime
51
+ $ spectra install android-agent Install Spectra Android Native Agent (spectra-agent.jar)
52
+ $ spectra install android-agent -f Clean reinstall Spectra Android Native Agent
53
+ $ spectra install packages Install TestSpectra CLI, Matchers & Android Agent packages`)
52
54
  .action(installCommand);
53
55
  program
54
56
  .command('demo')
@@ -85,30 +87,41 @@ Examples:
85
87
  .action(lintCommand);
86
88
  program
87
89
  .command('doctor')
88
- .description('Verify local environment prerequisites (ADB, Spectra Android Driver, Chrome, Bun)')
90
+ .description('Verify local environment prerequisites (Bun, Git, ADB, Chrome, Spectra Android Agent, workspace packages)')
89
91
  .option('-v, --verbose', 'Show full installation paths for all detected runtimes and packages')
92
+ .option('--scope <category>', 'Only check one category: core, web, mobile (default: all)')
93
+ .option('--only <keys>', 'Only check specific dependencies (comma-separated): bun, git, cli, chrome, matchers, adb, agent')
90
94
  .option('--fix', 'Attempt automatic fix / download of missing tools')
91
95
  .option('--json', 'Output diagnostic results as JSON')
92
96
  .action(doctorCommand);
93
97
  program
94
- .command('update')
95
- .description('Update @testspectra/cli and @testspectra/matchers to the latest version from npm')
98
+ .command('upgrade [packages...]')
99
+ .alias('update')
100
+ .description('Upgrade TestSpectra ecosystem packages and system drivers to the latest versions')
96
101
  .option('--check', 'Only check for available updates without installing')
97
- .action(updateCommand);
102
+ .option('--packages-only', 'Only upgrade NPM packages in package.json')
103
+ .option('--drivers-only', 'Only upgrade system binaries and drivers (ADB, Agent, Chrome)')
104
+ .option('--json', 'Output the update check (packages + drivers) as JSON (implies --check, no changes applied)')
105
+ .addHelpText('after', `
106
+ Examples:
107
+ $ spectra upgrade Upgrade all packages and system drivers
108
+ $ spectra upgrade @testspectra/cli Upgrade only the CLI package
109
+ $ spectra upgrade @testspectra/cli @testspectra/matchers Upgrade specific packages
110
+ $ spectra upgrade --check Check what is outdated (packages + drivers)`)
111
+ .action(upgradeCommand);
98
112
  program
99
113
  .command('devices')
100
- .description('List connected Android/iOS devices and local browsers')
101
- .option('-t, --target <target>', 'Filter scope: android, ios, web, all', 'all')
114
+ .description('List connected Android devices (ADB)')
102
115
  .option('--json', 'Output results as JSON')
103
116
  .action(devicesCommand);
104
117
  program
105
118
  .command('license [action] [key]')
106
119
  .description('Manage TestSpectra commercial license (activate, status, remove)')
107
120
  .option('--json', 'Output license details as JSON')
108
- .addHelpText('after', `
109
- Examples:
110
- $ spectra license activate <KEY> Activate license key and auto-configure private driver registry
111
- $ spectra license status Display active license organization, plan, and expiration
121
+ .addHelpText('after', `
122
+ Examples:
123
+ $ spectra license activate <KEY> Activate license key and auto-configure private driver registry
124
+ $ spectra license status Display active license organization, plan, and expiration
112
125
  $ spectra license remove Remove locally activated license credentials`)
113
126
  .action(licenseCommand);
114
127
  const skills = program
@@ -147,6 +160,12 @@ Examples:
147
160
  .option('-w, --cwd <dir>', 'Custom workspace directory')
148
161
  .option('-a, --agent <ids>', 'One-off override: comma-separated agent(s) to target, bypassing the saved "init" selection (claude, opencode, kilo, agents, gemini)')
149
162
  .action(skillsRemoveCommand);
163
+ const docs = program.command('docs').description('Manage generated TestSpectra workspace documentation');
164
+ docs
165
+ .command('update')
166
+ .description('Regenerate ARCHITECTURE.md from the current workspace state, without re-running init')
167
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
168
+ .action(docsUpdateCommand);
150
169
  program
151
170
  .command('run [paths...]')
152
171
  .description('Execute test suite or individual test case')
@@ -170,20 +189,29 @@ Examples:
170
189
  .action(runCommand);
171
190
  program
172
191
  .command('test')
173
- .description('Run React component tests via @testspectra/react (bundled Vitest browser mode)')
192
+ .description('Run React component tests via @testspectra/react')
193
+ .option('-r, --runner <engine>', 'Component test runner engine: spectra (default, native CDP) or vitest (Playwright browser mode)', 'spectra')
174
194
  .option('-w, --cwd <dir>', 'Custom workspace directory')
175
195
  .option('-s, --spec <path>', 'Spec file path (or substring filter) to execute')
176
196
  .option('--headless', 'Run in headless mode (default)')
177
197
  .option('--no-headless', 'Run in headed mode')
178
198
  .option('--headed', 'Run the browser in visible GUI mode (alias for --no-headless)')
179
- .option('--step-delay <ms>', 'Delay after each action/assertion in milliseconds (slow motion, for observing DOM transitions in --headed mode)')
199
+ .option('-d, --step-delay <ms>', 'Delay after each action/assertion in milliseconds (slow motion, for observing DOM transitions in --headed mode)')
180
200
  .option('--delay <ms>', 'Alias for --step-delay')
181
- .option('-c, --concurrency <number>', 'Run spec files sequentially (1) or in parallel (>1, default). Browser mode has no numeric N-worker sizing like `spectra run`.')
182
- .option('-o, --output <path>', 'Write a Vitest JSON test report to this path')
201
+ .option('-c, --concurrency <number>', 'Run spec files sequentially (1) or in parallel (>1, default).')
202
+ .option('-o, --output <path>', 'Write a JSON test report to this path')
183
203
  .action(testCommand);
184
204
  return program;
185
205
  }
186
206
  export async function runCli(args = process.argv) {
187
207
  const program = createCliProgram();
188
208
  await program.parseAsync(args);
209
+ // Commands whose stdout must stay machine-readable or is purely diagnostic never emit the
210
+ // interactive "update available" banner.
211
+ const silentCommands = ['doctor', 'install', 'upgrade', 'update'];
212
+ if (silentCommands.includes(program.args[0]))
213
+ return;
214
+ const { checkUpdateInBackground, displayUpdateNotificationIfNeeded } = await import('./utils/update-notifier.js');
215
+ checkUpdateInBackground();
216
+ displayUpdateNotificationIfNeeded();
189
217
  }
@@ -14,20 +14,20 @@ const __dirname = path.dirname(__filename);
14
14
  */
15
15
  describe('Component Test (.tsx) Import Exception', () => {
16
16
  const tempDir = path.join(__dirname, '__temp_tsx_imports__');
17
- const specFrontmatter = `---
18
- id: TC-0200
19
- title: Sample component test
20
- priority: High
21
- caseType: Positive
22
- status: automated
23
- coverage:
24
- web: automated
25
- ---
26
-
27
- # Sample component test
28
-
29
- ## Test Steps
30
- 1. Mount the component and assert it is visible.
17
+ const specFrontmatter = `---
18
+ id: TC-0200
19
+ title: Sample component test
20
+ priority: High
21
+ caseType: Positive
22
+ status: automated
23
+ coverage:
24
+ web: automated
25
+ ---
26
+
27
+ # Sample component test
28
+
29
+ ## Test Steps
30
+ 1. Mount the component and assert it is visible.
31
31
  `;
32
32
  beforeEach(() => {
33
33
  fs.mkdirSync(tempDir, { recursive: true });
@@ -39,23 +39,23 @@ coverage:
39
39
  }
40
40
  });
41
41
  it('does not flag no-manual-import for ordinary component/library imports in .test.tsx', () => {
42
- fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import { Button } from '../../src/components/Button/Button.js';
43
- import React from 'react';
44
-
45
- it('Sample component test', async () => {
46
- await Spectra.mount(<Button label="Save" />);
47
- });
42
+ fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import { Button } from '../../src/components/Button/Button.js';
43
+ import React from 'react';
44
+
45
+ it('Sample component test', async () => {
46
+ await Spectra.mount(<Button label="Save" />);
47
+ });
48
48
  `);
49
49
  const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
50
50
  const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
51
51
  expect(importErrors).toHaveLength(0);
52
52
  });
53
53
  it('still flags no-manual-import when a .test.tsx import resolves into page-objects/', () => {
54
- fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import ButtonComponent from '../../page-objects/ButtonComponent/web.js';
55
-
56
- it('Sample component test', async () => {
57
- await ButtonComponent.root.shouldBeVisible();
58
- });
54
+ fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import ButtonComponent from '../../page-objects/ButtonComponent/web.js';
55
+
56
+ it('Sample component test', async () => {
57
+ await ButtonComponent.root.shouldBeVisible();
58
+ });
59
59
  `);
60
60
  const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
61
61
  const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
@@ -63,50 +63,50 @@ it('Sample component test', async () => {
63
63
  expect(importErrors[0].message).toContain('page-objects');
64
64
  });
65
65
  it('still flags no-manual-import when a .test.tsx import resolves into support/steps/', () => {
66
- fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import verifyButtonState from '../../support/steps/verifyButtonState/web.step.js';
67
-
68
- it('Sample component test', async () => {
69
- await verifyButtonState('Save', true);
70
- });
66
+ fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import verifyButtonState from '../../support/steps/verifyButtonState/web.step.js';
67
+
68
+ it('Sample component test', async () => {
69
+ await verifyButtonState('Save', true);
70
+ });
71
71
  `);
72
72
  const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
73
73
  const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
74
74
  expect(importErrors).toHaveLength(1);
75
75
  });
76
76
  it('still flags no-manual-import when a .test.tsx import resolves into fixtures/', () => {
77
- fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import users from '../../fixtures/users.json';
78
-
79
- it('Sample component test', async () => {
80
- expect(users).toBeDefined();
81
- });
77
+ fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import users from '../../fixtures/users.json';
78
+
79
+ it('Sample component test', async () => {
80
+ expect(users).toBeDefined();
81
+ });
82
82
  `);
83
83
  const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
84
84
  const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
85
85
  expect(importErrors).toHaveLength(1);
86
86
  });
87
87
  it('still enforces single-it-required and no-describe-block in .test.tsx files', () => {
88
- fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import { Button } from '../../src/components/Button/Button.js';
89
-
90
- describe('Button', () => {
91
- it('Sample component test', async () => {
92
- await Spectra.mount(<Button label="Save" />);
93
- });
94
-
95
- it('Another test', async () => {
96
- await Spectra.mount(<Button label="Cancel" />);
97
- });
98
- });
88
+ fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import { Button } from '../../src/components/Button/Button.js';
89
+
90
+ describe('Button', () => {
91
+ it('Sample component test', async () => {
92
+ await Spectra.mount(<Button label="Save" />);
93
+ });
94
+
95
+ it('Another test', async () => {
96
+ await Spectra.mount(<Button label="Cancel" />);
97
+ });
98
+ });
99
99
  `);
100
100
  const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
101
101
  expect(diagnostics.some((d) => d.code === 'testspectra/no-describe-block')).toBe(true);
102
102
  expect(diagnostics.some((d) => d.code === 'testspectra/single-it-required')).toBe(true);
103
103
  });
104
104
  it('still forbids all imports (unchanged behavior) in plain .test.ts E2E specs', () => {
105
- fs.writeFileSync(path.join(tempDir, 'web.test.ts'), `import { LoginPage } from '../../page-objects/LoginPage/web.js';
106
-
107
- it('Sample component test', async () => {
108
- await LoginPage.submitButton.click();
109
- });
105
+ fs.writeFileSync(path.join(tempDir, 'web.test.ts'), `import { LoginPage } from '../../page-objects/LoginPage/web.js';
106
+
107
+ it('Sample component test', async () => {
108
+ await LoginPage.submitButton.click();
109
+ });
110
110
  `);
111
111
  const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
112
112
  const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');