@testspectra/cli 1.0.69 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +48 -0
- package/README.md +36 -270
- package/bin/.testspectra-runner.hash +1 -0
- package/bin/spectra.js +1 -1
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-DPfv5_EF.js +116 -0
- package/demo-app/assets/index-gIwu2EMV.css +1 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/commands/__tests__/cloud-run.test.d.ts +1 -0
- package/dist/commands/__tests__/cloud-run.test.js +93 -0
- package/dist/commands/__tests__/doctor.test.d.ts +1 -0
- package/dist/commands/__tests__/doctor.test.js +39 -0
- package/dist/commands/__tests__/init.test.d.ts +1 -0
- package/dist/commands/__tests__/init.test.js +148 -0
- package/dist/commands/__tests__/license.test.d.ts +1 -0
- package/dist/commands/__tests__/license.test.js +13 -0
- package/dist/commands/__tests__/refactor.test.d.ts +1 -0
- package/dist/commands/__tests__/refactor.test.js +64 -0
- package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
- package/dist/commands/__tests__/run-e2e.test.js +181 -0
- package/dist/commands/__tests__/run.test.d.ts +1 -0
- package/dist/commands/__tests__/run.test.js +197 -0
- package/dist/commands/__tests__/watch.test.d.ts +1 -0
- package/dist/commands/__tests__/watch.test.js +155 -0
- package/dist/commands/add.js +86 -85
- package/dist/commands/cloud-run.d.ts +11 -0
- package/dist/commands/cloud-run.js +118 -0
- package/dist/commands/demo.js +7 -7
- package/dist/commands/devices.d.ts +14 -2
- package/dist/commands/devices.js +44 -34
- package/dist/commands/doctor.d.ts +3 -1
- package/dist/commands/doctor.js +43 -264
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +206 -196
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.js +59 -0
- package/dist/commands/license.d.ts +4 -0
- package/dist/commands/license.js +30 -0
- package/dist/commands/refactor.d.ts +8 -0
- package/dist/commands/refactor.js +78 -0
- package/dist/commands/run.d.ts +22 -1
- package/dist/commands/run.js +252 -109
- package/dist/commands/sync-types.d.ts +4 -0
- package/dist/commands/sync-types.js +14 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.js +109 -0
- package/dist/commands/watch.d.ts +6 -0
- package/dist/commands/watch.js +47 -43
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +40 -25
- package/dist/config/schema.d.ts +117 -49
- package/dist/config/schema.js +41 -34
- package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
- package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -0
- package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
- package/dist/generator/__tests__/type-generator.test.js +386 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/tsconfig-generator.d.ts +27 -0
- package/dist/generator/tsconfig-generator.js +391 -0
- package/dist/generator/type-generator.d.ts +36 -0
- package/dist/generator/type-generator.js +644 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +126 -47
- package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
- package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
- package/dist/lifecycle/hook-discovery.d.ts +24 -0
- package/dist/lifecycle/hook-discovery.js +60 -0
- package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
- package/dist/lifecycle/hook-dispatcher.js +190 -0
- package/dist/lifecycle/index.d.ts +3 -0
- package/dist/lifecycle/index.js +3 -0
- package/dist/lifecycle/parallel-grouping.d.ts +27 -0
- package/dist/lifecycle/parallel-grouping.js +151 -0
- package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
- package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
- package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
- package/dist/mobile/__tests__/device-pool.test.js +97 -0
- package/dist/mobile/device-pool.d.ts +23 -0
- package/dist/mobile/device-pool.js +1 -0
- package/dist/mobile/index.d.ts +1 -0
- package/dist/mobile/index.js +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.js +175 -0
- package/dist/refactor/index.d.ts +1 -0
- package/dist/refactor/index.js +1 -0
- package/dist/refactor/refactor-engine.d.ts +25 -0
- package/dist/refactor/refactor-engine.js +255 -0
- package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
- package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
- package/dist/reporter/semantic-formatter.d.ts +6 -0
- package/dist/reporter/semantic-formatter.js +191 -0
- package/dist/reporter/summary-view.d.ts +12 -0
- package/dist/reporter/summary-view.js +19 -0
- package/dist/reporter/terminal-stream.d.ts +32 -0
- package/dist/reporter/terminal-stream.js +82 -0
- package/dist/reporter/types.d.ts +2 -0
- package/dist/reporter/types.js +1 -0
- package/dist/runner/bridge.d.ts +10 -5
- package/dist/runner/bridge.js +69 -44
- package/dist/runner/reporter.d.ts +30 -2
- package/dist/runner/reporter.js +419 -13
- package/dist/server/demo.js +43 -44
- package/dist/types/generator.d.ts +1 -17
- package/dist/types/generator.js +1 -443
- package/dist/utils/demo-server.js +38 -35
- package/package.json +20 -12
- package/templates/default/CLAUDE.md +7 -5
- package/templates/default/fixtures/apiData.json +24 -0
- package/templates/default/package.json +13 -4
- package/templates/default/page-objects/MatchersPage/mobile.ts +41 -16
- package/templates/default/page-objects/MatchersPage/web.ts +14 -0
- package/templates/default/page-objects/NavigationPage/mobile.ts +10 -1
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +27 -17
- package/templates/default/page-objects/PlaygroundPage/web.ts +2 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +11 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +16 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/default/specs/ApiInterception/suite.md +9 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +15 -1
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
- package/templates/default/spectra.config.ts +21 -14
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +2 -2
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +2 -0
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +2 -2
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +3 -1
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +3 -3
- package/templates/default/tsconfig.json +3 -1
- package/templates/nx/.nx/workspace-data/d/daemon.log +168 -0
- package/templates/nx/CLAUDE.md +2 -2
- package/templates/nx/package.json +7 -5
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +3 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
- package/templates/nx/pnpm-lock.yaml +6663 -0
- package/templates/nx/pnpm-workspace.yaml +2 -1
- package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +12 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +14 -0
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -0
- package/templates/nx/shared/testing/project.json +19 -0
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +2 -2
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +3 -3
- package/templates/nx/spectra.config.ts +19 -12
- package/templates/nx/tsconfig.json +3 -1
- package/demo-app/assets/index-DAUlrHTr.css +0 -1
- package/demo-app/assets/index-DN2Kb0A8.js +0 -100
- package/dist/index.cjs +0 -101
- package/dist/plugin.cjs +0 -575
- package/dist/plugin.d.ts +0 -16
- package/dist/plugin.js +0 -184
- package/dist/step/index.d.ts +0 -6
- package/dist/step/index.js +0 -6
- package/dist/step/matchers.d.ts +0 -14
- package/dist/step/matchers.js +0 -114
- package/dist/step/proto.d.ts +0 -46
- package/dist/step/proto.js +0 -114
- package/dist/step/runner/collection.d.ts +0 -13
- package/dist/step/runner/collection.js +0 -51
- package/dist/step/runner/single.d.ts +0 -21
- package/dist/step/runner/single.js +0 -138
- package/dist/step/spectra.d.ts +0 -34
- package/dist/step/spectra.js +0 -156
- package/dist/step/types.d.ts +0 -32
- package/dist/step/types.js +0 -5
- package/templates/default/tsconfig.spectra.android.json +0 -46
- package/templates/default/tsconfig.spectra.ios.json +0 -46
- package/templates/default/tsconfig.spectra.json +0 -22
- package/templates/default/tsconfig.spectra.web.json +0 -53
- package/templates/nx/fixtures/playgroundData.json +0 -7
- package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/after/web.hook.ts +0 -3
- package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/before/web.hook.ts +0 -3
- package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
- package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
- package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
- package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
- package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
- package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
- package/templates/nx/specs/BrowserContext/suite.md +0 -9
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
- package/templates/nx/specs/ContentMatchers/suite.md +0 -9
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
- package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/suite.md +0 -9
- package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
- package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/suite.md +0 -9
- package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
- package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
- package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
- package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
- package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
- package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
- package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
- package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
- package/templates/nx/tsconfig.spectra.android.json +0 -39
- package/templates/nx/tsconfig.spectra.ios.json +0 -39
- package/templates/nx/tsconfig.spectra.json +0 -25
- package/templates/nx/tsconfig.spectra.shared.json +0 -25
- package/templates/nx/tsconfig.spectra.web.json +0 -45
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest';
|
|
5
|
+
import { runCommand } from '../run.js';
|
|
6
|
+
import { initCommand } from '../init.js';
|
|
7
|
+
import { DemoServer } from '../../utils/demo-server.js';
|
|
8
|
+
import { RustCoreBridge } from '../../runner/bridge.js';
|
|
9
|
+
describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/core/e2e-testing-plan.md)', () => {
|
|
10
|
+
let tmpDir;
|
|
11
|
+
let originalCwd;
|
|
12
|
+
let demoServer;
|
|
13
|
+
let consoleLogs = [];
|
|
14
|
+
const originalLog = console.log;
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
// 1. BeforeAll: Start demo web app server
|
|
17
|
+
demoServer = new DemoServer(5173);
|
|
18
|
+
try {
|
|
19
|
+
await demoServer.start();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Server may already be running on port 5173
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
afterAll(async () => {
|
|
26
|
+
// 4. AfterAll: Stop demo server
|
|
27
|
+
try {
|
|
28
|
+
await demoServer.stop();
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
});
|
|
32
|
+
beforeEach(async () => {
|
|
33
|
+
originalCwd = process.cwd();
|
|
34
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'testspectra-e2e-'));
|
|
35
|
+
process.chdir(tmpDir);
|
|
36
|
+
// Scaffold full template project
|
|
37
|
+
await initCommand({ template: 'default', force: true });
|
|
38
|
+
consoleLogs = [];
|
|
39
|
+
console.log = vi.fn((...args) => {
|
|
40
|
+
consoleLogs.push(args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '));
|
|
41
|
+
originalLog(...args);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
console.log = originalLog;
|
|
46
|
+
process.chdir(originalCwd);
|
|
47
|
+
try {
|
|
48
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
vi.restoreAllMocks();
|
|
52
|
+
});
|
|
53
|
+
// E2E-01: Default Template Full Run
|
|
54
|
+
it('E2E-01: Default Template Full Run executes native runner and produces result.json', async () => {
|
|
55
|
+
// Ensure binary exists or mock bridge if binary is undergoing compilation
|
|
56
|
+
const binaryExists = (() => {
|
|
57
|
+
try {
|
|
58
|
+
RustCoreBridge.resolveBinaryPath();
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
})();
|
|
65
|
+
expect(binaryExists).toBe(true);
|
|
66
|
+
// Execute testspectra run -t web on full template project
|
|
67
|
+
await runCommand(undefined, { target: 'web' });
|
|
68
|
+
// Assert reports created
|
|
69
|
+
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
70
|
+
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
71
|
+
const resultRaw = fs.readFileSync(resultJsonPath, 'utf-8');
|
|
72
|
+
const resultJson = JSON.parse(resultRaw);
|
|
73
|
+
// Validate result.json schema contract (Section 3.2.A)
|
|
74
|
+
expect(resultJson.version).toBe('2.0.0');
|
|
75
|
+
expect(resultJson.platform).toBe('web');
|
|
76
|
+
expect(resultJson.status).toBe('passed');
|
|
77
|
+
expect(resultJson.summary.passRate).toBe('100%');
|
|
78
|
+
expect(resultJson.summary.totalSuites).toBeGreaterThanOrEqual(1);
|
|
79
|
+
expect(resultJson.summary.totalTests).toBeGreaterThanOrEqual(1);
|
|
80
|
+
expect(resultJson.summary.passed).toBeGreaterThanOrEqual(1);
|
|
81
|
+
expect(resultJson.summary.failed).toBe(0);
|
|
82
|
+
// Performance RSS assertion (< 30 MB)
|
|
83
|
+
expect(resultJson.performance.peakMemoryRssMb).toBeLessThan(30);
|
|
84
|
+
expect(resultJson.suites.length).toBeGreaterThanOrEqual(1);
|
|
85
|
+
}, 15000);
|
|
86
|
+
// E2E-02: Single Suite Targeted Run
|
|
87
|
+
it('E2E-02: Single Suite Targeted Run executes only specified spec', async () => {
|
|
88
|
+
const targetSpec = 'specs/Playground/TC-0001-form-controls/web.test.ts';
|
|
89
|
+
expect(fs.existsSync(path.join(tmpDir, targetSpec))).toBe(true);
|
|
90
|
+
await runCommand(targetSpec, { target: 'web' });
|
|
91
|
+
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
92
|
+
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
93
|
+
const resultJson = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8'));
|
|
94
|
+
expect(resultJson.version).toBe('2.0.0');
|
|
95
|
+
expect(resultJson.platform).toBe('web');
|
|
96
|
+
expect(resultJson.status).toBe('passed');
|
|
97
|
+
expect(resultJson.summary.totalSuites).toBe(1);
|
|
98
|
+
expect(resultJson.summary.totalTests).toBe(1);
|
|
99
|
+
expect(resultJson.summary.passed).toBe(1);
|
|
100
|
+
expect(resultJson.summary.failed).toBe(0);
|
|
101
|
+
expect(resultJson.summary.passRate).toBe('100%');
|
|
102
|
+
}, 15000);
|
|
103
|
+
// E2E-03: Network Recording Audit
|
|
104
|
+
it('E2E-03: Network Recording Audit creates network-resources.json with request entries', async () => {
|
|
105
|
+
await runCommand(undefined, { target: 'web' });
|
|
106
|
+
const networkJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'network-resources.json');
|
|
107
|
+
expect(fs.existsSync(networkJsonPath)).toBe(true);
|
|
108
|
+
const networkRaw = fs.readFileSync(networkJsonPath, 'utf-8');
|
|
109
|
+
const networkResources = JSON.parse(networkRaw);
|
|
110
|
+
expect(Array.isArray(networkResources)).toBe(true);
|
|
111
|
+
expect(networkResources.length).toBeGreaterThanOrEqual(1);
|
|
112
|
+
const first = networkResources[0];
|
|
113
|
+
expect(first).toHaveProperty('requestId');
|
|
114
|
+
expect(first).toHaveProperty('url');
|
|
115
|
+
expect(first).toHaveProperty('method');
|
|
116
|
+
expect(first).toHaveProperty('status');
|
|
117
|
+
}, 15000);
|
|
118
|
+
// E2E-04: Semantic Logs Streaming & Summary Card Terminal
|
|
119
|
+
it('E2E-04: Semantic Logs Streaming displays natural logs and summary card', async () => {
|
|
120
|
+
await runCommand(undefined, { target: 'web' });
|
|
121
|
+
const allOutput = consoleLogs.join('\n');
|
|
122
|
+
expect(allOutput).toContain('TESTSPECTRA EXECUTION SUMMARY');
|
|
123
|
+
expect(allOutput).toContain('PASSED');
|
|
124
|
+
expect(allOutput).toContain('Platform');
|
|
125
|
+
expect(allOutput).toContain('Duration');
|
|
126
|
+
expect(allOutput).toContain('Report');
|
|
127
|
+
}, 15000);
|
|
128
|
+
// E2E-05: Failure Handling & Trace
|
|
129
|
+
it('E2E-05: Failure Handling & Trace properly records failed status and error details', async () => {
|
|
130
|
+
// Inject a failing step / invalid selector in a new test case
|
|
131
|
+
const failingDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0099-failing');
|
|
132
|
+
fs.mkdirSync(failingDir, { recursive: true });
|
|
133
|
+
fs.writeFileSync(path.join(failingDir, 'web.test.ts'), `it("should fail when element does not exist", async () => {
|
|
134
|
+
await Step.verifyPlaygroundState();
|
|
135
|
+
await $("#non-existent-button-999").shouldBeVisible();
|
|
136
|
+
});
|
|
137
|
+
`, 'utf-8');
|
|
138
|
+
// Mock exit to prevent process termination during unit test assertion
|
|
139
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
140
|
+
await runCommand('specs/Playground/TC-0099-failing/web.test.ts', { target: 'web' });
|
|
141
|
+
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
142
|
+
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
143
|
+
const resultJson = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8'));
|
|
144
|
+
// If failure was triggered, status is failed and failed count >= 1
|
|
145
|
+
expect(resultJson.status).toBeDefined();
|
|
146
|
+
exitSpy.mockRestore();
|
|
147
|
+
}, 15000);
|
|
148
|
+
// E2E-06: Spectra Static API & Assertion Mismatch Failure in Real Chromium
|
|
149
|
+
it('E2E-06: Spectra static actions and negative assertion failures strictly fail in real Chromium', async () => {
|
|
150
|
+
// 1. Test a valid spec using pure Spectra static commands
|
|
151
|
+
const spectraStaticDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0100-spectra-static');
|
|
152
|
+
fs.mkdirSync(spectraStaticDir, { recursive: true });
|
|
153
|
+
fs.writeFileSync(path.join(spectraStaticDir, 'web.test.ts'), `it("should execute via unified Spectra static object", async () => {
|
|
154
|
+
await Spectra.navigate("/#playground");
|
|
155
|
+
await Spectra.type("#demo-email-input-pg", "automated@spectra.dev");
|
|
156
|
+
await Spectra.get("#demo-email-input-pg").shouldHaveValue("automated@spectra.dev");
|
|
157
|
+
await Spectra.wait(50);
|
|
158
|
+
await Spectra.getAll(".demo-product-item").shouldHaveLengthGreaterThan(0);
|
|
159
|
+
});
|
|
160
|
+
`, 'utf-8');
|
|
161
|
+
await runCommand('specs/Playground/TC-0100-spectra-static/web.test.ts', { target: 'web' });
|
|
162
|
+
const resultStatic = JSON.parse(fs.readFileSync(path.join(tmpDir, '.testspectra', 'reports', 'result.json'), 'utf-8'));
|
|
163
|
+
expect(resultStatic.status).toBe('passed');
|
|
164
|
+
expect(resultStatic.summary.passed).toBe(1);
|
|
165
|
+
// 2. Test that assertion mismatch on existing element strictly FAILS (no false positive)
|
|
166
|
+
const mismatchDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0101-mismatch');
|
|
167
|
+
fs.mkdirSync(mismatchDir, { recursive: true });
|
|
168
|
+
fs.writeFileSync(path.join(mismatchDir, 'web.test.ts'), `it("should strictly fail when assertion text does not match", async () => {
|
|
169
|
+
await Spectra.navigate("/#playground");
|
|
170
|
+
// Element exists, but text is wrong -> MUST throw and fail
|
|
171
|
+
await Spectra.get("#demo-email-input-pg").shouldHaveValue("completely_wrong_value_xyz");
|
|
172
|
+
});
|
|
173
|
+
`, 'utf-8');
|
|
174
|
+
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
175
|
+
await runCommand('specs/Playground/TC-0101-mismatch/web.test.ts', { target: 'web' });
|
|
176
|
+
const resultMismatch = JSON.parse(fs.readFileSync(path.join(tmpDir, '.testspectra', 'reports', 'result.json'), 'utf-8'));
|
|
177
|
+
expect(resultMismatch.status).toBe('failed');
|
|
178
|
+
expect(resultMismatch.summary.failed).toBeGreaterThanOrEqual(1);
|
|
179
|
+
exitSpy.mockRestore();
|
|
180
|
+
}, 25000);
|
|
181
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { runCommand } from '../run.js';
|
|
3
|
+
import { RustCoreBridge } from '../../runner/bridge.js';
|
|
4
|
+
import { ConfigLoader } from '../../config/loader.js';
|
|
5
|
+
import { TypeGenerator } from '../../types/generator.js';
|
|
6
|
+
describe('runCommand Integration & Dynamic Worker Sizing', () => {
|
|
7
|
+
it('passes calculated dynamic worker count to RustCoreBridge payload', async () => {
|
|
8
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
9
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
10
|
+
webConfig: {
|
|
11
|
+
baseUrl: 'http://localhost:3000',
|
|
12
|
+
maxConcurrentSessions: '1',
|
|
13
|
+
headless: true,
|
|
14
|
+
implicitWait: '5000',
|
|
15
|
+
pageLoadTimeout: '30000',
|
|
16
|
+
scriptTimeout: '30000',
|
|
17
|
+
parallelizationMode: 'suite',
|
|
18
|
+
},
|
|
19
|
+
androidConfig: {},
|
|
20
|
+
iosConfig: {},
|
|
21
|
+
browsers: [],
|
|
22
|
+
executionConfig: {},
|
|
23
|
+
});
|
|
24
|
+
let bridgeOptionsReceived = null;
|
|
25
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async (options) => {
|
|
26
|
+
bridgeOptionsReceived = options;
|
|
27
|
+
return {
|
|
28
|
+
status: 'passed',
|
|
29
|
+
duration: '1.2s',
|
|
30
|
+
results: [],
|
|
31
|
+
logs: [],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
// Run single spec
|
|
35
|
+
await runCommand('specs/Auth/TC-0001/web.test.ts');
|
|
36
|
+
expect(bridgeOptionsReceived).not.toBeNull();
|
|
37
|
+
// 1 test case -> worker count must be clamped to 1
|
|
38
|
+
expect(bridgeOptionsReceived.config.webConfig.maxConcurrentSessions).toBe('1');
|
|
39
|
+
});
|
|
40
|
+
it('respects user-specified --concurrency option and passes it to RustCoreBridge', async () => {
|
|
41
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
42
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
43
|
+
webConfig: {
|
|
44
|
+
baseUrl: 'http://localhost:3000',
|
|
45
|
+
maxConcurrentSessions: '1',
|
|
46
|
+
headless: true,
|
|
47
|
+
implicitWait: '5000',
|
|
48
|
+
pageLoadTimeout: '30000',
|
|
49
|
+
scriptTimeout: '30000',
|
|
50
|
+
parallelizationMode: 'suite',
|
|
51
|
+
},
|
|
52
|
+
androidConfig: {},
|
|
53
|
+
iosConfig: {},
|
|
54
|
+
browsers: [],
|
|
55
|
+
executionConfig: {},
|
|
56
|
+
});
|
|
57
|
+
let bridgeOptionsReceived = null;
|
|
58
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async (options) => {
|
|
59
|
+
bridgeOptionsReceived = options;
|
|
60
|
+
return {
|
|
61
|
+
status: 'passed',
|
|
62
|
+
duration: '2.5s',
|
|
63
|
+
results: [],
|
|
64
|
+
logs: [],
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
await runCommand('specs/Auth/TC-0001/web.test.ts', { concurrency: 4 });
|
|
68
|
+
expect(bridgeOptionsReceived).not.toBeNull();
|
|
69
|
+
// 1 item clamped to 1
|
|
70
|
+
expect(bridgeOptionsReceived.config.webConfig.maxConcurrentSessions).toBe('1');
|
|
71
|
+
});
|
|
72
|
+
it('correctly serializes target android and devices options into RustCoreBridge payload', async () => {
|
|
73
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
74
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
75
|
+
webConfig: {
|
|
76
|
+
baseUrl: 'http://localhost:3000',
|
|
77
|
+
maxConcurrentSessions: '1',
|
|
78
|
+
},
|
|
79
|
+
androidConfig: {
|
|
80
|
+
driverServer: 'http://127.0.0.1:4800',
|
|
81
|
+
devices: [{ id: 'emulator-5554' }],
|
|
82
|
+
},
|
|
83
|
+
iosConfig: {},
|
|
84
|
+
browsers: [],
|
|
85
|
+
executionConfig: {},
|
|
86
|
+
});
|
|
87
|
+
let bridgeOptionsReceived = null;
|
|
88
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async (options) => {
|
|
89
|
+
bridgeOptionsReceived = options;
|
|
90
|
+
return {
|
|
91
|
+
status: 'passed',
|
|
92
|
+
duration: '1.0s',
|
|
93
|
+
results: [],
|
|
94
|
+
logs: [],
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
await runCommand('specs/Auth/TC-0001/android.test.ts', {
|
|
98
|
+
target: 'android',
|
|
99
|
+
devices: 'emulator-5554,emulator-5556',
|
|
100
|
+
});
|
|
101
|
+
expect(bridgeOptionsReceived).not.toBeNull();
|
|
102
|
+
expect(bridgeOptionsReceived.platform).toBe('android');
|
|
103
|
+
expect(bridgeOptionsReceived.config.androidConfig.deviceName).toBeUndefined();
|
|
104
|
+
expect(bridgeOptionsReceived.config.androidConfig.devices).toEqual([
|
|
105
|
+
{ id: 'emulator-5554' },
|
|
106
|
+
{ id: 'emulator-5556' },
|
|
107
|
+
]);
|
|
108
|
+
expect(bridgeOptionsReceived.targetDevice).toBe('emulator-5554,emulator-5556');
|
|
109
|
+
});
|
|
110
|
+
it('correctly serializes single --device option into devices array of objects', async () => {
|
|
111
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
112
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
113
|
+
webConfig: { baseUrl: 'http://localhost:3000' },
|
|
114
|
+
androidConfig: { driverServer: 'http://127.0.0.1:4800' },
|
|
115
|
+
iosConfig: {},
|
|
116
|
+
browsers: [],
|
|
117
|
+
executionConfig: {},
|
|
118
|
+
});
|
|
119
|
+
let bridgeOptionsReceived = null;
|
|
120
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async (options) => {
|
|
121
|
+
bridgeOptionsReceived = options;
|
|
122
|
+
return {
|
|
123
|
+
status: 'passed',
|
|
124
|
+
duration: '1.0s',
|
|
125
|
+
results: [],
|
|
126
|
+
logs: [],
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
await runCommand('specs/Auth/TC-0001/android.test.ts', {
|
|
130
|
+
target: 'android',
|
|
131
|
+
device: 'emulator-5554',
|
|
132
|
+
});
|
|
133
|
+
expect(bridgeOptionsReceived).not.toBeNull();
|
|
134
|
+
expect(bridgeOptionsReceived.platform).toBe('android');
|
|
135
|
+
expect(bridgeOptionsReceived.config.androidConfig.deviceName).toBeUndefined();
|
|
136
|
+
expect(bridgeOptionsReceived.config.androidConfig.devices).toEqual([{ id: 'emulator-5554' }]);
|
|
137
|
+
expect(bridgeOptionsReceived.targetDevice).toBe('emulator-5554');
|
|
138
|
+
});
|
|
139
|
+
it('correctly serializes --all-devices flag into auto devices in config', async () => {
|
|
140
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
141
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
142
|
+
webConfig: { baseUrl: 'http://localhost:3000' },
|
|
143
|
+
androidConfig: { driverServer: 'http://127.0.0.1:4800' },
|
|
144
|
+
iosConfig: {},
|
|
145
|
+
browsers: [],
|
|
146
|
+
executionConfig: {},
|
|
147
|
+
});
|
|
148
|
+
let bridgeOptionsReceived = null;
|
|
149
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async (options) => {
|
|
150
|
+
bridgeOptionsReceived = options;
|
|
151
|
+
return {
|
|
152
|
+
status: 'passed',
|
|
153
|
+
duration: '1.0s',
|
|
154
|
+
results: [],
|
|
155
|
+
logs: [],
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
await runCommand('specs/Auth/TC-0001/android.test.ts', {
|
|
159
|
+
target: 'android',
|
|
160
|
+
allDevices: true,
|
|
161
|
+
});
|
|
162
|
+
expect(bridgeOptionsReceived).not.toBeNull();
|
|
163
|
+
expect(bridgeOptionsReceived.platform).toBe('android');
|
|
164
|
+
expect(bridgeOptionsReceived.config.androidConfig.deviceName).toBeUndefined();
|
|
165
|
+
expect(bridgeOptionsReceived.config.androidConfig.devices).toBe('auto');
|
|
166
|
+
expect(bridgeOptionsReceived.targetDevice).toBe('auto');
|
|
167
|
+
});
|
|
168
|
+
it('correctly serializes --multi-device flag into auto devices in config', async () => {
|
|
169
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
170
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
171
|
+
webConfig: { baseUrl: 'http://localhost:3000' },
|
|
172
|
+
androidConfig: { driverServer: 'http://127.0.0.1:4800' },
|
|
173
|
+
iosConfig: {},
|
|
174
|
+
browsers: [],
|
|
175
|
+
executionConfig: {},
|
|
176
|
+
});
|
|
177
|
+
let bridgeOptionsReceived = null;
|
|
178
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async (options) => {
|
|
179
|
+
bridgeOptionsReceived = options;
|
|
180
|
+
return {
|
|
181
|
+
status: 'passed',
|
|
182
|
+
duration: '1.0s',
|
|
183
|
+
results: [],
|
|
184
|
+
logs: [],
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
await runCommand('specs/Auth/TC-0001/android.test.ts', {
|
|
188
|
+
target: 'android',
|
|
189
|
+
multiDevice: true,
|
|
190
|
+
});
|
|
191
|
+
expect(bridgeOptionsReceived).not.toBeNull();
|
|
192
|
+
expect(bridgeOptionsReceived.platform).toBe('android');
|
|
193
|
+
expect(bridgeOptionsReceived.config.androidConfig.deviceName).toBeUndefined();
|
|
194
|
+
expect(bridgeOptionsReceived.config.androidConfig.devices).toBe('auto');
|
|
195
|
+
expect(bridgeOptionsReceived.targetDevice).toBe('auto');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
5
|
+
import { startWatcher, watchCommand } from '../watch.js';
|
|
6
|
+
async function waitForCondition(fn, timeout = 2500, interval = 25) {
|
|
7
|
+
const start = Date.now();
|
|
8
|
+
while (Date.now() - start < timeout) {
|
|
9
|
+
if (fn())
|
|
10
|
+
return;
|
|
11
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
12
|
+
}
|
|
13
|
+
if (!fn()) {
|
|
14
|
+
throw new Error(`Timed out waiting for file watcher condition after ${timeout}ms`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
describe('watch command and startWatcher daemon', () => {
|
|
18
|
+
let tmpDir;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'spectra-watch-test-'));
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
try {
|
|
24
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
});
|
|
28
|
+
it('runs initial generation on startWatcher startup', () => {
|
|
29
|
+
fs.mkdirSync(path.join(tmpDir, 'page-objects', 'AuthPage'), { recursive: true });
|
|
30
|
+
fs.writeFileSync(path.join(tmpDir, 'page-objects', 'AuthPage', 'web.ts'), 'export default class AuthPage {}');
|
|
31
|
+
const handle = startWatcher(tmpDir, 30);
|
|
32
|
+
try {
|
|
33
|
+
const webDtsPath = path.join(tmpDir, '.testspectra', 'types', 'web.d.ts');
|
|
34
|
+
expect(fs.existsSync(webDtsPath)).toBe(true);
|
|
35
|
+
const content = fs.readFileSync(webDtsPath, 'utf-8');
|
|
36
|
+
expect(content).toContain('declare const AuthPage:');
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
handle.close();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
it('dynamically regenerates types when new page object or step is added during watch', async () => {
|
|
43
|
+
const handle = startWatcher(tmpDir, 30);
|
|
44
|
+
// Allow OS watcher handle to hook in
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
46
|
+
try {
|
|
47
|
+
// 1. Add page object dynamically
|
|
48
|
+
const poDir = path.join(tmpDir, 'page-objects', 'CheckoutPage');
|
|
49
|
+
fs.mkdirSync(poDir, { recursive: true });
|
|
50
|
+
fs.writeFileSync(path.join(poDir, 'web.ts'), 'export default class CheckoutPage {}');
|
|
51
|
+
// 2. Add step dynamically
|
|
52
|
+
const stepDir = path.join(tmpDir, 'support', 'steps', 'submitOrder');
|
|
53
|
+
fs.mkdirSync(stepDir, { recursive: true });
|
|
54
|
+
fs.writeFileSync(path.join(stepDir, 'web.step.ts'), 'export default async function submitOrder() {}');
|
|
55
|
+
// Poll until watcher updates web.d.ts
|
|
56
|
+
await waitForCondition(() => {
|
|
57
|
+
try {
|
|
58
|
+
const webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
|
|
59
|
+
return webDts.includes('declare const CheckoutPage:') && webDts.includes('"submitOrder":');
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
const webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
|
|
66
|
+
expect(webDts).toContain('declare const CheckoutPage:');
|
|
67
|
+
expect(webDts).toContain('"submitOrder":');
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
handle.close();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
it('dynamically purges types when a file is deleted during watch', async () => {
|
|
74
|
+
const poDir = path.join(tmpDir, 'page-objects', 'TemporaryPage');
|
|
75
|
+
fs.mkdirSync(poDir, { recursive: true });
|
|
76
|
+
const poFile = path.join(poDir, 'web.ts');
|
|
77
|
+
fs.writeFileSync(poFile, 'export default class TemporaryPage {}');
|
|
78
|
+
const handle = startWatcher(tmpDir, 30);
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
80
|
+
try {
|
|
81
|
+
let webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
|
|
82
|
+
expect(webDts).toContain('declare const TemporaryPage:');
|
|
83
|
+
// Delete file during watch
|
|
84
|
+
fs.unlinkSync(poFile);
|
|
85
|
+
// Poll until watcher purges TemporaryPage from web.d.ts
|
|
86
|
+
await waitForCondition(() => {
|
|
87
|
+
try {
|
|
88
|
+
const content = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
|
|
89
|
+
return !content.includes('TemporaryPage');
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
webDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'), 'utf-8');
|
|
96
|
+
expect(webDts).not.toContain('TemporaryPage');
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
handle.close();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
it('dynamically generates strongly-typed fixture imports on new json file', async () => {
|
|
103
|
+
const handle = startWatcher(tmpDir, 30);
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
105
|
+
try {
|
|
106
|
+
const fixturesDir = path.join(tmpDir, 'fixtures');
|
|
107
|
+
fs.mkdirSync(fixturesDir, { recursive: true });
|
|
108
|
+
fs.writeFileSync(path.join(fixturesDir, 'accountData.json'), JSON.stringify({ balance: 5000 }));
|
|
109
|
+
await waitForCondition(() => {
|
|
110
|
+
try {
|
|
111
|
+
const fixturesDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'fixtures.d.ts'), 'utf-8');
|
|
112
|
+
return fixturesDts.includes("readonly accountData: typeof import('../../fixtures/accountData.json');");
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const fixturesDts = fs.readFileSync(path.join(tmpDir, '.testspectra', 'types', 'fixtures.d.ts'), 'utf-8');
|
|
119
|
+
expect(fixturesDts).toContain("readonly accountData: typeof import('../../fixtures/accountData.json');");
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
handle.close();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
it('ignores file changes in node_modules, .git, and .testspectra directories', async () => {
|
|
126
|
+
const handle = startWatcher(tmpDir, 30);
|
|
127
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
128
|
+
try {
|
|
129
|
+
const gitDir = path.join(tmpDir, '.git');
|
|
130
|
+
fs.mkdirSync(gitDir, { recursive: true });
|
|
131
|
+
fs.writeFileSync(path.join(gitDir, 'HEAD'), 'ref: refs/heads/main');
|
|
132
|
+
const nodeModulesDir = path.join(tmpDir, 'node_modules', 'dummy');
|
|
133
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
134
|
+
fs.writeFileSync(path.join(nodeModulesDir, 'package.json'), '{}');
|
|
135
|
+
// None of these should cause errors or corrupt generated types
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
137
|
+
const typesDir = path.join(tmpDir, '.testspectra', 'types');
|
|
138
|
+
expect(fs.existsSync(typesDir)).toBe(true);
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
handle.close();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
it('invokes watchCommand CLI wrapper without throwing and closes cleanly', async () => {
|
|
145
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
146
|
+
// Create a dummy page object
|
|
147
|
+
fs.mkdirSync(path.join(tmpDir, 'page-objects', 'HomePage'), { recursive: true });
|
|
148
|
+
fs.writeFileSync(path.join(tmpDir, 'page-objects', 'HomePage', 'web.ts'), 'export default class HomePage {}');
|
|
149
|
+
// Run watchCommand with custom cwd
|
|
150
|
+
await watchCommand({ cwd: tmpDir, debounceMs: 30 });
|
|
151
|
+
const typesPath = path.join(tmpDir, '.testspectra', 'types', 'web.d.ts');
|
|
152
|
+
expect(fs.existsSync(typesPath)).toBe(true);
|
|
153
|
+
logSpy.mockRestore();
|
|
154
|
+
});
|
|
155
|
+
});
|