@testspectra/cli 1.0.70 → 1.1.0-rc.1

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 (427) hide show
  1. package/LICENSE.md +48 -0
  2. package/README.md +36 -270
  3. package/bin/.testspectra-runner.hash +1 -0
  4. package/bin/spectra.js +1 -1
  5. package/bin/testspectra-runner +0 -0
  6. package/demo-app/assets/index-BhlW-eXG.css +1 -0
  7. package/demo-app/assets/index-DOtRypCa.js +154 -0
  8. package/demo-app/index.html +2 -2
  9. package/dist/.tsbuildinfo +1 -0
  10. package/dist/commands/__tests__/doctor.test.d.ts +1 -0
  11. package/dist/commands/__tests__/doctor.test.js +39 -0
  12. package/dist/commands/__tests__/init.test.d.ts +1 -0
  13. package/dist/commands/__tests__/init.test.js +148 -0
  14. package/dist/commands/__tests__/license.test.d.ts +1 -0
  15. package/dist/commands/__tests__/license.test.js +13 -0
  16. package/dist/commands/__tests__/refactor.test.d.ts +1 -0
  17. package/dist/commands/__tests__/refactor.test.js +64 -0
  18. package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
  19. package/dist/commands/__tests__/run-e2e.test.js +422 -0
  20. package/dist/commands/__tests__/run.test.d.ts +1 -0
  21. package/dist/commands/__tests__/run.test.js +273 -0
  22. package/dist/commands/__tests__/watch.test.d.ts +1 -0
  23. package/dist/commands/__tests__/watch.test.js +155 -0
  24. package/dist/commands/add.js +86 -85
  25. package/dist/commands/demo.js +21 -11
  26. package/dist/commands/devices.d.ts +14 -2
  27. package/dist/commands/devices.js +44 -34
  28. package/dist/commands/doctor.d.ts +3 -1
  29. package/dist/commands/doctor.js +43 -264
  30. package/dist/commands/init.d.ts +1 -1
  31. package/dist/commands/init.js +211 -196
  32. package/dist/commands/install.d.ts +4 -0
  33. package/dist/commands/install.js +59 -0
  34. package/dist/commands/license.d.ts +4 -0
  35. package/dist/commands/license.js +30 -0
  36. package/dist/commands/lint.d.ts +5 -0
  37. package/dist/commands/lint.js +55 -0
  38. package/dist/commands/refactor.d.ts +9 -0
  39. package/dist/commands/refactor.js +172 -0
  40. package/dist/commands/run.d.ts +23 -2
  41. package/dist/commands/run.js +322 -111
  42. package/dist/commands/skills.d.ts +9 -0
  43. package/dist/commands/skills.js +28 -0
  44. package/dist/commands/sync-types.d.ts +4 -0
  45. package/dist/commands/sync-types.js +15 -0
  46. package/dist/commands/test.d.ts +17 -0
  47. package/dist/commands/test.js +76 -0
  48. package/dist/commands/update.js +39 -43
  49. package/dist/commands/watch.d.ts +6 -0
  50. package/dist/commands/watch.js +55 -45
  51. package/dist/config/loader.d.ts +1 -1
  52. package/dist/config/loader.js +40 -25
  53. package/dist/config/schema.d.ts +104 -145
  54. package/dist/config/schema.js +36 -45
  55. package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
  56. package/dist/generator/__tests__/tsconfig-isolation.test.js +282 -0
  57. package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
  58. package/dist/generator/__tests__/type-generator.test.js +386 -0
  59. package/dist/generator/index.d.ts +2 -0
  60. package/dist/generator/index.js +2 -0
  61. package/dist/generator/tsconfig-generator.d.ts +27 -0
  62. package/dist/generator/tsconfig-generator.js +419 -0
  63. package/dist/generator/type-generator.d.ts +59 -0
  64. package/dist/generator/type-generator.js +740 -0
  65. package/dist/index.d.ts +9 -5
  66. package/dist/index.js +165 -51
  67. package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
  68. package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
  69. package/dist/lifecycle/hook-discovery.d.ts +24 -0
  70. package/dist/lifecycle/hook-discovery.js +60 -0
  71. package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
  72. package/dist/lifecycle/hook-dispatcher.js +190 -0
  73. package/dist/lifecycle/index.d.ts +3 -0
  74. package/dist/lifecycle/index.js +3 -0
  75. package/dist/lifecycle/parallel-grouping.d.ts +27 -0
  76. package/dist/lifecycle/parallel-grouping.js +151 -0
  77. package/dist/linter/__tests__/anonymous-export.test.d.ts +1 -0
  78. package/dist/linter/__tests__/anonymous-export.test.js +184 -0
  79. package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
  80. package/dist/linter/__tests__/component-test-imports.test.js +115 -0
  81. package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
  82. package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
  83. package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
  84. package/dist/linter/__tests__/spec-schema.test.js +369 -0
  85. package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
  86. package/dist/linter/__tests__/step-parity.test.js +372 -0
  87. package/dist/linter/discovery.d.ts +30 -0
  88. package/dist/linter/discovery.js +249 -0
  89. package/dist/linter/extractor.d.ts +67 -0
  90. package/dist/linter/extractor.js +245 -0
  91. package/dist/linter/index.d.ts +7 -0
  92. package/dist/linter/index.js +7 -0
  93. package/dist/linter/parser.d.ts +24 -0
  94. package/dist/linter/parser.js +219 -0
  95. package/dist/linter/schemas/spec.schema.d.ts +83 -0
  96. package/dist/linter/schemas/spec.schema.js +150 -0
  97. package/dist/linter/schemas/suite.schema.d.ts +59 -0
  98. package/dist/linter/schemas/suite.schema.js +83 -0
  99. package/dist/linter/spec-linter.d.ts +23 -0
  100. package/dist/linter/spec-linter.js +630 -0
  101. package/dist/linter/types.d.ts +28 -0
  102. package/dist/linter/types.js +1 -0
  103. package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
  104. package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
  105. package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
  106. package/dist/mobile/__tests__/device-pool.test.js +97 -0
  107. package/dist/mobile/device-pool.d.ts +23 -0
  108. package/dist/mobile/device-pool.js +1 -0
  109. package/dist/mobile/index.d.ts +1 -0
  110. package/dist/mobile/index.js +1 -0
  111. package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
  112. package/dist/refactor/__tests__/refactor-engine.test.js +873 -0
  113. package/dist/refactor/index.d.ts +1 -0
  114. package/dist/refactor/index.js +1 -0
  115. package/dist/refactor/refactor-engine.d.ts +50 -0
  116. package/dist/refactor/refactor-engine.js +587 -0
  117. package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
  118. package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
  119. package/dist/reporter/semantic-formatter.d.ts +6 -0
  120. package/dist/reporter/semantic-formatter.js +206 -0
  121. package/dist/reporter/summary-view.d.ts +12 -0
  122. package/dist/reporter/summary-view.js +19 -0
  123. package/dist/reporter/terminal-stream.d.ts +32 -0
  124. package/dist/reporter/terminal-stream.js +82 -0
  125. package/dist/reporter/types.d.ts +2 -0
  126. package/dist/reporter/types.js +1 -0
  127. package/dist/runner/bridge.d.ts +10 -5
  128. package/dist/runner/bridge.js +101 -45
  129. package/dist/runner/reporter.d.ts +32 -2
  130. package/dist/runner/reporter.js +427 -13
  131. package/dist/server/demo.js +43 -44
  132. package/dist/types/generator.d.ts +1 -17
  133. package/dist/types/generator.js +1 -443
  134. package/dist/utils/__tests__/api-server.test.d.ts +1 -0
  135. package/dist/utils/__tests__/api-server.test.js +62 -0
  136. package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
  137. package/dist/utils/__tests__/demo-server.test.js +35 -0
  138. package/dist/utils/api-server.d.ts +19 -0
  139. package/dist/utils/api-server.js +179 -0
  140. package/dist/utils/demo-server.d.ts +5 -0
  141. package/dist/utils/demo-server.js +46 -36
  142. package/package.json +25 -13
  143. package/templates/default/CLAUDE.md +12 -5
  144. package/templates/default/fixtures/apiData.json +24 -0
  145. package/templates/default/package.json +12 -3
  146. package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
  147. package/templates/default/page-objects/AuthPage/web.ts +10 -0
  148. package/templates/default/page-objects/MatchersPage/mobile.ts +66 -17
  149. package/templates/default/page-objects/MatchersPage/web.ts +22 -2
  150. package/templates/default/page-objects/NavigationPage/mobile.ts +11 -2
  151. package/templates/default/page-objects/NavigationPage/web.ts +1 -1
  152. package/templates/default/page-objects/PlaygroundPage/mobile.ts +34 -18
  153. package/templates/default/page-objects/PlaygroundPage/web.ts +4 -2
  154. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -0
  155. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -0
  156. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -0
  157. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
  158. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
  159. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -0
  160. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -0
  161. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -0
  162. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
  163. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
  164. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
  165. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
  166. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
  167. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
  168. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
  169. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
  170. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
  171. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
  172. package/templates/default/specs/ApiInterception/suite.md +10 -0
  173. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
  174. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
  175. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
  176. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
  177. package/templates/default/specs/ApiSeeding/suite.md +9 -0
  178. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
  179. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
  180. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
  181. package/templates/default/specs/Authentication/suite.md +9 -0
  182. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
  183. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +5 -0
  184. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  185. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
  186. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
  187. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
  188. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
  189. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
  190. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
  191. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +5 -0
  192. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
  193. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
  194. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
  195. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
  196. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
  197. package/templates/default/specs/DeepLink/suite.md +9 -0
  198. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
  199. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +5 -0
  200. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  201. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
  202. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +7 -0
  203. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  204. package/templates/default/specs/ElementMatchers/suite.md +2 -1
  205. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  206. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  207. package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
  208. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
  209. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
  210. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
  211. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
  212. package/templates/default/specs/PermissionRationale/suite.md +9 -0
  213. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
  214. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
  215. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
  216. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
  217. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
  218. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
  219. package/templates/default/spectra.config.ts +22 -31
  220. package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
  221. package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
  222. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
  223. package/templates/default/support/actions/seedSession/web.action.ts +13 -0
  224. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -1
  225. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +3 -3
  226. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -2
  227. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +4 -4
  228. package/templates/default/tsconfig.json +3 -1
  229. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
  230. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
  231. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  232. package/templates/nx/.nx/workspace-data/d/daemon.log +5366 -0
  233. package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
  234. package/templates/nx/.nx/workspace-data/file-map.json +464 -0
  235. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
  236. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  237. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
  238. package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
  239. package/templates/nx/CLAUDE.md +2 -2
  240. package/templates/nx/package.json +6 -4
  241. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -0
  242. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  243. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  244. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -0
  245. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  246. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  247. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
  248. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
  249. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  250. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +1 -1
  251. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +15 -5
  252. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
  253. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
  254. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
  255. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  256. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
  257. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  258. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  259. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  260. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
  261. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
  262. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
  263. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
  264. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
  265. package/templates/nx/pnpm-lock.yaml +6663 -0
  266. package/templates/nx/pnpm-workspace.yaml +2 -1
  267. package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
  268. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +13 -1
  269. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +85 -17
  270. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
  271. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
  272. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
  273. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +4 -2
  274. package/templates/nx/shared/testing/project.json +19 -0
  275. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
  276. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
  277. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
  278. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +3 -3
  279. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +2 -2
  280. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +4 -4
  281. package/templates/nx/spectra.config.ts +20 -29
  282. package/templates/nx/tsconfig.json +3 -1
  283. package/templates/react-component/.cursorrules +17 -0
  284. package/templates/react-component/.vscode/extensions.json +5 -0
  285. package/templates/react-component/CLAUDE.md +14 -0
  286. package/templates/react-component/README.md +69 -0
  287. package/templates/react-component/fixtures/component-mock.json +12 -0
  288. package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
  289. package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
  290. package/templates/react-component/package.json +27 -0
  291. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
  292. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
  293. package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
  294. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
  295. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
  296. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
  297. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
  298. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
  299. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
  300. package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
  301. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
  302. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
  303. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
  304. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
  305. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
  306. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
  307. package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
  308. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
  309. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
  310. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
  311. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
  312. package/templates/react-component/specs/InputComponent/suite.md +12 -0
  313. package/templates/react-component/spectra.config.ts +27 -0
  314. package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
  315. package/templates/react-component/src/components/Button/Button.tsx +57 -0
  316. package/templates/react-component/src/components/Input/Input.tsx +41 -0
  317. package/templates/react-component/src/test-utils.tsx +23 -0
  318. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
  319. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
  320. package/templates/react-component/tsconfig.json +8 -0
  321. package/demo-app/assets/index-DAUlrHTr.css +0 -1
  322. package/demo-app/assets/index-DN2Kb0A8.js +0 -100
  323. package/dist/index.cjs +0 -101
  324. package/dist/plugin.cjs +0 -575
  325. package/dist/plugin.d.ts +0 -16
  326. package/dist/plugin.js +0 -184
  327. package/dist/step/index.d.ts +0 -6
  328. package/dist/step/index.js +0 -6
  329. package/dist/step/matchers.d.ts +0 -14
  330. package/dist/step/matchers.js +0 -114
  331. package/dist/step/proto.d.ts +0 -46
  332. package/dist/step/proto.js +0 -114
  333. package/dist/step/runner/collection.d.ts +0 -13
  334. package/dist/step/runner/collection.js +0 -51
  335. package/dist/step/runner/single.d.ts +0 -21
  336. package/dist/step/runner/single.js +0 -138
  337. package/dist/step/spectra.d.ts +0 -34
  338. package/dist/step/spectra.js +0 -156
  339. package/dist/step/types.d.ts +0 -32
  340. package/dist/step/types.js +0 -5
  341. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  342. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  343. package/templates/default/tsconfig.spectra.android.json +0 -46
  344. package/templates/default/tsconfig.spectra.ios.json +0 -46
  345. package/templates/default/tsconfig.spectra.json +0 -22
  346. package/templates/default/tsconfig.spectra.web.json +0 -53
  347. package/templates/nx/fixtures/playgroundData.json +0 -7
  348. package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
  349. package/templates/nx/global-hooks/after/web.hook.ts +0 -3
  350. package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
  351. package/templates/nx/global-hooks/before/web.hook.ts +0 -3
  352. package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
  353. package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
  354. package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
  355. package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
  356. package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
  357. package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
  358. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
  359. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
  360. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
  361. package/templates/nx/specs/BrowserContext/suite.md +0 -9
  362. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  363. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
  364. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  365. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
  366. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
  367. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
  368. package/templates/nx/specs/ContentMatchers/suite.md +0 -9
  369. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
  370. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
  371. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
  372. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
  373. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
  374. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
  375. package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
  376. package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
  377. package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
  378. package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
  379. package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
  380. package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
  381. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
  382. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
  383. package/templates/nx/specs/ElementMatchers/suite.md +0 -9
  384. package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
  385. package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
  386. package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
  387. package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
  388. package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
  389. package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
  390. package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
  391. package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
  392. package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
  393. package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
  394. package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
  395. package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
  396. package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
  397. package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
  398. package/templates/nx/specs/Playground/suite.md +0 -9
  399. package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
  400. package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
  401. package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
  402. package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
  403. package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
  404. package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
  405. package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
  406. package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
  407. package/templates/nx/tsconfig.spectra.android.json +0 -39
  408. package/templates/nx/tsconfig.spectra.ios.json +0 -39
  409. package/templates/nx/tsconfig.spectra.json +0 -25
  410. package/templates/nx/tsconfig.spectra.shared.json +0 -25
  411. package/templates/nx/tsconfig.spectra.web.json +0 -45
  412. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  413. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  414. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  415. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  416. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  417. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  418. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  419. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
  420. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  421. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  422. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  423. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  424. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  425. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  426. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  427. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
package/dist/index.d.ts CHANGED
@@ -1,7 +1,11 @@
1
- import { Command } from "commander";
2
- export * from "./config/schema.js";
3
- export * from "./config/loader.js";
4
- export * from "./types/generator.js";
5
- export * from "@testspectra/matchers";
1
+ import { Command } from 'commander';
2
+ export * from './config/schema.js';
3
+ export * from './config/loader.js';
4
+ export * from './generator/index.js';
5
+ export * from './refactor/index.js';
6
+ export * from './mobile/index.js';
7
+ export * from './linter/index.js';
8
+ export * from './types/generator.js';
9
+ export * from '@testspectra/matchers';
6
10
  export declare function createCliProgram(): Command;
7
11
  export declare function runCli(args?: string[]): Promise<void>;
package/dist/index.js CHANGED
@@ -1,72 +1,186 @@
1
- import { Command } from "commander";
2
- import { addCommand } from "./commands/add.js";
3
- import { demoCommand } from "./commands/demo.js";
4
- import { devicesCommand } from "./commands/devices.js";
5
- import { doctorCommand } from "./commands/doctor.js";
6
- import { initCommand } from "./commands/init.js";
7
- import { runCommand } from "./commands/run.js";
8
- import { updateCommand } from "./commands/update.js";
9
- import { watchCommand } from "./commands/watch.js";
10
- export * from "./config/schema.js";
11
- export * from "./config/loader.js";
12
- export * from "./types/generator.js";
13
- export * from "@testspectra/matchers";
1
+ import { Command } from 'commander';
2
+ import { addCommand } from './commands/add.js';
3
+ import { demoCommand } from './commands/demo.js';
4
+ import { devicesCommand } from './commands/devices.js';
5
+ import { doctorCommand } from './commands/doctor.js';
6
+ import { initCommand } from './commands/init.js';
7
+ import { installCommand } from './commands/install.js';
8
+ import { runCommand } from './commands/run.js';
9
+ import { testCommand } from './commands/test.js';
10
+ import { updateCommand } from './commands/update.js';
11
+ import { watchCommand } from './commands/watch.js';
12
+ import { syncTypesCommand } from './commands/sync-types.js';
13
+ import { refactorCommand } from './commands/refactor.js';
14
+ import { lintCommand } from './commands/lint.js';
15
+ import { licenseCommand } from './commands/license.js';
16
+ import { skillsInitCommand, skillsListCommand, skillsAddCommand, skillsUpdateCommand, skillsRemoveCommand, } from './commands/skills.js';
17
+ export * from './config/schema.js';
18
+ export * from './config/loader.js';
19
+ export * from './generator/index.js';
20
+ export * from './refactor/index.js';
21
+ export * from './mobile/index.js';
22
+ export * from './linter/index.js';
23
+ export * from './types/generator.js';
24
+ export * from '@testspectra/matchers';
14
25
  export function createCliProgram() {
15
26
  const program = new Command();
27
+ program.name('spectra').description('TestSpectra Zero-Config Cross-Platform Test Runner CLI').version('1.0.0');
16
28
  program
17
- .name("spectra")
18
- .description("TestSpectra Zero-Config Cross-Platform Test Runner CLI")
19
- .version("1.0.0");
20
- program
21
- .command("init")
22
- .description("Initialize testspectra.config.json and project directories")
23
- .option("-t, --template <template>", "Project template: default, nx")
24
- .option("-f, --force", "Overwrite existing configuration if present")
29
+ .command('init')
30
+ .description('Initialize testspectra.config.json and project directories')
31
+ .option('-t, --template <template>', 'Project template: default, nx, react-component')
32
+ .option('-f, --force', 'Overwrite existing configuration if present')
25
33
  .action(initCommand);
26
34
  program
27
- .command("add [module]")
28
- .description("Add a new TestSpectra E2E testing module to workspace or standalone project")
29
- .option("-e, --e2e-folder <name>", "Name of E2E sub-folder (default: e2e)")
30
- .option("-f, --force", "Overwrite existing module files if present")
35
+ .command('add [module]')
36
+ .description('Add a new TestSpectra E2E testing module to workspace or standalone project')
37
+ .option('-e, --e2e-folder <name>', 'Name of E2E sub-folder (default: e2e)')
38
+ .option('-f, --force', 'Overwrite existing module files if present')
31
39
  .action(addCommand);
32
40
  program
33
- .command("demo")
34
- .description("Launch the built-in TestSpectra Demo Web App server on localhost:5173")
35
- .option("-p, --port <port>", "Port to bind server (default: 5173)")
41
+ .command('install [dependency]')
42
+ .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`)
52
+ .action(installCommand);
53
+ program
54
+ .command('demo')
55
+ .description('Launch the built-in TestSpectra Demo Web App server on localhost:5173')
56
+ .option('-p, --port <port>', 'Port to bind server (default: 5173)')
36
57
  .action(demoCommand);
37
58
  program
38
- .command("watch")
39
- .description("Watch workspace and continuously auto-generate TestSpectra ambient declarations")
40
- .option("-w, --cwd <dir>", "Custom workspace directory")
59
+ .command('watch')
60
+ .description('Watch workspace and continuously auto-generate TestSpectra ambient declarations')
61
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
41
62
  .action(watchCommand);
42
63
  program
43
- .command("doctor")
44
- .description("Verify local environment prerequisites (ADB, Java, Chrome, Bun, Node)")
45
- .option("--fix", "Attempt automatic fix / download of missing tools")
46
- .option("--json", "Output diagnostic results as JSON")
64
+ .command('sync-types')
65
+ .description('Scan workspace and regenerate .testspectra/types/*.d.ts ambient declarations')
66
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
67
+ .action(syncTypesCommand);
68
+ program
69
+ .command('refactor [entityType] [oldName] [newName]')
70
+ .description('Atomically rename and propagate symbols (page-object, step, action, fixture)')
71
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
72
+ .option('--from <path>', 'Source file or directory to refactor from')
73
+ .option('--to <path>', 'Destination file or directory to refactor to')
74
+ .option('--dry-run', 'Preview changes without modifying files')
75
+ .option('--json', 'Output results as JSON')
76
+ .action(async (...args) => {
77
+ await refactorCommand(...args);
78
+ });
79
+ program
80
+ .command('lint [path]')
81
+ .description('Validate spec and suite schemas, frontmatter, and step integrity')
82
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
83
+ .option('--strict', 'Treat warnings as errors and exit with code 1')
84
+ .option('--json', 'Output diagnostic results as JSON')
85
+ .action(lintCommand);
86
+ program
87
+ .command('doctor')
88
+ .description('Verify local environment prerequisites (ADB, Spectra Android Driver, Chrome, Bun)')
89
+ .option('-v, --verbose', 'Show full installation paths for all detected runtimes and packages')
90
+ .option('--fix', 'Attempt automatic fix / download of missing tools')
91
+ .option('--json', 'Output diagnostic results as JSON')
47
92
  .action(doctorCommand);
48
93
  program
49
- .command("update")
50
- .description("Update @testspectra/cli and @testspectra/matchers to the latest version from npm")
51
- .option("--check", "Only check for available updates without installing")
94
+ .command('update')
95
+ .description('Update @testspectra/cli and @testspectra/matchers to the latest version from npm')
96
+ .option('--check', 'Only check for available updates without installing')
52
97
  .action(updateCommand);
53
98
  program
54
- .command("devices")
55
- .description("List connected Android/iOS devices and local browsers")
56
- .option("-t, --target <target>", "Filter scope: android, ios, web, all", "all")
99
+ .command('devices')
100
+ .description('List connected Android/iOS devices and local browsers')
101
+ .option('-t, --target <target>', 'Filter scope: android, ios, web, all', 'all')
102
+ .option('--json', 'Output results as JSON')
57
103
  .action(devicesCommand);
58
104
  program
59
- .command("run [path]")
60
- .description("Execute test suite or individual test case")
61
- .option("-s, --spec <path>", "Spec file path to execute")
62
- .option("-t, --target <target>", "Target platform: web, android, ios, common", "web")
63
- .option("-d, --device <device>", "Target device name or UDID")
64
- .option("--demo", "Automatically launch built-in demo web app server during test execution")
65
- .option("--headless", "Run in headless mode")
66
- .option("--no-headless", "Run in headed mode")
67
- .option("-w, --workdir <dir>", "Custom app data / temp workdir")
68
- .option("-o, --output <path>", "Custom test report JSON output path")
105
+ .command('license [action] [key]')
106
+ .description('Manage TestSpectra commercial license (activate, status, remove)')
107
+ .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
112
+ $ spectra license remove Remove locally activated license credentials`)
113
+ .action(licenseCommand);
114
+ const skills = program
115
+ .command('skills')
116
+ .description('Manage AI agent skills that standardize TestSpectra script authoring (@testspectra/skills)');
117
+ skills
118
+ .command('init')
119
+ .description('Prompt which AI agent(s) you use and save the choice for future add/update/remove/list')
120
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
121
+ .action(skillsInitCommand);
122
+ skills
123
+ .command('list')
124
+ .alias('ls')
125
+ .description('List all available and installed TestSpectra AI agent skills')
126
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
127
+ .option('-a, --agent <ids>', 'One-off override: comma-separated agent(s) to target, bypassing the saved "init" selection (claude, opencode, kilo, agents, gemini)')
128
+ .action(skillsListCommand);
129
+ skills
130
+ .command('add [skillIds...]')
131
+ .alias('install')
132
+ .description('Install specified skills into the detected AI agent directories (interactive if none provided)')
133
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
134
+ .option('-a, --agent <ids>', 'One-off override: comma-separated agent(s) to target, bypassing the saved "init" selection (claude, opencode, kilo, agents, gemini)')
135
+ .action(skillsAddCommand);
136
+ skills
137
+ .command('update [skillIds...]')
138
+ .alias('up')
139
+ .description('Update installed skills to the current bundled version (defaults to all installed)')
140
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
141
+ .option('-a, --agent <ids>', 'One-off override: comma-separated agent(s) to target, bypassing the saved "init" selection (claude, opencode, kilo, agents, gemini)')
142
+ .action(skillsUpdateCommand);
143
+ skills
144
+ .command('remove <skillIds...>')
145
+ .alias('rm')
146
+ .description('Remove specified skills from the detected AI agent directories')
147
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
148
+ .option('-a, --agent <ids>', 'One-off override: comma-separated agent(s) to target, bypassing the saved "init" selection (claude, opencode, kilo, agents, gemini)')
149
+ .action(skillsRemoveCommand);
150
+ program
151
+ .command('run [paths...]')
152
+ .description('Execute test suite or individual test case')
153
+ .option('-s, --spec <path>', 'Spec file path to execute')
154
+ .option('-t, --target <target>', 'Target platform: web, android, ios, common', 'web')
155
+ .option('-d, --device <device>', 'Target device name or UDID')
156
+ .option('--all-devices', 'Distribute test execution across all connected devices')
157
+ .option('--multi-device', 'Distribute test execution across all connected devices (alias for --all-devices)')
158
+ .option('--devices <list>', 'Comma-separated list of device UDIDs to run on')
159
+ .option('--license-key <key>', 'Explicit commercial license key override')
160
+ .option('--demo', 'Automatically launch built-in demo web app server during test execution')
161
+ .option('--headless', 'Run in headless mode')
162
+ .option('--no-headless', 'Run in headed mode')
163
+ .option('--headed', 'Run browser in visible GUI mode (alias for --no-headless)')
164
+ .option('--step-delay <ms>', 'Delay between consecutive actions and commands in milliseconds (slow motion)')
165
+ .option('--delay <ms>', 'Alias for --step-delay')
166
+ .option('--failed [targets]', 'Re-run failed tests from previous run (accepts comma-separated test IDs or spec paths)')
167
+ .option('-c, --concurrency <number>', 'Maximum parallel worker concurrency (auto-detected if omitted)')
168
+ .option('-w, --workdir <dir>', 'Custom app data / temp workdir')
169
+ .option('-o, --output <path>', 'Custom test report JSON output path')
69
170
  .action(runCommand);
171
+ program
172
+ .command('test')
173
+ .description('Run React component tests via @testspectra/react (bundled Vitest browser mode)')
174
+ .option('-w, --cwd <dir>', 'Custom workspace directory')
175
+ .option('-s, --spec <path>', 'Spec file path (or substring filter) to execute')
176
+ .option('--headless', 'Run in headless mode (default)')
177
+ .option('--no-headless', 'Run in headed mode')
178
+ .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)')
180
+ .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')
183
+ .action(testCommand);
70
184
  return program;
71
185
  }
72
186
  export async function runCli(args = process.argv) {
@@ -0,0 +1,290 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
5
+ import { discoverGlobalHooks, discoverSuiteHooks } from '../hook-discovery.js';
6
+ import { LifecycleDispatcher, HookExecutionError } from '../hook-dispatcher.js';
7
+ import { calculateWorkerCount, groupSpecs } from '../parallel-grouping.js';
8
+ describe('Core Lifecycle Execution Engine (docs/v2/cli/lifecycle-execution-engine.md)', () => {
9
+ let tmpDir;
10
+ beforeEach(() => {
11
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'testspectra-lifecycle-test-'));
12
+ });
13
+ afterEach(() => {
14
+ try {
15
+ fs.rmSync(tmpDir, { recursive: true, force: true });
16
+ }
17
+ catch { }
18
+ });
19
+ describe('Hook Discovery', () => {
20
+ it('discovers global-hooks and filters by platform hierarchy', () => {
21
+ const gBefore = path.join(tmpDir, 'global-hooks', 'before');
22
+ const gAfter = path.join(tmpDir, 'global-hooks', 'after');
23
+ fs.mkdirSync(gBefore, { recursive: true });
24
+ fs.mkdirSync(gAfter, { recursive: true });
25
+ fs.writeFileSync(path.join(gBefore, 'web.hook.ts'), 'export default async () => {}');
26
+ fs.writeFileSync(path.join(gBefore, 'mobile.hook.ts'), 'export default async () => {}');
27
+ fs.writeFileSync(path.join(gBefore, 'common.hook.ts'), 'export default async () => {}');
28
+ fs.writeFileSync(path.join(gAfter, 'web.hook.ts'), 'export default async () => {}');
29
+ const webGlobal = discoverGlobalHooks(tmpDir, 'web');
30
+ expect(webGlobal.before.some((h) => h.endsWith('web.hook.ts'))).toBe(true);
31
+ expect(webGlobal.before.some((h) => h.endsWith('common.hook.ts'))).toBe(true);
32
+ expect(webGlobal.before.some((h) => h.endsWith('mobile.hook.ts'))).toBe(false);
33
+ const mobileGlobal = discoverGlobalHooks(tmpDir, 'mobile');
34
+ expect(mobileGlobal.before.some((h) => h.endsWith('mobile.hook.ts'))).toBe(true);
35
+ expect(mobileGlobal.before.some((h) => h.endsWith('common.hook.ts'))).toBe(true);
36
+ expect(mobileGlobal.before.some((h) => h.endsWith('web.hook.ts'))).toBe(false);
37
+ });
38
+ it('discovers suite hooks (before, beforeEach, afterEach, after)', () => {
39
+ const suiteDir = path.join(tmpDir, 'specs', 'Auth');
40
+ for (const hookType of ['before', 'beforeEach', 'afterEach', 'after']) {
41
+ const hDir = path.join(suiteDir, 'hooks', hookType);
42
+ fs.mkdirSync(hDir, { recursive: true });
43
+ fs.writeFileSync(path.join(hDir, 'web.hook.ts'), 'export default async () => {}');
44
+ }
45
+ const suiteHooks = discoverSuiteHooks(suiteDir, 'web');
46
+ expect(suiteHooks.before.length).toBe(1);
47
+ expect(suiteHooks.beforeEach.length).toBe(1);
48
+ expect(suiteHooks.afterEach.length).toBe(1);
49
+ expect(suiteHooks.after.length).toBe(1);
50
+ });
51
+ });
52
+ describe('Lifecycle Dispatcher & Execution Flow', () => {
53
+ it('single run executes suite hooks without triggering global hooks', async () => {
54
+ const executionOrder = [];
55
+ const globalBeforeFn = vi.fn(async () => {
56
+ executionOrder.push('globalBefore');
57
+ });
58
+ const globalAfterFn = vi.fn(async () => {
59
+ executionOrder.push('globalAfter');
60
+ });
61
+ const suiteHooks = {
62
+ before: [
63
+ async () => {
64
+ executionOrder.push('suiteBefore');
65
+ },
66
+ ],
67
+ beforeEach: [
68
+ async () => {
69
+ executionOrder.push('beforeEach');
70
+ },
71
+ ],
72
+ afterEach: [
73
+ async () => {
74
+ executionOrder.push('afterEach');
75
+ },
76
+ ],
77
+ after: [
78
+ async () => {
79
+ executionOrder.push('suiteAfter');
80
+ },
81
+ ],
82
+ };
83
+ const dispatcher = new LifecycleDispatcher('single', {
84
+ before: [globalBeforeFn],
85
+ after: [globalAfterFn],
86
+ });
87
+ // Single run bypasses global before & global after
88
+ await dispatcher.runGlobalBefore();
89
+ expect(globalBeforeFn).not.toHaveBeenCalled();
90
+ const result = await dispatcher.executeSuite('AuthSuite', suiteHooks, [
91
+ {
92
+ id: 'TC-01',
93
+ title: 'Valid Login',
94
+ fn: async () => {
95
+ executionOrder.push('testBody');
96
+ },
97
+ },
98
+ ]);
99
+ await dispatcher.runGlobalAfter();
100
+ expect(globalAfterFn).not.toHaveBeenCalled();
101
+ expect(result.status).toBe('PASSED');
102
+ expect(executionOrder).toEqual(['suiteBefore', 'beforeEach', 'testBody', 'afterEach', 'suiteAfter']);
103
+ });
104
+ it('global batch run executes global-hooks/before once at start and global-hooks/after once at finish', async () => {
105
+ const executionOrder = [];
106
+ const globalBeforeFn = vi.fn(async () => {
107
+ executionOrder.push('globalBefore');
108
+ });
109
+ const globalAfterFn = vi.fn(async () => {
110
+ executionOrder.push('globalAfter');
111
+ });
112
+ const dispatcher = new LifecycleDispatcher('global', {
113
+ before: [globalBeforeFn],
114
+ after: [globalAfterFn],
115
+ });
116
+ await dispatcher.runGlobalBefore();
117
+ expect(globalBeforeFn).toHaveBeenCalledTimes(1);
118
+ const suiteHooks = {
119
+ before: [
120
+ async () => {
121
+ executionOrder.push('suiteBefore');
122
+ },
123
+ ],
124
+ beforeEach: [
125
+ async () => {
126
+ executionOrder.push('beforeEach');
127
+ },
128
+ ],
129
+ afterEach: [
130
+ async () => {
131
+ executionOrder.push('afterEach');
132
+ },
133
+ ],
134
+ after: [
135
+ async () => {
136
+ executionOrder.push('suiteAfter');
137
+ },
138
+ ],
139
+ };
140
+ const result = await dispatcher.executeSuite('AuthSuite', suiteHooks, [
141
+ {
142
+ id: 'TC-01',
143
+ title: 'Test 1',
144
+ fn: async () => {
145
+ executionOrder.push('tc1');
146
+ },
147
+ },
148
+ {
149
+ id: 'TC-02',
150
+ title: 'Test 2',
151
+ fn: async () => {
152
+ executionOrder.push('tc2');
153
+ },
154
+ },
155
+ ]);
156
+ await dispatcher.runGlobalAfter();
157
+ expect(globalAfterFn).toHaveBeenCalledTimes(1);
158
+ expect(result.status).toBe('PASSED');
159
+ expect(executionOrder).toEqual([
160
+ 'globalBefore',
161
+ 'suiteBefore',
162
+ 'beforeEach',
163
+ 'tc1',
164
+ 'afterEach',
165
+ 'beforeEach',
166
+ 'tc2',
167
+ 'afterEach',
168
+ 'suiteAfter',
169
+ 'globalAfter',
170
+ ]);
171
+ });
172
+ it('when beforeEach fails, test body is skipped, afterEach is guaranteed to execute, and test is marked as ERROR', async () => {
173
+ const testBodyFn = vi.fn();
174
+ let afterEachRan = false;
175
+ const suiteHooks = {
176
+ before: [],
177
+ beforeEach: [
178
+ async () => {
179
+ throw new Error('Database connection failed in beforeEach');
180
+ },
181
+ ],
182
+ afterEach: [
183
+ async () => {
184
+ afterEachRan = true;
185
+ },
186
+ ],
187
+ after: [],
188
+ };
189
+ const dispatcher = new LifecycleDispatcher('single');
190
+ const result = await dispatcher.executeSuite('FailingBeforeEachSuite', suiteHooks, [
191
+ {
192
+ id: 'TC-FAIL-01',
193
+ title: 'Should be skipped',
194
+ fn: testBodyFn,
195
+ },
196
+ ]);
197
+ expect(testBodyFn).not.toHaveBeenCalled();
198
+ expect(afterEachRan).toBe(true);
199
+ expect(result.results[0].status).toBe('ERROR');
200
+ expect(result.results[0].error?.message).toContain('Database connection failed in beforeEach');
201
+ expect(result.status).toBe('ERROR');
202
+ });
203
+ it('when afterEach fails, test transitions to ERROR status', async () => {
204
+ const suiteHooks = {
205
+ before: [],
206
+ beforeEach: [],
207
+ afterEach: [
208
+ async () => {
209
+ throw new Error('Teardown resource leak in afterEach');
210
+ },
211
+ ],
212
+ after: [],
213
+ };
214
+ const dispatcher = new LifecycleDispatcher('single');
215
+ const result = await dispatcher.executeSuite('TeardownFailSuite', suiteHooks, [
216
+ {
217
+ id: 'TC-01',
218
+ title: 'Passing Body but failing afterEach',
219
+ fn: async () => {
220
+ // Passing body
221
+ },
222
+ },
223
+ ]);
224
+ expect(result.results[0].status).toBe('ERROR');
225
+ expect(result.results[0].error?.message).toContain('Teardown resource leak in afterEach');
226
+ expect(result.status).toBe('ERROR');
227
+ });
228
+ it('when global-hooks/before fails, throws HookExecutionError immediately', async () => {
229
+ const dispatcher = new LifecycleDispatcher('global', {
230
+ before: [
231
+ async () => {
232
+ throw new Error('Global health check failed');
233
+ },
234
+ ],
235
+ after: [],
236
+ });
237
+ await expect(dispatcher.runGlobalBefore()).rejects.toThrow(HookExecutionError);
238
+ await expect(dispatcher.runGlobalBefore()).rejects.toThrow('Global health check failed');
239
+ });
240
+ });
241
+ describe('Parallel Spec Grouping & Dynamic Worker Sizing', () => {
242
+ it('prioritizes explicit user concurrency flag over auto-calculation', () => {
243
+ const targets = Array.from({ length: 10 }, () => 'spec.ts');
244
+ expect(calculateWorkerCount(targets, { userConcurrency: 4 })).toBe(4);
245
+ // Clamps to item count if items < userConcurrency
246
+ expect(calculateWorkerCount(['only-one.ts'], { userConcurrency: 4 })).toBe(1);
247
+ });
248
+ it('enforces low-CPU safety ratio based on container/host CPU quota', () => {
249
+ const targets = Array.from({ length: 10 }, () => 'spec.ts');
250
+ const highRam = 16 * 1024 * 1024 * 1024; // 16 GB
251
+ // CPU <= 2 (e.g. 2-vCPU standard CI runner): clamped to 1 worker
252
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: 1, memoryBytesOverride: highRam })).toBe(1);
253
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: 2, memoryBytesOverride: highRam })).toBe(1);
254
+ // CPU 3 - 4: allocates 2 workers
255
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: 3, memoryBytesOverride: highRam })).toBe(2);
256
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: 4, memoryBytesOverride: highRam })).toBe(2);
257
+ // CPU > 4: allocates (cpu - 2) capped at maxWorkersCap (default 6)
258
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: 6, memoryBytesOverride: highRam })).toBe(4);
259
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: 8, memoryBytesOverride: highRam })).toBe(6);
260
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: 64, memoryBytesOverride: highRam })).toBe(6);
261
+ });
262
+ it('enforces memory-aware guard to prevent OOM kills during browser automation', () => {
263
+ const targets = Array.from({ length: 10 }, () => 'spec.ts');
264
+ const highCpu = 16;
265
+ // 500 MB RAM budget -> allows at most 1 worker (~600MB budget per browser)
266
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: highCpu, memoryBytesOverride: 500 * 1024 * 1024 })).toBe(1);
267
+ // 1.5 GB RAM budget -> allows 2 workers
268
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: highCpu, memoryBytesOverride: 1500 * 1024 * 1024 })).toBe(2);
269
+ // 2.5 GB RAM budget -> allows 4 workers
270
+ expect(calculateWorkerCount(targets, { cpuQuotaOverride: highCpu, memoryBytesOverride: 2500 * 1024 * 1024 })).toBe(4);
271
+ });
272
+ it('groups specs by suite directory in nested array format when mode is "suite"', () => {
273
+ const specs = [
274
+ 'specs/Auth/TC-001/web.test.ts',
275
+ 'specs/Auth/TC-002/web.test.ts',
276
+ 'specs/Cart/TC-010/web.test.ts',
277
+ 'specs/Cart/TC-011/web.test.ts',
278
+ ];
279
+ const grouped = groupSpecs(specs, 'suite');
280
+ expect(grouped.length).toBe(2);
281
+ expect(grouped[0]).toEqual(['specs/Auth/TC-001/web.test.ts', 'specs/Auth/TC-002/web.test.ts']);
282
+ expect(grouped[1]).toEqual(['specs/Cart/TC-010/web.test.ts', 'specs/Cart/TC-011/web.test.ts']);
283
+ });
284
+ it('flattens specs when mode is "testcase"', () => {
285
+ const specs = ['specs/Auth/TC-001/web.test.ts', 'specs/Auth/TC-002/web.test.ts', 'specs/Cart/TC-010/web.test.ts'];
286
+ const flat = groupSpecs(specs, 'testcase');
287
+ expect(flat).toEqual(specs);
288
+ });
289
+ });
290
+ });
@@ -0,0 +1,24 @@
1
+ import { PlatformTarget } from '../generator/type-generator.js';
2
+ export type HookEntry = string | (() => Promise<void> | void);
3
+ export interface DiscoveredSuiteHooks {
4
+ before: string[];
5
+ beforeEach: string[];
6
+ afterEach: string[];
7
+ after: string[];
8
+ }
9
+ export interface DiscoveredGlobalHooks {
10
+ before: string[];
11
+ after: string[];
12
+ }
13
+ export interface SuiteHooks {
14
+ before: HookEntry[];
15
+ beforeEach: HookEntry[];
16
+ afterEach: HookEntry[];
17
+ after: HookEntry[];
18
+ }
19
+ export interface GlobalHooks {
20
+ before: HookEntry[];
21
+ after: HookEntry[];
22
+ }
23
+ export declare function discoverGlobalHooks(cwd: string, platform?: PlatformTarget): DiscoveredGlobalHooks;
24
+ export declare function discoverSuiteHooks(suiteDir: string, platform?: PlatformTarget): DiscoveredSuiteHooks;
@@ -0,0 +1,60 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { PLATFORM_HIERARCHY } from '../generator/type-generator.js';
4
+ function resolveHookFilesForType(dir, platform = 'web') {
5
+ if (!fs.existsSync(dir))
6
+ return [];
7
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
8
+ const hierarchy = PLATFORM_HIERARCHY[platform] || ['common'];
9
+ const matched = [];
10
+ // Group files by base name if applicable or collect directly
11
+ for (const entry of entries) {
12
+ if (!entry.isFile() || !entry.name.endsWith('.hook.ts'))
13
+ continue;
14
+ const parts = entry.name.split('.');
15
+ // Formats:
16
+ // 1. web.hook.ts -> parts = ['web', 'hook', 'ts']
17
+ // 2. auth.web.hook.ts -> parts = ['auth', 'web', 'hook', 'ts']
18
+ // 3. auth.hook.ts -> parts = ['auth', 'hook', 'ts']
19
+ let filePlatform = 'common';
20
+ if (parts.length === 3) {
21
+ filePlatform = parts[0];
22
+ }
23
+ else if (parts.length >= 4) {
24
+ filePlatform = parts[parts.length - 3];
25
+ }
26
+ if (hierarchy.includes(filePlatform) || filePlatform === 'common') {
27
+ matched.push(path.join(dir, entry.name));
28
+ }
29
+ }
30
+ // Sort by hierarchy priority
31
+ matched.sort((a, b) => {
32
+ const getPriority = (filePath) => {
33
+ const base = path.basename(filePath);
34
+ for (let i = 0; i < hierarchy.length; i++) {
35
+ if (base.includes(`.${hierarchy[i]}.`) || base.startsWith(`${hierarchy[i]}.`)) {
36
+ return i;
37
+ }
38
+ }
39
+ return hierarchy.length;
40
+ };
41
+ return getPriority(a) - getPriority(b);
42
+ });
43
+ return matched;
44
+ }
45
+ export function discoverGlobalHooks(cwd, platform = 'web') {
46
+ const globalHooksDir = path.join(cwd, 'global-hooks');
47
+ return {
48
+ before: resolveHookFilesForType(path.join(globalHooksDir, 'before'), platform),
49
+ after: resolveHookFilesForType(path.join(globalHooksDir, 'after'), platform),
50
+ };
51
+ }
52
+ export function discoverSuiteHooks(suiteDir, platform = 'web') {
53
+ const hooksDir = path.join(suiteDir, 'hooks');
54
+ return {
55
+ before: resolveHookFilesForType(path.join(hooksDir, 'before'), platform),
56
+ beforeEach: resolveHookFilesForType(path.join(hooksDir, 'beforeEach'), platform),
57
+ afterEach: resolveHookFilesForType(path.join(hooksDir, 'afterEach'), platform),
58
+ after: resolveHookFilesForType(path.join(hooksDir, 'after'), platform),
59
+ };
60
+ }