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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/README.md +50 -0
  2. package/bin/spectra.js +0 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/commands/__tests__/doctor-filter.test.d.ts +1 -0
  5. package/dist/commands/__tests__/doctor-filter.test.js +37 -0
  6. package/dist/commands/__tests__/run-e2e.test.js +49 -49
  7. package/dist/commands/__tests__/test-error-streaming.test.d.ts +1 -0
  8. package/dist/commands/__tests__/test-error-streaming.test.js +85 -0
  9. package/dist/commands/__tests__/test-scaffolding.test.d.ts +1 -0
  10. package/dist/commands/__tests__/test-scaffolding.test.js +32 -0
  11. package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
  12. package/dist/commands/__tests__/upgrade.test.js +95 -0
  13. package/dist/commands/add.js +5 -4
  14. package/dist/commands/devices.js +1 -0
  15. package/dist/commands/docs.d.ts +4 -0
  16. package/dist/commands/docs.js +17 -0
  17. package/dist/commands/doctor.d.ts +9 -1
  18. package/dist/commands/doctor.js +17 -3
  19. package/dist/commands/init.js +7 -160
  20. package/dist/commands/install.js +5 -0
  21. package/dist/commands/license.js +1 -0
  22. package/dist/commands/run.d.ts +2 -0
  23. package/dist/commands/run.js +22 -0
  24. package/dist/commands/test.d.ts +13 -0
  25. package/dist/commands/test.js +115 -21
  26. package/dist/commands/upgrade.d.ts +12 -0
  27. package/dist/commands/upgrade.js +197 -0
  28. package/dist/config/schema.d.ts +53 -0
  29. package/dist/config/schema.js +2 -0
  30. package/dist/generator/__tests__/tsconfig-isolation.test.js +2 -1
  31. package/dist/generator/__tests__/type-generator.test.js +38 -0
  32. package/dist/generator/architecture-doc-generator.d.ts +14 -0
  33. package/dist/generator/architecture-doc-generator.js +80 -0
  34. package/dist/generator/index.d.ts +1 -0
  35. package/dist/generator/index.js +1 -0
  36. package/dist/generator/tsconfig-generator.js +1 -1
  37. package/dist/generator/type-generator.d.ts +5 -2
  38. package/dist/generator/type-generator.js +14 -17
  39. package/dist/index.js +56 -26
  40. package/dist/linter/__tests__/component-test-imports.test.js +51 -51
  41. package/dist/linter/__tests__/shared-lib-boundary.test.js +15 -15
  42. package/dist/linter/__tests__/spec-schema.test.js +167 -167
  43. package/dist/linter/discovery.d.ts +3 -3
  44. package/dist/linter/discovery.js +7 -7
  45. package/dist/linter/schemas/spec.schema.d.ts +8 -8
  46. package/dist/linter/schemas/suite.schema.d.ts +4 -4
  47. package/dist/runner/bridge.d.ts +1 -1
  48. package/dist/runner/bridge.js +101 -11
  49. package/dist/runner/reporter.d.ts +1 -0
  50. package/dist/runner/reporter.js +12 -6
  51. package/dist/utils/__tests__/dev-server-manager.test.d.ts +1 -0
  52. package/dist/utils/__tests__/dev-server-manager.test.js +72 -0
  53. package/dist/utils/background-update-check.d.ts +1 -0
  54. package/dist/utils/background-update-check.js +43 -0
  55. package/dist/utils/dependency-updates.d.ts +15 -0
  56. package/dist/utils/dependency-updates.js +107 -0
  57. package/dist/utils/dev-server-manager.d.ts +40 -0
  58. package/dist/utils/dev-server-manager.js +257 -0
  59. package/dist/utils/update-notifier.d.ts +2 -0
  60. package/dist/utils/update-notifier.js +67 -0
  61. package/package.json +36 -30
  62. package/templates/default/CLAUDE.md +16 -16
  63. package/templates/default/global-hooks/after/mobile.hook.ts +3 -3
  64. package/templates/default/global-hooks/after/web.hook.ts +3 -3
  65. package/templates/default/global-hooks/before/mobile.hook.ts +3 -3
  66. package/templates/default/global-hooks/before/web.hook.ts +3 -3
  67. package/templates/default/package.json +2 -2
  68. package/templates/default/page-objects/AuthPage/mobile.ts +5 -5
  69. package/templates/default/page-objects/AuthPage/web.ts +10 -10
  70. package/templates/default/page-objects/MatchersPage/mobile.ts +70 -70
  71. package/templates/default/page-objects/MatchersPage/web.ts +43 -43
  72. package/templates/default/page-objects/NavigationPage/mobile.ts +14 -14
  73. package/templates/default/page-objects/NavigationPage/web.ts +8 -8
  74. package/templates/default/page-objects/PlaygroundPage/mobile.ts +42 -42
  75. package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
  76. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -53
  77. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -19
  78. package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -31
  79. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -3
  80. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -3
  81. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -27
  82. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -19
  83. package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -26
  84. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -30
  85. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -22
  86. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -27
  87. package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -20
  88. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -24
  89. package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -26
  90. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -37
  91. package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -21
  92. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -29
  93. package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -16
  94. package/templates/default/specs/ApiInterception/suite.md +10 -10
  95. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -30
  96. package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -23
  97. package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -16
  98. package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -28
  99. package/templates/default/specs/ApiSeeding/suite.md +9 -9
  100. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -7
  101. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -19
  102. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -9
  103. package/templates/default/specs/Authentication/suite.md +9 -9
  104. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +9 -9
  105. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +17 -17
  106. package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  107. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -17
  108. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -28
  109. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -26
  110. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -26
  111. package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -19
  112. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +11 -11
  113. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +17 -17
  114. package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  115. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -14
  116. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -21
  117. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -28
  118. package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -29
  119. package/templates/default/specs/DeepLink/suite.md +9 -9
  120. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +11 -11
  121. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +17 -17
  122. package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  123. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +29 -29
  124. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +19 -19
  125. package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  126. package/templates/default/specs/ElementMatchers/suite.md +10 -10
  127. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  128. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  129. package/templates/default/specs/EnvironmentConfig/suite.md +9 -9
  130. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -26
  131. package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -22
  132. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -13
  133. package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -20
  134. package/templates/default/specs/PermissionRationale/suite.md +9 -9
  135. package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +6 -6
  136. package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +18 -18
  137. package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  138. package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +6 -6
  139. package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +18 -18
  140. package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  141. package/templates/default/spectra.config.ts +52 -52
  142. package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -4
  143. package/templates/default/support/actions/fillCredentials/web.action.ts +4 -4
  144. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -9
  145. package/templates/default/support/actions/seedSession/web.action.ts +13 -13
  146. package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +5 -5
  147. package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  148. package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +6 -6
  149. package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  150. package/templates/docs/ARCHITECTURE.default.md +26 -0
  151. package/templates/docs/ARCHITECTURE.nx.md +119 -0
  152. package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm} +0 -0
  153. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  154. package/templates/nx/.nx/workspace-data/d/daemon.log +6343 -1612
  155. package/templates/nx/.nx/workspace-data/d/server-process.json +1 -1
  156. package/templates/nx/.nx/workspace-data/file-map.json +116 -130
  157. package/templates/nx/.nx/workspace-data/lockfile.hash +1 -1
  158. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  159. package/templates/nx/.nx/workspace-data/parsed-lock-file.json +504 -504
  160. package/templates/nx/.nx/workspace-data/project-graph.json +560 -559
  161. package/templates/nx/.nx/workspace-data/source-maps.json +336 -0
  162. package/templates/nx/CLAUDE.md +10 -10
  163. package/templates/nx/package.json +3 -3
  164. package/templates/nx/packages/matchers/e2e/project.json +2 -2
  165. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -27
  166. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -27
  167. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -24
  168. package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -24
  169. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -3
  170. package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
  171. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -3
  172. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +17 -17
  173. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -3
  174. package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
  175. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -3
  176. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
  177. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -3
  178. package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
  179. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
  180. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
  181. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -9
  182. package/templates/nx/packages/playground/e2e/project.json +2 -2
  183. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -3
  184. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
  185. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -3
  186. package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
  187. package/templates/nx/pnpm-lock.yaml +6663 -6663
  188. package/templates/nx/pnpm-workspace.yaml +9 -9
  189. package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +33 -33
  190. package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +91 -91
  191. package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -5
  192. package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -8
  193. package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -26
  194. package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +31 -31
  195. package/templates/nx/shared/testing/project.json +18 -19
  196. package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -4
  197. package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -4
  198. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -3
  199. package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -5
  200. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -4
  201. package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -6
  202. package/templates/nx/spectra.config.ts +65 -52
  203. package/templates/react-component/.cursorrules +17 -17
  204. package/templates/react-component/.vscode/extensions.json +5 -5
  205. package/templates/react-component/CLAUDE.md +14 -14
  206. package/templates/react-component/README.md +69 -69
  207. package/templates/react-component/fixtures/component-mock.json +12 -12
  208. package/templates/react-component/global-hooks/after/web.hook.ts +3 -3
  209. package/templates/react-component/global-hooks/before/web.hook.ts +3 -3
  210. package/templates/react-component/package.json +27 -27
  211. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -23
  212. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -23
  213. package/templates/react-component/page-objects/InputComponent/web.ts +13 -13
  214. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -18
  215. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -11
  216. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -18
  217. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -10
  218. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -18
  219. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -16
  220. package/templates/react-component/specs/BadgeComponent/suite.md +12 -12
  221. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -19
  222. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +25 -28
  223. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -19
  224. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +13 -22
  225. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -18
  226. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -8
  227. package/templates/react-component/specs/ButtonComponent/suite.md +12 -12
  228. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -20
  229. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +24 -28
  230. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -18
  231. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -9
  232. package/templates/react-component/specs/InputComponent/suite.md +12 -12
  233. package/templates/react-component/spectra.config.ts +17 -27
  234. package/templates/react-component/src/components/Badge/Badge.tsx +60 -60
  235. package/templates/react-component/src/components/Button/Button.tsx +59 -57
  236. package/templates/react-component/src/components/Input/Input.tsx +40 -41
  237. package/templates/react-component/src/test-utils.tsx +23 -23
  238. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -9
  239. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -12
  240. package/bin/.testspectra-runner.hash +0 -1
  241. package/bin/testspectra-runner.exe +0 -0
  242. package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal +0 -0
  243. /package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db} +0 -0
@@ -0,0 +1,257 @@
1
+ import net from 'node:net';
2
+ import http from 'node:http';
3
+ import https from 'node:https';
4
+ import path from 'node:path';
5
+ import spawn from 'cross-spawn';
6
+ export class DevServerManager {
7
+ spawnedProcesses = new Map();
8
+ cleanupRegistered = false;
9
+ constructor() {
10
+ this.registerExitHooks();
11
+ }
12
+ /**
13
+ * Safely splits a shell command string into binary executable and argument array without shell expansion.
14
+ */
15
+ static parseCommand(command) {
16
+ const trimmed = command.trim();
17
+ if (!trimmed) {
18
+ throw new Error('Dev server command cannot be empty');
19
+ }
20
+ const tokens = [];
21
+ let current = '';
22
+ let inQuotes = false;
23
+ let quoteChar = '';
24
+ for (let i = 0; i < trimmed.length; i++) {
25
+ const char = trimmed[i];
26
+ if ((char === '"' || char === "'") && (i === 0 || trimmed[i - 1] !== '\\')) {
27
+ if (inQuotes && char === quoteChar) {
28
+ inQuotes = false;
29
+ quoteChar = '';
30
+ }
31
+ else if (!inQuotes) {
32
+ inQuotes = true;
33
+ quoteChar = char;
34
+ }
35
+ else {
36
+ current += char;
37
+ }
38
+ }
39
+ else if (/\s/.test(char) && !inQuotes) {
40
+ if (current.length > 0) {
41
+ tokens.push(current);
42
+ current = '';
43
+ }
44
+ }
45
+ else {
46
+ current += char;
47
+ }
48
+ }
49
+ if (current.length > 0) {
50
+ tokens.push(current);
51
+ }
52
+ const bin = tokens[0];
53
+ const args = tokens.slice(1);
54
+ return { bin, args };
55
+ }
56
+ /**
57
+ * Probes a URL to verify if the server is actively listening and responsive.
58
+ * Performs a rapid TCP connection test followed by an HTTP/HTTPS request.
59
+ */
60
+ static async probeUrl(targetUrl, timeoutMs = 1500) {
61
+ try {
62
+ const parsed = new URL(targetUrl);
63
+ const isHttps = parsed.protocol === 'https:';
64
+ const port = parsed.port ? parseInt(parsed.port, 10) : isHttps ? 443 : 80;
65
+ const hostname = parsed.hostname;
66
+ // 1. TCP Probe (Fast check)
67
+ const tcpReachable = await new Promise((resolve) => {
68
+ const socket = net.createConnection({ host: hostname, port, timeout: timeoutMs }, () => {
69
+ socket.destroy();
70
+ resolve(true);
71
+ });
72
+ socket.on('error', () => {
73
+ socket.destroy();
74
+ resolve(false);
75
+ });
76
+ socket.on('timeout', () => {
77
+ socket.destroy();
78
+ resolve(false);
79
+ });
80
+ });
81
+ if (!tcpReachable) {
82
+ return false;
83
+ }
84
+ // 2. HTTP/HTTPS Probe (Verify application readiness, bypass self-signed certs)
85
+ return await new Promise((resolve) => {
86
+ const transport = isHttps ? https : http;
87
+ const req = transport.request(targetUrl, {
88
+ method: 'HEAD',
89
+ timeout: timeoutMs,
90
+ rejectUnauthorized: false,
91
+ }, (res) => {
92
+ // Any valid HTTP response means server is alive and accepting traffic
93
+ res.resume();
94
+ resolve(true);
95
+ });
96
+ req.on('error', () => resolve(false));
97
+ req.on('timeout', () => {
98
+ req.destroy();
99
+ resolve(false);
100
+ });
101
+ req.end();
102
+ });
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ }
108
+ /**
109
+ * Ensures an individual dev server is running. If already responding and reuseExistingServer !== false,
110
+ * reuses the instance. Otherwise, spawns the process and polls until ready.
111
+ */
112
+ async ensureServer(config, rootCwd = process.cwd()) {
113
+ const serverName = config.name || config.url;
114
+ const timeoutMs = config.timeout ?? 60000;
115
+ const reuse = config.reuseExistingServer !== false;
116
+ // Check if server is already running
117
+ if (reuse) {
118
+ const isRunning = await DevServerManager.probeUrl(config.url, 1000);
119
+ if (isRunning) {
120
+ console.log(`\x1b[36m[TestSpectra DevServer]\x1b[0m ⚡ Reusing active server \x1b[1m"${serverName}"\x1b[0m at \x1b[33m${config.url}\x1b[0m`);
121
+ return {
122
+ name: serverName,
123
+ url: config.url,
124
+ reused: true,
125
+ };
126
+ }
127
+ }
128
+ console.log(`\x1b[36m[TestSpectra DevServer]\x1b[0m 🚀 Launching dev server \x1b[1m"${serverName}"\x1b[0m (\x1b[90m${config.command}\x1b[0m)...`);
129
+ const { bin, args } = DevServerManager.parseCommand(config.command);
130
+ const execCwd = config.cwd ? path.resolve(rootCwd, config.cwd) : rootCwd;
131
+ const logBuffer = [];
132
+ const pushLog = (data) => {
133
+ const lines = data.toString().split('\n');
134
+ for (const line of lines) {
135
+ if (line.trim()) {
136
+ logBuffer.push(line);
137
+ if (logBuffer.length > 50)
138
+ logBuffer.shift();
139
+ }
140
+ }
141
+ };
142
+ const child = spawn(bin, args, {
143
+ cwd: execCwd,
144
+ env: { ...process.env, ...config.env },
145
+ stdio: ['ignore', 'pipe', 'pipe'],
146
+ detached: process.platform !== 'win32',
147
+ });
148
+ child.stdout?.on('data', pushLog);
149
+ child.stderr?.on('data', pushLog);
150
+ this.spawnedProcesses.set(serverName, {
151
+ child,
152
+ name: serverName,
153
+ url: config.url,
154
+ logBuffer,
155
+ });
156
+ const startTime = Date.now();
157
+ let isReady = false;
158
+ while (Date.now() - startTime < timeoutMs) {
159
+ if (child.exitCode !== null) {
160
+ const errorLogs = logBuffer.slice(-20).join('\n');
161
+ throw new Error(`Dev server "${serverName}" terminated prematurely with exit code ${child.exitCode}.\n` +
162
+ `Last output:\n${errorLogs || '(no output)'}`);
163
+ }
164
+ isReady = await DevServerManager.probeUrl(config.url, 500);
165
+ if (isReady) {
166
+ const durationSec = ((Date.now() - startTime) / 1000).toFixed(1);
167
+ console.log(`\x1b[32m[TestSpectra DevServer] ✓ Server "${serverName}" ready at ${config.url} (${durationSec}s)\x1b[0m`);
168
+ return {
169
+ name: serverName,
170
+ url: config.url,
171
+ process: child,
172
+ reused: false,
173
+ };
174
+ }
175
+ await new Promise((r) => setTimeout(r, 200));
176
+ }
177
+ // Timeout exceeded — dump logs and kill child
178
+ this.killProcess(child);
179
+ this.spawnedProcesses.delete(serverName);
180
+ const errorLogs = logBuffer.slice(-25).join('\n');
181
+ throw new Error(`Dev server "${serverName}" timed out after ${timeoutMs}ms waiting for ${config.url}.\n` +
182
+ `Last output:\n${errorLogs || '(no output)'}`);
183
+ }
184
+ /**
185
+ * Starts all matching dev servers in parallel.
186
+ */
187
+ async startAll(configs = [], targetHostFilter, rootCwd = process.cwd()) {
188
+ if (!configs || configs.length === 0) {
189
+ return [];
190
+ }
191
+ let targetConfigs = configs;
192
+ if (targetHostFilter) {
193
+ const matched = configs.filter((c) => c.name === targetHostFilter);
194
+ if (matched.length > 0) {
195
+ targetConfigs = matched;
196
+ }
197
+ }
198
+ return Promise.all(targetConfigs.map((cfg) => this.ensureServer(cfg, rootCwd)));
199
+ }
200
+ /**
201
+ * Safely terminates all spawned dev server child processes.
202
+ */
203
+ async stopAll() {
204
+ if (this.spawnedProcesses.size === 0)
205
+ return;
206
+ for (const entry of this.spawnedProcesses.values()) {
207
+ try {
208
+ this.killProcess(entry.child);
209
+ }
210
+ catch { }
211
+ }
212
+ this.spawnedProcesses.clear();
213
+ }
214
+ killProcess(child) {
215
+ if (child.pid && !child.killed) {
216
+ if (process.platform !== 'win32') {
217
+ try {
218
+ process.kill(-child.pid, 'SIGTERM');
219
+ }
220
+ catch {
221
+ try {
222
+ child.kill('SIGTERM');
223
+ }
224
+ catch { }
225
+ }
226
+ }
227
+ else {
228
+ try {
229
+ child.kill('SIGTERM');
230
+ }
231
+ catch { }
232
+ }
233
+ }
234
+ }
235
+ registerExitHooks() {
236
+ if (this.cleanupRegistered)
237
+ return;
238
+ this.cleanupRegistered = true;
239
+ const cleanup = () => {
240
+ for (const entry of this.spawnedProcesses.values()) {
241
+ try {
242
+ this.killProcess(entry.child);
243
+ }
244
+ catch { }
245
+ }
246
+ };
247
+ process.once('exit', cleanup);
248
+ process.once('SIGINT', () => {
249
+ cleanup();
250
+ process.exit(130);
251
+ });
252
+ process.once('SIGTERM', () => {
253
+ cleanup();
254
+ process.exit(143);
255
+ });
256
+ }
257
+ }
@@ -0,0 +1,2 @@
1
+ export declare function checkUpdateInBackground(): void;
2
+ export declare function displayUpdateNotificationIfNeeded(): void;
@@ -0,0 +1,67 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { fileURLToPath } from 'url';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
8
+ export function checkUpdateInBackground() {
9
+ const home = process.env.HOME || process.env.USERPROFILE || '';
10
+ const spectraDir = path.join(home, '.testspectra');
11
+ const cachePath = path.join(spectraDir, 'update-cache.json');
12
+ try {
13
+ if (!fs.existsSync(spectraDir)) {
14
+ fs.mkdirSync(spectraDir, { recursive: true });
15
+ }
16
+ if (fs.existsSync(cachePath)) {
17
+ const stats = fs.statSync(cachePath);
18
+ const now = Date.now();
19
+ if (now - stats.mtimeMs < CACHE_TTL_MS) {
20
+ // Cache is still valid, don't spawn background check
21
+ return;
22
+ }
23
+ }
24
+ }
25
+ catch {
26
+ // Ignore fs errors
27
+ }
28
+ // Spawn background check
29
+ const checkerScript = path.join(__dirname, 'background-update-check.js');
30
+ if (fs.existsSync(checkerScript)) {
31
+ const child = spawn(process.execPath, [checkerScript, cachePath], {
32
+ detached: true,
33
+ stdio: 'ignore',
34
+ windowsHide: true,
35
+ });
36
+ child.unref();
37
+ }
38
+ }
39
+ export function displayUpdateNotificationIfNeeded() {
40
+ const home = process.env.HOME || process.env.USERPROFILE || '';
41
+ const cachePath = path.join(home, '.testspectra', 'update-cache.json');
42
+ if (!fs.existsSync(cachePath))
43
+ return;
44
+ try {
45
+ const cache = JSON.parse(fs.readFileSync(cachePath, 'utf-8'));
46
+ if (cache.updatesAvailable && cache.updatesAvailable.length > 0) {
47
+ console.log('\n\x1b[33m╭─────────────────────────────────────────────────────────────────╮\x1b[0m');
48
+ console.log('\x1b[33m│\x1b[0m \x1b[33m│\x1b[0m');
49
+ const pkg = cache.updatesAvailable[0];
50
+ const msg = `Update available: ${pkg.pkg} ${pkg.current ?? '0.0.0'} → ${pkg.latest}`;
51
+ const padding = Math.max(0, 63 - msg.length);
52
+ const padLeft = Math.floor(padding / 2);
53
+ const padRight = padding - padLeft;
54
+ console.log(`\x1b[33m│\x1b[0m${' '.repeat(padLeft)}\x1b[37m${msg}\x1b[0m${' '.repeat(padRight)}\x1b[33m│\x1b[0m`);
55
+ const cmdMsg = 'Run `spectra upgrade` to update CLI, Matchers, and Drivers';
56
+ const cmdPadding = Math.max(0, 63 - cmdMsg.length);
57
+ const cmdPadLeft = Math.floor(cmdPadding / 2);
58
+ const cmdPadRight = cmdPadding - cmdPadLeft;
59
+ console.log(`\x1b[33m│\x1b[0m${' '.repeat(cmdPadLeft)}\x1b[36m${cmdMsg}\x1b[0m${' '.repeat(cmdPadRight)}\x1b[33m│\x1b[0m`);
60
+ console.log('\x1b[33m│\x1b[0m \x1b[33m│\x1b[0m');
61
+ console.log('\x1b[33m╰─────────────────────────────────────────────────────────────────╯\x1b[0m\n');
62
+ }
63
+ }
64
+ catch {
65
+ // Ignore errors reading cache
66
+ }
67
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@testspectra/cli",
3
- "version": "1.1.8-rc.3",
4
- "description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
3
+ "version": "1.1.8-rc.31",
4
+ "description": "TestSpectra Cross-Platform Test Runner CLI",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
@@ -16,15 +16,6 @@
16
16
  "spectra": "./bin/spectra.js",
17
17
  "testspectra": "./bin/spectra.js"
18
18
  },
19
- "scripts": {
20
- "build:rust": "node scripts/ensure-runner-binary.js",
21
- "prebuild": "node scripts/ensure-runner-binary.js",
22
- "build": "tsc && node scripts/copy-demo.js",
23
- "watch": "tsc -w",
24
- "test": "vitest run",
25
- "test:watch": "vitest",
26
- "prepublishOnly": "npm run build"
27
- },
28
19
  "files": [
29
20
  "dist",
30
21
  "bin",
@@ -34,26 +25,41 @@
34
25
  "LICENSE.md"
35
26
  ],
36
27
  "dependencies": {
37
- "@clack/prompts": "catalog:",
38
- "@testspectra/matchers": "^1.1.8-rc.3",
39
- "@testspectra/react": "^1.1.8-rc.3",
40
- "@testspectra/skills": "^1.1.8-rc.3",
41
- "chalk": "catalog:",
42
- "commander": "catalog:",
43
- "dotenv": "catalog:",
44
- "ora": "catalog:",
45
- "typescript": "catalog:",
46
- "yaml": "catalog:",
47
- "zod": "catalog:"
28
+ "@clack/prompts": "^1.7.0",
29
+ "@testspectra/matchers": "^1.1.8-rc.31",
30
+ "@testspectra/react": "^1.1.8-rc.31",
31
+ "@testspectra/skills": "^1.1.8-rc.31",
32
+ "chalk": "^5.3.0",
33
+ "commander": "^12.1.0",
34
+ "cross-spawn": "^7.0.6",
35
+ "dotenv": "^16.4.5",
36
+ "ora": "^8.0.1",
37
+ "typescript": "^5.6.3",
38
+ "yaml": "^2.7.0",
39
+ "zod": "^3.23.8"
40
+ },
41
+ "optionalDependencies": {
42
+ "@testspectra/cli-darwin-arm64": "1.1.8-rc.31",
43
+ "@testspectra/cli-linux-x64": "1.1.8-rc.31",
44
+ "@testspectra/cli-win32-x64": "1.1.8-rc.31"
48
45
  },
49
46
  "devDependencies": {
50
- "@types/node": "catalog:",
51
- "@wdio/globals": "catalog:",
52
- "@wdio/mocha-framework": "catalog:",
53
- "@wdio/types": "catalog:",
54
- "esbuild": "catalog:",
55
- "vitest": "catalog:"
47
+ "@types/cross-spawn": "^6.0.6",
48
+ "@types/node": "^20.14.0",
49
+ "@wdio/globals": "^9.24.0",
50
+ "@wdio/mocha-framework": "^9.24.0",
51
+ "@wdio/types": "^9.24.0",
52
+ "esbuild": "^0.28.2",
53
+ "vitest": "^2.1.8"
56
54
  },
57
55
  "type": "module",
58
- "license": "SEE LICENSE IN LICENSE.md"
59
- }
56
+ "license": "SEE LICENSE IN LICENSE.md",
57
+ "scripts": {
58
+ "build:rust": "node scripts/ensure-runner-binary.js",
59
+ "prebuild": "node scripts/ensure-runner-binary.js",
60
+ "build": "tsc && node scripts/copy-demo.js",
61
+ "watch": "tsc -w",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest"
64
+ }
65
+ }
@@ -1,16 +1,16 @@
1
- # CLAUDE.md - TestSpectra Framework Conventions
2
-
3
- ## Commands
4
- - Run all Web tests: `pnpm test:web` (or `spectra run -t web`)
5
- - Run all Mobile tests: `pnpm test:mobile` (or `spectra run -t mobile`)
6
- - Run Android tests: `pnpm test:android` (or `spectra run -t android`)
7
- - Run iOS tests: `pnpm test:ios` (or `spectra run -t ios`)
8
- - Run specific spec: `spectra run "specs/Playground/TC-0001-form-controls/mobile.test.ts" -t mobile`
9
- - Type checking: `pnpm type-check` (or `tsc -b`)
10
- - Preflight doctor: `pnpm doctor` (or `spectra doctor`)
11
- - Sync ambient types: `pnpm sync-types`
12
-
13
- ## Element Selectors
14
- - `~name` → **accessibility id** (Android `content-desc`, RN `accessibilityLabel`, web `aria-label`) — the portable default.
15
- - `#name` → **native id** (Android `resource-id`, RN `testID`, web CSS `id`).
16
- - Each prefix maps to exactly one attribute (no silent cross-matching). Prefer `~` for user-perceivable elements, `#` for stable test ids. See `docs/v2/cli/selectors.md`.
1
+ # CLAUDE.md - TestSpectra Framework Conventions
2
+
3
+ ## Commands
4
+ - Run all Web tests: `pnpm test:web` (or `spectra run -t web`)
5
+ - Run all Mobile tests: `pnpm test:mobile` (or `spectra run -t mobile`)
6
+ - Run Android tests: `pnpm test:android` (or `spectra run -t android`)
7
+ - Run iOS tests: `pnpm test:ios` (or `spectra run -t ios`)
8
+ - Run specific spec: `spectra run "specs/Playground/TC-0001-form-controls/mobile.test.ts" -t mobile`
9
+ - Type checking: `pnpm type-check` (or `tsc -b`)
10
+ - Preflight doctor: `pnpm doctor` (or `spectra doctor`)
11
+ - Sync ambient types: `pnpm sync-types`
12
+
13
+ ## Element Selectors
14
+ - `~name` → **accessibility id** (Android `content-desc`, RN `accessibilityLabel`, web `aria-label`) — the portable default.
15
+ - `#name` → **native id** (Android `resource-id`, RN `testID`, web CSS `id`).
16
+ - Each prefix maps to exactly one attribute (no silent cross-matching). Prefer `~` for user-perceivable elements, `#` for stable test ids. See `docs/v2/cli/selectors.md`.
@@ -1,3 +1,3 @@
1
- export default async function () {
2
- // Global mobile teardown
3
- }
1
+ export default async function () {
2
+ // Global mobile teardown
3
+ }
@@ -1,3 +1,3 @@
1
- export default async function () {
2
- // Global web teardown
3
- }
1
+ export default async function () {
2
+ // Global web teardown
3
+ }
@@ -1,3 +1,3 @@
1
- export default async function () {
2
- // Global mobile initialization
3
- }
1
+ export default async function () {
2
+ // Global mobile initialization
3
+ }
@@ -1,3 +1,3 @@
1
- export default async function () {
2
- // Global web initialization
3
- }
1
+ export default async function () {
2
+ // Global web initialization
3
+ }
@@ -17,8 +17,8 @@
17
17
  "postinstall": "spectra sync-types"
18
18
  },
19
19
  "devDependencies": {
20
- "@testspectra/cli": "^1.1.8-rc.3",
21
- "@testspectra/matchers": "^1.1.8-rc.3",
20
+ "@testspectra/cli": "^1.1.8-rc.31",
21
+ "@testspectra/matchers": "^1.1.8-rc.31",
22
22
  "@types/node": "^20.14.0",
23
23
  "@wdio/cli": "^9.2.8",
24
24
  "@wdio/local-runner": "^9.2.8",
@@ -1,5 +1,5 @@
1
- class AuthPage {
2
- get statusBadge() { return Spectra.get("~auth-status-badge"); }
3
- }
4
-
5
- export default new AuthPage();
1
+ class AuthPage {
2
+ get statusBadge() { return Spectra.get("~auth-status-badge"); }
3
+ }
4
+
5
+ export default new AuthPage();
@@ -1,10 +1,10 @@
1
- class AuthPage {
2
- get checkStatusButton() { return Spectra.get("#check-auth-status-btn"); }
3
- get statusBadge() { return Spectra.get("#auth-status-badge"); }
4
-
5
- async open() {
6
- await Spectra.navigate("/#auth");
7
- }
8
- }
9
-
10
- export default new AuthPage();
1
+ class AuthPage {
2
+ get checkStatusButton() { return Spectra.get("#check-auth-status-btn"); }
3
+ get statusBadge() { return Spectra.get("#auth-status-badge"); }
4
+
5
+ async open() {
6
+ await Spectra.navigate("/#auth");
7
+ }
8
+ }
9
+
10
+ export default new AuthPage();