@testspectra/cli 1.0.70 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/LICENSE.md +48 -0
  2. package/README.md +36 -270
  3. package/bin/.testspectra-runner.hash +1 -0
  4. package/bin/spectra.js +1 -1
  5. package/bin/testspectra-runner +0 -0
  6. package/demo-app/assets/index-DPfv5_EF.js +116 -0
  7. package/demo-app/assets/index-gIwu2EMV.css +1 -0
  8. package/demo-app/index.html +2 -2
  9. package/dist/.tsbuildinfo +1 -0
  10. package/dist/commands/__tests__/cloud-run.test.d.ts +1 -0
  11. package/dist/commands/__tests__/cloud-run.test.js +93 -0
  12. package/dist/commands/__tests__/doctor.test.d.ts +1 -0
  13. package/dist/commands/__tests__/doctor.test.js +39 -0
  14. package/dist/commands/__tests__/init.test.d.ts +1 -0
  15. package/dist/commands/__tests__/init.test.js +148 -0
  16. package/dist/commands/__tests__/license.test.d.ts +1 -0
  17. package/dist/commands/__tests__/license.test.js +13 -0
  18. package/dist/commands/__tests__/refactor.test.d.ts +1 -0
  19. package/dist/commands/__tests__/refactor.test.js +64 -0
  20. package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
  21. package/dist/commands/__tests__/run-e2e.test.js +181 -0
  22. package/dist/commands/__tests__/run.test.d.ts +1 -0
  23. package/dist/commands/__tests__/run.test.js +197 -0
  24. package/dist/commands/__tests__/watch.test.d.ts +1 -0
  25. package/dist/commands/__tests__/watch.test.js +155 -0
  26. package/dist/commands/add.js +86 -85
  27. package/dist/commands/cloud-run.d.ts +11 -0
  28. package/dist/commands/cloud-run.js +118 -0
  29. package/dist/commands/demo.js +7 -7
  30. package/dist/commands/devices.d.ts +14 -2
  31. package/dist/commands/devices.js +44 -34
  32. package/dist/commands/doctor.d.ts +3 -1
  33. package/dist/commands/doctor.js +43 -264
  34. package/dist/commands/init.d.ts +1 -1
  35. package/dist/commands/init.js +206 -196
  36. package/dist/commands/install.d.ts +4 -0
  37. package/dist/commands/install.js +59 -0
  38. package/dist/commands/license.d.ts +4 -0
  39. package/dist/commands/license.js +30 -0
  40. package/dist/commands/refactor.d.ts +8 -0
  41. package/dist/commands/refactor.js +78 -0
  42. package/dist/commands/run.d.ts +22 -1
  43. package/dist/commands/run.js +252 -109
  44. package/dist/commands/sync-types.d.ts +4 -0
  45. package/dist/commands/sync-types.js +14 -0
  46. package/dist/commands/update.js +39 -43
  47. package/dist/commands/watch.d.ts +6 -0
  48. package/dist/commands/watch.js +47 -43
  49. package/dist/config/loader.d.ts +1 -1
  50. package/dist/config/loader.js +40 -25
  51. package/dist/config/schema.d.ts +117 -49
  52. package/dist/config/schema.js +41 -34
  53. package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
  54. package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -0
  55. package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
  56. package/dist/generator/__tests__/type-generator.test.js +386 -0
  57. package/dist/generator/index.d.ts +2 -0
  58. package/dist/generator/index.js +2 -0
  59. package/dist/generator/tsconfig-generator.d.ts +27 -0
  60. package/dist/generator/tsconfig-generator.js +391 -0
  61. package/dist/generator/type-generator.d.ts +36 -0
  62. package/dist/generator/type-generator.js +644 -0
  63. package/dist/index.d.ts +9 -5
  64. package/dist/index.js +124 -51
  65. package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
  66. package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
  67. package/dist/lifecycle/hook-discovery.d.ts +24 -0
  68. package/dist/lifecycle/hook-discovery.js +60 -0
  69. package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
  70. package/dist/lifecycle/hook-dispatcher.js +190 -0
  71. package/dist/lifecycle/index.d.ts +3 -0
  72. package/dist/lifecycle/index.js +3 -0
  73. package/dist/lifecycle/parallel-grouping.d.ts +27 -0
  74. package/dist/lifecycle/parallel-grouping.js +151 -0
  75. package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
  76. package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
  77. package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
  78. package/dist/mobile/__tests__/device-pool.test.js +97 -0
  79. package/dist/mobile/device-pool.d.ts +23 -0
  80. package/dist/mobile/device-pool.js +1 -0
  81. package/dist/mobile/index.d.ts +1 -0
  82. package/dist/mobile/index.js +1 -0
  83. package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
  84. package/dist/refactor/__tests__/refactor-engine.test.js +175 -0
  85. package/dist/refactor/index.d.ts +1 -0
  86. package/dist/refactor/index.js +1 -0
  87. package/dist/refactor/refactor-engine.d.ts +25 -0
  88. package/dist/refactor/refactor-engine.js +255 -0
  89. package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
  90. package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
  91. package/dist/reporter/semantic-formatter.d.ts +6 -0
  92. package/dist/reporter/semantic-formatter.js +191 -0
  93. package/dist/reporter/summary-view.d.ts +12 -0
  94. package/dist/reporter/summary-view.js +19 -0
  95. package/dist/reporter/terminal-stream.d.ts +32 -0
  96. package/dist/reporter/terminal-stream.js +82 -0
  97. package/dist/reporter/types.d.ts +2 -0
  98. package/dist/reporter/types.js +1 -0
  99. package/dist/runner/bridge.d.ts +10 -5
  100. package/dist/runner/bridge.js +69 -44
  101. package/dist/runner/reporter.d.ts +30 -2
  102. package/dist/runner/reporter.js +419 -13
  103. package/dist/server/demo.js +43 -44
  104. package/dist/types/generator.d.ts +1 -17
  105. package/dist/types/generator.js +1 -443
  106. package/dist/utils/demo-server.js +38 -35
  107. package/package.json +20 -12
  108. package/templates/default/CLAUDE.md +7 -5
  109. package/templates/default/fixtures/apiData.json +24 -0
  110. package/templates/default/package.json +13 -4
  111. package/templates/default/page-objects/MatchersPage/mobile.ts +41 -16
  112. package/templates/default/page-objects/MatchersPage/web.ts +14 -0
  113. package/templates/default/page-objects/NavigationPage/mobile.ts +10 -1
  114. package/templates/default/page-objects/PlaygroundPage/mobile.ts +27 -17
  115. package/templates/default/page-objects/PlaygroundPage/web.ts +2 -0
  116. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +11 -0
  117. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  118. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  119. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +16 -0
  120. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  121. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  122. package/templates/default/specs/ApiInterception/suite.md +9 -0
  123. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
  124. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  125. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +15 -1
  126. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
  127. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
  128. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
  129. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
  130. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  131. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
  132. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  133. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
  134. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
  135. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
  136. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
  137. package/templates/default/spectra.config.ts +21 -14
  138. package/templates/default/support/actions/fillCredentials/mobile.action.ts +2 -2
  139. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +2 -0
  140. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +2 -2
  141. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +3 -1
  142. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +3 -3
  143. package/templates/default/tsconfig.json +3 -1
  144. package/templates/nx/.nx/workspace-data/d/daemon.log +168 -0
  145. package/templates/nx/CLAUDE.md +2 -2
  146. package/templates/nx/package.json +7 -5
  147. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +3 -0
  148. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
  149. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
  150. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +3 -0
  151. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
  152. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
  153. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
  154. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
  155. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
  156. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
  157. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
  158. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
  159. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
  160. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
  161. package/templates/nx/pnpm-lock.yaml +6663 -0
  162. package/templates/nx/pnpm-workspace.yaml +2 -1
  163. package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
  164. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +12 -0
  165. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +14 -0
  166. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -0
  167. package/templates/nx/shared/testing/project.json +19 -0
  168. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +2 -2
  169. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
  170. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +3 -3
  171. package/templates/nx/spectra.config.ts +19 -12
  172. package/templates/nx/tsconfig.json +3 -1
  173. package/demo-app/assets/index-DAUlrHTr.css +0 -1
  174. package/demo-app/assets/index-DN2Kb0A8.js +0 -100
  175. package/dist/index.cjs +0 -101
  176. package/dist/plugin.cjs +0 -575
  177. package/dist/plugin.d.ts +0 -16
  178. package/dist/plugin.js +0 -184
  179. package/dist/step/index.d.ts +0 -6
  180. package/dist/step/index.js +0 -6
  181. package/dist/step/matchers.d.ts +0 -14
  182. package/dist/step/matchers.js +0 -114
  183. package/dist/step/proto.d.ts +0 -46
  184. package/dist/step/proto.js +0 -114
  185. package/dist/step/runner/collection.d.ts +0 -13
  186. package/dist/step/runner/collection.js +0 -51
  187. package/dist/step/runner/single.d.ts +0 -21
  188. package/dist/step/runner/single.js +0 -138
  189. package/dist/step/spectra.d.ts +0 -34
  190. package/dist/step/spectra.js +0 -156
  191. package/dist/step/types.d.ts +0 -32
  192. package/dist/step/types.js +0 -5
  193. package/templates/default/tsconfig.spectra.android.json +0 -46
  194. package/templates/default/tsconfig.spectra.ios.json +0 -46
  195. package/templates/default/tsconfig.spectra.json +0 -22
  196. package/templates/default/tsconfig.spectra.web.json +0 -53
  197. package/templates/nx/fixtures/playgroundData.json +0 -7
  198. package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
  199. package/templates/nx/global-hooks/after/web.hook.ts +0 -3
  200. package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
  201. package/templates/nx/global-hooks/before/web.hook.ts +0 -3
  202. package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
  203. package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
  204. package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
  205. package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
  206. package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
  207. package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
  208. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
  209. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
  210. package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
  211. package/templates/nx/specs/BrowserContext/suite.md +0 -9
  212. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
  213. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
  214. package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
  215. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
  216. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
  217. package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
  218. package/templates/nx/specs/ContentMatchers/suite.md +0 -9
  219. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
  220. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
  221. package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
  222. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
  223. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
  224. package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
  225. package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
  226. package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
  227. package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
  228. package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
  229. package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
  230. package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
  231. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
  232. package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
  233. package/templates/nx/specs/ElementMatchers/suite.md +0 -9
  234. package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
  235. package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
  236. package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
  237. package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
  238. package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
  239. package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
  240. package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
  241. package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
  242. package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
  243. package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
  244. package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
  245. package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
  246. package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
  247. package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
  248. package/templates/nx/specs/Playground/suite.md +0 -9
  249. package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
  250. package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
  251. package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
  252. package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
  253. package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
  254. package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
  255. package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
  256. package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
  257. package/templates/nx/tsconfig.spectra.android.json +0 -39
  258. package/templates/nx/tsconfig.spectra.ios.json +0 -39
  259. package/templates/nx/tsconfig.spectra.json +0 -25
  260. package/templates/nx/tsconfig.spectra.shared.json +0 -25
  261. package/templates/nx/tsconfig.spectra.web.json +0 -45
  262. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  263. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  264. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  265. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  266. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  267. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  268. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  269. /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
  270. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
  271. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
  272. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
  273. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
  274. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
  275. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
  276. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
  277. /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
@@ -0,0 +1,191 @@
1
+ function extractTargetString(target) {
2
+ if (!target)
3
+ return '';
4
+ if (typeof target === 'string')
5
+ return target;
6
+ if (typeof target === 'object') {
7
+ if (typeof target.selector === 'string')
8
+ return target.selector;
9
+ if (typeof target.name === 'string')
10
+ return target.name;
11
+ if (typeof target.target === 'string')
12
+ return target.target;
13
+ }
14
+ return String(target);
15
+ }
16
+ function formatElementTarget(target) {
17
+ const sel = extractTargetString(target);
18
+ if (!sel)
19
+ return 'element {target}';
20
+ if (sel.startsWith('element "') || sel.startsWith('collection "')) {
21
+ return sel;
22
+ }
23
+ return `element "${sel}"`;
24
+ }
25
+ function formatCollectionTarget(target) {
26
+ const sel = extractTargetString(target);
27
+ if (!sel)
28
+ return 'collection "{selector}"';
29
+ if (sel.startsWith('collection "')) {
30
+ return sel;
31
+ }
32
+ return `collection "${sel}"`;
33
+ }
34
+ /**
35
+ * Transforms raw matcher assertions and element commands into grammatically correct English sentences.
36
+ */
37
+ export function formatAction(actionKey, target, args = {}) {
38
+ const el = formatElementTarget(target);
39
+ switch (actionKey) {
40
+ case 'click':
41
+ return `Click on ${el}`;
42
+ case 'doubleClick':
43
+ return `Double-click on ${el}`;
44
+ case 'rightClick':
45
+ return `Right-click on ${el}`;
46
+ case 'type':
47
+ return `Type "${args.text ?? args.value ?? '{text}'}" into ${el}`;
48
+ case 'clear':
49
+ return `Clear text in ${el}`;
50
+ case 'select':
51
+ case 'selectOption':
52
+ return `Select option "${args.option ?? args.value ?? args.text ?? '{option}'}" in ${el}`;
53
+ case 'hover':
54
+ return `Hover over ${el}`;
55
+ case 'dragDrop':
56
+ const dest = args.dest || args.destination ? formatElementTarget(args.dest || args.destination) : 'element {dest}';
57
+ return `Drag ${el} and drop onto ${dest}`;
58
+ case 'scrollIntoView':
59
+ return `Scroll ${el} into view`;
60
+ case 'longPress':
61
+ return `Long-press on ${el} for ${args.durationMs ?? args.ms ?? args.duration ?? '{durationMs}'}ms`;
62
+ case 'waitForElement':
63
+ return `Wait for ${el} to be displayed`;
64
+ case 'navigate':
65
+ return `Navigate to "${args.url ?? '{url}'}"`;
66
+ case 'back':
67
+ return `Navigate back in browser`;
68
+ case 'forward':
69
+ return `Navigate forward in browser`;
70
+ case 'refresh':
71
+ return `Refresh current page`;
72
+ case 'setViewport':
73
+ return `Set viewport to ${args.width ?? '{width}'}x${args.height ?? '{height}'}`;
74
+ case 'wait':
75
+ return `Wait for ${args.ms ?? args.durationMs ?? '{ms}'}ms`;
76
+ case 'pressKey':
77
+ return `Press keyboard key "${args.key ?? args.value ?? '{key}'}"`;
78
+ case 'scroll':
79
+ return `Scroll ${args.direction ?? '{direction}'} by ${args.pixels ?? '{pixels}'}px`;
80
+ case 'swipe':
81
+ return `Swipe ${args.direction ?? '{direction}'} by ${args.distance ?? '{distance}'}px`;
82
+ case 'intercept':
83
+ return `Intercept ${args.method ?? '{method}'} "${args.url ?? '{url}'}" mock rule registered`;
84
+ case 'clearCookies':
85
+ return `Clear all browser cookies`;
86
+ case 'clearLocalStorage':
87
+ return `Clear browser localStorage`;
88
+ default:
89
+ return `Perform ${actionKey} on ${el}`;
90
+ }
91
+ }
92
+ export function formatAssertion(assertionKey, target, args = {}) {
93
+ const el = formatElementTarget(target);
94
+ const col = formatCollectionTarget(target);
95
+ switch (assertionKey) {
96
+ case 'be.visible':
97
+ return `Expect ${el} to be visible`;
98
+ case 'not.be.visible':
99
+ return `Expect ${el} not to be visible`;
100
+ case 'exist':
101
+ return `Expect ${el} to exist in DOM`;
102
+ case 'not.exist':
103
+ return `Expect ${el} not to exist in DOM`;
104
+ case 'be.clickable':
105
+ return `Expect ${el} to be clickable`;
106
+ case 'not.be.clickable':
107
+ return `Expect ${el} not to be clickable`;
108
+ case 'be.enabled':
109
+ return `Expect ${el} to be enabled`;
110
+ case 'be.disabled':
111
+ return `Expect ${el} to be disabled`;
112
+ case 'be.checked':
113
+ return `Expect ${el} to be checked`;
114
+ case 'not.be.checked':
115
+ return `Expect ${el} not to be checked`;
116
+ case 'be.selected':
117
+ return `Expect ${el} to be selected`;
118
+ case 'not.be.selected':
119
+ return `Expect ${el} not to be selected`;
120
+ case 'be.focused':
121
+ return `Expect ${el} to be focused`;
122
+ case 'not.be.focused':
123
+ return `Expect ${el} not to be focused`;
124
+ case 'have.text':
125
+ return `Expect ${el} to have text "${args.expected ?? args.text ?? '{str}'}"`;
126
+ case 'not.have.text':
127
+ return `Expect ${el} not to have text "${args.expected ?? args.text ?? '{str}'}"`;
128
+ case 'contain.text':
129
+ return `Expect ${el} to contain text "${args.expected ?? args.text ?? '{sub}'}"`;
130
+ case 'not.contain.text':
131
+ return `Expect ${el} not to contain text "${args.expected ?? args.text ?? '{s}'}"`;
132
+ case 'have.value':
133
+ return `Expect ${el} to have value "${args.expected ?? args.value ?? '{val}'}"`;
134
+ case 'not.have.value':
135
+ return `Expect ${el} not to have value "${args.expected ?? args.value ?? '{val}'}"`;
136
+ case 'contain.value':
137
+ return `Expect ${el} to contain value "${args.expected ?? args.value ?? '{s}'}"`;
138
+ case 'not.contain.value':
139
+ return `Expect ${el} not to contain value "${args.expected ?? args.value ?? '{s}'}"`;
140
+ case 'have.attr':
141
+ return args.value !== undefined
142
+ ? `Expect ${el} to have attribute "${args.name ?? args.expected ?? '{k}'}" = "${args.value}"`
143
+ : `Expect ${el} to have attribute "${args.name ?? args.expected ?? '{k}'}"`;
144
+ case 'not.have.attr':
145
+ return `Expect ${el} not to have attribute "${args.name ?? args.expected ?? '{k}'}"`;
146
+ case 'have.class':
147
+ return `Expect ${el} to have class "${args.expected ?? args.className ?? '{cls}'}"`;
148
+ case 'not.have.class':
149
+ return `Expect ${el} not to have class "${args.expected ?? args.className ?? '{cls}'}"`;
150
+ case 'have.css':
151
+ return `Expect ${el} to have CSS "${args.name ?? args.property ?? '{prop}'}" = "${args.value ?? '{val}'}"`;
152
+ case 'not.have.css':
153
+ return `Expect ${el} not to have CSS "${args.name ?? args.property ?? '{p}'}" = "${args.value ?? '{v}'}"`;
154
+ // Collections
155
+ case 'have.length':
156
+ return `Expect ${col} count to equal ${args.expected ?? args.length ?? '{n}'}`;
157
+ case 'not.have.length':
158
+ return `Expect ${col} count not to equal ${args.expected ?? args.length ?? '{n}'}`;
159
+ case 'have.length.greaterThan':
160
+ return `Expect ${col} count to be greater than ${args.expected ?? args.min ?? '{n}'}`;
161
+ case 'have.length.lessThan':
162
+ return `Expect ${col} count to be less than ${args.expected ?? args.max ?? '{n}'}`;
163
+ case 'be.empty':
164
+ return `Expect ${col} to be empty`;
165
+ case 'not.be.empty':
166
+ return `Expect ${col} not to be empty`;
167
+ // Browser Context
168
+ case 'have.url':
169
+ case 'shouldHaveUrl':
170
+ return `Expect browser URL to be "${args.expected ?? args.url ?? '{url}'}"`;
171
+ case 'contain.url':
172
+ case 'shouldContainUrl':
173
+ return `Expect browser URL to contain "${args.expected ?? args.url ?? '{url}'}"`;
174
+ case 'have.title':
175
+ case 'shouldHaveTitle':
176
+ return `Expect browser title to be "${args.expected ?? args.title ?? '{title}'}"`;
177
+ case 'contain.title':
178
+ case 'shouldContainTitle':
179
+ return `Expect browser title to contain "${args.expected ?? args.title ?? '{title}'}"`;
180
+ case 'be.loaded':
181
+ case 'shouldBeLoaded':
182
+ case 'be.pageLoaded':
183
+ case 'shouldBePageLoaded':
184
+ return `Expect page to be fully loaded`;
185
+ case 'have.noConsoleErrors':
186
+ case 'shouldHaveNoConsoleErrors':
187
+ return `Expect browser console to have no errors`;
188
+ default:
189
+ return `Expect ${el} to match ${assertionKey}`;
190
+ }
191
+ }
@@ -0,0 +1,12 @@
1
+ export interface ExecutionSummary {
2
+ totalSuites: number;
3
+ totalTests: number;
4
+ passedCount: number;
5
+ failedCount: number;
6
+ durationMs: number;
7
+ reportLocation: string;
8
+ }
9
+ /**
10
+ * Renders the final execution summary box.
11
+ */
12
+ export declare function renderSummaryView(summary: ExecutionSummary): void;
@@ -0,0 +1,19 @@
1
+ import chalk from 'chalk';
2
+ /**
3
+ * Renders the final execution summary box.
4
+ */
5
+ export function renderSummaryView(summary) {
6
+ const { totalSuites, totalTests, passedCount, failedCount, durationMs, reportLocation } = summary;
7
+ const durationSec = (durationMs / 1000).toFixed(2);
8
+ console.log();
9
+ console.log(chalk.bold('TestSpectra Execution Summary'));
10
+ console.log(chalk.gray('────────────────────────────────────────'));
11
+ console.log(`Suites: ${chalk.bold(totalSuites)}`);
12
+ console.log(`Tests: ${chalk.bold(totalTests)} total, ` +
13
+ `${passedCount > 0 ? chalk.green.bold(`${passedCount} passed`) : '0 passed'}, ` +
14
+ `${failedCount > 0 ? chalk.red.bold(`${failedCount} failed`) : '0 failed'}`);
15
+ console.log(`Duration: ${durationSec}s`);
16
+ console.log(`Report: ${chalk.cyan(reportLocation)}`);
17
+ console.log(chalk.gray('────────────────────────────────────────'));
18
+ console.log();
19
+ }
@@ -0,0 +1,32 @@
1
+ export declare class TerminalStream {
2
+ private spinner;
3
+ private currentTestTitle;
4
+ private stepCount;
5
+ constructor();
6
+ /**
7
+ * Starts a new test case, logging the pending bullet and test title.
8
+ */
9
+ startTest(title: string): void;
10
+ /**
11
+ * Begins a new step, rendering an active spinner on the terminal with 2 spaces indentation.
12
+ */
13
+ startStep(text: string): void;
14
+ /**
15
+ * Marks the current step as passed, updating the spinner to a green checkmark
16
+ * and appending the duration in gray with 2 spaces indentation.
17
+ */
18
+ passStep(text: string, durationMs: number): void;
19
+ /**
20
+ * Marks the current step as failed, updating the spinner to a red cross,
21
+ * coloring the text red, appending the duration, and optionally logging the error with 2 spaces indentation.
22
+ */
23
+ failStep(text: string, durationMs: number, errorMsg?: string): void;
24
+ /**
25
+ * Concludes the active test case as passed, overwriting the initial "●" line with "✓" and duration in-place.
26
+ */
27
+ passTest(title: string, durationMs: number): void;
28
+ /**
29
+ * Concludes the active test case as failed, overwriting the initial "●" line with "✗" and duration in-place.
30
+ */
31
+ failTest(title: string, durationMs: number): void;
32
+ }
@@ -0,0 +1,82 @@
1
+ import ora from 'ora';
2
+ import chalk from 'chalk';
3
+ export class TerminalStream {
4
+ spinner;
5
+ currentTestTitle = '';
6
+ stepCount = 0;
7
+ constructor() {
8
+ this.spinner = ora({
9
+ spinner: 'dots',
10
+ color: 'cyan',
11
+ prefixText: ' ',
12
+ });
13
+ }
14
+ /**
15
+ * Starts a new test case, logging the pending bullet and test title.
16
+ */
17
+ startTest(title) {
18
+ if (this.spinner.isSpinning) {
19
+ this.spinner.stop();
20
+ }
21
+ this.currentTestTitle = title;
22
+ this.stepCount = 0;
23
+ console.log(`${chalk.hex('#2dd4bf')('●')} ${chalk.white.bold(title)}`);
24
+ }
25
+ /**
26
+ * Begins a new step, rendering an active spinner on the terminal with 2 spaces indentation.
27
+ */
28
+ startStep(text) {
29
+ if (this.spinner.isSpinning) {
30
+ this.spinner.stop();
31
+ }
32
+ this.spinner.start(text);
33
+ }
34
+ /**
35
+ * Marks the current step as passed, updating the spinner to a green checkmark
36
+ * and appending the duration in gray with 2 spaces indentation.
37
+ */
38
+ passStep(text, durationMs) {
39
+ const durationStr = chalk.gray(`(${durationMs}ms)`);
40
+ this.spinner.succeed(`${text} ${durationStr}`);
41
+ this.stepCount++;
42
+ }
43
+ /**
44
+ * Marks the current step as failed, updating the spinner to a red cross,
45
+ * coloring the text red, appending the duration, and optionally logging the error with 2 spaces indentation.
46
+ */
47
+ failStep(text, durationMs, errorMsg) {
48
+ const durationStr = chalk.gray(`(${durationMs}ms)`);
49
+ this.spinner.fail(`${chalk.red(text)} ${durationStr}`);
50
+ this.stepCount++;
51
+ if (errorMsg) {
52
+ console.error(chalk.red(` ↳ ${errorMsg}`));
53
+ this.stepCount++;
54
+ }
55
+ }
56
+ /**
57
+ * Concludes the active test case as passed, overwriting the initial "●" line with "✓" and duration in-place.
58
+ */
59
+ passTest(title, durationMs) {
60
+ if (this.spinner.isSpinning) {
61
+ this.spinner.stop();
62
+ }
63
+ const testTitle = title || this.currentTestTitle;
64
+ const durationStr = chalk.gray(`(${durationMs}ms)`);
65
+ const completedHeader = `${chalk.hex('#34d399')('✓')} ${chalk.white.bold(testTitle)} ${durationStr}`;
66
+ const linesToMoveUp = this.stepCount + 1;
67
+ process.stdout.write(`\x1b[${linesToMoveUp}A\r\x1b[2K${completedHeader}\x1b[${linesToMoveUp}B\r\n`);
68
+ }
69
+ /**
70
+ * Concludes the active test case as failed, overwriting the initial "●" line with "✗" and duration in-place.
71
+ */
72
+ failTest(title, durationMs) {
73
+ if (this.spinner.isSpinning) {
74
+ this.spinner.stop();
75
+ }
76
+ const testTitle = title || this.currentTestTitle;
77
+ const durationStr = chalk.gray(`(${durationMs}ms)`);
78
+ const completedHeader = `${chalk.hex('#f87171')('✗')} ${chalk.white.bold(testTitle)} ${durationStr}`;
79
+ const linesToMoveUp = this.stepCount + 1;
80
+ process.stdout.write(`\x1b[${linesToMoveUp}A\r\x1b[2K${completedHeader}\x1b[${linesToMoveUp}B\r\n`);
81
+ }
82
+ }
@@ -0,0 +1,2 @@
1
+ export type ActionKey = 'click' | 'doubleClick' | 'rightClick' | 'type' | 'clear' | 'select' | 'hover' | 'dragDrop' | 'scrollIntoView' | 'longPress' | 'waitForElement' | 'navigate' | 'back' | 'forward' | 'refresh' | 'setViewport' | 'wait' | 'pressKey' | 'scroll' | 'swipe' | 'intercept' | 'clearCookies' | 'clearLocalStorage';
2
+ export type AssertionKey = 'be.visible' | 'not.be.visible' | 'exist' | 'not.exist' | 'be.clickable' | 'not.be.clickable' | 'be.enabled' | 'be.disabled' | 'be.checked' | 'not.be.checked' | 'be.selected' | 'not.be.selected' | 'be.focused' | 'not.be.focused' | 'have.text' | 'not.have.text' | 'contain.text' | 'not.contain.text' | 'have.value' | 'not.have.value' | 'contain.value' | 'not.contain.value' | 'have.attr' | 'not.have.attr' | 'have.class' | 'not.have.class' | 'have.css' | 'not.have.css' | 'have.length' | 'not.have.length' | 'have.length.greaterThan' | 'have.length.lessThan' | 'be.empty' | 'not.be.empty' | 'shouldHaveUrl' | 'shouldContainUrl' | 'shouldHaveTitle' | 'shouldContainTitle' | 'shouldBeLoaded' | 'shouldBePageLoaded' | 'shouldHaveNoConsoleErrors' | 'have.url' | 'contain.url' | 'have.title' | 'contain.title' | 'be.loaded' | 'be.pageLoaded' | 'have.noConsoleErrors';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,18 +1,23 @@
1
- import { ConfigData } from "../config/schema.js";
2
- import { Reporter, TestRunResult } from "./reporter.js";
1
+ import { ConfigData } from '../config/schema.js';
2
+ import { Reporter, TestRunResult } from './reporter.js';
3
3
  export interface RunOptions {
4
4
  baseDir: string;
5
5
  appDataPath: string;
6
- platform: "web" | "android" | "ios" | "common";
7
- suite: string;
8
- testCases: Array<{
6
+ platform: 'web' | 'android' | 'ios' | 'common' | 'mobile';
7
+ suite?: string;
8
+ testCases?: Array<{
9
9
  id: string;
10
10
  title: string;
11
11
  executionOrder?: number;
12
12
  }>;
13
13
  config: ConfigData;
14
14
  targetDevice?: string;
15
+ licenseKey?: string;
15
16
  outputJsonPath?: string;
17
+ spec?: string;
18
+ concurrency?: number;
19
+ failedOnly?: boolean;
20
+ failedTarget?: string;
16
21
  }
17
22
  export declare class RustCoreBridge {
18
23
  static resolveBinaryPath(): string;
@@ -1,19 +1,25 @@
1
- import { spawn } from "child_process";
2
- import fs from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
1
+ import { spawn } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
  export class RustCoreBridge {
8
8
  static resolveBinaryPath() {
9
9
  // 1. Look for precompiled release/debug binary in workspace target
10
- const workspaceRoot = path.resolve(__dirname, "../../..");
10
+ const workspaceRoot = path.resolve(__dirname, '../../..');
11
+ const isWindows = process.platform === 'win32';
12
+ const binaryName = isWindows ? 'testspectra-runner.exe' : 'testspectra-runner';
11
13
  const candidatePaths = [
12
- path.join(workspaceRoot, "target/debug/testspectra-runner"),
13
- path.join(workspaceRoot, "target/release/testspectra-runner"),
14
- path.join(workspaceRoot, "core/test-runner/target/debug/testspectra-runner"),
15
- path.join(workspaceRoot, "core/test-runner/target/release/testspectra-runner"),
16
- path.join(__dirname, "../../bin/testspectra-runner"),
14
+ path.join(workspaceRoot, 'core/target/release', binaryName),
15
+ path.join(workspaceRoot, 'target/release', binaryName),
16
+ path.join(workspaceRoot, 'cli/bin', binaryName),
17
+ path.join(__dirname, '../../bin', binaryName),
18
+ path.join(workspaceRoot, 'core/cli/target/release', binaryName),
19
+ path.join(workspaceRoot, 'core/target/debug', binaryName),
20
+ path.join(workspaceRoot, 'target/debug', binaryName),
21
+ path.join(workspaceRoot, 'core/cli/target/debug', binaryName),
22
+ path.join(__dirname, '../../bin/testspectra-runner'),
17
23
  ];
18
24
  for (const p of candidatePaths) {
19
25
  if (fs.existsSync(p)) {
@@ -23,7 +29,8 @@ export class RustCoreBridge {
23
29
  return `${p}.exe`;
24
30
  }
25
31
  }
26
- return "cargo"; // Fallback to cargo run
32
+ throw new Error("[TestSpectra] Native core runner binary 'testspectra-runner' not found.\n" +
33
+ "Please build the project first using 'pnpm run build' or 'pnpm run build:core'.");
27
34
  }
28
35
  static async run(options, reporter) {
29
36
  const binPath = this.resolveBinaryPath();
@@ -35,9 +42,15 @@ export class RustCoreBridge {
35
42
  testCases: options.testCases,
36
43
  config: options.config,
37
44
  targetDevice: options.targetDevice,
45
+ licenseKey: options.licenseKey,
46
+ outputJsonPath: options.outputJsonPath,
47
+ spec: options.spec,
48
+ concurrency: options.concurrency,
49
+ failedOnly: options.failedOnly,
50
+ failedTarget: options.failedTarget,
38
51
  });
39
- const home = process.env.HOME || process.env.USERPROFILE || "";
40
- const globalDriversDir = path.join(home, ".testspectra", "drivers");
52
+ const home = process.env.HOME || process.env.USERPROFILE || '';
53
+ const globalDriversDir = path.join(home, '.testspectra', 'drivers');
41
54
  if (!fs.existsSync(globalDriversDir)) {
42
55
  try {
43
56
  fs.mkdirSync(globalDriversDir, { recursive: true });
@@ -47,39 +60,37 @@ export class RustCoreBridge {
47
60
  const runnerEnv = {
48
61
  ...process.env,
49
62
  TEST_SPECTRA_DRIVER_CACHE: globalDriversDir,
50
- RUST_LOG: "info",
63
+ RUST_LOG: 'info',
51
64
  };
52
65
  return new Promise((resolve, reject) => {
53
- let child;
54
- if (binPath === "cargo") {
55
- const workspaceRoot = path.resolve(__dirname, "../../..");
56
- child = spawn("cargo", ["run", "--manifest-path", "core/test-runner/Cargo.toml", "--bin", "testspectra-runner", "--", payload], {
57
- cwd: workspaceRoot,
58
- env: runnerEnv,
59
- });
60
- }
61
- else {
62
- child = spawn(binPath, [payload], {
63
- env: runnerEnv,
64
- });
65
- }
66
- let runStatus = "failed";
67
- let runDuration = "0s";
68
- child.stdout.on("data", (data) => {
69
- const lines = data.toString().split("\n");
66
+ const child = spawn(binPath, [payload], {
67
+ env: runnerEnv,
68
+ });
69
+ let runStatus = 'failed';
70
+ let runDuration = '0s';
71
+ child.stdout.on('data', (data) => {
72
+ const lines = data.toString().split('\n');
70
73
  for (const line of lines) {
71
74
  if (!line.trim())
72
75
  continue;
76
+ if (line.startsWith('[TESTSPECTRA_')) {
77
+ reporter.addLog({
78
+ timestamp: new Date().toLocaleTimeString(),
79
+ level: 'INFO',
80
+ message: line,
81
+ });
82
+ continue;
83
+ }
73
84
  try {
74
85
  const parsed = JSON.parse(line);
75
- if (parsed.type === "log") {
86
+ if (parsed.type === 'log') {
76
87
  reporter.addLog({
77
88
  timestamp: parsed.timestamp,
78
89
  level: parsed.level,
79
90
  message: parsed.message,
80
91
  });
81
92
  }
82
- else if (parsed.type === "network") {
93
+ else if (parsed.type === 'network') {
83
94
  reporter.addNetwork({
84
95
  requestId: parsed.requestId,
85
96
  url: parsed.url,
@@ -87,14 +98,14 @@ export class RustCoreBridge {
87
98
  status: parsed.status,
88
99
  });
89
100
  }
90
- else if (parsed.type === "complete") {
101
+ else if (parsed.type === 'complete') {
91
102
  runStatus = parsed.status;
92
103
  runDuration = parsed.duration;
93
104
  }
94
- else if (parsed.type === "error") {
105
+ else if (parsed.type === 'error') {
95
106
  reporter.addLog({
96
107
  timestamp: new Date().toLocaleTimeString(),
97
- level: "ERROR",
108
+ level: 'ERROR',
98
109
  message: parsed.message,
99
110
  });
100
111
  }
@@ -105,29 +116,43 @@ export class RustCoreBridge {
105
116
  }
106
117
  }
107
118
  });
108
- child.stderr.on("data", (data) => {
119
+ child.stderr.on('data', (data) => {
109
120
  const str = data.toString();
110
121
  // Ignore normal compilation progress
111
- if (!str.includes("Compiling") && !str.includes("Checking") && !str.includes("Finished")) {
122
+ if (!str.includes('Compiling') && !str.includes('Checking') && !str.includes('Finished')) {
112
123
  reporter.addLog({
113
124
  timestamp: new Date().toLocaleTimeString(),
114
- level: "DEBUG",
125
+ level: 'DEBUG',
115
126
  message: str.trim(),
116
127
  });
117
128
  }
118
129
  });
119
- child.on("close", (code) => {
120
- const result = reporter.generateResult(code === 0 ? "passed" : runStatus, runDuration);
121
- if (options.outputJsonPath) {
130
+ child.on('close', (code) => {
131
+ let result = reporter.generateResult(code === 0 ? 'passed' : runStatus, runDuration);
132
+ if (options.outputJsonPath && fs.existsSync(options.outputJsonPath)) {
133
+ try {
134
+ const raw = fs.readFileSync(options.outputJsonPath, 'utf-8');
135
+ const parsed = JSON.parse(raw);
136
+ if (parsed.version && parsed.summary) {
137
+ result.status = parsed.status || result.status;
138
+ result.duration = parsed.duration || result.duration;
139
+ result.passedCount = parsed.summary.passed;
140
+ result.failedCount = parsed.summary.failed;
141
+ return resolve(result);
142
+ }
143
+ }
144
+ catch { }
145
+ }
146
+ else if (options.outputJsonPath) {
122
147
  const outDir = path.dirname(options.outputJsonPath);
123
148
  if (!fs.existsSync(outDir)) {
124
149
  fs.mkdirSync(outDir, { recursive: true });
125
150
  }
126
- fs.writeFileSync(options.outputJsonPath, JSON.stringify(result, null, 2), "utf-8");
151
+ fs.writeFileSync(options.outputJsonPath, JSON.stringify(result, null, 2), 'utf-8');
127
152
  }
128
153
  resolve(result);
129
154
  });
130
- child.on("error", (err) => {
155
+ child.on('error', (err) => {
131
156
  reject(err);
132
157
  });
133
158
  });
@@ -1,6 +1,6 @@
1
1
  export interface TestLog {
2
2
  timestamp: string;
3
- level: "INFO" | "SUCCESS" | "WARNING" | "ERROR" | "DEBUG" | string;
3
+ level: 'INFO' | 'SUCCESS' | 'WARNING' | 'ERROR' | 'DEBUG' | string;
4
4
  message: string;
5
5
  }
6
6
  export interface NetworkResource {
@@ -10,14 +10,42 @@ export interface NetworkResource {
10
10
  status: number;
11
11
  }
12
12
  export interface TestRunResult {
13
- status: "passed" | "failed" | "error" | string;
13
+ status: 'passed' | 'failed' | 'error' | string;
14
14
  duration: string;
15
+ passedCount: number;
16
+ failedCount: number;
15
17
  logs: TestLog[];
16
18
  networkResources?: NetworkResource[];
17
19
  }
20
+ export interface PendingStep {
21
+ text: string;
22
+ duration: number;
23
+ passed: boolean;
24
+ }
25
+ export interface PendingTestCase {
26
+ title: string;
27
+ workerId?: number;
28
+ steps: PendingStep[];
29
+ currentStep?: string;
30
+ errorMsg?: string;
31
+ startTime: number;
32
+ }
18
33
  export declare class Reporter {
19
34
  private logs;
20
35
  private networkEvents;
36
+ private pendingTests;
37
+ private workerToTest;
38
+ private passedCount;
39
+ private failedCount;
40
+ private isInteractive;
41
+ private lastRenderedLines;
42
+ private spinnerFrames;
43
+ private spinnerIndex;
44
+ private spinnerTimer;
45
+ constructor();
46
+ private clearActiveSlots;
47
+ private renderActiveSlots;
48
+ private resolveTestCase;
21
49
  addLog(log: TestLog): void;
22
50
  addNetwork(res: NetworkResource): void;
23
51
  getLogs(): TestLog[];