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

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 (243) hide show
  1. package/README.md +50 -0
  2. package/bin/spectra.js +0 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/commands/__tests__/doctor-filter.test.d.ts +1 -0
  5. package/dist/commands/__tests__/doctor-filter.test.js +37 -0
  6. package/dist/commands/__tests__/run-e2e.test.js +49 -49
  7. package/dist/commands/__tests__/test-error-streaming.test.d.ts +1 -0
  8. package/dist/commands/__tests__/test-error-streaming.test.js +85 -0
  9. package/dist/commands/__tests__/test-scaffolding.test.d.ts +1 -0
  10. package/dist/commands/__tests__/test-scaffolding.test.js +32 -0
  11. package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
  12. package/dist/commands/__tests__/upgrade.test.js +95 -0
  13. package/dist/commands/add.js +5 -4
  14. package/dist/commands/devices.js +1 -0
  15. package/dist/commands/docs.d.ts +4 -0
  16. package/dist/commands/docs.js +17 -0
  17. package/dist/commands/doctor.d.ts +9 -1
  18. package/dist/commands/doctor.js +17 -3
  19. package/dist/commands/init.js +7 -160
  20. package/dist/commands/install.js +5 -0
  21. package/dist/commands/license.js +1 -0
  22. package/dist/commands/run.d.ts +2 -0
  23. package/dist/commands/run.js +22 -0
  24. package/dist/commands/test.d.ts +13 -0
  25. package/dist/commands/test.js +115 -21
  26. package/dist/commands/upgrade.d.ts +12 -0
  27. package/dist/commands/upgrade.js +197 -0
  28. package/dist/config/schema.d.ts +53 -0
  29. package/dist/config/schema.js +2 -0
  30. package/dist/generator/__tests__/tsconfig-isolation.test.js +2 -1
  31. package/dist/generator/__tests__/type-generator.test.js +38 -0
  32. package/dist/generator/architecture-doc-generator.d.ts +14 -0
  33. package/dist/generator/architecture-doc-generator.js +80 -0
  34. package/dist/generator/index.d.ts +1 -0
  35. package/dist/generator/index.js +1 -0
  36. package/dist/generator/tsconfig-generator.js +1 -1
  37. package/dist/generator/type-generator.d.ts +5 -2
  38. package/dist/generator/type-generator.js +14 -17
  39. package/dist/index.js +56 -26
  40. package/dist/linter/__tests__/component-test-imports.test.js +51 -51
  41. package/dist/linter/__tests__/shared-lib-boundary.test.js +15 -15
  42. package/dist/linter/__tests__/spec-schema.test.js +167 -167
  43. package/dist/linter/discovery.d.ts +3 -3
  44. package/dist/linter/discovery.js +7 -7
  45. package/dist/linter/schemas/spec.schema.d.ts +8 -8
  46. package/dist/linter/schemas/suite.schema.d.ts +4 -4
  47. package/dist/runner/bridge.d.ts +1 -1
  48. package/dist/runner/bridge.js +101 -11
  49. package/dist/runner/reporter.d.ts +1 -0
  50. package/dist/runner/reporter.js +12 -6
  51. package/dist/utils/__tests__/dev-server-manager.test.d.ts +1 -0
  52. package/dist/utils/__tests__/dev-server-manager.test.js +72 -0
  53. package/dist/utils/background-update-check.d.ts +1 -0
  54. package/dist/utils/background-update-check.js +43 -0
  55. package/dist/utils/dependency-updates.d.ts +15 -0
  56. package/dist/utils/dependency-updates.js +107 -0
  57. package/dist/utils/dev-server-manager.d.ts +40 -0
  58. package/dist/utils/dev-server-manager.js +257 -0
  59. package/dist/utils/update-notifier.d.ts +2 -0
  60. package/dist/utils/update-notifier.js +67 -0
  61. package/package.json +36 -30
  62. package/templates/default/CLAUDE.md +16 -16
  63. package/templates/default/global-hooks/after/mobile.hook.ts +3 -3
  64. package/templates/default/global-hooks/after/web.hook.ts +3 -3
  65. package/templates/default/global-hooks/before/mobile.hook.ts +3 -3
  66. package/templates/default/global-hooks/before/web.hook.ts +3 -3
  67. package/templates/default/package.json +2 -2
  68. package/templates/default/page-objects/AuthPage/mobile.ts +5 -5
  69. package/templates/default/page-objects/AuthPage/web.ts +10 -10
  70. package/templates/default/page-objects/MatchersPage/mobile.ts +70 -70
  71. package/templates/default/page-objects/MatchersPage/web.ts +43 -43
  72. package/templates/default/page-objects/NavigationPage/mobile.ts +14 -14
  73. package/templates/default/page-objects/NavigationPage/web.ts +8 -8
  74. package/templates/default/page-objects/PlaygroundPage/mobile.ts +42 -42
  75. package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
  76. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -53
  77. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -19
  78. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -31
  79. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -3
  80. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -3
  81. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -27
  82. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -19
  83. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -26
  84. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -30
  85. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -22
  86. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -27
  87. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -20
  88. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -24
  89. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -26
  90. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -37
  91. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -21
  92. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -29
  93. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -16
  94. package/templates/default/specs/ApiInterception/suite.md +10 -10
  95. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -30
  96. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -23
  97. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -16
  98. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -28
  99. package/templates/default/specs/ApiSeeding/suite.md +9 -9
  100. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -7
  101. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -19
  102. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -9
  103. package/templates/default/specs/Authentication/suite.md +9 -9
  104. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +9 -9
  105. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +17 -17
  106. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  107. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -17
  108. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -28
  109. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -26
  110. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -26
  111. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -19
  112. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +11 -11
  113. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +17 -17
  114. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  115. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -14
  116. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -21
  117. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -28
  118. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -29
  119. package/templates/default/specs/DeepLink/suite.md +9 -9
  120. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +11 -11
  121. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +17 -17
  122. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  123. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +29 -29
  124. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +19 -19
  125. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  126. package/templates/default/specs/ElementMatchers/suite.md +10 -10
  127. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  128. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  129. package/templates/default/specs/EnvironmentConfig/suite.md +9 -9
  130. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -26
  131. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -22
  132. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -13
  133. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -20
  134. package/templates/default/specs/PermissionRationale/suite.md +9 -9
  135. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +6 -6
  136. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +18 -18
  137. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  138. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +6 -6
  139. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +18 -18
  140. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  141. package/templates/default/spectra.config.ts +52 -52
  142. package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -4
  143. package/templates/default/support/actions/fillCredentials/web.action.ts +4 -4
  144. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -9
  145. package/templates/default/support/actions/seedSession/web.action.ts +13 -13
  146. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +5 -5
  147. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  148. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +6 -6
  149. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  150. package/templates/docs/ARCHITECTURE.default.md +26 -0
  151. package/templates/docs/ARCHITECTURE.nx.md +119 -0
  152. package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm} +0 -0
  153. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  154. package/templates/nx/.nx/workspace-data/d/daemon.log +6343 -1612
  155. package/templates/nx/.nx/workspace-data/d/server-process.json +1 -1
  156. package/templates/nx/.nx/workspace-data/file-map.json +116 -130
  157. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -1
  158. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  159. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +504 -504
  160. package/templates/nx/.nx/workspace-data/project-graph.json +560 -559
  161. package/templates/nx/.nx/workspace-data/source-maps.json +336 -0
  162. package/templates/nx/CLAUDE.md +10 -10
  163. package/templates/nx/package.json +3 -3
  164. package/templates/nx/packages/matchers/e2e/project.json +2 -2
  165. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -27
  166. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -27
  167. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -24
  168. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -24
  169. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -3
  170. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  171. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -3
  172. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +17 -17
  173. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -3
  174. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  175. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -3
  176. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  177. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -3
  178. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  179. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  180. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  181. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -9
  182. package/templates/nx/packages/playground/e2e/project.json +2 -2
  183. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -3
  184. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  185. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -3
  186. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  187. package/templates/nx/pnpm-lock.yaml +6663 -6663
  188. package/templates/nx/pnpm-workspace.yaml +9 -9
  189. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +33 -33
  190. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +91 -91
  191. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -5
  192. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -8
  193. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -26
  194. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +31 -31
  195. package/templates/nx/shared/testing/project.json +18 -19
  196. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -4
  197. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -4
  198. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -3
  199. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  200. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -4
  201. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  202. package/templates/nx/spectra.config.ts +65 -52
  203. package/templates/react-component/.cursorrules +17 -17
  204. package/templates/react-component/.vscode/extensions.json +5 -5
  205. package/templates/react-component/CLAUDE.md +14 -14
  206. package/templates/react-component/README.md +69 -69
  207. package/templates/react-component/fixtures/component-mock.json +12 -12
  208. package/templates/react-component/global-hooks/after/web.hook.ts +3 -3
  209. package/templates/react-component/global-hooks/before/web.hook.ts +3 -3
  210. package/templates/react-component/package.json +27 -27
  211. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -23
  212. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -23
  213. package/templates/react-component/page-objects/InputComponent/web.ts +13 -13
  214. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -18
  215. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -11
  216. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -18
  217. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -10
  218. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -18
  219. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -16
  220. package/templates/react-component/specs/BadgeComponent/suite.md +12 -12
  221. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -19
  222. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +25 -28
  223. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -19
  224. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +13 -22
  225. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -18
  226. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -8
  227. package/templates/react-component/specs/ButtonComponent/suite.md +12 -12
  228. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -20
  229. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +24 -28
  230. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -18
  231. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -9
  232. package/templates/react-component/specs/InputComponent/suite.md +12 -12
  233. package/templates/react-component/spectra.config.ts +17 -27
  234. package/templates/react-component/src/components/Badge/Badge.tsx +60 -60
  235. package/templates/react-component/src/components/Button/Button.tsx +59 -57
  236. package/templates/react-component/src/components/Input/Input.tsx +40 -41
  237. package/templates/react-component/src/test-utils.tsx +23 -23
  238. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -9
  239. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -12
  240. package/bin/.testspectra-runner.hash +0 -1
  241. package/bin/testspectra-runner.exe +0 -0
  242. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal +0 -0
  243. /package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db} +0 -0
@@ -6,6 +6,7 @@ import chalk from 'chalk';
6
6
  import { ConfigLoader } from '../config/loader.js';
7
7
  import { TypeGenerator } from '../types/generator.js';
8
8
  import { TsConfigGenerator } from '../generator/tsconfig-generator.js';
9
+ import { generateArchitectureDoc } from '../generator/architecture-doc-generator.js';
9
10
  function parsePnpmWorkspaceGlobs(content) {
10
11
  const lines = content.split('\n');
11
12
  const globs = [];
@@ -460,6 +461,8 @@ export async function initCommand(options = {}) {
460
461
  (typeof rootPkg.devDependencies['@testspectra/matchers'] === 'string' &&
461
462
  rootPkg.devDependencies['@testspectra/matchers'].startsWith('link:')))
462
463
  rootPkg.devDependencies['@testspectra/matchers'] = cliDepVersion;
464
+ if (!rootPkg.devDependencies['nx'])
465
+ rootPkg.devDependencies['nx'] = '^20.8.4';
463
466
  if (!rootPkg.devDependencies['typescript'])
464
467
  rootPkg.devDependencies['typescript'] = '^5.4.5';
465
468
  if (!rootPkg.devDependencies['@wdio/cli'])
@@ -508,6 +511,7 @@ export async function initCommand(options = {}) {
508
511
  copyRecursive(sampleE2eSrc, targetE2eDir, {
509
512
  'playground-e2e': e2eProjectName,
510
513
  'packages/playground/e2e': path.relative(cwd, targetE2eDir).replace(/\\/g, '/'),
514
+ 'testspectra:scope:playground': `testspectra:scope:${featureName}`,
511
515
  'scope:playground': `scope:${featureName}`,
512
516
  });
513
517
  // Adjust project.json
@@ -519,7 +523,7 @@ export async function initCommand(options = {}) {
519
523
  pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
520
524
  pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
521
525
  pObj.targets['type-check'].options.cwd = '.';
522
- pObj.tags = [`scope:${featureName}`, 'type:e2e'];
526
+ pObj.tags = ['testspectra:e2e', `testspectra:scope:${featureName}`];
523
527
  fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), 'utf-8');
524
528
  }
525
529
  catch { }
@@ -557,137 +561,7 @@ export async function initCommand(options = {}) {
557
561
  // 7. Generate Ambient Types in ROOT only
558
562
  TypeGenerator.writeDeclarationFiles(cwd);
559
563
  ensureVsCodeExtensions(cwd);
560
- const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
561
-
562
- This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
563
-
564
- ---
565
-
566
- ## 1. Directory & File Overview
567
-
568
- ### Centralized Root Elements
569
- - \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
570
- - \`./.testspectra/\`: Local test project cache, runtime metadata, auto-generated master TSConfig (\`.testspectra/tsconfig.json\`), platform sub-configs (\`.testspectra/tsconfig/\`), and universal ambient types (\`.testspectra/types/\`). Global binaries and drivers are managed at \`~/.testspectra/drivers/\`.
571
- - \`./nx.json\`: Nx target defaults with execution caching and inputs.
572
- - \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
573
- - \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
574
-
575
- ### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
576
- Provides cross-feature reusable test entities with **100% zero-import global resolution**:
577
- - \`page-objects/\`: Shared Page Object models (e.g. NavigationBar, AppHeader).
578
- - \`steps/\`: Cross-feature business step flows (e.g. \`Step.loginAsAdmin()\`).
579
- - \`actions/\`: Custom atomic actions (e.g. \`Spectra.dismissBanner()\`).
580
- - \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
581
-
582
- ### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
583
- ${featureE2eDirs
584
- .map((d) => {
585
- const rel = path.relative(cwd, d);
586
- const name = path.basename(path.dirname(d));
587
- const e2eName = `${name}-${e2eFolderName}`;
588
- return `- \`./${rel}\` (**${name}**, Nx Project: \`${e2eName}\`):
589
- - \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
590
- - \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
591
- - \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
592
- })
593
- .join('\n')}
594
-
595
- ---
596
-
597
- ## 2. Zero-Import Consumption Flow & Mechanics
598
-
599
- TestSpectra completely eliminates boilerplate \`import\` statements across all test specs, steps, and page objects.
600
-
601
- ### How It Works:
602
- 1. **Centralized Ambient Aggregation**:
603
- TestSpectra Language Service Plugin & CLI automatically scan:
604
- - **Local Feature Entities**: \`<feature>/${e2eFolderName}/page-objects/\`, \`<feature>/${e2eFolderName}/steps/\`, \`<feature>/${e2eFolderName}/actions/\`
605
- - **Shared Library Entities**: \`./${sharedTestingRelPath}/page-objects/\`, \`./${sharedTestingRelPath}/steps/\`, \`./${sharedTestingRelPath}/actions/\`, \`./${sharedTestingRelPath}/fixtures/\`
606
- 2. **Ambient Typing Generation**:
607
- These are compiled into \`.testspectra/types/web.d.ts\`, \`android.d.ts\`, \`ios.d.ts\`, \`fixtures.d.ts\`.
608
- 3. **Instant IDE Autocomplete & Zero Clutter**:
609
- Specs consume both local feature objects and shared library utilities globally.
610
-
611
- ### Code Example:
612
- \`\`\`typescript
613
- // packages/features/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts
614
- // 🌟 NOTICE: 100% Zero-Import & Flat Execution! (No import statement, no describe wrapper)
615
-
616
- it("should navigate via shared header and login with local POM", async () => {
617
- // 1. Consumed from Shared Testing Library (${sharedTestingRelPath}/page-objects/NavigationBar)
618
- await NavigationBar.goToLogin();
619
-
620
- // 2. Consumed from Shared Testing Library (${sharedTestingRelPath}/steps/loginAsAdmin)
621
- await Step.loginAsAdmin();
622
-
623
- // 3. Consumed from Local Feature POM (page-objects/LoginPage)
624
- await LoginPage.flashAlert.shouldBeVisible();
625
- await LoginPage.flashAlert.shouldContainText("Welcome");
626
-
627
- // 4. Consumed from Shared Testing Library Custom Action (${sharedTestingRelPath}/actions/dismissBanner)
628
- await Spectra.dismissBanner();
629
- });
630
- \`\`\`
631
-
632
- ---
633
-
634
- ## 3. How to Run Tests
635
-
636
- You can execute tests either from the **Monorepo Root via Nx** or directly inside each **Feature Directory via CLI**:
637
-
638
- ### Option A: From Root Monorepo via Nx (Recommended for CI & Team Workflows)
639
-
640
- \`\`\`bash
641
- # 1. Run E2E for a SPECIFIC feature module (e.g. auth)
642
- pnpm nx run auth-${e2eFolderName}:e2e
643
-
644
- # 2. Run E2E for a specific feature on ANDROID or IOS
645
- pnpm nx run auth-${e2eFolderName}:e2e --configuration=android
646
- pnpm nx run auth-${e2eFolderName}:e2e --configuration=ios
647
-
648
- # 3. Run E2E for a specific feature in HEADLESS browser mode
649
- pnpm nx run auth-${e2eFolderName}:e2e --configuration=headless
650
-
651
- # 4. Run ALL feature E2E suites across the entire monorepo in parallel
652
- pnpm nx run-many -t e2e
653
-
654
- # 5. Run only E2E tests for features modified in current Git branch / PR
655
- pnpm nx affected -t e2e
656
- \`\`\`
657
-
658
- ### Option B: From Inside the Feature Directory (Direct CLI)
659
-
660
- \`\`\`bash
661
- # Navigate to the feature e2e folder
662
- cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
663
-
664
- # Run all specs in this feature
665
- pnpm spectra run
666
-
667
- # Run a specific test case file
668
- pnpm spectra run specs/Auth/TC-AUTH-01/web.test.ts
669
-
670
- # Target Android device or emulator
671
- pnpm spectra run --target android
672
-
673
- # Run in headed / interactive browser mode
674
- pnpm spectra run --no-headless
675
- \`\`\`
676
-
677
- ---
678
-
679
- ## 4. Type Checking & Validation
680
-
681
- \`\`\`bash
682
- # Type check all E2E projects via Nx
683
- pnpm nx run-many -t type-check
684
-
685
- # Or standard TypeScript project references build from root
686
- pnpm type-check
687
- # or: npx tsc -b
688
- \`\`\`
689
- `;
690
- fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), archDocContent, 'utf-8');
564
+ generateArchitectureDoc(cwd);
691
565
  // 8. Ensure root .gitignore ignores .testspectra/ and *.tsbuildinfo
692
566
  const rootGitignorePath = path.join(cwd, '.gitignore');
693
567
  const ignoreEntries = ['.testspectra/', '*.tsbuildinfo', '.nx/cache'];
@@ -741,34 +615,7 @@ pnpm type-check
741
615
  TypeGenerator.writeDeclarationFiles(cwd);
742
616
  ensureVsCodeExtensions(cwd);
743
617
  // Standalone Architecture Documentation
744
- const standaloneArchDoc = `# TestSpectra Project Architecture
745
-
746
- This project is built with the TestSpectra testing framework.
747
-
748
- ## Structure & File Map
749
- - \`./spectra.config.ts\`: Typed runner configuration.
750
- - \`./specs/\`: Layered test cases (\`specs/<Suite>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
751
- - \`./page-objects/\`: Page Object models per platform.
752
- - \`./steps/\`: Business flows accessible globally via \`Step.*\`.
753
- - \`./actions/\`: Atomic actions accessible globally via \`Spectra.*\`.
754
- - \`./fixtures/\`: JSON fixtures accessible globally via \`Fixture.*\`.
755
- - \`./global-hooks/\`: Global suite lifecycle hooks.
756
- - \`./.testspectra/\`: Auto-generated ambient typings and local runtime cache.
757
-
758
- ## Execution
759
- \`\`\`bash
760
- # Run web test suite
761
- pnpm test
762
-
763
- # Run Android / iOS
764
- pnpm spectra run --target android
765
- pnpm spectra run --target ios
766
-
767
- # Type check
768
- pnpm type-check
769
- \`\`\`
770
- `;
771
- fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), standaloneArchDoc, 'utf-8');
618
+ generateArchitectureDoc(cwd);
772
619
  // Safely update root README.md (do not overwrite existing content)
773
620
  const standaloneReadmePath = path.join(cwd, 'README.md');
774
621
  const standaloneArchSection = `\n## E2E Testing Architecture (TestSpectra)\n\nDetailed test architecture and folder map are documented in: \n👉 **[ARCHITECTURE.md](./ARCHITECTURE.md)**\n\n- **Run tests**: \`pnpm test\`\n- **Type-check**: \`pnpm type-check\`\n`;
@@ -17,6 +17,9 @@ export async function installCommand(item, options = {}) {
17
17
  if (item) {
18
18
  args.push(item);
19
19
  }
20
+ if (options.force) {
21
+ args.push('--force');
22
+ }
20
23
  return new Promise((resolve) => {
21
24
  let child;
22
25
  if (binPath === 'cargo') {
@@ -34,6 +37,7 @@ export async function installCommand(item, options = {}) {
34
37
  cwd: workspaceRoot,
35
38
  env: runnerEnv,
36
39
  stdio: 'inherit',
40
+ windowsHide: true,
37
41
  });
38
42
  }
39
43
  else {
@@ -41,6 +45,7 @@ export async function installCommand(item, options = {}) {
41
45
  cwd,
42
46
  env: runnerEnv,
43
47
  stdio: 'inherit',
48
+ windowsHide: true,
44
49
  });
45
50
  }
46
51
  child.on('close', (code) => {
@@ -14,6 +14,7 @@ export async function licenseCommand(action = 'status', key, options = {}) {
14
14
  const child = spawn(binPath, args, {
15
15
  stdio: 'inherit',
16
16
  env: process.env,
17
+ windowsHide: true,
17
18
  });
18
19
  child.on('close', (code) => {
19
20
  if (code === 0) {
@@ -1,6 +1,8 @@
1
1
  export interface RunCommandOptions {
2
2
  spec?: string;
3
3
  target?: 'web' | 'android' | 'ios' | 'common' | 'mobile';
4
+ host?: string;
5
+ baseUrl?: string;
4
6
  device?: string;
5
7
  devices?: string;
6
8
  allDevices?: boolean;
@@ -6,6 +6,7 @@ import { Reporter } from '../runner/reporter.js';
6
6
  import { TypeGenerator } from '../types/generator.js';
7
7
  import { DemoServer } from '../utils/demo-server.js';
8
8
  import { DemoApiServer } from '../utils/api-server.js';
9
+ import { DevServerManager } from '../utils/dev-server-manager.js';
9
10
  function renderSummaryCard(options) {
10
11
  const width = 64;
11
12
  const borderColor = options.isPassed ? '\x1b[38;2;52;211;153m' : '\x1b[38;2;248;113;113m';
@@ -157,9 +158,29 @@ export async function runCommand(specPaths, options = {}) {
157
158
  }
158
159
  }
159
160
  const config = await ConfigLoader.loadConfig(cwd);
161
+ if (options.baseUrl) {
162
+ config.webConfig.baseUrl = options.baseUrl;
163
+ }
164
+ else if (options.host) {
165
+ if (config.webConfig.hosts && config.webConfig.hosts[options.host]) {
166
+ config.webConfig.baseUrl = config.webConfig.hosts[options.host];
167
+ console.log(`\x1b[36m[TestSpectra]\x1b[0m 🎯 Active Host target: \x1b[33m${options.host}\x1b[0m (\x1b[32m${config.webConfig.baseUrl}\x1b[0m)`);
168
+ }
169
+ else {
170
+ console.warn(`\x1b[33m[TestSpectra] Warning: Target host "${options.host}" is not defined in spectra.config.ts (webConfig.hosts). Falling back to ${config.webConfig.baseUrl}\x1b[0m`);
171
+ }
172
+ }
160
173
  let demoServer = null;
161
174
  let apiServer = null;
175
+ const devServerManager = new DevServerManager();
162
176
  try {
177
+ const configuredDevServers = [
178
+ ...(config.webConfig.devServers || []),
179
+ ...(config.webConfig.devServer ? [config.webConfig.devServer] : []),
180
+ ];
181
+ if (configuredDevServers.length > 0 && !options.demo) {
182
+ await devServerManager.startAll(configuredDevServers, options.host, cwd);
183
+ }
163
184
  if (options.demo) {
164
185
  demoServer = new DemoServer(5173);
165
186
  try {
@@ -343,6 +364,7 @@ export async function runCommand(specPaths, options = {}) {
343
364
  process.exit(1);
344
365
  }
345
366
  finally {
367
+ await devServerManager.stopAll();
346
368
  if (demoServer) {
347
369
  await demoServer.stop();
348
370
  }
@@ -1,5 +1,6 @@
1
1
  export interface TestCommandOptions {
2
2
  cwd?: string;
3
+ runner?: 'spectra' | 'vitest';
3
4
  headed?: boolean;
4
5
  headless?: boolean;
5
6
  stepDelay?: string | number;
@@ -8,6 +9,18 @@ export interface TestCommandOptions {
8
9
  output?: string;
9
10
  concurrency?: string | number;
10
11
  }
12
+ /**
13
+ * Guarantees the `.testspectra` solution-tsconfig scaffolding exists before Vitest starts.
14
+ *
15
+ * The bundled Vitest browser runner transpiles raw TypeScript specs/page-objects Just-In-Time.
16
+ * When it does, `vite:esbuild` walks the nearest `tsconfig.json` and tries to read every entry in
17
+ * its `references` — including `./.testspectra/tsconfig.json`. That directory is gitignored, so on
18
+ * a clean clone / fresh CI checkout the reference points at a file that does not exist, esbuild
19
+ * throws `ENOENT`, Vite answers HTTP 500, and every suite dies with "Failed to fetch dynamically
20
+ * imported module" before a single assertion runs. Regenerating the scaffolding up front (same
21
+ * output as `spectra sync-types`) keeps the reference resolvable.
22
+ */
23
+ export declare function ensureTestSpectraScaffolding(cwd: string): void;
11
24
  /**
12
25
  * `spectra test` — delegates to `@testspectra/react`'s bundled Vitest browser-mode runner for
13
26
  * component testing. Deliberately separate from `spectra run` (E2E via core/orchestrator): see
@@ -1,9 +1,30 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
3
  import chalk from 'chalk';
3
4
  import * as p from '@clack/prompts';
5
+ import { ConfigLoader } from '../config/loader.js';
4
6
  import { renderSummaryView } from '../reporter/summary-view.js';
5
7
  import { Reporter } from '../runner/reporter.js';
8
+ import { RustCoreBridge } from '../runner/bridge.js';
9
+ import { TypeGenerator } from '../generator/type-generator.js';
6
10
  import { runSystemChecks } from './doctor.js';
11
+ /**
12
+ * Guarantees the `.testspectra` solution-tsconfig scaffolding exists before Vitest starts.
13
+ *
14
+ * The bundled Vitest browser runner transpiles raw TypeScript specs/page-objects Just-In-Time.
15
+ * When it does, `vite:esbuild` walks the nearest `tsconfig.json` and tries to read every entry in
16
+ * its `references` — including `./.testspectra/tsconfig.json`. That directory is gitignored, so on
17
+ * a clean clone / fresh CI checkout the reference points at a file that does not exist, esbuild
18
+ * throws `ENOENT`, Vite answers HTTP 500, and every suite dies with "Failed to fetch dynamically
19
+ * imported module" before a single assertion runs. Regenerating the scaffolding up front (same
20
+ * output as `spectra sync-types`) keeps the reference resolvable.
21
+ */
22
+ export function ensureTestSpectraScaffolding(cwd) {
23
+ const masterConfigPath = path.join(cwd, '.testspectra', 'tsconfig.json');
24
+ if (fs.existsSync(masterConfigPath))
25
+ return;
26
+ TypeGenerator.generateAll(cwd);
27
+ }
7
28
  /**
8
29
  * Finds the Chrome-for-Testing binary that `spectra doctor` already installs/manages for the
9
30
  * E2E CDP driver (`core/driver-cdp`) — same install, same binary. `@testspectra/react` must not
@@ -14,8 +35,10 @@ import { runSystemChecks } from './doctor.js';
14
35
  */
15
36
  async function resolveBrowserExecutable(cwd) {
16
37
  try {
17
- const { dependencies } = await runSystemChecks(cwd);
18
- const chrome = dependencies.find((d) => d.category === 'web' && (d.name.includes('Chrome') || d.name.includes('Chromium')));
38
+ // `--only chrome` runs exactly one check — no adb/bun/git spawns, no Matchers lookup — since
39
+ // this call exists solely to locate the managed browser binary.
40
+ const { dependencies } = await runSystemChecks(cwd, { only: 'chrome' });
41
+ const chrome = dependencies.find((d) => d.category === 'web' && d.name.includes('Chrome'));
19
42
  return chrome?.installed && chrome.location ? chrome.location : null;
20
43
  }
21
44
  catch {
@@ -30,6 +53,11 @@ async function resolveBrowserExecutable(cwd) {
30
53
  */
31
54
  export async function testCommand(options = {}) {
32
55
  const cwd = options.cwd ?? process.cwd();
56
+ // User-facing phase log. Deliberately describes TestSpectra's own stages only — never the
57
+ // bundled runtime/toolchain underneath — so the CLI surface stays implementation-agnostic.
58
+ const logPhase = (message) => p.log.step(chalk.dim(message));
59
+ logPhase('Preparing component test workspace…');
60
+ ensureTestSpectraScaffolding(cwd);
33
61
  let reactPkg;
34
62
  try {
35
63
  reactPkg = await import('@testspectra/react');
@@ -41,6 +69,7 @@ export async function testCommand(options = {}) {
41
69
  process.exit(1);
42
70
  return;
43
71
  }
72
+ logPhase('Resolving managed test browser…');
44
73
  const browserExecutablePath = await resolveBrowserExecutable(cwd);
45
74
  if (!browserExecutablePath) {
46
75
  p.log.error(chalk.red('✖ TestSpectra-managed Chrome for Testing was not found.'));
@@ -64,6 +93,21 @@ export async function testCommand(options = {}) {
64
93
  else if (options.headless !== undefined) {
65
94
  headless = options.headless;
66
95
  }
96
+ // Fallback to spectra.config.ts if options were not explicitly provided on CLI
97
+ try {
98
+ const config = await ConfigLoader.loadConfig(cwd);
99
+ if (stepDelayMs === undefined && config?.webConfig?.stepDelay !== undefined) {
100
+ const parsed = parseInt(String(config.webConfig.stepDelay), 10);
101
+ if (!isNaN(parsed) && parsed >= 0)
102
+ stepDelayMs = parsed;
103
+ }
104
+ if (!options.headed && options.headless === undefined && config?.webConfig?.headless !== undefined) {
105
+ headless = String(config.webConfig.headless) !== 'false' && Boolean(config.webConfig.headless);
106
+ }
107
+ }
108
+ catch {
109
+ /* ignore config load failures */
110
+ }
67
111
  const concurrency = options.concurrency !== undefined ? parseInt(String(options.concurrency), 10) : undefined;
68
112
  const outputFile = options.output ? path.resolve(cwd, options.output) : undefined;
69
113
  // Reuses the exact same semantic terminal reporter `spectra run` drives (see
@@ -72,31 +116,81 @@ export async function testCommand(options = {}) {
72
116
  // / setup.ts), so the same Reporter class parses them with zero changes on either side.
73
117
  const reporter = new Reporter();
74
118
  const runStartedAt = Date.now();
75
- const ok = await reactPkg.runComponentTests({
76
- cwd,
77
- headless,
78
- stepDelayMs,
79
- spec: options.spec,
80
- outputFile,
81
- concurrency: concurrency !== undefined && !isNaN(concurrency) ? concurrency : undefined,
82
- browserExecutablePath,
83
- onLine: (line) => {
84
- if (!line.startsWith('[TESTSPECTRA_'))
85
- return;
86
- reporter.addLog({ timestamp: new Date().toLocaleTimeString(), level: 'INFO', message: line });
87
- if (!reporter.isInteractiveMode()) {
88
- console.log(line);
89
- }
90
- },
91
- });
119
+ let ok = false;
120
+ if (options.runner === 'vitest') {
121
+ logPhase('Starting component test runner (Vitest Browser Mode)…');
122
+ ok = await reactPkg.runComponentTests({
123
+ runner: 'vitest',
124
+ cwd,
125
+ headless,
126
+ stepDelayMs,
127
+ spec: options.spec,
128
+ outputFile,
129
+ concurrency: concurrency !== undefined && !isNaN(concurrency) ? concurrency : undefined,
130
+ browserExecutablePath,
131
+ onLine: (line) => {
132
+ const cleanLine = line.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '').trim();
133
+ if (!cleanLine)
134
+ return;
135
+ if (cleanLine.startsWith('[TESTSPECTRA_')) {
136
+ reporter.addLog({ timestamp: new Date().toLocaleTimeString(), level: 'INFO', message: cleanLine });
137
+ if (!reporter.isInteractiveMode()) {
138
+ console.log(cleanLine);
139
+ }
140
+ return;
141
+ }
142
+ if (reactPkg.isVitestNoise?.(cleanLine)) {
143
+ return;
144
+ }
145
+ const sanitized = reactPkg.sanitizeErrorMessage?.(cleanLine) ?? cleanLine;
146
+ reporter.addLog({ timestamp: new Date().toLocaleTimeString(), level: 'ERROR', message: sanitized });
147
+ if (!reporter.isInteractiveMode()) {
148
+ console.error(chalk.red(sanitized));
149
+ }
150
+ },
151
+ });
152
+ }
153
+ else {
154
+ logPhase('Starting component test runner (Spectra Native CDP Engine)…');
155
+ const bundleServer = await reactPkg.startBundleServer({
156
+ testRoot: cwd,
157
+ stepDelayMs: stepDelayMs ?? 0,
158
+ });
159
+ try {
160
+ const home = process.env.HOME || process.env.USERPROFILE || '';
161
+ const globalAppDataPath = path.join(home, '.testspectra');
162
+ const appDataPath = globalAppDataPath;
163
+ const runResult = await RustCoreBridge.run({
164
+ baseDir: cwd,
165
+ appDataPath,
166
+ platform: 'component',
167
+ config: {
168
+ bundleServerUrl: bundleServer.url,
169
+ webConfig: {
170
+ headless,
171
+ stepDelay: stepDelayMs,
172
+ browserExecutablePath,
173
+ },
174
+ },
175
+ specs: options.spec ? [options.spec] : undefined,
176
+ concurrency: concurrency !== undefined && !isNaN(concurrency) ? concurrency : undefined,
177
+ outputJsonPath: outputFile,
178
+ }, reporter);
179
+ ok = runResult.status.toLowerCase() === 'passed';
180
+ }
181
+ finally {
182
+ bundleServer.stop();
183
+ }
184
+ }
92
185
  const durationMs = Date.now() - runStartedAt;
93
186
  const result = reporter.generateResult(ok ? 'passed' : 'failed', `${(durationMs / 1000).toFixed(1)}s`);
94
- const totalTests = result.passedCount + result.failedCount;
187
+ const failedCount = !ok && result.failedCount === 0 ? 1 : result.failedCount;
188
+ const totalTests = result.passedCount + failedCount;
95
189
  renderSummaryView({
96
190
  totalSuites: totalTests,
97
191
  totalTests,
98
192
  passedCount: result.passedCount,
99
- failedCount: result.failedCount,
193
+ failedCount,
100
194
  durationMs,
101
195
  reportLocation: outputFile ? path.relative(cwd, outputFile) : '(use -o/--output to save a report)',
102
196
  });
@@ -0,0 +1,12 @@
1
+ export interface PackageUpdate {
2
+ pkg: string;
3
+ current: string | null;
4
+ latest: string | null;
5
+ needsUpdate: boolean;
6
+ }
7
+ export declare function upgradeCommand(requestedPackages?: string[], options?: {
8
+ check?: boolean;
9
+ packagesOnly?: boolean;
10
+ driversOnly?: boolean;
11
+ json?: boolean;
12
+ }): Promise<void>;