@testspectra/cli 1.0.58 → 1.0.62

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 (310) hide show
  1. package/bin/testspectra-runner +0 -0
  2. package/dist/commands/add.js +3 -9
  3. package/dist/commands/init.js +15 -15
  4. package/dist/commands/run.d.ts +1 -0
  5. package/dist/commands/run.js +22 -13
  6. package/dist/index.js +1 -0
  7. package/dist/server/demo.d.ts +8 -0
  8. package/dist/server/demo.js +104 -0
  9. package/dist/types/generator.d.ts +1 -1
  10. package/dist/types/generator.js +50 -15
  11. package/package.json +2 -2
  12. package/templates/default/.cursorrules +18 -0
  13. package/templates/default/CLAUDE.md +10 -0
  14. package/templates/default/fixtures/playgroundData.json +7 -0
  15. package/templates/default/global-hooks/after/mobile.hook.ts +3 -0
  16. package/templates/default/global-hooks/after/web.hook.ts +3 -0
  17. package/templates/default/global-hooks/before/mobile.hook.ts +3 -0
  18. package/templates/default/global-hooks/before/web.hook.ts +3 -0
  19. package/templates/default/package.json +5 -1
  20. package/templates/default/page-objects/MatchersPage/mobile.ts +21 -0
  21. package/templates/default/page-objects/MatchersPage/web.ts +23 -0
  22. package/templates/default/page-objects/NavigationPage/mobile.ts +5 -0
  23. package/templates/default/page-objects/NavigationPage/web.ts +8 -0
  24. package/templates/default/page-objects/PlaygroundPage/mobile.ts +26 -0
  25. package/templates/default/page-objects/PlaygroundPage/web.ts +29 -0
  26. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -0
  27. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +12 -0
  28. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +5 -0
  29. package/templates/default/specs/BrowserContext/hooks/after/mobile.hook.ts +1 -0
  30. package/templates/default/specs/BrowserContext/hooks/after/web.hook.ts +1 -0
  31. package/templates/default/specs/BrowserContext/hooks/afterEach/mobile.hook.ts +1 -0
  32. package/templates/default/specs/BrowserContext/hooks/afterEach/web.hook.ts +1 -0
  33. package/templates/default/specs/BrowserContext/hooks/before/mobile.hook.ts +1 -0
  34. package/templates/default/specs/BrowserContext/hooks/before/web.hook.ts +1 -0
  35. package/templates/default/specs/BrowserContext/hooks/beforeEach/mobile.hook.ts +1 -0
  36. package/templates/default/specs/BrowserContext/hooks/beforeEach/web.hook.ts +1 -0
  37. package/templates/default/specs/BrowserContext/suite.md +9 -0
  38. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -0
  39. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +12 -0
  40. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +7 -0
  41. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -0
  42. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +12 -0
  43. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +6 -0
  44. package/templates/default/specs/ContentMatchers/hooks/after/mobile.hook.ts +1 -0
  45. package/templates/default/specs/ContentMatchers/hooks/after/web.hook.ts +1 -0
  46. package/templates/default/specs/ContentMatchers/hooks/afterEach/mobile.hook.ts +1 -0
  47. package/templates/default/specs/ContentMatchers/hooks/afterEach/web.hook.ts +1 -0
  48. package/templates/default/specs/ContentMatchers/hooks/before/mobile.hook.ts +1 -0
  49. package/templates/default/specs/ContentMatchers/hooks/before/web.hook.ts +1 -0
  50. package/templates/default/specs/ContentMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
  51. package/templates/default/specs/ContentMatchers/hooks/beforeEach/web.hook.ts +1 -0
  52. package/templates/default/specs/ContentMatchers/suite.md +9 -0
  53. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -0
  54. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +12 -0
  55. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +5 -0
  56. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -0
  57. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +12 -0
  58. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +8 -0
  59. package/templates/default/specs/ElementMatchers/hooks/after/mobile.hook.ts +1 -0
  60. package/templates/default/specs/ElementMatchers/hooks/after/web.hook.ts +1 -0
  61. package/templates/default/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +1 -0
  62. package/templates/default/specs/ElementMatchers/hooks/afterEach/web.hook.ts +1 -0
  63. package/templates/default/specs/ElementMatchers/hooks/before/mobile.hook.ts +1 -0
  64. package/templates/default/specs/ElementMatchers/hooks/before/web.hook.ts +1 -0
  65. package/templates/default/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
  66. package/templates/default/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +1 -0
  67. package/templates/default/specs/ElementMatchers/suite.md +9 -0
  68. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -0
  69. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +12 -0
  70. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -0
  71. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -0
  72. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +12 -0
  73. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -0
  74. package/templates/default/specs/Playground/hooks/after/mobile.hook.ts +1 -0
  75. package/templates/default/specs/Playground/hooks/after/web.hook.ts +1 -0
  76. package/templates/default/specs/Playground/hooks/afterEach/mobile.hook.ts +1 -0
  77. package/templates/default/specs/Playground/hooks/afterEach/web.hook.ts +1 -0
  78. package/templates/default/specs/Playground/hooks/before/mobile.hook.ts +1 -0
  79. package/templates/default/specs/Playground/hooks/before/web.hook.ts +1 -0
  80. package/templates/default/specs/Playground/hooks/beforeEach/mobile.hook.ts +1 -0
  81. package/templates/default/specs/Playground/hooks/beforeEach/web.hook.ts +1 -0
  82. package/templates/default/specs/Playground/suite.md +9 -0
  83. package/templates/default/spectra.config.ts +1 -1
  84. package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -0
  85. package/templates/default/support/actions/fillCredentials/web.action.ts +4 -0
  86. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -0
  87. package/templates/default/support/steps/togglePlaygroundStates/step.md +7 -0
  88. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -0
  89. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -0
  90. package/templates/default/support/steps/verifyPlaygroundState/step.md +7 -0
  91. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -0
  92. package/templates/default/tsconfig.spectra.android.json +7 -4
  93. package/templates/default/tsconfig.spectra.ios.json +7 -4
  94. package/templates/default/tsconfig.spectra.web.json +10 -6
  95. package/templates/nx/.cursorrules +18 -0
  96. package/templates/nx/CLAUDE.md +10 -0
  97. package/templates/nx/fixtures/playgroundData.json +7 -0
  98. package/templates/nx/global-hooks/after/mobile.hook.ts +3 -0
  99. package/templates/nx/global-hooks/after/web.hook.ts +3 -0
  100. package/templates/nx/global-hooks/before/mobile.hook.ts +3 -0
  101. package/templates/nx/global-hooks/before/web.hook.ts +3 -0
  102. package/templates/nx/package.json +1 -1
  103. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -0
  104. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +12 -0
  105. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +5 -0
  106. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/after/mobile.hook.ts +1 -0
  107. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/after/web.hook.ts +1 -0
  108. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/afterEach/mobile.hook.ts +1 -0
  109. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/afterEach/web.hook.ts +1 -0
  110. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/before/mobile.hook.ts +1 -0
  111. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/before/web.hook.ts +1 -0
  112. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/beforeEach/mobile.hook.ts +1 -0
  113. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/hooks/beforeEach/web.hook.ts +1 -0
  114. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/suite.md +9 -0
  115. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -0
  116. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/spec.md +12 -0
  117. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +7 -0
  118. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -0
  119. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +12 -0
  120. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +6 -0
  121. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/after/mobile.hook.ts +1 -0
  122. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/after/web.hook.ts +1 -0
  123. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/afterEach/mobile.hook.ts +1 -0
  124. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/afterEach/web.hook.ts +1 -0
  125. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/before/mobile.hook.ts +1 -0
  126. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/before/web.hook.ts +1 -0
  127. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
  128. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/hooks/beforeEach/web.hook.ts +1 -0
  129. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/suite.md +9 -0
  130. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -0
  131. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +12 -0
  132. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +5 -0
  133. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -0
  134. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +12 -0
  135. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +8 -0
  136. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/after/mobile.hook.ts +1 -0
  137. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/after/web.hook.ts +1 -0
  138. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +1 -0
  139. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/afterEach/web.hook.ts +1 -0
  140. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/before/mobile.hook.ts +1 -0
  141. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/before/web.hook.ts +1 -0
  142. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
  143. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +1 -0
  144. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/suite.md +9 -0
  145. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -0
  146. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/spec.md +12 -0
  147. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -0
  148. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -0
  149. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/spec.md +12 -0
  150. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -0
  151. package/templates/nx/packages/playground/e2e/specs/Playground/hooks/after/mobile.hook.ts +1 -0
  152. package/templates/nx/packages/playground/e2e/specs/Playground/hooks/after/web.hook.ts +1 -0
  153. package/templates/nx/packages/playground/e2e/specs/Playground/hooks/afterEach/mobile.hook.ts +1 -0
  154. package/templates/nx/packages/playground/e2e/specs/Playground/hooks/afterEach/web.hook.ts +1 -0
  155. package/templates/nx/packages/playground/e2e/specs/Playground/hooks/before/mobile.hook.ts +1 -0
  156. package/templates/nx/packages/playground/e2e/specs/Playground/hooks/before/web.hook.ts +1 -0
  157. package/templates/nx/packages/playground/e2e/specs/Playground/hooks/beforeEach/mobile.hook.ts +1 -0
  158. package/templates/nx/packages/playground/e2e/specs/Playground/hooks/beforeEach/web.hook.ts +1 -0
  159. package/templates/nx/packages/playground/e2e/specs/Playground/suite.md +9 -0
  160. package/templates/nx/page-objects/MatchersPage/mobile.ts +21 -0
  161. package/templates/nx/page-objects/MatchersPage/web.ts +23 -0
  162. package/templates/nx/page-objects/NavigationPage/mobile.ts +5 -0
  163. package/templates/nx/page-objects/NavigationPage/web.ts +8 -0
  164. package/templates/nx/page-objects/PlaygroundPage/mobile.ts +26 -0
  165. package/templates/nx/page-objects/PlaygroundPage/web.ts +29 -0
  166. package/templates/nx/shared/testing/fixtures/playgroundData.json +7 -0
  167. package/templates/nx/shared/testing/global-hooks/after/mobile.hook.ts +3 -0
  168. package/templates/nx/shared/testing/global-hooks/after/web.hook.ts +3 -0
  169. package/templates/nx/shared/testing/global-hooks/before/mobile.hook.ts +3 -0
  170. package/templates/nx/shared/testing/global-hooks/before/web.hook.ts +3 -0
  171. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +21 -0
  172. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +23 -0
  173. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -0
  174. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -0
  175. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -0
  176. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +29 -0
  177. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -0
  178. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -0
  179. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -0
  180. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/step.md +7 -0
  181. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -0
  182. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -0
  183. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/step.md +7 -0
  184. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -0
  185. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -0
  186. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +12 -0
  187. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +5 -0
  188. package/templates/nx/specs/BrowserContext/hooks/after/mobile.hook.ts +1 -0
  189. package/templates/nx/specs/BrowserContext/hooks/after/web.hook.ts +1 -0
  190. package/templates/nx/specs/BrowserContext/hooks/afterEach/mobile.hook.ts +1 -0
  191. package/templates/nx/specs/BrowserContext/hooks/afterEach/web.hook.ts +1 -0
  192. package/templates/nx/specs/BrowserContext/hooks/before/mobile.hook.ts +1 -0
  193. package/templates/nx/specs/BrowserContext/hooks/before/web.hook.ts +1 -0
  194. package/templates/nx/specs/BrowserContext/hooks/beforeEach/mobile.hook.ts +1 -0
  195. package/templates/nx/specs/BrowserContext/hooks/beforeEach/web.hook.ts +1 -0
  196. package/templates/nx/specs/BrowserContext/suite.md +9 -0
  197. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -0
  198. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +12 -0
  199. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +7 -0
  200. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -0
  201. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +12 -0
  202. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +6 -0
  203. package/templates/nx/specs/ContentMatchers/hooks/after/mobile.hook.ts +1 -0
  204. package/templates/nx/specs/ContentMatchers/hooks/after/web.hook.ts +1 -0
  205. package/templates/nx/specs/ContentMatchers/hooks/afterEach/mobile.hook.ts +1 -0
  206. package/templates/nx/specs/ContentMatchers/hooks/afterEach/web.hook.ts +1 -0
  207. package/templates/nx/specs/ContentMatchers/hooks/before/mobile.hook.ts +1 -0
  208. package/templates/nx/specs/ContentMatchers/hooks/before/web.hook.ts +1 -0
  209. package/templates/nx/specs/ContentMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
  210. package/templates/nx/specs/ContentMatchers/hooks/beforeEach/web.hook.ts +1 -0
  211. package/templates/nx/specs/ContentMatchers/suite.md +9 -0
  212. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -0
  213. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +12 -0
  214. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +5 -0
  215. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -0
  216. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +12 -0
  217. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +8 -0
  218. package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +1 -0
  219. package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +1 -0
  220. package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +1 -0
  221. package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +1 -0
  222. package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +1 -0
  223. package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +1 -0
  224. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +1 -0
  225. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +1 -0
  226. package/templates/nx/specs/ElementMatchers/suite.md +9 -0
  227. package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -0
  228. package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +12 -0
  229. package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +6 -0
  230. package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -0
  231. package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +12 -0
  232. package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -0
  233. package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +1 -0
  234. package/templates/nx/specs/Playground/hooks/after/web.hook.ts +1 -0
  235. package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +1 -0
  236. package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +1 -0
  237. package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +1 -0
  238. package/templates/nx/specs/Playground/hooks/before/web.hook.ts +1 -0
  239. package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +1 -0
  240. package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +1 -0
  241. package/templates/nx/specs/Playground/suite.md +9 -0
  242. package/templates/nx/spectra.config.ts +1 -1
  243. package/templates/nx/support/actions/fillCredentials/mobile.action.ts +4 -0
  244. package/templates/nx/support/actions/fillCredentials/web.action.ts +4 -0
  245. package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +3 -0
  246. package/templates/nx/support/steps/togglePlaygroundStates/step.md +7 -0
  247. package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +5 -0
  248. package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +4 -0
  249. package/templates/nx/support/steps/verifyPlaygroundState/step.md +7 -0
  250. package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +6 -0
  251. package/templates/default/actions/applyDiscount/mobile.action.ts +0 -6
  252. package/templates/default/actions/applyDiscount/web.action.ts +0 -6
  253. package/templates/default/actions/verifyOtp/mobile.action.ts +0 -6
  254. package/templates/default/actions/verifyOtp/web.action.ts +0 -6
  255. package/templates/default/fixtures/couponCode.json +0 -1
  256. package/templates/default/fixtures/searchQuery.json +0 -1
  257. package/templates/default/fixtures/userData.json +0 -1
  258. package/templates/default/global-hooks/before.web.hook.ts +0 -4
  259. package/templates/default/page-objects/LoginPage/mobile.ts +0 -32
  260. package/templates/default/page-objects/LoginPage/web.ts +0 -32
  261. package/templates/default/page-objects/ProductPage/mobile.ts +0 -39
  262. package/templates/default/page-objects/ProductPage/web.ts +0 -38
  263. package/templates/default/page-objects/SettingsPage/common.ts +0 -26
  264. package/templates/default/specs/Auth/TC-AUTH-01/android.test.ts +0 -6
  265. package/templates/default/specs/Auth/TC-AUTH-01/ios.test.ts +0 -6
  266. package/templates/default/specs/Auth/TC-AUTH-01/web.test.ts +0 -13
  267. package/templates/default/specs/Auth/TC-AUTH-02/android.test.ts +0 -5
  268. package/templates/default/specs/Auth/TC-AUTH-02/web.test.ts +0 -5
  269. package/templates/default/specs/Auth/hooks/before.android.hook.ts +0 -3
  270. package/templates/default/specs/Auth/hooks/before.ios.hook.ts +0 -3
  271. package/templates/default/specs/Auth/hooks/before.web.hook.ts +0 -3
  272. package/templates/default/specs/Product/TC-PROD-01/mobile.test.ts +0 -8
  273. package/templates/default/specs/Product/TC-PROD-01/web.test.ts +0 -8
  274. package/templates/default/specs/Product/TC-PROD-02/mobile.test.ts +0 -6
  275. package/templates/default/specs/Product/TC-PROD-02/web.test.ts +0 -6
  276. package/templates/default/specs/Product/hooks/before.web.hook.ts +0 -3
  277. package/templates/default/specs/Settings/TC-SET-01/common.test.ts +0 -8
  278. package/templates/default/steps/loginUser/mobile.step.ts +0 -4
  279. package/templates/default/steps/loginUser/web.step.ts +0 -4
  280. package/templates/default/steps/searchProduct/mobile.step.ts +0 -5
  281. package/templates/default/steps/searchProduct/web.step.ts +0 -5
  282. package/templates/nx/modules/auth/e2e/actions/verifyOtp/mobile.action.ts +0 -6
  283. package/templates/nx/modules/auth/e2e/actions/verifyOtp/web.action.ts +0 -6
  284. package/templates/nx/modules/auth/e2e/fixtures/userData.json +0 -1
  285. package/templates/nx/modules/auth/e2e/global-hooks/before.web.hook.ts +0 -4
  286. package/templates/nx/modules/auth/e2e/package.json +0 -19
  287. package/templates/nx/modules/auth/e2e/page-objects/LoginPage/mobile.ts +0 -32
  288. package/templates/nx/modules/auth/e2e/page-objects/LoginPage/web.ts +0 -32
  289. package/templates/nx/modules/auth/e2e/page-objects/SettingsPage/common.ts +0 -26
  290. package/templates/nx/modules/auth/e2e/project.json +0 -34
  291. package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-01/android.test.ts +0 -6
  292. package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-01/ios.test.ts +0 -6
  293. package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts +0 -19
  294. package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-02/android.test.ts +0 -5
  295. package/templates/nx/modules/auth/e2e/specs/Auth/TC-AUTH-02/web.test.ts +0 -5
  296. package/templates/nx/modules/auth/e2e/specs/Auth/hooks/before.android.hook.ts +0 -3
  297. package/templates/nx/modules/auth/e2e/specs/Auth/hooks/before.ios.hook.ts +0 -3
  298. package/templates/nx/modules/auth/e2e/specs/Auth/hooks/before.web.hook.ts +0 -3
  299. package/templates/nx/modules/auth/e2e/specs/Settings/TC-SET-01/common.test.ts +0 -8
  300. package/templates/nx/modules/auth/e2e/steps/loginUser/mobile.step.ts +0 -4
  301. package/templates/nx/modules/auth/e2e/steps/loginUser/web.step.ts +0 -4
  302. package/templates/nx/modules/auth/package.json +0 -7
  303. package/templates/nx/modules/auth/src/index.ts +0 -3
  304. package/templates/nx/modules/catalog/package.json +0 -7
  305. package/templates/nx/modules/catalog/src/index.ts +0 -6
  306. package/templates/nx/shared/testing/actions/dismissBanner/common.action.ts +0 -6
  307. package/templates/nx/shared/testing/fixtures/appConfig.json +0 -4
  308. package/templates/nx/shared/testing/package.json +0 -8
  309. package/templates/nx/shared/testing/page-objects/NavigationBar/common.ts +0 -13
  310. package/templates/nx/shared/testing/steps/loginAsAdmin/common.step.ts +0 -4
Binary file
@@ -151,18 +151,12 @@ export async function addCommand(moduleNameArg, options = {}) {
151
151
  cur = rootDir;
152
152
  while (cur !== path.dirname(cur)) {
153
153
  if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml"))) {
154
- try {
155
- const wsContent = fs.readFileSync(path.join(cur, "pnpm-workspace.yaml"), "utf-8");
156
- if (wsContent.includes("cli")) {
157
- isInternalWorkspace = true;
158
- break;
159
- }
160
- }
161
- catch { }
154
+ isInternalWorkspace = true;
155
+ break;
162
156
  }
163
157
  cur = path.dirname(cur);
164
158
  }
165
- const cliDepVersion = isInternalWorkspace ? "workspace:*" : cliVersion;
159
+ const cliDepVersion = isInternalWorkspace ? "workspace:*" : (cliVersion.startsWith("^") ? cliVersion : `^${cliVersion}`);
166
160
  function copyRecursive(src, dest, replacements = {}) {
167
161
  const base = path.basename(src);
168
162
  if (base === "node_modules" || base === "dist" || base === ".testspectra" || base === ".git") {
@@ -256,19 +256,13 @@ export async function initCommand(options = {}) {
256
256
  let cur = cwd;
257
257
  while (cur !== path.dirname(cur)) {
258
258
  if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml"))) {
259
- try {
260
- const wsContent = fs.readFileSync(path.join(cur, "pnpm-workspace.yaml"), "utf-8");
261
- if (wsContent.includes("cli")) {
262
- isInternalWorkspace = true;
263
- break;
264
- }
265
- }
266
- catch { }
259
+ isInternalWorkspace = true;
260
+ break;
267
261
  }
268
262
  cur = path.dirname(cur);
269
263
  }
270
264
  const projectName = path.basename(cwd);
271
- const cliDepVersion = isInternalWorkspace ? "workspace:*" : cliVersion;
265
+ const cliDepVersion = isInternalWorkspace ? "workspace:*" : (cliVersion.startsWith("^") ? cliVersion : `^${cliVersion}`);
272
266
  // Helper: Copy directory contents recursively
273
267
  function copyRecursive(src, dest, replacements = {}) {
274
268
  const base = path.basename(src);
@@ -442,6 +436,14 @@ export async function initCommand(options = {}) {
442
436
  rootPkg.devDependencies["@testspectra/matchers"] = cliDepVersion;
443
437
  if (!rootPkg.devDependencies["typescript"])
444
438
  rootPkg.devDependencies["typescript"] = "^5.4.5";
439
+ if (!rootPkg.devDependencies["@wdio/cli"])
440
+ rootPkg.devDependencies["@wdio/cli"] = "^9.2.8";
441
+ if (!rootPkg.devDependencies["@wdio/local-runner"])
442
+ rootPkg.devDependencies["@wdio/local-runner"] = "^9.2.8";
443
+ if (!rootPkg.devDependencies["@wdio/spec-reporter"])
444
+ rootPkg.devDependencies["@wdio/spec-reporter"] = "^9.2.8";
445
+ if (!rootPkg.devDependencies["@wdio/devtools-service"])
446
+ rootPkg.devDependencies["@wdio/devtools-service"] = "^9.2.8";
445
447
  if (!rootPkg.devDependencies["@wdio/globals"])
446
448
  rootPkg.devDependencies["@wdio/globals"] = "^9.2.8";
447
449
  if (!rootPkg.devDependencies["@wdio/mocha-framework"])
@@ -704,12 +706,10 @@ pnpm type-check
704
706
  const s = p.spinner();
705
707
  s.start("Scaffolding standalone TestSpectra project...");
706
708
  copyRecursive(templateDir, cwd);
707
- if (!isInternalWorkspace) {
708
- const pnpmWorkspacePath = path.join(cwd, "pnpm-workspace.yaml");
709
- if (!fs.existsSync(pnpmWorkspacePath) || options.force) {
710
- const pnpmWsContent = `allowBuilds:\n chromedriver: true\n edgedriver: true\n geckodriver: true\n`;
711
- fs.writeFileSync(pnpmWorkspacePath, pnpmWsContent, "utf-8");
712
- }
709
+ const standaloneWsPath = path.join(cwd, "pnpm-workspace.yaml");
710
+ if (!fs.existsSync(standaloneWsPath) || options.force) {
711
+ const pnpmWsContent = `allowBuilds:\n chromedriver: true\n edgedriver: true\n geckodriver: true\n`;
712
+ fs.writeFileSync(standaloneWsPath, pnpmWsContent, "utf-8");
713
713
  }
714
714
  TypeGenerator.writeDeclarationFiles(cwd);
715
715
  ensureVsCodeExtensions(cwd);
@@ -1,4 +1,5 @@
1
1
  export interface RunCommandOptions {
2
+ spec?: string;
2
3
  target?: "web" | "android" | "ios" | "common";
3
4
  device?: string;
4
5
  headless?: boolean;
@@ -6,6 +6,7 @@ import { Reporter } from "../runner/reporter.js";
6
6
  import { TypeGenerator } from "../types/generator.js";
7
7
  export async function runCommand(specPath, options = {}) {
8
8
  const cwd = process.cwd();
9
+ const effectiveSpecPath = specPath || options.spec;
9
10
  // Ensure latest ambient types are up-to-date
10
11
  TypeGenerator.writeDeclarationFiles(cwd);
11
12
  const config = await ConfigLoader.loadConfig(cwd);
@@ -13,11 +14,11 @@ export async function runCommand(specPath, options = {}) {
13
14
  config.webConfig.headlessMode = options.headless;
14
15
  }
15
16
  const platform = options.target || "web";
16
- // Find workspace root for centralized .testspectra
17
+ // Find workspace root for centralized .testspectra (monorepo root or current test project)
17
18
  let workspaceRoot = cwd;
18
19
  let cur = cwd;
19
20
  while (cur !== path.dirname(cur)) {
20
- if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cur, "nx.json")) || fs.existsSync(path.join(cur, "spectra.config.ts")) || fs.existsSync(path.join(cur, "testspectra.config.ts"))) {
21
+ if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cur, "nx.json"))) {
21
22
  workspaceRoot = cur;
22
23
  break;
23
24
  }
@@ -30,18 +31,25 @@ export async function runCommand(specPath, options = {}) {
30
31
  // Determine suite/test case name
31
32
  let suiteName = "default";
32
33
  let testCases = [];
33
- if (specPath) {
34
- const parsed = path.parse(specPath);
35
- const baseName = parsed.name.replace(/\.(web|android|ios|common|test|spec)/g, "");
36
- suiteName = baseName;
34
+ if (effectiveSpecPath) {
35
+ const relSpec = path.isAbsolute(effectiveSpecPath) ? path.relative(cwd, effectiveSpecPath) : effectiveSpecPath;
36
+ const normalized = relSpec.replace(/\\/g, "/");
37
+ const parts = normalized.split("/");
38
+ if (parts.length >= 2 && (parts[0] === "specs" || parts[0] === "suites")) {
39
+ suiteName = parts[1];
40
+ }
41
+ else {
42
+ const parsed = path.parse(normalized);
43
+ suiteName = parsed.name.replace(/\.(web|android|ios|common|test|spec)/g, "") || "default";
44
+ }
37
45
  testCases.push({
38
- id: baseName,
39
- title: baseName,
46
+ id: normalized,
47
+ title: normalized,
40
48
  executionOrder: 1,
41
49
  });
42
50
  }
43
51
  else {
44
- // 1. Scan specs/ directory (Option A: specs/<Suite>/<CaseId>/web.test.ts)
52
+ // 1. Scan specs/ directory (specs/<Suite>/<CaseId>/web.test.ts)
45
53
  const specsDir = path.join(cwd, "specs");
46
54
  if (fs.existsSync(specsDir)) {
47
55
  const suiteEntries = fs.readdirSync(specsDir, { withFileTypes: true });
@@ -55,11 +63,11 @@ export async function runCommand(specPath, options = {}) {
55
63
  if (cEntry.name.startsWith(".") || cEntry.name === "hooks")
56
64
  continue;
57
65
  if (cEntry.isDirectory()) {
58
- testCases.push({ id: cEntry.name, title: `${currentSuite} / ${cEntry.name}` });
66
+ testCases.push({ id: path.join("specs", currentSuite, cEntry.name).replace(/\\/g, "/"), title: `${currentSuite} / ${cEntry.name}` });
59
67
  }
60
68
  else if (cEntry.isFile() && (cEntry.name.endsWith(".test.ts") || cEntry.name.endsWith(".spec.ts"))) {
61
- const id = cEntry.name.replace(/\.(web|android|ios|common|all|mobile|test|spec)\.ts$/g, "");
62
- testCases.push({ id, title: `${currentSuite} / ${id}` });
69
+ const id = path.join("specs", currentSuite, cEntry.name).replace(/\\/g, "/");
70
+ testCases.push({ id, title: `${currentSuite} / ${cEntry.name}` });
63
71
  }
64
72
  }
65
73
  }
@@ -77,7 +85,7 @@ export async function runCommand(specPath, options = {}) {
77
85
  for (const cEntry of caseEntries) {
78
86
  if (cEntry.name.startsWith(".") || cEntry.name === "hooks" || !cEntry.isDirectory())
79
87
  continue;
80
- testCases.push({ id: cEntry.name, title: `${currentSuite} / ${cEntry.name}` });
88
+ testCases.push({ id: path.join("suites", currentSuite, cEntry.name).replace(/\\/g, "/"), title: `${currentSuite} / ${cEntry.name}` });
81
89
  }
82
90
  }
83
91
  }
@@ -93,6 +101,7 @@ export async function runCommand(specPath, options = {}) {
93
101
  ? path.resolve(options.output)
94
102
  : path.join(appDataPath, "reports/result.json");
95
103
  console.log(`\x1b[36m[TestSpectra]\x1b[0m Starting execution on target \x1b[1m${platform}\x1b[0m...`);
104
+ console.log(`\x1b[36m[TestSpectra]\x1b[0m Base URL: \x1b[33m${config.webConfig.baseUrl}\x1b[0m`);
96
105
  console.log(`\x1b[36m[TestSpectra]\x1b[0m Suite: ${suiteName} (${testCases.length} case(s))\n`);
97
106
  const reporter = new Reporter();
98
107
  try {
package/dist/index.js CHANGED
@@ -45,6 +45,7 @@ export function createCliProgram() {
45
45
  program
46
46
  .command("run [path]")
47
47
  .description("Execute test suite or individual test case")
48
+ .option("-s, --spec <path>", "Spec file path to execute")
48
49
  .option("-t, --target <target>", "Target platform: web, android, ios, common", "web")
49
50
  .option("-d, --device <device>", "Target device name or UDID")
50
51
  .option("--headless", "Run in headless mode")
@@ -0,0 +1,8 @@
1
+ export declare class DemoAppServer {
2
+ private server;
3
+ port: number;
4
+ url: string;
5
+ static findDemoAppDir(cwd: string): string | null;
6
+ start(cwd: string): Promise<string>;
7
+ stop(): void;
8
+ }
@@ -0,0 +1,104 @@
1
+ import http from "http";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ import { execSync } from "child_process";
6
+ import chalk from "chalk";
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ export class DemoAppServer {
10
+ server = null;
11
+ port = 0;
12
+ url = "";
13
+ static findDemoAppDir(cwd) {
14
+ // 1. Check upward from cwd
15
+ let cur = cwd;
16
+ while (cur !== path.dirname(cur)) {
17
+ const cand = path.join(cur, "apps", "demo-web-app");
18
+ if (fs.existsSync(cand) && fs.existsSync(path.join(cand, "package.json"))) {
19
+ return cand;
20
+ }
21
+ cur = path.dirname(cur);
22
+ }
23
+ // 2. Check relative to CLI package location
24
+ const relCand = path.resolve(__dirname, "../../../apps/demo-web-app");
25
+ if (fs.existsSync(relCand) && fs.existsSync(path.join(relCand, "package.json"))) {
26
+ return relCand;
27
+ }
28
+ return null;
29
+ }
30
+ async start(cwd) {
31
+ const demoDir = DemoAppServer.findDemoAppDir(cwd);
32
+ if (!demoDir) {
33
+ throw new Error("Could not locate 'apps/demo-web-app'. Ensure you are running within the TestSpectra workspace.");
34
+ }
35
+ const distDir = path.join(demoDir, "dist");
36
+ if (!fs.existsSync(distDir) || !fs.existsSync(path.join(distDir, "index.html"))) {
37
+ console.log(chalk.cyan("[TestSpectra] ") +
38
+ chalk.dim("Building demo web app bundle (apps/demo-web-app)..."));
39
+ execSync("pnpm --filter @testspectra/demo-web-app build", {
40
+ cwd: path.resolve(demoDir, "../.."),
41
+ stdio: "inherit",
42
+ });
43
+ }
44
+ const mimeTypes = {
45
+ ".html": "text/html; charset=utf-8",
46
+ ".js": "text/javascript; charset=utf-8",
47
+ ".css": "text/css; charset=utf-8",
48
+ ".json": "application/json",
49
+ ".svg": "image/svg+xml",
50
+ ".png": "image/png",
51
+ ".jpg": "image/jpeg",
52
+ ".jpeg": "image/jpeg",
53
+ ".ico": "image/x-icon",
54
+ ".woff": "font/woff",
55
+ ".woff2": "font/woff2",
56
+ ".ttf": "font/ttf",
57
+ };
58
+ return new Promise((resolve, reject) => {
59
+ this.server = http.createServer((req, res) => {
60
+ const rawUrl = req.url || "/";
61
+ const cleanPath = rawUrl.split("?")[0].split("#")[0];
62
+ let filePath = path.join(distDir, cleanPath === "/" ? "index.html" : cleanPath);
63
+ // Fallback to index.html for SPA client-side routing
64
+ if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
65
+ filePath = path.join(distDir, "index.html");
66
+ }
67
+ const ext = path.extname(filePath).toLowerCase();
68
+ const contentType = mimeTypes[ext] || "application/octet-stream";
69
+ fs.readFile(filePath, (err, content) => {
70
+ if (err) {
71
+ res.writeHead(500, { "Content-Type": "text/plain" });
72
+ res.end("Internal Server Error");
73
+ }
74
+ else {
75
+ res.writeHead(200, {
76
+ "Content-Type": contentType,
77
+ "Cache-Control": "no-cache",
78
+ "Access-Control-Allow-Origin": "*",
79
+ });
80
+ res.end(content);
81
+ }
82
+ });
83
+ });
84
+ this.server.listen(0, "127.0.0.1", () => {
85
+ const addr = this.server?.address();
86
+ if (addr && typeof addr === "object") {
87
+ this.port = addr.port;
88
+ this.url = `http://127.0.0.1:${this.port}`;
89
+ resolve(this.url);
90
+ }
91
+ else {
92
+ reject(new Error("Failed to bind demo preview server address"));
93
+ }
94
+ });
95
+ this.server.on("error", reject);
96
+ });
97
+ }
98
+ stop() {
99
+ if (this.server) {
100
+ this.server.close();
101
+ this.server = null;
102
+ }
103
+ }
104
+ }
@@ -12,6 +12,6 @@ export declare class TypeGenerator {
12
12
  static getPageObjectsDirsForScope(scopeDir: string, sharedDir?: string): string[];
13
13
  static getEntityDirsForScope(entityType: "actions" | "steps" | "fixtures", scopeDir: string, sharedDir?: string): string[];
14
14
  static generateDeclarationsForScope(cwd: string, outputTypesDir: string, scopeDir: string, sharedDir?: string): Record<string, string>;
15
- static generateFixturesDeclarationForScope(scopeDir: string, sharedDir?: string): string;
15
+ static generateFixturesDeclarationForScope(cwd: string, outputTypesDir: string, scopeDir: string, sharedDir?: string): string;
16
16
  static writeDeclarationFiles(cwd: string): void;
17
17
  }
@@ -2,10 +2,10 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  const PLATFORM_HIERARCHY = {
4
4
  web: ["web", "common"],
5
- ios: ["ios", "mobile", "common"],
6
- android: ["android", "mobile", "common"],
7
- mobile: ["mobile", "common"],
8
- common: ["common"],
5
+ ios: ["ios", "mobile", "common", "web"],
6
+ android: ["android", "mobile", "common", "web"],
7
+ mobile: ["mobile", "common", "android", "ios", "web"],
8
+ common: ["common", "web", "mobile", "android", "ios"],
9
9
  };
10
10
  export class TypeGenerator {
11
11
  /**
@@ -21,7 +21,12 @@ export class TypeGenerator {
21
21
  path.join(cwd, "testing"),
22
22
  ];
23
23
  for (const cand of sharedCandidates) {
24
- if (fs.existsSync(cand) && (fs.existsSync(path.join(cand, "page-objects")) || fs.existsSync(path.join(cand, "steps")) || fs.existsSync(path.join(cand, "actions")))) {
24
+ if (fs.existsSync(cand) &&
25
+ (fs.existsSync(path.join(cand, "page-objects")) ||
26
+ fs.existsSync(path.join(cand, "support")) ||
27
+ fs.existsSync(path.join(cand, "steps")) ||
28
+ fs.existsSync(path.join(cand, "actions")) ||
29
+ fs.existsSync(path.join(cand, "fixtures")))) {
25
30
  if (!scopes.some((s) => s.dir === cand)) {
26
31
  scopes.push({ name: "shared", dir: cand, isShared: true });
27
32
  }
@@ -124,9 +129,15 @@ export class TypeGenerator {
124
129
  scanDir(scopeDir, 6);
125
130
  }
126
131
  else {
127
- const candidates = [path.join(scopeDir, entityType)];
132
+ const candidates = [
133
+ path.join(scopeDir, entityType),
134
+ path.join(scopeDir, "support", entityType),
135
+ path.join(scopeDir, "support", "testing", entityType),
136
+ ];
128
137
  if (sharedDir) {
129
138
  candidates.push(path.join(sharedDir, entityType));
139
+ candidates.push(path.join(sharedDir, "support", entityType));
140
+ candidates.push(path.join(sharedDir, "support", "testing", entityType));
130
141
  }
131
142
  for (const c of candidates) {
132
143
  if (fs.existsSync(c) && !dirs.includes(c))
@@ -244,7 +255,11 @@ export class TypeGenerator {
244
255
  if (matchedFile) {
245
256
  if (!matchedFile.startsWith("."))
246
257
  matchedFile = `./${matchedFile}`;
247
- content += ` ${actionName}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
258
+ const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
259
+ content += ` "${actionName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
260
+ if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
261
+ content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
262
+ }
248
263
  declaredActions.add(actionName);
249
264
  }
250
265
  }
@@ -258,7 +273,11 @@ export class TypeGenerator {
258
273
  let rel = path.relative(outputTypesDir, path.join(actionsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
259
274
  if (!rel.startsWith("."))
260
275
  rel = `./${rel}`;
261
- content += ` ${actionName}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
276
+ const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
277
+ content += ` "${actionName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
278
+ if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
279
+ content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
280
+ }
262
281
  declaredActions.add(actionName);
263
282
  }
264
283
  }
@@ -298,7 +317,11 @@ export class TypeGenerator {
298
317
  if (matchedFile) {
299
318
  if (!matchedFile.startsWith("."))
300
319
  matchedFile = `./${matchedFile}`;
301
- content += ` ${stepName}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
320
+ const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
321
+ content += ` "${stepName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
322
+ if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
323
+ content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
324
+ }
302
325
  declaredSteps.add(stepName);
303
326
  }
304
327
  }
@@ -312,7 +335,11 @@ export class TypeGenerator {
312
335
  let rel = path.relative(outputTypesDir, path.join(stepsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
313
336
  if (!rel.startsWith("."))
314
337
  rel = `./${rel}`;
315
- content += ` ${stepName}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
338
+ const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
339
+ content += ` "${stepName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
340
+ if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
341
+ content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
342
+ }
316
343
  declaredSteps.add(stepName);
317
344
  }
318
345
  }
@@ -324,7 +351,7 @@ export class TypeGenerator {
324
351
  }
325
352
  return results;
326
353
  }
327
- static generateFixturesDeclarationForScope(scopeDir, sharedDir) {
354
+ static generateFixturesDeclarationForScope(cwd, outputTypesDir, scopeDir, sharedDir) {
328
355
  const fixturesDirs = this.getEntityDirsForScope("fixtures", scopeDir, sharedDir);
329
356
  let content = `// Auto-generated ambient fixture declarations for TestSpectra\n`;
330
357
  content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
@@ -346,7 +373,15 @@ export class TypeGenerator {
346
373
  const varName = parsed.name.replace(/[^a-zA-Z0-9_$]/g, "_");
347
374
  if (declaredFixtures.has(varName))
348
375
  continue;
349
- content += ` readonly ${varName}: string;\n`;
376
+ if (entry.name.endsWith(".json")) {
377
+ let rel = path.relative(outputTypesDir, path.join(dir, entry.name)).replace(/\\/g, "/");
378
+ if (!rel.startsWith("."))
379
+ rel = `./${rel}`;
380
+ content += ` readonly ${varName}: typeof import('${rel}');\n`;
381
+ }
382
+ else {
383
+ content += ` readonly ${varName}: string;\n`;
384
+ }
350
385
  declaredFixtures.add(varName);
351
386
  }
352
387
  }
@@ -368,7 +403,7 @@ export class TypeGenerator {
368
403
  const scopes = this.getEntityScopes(cwd);
369
404
  const sharedScope = scopes.find((s) => s.isShared);
370
405
  // 1. Generate Global / Standalone Root Types (or fallback)
371
- const rootFixtures = this.generateFixturesDeclarationForScope(cwd);
406
+ const rootFixtures = this.generateFixturesDeclarationForScope(cwd, rootTypesDir, cwd);
372
407
  fs.writeFileSync(path.join(rootTypesDir, "fixtures.d.ts"), rootFixtures, "utf-8");
373
408
  const rootDeclarations = this.generateDeclarationsForScope(cwd, rootTypesDir, cwd);
374
409
  for (const [platform, content] of Object.entries(rootDeclarations)) {
@@ -379,7 +414,7 @@ export class TypeGenerator {
379
414
  const sharedTypesDir = path.join(rootTypesDir, "shared");
380
415
  if (!fs.existsSync(sharedTypesDir))
381
416
  fs.mkdirSync(sharedTypesDir, { recursive: true });
382
- const sharedFixtures = this.generateFixturesDeclarationForScope(sharedScope.dir);
417
+ const sharedFixtures = this.generateFixturesDeclarationForScope(cwd, sharedTypesDir, sharedScope.dir);
383
418
  fs.writeFileSync(path.join(sharedTypesDir, "fixtures.d.ts"), sharedFixtures, "utf-8");
384
419
  // Shared scope has NO feature dependencies (only shared itself)
385
420
  const sharedDeclarations = this.generateDeclarationsForScope(cwd, sharedTypesDir, sharedScope.dir);
@@ -393,7 +428,7 @@ export class TypeGenerator {
393
428
  const featureTypesDir = path.join(rootTypesDir, "features", feature.name);
394
429
  if (!fs.existsSync(featureTypesDir))
395
430
  fs.mkdirSync(featureTypesDir, { recursive: true });
396
- const featureFixtures = this.generateFixturesDeclarationForScope(feature.dir, sharedScope?.dir);
431
+ const featureFixtures = this.generateFixturesDeclarationForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
397
432
  fs.writeFileSync(path.join(featureTypesDir, "fixtures.d.ts"), featureFixtures, "utf-8");
398
433
  // Feature scope gets (feature + shared) ONLY
399
434
  const featureDeclarations = this.generateDeclarationsForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testspectra/cli",
3
- "version": "1.0.58",
3
+ "version": "1.0.62",
4
4
  "description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "dependencies": {
31
31
  "@clack/prompts": "^1.7.0",
32
- "@testspectra/matchers": "^1.0.58",
32
+ "@testspectra/matchers": "^1.0.62",
33
33
  "chalk": "^5.3.0",
34
34
  "commander": "^12.1.0",
35
35
  "dotenv": "^16.4.5",
@@ -0,0 +1,18 @@
1
+ # TestSpectra AI Instructions for Cursor
2
+
3
+ You are developing end-to-end automated tests with the TestSpectra testing framework.
4
+
5
+ ## Key Rules & Conventions
6
+ 1. **Zero-Import Standard**: Never add manual `import` statements for Page Objects (`PlaygroundPage`, `MatchersPage`, `NavigationPage`), Steps (`Step.*`), Fixtures (`Fixture.*`), or Actions (`Spectra.*`). All typings are provided globally and ambiently via `.testspectra/types/`.
7
+ 2. **Platform Separation**:
8
+ - `web.test.ts` / `web.ts` / `web.step.ts` / `web.hook.ts`: Web platform implementations.
9
+ - `mobile.test.ts` / `mobile.ts` / `mobile.step.ts` / `mobile.hook.ts`: Mobile platform implementations.
10
+ - `common.test.ts` / `common.ts` / `common.step.ts` / `common.hook.ts`: Cross-platform shared implementations.
11
+ 3. **CamelCase Naming Convention**:
12
+ - Shared Steps (`support/steps/loginAsAdmin/`, `support/steps/verifyPlaygroundState/`)
13
+ - Custom Actions (`support/actions/applyDiscount/`, `support/actions/verifyOtp/`)
14
+ - Lifecycle Hooks (`global-hooks/before/`, `global-hooks/after/`, `specs/<suite>/hooks/before/`, `specs/<suite>/hooks/beforeEach/`, `specs/<suite>/hooks/after/`, `specs/<suite>/hooks/afterEach/`)
15
+ 4. **Spec-as-Code**:
16
+ - Suites are defined in `specs/<suite-name>/suite.md` with YAML frontmatter (`id`, `name`, `executionOrder`, `tags`).
17
+ - Test cases are defined in `specs/<suite-name>/<TC-ID-slug>/spec.md` with YAML frontmatter (`id`, `title`, `priority`, `coverage`).
18
+ - Shared steps are defined in `support/steps/<stepName>/step.md`.
@@ -0,0 +1,10 @@
1
+ # CLAUDE.md - TestSpectra Framework Conventions
2
+
3
+ ## Commands
4
+ - Run all Web tests against live demo app: `pnpm spectra run --demo`
5
+ - Run specific spec with demo app: `pnpm spectra run --spec specs/authentication/TC-0001-valid-login/web.test.ts --demo`
6
+ - Run Web tests: `pnpm test`
7
+ - Run Mobile tests: `pnpm spectra run --target android` or `pnpm spectra run --target ios`
8
+ - Type checking: `pnpm type-check` (or `tsc -b`)
9
+ - Preflight doctor: `pnpm spectra doctor`
10
+ - List devices: `pnpm spectra devices`
@@ -0,0 +1,7 @@
1
+ {
2
+ "defaultEmail": "admin@testspectra.dev",
3
+ "defaultToken": "JWT_abc123xyz",
4
+ "pageTitle": "TestSpectra — Matchers API Reference",
5
+ "welcomeText": "Welcome to TestSpectra",
6
+ "dashboardHref": "/dashboard"
7
+ }
@@ -0,0 +1,3 @@
1
+ export default async function () {
2
+ // Global mobile teardown
3
+ }
@@ -0,0 +1,3 @@
1
+ export default async function () {
2
+ // Global web teardown
3
+ }
@@ -0,0 +1,3 @@
1
+ export default async function () {
2
+ // Global mobile initialization
3
+ }
@@ -0,0 +1,3 @@
1
+ export default async function () {
2
+ // Global web initialization
3
+ }
@@ -9,8 +9,12 @@
9
9
  },
10
10
  "devDependencies": {
11
11
  "@testspectra/cli": "workspace:*",
12
- "@testspectra/matchers": "^1.0.58",
12
+ "@testspectra/matchers": "^1.0.62",
13
13
  "@types/node": "^20.14.0",
14
+ "@wdio/cli": "^9.2.8",
15
+ "@wdio/local-runner": "^9.2.8",
16
+ "@wdio/spec-reporter": "^9.2.8",
17
+ "@wdio/devtools-service": "^8.46.0",
14
18
  "@wdio/globals": "^9.2.8",
15
19
  "@wdio/mocha-framework": "^9.2.8",
16
20
  "webdriverio": "^9.2.8",
@@ -0,0 +1,21 @@
1
+ export class MatchersPage {
2
+ get visibleElement() { return Spectra.get("~visible_element"); }
3
+ get hiddenElement() { return Spectra.get("~hidden_element"); }
4
+ get existingElement() { return Spectra.get("~existing_element"); }
5
+ get submitButton() { return Spectra.get("~submit_button"); }
6
+ get disabledButton() { return Spectra.get("~disabled_button"); }
7
+ get checkbox() { return Spectra.get("~checkbox"); }
8
+ get radio() { return Spectra.get("~radio"); }
9
+ get emailInput() { return Spectra.get("~email_input"); }
10
+ get tokenInput() { return Spectra.get("~token_input"); }
11
+ get exactText() { return Spectra.get("~exact_text"); }
12
+ get partialText() { return Spectra.get("~partial_text"); }
13
+ get activeTab() { return Spectra.get("~active_tab"); }
14
+ get navLink() { return Spectra.get("~nav_link"); }
15
+ get productCards() { return Spectra.getAll("~product_card"); }
16
+ get emptyList() { return Spectra.getAll("~empty_list"); }
17
+
18
+ async open(_section = "") {}
19
+ }
20
+
21
+ export default new MatchersPage();
@@ -0,0 +1,23 @@
1
+ export class MatchersPage {
2
+ get visibleElement() { return Spectra.get("#demo-visible-el"); }
3
+ get hiddenElement() { return Spectra.get("#demo-hidden-el"); }
4
+ get existingElement() { return Spectra.get("#demo-existing-el"); }
5
+ get submitButton() { return Spectra.get("#demo-submit-btn"); }
6
+ get disabledButton() { return Spectra.get("#demo-disabled-btn"); }
7
+ get checkbox() { return Spectra.get("#demo-checkbox"); }
8
+ get radio() { return Spectra.get("#demo-radio"); }
9
+ get emailInput() { return Spectra.get("#demo-email-input"); }
10
+ get tokenInput() { return Spectra.get("#demo-token-input"); }
11
+ get exactText() { return Spectra.get("#demo-exact-text"); }
12
+ get partialText() { return Spectra.get("#demo-partial-text"); }
13
+ get activeTab() { return Spectra.get("#demo-active-tab"); }
14
+ get navLink() { return Spectra.get("#demo-nav-link"); }
15
+ get productCards() { return Spectra.getAll(".demo-product-card"); }
16
+ get emptyList() { return Spectra.getAll("#demo-empty-list > *"); }
17
+
18
+ async open(section = "") {
19
+ await browser.url("/" + (section ? "#" + section : ""));
20
+ }
21
+ }
22
+
23
+ export default new MatchersPage();
@@ -0,0 +1,5 @@
1
+ export class NavigationPage {
2
+ get sidebarLinks() { return Spectra.getAll("~sidebar_link"); }
3
+ }
4
+
5
+ export default new NavigationPage();
@@ -0,0 +1,8 @@
1
+ export class NavigationPage {
2
+ get sidebarLinks() { return Spectra.getAll("aside a"); }
3
+ get overviewLink() { return Spectra.get("aside a[href='#overview']"); }
4
+ get visibilityLink() { return Spectra.get("aside a[href='#visibility']"); }
5
+ get playgroundLink() { return Spectra.get("aside a[href='#playground']"); }
6
+ }
7
+
8
+ export default new NavigationPage();