@testspectra/cli 1.1.0-rc.0 → 1.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/bin/.testspectra-runner.hash +1 -0
  2. package/bin/testspectra-runner +0 -0
  3. package/demo-app/assets/index-DOtRypCa.js +154 -0
  4. package/demo-app/index.html +1 -1
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/commands/__tests__/run.test.js +9 -13
  7. package/dist/commands/init.d.ts +1 -1
  8. package/dist/commands/init.js +6 -1
  9. package/dist/commands/run.js +1 -0
  10. package/dist/commands/sync-types.js +1 -0
  11. package/dist/commands/test.d.ts +17 -0
  12. package/dist/commands/test.js +76 -0
  13. package/dist/commands/watch.js +9 -2
  14. package/dist/config/loader.js +1 -1
  15. package/dist/config/schema.d.ts +5 -109
  16. package/dist/config/schema.js +1 -16
  17. package/dist/generator/__tests__/tsconfig-isolation.test.js +6 -2
  18. package/dist/generator/tsconfig-generator.js +42 -7
  19. package/dist/generator/type-generator.d.ts +24 -1
  20. package/dist/generator/type-generator.js +104 -8
  21. package/dist/index.js +15 -1
  22. package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
  23. package/dist/linter/__tests__/component-test-imports.test.js +115 -0
  24. package/dist/linter/extractor.d.ts +1 -0
  25. package/dist/linter/extractor.js +11 -1
  26. package/dist/linter/schemas/spec.schema.d.ts +12 -12
  27. package/dist/linter/schemas/suite.schema.d.ts +8 -8
  28. package/dist/linter/spec-linter.js +38 -3
  29. package/dist/reporter/semantic-formatter.js +17 -2
  30. package/dist/reporter/types.d.ts +1 -1
  31. package/dist/runner/reporter.js +7 -3
  32. package/dist/utils/api-server.js +46 -1
  33. package/package.json +4 -3
  34. package/templates/default/package.json +2 -2
  35. package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
  36. package/templates/default/page-objects/AuthPage/web.ts +10 -0
  37. package/templates/default/page-objects/MatchersPage/mobile.ts +13 -0
  38. package/templates/default/page-objects/MatchersPage/web.ts +5 -0
  39. package/templates/default/page-objects/PlaygroundPage/mobile.ts +6 -0
  40. package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +6 -9
  41. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
  42. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
  43. package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
  44. package/templates/default/specs/Authentication/suite.md +9 -0
  45. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
  46. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
  47. package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
  48. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
  49. package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
  50. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  51. package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  52. package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
  53. package/templates/default/spectra.config.ts +1 -16
  54. package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
  55. package/templates/default/support/actions/seedSession/web.action.ts +13 -0
  56. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
  57. package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
  58. package/templates/nx/.nx/workspace-data/d/daemon.log +1060 -0
  59. package/templates/nx/.nx/workspace-data/d/server-process.json +1 -1
  60. package/templates/nx/.nx/workspace-data/file-map.json +116 -104
  61. package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
  62. package/templates/nx/package.json +2 -2
  63. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
  64. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
  65. package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
  66. package/templates/nx/spectra.config.ts +1 -16
  67. package/templates/react-component/.cursorrules +17 -0
  68. package/templates/react-component/.vscode/extensions.json +5 -0
  69. package/templates/react-component/CLAUDE.md +14 -0
  70. package/templates/react-component/README.md +69 -0
  71. package/templates/react-component/fixtures/component-mock.json +12 -0
  72. package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
  73. package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
  74. package/templates/react-component/package.json +27 -0
  75. package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
  76. package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
  77. package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
  78. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
  79. package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
  80. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
  81. package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
  82. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
  83. package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
  84. package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
  85. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
  86. package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
  87. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
  88. package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
  89. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
  90. package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
  91. package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
  92. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
  93. package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
  94. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
  95. package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
  96. package/templates/react-component/specs/InputComponent/suite.md +12 -0
  97. package/templates/react-component/spectra.config.ts +27 -0
  98. package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
  99. package/templates/react-component/src/components/Button/Button.tsx +57 -0
  100. package/templates/react-component/src/components/Input/Input.tsx +41 -0
  101. package/templates/react-component/src/test-utils.tsx +23 -0
  102. package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
  103. package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
  104. package/templates/react-component/tsconfig.json +8 -0
  105. package/demo-app/assets/index-BGllUp7o.js +0 -140
@@ -6,19 +6,17 @@ import { TypeGenerator } from '../../types/generator.js';
6
6
  describe('runCommand Integration & Dynamic Worker Sizing', () => {
7
7
  it('passes calculated dynamic worker count to RustCoreBridge payload', async () => {
8
8
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
9
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
9
10
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
10
11
  webConfig: {
11
12
  baseUrl: 'http://localhost:3000',
12
13
  maxConcurrentSessions: '1',
13
14
  headless: true,
14
15
  implicitWait: '5000',
15
- pageLoadTimeout: '30000',
16
- scriptTimeout: '30000',
17
16
  parallelizationMode: 'suite',
18
17
  },
19
18
  androidConfig: {},
20
19
  iosConfig: {},
21
- browsers: [],
22
20
  executionConfig: {},
23
21
  });
24
22
  let bridgeOptionsReceived = null;
@@ -39,19 +37,17 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
39
37
  });
40
38
  it('respects user-specified --concurrency option and passes it to RustCoreBridge', async () => {
41
39
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
40
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
42
41
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
43
42
  webConfig: {
44
43
  baseUrl: 'http://localhost:3000',
45
44
  maxConcurrentSessions: '1',
46
45
  headless: true,
47
46
  implicitWait: '5000',
48
- pageLoadTimeout: '30000',
49
- scriptTimeout: '30000',
50
47
  parallelizationMode: 'suite',
51
48
  },
52
49
  androidConfig: {},
53
50
  iosConfig: {},
54
- browsers: [],
55
51
  executionConfig: {},
56
52
  });
57
53
  let bridgeOptionsReceived = null;
@@ -71,6 +67,7 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
71
67
  });
72
68
  it('correctly serializes target android and devices options into RustCoreBridge payload', async () => {
73
69
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
70
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
74
71
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
75
72
  webConfig: {
76
73
  baseUrl: 'http://localhost:3000',
@@ -81,7 +78,6 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
81
78
  devices: [{ id: 'emulator-5554' }],
82
79
  },
83
80
  iosConfig: {},
84
- browsers: [],
85
81
  executionConfig: {},
86
82
  });
87
83
  let bridgeOptionsReceived = null;
@@ -109,11 +105,11 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
109
105
  });
110
106
  it('correctly serializes single --device option into devices array of objects', async () => {
111
107
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
108
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
112
109
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
113
110
  webConfig: { baseUrl: 'http://localhost:3000' },
114
111
  androidConfig: { driverServer: 'http://127.0.0.1:4800' },
115
112
  iosConfig: {},
116
- browsers: [],
117
113
  executionConfig: {},
118
114
  });
119
115
  let bridgeOptionsReceived = null;
@@ -138,11 +134,11 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
138
134
  });
139
135
  it('correctly serializes --all-devices flag into auto devices in config', async () => {
140
136
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
137
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
141
138
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
142
139
  webConfig: { baseUrl: 'http://localhost:3000' },
143
140
  androidConfig: { driverServer: 'http://127.0.0.1:4800' },
144
141
  iosConfig: {},
145
- browsers: [],
146
142
  executionConfig: {},
147
143
  });
148
144
  let bridgeOptionsReceived = null;
@@ -167,11 +163,11 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
167
163
  });
168
164
  it('correctly serializes --multi-device flag into auto devices in config', async () => {
169
165
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
166
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
170
167
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
171
168
  webConfig: { baseUrl: 'http://localhost:3000' },
172
169
  androidConfig: { driverServer: 'http://127.0.0.1:4800' },
173
170
  iosConfig: {},
174
- browsers: [],
175
171
  executionConfig: {},
176
172
  });
177
173
  let bridgeOptionsReceived = null;
@@ -196,11 +192,11 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
196
192
  });
197
193
  it('sets process.exitCode to 1 when tests fail', async () => {
198
194
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
195
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
199
196
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
200
197
  webConfig: { baseUrl: 'http://localhost:3000' },
201
198
  androidConfig: {},
202
199
  iosConfig: {},
203
- browsers: [],
204
200
  executionConfig: {},
205
201
  });
206
202
  vi.spyOn(RustCoreBridge, 'run').mockImplementation(async () => {
@@ -220,11 +216,11 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
220
216
  });
221
217
  it('incorporates TESTSPECTRA_CARRIED_OVER into summary card calculation', async () => {
222
218
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
219
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
223
220
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
224
221
  webConfig: { baseUrl: 'http://localhost:3000' },
225
222
  androidConfig: {},
226
223
  iosConfig: {},
227
- browsers: [],
228
224
  executionConfig: {},
229
225
  });
230
226
  vi.spyOn(RustCoreBridge, 'run').mockImplementation(async () => {
@@ -253,11 +249,11 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
253
249
  });
254
250
  it('correctly passes multiple specs to RustCoreBridge payload', async () => {
255
251
  vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
252
+ vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
256
253
  vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
257
254
  webConfig: { baseUrl: 'http://localhost:3000' },
258
255
  androidConfig: {},
259
256
  iosConfig: {},
260
- browsers: [],
261
257
  executionConfig: {},
262
258
  });
263
259
  let bridgeOptionsReceived = null;
@@ -1,5 +1,5 @@
1
1
  export interface InitCommandOptions {
2
2
  force?: boolean;
3
- template?: 'default' | 'nx';
3
+ template?: 'default' | 'nx' | 'react-component';
4
4
  }
5
5
  export declare function initCommand(options?: InitCommandOptions): Promise<void>;
@@ -215,10 +215,15 @@ export async function initCommand(options = {}) {
215
215
  hint: 'Interactive workspace feature discovery & centralized config',
216
216
  },
217
217
  {
218
- label: 'Standard Project',
218
+ label: 'Standard E2E Project',
219
219
  value: 'default',
220
220
  hint: 'Standalone single-suite E2E testing project',
221
221
  },
222
+ {
223
+ label: 'React Component Testing',
224
+ value: 'react-component',
225
+ hint: 'Real-browser React component testing with Vite & Testing Library',
226
+ },
222
227
  ],
223
228
  });
224
229
  if (p.isCancel(templateChoice)) {
@@ -125,6 +125,7 @@ export async function runCommand(specPaths, options = {}) {
125
125
  const effectiveSpecPaths = specPaths && specPaths.length > 0 ? specPaths : options.spec ? [options.spec] : undefined;
126
126
  // Ensure latest ambient types are up-to-date
127
127
  TypeGenerator.writeDeclarationFiles(cwd);
128
+ await TypeGenerator.syncEnvDeclaration(cwd);
128
129
  const home = process.env.HOME || process.env.USERPROFILE || '';
129
130
  const globalAppDataPath = path.join(home, '.testspectra');
130
131
  const appDataPath = options.workdir ? path.resolve(options.workdir) : globalAppDataPath;
@@ -5,6 +5,7 @@ export async function syncTypesCommand(options = {}) {
5
5
  const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
6
6
  try {
7
7
  TypeGenerator.generateAll(cwd);
8
+ await TypeGenerator.syncEnvDeclaration(cwd);
8
9
  console.log(chalk.green('✔ Successfully synchronized TestSpectra ambient type declarations in .testspectra/types/'));
9
10
  }
10
11
  catch (err) {
@@ -0,0 +1,17 @@
1
+ export interface TestCommandOptions {
2
+ cwd?: string;
3
+ headed?: boolean;
4
+ headless?: boolean;
5
+ stepDelay?: string | number;
6
+ delay?: string | number;
7
+ spec?: string;
8
+ output?: string;
9
+ concurrency?: string | number;
10
+ }
11
+ /**
12
+ * `spectra test` — delegates to `@testspectra/react`'s bundled Vitest browser-mode runner for
13
+ * component testing. Deliberately separate from `spectra run` (E2E via core/orchestrator): see
14
+ * docs/v2/features/component-testing/react/README.md §2 for why these are two independent
15
+ * runners rather than one extended pipeline.
16
+ */
17
+ export declare function testCommand(options?: TestCommandOptions): Promise<void>;
@@ -0,0 +1,76 @@
1
+ import path from 'node:path';
2
+ import chalk from 'chalk';
3
+ import * as p from '@clack/prompts';
4
+ import { runSystemChecks } from './doctor.js';
5
+ /**
6
+ * Finds the Chrome-for-Testing binary that `spectra doctor` already installs/manages for the
7
+ * E2E CDP driver (`core/driver-cdp`) — same install, same binary. `@testspectra/react` must not
8
+ * contain any browser-discovery logic of its own (see the package-level comment in
9
+ * tools/react/vitest.config.ts): this is the one place that resolves the path, passed down to
10
+ * the bundled Vitest/Playwright runner as a plain argument (env var), so component testing never
11
+ * requires a separate `playwright install` download alongside TestSpectra's own browser.
12
+ */
13
+ async function resolveBrowserExecutable(cwd) {
14
+ try {
15
+ const { dependencies } = await runSystemChecks(cwd);
16
+ const chrome = dependencies.find((d) => d.category === 'web' && (d.name.includes('Chrome') || d.name.includes('Chromium')));
17
+ return chrome?.installed && chrome.location ? chrome.location : null;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ /**
24
+ * `spectra test` — delegates to `@testspectra/react`'s bundled Vitest browser-mode runner for
25
+ * component testing. Deliberately separate from `spectra run` (E2E via core/orchestrator): see
26
+ * docs/v2/features/component-testing/react/README.md §2 for why these are two independent
27
+ * runners rather than one extended pipeline.
28
+ */
29
+ export async function testCommand(options = {}) {
30
+ const cwd = options.cwd ?? process.cwd();
31
+ let reactPkg;
32
+ try {
33
+ reactPkg = await import('@testspectra/react');
34
+ }
35
+ catch {
36
+ p.log.error(chalk.red('✖ @testspectra/react is not installed in this workspace.'));
37
+ p.log.info(chalk.dim('Component testing requires the @testspectra/react package.'));
38
+ p.log.info(chalk.dim(' 👉 Install it: pnpm add -D @testspectra/react'));
39
+ process.exit(1);
40
+ return;
41
+ }
42
+ const browserExecutablePath = await resolveBrowserExecutable(cwd);
43
+ if (!browserExecutablePath) {
44
+ p.log.error(chalk.red('✖ TestSpectra-managed Chrome for Testing was not found.'));
45
+ p.log.info(chalk.dim('Component testing reuses the same browser install as `spectra run` (web).'));
46
+ p.log.info(chalk.dim(' 👉 Install it: spectra doctor --fix'));
47
+ process.exit(1);
48
+ return;
49
+ }
50
+ let stepDelayMs;
51
+ const rawDelay = options.stepDelay ?? options.delay;
52
+ if (rawDelay !== undefined) {
53
+ const delayNum = parseInt(String(rawDelay), 10);
54
+ if (!isNaN(delayNum) && delayNum >= 0) {
55
+ stepDelayMs = delayNum;
56
+ }
57
+ }
58
+ let headless = true;
59
+ if (options.headed) {
60
+ headless = false;
61
+ }
62
+ else if (options.headless !== undefined) {
63
+ headless = options.headless;
64
+ }
65
+ const concurrency = options.concurrency !== undefined ? parseInt(String(options.concurrency), 10) : undefined;
66
+ const ok = await reactPkg.runComponentTests({
67
+ cwd,
68
+ headless,
69
+ stepDelayMs,
70
+ spec: options.spec,
71
+ outputFile: options.output ? path.resolve(cwd, options.output) : undefined,
72
+ concurrency: concurrency !== undefined && !isNaN(concurrency) ? concurrency : undefined,
73
+ browserExecutablePath,
74
+ });
75
+ process.exit(ok ? 0 : 1);
76
+ }
@@ -3,15 +3,22 @@ import path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { TypeGenerator } from '../types/generator.js';
5
5
  export function startWatcher(cwd, debounceMs = 60) {
6
- // Initial generation
6
+ // Initial generation. TypeGenerator.generateAll itself stays synchronous (reading
7
+ // spectra.config.ts is unavoidably async, so that part is handled by the separate
8
+ // syncEnvDeclaration call below); startWatcher stays sync so it can return the watcher handle
9
+ // immediately, with the async env sync fired-and-forgotten.
7
10
  TypeGenerator.generateAll(cwd);
11
+ TypeGenerator.syncEnvDeclaration(cwd).catch((err) => {
12
+ console.error(chalk.red(`✖ Error generating initial env.d.ts: ${err?.message}`));
13
+ });
8
14
  let debounceTimer = null;
9
15
  const triggerRegen = (filePath, eventType) => {
10
16
  if (debounceTimer)
11
17
  clearTimeout(debounceTimer);
12
- debounceTimer = setTimeout(() => {
18
+ debounceTimer = setTimeout(async () => {
13
19
  try {
14
20
  TypeGenerator.generateAll(cwd);
21
+ await TypeGenerator.syncEnvDeclaration(cwd);
15
22
  const time = new Date().toLocaleTimeString();
16
23
  const rel = path.relative(cwd, filePath) || filePath;
17
24
  console.log(chalk.gray(`[${time}] `) + chalk.cyan(`✨ Updated ambient types `) + chalk.gray(`(${eventType}: ${rel})`));
@@ -89,7 +89,7 @@ export class ConfigLoader {
89
89
  environmentVariables: overrides?.executionConfig?.environmentVariables ??
90
90
  loaded.executionConfig?.environmentVariables ??
91
91
  DEFAULT_CONFIG_DATA.executionConfig?.environmentVariables ??
92
- [],
92
+ {},
93
93
  },
94
94
  };
95
95
  }
@@ -20,19 +20,11 @@ export interface WebConfig {
20
20
  headless: boolean | 'new';
21
21
  /**
22
22
  * Implicit wait timeout (in milliseconds) for finding elements before throwing an error.
23
+ * Also the ceiling auto-waiting element assertions/actions poll against (capped at 2000ms for
24
+ * assertions — see `getAssertionTimeoutMs()`).
23
25
  * @default "5000"
24
26
  */
25
27
  implicitWait: string;
26
- /**
27
- * Maximum time (in milliseconds) allowed for a webpage to complete navigation and initial load.
28
- * @default "30000"
29
- */
30
- pageLoadTimeout: string;
31
- /**
32
- * Maximum time (in milliseconds) allowed for asynchronous scripts to finish executing.
33
- * @default "30000"
34
- */
35
- scriptTimeout: string;
36
28
  /**
37
29
  * Delay between consecutive actions and commands in milliseconds (slow motion).
38
30
  * @default 0
@@ -46,37 +38,6 @@ export interface WebConfig {
46
38
  */
47
39
  parallelizationMode: 'testcase' | 'suite';
48
40
  }
49
- /**
50
- * Browser target definition for web test execution.
51
- */
52
- export interface Browser {
53
- /**
54
- * Unique identifier for this browser configuration.
55
- */
56
- id: string;
57
- /**
58
- * Browser type/engine: `"chrome"`, `"firefox"`, `"safari"`, `"edge"`.
59
- * @default "chrome"
60
- */
61
- type: string;
62
- /**
63
- * Whether to simulate mobile viewport and touch metrics.
64
- * @default false
65
- */
66
- mobileEmulation: boolean;
67
- /**
68
- * Emulated device name when `mobileEmulation` is true (e.g. `"iPhone 14"`, `"Pixel 7"`).
69
- */
70
- deviceName?: string;
71
- /**
72
- * Custom browser window width in pixels.
73
- */
74
- width?: string;
75
- /**
76
- * Custom browser window height in pixels.
77
- */
78
- height?: string;
79
- }
80
41
  /**
81
42
  * Target Android device specification for multi-device parallel execution.
82
43
  */
@@ -260,40 +221,6 @@ export interface IosConfig {
260
221
  */
261
222
  parallelizationMode: 'testcase' | 'suite';
262
223
  }
263
- /**
264
- * Load testing stage ramp-up profile.
265
- */
266
- export interface LoadStage {
267
- /**
268
- * Stage identifier.
269
- */
270
- id: string;
271
- /**
272
- * Duration for this load stage (e.g. `"30s"`, `"2m"`).
273
- */
274
- duration: string;
275
- /**
276
- * Target virtual users (VUs) to scale to during this stage.
277
- */
278
- targetVUs: string;
279
- }
280
- /**
281
- * Performance and pass/fail metric threshold definition.
282
- */
283
- export interface SuccessThreshold {
284
- /**
285
- * Unique threshold identifier.
286
- */
287
- id: string;
288
- /**
289
- * Performance metric type (e.g. `"http_req_duration"`, `"http_req_failed"`).
290
- */
291
- metricType: string;
292
- /**
293
- * Maximum acceptable value (e.g. `"500ms"`, `"0.01"`).
294
- */
295
- maxValue: string;
296
- }
297
224
  /**
298
225
  * Specific domain filter for real-time network interception and recording.
299
226
  */
@@ -307,19 +234,6 @@ export interface MonitoredDomain {
307
234
  */
308
235
  enabled: boolean;
309
236
  }
310
- /**
311
- * Custom environment variable key-value pair injected into the test execution sandbox.
312
- */
313
- export interface EnvironmentVariable {
314
- /**
315
- * Environment variable name (e.g. `"API_SECRET_KEY"`).
316
- */
317
- key: string;
318
- /**
319
- * Environment variable value.
320
- */
321
- value: string;
322
- }
323
237
  /**
324
238
  * Test execution, CDP network monitoring, and sandbox environment configuration.
325
239
  */
@@ -344,9 +258,10 @@ export interface ExecutionConfig {
344
258
  */
345
259
  monitoredDomains?: MonitoredDomain[];
346
260
  /**
347
- * Custom environment variables injected into the test process runtime.
261
+ * Custom environment variables injected into the test process runtime
262
+ * (e.g. `{ API_SECRET_KEY: "..." }`).
348
263
  */
349
- environmentVariables?: EnvironmentVariable[];
264
+ environmentVariables?: Record<string, string>;
350
265
  }
351
266
  /**
352
267
  * Root configuration data structure matching TestSpectra project schema.
@@ -356,10 +271,6 @@ export interface ConfigData {
356
271
  * Web browser execution settings.
357
272
  */
358
273
  webConfig: WebConfig;
359
- /**
360
- * List of target browsers for web automation runs.
361
- */
362
- browsers: Browser[];
363
274
  /**
364
275
  * Android Appium driver and device options.
365
276
  */
@@ -368,21 +279,6 @@ export interface ConfigData {
368
279
  * iOS Appium driver and device options.
369
280
  */
370
281
  iosConfig: IosConfig;
371
- /**
372
- * Load testing parameters.
373
- */
374
- loadConfig: {
375
- virtualUsers: string;
376
- duration: string;
377
- };
378
- /**
379
- * Load testing stages.
380
- */
381
- loadStages: LoadStage[];
382
- /**
383
- * Pass/fail metric thresholds.
384
- */
385
- thresholds: SuccessThreshold[];
386
282
  /**
387
283
  * Advanced execution and network recording settings.
388
284
  */
@@ -25,17 +25,8 @@ export const DEFAULT_CONFIG_DATA = {
25
25
  maxConcurrentSessions: '1',
26
26
  headless: true,
27
27
  implicitWait: '5000',
28
- pageLoadTimeout: '30000',
29
- scriptTimeout: '30000',
30
28
  parallelizationMode: 'testcase',
31
29
  },
32
- browsers: [
33
- {
34
- id: 'chrome-desktop',
35
- type: 'chrome',
36
- mobileEmulation: false,
37
- },
38
- ],
39
30
  androidConfig: {
40
31
  driverServer: 'http://127.0.0.1:4800',
41
32
  appPackage: '',
@@ -70,17 +61,11 @@ export const DEFAULT_CONFIG_DATA = {
70
61
  },
71
62
  ],
72
63
  },
73
- loadConfig: {
74
- virtualUsers: '10',
75
- duration: '1m',
76
- },
77
- loadStages: [],
78
- thresholds: [],
79
64
  executionConfig: {
80
65
  networkMonitoringEnabled: true,
81
66
  fastResponseTime: '200',
82
67
  normalResponseTime: '1000',
83
68
  monitoredDomains: [],
84
- environmentVariables: [],
69
+ environmentVariables: {},
85
70
  },
86
71
  };
@@ -261,7 +261,7 @@ describe('Platform TSConfigs & Type Isolation (docs/v2/cli/tsconfig-platform-iso
261
261
  expect(threw).toBe(true);
262
262
  expect(errorMessage).toContain("Cannot find name 'AuthLoginPage'");
263
263
  });
264
- it('E2E: compiles the full Nx monorepo template via tsc -b with 0 errors', () => {
264
+ it('E2E: compiles the full Nx monorepo template via tsc -b with 0 errors', async () => {
265
265
  let root = process.cwd();
266
266
  while (root && root !== path.dirname(root)) {
267
267
  if (fs.existsSync(path.join(root, 'pnpm-workspace.yaml')))
@@ -269,8 +269,12 @@ describe('Platform TSConfigs & Type Isolation (docs/v2/cli/tsconfig-platform-iso
269
269
  root = path.dirname(root);
270
270
  }
271
271
  const nxTemplateDir = path.join(root, 'cli', 'templates', 'nx');
272
- // Generate latest ambient types for the nx template
272
+ // Generate latest ambient types for the nx template, including env.d.ts (a separate async
273
+ // step — see TypeGenerator.syncEnvDeclaration — since spectra.config.ts's
274
+ // executionConfig.environmentVariables is what the template's EnvironmentConfig suite
275
+ // exercises via the typed Env global).
273
276
  TypeGenerator.generateAll(nxTemplateDir);
277
+ await TypeGenerator.syncEnvDeclaration(nxTemplateDir);
274
278
  expect(() => {
275
279
  execSync(`npx tsc -b ${nxTemplateDir}`, { stdio: 'pipe' });
276
280
  }).not.toThrow();