@testspectra/cli 1.0.70 → 1.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/LICENSE.md +48 -0
  2. package/README.md +36 -270
  3. package/bin/spectra.js +1 -1
  4. package/bin/testspectra-runner +0 -0
  5. package/demo-app/assets/index-BGllUp7o.js +140 -0
  6. package/demo-app/assets/index-BhlW-eXG.css +1 -0
  7. package/demo-app/index.html +2 -2
  8. package/dist/.tsbuildinfo +1 -0
  9. package/dist/commands/__tests__/doctor.test.d.ts +1 -0
  10. package/dist/commands/__tests__/doctor.test.js +39 -0
  11. package/dist/commands/__tests__/init.test.d.ts +1 -0
  12. package/dist/commands/__tests__/init.test.js +148 -0
  13. package/dist/commands/__tests__/license.test.d.ts +1 -0
  14. package/dist/commands/__tests__/license.test.js +13 -0
  15. package/dist/commands/__tests__/refactor.test.d.ts +1 -0
  16. package/dist/commands/__tests__/refactor.test.js +64 -0
  17. package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
  18. package/dist/commands/__tests__/run-e2e.test.js +422 -0
  19. package/dist/commands/__tests__/run.test.d.ts +1 -0
  20. package/dist/commands/__tests__/run.test.js +277 -0
  21. package/dist/commands/__tests__/watch.test.d.ts +1 -0
  22. package/dist/commands/__tests__/watch.test.js +155 -0
  23. package/dist/commands/add.js +86 -85
  24. package/dist/commands/demo.js +21 -11
  25. package/dist/commands/devices.d.ts +14 -2
  26. package/dist/commands/devices.js +44 -34
  27. package/dist/commands/doctor.d.ts +3 -1
  28. package/dist/commands/doctor.js +43 -264
  29. package/dist/commands/init.d.ts +1 -1
  30. package/dist/commands/init.js +206 -196
  31. package/dist/commands/install.d.ts +4 -0
  32. package/dist/commands/install.js +59 -0
  33. package/dist/commands/license.d.ts +4 -0
  34. package/dist/commands/license.js +30 -0
  35. package/dist/commands/lint.d.ts +5 -0
  36. package/dist/commands/lint.js +55 -0
  37. package/dist/commands/refactor.d.ts +9 -0
  38. package/dist/commands/refactor.js +172 -0
  39. package/dist/commands/run.d.ts +23 -2
  40. package/dist/commands/run.js +321 -111
  41. package/dist/commands/skills.d.ts +9 -0
  42. package/dist/commands/skills.js +28 -0
  43. package/dist/commands/sync-types.d.ts +4 -0
  44. package/dist/commands/sync-types.js +14 -0
  45. package/dist/commands/update.js +39 -43
  46. package/dist/commands/watch.d.ts +6 -0
  47. package/dist/commands/watch.js +46 -43
  48. package/dist/config/loader.d.ts +1 -1
  49. package/dist/config/loader.js +40 -25
  50. package/dist/config/schema.d.ts +117 -54
  51. package/dist/config/schema.js +41 -35
  52. package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
  53. package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -0
  54. package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
  55. package/dist/generator/__tests__/type-generator.test.js +386 -0
  56. package/dist/generator/index.d.ts +2 -0
  57. package/dist/generator/index.js +2 -0
  58. package/dist/generator/tsconfig-generator.d.ts +27 -0
  59. package/dist/generator/tsconfig-generator.js +384 -0
  60. package/dist/generator/type-generator.d.ts +36 -0
  61. package/dist/generator/type-generator.js +644 -0
  62. package/dist/index.d.ts +9 -5
  63. package/dist/index.js +151 -51
  64. package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
  65. package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
  66. package/dist/lifecycle/hook-discovery.d.ts +24 -0
  67. package/dist/lifecycle/hook-discovery.js +60 -0
  68. package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
  69. package/dist/lifecycle/hook-dispatcher.js +190 -0
  70. package/dist/lifecycle/index.d.ts +3 -0
  71. package/dist/lifecycle/index.js +3 -0
  72. package/dist/lifecycle/parallel-grouping.d.ts +27 -0
  73. package/dist/lifecycle/parallel-grouping.js +151 -0
  74. package/dist/linter/__tests__/anonymous-export.test.d.ts +1 -0
  75. package/dist/linter/__tests__/anonymous-export.test.js +184 -0
  76. package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
  77. package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
  78. package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
  79. package/dist/linter/__tests__/spec-schema.test.js +369 -0
  80. package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
  81. package/dist/linter/__tests__/step-parity.test.js +372 -0
  82. package/dist/linter/discovery.d.ts +30 -0
  83. package/dist/linter/discovery.js +249 -0
  84. package/dist/linter/extractor.d.ts +66 -0
  85. package/dist/linter/extractor.js +235 -0
  86. package/dist/linter/index.d.ts +7 -0
  87. package/dist/linter/index.js +7 -0
  88. package/dist/linter/parser.d.ts +24 -0
  89. package/dist/linter/parser.js +219 -0
  90. package/dist/linter/schemas/spec.schema.d.ts +83 -0
  91. package/dist/linter/schemas/spec.schema.js +150 -0
  92. package/dist/linter/schemas/suite.schema.d.ts +59 -0
  93. package/dist/linter/schemas/suite.schema.js +83 -0
  94. package/dist/linter/spec-linter.d.ts +23 -0
  95. package/dist/linter/spec-linter.js +595 -0
  96. package/dist/linter/types.d.ts +28 -0
  97. package/dist/linter/types.js +1 -0
  98. package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
  99. package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
  100. package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
  101. package/dist/mobile/__tests__/device-pool.test.js +97 -0
  102. package/dist/mobile/device-pool.d.ts +23 -0
  103. package/dist/mobile/device-pool.js +1 -0
  104. package/dist/mobile/index.d.ts +1 -0
  105. package/dist/mobile/index.js +1 -0
  106. package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
  107. package/dist/refactor/__tests__/refactor-engine.test.js +873 -0
  108. package/dist/refactor/index.d.ts +1 -0
  109. package/dist/refactor/index.js +1 -0
  110. package/dist/refactor/refactor-engine.d.ts +50 -0
  111. package/dist/refactor/refactor-engine.js +587 -0
  112. package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
  113. package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
  114. package/dist/reporter/semantic-formatter.d.ts +6 -0
  115. package/dist/reporter/semantic-formatter.js +191 -0
  116. package/dist/reporter/summary-view.d.ts +12 -0
  117. package/dist/reporter/summary-view.js +19 -0
  118. package/dist/reporter/terminal-stream.d.ts +32 -0
  119. package/dist/reporter/terminal-stream.js +82 -0
  120. package/dist/reporter/types.d.ts +2 -0
  121. package/dist/reporter/types.js +1 -0
  122. package/dist/runner/bridge.d.ts +10 -5
  123. package/dist/runner/bridge.js +101 -45
  124. package/dist/runner/reporter.d.ts +32 -2
  125. package/dist/runner/reporter.js +423 -13
  126. package/dist/server/demo.js +43 -44
  127. package/dist/types/generator.d.ts +1 -17
  128. package/dist/types/generator.js +1 -443
  129. package/dist/utils/__tests__/api-server.test.d.ts +1 -0
  130. package/dist/utils/__tests__/api-server.test.js +62 -0
  131. package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
  132. package/dist/utils/__tests__/demo-server.test.js +35 -0
  133. package/dist/utils/api-server.d.ts +19 -0
  134. package/dist/utils/api-server.js +134 -0
  135. package/dist/utils/demo-server.d.ts +5 -0
  136. package/dist/utils/demo-server.js +46 -36
  137. package/package.json +24 -13
  138. package/templates/default/CLAUDE.md +12 -5
  139. package/templates/default/fixtures/apiData.json +24 -0
  140. package/templates/default/package.json +12 -3
  141. package/templates/default/page-objects/MatchersPage/mobile.ts +53 -17
  142. package/templates/default/page-objects/MatchersPage/web.ts +17 -2
  143. package/templates/default/page-objects/NavigationPage/mobile.ts +11 -2
  144. package/templates/default/page-objects/NavigationPage/web.ts +1 -1
  145. package/templates/default/page-objects/PlaygroundPage/mobile.ts +28 -18
  146. package/templates/default/page-objects/PlaygroundPage/web.ts +4 -2
  147. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -0
  148. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -0
  149. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -0
  150. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
  151. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
  152. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -0
  153. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -0
  154. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -0
  155. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +33 -0
  156. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
  157. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
  158. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
  159. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
  160. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
  161. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
  162. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
  163. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
  164. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
  165. package/templates/default/specs/ApiInterception/suite.md +10 -0
  166. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
  167. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
  168. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
  169. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
  170. package/templates/default/specs/ApiSeeding/suite.md +9 -0
  171. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
  172. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +5 -0
  173. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  174. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
  175. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
  176. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
  177. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +5 -0
  178. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
  179. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
  180. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
  181. package/templates/default/specs/DeepLink/suite.md +9 -0
  182. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
  183. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +5 -0
  184. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  185. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
  186. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +7 -0
  187. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  188. package/templates/default/specs/ElementMatchers/suite.md +2 -1
  189. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
  190. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
  191. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
  192. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
  193. package/templates/default/specs/PermissionRationale/suite.md +9 -0
  194. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
  195. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
  196. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
  197. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
  198. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
  199. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
  200. package/templates/default/spectra.config.ts +21 -15
  201. package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
  202. package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
  203. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -1
  204. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +3 -3
  205. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -2
  206. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +4 -4
  207. package/templates/default/tsconfig.json +3 -1
  208. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
  209. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
  210. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  211. package/templates/nx/.nx/workspace-data/d/daemon.log +4306 -0
  212. package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
  213. package/templates/nx/.nx/workspace-data/file-map.json +452 -0
  214. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
  215. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  216. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
  217. package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
  218. package/templates/nx/CLAUDE.md +2 -2
  219. package/templates/nx/package.json +6 -4
  220. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -0
  221. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  222. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  223. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -0
  224. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  225. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  226. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
  227. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
  228. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  229. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +1 -1
  230. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +15 -5
  231. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
  232. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
  233. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
  234. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  235. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
  236. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  237. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
  238. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
  239. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
  240. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
  241. package/templates/nx/pnpm-lock.yaml +6663 -0
  242. package/templates/nx/pnpm-workspace.yaml +2 -1
  243. package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
  244. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +13 -1
  245. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +85 -17
  246. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
  247. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
  248. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
  249. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +4 -2
  250. package/templates/nx/shared/testing/project.json +19 -0
  251. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
  252. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
  253. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
  254. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +3 -3
  255. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +2 -2
  256. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +4 -4
  257. package/templates/nx/spectra.config.ts +19 -13
  258. package/templates/nx/tsconfig.json +3 -1
  259. package/demo-app/assets/index-DAUlrHTr.css +0 -1
  260. package/demo-app/assets/index-DN2Kb0A8.js +0 -100
  261. package/dist/index.cjs +0 -101
  262. package/dist/plugin.cjs +0 -575
  263. package/dist/plugin.d.ts +0 -16
  264. package/dist/plugin.js +0 -184
  265. package/dist/step/index.d.ts +0 -6
  266. package/dist/step/index.js +0 -6
  267. package/dist/step/matchers.d.ts +0 -14
  268. package/dist/step/matchers.js +0 -114
  269. package/dist/step/proto.d.ts +0 -46
  270. package/dist/step/proto.js +0 -114
  271. package/dist/step/runner/collection.d.ts +0 -13
  272. package/dist/step/runner/collection.js +0 -51
  273. package/dist/step/runner/single.d.ts +0 -21
  274. package/dist/step/runner/single.js +0 -138
  275. package/dist/step/spectra.d.ts +0 -34
  276. package/dist/step/spectra.js +0 -156
  277. package/dist/step/types.d.ts +0 -32
  278. package/dist/step/types.js +0 -5
  279. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  280. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  281. package/templates/default/tsconfig.spectra.android.json +0 -46
  282. package/templates/default/tsconfig.spectra.ios.json +0 -46
  283. package/templates/default/tsconfig.spectra.json +0 -22
  284. package/templates/default/tsconfig.spectra.web.json +0 -53
  285. package/templates/nx/fixtures/playgroundData.json +0 -7
  286. package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
  287. package/templates/nx/global-hooks/after/web.hook.ts +0 -3
  288. package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
  289. package/templates/nx/global-hooks/before/web.hook.ts +0 -3
  290. package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
  291. package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
  292. package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
  293. package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
  294. package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
  295. package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
  296. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
  297. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
  298. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
  299. package/templates/nx/specs/BrowserContext/suite.md +0 -9
  300. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  301. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
  302. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  303. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
  304. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
  305. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
  306. package/templates/nx/specs/ContentMatchers/suite.md +0 -9
  307. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
  308. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
  309. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
  310. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
  311. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
  312. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
  313. package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
  314. package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
  315. package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
  316. package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
  317. package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
  318. package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
  319. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
  320. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
  321. package/templates/nx/specs/ElementMatchers/suite.md +0 -9
  322. package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
  323. package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
  324. package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
  325. package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
  326. package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
  327. package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
  328. package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
  329. package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
  330. package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
  331. package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
  332. package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
  333. package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
  334. package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
  335. package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
  336. package/templates/nx/specs/Playground/suite.md +0 -9
  337. package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
  338. package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
  339. package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
  340. package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
  341. package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
  342. package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
  343. package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
  344. package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
  345. package/templates/nx/tsconfig.spectra.android.json +0 -39
  346. package/templates/nx/tsconfig.spectra.ios.json +0 -39
  347. package/templates/nx/tsconfig.spectra.json +0 -25
  348. package/templates/nx/tsconfig.spectra.shared.json +0 -25
  349. package/templates/nx/tsconfig.spectra.web.json +0 -45
  350. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  351. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  352. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  353. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  354. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  355. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  356. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  357. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
  358. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  359. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  360. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  361. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  362. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  363. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  364. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  365. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
@@ -1,26 +1,26 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
4
- import * as p from "@clack/prompts";
5
- import chalk from "chalk";
6
- import { TypeGenerator } from "../types/generator.js";
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import * as p from '@clack/prompts';
5
+ import chalk from 'chalk';
6
+ import { TypeGenerator } from '../types/generator.js';
7
7
  function parsePnpmWorkspaceGlobs(content) {
8
- const lines = content.split("\n");
8
+ const lines = content.split('\n');
9
9
  const globs = [];
10
10
  let inPackages = false;
11
11
  for (const line of lines) {
12
12
  const trimmed = line.trim();
13
- if (trimmed.startsWith("packages:")) {
13
+ if (trimmed.startsWith('packages:')) {
14
14
  inPackages = true;
15
15
  continue;
16
16
  }
17
17
  if (inPackages) {
18
- if (trimmed.startsWith("-")) {
19
- const item = trimmed.replace(/^-\s*['"]?/, "").replace(/['"]?\s*$/, "");
18
+ if (trimmed.startsWith('-')) {
19
+ const item = trimmed.replace(/^-\s*['"]?/, '').replace(/['"]?\s*$/, '');
20
20
  if (item)
21
21
  globs.push(item);
22
22
  }
23
- else if (trimmed && !trimmed.startsWith("#")) {
23
+ else if (trimmed && !trimmed.startsWith('#')) {
24
24
  break;
25
25
  }
26
26
  }
@@ -34,11 +34,11 @@ function scanDirectoriesForProjects(cwd, patterns) {
34
34
  if (currentDepth > maxDepth || !fs.existsSync(dir))
35
35
  return;
36
36
  const base = path.basename(dir);
37
- if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git" || base === "shared") {
37
+ if (base === 'node_modules' || base === 'dist' || base === '.testspectra' || base === '.git' || base === 'shared') {
38
38
  return;
39
39
  }
40
- const hasPkg = fs.existsSync(path.join(dir, "package.json"));
41
- const hasNx = fs.existsSync(path.join(dir, "project.json"));
40
+ const hasPkg = fs.existsSync(path.join(dir, 'package.json'));
41
+ const hasNx = fs.existsSync(path.join(dir, 'project.json'));
42
42
  if ((hasPkg || hasNx) && dir !== cwd) {
43
43
  const rel = path.relative(cwd, dir);
44
44
  if (!visited.has(rel)) {
@@ -46,7 +46,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
46
46
  let name = path.basename(dir);
47
47
  if (hasPkg) {
48
48
  try {
49
- const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf-8"));
49
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
50
50
  if (pkg.name)
51
51
  name = pkg.name;
52
52
  }
@@ -54,7 +54,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
54
54
  }
55
55
  else if (hasNx) {
56
56
  try {
57
- const nxJson = JSON.parse(fs.readFileSync(path.join(dir, "project.json"), "utf-8"));
57
+ const nxJson = JSON.parse(fs.readFileSync(path.join(dir, 'project.json'), 'utf-8'));
58
58
  if (nxJson.name)
59
59
  name = nxJson.name;
60
60
  }
@@ -81,7 +81,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
81
81
  }
82
82
  if (patterns.length > 0) {
83
83
  for (const pat of patterns) {
84
- const cleanPat = pat.replace(/\/\*\*?$/, "").replace(/\/\*$/, "");
84
+ const cleanPat = pat.replace(/\/\*\*?$/, '').replace(/\/\*$/, '');
85
85
  const searchRoot = path.join(cwd, cleanPat);
86
86
  if (fs.existsSync(searchRoot)) {
87
87
  const stat = fs.statSync(searchRoot);
@@ -92,7 +92,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
92
92
  }
93
93
  }
94
94
  else {
95
- for (const fallback of ["packages", "modules", "apps", "libs", "src"]) {
95
+ for (const fallback of ['packages', 'modules', 'apps', 'libs', 'src']) {
96
96
  const fbPath = path.join(cwd, fallback);
97
97
  if (fs.existsSync(fbPath)) {
98
98
  search(fbPath, 1, 3);
@@ -107,43 +107,43 @@ export async function addCommand(moduleNameArg, options = {}) {
107
107
  let rootDir = cwd;
108
108
  let cur = cwd;
109
109
  while (cur !== path.dirname(cur)) {
110
- if (fs.existsSync(path.join(cur, "spectra.config.ts")) ||
111
- fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) ||
112
- fs.existsSync(path.join(cur, "nx.json"))) {
110
+ if (fs.existsSync(path.join(cur, 'spectra.config.ts')) ||
111
+ fs.existsSync(path.join(cur, 'pnpm-workspace.yaml')) ||
112
+ fs.existsSync(path.join(cur, 'nx.json'))) {
113
113
  rootDir = cur;
114
114
  break;
115
115
  }
116
116
  cur = path.dirname(cur);
117
117
  }
118
- p.intro(chalk.bold.cyan("✨ TestSpectra Module Generator"));
118
+ p.intro(chalk.bold.cyan('✨ TestSpectra Module Generator'));
119
119
  // Check if we are in a monorepo
120
- const pnpmWorkspacePath = path.join(rootDir, "pnpm-workspace.yaml");
121
- const nxJsonPath = path.join(rootDir, "nx.json");
120
+ const pnpmWorkspacePath = path.join(rootDir, 'pnpm-workspace.yaml');
121
+ const nxJsonPath = path.join(rootDir, 'nx.json');
122
122
  const isMonorepo = fs.existsSync(pnpmWorkspacePath) || fs.existsSync(nxJsonPath);
123
123
  // 2. Resolve template directory
124
124
  const __filename = fileURLToPath(import.meta.url);
125
125
  const __dirname = path.dirname(__filename);
126
- let templateDir = path.resolve(__dirname, "../templates/nx");
126
+ let templateDir = path.resolve(__dirname, '../templates/nx');
127
127
  if (!fs.existsSync(templateDir)) {
128
- templateDir = path.resolve(__dirname, "../../templates/nx");
128
+ templateDir = path.resolve(__dirname, '../../templates/nx');
129
129
  }
130
130
  if (!fs.existsSync(templateDir)) {
131
- templateDir = path.resolve(__dirname, "../../../templates/nx");
131
+ templateDir = path.resolve(__dirname, '../../../templates/nx');
132
132
  }
133
- const sampleE2eSrc = path.join(templateDir, "packages/playground/e2e");
133
+ const sampleE2eSrc = path.join(templateDir, 'packages/playground/e2e');
134
134
  if (!fs.existsSync(sampleE2eSrc)) {
135
135
  p.cancel(chalk.red(`Template directory not found at: ${sampleE2eSrc}`));
136
136
  return;
137
137
  }
138
138
  // 3. Resolve CLI dependency version
139
- let cliVersion = "^1.0.65";
139
+ let cliVersion = '^1.0.65';
140
140
  try {
141
141
  let pDir = __dirname;
142
142
  while (pDir !== path.dirname(pDir)) {
143
- const cand = path.join(pDir, "package.json");
143
+ const cand = path.join(pDir, 'package.json');
144
144
  if (fs.existsSync(cand)) {
145
- const cliPkg = JSON.parse(fs.readFileSync(cand, "utf-8"));
146
- if (cliPkg.name === "@testspectra/cli" && cliPkg.version) {
145
+ const cliPkg = JSON.parse(fs.readFileSync(cand, 'utf-8'));
146
+ if (cliPkg.name === '@testspectra/cli' && cliPkg.version) {
147
147
  cliVersion = `^${cliPkg.version}`;
148
148
  break;
149
149
  }
@@ -152,10 +152,10 @@ export async function addCommand(moduleNameArg, options = {}) {
152
152
  }
153
153
  }
154
154
  catch { }
155
- const cliDepVersion = cliVersion.startsWith("^") ? cliVersion : `^${cliVersion}`;
155
+ const cliDepVersion = cliVersion.startsWith('^') ? cliVersion : `^${cliVersion}`;
156
156
  function copyRecursive(src, dest, replacements = {}) {
157
157
  const base = path.basename(src);
158
- if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git") {
158
+ if (base === 'node_modules' || base === 'dist' || base === '.testspectra' || base === '.git') {
159
159
  return;
160
160
  }
161
161
  const stat = fs.statSync(src);
@@ -172,23 +172,23 @@ export async function addCommand(moduleNameArg, options = {}) {
172
172
  if (fs.existsSync(dest) && !options.force) {
173
173
  return;
174
174
  }
175
- let content = fs.readFileSync(src, "utf-8");
175
+ let content = fs.readFileSync(src, 'utf-8');
176
176
  for (const [k, v] of Object.entries(replacements)) {
177
177
  content = content.replaceAll(k, v);
178
178
  }
179
- if (dest.endsWith("package.json")) {
179
+ if (dest.endsWith('package.json')) {
180
180
  try {
181
181
  const pkg = JSON.parse(content);
182
182
  if (pkg.devDependencies) {
183
183
  for (const key of Object.keys(pkg.devDependencies)) {
184
- if (key.startsWith("@testspectra/") || pkg.devDependencies[key] === "workspace:*") {
184
+ if (key.startsWith('@testspectra/') || pkg.devDependencies[key] === 'workspace:*') {
185
185
  pkg.devDependencies[key] = cliDepVersion;
186
186
  }
187
187
  }
188
188
  }
189
189
  if (pkg.dependencies) {
190
190
  for (const key of Object.keys(pkg.dependencies)) {
191
- if (key.startsWith("@testspectra/") || pkg.dependencies[key] === "workspace:*") {
191
+ if (key.startsWith('@testspectra/') || pkg.dependencies[key] === 'workspace:*') {
192
192
  pkg.dependencies[key] = cliDepVersion;
193
193
  }
194
194
  }
@@ -197,7 +197,7 @@ export async function addCommand(moduleNameArg, options = {}) {
197
197
  }
198
198
  catch { }
199
199
  }
200
- fs.writeFileSync(dest, content, "utf-8");
200
+ fs.writeFileSync(dest, content, 'utf-8');
201
201
  }
202
202
  }
203
203
  // If in a Monorepo
@@ -205,13 +205,13 @@ export async function addCommand(moduleNameArg, options = {}) {
205
205
  let pnpmPatterns = [];
206
206
  if (fs.existsSync(pnpmWorkspacePath)) {
207
207
  try {
208
- pnpmPatterns = parsePnpmWorkspaceGlobs(fs.readFileSync(pnpmWorkspacePath, "utf-8"));
208
+ pnpmPatterns = parsePnpmWorkspaceGlobs(fs.readFileSync(pnpmWorkspacePath, 'utf-8'));
209
209
  }
210
210
  catch { }
211
211
  }
212
212
  const detectedProjects = scanDirectoriesForProjects(rootDir, pnpmPatterns);
213
- let targetProjectPath = "";
214
- let featureName = "";
213
+ let targetProjectPath = '';
214
+ let featureName = '';
215
215
  if (moduleNameArg) {
216
216
  // Find if moduleNameArg matches an existing detected package
217
217
  const match = detectedProjects.find((p) => p.name === moduleNameArg || p.relPath === moduleNameArg || path.basename(p.relPath) === moduleNameArg);
@@ -226,35 +226,35 @@ export async function addCommand(moduleNameArg, options = {}) {
226
226
  }
227
227
  else {
228
228
  const mode = await p.select({
229
- message: "How would you like to add the TestSpectra module?",
229
+ message: 'How would you like to add the TestSpectra module?',
230
230
  options: [
231
231
  {
232
- label: "Add E2E to an existing workspace project / feature",
233
- value: "existing",
234
- hint: "Attach E2E test suite to a detected workspace package",
232
+ label: 'Add E2E to an existing workspace project / feature',
233
+ value: 'existing',
234
+ hint: 'Attach E2E test suite to a detected workspace package',
235
235
  },
236
236
  {
237
- label: "Create a new custom feature / module path",
238
- value: "custom",
239
- hint: "Specify a custom path e.g. packages/features/billing",
237
+ label: 'Create a new custom feature / module path',
238
+ value: 'custom',
239
+ hint: 'Specify a custom path e.g. packages/features/billing',
240
240
  },
241
241
  ],
242
242
  });
243
243
  if (p.isCancel(mode)) {
244
- p.cancel("Operation cancelled.");
244
+ p.cancel('Operation cancelled.');
245
245
  return;
246
246
  }
247
- if (mode === "existing" && detectedProjects.length > 0) {
247
+ if (mode === 'existing' && detectedProjects.length > 0) {
248
248
  const choice = await p.select({
249
- message: "Select workspace project to add TestSpectra E2E testing to:",
249
+ message: 'Select workspace project to add TestSpectra E2E testing to:',
250
250
  options: detectedProjects.map((proj) => ({
251
251
  value: proj.relPath,
252
252
  label: `${proj.relPath} (${proj.name})`,
253
- hint: proj.hasNxProject ? "Nx project.json" : "package.json",
253
+ hint: proj.hasNxProject ? 'Nx project.json' : 'package.json',
254
254
  })),
255
255
  });
256
256
  if (p.isCancel(choice)) {
257
- p.cancel("Operation cancelled.");
257
+ p.cancel('Operation cancelled.');
258
258
  return;
259
259
  }
260
260
  targetProjectPath = choice;
@@ -262,22 +262,22 @@ export async function addCommand(moduleNameArg, options = {}) {
262
262
  }
263
263
  else {
264
264
  const customPath = await p.text({
265
- message: "Enter path for the new feature module (e.g. packages/features/payment):",
266
- placeholder: "packages/features/new-feature",
265
+ message: 'Enter path for the new feature module (e.g. packages/features/payment):',
266
+ placeholder: 'packages/features/new-feature',
267
267
  validate: (val) => {
268
268
  if (!val || !val.trim())
269
- return "Path cannot be empty";
269
+ return 'Path cannot be empty';
270
270
  },
271
271
  });
272
272
  if (p.isCancel(customPath)) {
273
- p.cancel("Operation cancelled.");
273
+ p.cancel('Operation cancelled.');
274
274
  return;
275
275
  }
276
276
  targetProjectPath = customPath.trim();
277
277
  featureName = path.basename(targetProjectPath);
278
278
  }
279
279
  }
280
- const e2eFolderName = options.e2eFolder || "e2e";
280
+ const e2eFolderName = options.e2eFolder || 'e2e';
281
281
  const targetE2eDir = path.join(rootDir, targetProjectPath, e2eFolderName);
282
282
  const e2eProjectName = `${featureName}-${e2eFolderName}`;
283
283
  if (fs.existsSync(targetE2eDir) && !options.force) {
@@ -287,38 +287,39 @@ export async function addCommand(moduleNameArg, options = {}) {
287
287
  const s = p.spinner();
288
288
  s.start(`Scaffolding ${e2eProjectName} in ./${path.relative(rootDir, targetE2eDir)}...`);
289
289
  copyRecursive(sampleE2eSrc, targetE2eDir, {
290
- "playground-e2e": e2eProjectName,
291
- "packages/playground/e2e": path.relative(rootDir, targetE2eDir).replace(/\\/g, "/"),
292
- "scope:playground": `scope:${featureName}`,
290
+ 'playground-e2e': e2eProjectName,
291
+ 'packages/playground/e2e': path.relative(rootDir, targetE2eDir).replace(/\\/g, '/'),
292
+ 'scope:playground': `scope:${featureName}`,
293
293
  });
294
294
  // Adjust project.json
295
- const projJsonPath = path.join(targetE2eDir, "project.json");
295
+ const projJsonPath = path.join(targetE2eDir, 'project.json');
296
296
  if (fs.existsSync(projJsonPath)) {
297
297
  try {
298
- const pObj = JSON.parse(fs.readFileSync(projJsonPath, "utf-8"));
298
+ const pObj = JSON.parse(fs.readFileSync(projJsonPath, 'utf-8'));
299
299
  pObj.name = e2eProjectName;
300
- pObj.sourceRoot = path.relative(rootDir, targetE2eDir).replace(/\\/g, "/");
301
- pObj.targets.e2e.options.cwd = path.relative(rootDir, targetE2eDir).replace(/\\/g, "/");
302
- pObj.targets["type-check"].options.cwd = ".";
303
- pObj.tags = [`scope:${featureName}`, "type:e2e"];
304
- fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), "utf-8");
300
+ pObj.sourceRoot = path.relative(rootDir, targetE2eDir).replace(/\\/g, '/');
301
+ pObj.targets.e2e.options.cwd = path.relative(rootDir, targetE2eDir).replace(/\\/g, '/');
302
+ pObj.targets['type-check'].options.cwd = '.';
303
+ pObj.tags = [`scope:${featureName}`, 'type:e2e'];
304
+ fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), 'utf-8');
305
305
  }
306
306
  catch { }
307
307
  }
308
308
  // Ensure no local spectra.config.ts (centralized in root)
309
- const localConfig = path.join(targetE2eDir, "spectra.config.ts");
309
+ const localConfig = path.join(targetE2eDir, 'spectra.config.ts');
310
310
  if (fs.existsSync(localConfig))
311
311
  fs.unlinkSync(localConfig);
312
- // Ensure Root Solution tsconfig.json references tsconfig.spectra.json
313
- const rootTsConfigPath = path.join(rootDir, "tsconfig.json");
312
+ // Ensure Root Solution tsconfig.json references .testspectra/tsconfig.json
313
+ const rootTsConfigPath = path.join(rootDir, 'tsconfig.json');
314
314
  if (fs.existsSync(rootTsConfigPath)) {
315
315
  try {
316
- const rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath, "utf-8"));
316
+ const rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath, 'utf-8'));
317
317
  if (!rootTsConfig.references)
318
318
  rootTsConfig.references = [];
319
- if (!rootTsConfig.references.some((r) => r.path === "./tsconfig.spectra.json")) {
320
- rootTsConfig.references.push({ path: "./tsconfig.spectra.json" });
321
- fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2), "utf-8");
319
+ rootTsConfig.references = rootTsConfig.references.filter((r) => r.path !== './tsconfig.spectra.json');
320
+ if (!rootTsConfig.references.some((r) => r.path === './.testspectra/tsconfig.json')) {
321
+ rootTsConfig.references.push({ path: './.testspectra/tsconfig.json' });
322
+ fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2), 'utf-8');
322
323
  }
323
324
  }
324
325
  catch { }
@@ -329,38 +330,38 @@ export async function addCommand(moduleNameArg, options = {}) {
329
330
  p.log.success(chalk.green(`Directory: ./${path.relative(rootDir, targetE2eDir)}`));
330
331
  p.log.success(chalk.green(`Nx Target: ${e2eProjectName}:e2e`));
331
332
  p.log.message(chalk.cyan(`Run tests: pnpm nx run ${e2eProjectName}:e2e`));
332
- p.outro(chalk.bold.green("🎉 New TestSpectra module ready!"));
333
+ p.outro(chalk.bold.green('🎉 New TestSpectra module ready!'));
333
334
  return;
334
335
  }
335
336
  // Standalone mode: adding a new spec suite in specs/<ModuleName>
336
337
  let targetModule = moduleNameArg;
337
338
  if (!targetModule) {
338
339
  const input = await p.text({
339
- message: "Enter name of the new test suite/module (e.g. Payment, Profile):",
340
- placeholder: "Checkout",
340
+ message: 'Enter name of the new test suite/module (e.g. Payment, Profile):',
341
+ placeholder: 'Checkout',
341
342
  validate: (val) => {
342
343
  if (!val || !val.trim())
343
- return "Module name cannot be empty";
344
+ return 'Module name cannot be empty';
344
345
  },
345
346
  });
346
347
  if (p.isCancel(input)) {
347
- p.cancel("Operation cancelled.");
348
+ p.cancel('Operation cancelled.');
348
349
  return;
349
350
  }
350
351
  targetModule = input.trim();
351
352
  }
352
- const specDir = path.join(cwd, "specs", targetModule, "TC-01");
353
- const poDir = path.join(cwd, "page-objects", `${targetModule}Page`);
353
+ const specDir = path.join(cwd, 'specs', targetModule, 'TC-01');
354
+ const poDir = path.join(cwd, 'page-objects', `${targetModule}Page`);
354
355
  if (!fs.existsSync(specDir))
355
356
  fs.mkdirSync(specDir, { recursive: true });
356
357
  if (!fs.existsSync(poDir))
357
358
  fs.mkdirSync(poDir, { recursive: true });
358
359
  // Create starter page object
359
360
  const poContent = `class ${targetModule}Page {\n get mainTitle() {\n return $("h1");\n }\n\n async open() {\n await Spectra.navigate("/${targetModule.toLowerCase()}");\n }\n}\n\nexport default new ${targetModule}Page();\n`;
360
- fs.writeFileSync(path.join(poDir, "common.ts"), poContent, "utf-8");
361
+ fs.writeFileSync(path.join(poDir, 'common.ts'), poContent, 'utf-8');
361
362
  // Create starter test spec (Zero-import, flat it() block)
362
363
  const specContent = `it("should verify ${targetModule} page components", async () => {\n await ${targetModule}Page.open();\n await ${targetModule}Page.mainTitle.shouldBeVisible();\n});\n`;
363
- fs.writeFileSync(path.join(specDir, "web.test.ts"), specContent, "utf-8");
364
+ fs.writeFileSync(path.join(specDir, 'web.test.ts'), specContent, 'utf-8');
364
365
  TypeGenerator.writeDeclarationFiles(cwd);
365
366
  p.log.success(chalk.green(`Created Page Object: ./page-objects/${targetModule}Page/common.ts`));
366
367
  p.log.success(chalk.green(`Created Test Spec: ./specs/${targetModule}/TC-01/web.test.ts`));
@@ -1,24 +1,34 @@
1
- import chalk from "chalk";
2
- import * as p from "@clack/prompts";
3
- import { DemoServer } from "../utils/demo-server.js";
1
+ import chalk from 'chalk';
2
+ import * as p from '@clack/prompts';
3
+ import { DemoServer } from '../utils/demo-server.js';
4
+ import { DemoApiServer } from '../utils/api-server.js';
4
5
  export async function demoCommand(options) {
5
6
  const port = options.port ? parseInt(options.port, 10) : 5173;
6
- p.intro(chalk.bold.cyan("🌐 TestSpectra Demo Web App"));
7
+ p.intro(chalk.bold.cyan('🌐 TestSpectra Demo Web App'));
7
8
  const server = new DemoServer(port);
9
+ const apiServer = new DemoApiServer();
8
10
  try {
9
11
  const { url } = await server.start();
10
12
  p.log.success(chalk.green(`Demo web app is live at: ${chalk.bold.underline(url)}`));
13
+ // The web app fetches its API sections from the separate self-hosted API server, so start it
14
+ // too — otherwise the API demo sections show only their offline fallback data.
15
+ try {
16
+ const { url: apiUrl } = await apiServer.start();
17
+ p.log.success(chalk.green(`Demo API server is live at: ${chalk.bold.underline(apiUrl)}`));
18
+ }
19
+ catch (err) {
20
+ p.log.warn(chalk.yellow(`Could not start demo API server: ${err.message}`));
21
+ }
11
22
  p.log.info(chalk.dim("You can now run tests against this server in another terminal (e.g. 'spectra run -t web')."));
12
- p.log.info(chalk.dim("Press Ctrl+C to stop the server."));
23
+ p.log.info(chalk.dim('Press Ctrl+C to stop the servers.'));
13
24
  // Handle termination gracefully
14
- process.on("SIGINT", async () => {
25
+ const shutdown = async () => {
15
26
  await server.stop();
27
+ await apiServer.stop();
16
28
  process.exit(0);
17
- });
18
- process.on("SIGTERM", async () => {
19
- await server.stop();
20
- process.exit(0);
21
- });
29
+ };
30
+ process.on('SIGINT', shutdown);
31
+ process.on('SIGTERM', shutdown);
22
32
  await new Promise(() => { });
23
33
  }
24
34
  catch (err) {
@@ -1,3 +1,15 @@
1
- export declare function devicesCommand(options: {
2
- target?: "android" | "ios" | "web" | "all";
1
+ export interface AdbDevice {
2
+ udid: string;
3
+ id: string;
4
+ model: string;
5
+ name: string;
6
+ status: string;
7
+ connectionType: 'usb' | 'wifi' | string;
8
+ version?: string | null;
9
+ androidId?: string | null;
10
+ }
11
+ export declare function getConnectedDevices(cwd?: string): Promise<AdbDevice[]>;
12
+ export declare function devicesCommand(options?: {
13
+ target?: 'android' | 'ios' | 'web' | 'all';
14
+ json?: boolean;
3
15
  }): Promise<void>;
@@ -1,37 +1,47 @@
1
- import { execSync } from "child_process";
2
- export async function devicesCommand(options) {
3
- const target = options.target || "all";
4
- console.log(`\x1b[36m[TestSpectra Devices]\x1b[0m Listing connected test targets (scope: ${target})...\n`);
5
- if (target === "all" || target === "android") {
6
- console.log("\x1b[1mAndroid Devices (ADB):\x1b[0m");
7
- try {
8
- const adbOutput = execSync("adb devices -l", { stdio: "pipe" }).toString();
9
- const lines = adbOutput.split("\n").slice(1);
10
- let found = false;
11
- for (const line of lines) {
12
- if (line.trim()) {
13
- found = true;
14
- console.log(` \x1b[32m•\x1b[0m ${line.trim()}`);
15
- }
1
+ import { execFileSync, spawn } from 'child_process';
2
+ import path from 'path';
3
+ import { RustCoreBridge } from '../runner/bridge.js';
4
+ function getRunnerEnv() {
5
+ const home = process.env.HOME || process.env.USERPROFILE || '';
6
+ const globalDriversDir = path.join(home, '.testspectra', 'drivers');
7
+ return {
8
+ ...process.env,
9
+ TEST_SPECTRA_DRIVER_CACHE: globalDriversDir,
10
+ };
11
+ }
12
+ export async function getConnectedDevices(cwd = process.cwd()) {
13
+ const binPath = RustCoreBridge.resolveBinaryPath();
14
+ const stdout = execFileSync(binPath, ['devices', '--json'], {
15
+ cwd,
16
+ env: getRunnerEnv(),
17
+ encoding: 'utf8',
18
+ stdio: ['ignore', 'pipe', 'pipe'],
19
+ });
20
+ return JSON.parse(stdout.trim());
21
+ }
22
+ export async function devicesCommand(options = {}) {
23
+ const cwd = process.cwd();
24
+ const binPath = RustCoreBridge.resolveBinaryPath();
25
+ if (options.json) {
26
+ const devices = await getConnectedDevices(cwd);
27
+ console.log(JSON.stringify(devices, null, 2));
28
+ return;
29
+ }
30
+ return new Promise((resolve, reject) => {
31
+ const child = spawn(binPath, ['devices'], {
32
+ cwd,
33
+ env: getRunnerEnv(),
34
+ stdio: 'inherit',
35
+ });
36
+ child.on('close', (code) => {
37
+ if (code === 0) {
38
+ resolve();
16
39
  }
17
- if (!found) {
18
- console.log(" \x1b[90mNo Android devices/emulators connected via ADB.\x1b[0m");
40
+ else {
41
+ process.exitCode = code ?? 1;
42
+ resolve();
19
43
  }
20
- }
21
- catch {
22
- console.log(" \x1b[31mADB command failed or platform-tools not in PATH.\x1b[0m");
23
- }
24
- }
25
- if (target === "all" || target === "web") {
26
- console.log("\n\x1b[1mWeb Browsers:\x1b[0m");
27
- try {
28
- const chromeVer = execSync(process.platform === "darwin"
29
- ? '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --version'
30
- : "google-chrome --version", { stdio: "pipe" }).toString().trim();
31
- console.log(` \x1b[32m•\x1b[0m Chrome: ${chromeVer}`);
32
- }
33
- catch {
34
- console.log(" \x1b[90m• Chrome: Not installed\x1b[0m");
35
- }
36
- }
44
+ });
45
+ child.on('error', reject);
46
+ });
37
47
  }
@@ -1,11 +1,12 @@
1
1
  export interface DependencyStatus {
2
2
  name: string;
3
- category: "core" | "web" | "mobile";
3
+ category: 'core' | 'web' | 'mobile' | string;
4
4
  installed: boolean;
5
5
  version: string | null;
6
6
  required: boolean;
7
7
  required_version: string | null;
8
8
  depends_on: string[];
9
+ location?: string | null;
9
10
  }
10
11
  export interface SystemCheckResult {
11
12
  all_ready: boolean;
@@ -16,4 +17,5 @@ export declare function runSystemChecks(cwd?: string): Promise<SystemCheckResult
16
17
  export declare function doctorCommand(options?: {
17
18
  fix?: boolean;
18
19
  json?: boolean;
20
+ verbose?: boolean;
19
21
  }): Promise<void>;