@testspectra/cli 1.1.7 → 1.1.8-rc.2
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/bin/.testspectra-runner.hash +1 -1
- package/bin/spectra.js +0 -0
- package/bin/testspectra-runner.exe +0 -0
- package/demo-app/assets/index-BhlW-eXG.css +1 -0
- package/demo-app/assets/index-DOtRypCa.js +154 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/__tests__/run-e2e.test.js +270 -29
- package/dist/commands/__tests__/run.test.js +92 -16
- package/dist/commands/demo.js +17 -7
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +154 -149
- package/dist/commands/lint.d.ts +5 -0
- package/dist/commands/lint.js +55 -0
- package/dist/commands/refactor.d.ts +2 -1
- package/dist/commands/refactor.js +129 -35
- package/dist/commands/run.d.ts +2 -3
- package/dist/commands/run.js +93 -28
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.js +28 -0
- package/dist/commands/sync-types.js +1 -0
- package/dist/commands/test.d.ts +17 -0
- package/dist/commands/test.js +104 -0
- package/dist/commands/watch.js +9 -3
- package/dist/config/loader.js +1 -1
- package/dist/config/schema.d.ts +5 -114
- package/dist/config/schema.js +1 -17
- package/dist/generator/__tests__/tsconfig-isolation.test.js +6 -2
- package/dist/generator/__tests__/type-generator.test.js +1 -1
- package/dist/generator/tsconfig-generator.js +44 -16
- package/dist/generator/type-generator.d.ts +24 -1
- package/dist/generator/type-generator.js +104 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +76 -40
- package/dist/linter/__tests__/anonymous-export.test.js +184 -0
- package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
- package/dist/linter/__tests__/component-test-imports.test.js +115 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
- package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
- package/dist/linter/__tests__/spec-schema.test.js +369 -0
- package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
- package/dist/linter/__tests__/step-parity.test.js +372 -0
- package/dist/linter/discovery.d.ts +30 -0
- package/dist/linter/discovery.js +249 -0
- package/dist/linter/extractor.d.ts +67 -0
- package/dist/linter/extractor.js +245 -0
- package/dist/linter/index.d.ts +7 -0
- package/dist/linter/index.js +7 -0
- package/dist/linter/parser.d.ts +24 -0
- package/dist/linter/parser.js +219 -0
- package/dist/linter/schemas/spec.schema.d.ts +83 -0
- package/dist/linter/schemas/spec.schema.js +150 -0
- package/dist/linter/schemas/suite.schema.d.ts +59 -0
- package/dist/linter/schemas/suite.schema.js +83 -0
- package/dist/linter/spec-linter.d.ts +23 -0
- package/dist/linter/spec-linter.js +630 -0
- package/dist/linter/types.d.ts +28 -0
- package/dist/linter/types.js +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.js +698 -0
- package/dist/refactor/refactor-engine.d.ts +25 -0
- package/dist/refactor/refactor-engine.js +374 -42
- package/dist/reporter/semantic-formatter.js +22 -2
- package/dist/reporter/types.d.ts +1 -1
- package/dist/runner/bridge.d.ts +1 -1
- package/dist/runner/bridge.js +34 -21
- package/dist/runner/reporter.d.ts +2 -0
- package/dist/runner/reporter.js +11 -3
- package/dist/utils/__tests__/api-server.test.d.ts +1 -0
- package/dist/utils/__tests__/api-server.test.js +62 -0
- package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
- package/dist/utils/__tests__/demo-server.test.js +35 -0
- package/dist/utils/api-server.d.ts +19 -0
- package/dist/utils/api-server.js +179 -0
- package/dist/utils/demo-server.d.ts +5 -0
- package/dist/utils/demo-server.js +8 -1
- package/package.json +29 -25
- package/templates/default/CLAUDE.md +16 -11
- package/templates/default/global-hooks/after/mobile.hook.ts +3 -3
- package/templates/default/global-hooks/after/web.hook.ts +3 -3
- package/templates/default/global-hooks/before/mobile.hook.ts +3 -3
- package/templates/default/global-hooks/before/web.hook.ts +3 -3
- package/templates/default/package.json +3 -3
- package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
- package/templates/default/page-objects/AuthPage/web.ts +10 -0
- package/templates/default/page-objects/MatchersPage/mobile.ts +70 -46
- package/templates/default/page-objects/MatchersPage/web.ts +43 -37
- package/templates/default/page-objects/NavigationPage/mobile.ts +14 -14
- package/templates/default/page-objects/NavigationPage/web.ts +8 -8
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +42 -36
- package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -11
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -12
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -27
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -16
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -12
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -24
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
- package/templates/default/specs/ApiInterception/suite.md +10 -9
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
- package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
- package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
- package/templates/default/specs/ApiSeeding/suite.md +9 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
- package/templates/default/specs/Authentication/suite.md +9 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +9 -9
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +17 -12
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -12
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +11 -11
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +17 -12
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
- package/templates/default/specs/DeepLink/suite.md +9 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +11 -11
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +17 -12
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +29 -29
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +19 -12
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
- package/templates/default/specs/ElementMatchers/suite.md +10 -9
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
- package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
- package/templates/default/specs/PermissionRationale/suite.md +9 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +6 -6
- package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +18 -12
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +6 -6
- package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +18 -12
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
- package/templates/default/spectra.config.ts +52 -68
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +4 -4
- package/templates/default/support/actions/fillCredentials/web.action.ts +4 -4
- package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
- package/templates/default/support/actions/seedSession/web.action.ts +13 -0
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +5 -5
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +5 -5
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +6 -6
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +6 -6
- package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db +0 -0
- package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm +0 -0
- package/templates/nx/.nx/workspace-data/59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal +0 -0
- package/templates/nx/.nx/workspace-data/d/daemon.log +1577 -168
- package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
- package/templates/nx/.nx/workspace-data/file-map.json +464 -0
- package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
- package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
- package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
- package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
- package/templates/nx/CLAUDE.md +10 -10
- package/templates/nx/package.json +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -3
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -27
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -3
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -24
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +19 -19
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +17 -17
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +11 -11
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +11 -11
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +3 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +3 -3
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +6 -6
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +3 -3
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
- package/templates/nx/pnpm-lock.yaml +6663 -6663
- package/templates/nx/pnpm-workspace.yaml +9 -9
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +33 -33
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +91 -37
- package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +5 -5
- package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +8 -8
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +26 -26
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +31 -31
- package/templates/nx/shared/testing/project.json +19 -19
- package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +4 -4
- package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +4 -4
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +3 -3
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +5 -5
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +4 -4
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +6 -6
- package/templates/nx/spectra.config.ts +52 -68
- package/templates/react-component/.cursorrules +17 -0
- package/templates/react-component/.vscode/extensions.json +5 -0
- package/templates/react-component/CLAUDE.md +14 -0
- package/templates/react-component/README.md +69 -0
- package/templates/react-component/fixtures/component-mock.json +12 -0
- package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
- package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
- package/templates/react-component/package.json +27 -0
- package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
- package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
- package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
- package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
- package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
- package/templates/react-component/specs/InputComponent/suite.md +12 -0
- package/templates/react-component/spectra.config.ts +27 -0
- package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
- package/templates/react-component/src/components/Button/Button.tsx +57 -0
- package/templates/react-component/src/components/Input/Input.tsx +41 -0
- package/templates/react-component/src/test-utils.tsx +23 -0
- package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
- package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
- package/templates/react-component/tsconfig.json +8 -0
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-DPfv5_EF.js +0 -116
- package/demo-app/assets/index-gIwu2EMV.css +0 -1
- package/dist/commands/__tests__/cloud-run.test.js +0 -93
- package/dist/commands/cloud-run.d.ts +0 -13
- package/dist/commands/cloud-run.js +0 -120
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -17
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -17
- /package/dist/{commands/__tests__/cloud-run.test.d.ts → linter/__tests__/anonymous-export.test.d.ts} +0 -0
|
@@ -5,11 +5,13 @@ import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi }
|
|
|
5
5
|
import { runCommand } from '../run.js';
|
|
6
6
|
import { initCommand } from '../init.js';
|
|
7
7
|
import { DemoServer } from '../../utils/demo-server.js';
|
|
8
|
+
import { DemoApiServer } from '../../utils/api-server.js';
|
|
8
9
|
import { RustCoreBridge } from '../../runner/bridge.js';
|
|
9
10
|
describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/core/e2e-testing-plan.md)', () => {
|
|
10
11
|
let tmpDir;
|
|
11
12
|
let originalCwd;
|
|
12
13
|
let demoServer;
|
|
14
|
+
let apiServer;
|
|
13
15
|
let consoleLogs = [];
|
|
14
16
|
const originalLog = console.log;
|
|
15
17
|
beforeAll(async () => {
|
|
@@ -21,13 +23,27 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
21
23
|
catch {
|
|
22
24
|
// Server may already be running on port 5173
|
|
23
25
|
}
|
|
26
|
+
// The demo API server runs separately (port 5200). Tests that don't pass --demo still need it
|
|
27
|
+
// reachable: the web app now fetches the API cross-origin, so a JSON POST triggers a CORS
|
|
28
|
+
// preflight (OPTIONS) that isn't mocked and must reach a live API server.
|
|
29
|
+
apiServer = new DemoApiServer();
|
|
30
|
+
try {
|
|
31
|
+
await apiServer.start();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// API server may already be running on port 5200
|
|
35
|
+
}
|
|
24
36
|
});
|
|
25
37
|
afterAll(async () => {
|
|
26
|
-
// 4. AfterAll: Stop demo
|
|
38
|
+
// 4. AfterAll: Stop demo servers
|
|
27
39
|
try {
|
|
28
40
|
await demoServer.stop();
|
|
29
41
|
}
|
|
30
42
|
catch { }
|
|
43
|
+
try {
|
|
44
|
+
await apiServer.stop();
|
|
45
|
+
}
|
|
46
|
+
catch { }
|
|
31
47
|
});
|
|
32
48
|
beforeEach(async () => {
|
|
33
49
|
originalCwd = process.cwd();
|
|
@@ -64,7 +80,7 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
64
80
|
})();
|
|
65
81
|
expect(binaryExists).toBe(true);
|
|
66
82
|
// Execute testspectra run -t web on full template project
|
|
67
|
-
await runCommand(undefined, { target: 'web' });
|
|
83
|
+
await runCommand(undefined, { target: 'web', demo: true });
|
|
68
84
|
// Assert reports created
|
|
69
85
|
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
70
86
|
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
@@ -82,12 +98,12 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
82
98
|
// Performance RSS assertion (< 30 MB)
|
|
83
99
|
expect(resultJson.performance.peakMemoryRssMb).toBeLessThan(30);
|
|
84
100
|
expect(resultJson.suites.length).toBeGreaterThanOrEqual(1);
|
|
85
|
-
},
|
|
101
|
+
}, 30000);
|
|
86
102
|
// E2E-02: Single Suite Targeted Run
|
|
87
103
|
it('E2E-02: Single Suite Targeted Run executes only specified spec', async () => {
|
|
88
104
|
const targetSpec = 'specs/Playground/TC-0001-form-controls/web.test.ts';
|
|
89
105
|
expect(fs.existsSync(path.join(tmpDir, targetSpec))).toBe(true);
|
|
90
|
-
await runCommand(targetSpec, { target: 'web' });
|
|
106
|
+
await runCommand([targetSpec], { target: 'web', demo: true });
|
|
91
107
|
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
92
108
|
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
93
109
|
const resultJson = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8'));
|
|
@@ -99,10 +115,10 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
99
115
|
expect(resultJson.summary.passed).toBe(1);
|
|
100
116
|
expect(resultJson.summary.failed).toBe(0);
|
|
101
117
|
expect(resultJson.summary.passRate).toBe('100%');
|
|
102
|
-
},
|
|
118
|
+
}, 30000);
|
|
103
119
|
// E2E-03: Network Recording Audit
|
|
104
120
|
it('E2E-03: Network Recording Audit creates network-resources.json with request entries', async () => {
|
|
105
|
-
await runCommand(undefined, { target: 'web' });
|
|
121
|
+
await runCommand(undefined, { target: 'web', demo: true });
|
|
106
122
|
const networkJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'network-resources.json');
|
|
107
123
|
expect(fs.existsSync(networkJsonPath)).toBe(true);
|
|
108
124
|
const networkRaw = fs.readFileSync(networkJsonPath, 'utf-8');
|
|
@@ -114,68 +130,293 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
114
130
|
expect(first).toHaveProperty('url');
|
|
115
131
|
expect(first).toHaveProperty('method');
|
|
116
132
|
expect(first).toHaveProperty('status');
|
|
117
|
-
},
|
|
133
|
+
}, 30000);
|
|
118
134
|
// E2E-04: Semantic Logs Streaming & Summary Card Terminal
|
|
119
135
|
it('E2E-04: Semantic Logs Streaming displays natural logs and summary card', async () => {
|
|
120
|
-
await runCommand(undefined, { target: 'web' });
|
|
136
|
+
await runCommand(undefined, { target: 'web', demo: true });
|
|
121
137
|
const allOutput = consoleLogs.join('\n');
|
|
122
138
|
expect(allOutput).toContain('TESTSPECTRA EXECUTION SUMMARY');
|
|
123
139
|
expect(allOutput).toContain('PASSED');
|
|
124
140
|
expect(allOutput).toContain('Platform');
|
|
125
141
|
expect(allOutput).toContain('Duration');
|
|
126
142
|
expect(allOutput).toContain('Report');
|
|
127
|
-
},
|
|
143
|
+
}, 30000);
|
|
128
144
|
// E2E-05: Failure Handling & Trace
|
|
129
145
|
it('E2E-05: Failure Handling & Trace properly records failed status and error details', async () => {
|
|
130
146
|
// Inject a failing step / invalid selector in a new test case
|
|
131
147
|
const failingDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0099-failing');
|
|
132
148
|
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
|
-
});
|
|
149
|
+
fs.writeFileSync(path.join(failingDir, 'web.test.ts'), `it("should fail when element does not exist", async () => {
|
|
150
|
+
await Step.verifyPlaygroundState();
|
|
151
|
+
await $("#non-existent-button-999").shouldBeVisible();
|
|
152
|
+
});
|
|
137
153
|
`, 'utf-8');
|
|
138
154
|
// Mock exit to prevent process termination during unit test assertion
|
|
139
155
|
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
140
|
-
await runCommand('specs/Playground/TC-0099-failing/web.test.ts', { target: 'web' });
|
|
156
|
+
await runCommand(['specs/Playground/TC-0099-failing/web.test.ts'], { target: 'web', demo: true });
|
|
141
157
|
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
142
158
|
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
143
159
|
const resultJson = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8'));
|
|
144
160
|
// If failure was triggered, status is failed and failed count >= 1
|
|
145
161
|
expect(resultJson.status).toBeDefined();
|
|
146
162
|
exitSpy.mockRestore();
|
|
147
|
-
},
|
|
163
|
+
}, 30000);
|
|
148
164
|
// E2E-06: Spectra Static API & Assertion Mismatch Failure in Real Chromium
|
|
149
165
|
it('E2E-06: Spectra static actions and negative assertion failures strictly fail in real Chromium', async () => {
|
|
150
166
|
// 1. Test a valid spec using pure Spectra static commands
|
|
151
167
|
const spectraStaticDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0100-spectra-static');
|
|
152
168
|
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
|
-
});
|
|
169
|
+
fs.writeFileSync(path.join(spectraStaticDir, 'web.test.ts'), `it("should execute via unified Spectra static object", async () => {
|
|
170
|
+
await Spectra.navigate("/#playground");
|
|
171
|
+
await Spectra.type("#demo-email-input-pg", "automated@spectra.dev");
|
|
172
|
+
await Spectra.get("#demo-email-input-pg").shouldHaveValue("automated@spectra.dev");
|
|
173
|
+
await Spectra.wait(50);
|
|
174
|
+
await Spectra.getAll(".demo-product-item").shouldHaveLengthGreaterThan(0);
|
|
175
|
+
});
|
|
160
176
|
`, 'utf-8');
|
|
161
|
-
await runCommand('specs/Playground/TC-0100-spectra-static/web.test.ts', { target: 'web' });
|
|
177
|
+
await runCommand(['specs/Playground/TC-0100-spectra-static/web.test.ts'], { target: 'web', demo: true });
|
|
162
178
|
const resultStatic = JSON.parse(fs.readFileSync(path.join(tmpDir, '.testspectra', 'reports', 'result.json'), 'utf-8'));
|
|
163
179
|
expect(resultStatic.status).toBe('passed');
|
|
164
180
|
expect(resultStatic.summary.passed).toBe(1);
|
|
165
181
|
// 2. Test that assertion mismatch on existing element strictly FAILS (no false positive)
|
|
166
182
|
const mismatchDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0101-mismatch');
|
|
167
183
|
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
|
-
});
|
|
184
|
+
fs.writeFileSync(path.join(mismatchDir, 'web.test.ts'), `it("should strictly fail when assertion text does not match", async () => {
|
|
185
|
+
await Spectra.navigate("/#playground");
|
|
186
|
+
// Element exists, but text is wrong -> MUST throw and fail
|
|
187
|
+
await Spectra.get("#demo-email-input-pg").shouldHaveValue("completely_wrong_value_xyz");
|
|
188
|
+
});
|
|
173
189
|
`, 'utf-8');
|
|
174
190
|
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
175
|
-
await runCommand('specs/Playground/TC-0101-mismatch/web.test.ts', { target: 'web' });
|
|
191
|
+
await runCommand(['specs/Playground/TC-0101-mismatch/web.test.ts'], { target: 'web', demo: true });
|
|
176
192
|
const resultMismatch = JSON.parse(fs.readFileSync(path.join(tmpDir, '.testspectra', 'reports', 'result.json'), 'utf-8'));
|
|
177
193
|
expect(resultMismatch.status).toBe('failed');
|
|
178
194
|
expect(resultMismatch.summary.failed).toBeGreaterThanOrEqual(1);
|
|
179
195
|
exitSpy.mockRestore();
|
|
180
196
|
}, 25000);
|
|
197
|
+
// E2E-07: Spectra.intercept GET Mocking & Dynamic respondWith on Web in Real Chromium
|
|
198
|
+
it('E2E-07: Spectra.intercept GET Mocking & Dynamic respondWith on Web in real Chromium', async () => {
|
|
199
|
+
const targetSpec = 'specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts';
|
|
200
|
+
expect(fs.existsSync(path.join(tmpDir, targetSpec))).toBe(true);
|
|
201
|
+
await runCommand([targetSpec], { target: 'web' });
|
|
202
|
+
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
203
|
+
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
204
|
+
const resultJson = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8'));
|
|
205
|
+
expect(resultJson.version).toBe('2.0.0');
|
|
206
|
+
expect(resultJson.platform).toBe('web');
|
|
207
|
+
expect(resultJson.status).toBe('passed');
|
|
208
|
+
expect(resultJson.summary.totalSuites).toBe(1);
|
|
209
|
+
expect(resultJson.summary.totalTests).toBe(1);
|
|
210
|
+
expect(resultJson.summary.passed).toBe(1);
|
|
211
|
+
expect(resultJson.summary.failed).toBe(0);
|
|
212
|
+
expect(resultJson.summary.passRate).toBe('100%');
|
|
213
|
+
// Assert network interception log was recorded in network-resources.json
|
|
214
|
+
const networkJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'network-resources.json');
|
|
215
|
+
expect(fs.existsSync(networkJsonPath)).toBe(true);
|
|
216
|
+
const networkEntries = JSON.parse(fs.readFileSync(networkJsonPath, 'utf-8'));
|
|
217
|
+
const interceptedPostReq = networkEntries.find((entry) => entry.url && entry.url.includes('/api/posts'));
|
|
218
|
+
expect(interceptedPostReq).toBeDefined();
|
|
219
|
+
expect(interceptedPostReq.status).toBe(200);
|
|
220
|
+
}, 20000);
|
|
221
|
+
// E2E-08: Spectra.intercept POST Mocking with status 201 and payload verification on Web in Real Chromium
|
|
222
|
+
it('E2E-08: Spectra.intercept POST Mocking with status 201 and payload verification on Web', async () => {
|
|
223
|
+
const targetSpec = 'specs/ApiInterception/TC-0009-post-mocking/web.test.ts';
|
|
224
|
+
expect(fs.existsSync(path.join(tmpDir, targetSpec))).toBe(true);
|
|
225
|
+
await runCommand([targetSpec], { target: 'web' });
|
|
226
|
+
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
227
|
+
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
228
|
+
const resultJson = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8'));
|
|
229
|
+
expect(resultJson.version).toBe('2.0.0');
|
|
230
|
+
expect(resultJson.platform).toBe('web');
|
|
231
|
+
expect(resultJson.status).toBe('passed');
|
|
232
|
+
expect(resultJson.summary.totalSuites).toBe(1);
|
|
233
|
+
expect(resultJson.summary.totalTests).toBe(1);
|
|
234
|
+
expect(resultJson.summary.passed).toBe(1);
|
|
235
|
+
expect(resultJson.summary.failed).toBe(0);
|
|
236
|
+
const networkJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'network-resources.json');
|
|
237
|
+
expect(fs.existsSync(networkJsonPath)).toBe(true);
|
|
238
|
+
const networkEntries = JSON.parse(fs.readFileSync(networkJsonPath, 'utf-8'));
|
|
239
|
+
const postReq = networkEntries.find((entry) => entry.url && entry.url.includes('/api/posts') && entry.status === 201);
|
|
240
|
+
expect(postReq).toBeDefined();
|
|
241
|
+
}, 20000);
|
|
242
|
+
// E2E-09: Mobile API Interception & Socket Driver on Android target
|
|
243
|
+
it('E2E-09: Mobile API Interception and verification on Android target', async () => {
|
|
244
|
+
const net = await import('net');
|
|
245
|
+
// Start a mock Spectra Native Android Driver TCP Server on port 8200
|
|
246
|
+
const mockAndroidServer = net.createServer((socket) => {
|
|
247
|
+
let rxBuf = Buffer.alloc(0);
|
|
248
|
+
let fetchClickCount = 0;
|
|
249
|
+
socket.on('data', (chunk) => {
|
|
250
|
+
rxBuf = Buffer.concat([rxBuf, chunk]);
|
|
251
|
+
while (rxBuf.length >= 4) {
|
|
252
|
+
const payloadLen = rxBuf.readUInt32BE(0);
|
|
253
|
+
if (rxBuf.length < 4 + payloadLen)
|
|
254
|
+
break;
|
|
255
|
+
const payloadStr = rxBuf.slice(4, 4 + payloadLen).toString('utf8');
|
|
256
|
+
rxBuf = rxBuf.slice(4 + payloadLen);
|
|
257
|
+
try {
|
|
258
|
+
const msg = JSON.parse(payloadStr);
|
|
259
|
+
const cmd = msg.cmd;
|
|
260
|
+
const id = msg.id;
|
|
261
|
+
const params = msg.params || {};
|
|
262
|
+
let responseData = {};
|
|
263
|
+
if (cmd === 'FIND_ALL') {
|
|
264
|
+
const sel = (params.selector || '').toLowerCase();
|
|
265
|
+
// The driver now reads via a single full-hierarchy dump (`FIND_ALL xpath //*`) and
|
|
266
|
+
// resolves selectors in memory, while actions use a targeted lookup for a live
|
|
267
|
+
// nodeId. This mock returns the same node set for both, so either path resolves.
|
|
268
|
+
//
|
|
269
|
+
// TC-0008's mobile.test.ts drives 4 clicks in its single it() block: base mock
|
|
270
|
+
// (2 items) -> respondWith dynamic update (1 item) -> respondOnce queued response
|
|
271
|
+
// (1 item) -> reverted to base mock (2 items again).
|
|
272
|
+
const items = fetchClickCount === 2
|
|
273
|
+
? [{ nodeId: 'item-dyn-1', text: 'Dynamic Updated Mock Title' }]
|
|
274
|
+
: fetchClickCount === 3
|
|
275
|
+
? [{ nodeId: 'item-once-1', text: 'One-Time Queued Response' }]
|
|
276
|
+
: [
|
|
277
|
+
{ nodeId: 'item-1', text: 'Mocked Post 1 - TestSpectra Matchers' },
|
|
278
|
+
{ nodeId: 'item-2', text: 'Mocked Post 2 - Cross-Platform Automation' },
|
|
279
|
+
];
|
|
280
|
+
const mkNode = (nodeId, text, id) => ({
|
|
281
|
+
nodeId,
|
|
282
|
+
text,
|
|
283
|
+
resourceId: id,
|
|
284
|
+
contentDesc: id,
|
|
285
|
+
bounds: [0, 0, 10, 10],
|
|
286
|
+
displayed: true,
|
|
287
|
+
clickable: true,
|
|
288
|
+
enabled: true,
|
|
289
|
+
checked: false,
|
|
290
|
+
selected: false,
|
|
291
|
+
focused: false,
|
|
292
|
+
});
|
|
293
|
+
const tree = [
|
|
294
|
+
mkNode('toggle-1', '☰', 'toggle-sidebar-btn'),
|
|
295
|
+
mkNode('close-1', '✕', 'close-sidebar-btn'),
|
|
296
|
+
mkNode('nav-1', 'API Fetch', 'nav-item-api-fetch'),
|
|
297
|
+
mkNode('fetch-api-btn-node', 'Fetch Data', 'fetch-api-btn'),
|
|
298
|
+
mkNode('badge-1', '200 OK', 'fetch-status-badge'),
|
|
299
|
+
...items.map((n, i) => mkNode(n.nodeId || `item-${i}`, n.text, 'api-post-item')),
|
|
300
|
+
mkNode('badge-2', '201 Created', 'post-status-badge'),
|
|
301
|
+
mkNode('banner-1', 'Successfully created post', 'post-success-banner'),
|
|
302
|
+
mkNode('resp-json-1', '{"title": "TestSpectra Automated Post"}', 'post-response-json'),
|
|
303
|
+
];
|
|
304
|
+
responseData = sel.startsWith('//')
|
|
305
|
+
? tree
|
|
306
|
+
: tree.filter((n) => n.resourceId === sel || n.contentDesc === sel);
|
|
307
|
+
}
|
|
308
|
+
else if (cmd === 'CLICK' || cmd === 'PERFORM_ACTION') {
|
|
309
|
+
if (params.node_id === 'fetch-api-btn-node' || params.nodeId === 'fetch-api-btn-node') {
|
|
310
|
+
fetchClickCount++;
|
|
311
|
+
}
|
|
312
|
+
responseData = true;
|
|
313
|
+
}
|
|
314
|
+
else if (cmd === 'GET_ATTRIBUTE') {
|
|
315
|
+
const attr = params.attr;
|
|
316
|
+
if (attr === 'text') {
|
|
317
|
+
responseData = '200 OK';
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
responseData = 'Mock Attribute';
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
const respObj = { id, ok: true, data: responseData };
|
|
324
|
+
const respStr = JSON.stringify(respObj);
|
|
325
|
+
const respLen = Buffer.byteLength(respStr, 'utf8');
|
|
326
|
+
const frame = Buffer.alloc(4 + respLen);
|
|
327
|
+
frame.writeUInt32BE(respLen, 0);
|
|
328
|
+
frame.write(respStr, 4, 'utf8');
|
|
329
|
+
socket.write(frame);
|
|
330
|
+
}
|
|
331
|
+
catch { }
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
let isMockServerRunning = false;
|
|
336
|
+
await new Promise((resolve) => {
|
|
337
|
+
mockAndroidServer.once('error', (err) => {
|
|
338
|
+
if (err.code === 'EADDRINUSE') {
|
|
339
|
+
resolve();
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
mockAndroidServer.listen(8200, '127.0.0.1', () => {
|
|
343
|
+
isMockServerRunning = true;
|
|
344
|
+
resolve();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
try {
|
|
348
|
+
const configPath = path.join(tmpDir, 'spectra.config.ts');
|
|
349
|
+
if (fs.existsSync(configPath)) {
|
|
350
|
+
let conf = fs.readFileSync(configPath, 'utf-8');
|
|
351
|
+
conf = conf.replace(/appPackage:\s*["'][^"']*["'],/, 'appPackage: "",');
|
|
352
|
+
fs.writeFileSync(configPath, conf, 'utf-8');
|
|
353
|
+
}
|
|
354
|
+
const targetSpec = 'specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts';
|
|
355
|
+
expect(fs.existsSync(path.join(tmpDir, targetSpec))).toBe(true);
|
|
356
|
+
await runCommand([targetSpec], { target: 'android', device: 'mock-device' });
|
|
357
|
+
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
358
|
+
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
359
|
+
const resultJson = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8'));
|
|
360
|
+
expect(resultJson.version).toBe('2.0.0');
|
|
361
|
+
expect(resultJson.platform).toBe('android');
|
|
362
|
+
expect(resultJson.status).toBe('passed');
|
|
363
|
+
expect(resultJson.summary.passed).toBe(1);
|
|
364
|
+
expect(resultJson.summary.failed).toBe(0);
|
|
365
|
+
}
|
|
366
|
+
finally {
|
|
367
|
+
if (isMockServerRunning) {
|
|
368
|
+
await new Promise((resolve) => {
|
|
369
|
+
mockAndroidServer.close(() => resolve());
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}, 25000);
|
|
374
|
+
// E2E-10: Spectra.intercept FIFO respondOnce sequential polling and abort simulation
|
|
375
|
+
it('E2E-10: Spectra.intercept FIFO respondOnce sequential polling and abort simulation in real Chromium', async () => {
|
|
376
|
+
const fifoAbortDir = path.join(tmpDir, 'specs', 'ApiInterception', 'TC-0010-fifo-abort');
|
|
377
|
+
fs.mkdirSync(fifoAbortDir, { recursive: true });
|
|
378
|
+
fs.writeFileSync(path.join(fifoAbortDir, 'web.test.ts'), `it("should handle FIFO respondOnce queue and abort simulation", async () => {
|
|
379
|
+
await MatchersPage.open("api-fetch");
|
|
380
|
+
|
|
381
|
+
// Setup intercept with default fallback and FIFO queue
|
|
382
|
+
const mock = await Spectra.intercept("api/posts", "GET", [
|
|
383
|
+
{ id: 999, title: "Default Fallback Post" }
|
|
384
|
+
]);
|
|
385
|
+
|
|
386
|
+
await mock.respondOnce([
|
|
387
|
+
{ id: 101, title: "FIFO Queued Post #1" }
|
|
388
|
+
]);
|
|
389
|
+
await mock.respondOnce([
|
|
390
|
+
{ id: 102, title: "FIFO Queued Post #2" }
|
|
391
|
+
]);
|
|
392
|
+
|
|
393
|
+
// 1. First fetch triggers FIFO item #1
|
|
394
|
+
await MatchersPage.fetchApiButton.click();
|
|
395
|
+
await MatchersPage.fetchedItems.first().shouldContainText("FIFO Queued Post #1");
|
|
396
|
+
|
|
397
|
+
// 2. Second fetch triggers FIFO item #2
|
|
398
|
+
await MatchersPage.fetchApiButton.click();
|
|
399
|
+
await MatchersPage.fetchedItems.first().shouldContainText("FIFO Queued Post #2");
|
|
400
|
+
|
|
401
|
+
// 3. Third fetch falls back to default response
|
|
402
|
+
await MatchersPage.fetchApiButton.click();
|
|
403
|
+
await MatchersPage.fetchedItems.first().shouldContainText("Default Fallback Post");
|
|
404
|
+
|
|
405
|
+
// 4. Test abort simulation
|
|
406
|
+
await mock.abort("Failed");
|
|
407
|
+
await MatchersPage.fetchApiButton.click();
|
|
408
|
+
// On network failure, demo app shows fallback 200 OK from catch block
|
|
409
|
+
await MatchersPage.fetchStatusBadge.shouldHaveText("200 OK");
|
|
410
|
+
});
|
|
411
|
+
`, 'utf-8');
|
|
412
|
+
await runCommand(['specs/ApiInterception/TC-0010-fifo-abort/web.test.ts'], { target: 'web' });
|
|
413
|
+
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
414
|
+
expect(fs.existsSync(resultJsonPath)).toBe(true);
|
|
415
|
+
const resultJson = JSON.parse(fs.readFileSync(resultJsonPath, 'utf-8'));
|
|
416
|
+
expect(resultJson.version).toBe('2.0.0');
|
|
417
|
+
expect(resultJson.platform).toBe('web');
|
|
418
|
+
expect(resultJson.status).toBe('passed');
|
|
419
|
+
expect(resultJson.summary.passed).toBe(1);
|
|
420
|
+
expect(resultJson.summary.failed).toBe(0);
|
|
421
|
+
}, 25000);
|
|
181
422
|
});
|
|
@@ -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;
|
|
@@ -32,26 +30,24 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
|
|
|
32
30
|
};
|
|
33
31
|
});
|
|
34
32
|
// Run single spec
|
|
35
|
-
await runCommand('specs/Auth/TC-0001/web.test.ts');
|
|
33
|
+
await runCommand(['specs/Auth/TC-0001/web.test.ts']);
|
|
36
34
|
expect(bridgeOptionsReceived).not.toBeNull();
|
|
37
35
|
// 1 test case -> worker count must be clamped to 1
|
|
38
36
|
expect(bridgeOptionsReceived.config.webConfig.maxConcurrentSessions).toBe('1');
|
|
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;
|
|
@@ -64,13 +60,14 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
|
|
|
64
60
|
logs: [],
|
|
65
61
|
};
|
|
66
62
|
});
|
|
67
|
-
await runCommand('specs/Auth/TC-0001/web.test.ts', { concurrency: 4 });
|
|
63
|
+
await runCommand(['specs/Auth/TC-0001/web.test.ts'], { concurrency: 4 });
|
|
68
64
|
expect(bridgeOptionsReceived).not.toBeNull();
|
|
69
65
|
// 1 item clamped to 1
|
|
70
66
|
expect(bridgeOptionsReceived.config.webConfig.maxConcurrentSessions).toBe('1');
|
|
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;
|
|
@@ -94,7 +90,7 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
|
|
|
94
90
|
logs: [],
|
|
95
91
|
};
|
|
96
92
|
});
|
|
97
|
-
await runCommand('specs/Auth/TC-0001/android.test.ts', {
|
|
93
|
+
await runCommand(['specs/Auth/TC-0001/android.test.ts'], {
|
|
98
94
|
target: 'android',
|
|
99
95
|
devices: 'emulator-5554,emulator-5556',
|
|
100
96
|
});
|
|
@@ -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;
|
|
@@ -126,7 +122,7 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
|
|
|
126
122
|
logs: [],
|
|
127
123
|
};
|
|
128
124
|
});
|
|
129
|
-
await runCommand('specs/Auth/TC-0001/android.test.ts', {
|
|
125
|
+
await runCommand(['specs/Auth/TC-0001/android.test.ts'], {
|
|
130
126
|
target: 'android',
|
|
131
127
|
device: 'emulator-5554',
|
|
132
128
|
});
|
|
@@ -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;
|
|
@@ -155,7 +151,7 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
|
|
|
155
151
|
logs: [],
|
|
156
152
|
};
|
|
157
153
|
});
|
|
158
|
-
await runCommand('specs/Auth/TC-0001/android.test.ts', {
|
|
154
|
+
await runCommand(['specs/Auth/TC-0001/android.test.ts'], {
|
|
159
155
|
target: 'android',
|
|
160
156
|
allDevices: true,
|
|
161
157
|
});
|
|
@@ -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;
|
|
@@ -184,7 +180,7 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
|
|
|
184
180
|
logs: [],
|
|
185
181
|
};
|
|
186
182
|
});
|
|
187
|
-
await runCommand('specs/Auth/TC-0001/android.test.ts', {
|
|
183
|
+
await runCommand(['specs/Auth/TC-0001/android.test.ts'], {
|
|
188
184
|
target: 'android',
|
|
189
185
|
multiDevice: true,
|
|
190
186
|
});
|
|
@@ -194,4 +190,84 @@ describe('runCommand Integration & Dynamic Worker Sizing', () => {
|
|
|
194
190
|
expect(bridgeOptionsReceived.config.androidConfig.devices).toBe('auto');
|
|
195
191
|
expect(bridgeOptionsReceived.targetDevice).toBe('auto');
|
|
196
192
|
});
|
|
193
|
+
it('sets process.exitCode to 1 when tests fail', async () => {
|
|
194
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
195
|
+
vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
|
|
196
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
197
|
+
webConfig: { baseUrl: 'http://localhost:3000' },
|
|
198
|
+
androidConfig: {},
|
|
199
|
+
iosConfig: {},
|
|
200
|
+
executionConfig: {},
|
|
201
|
+
});
|
|
202
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async () => {
|
|
203
|
+
return {
|
|
204
|
+
status: 'failed',
|
|
205
|
+
duration: '1.5s',
|
|
206
|
+
passedCount: 1,
|
|
207
|
+
failedCount: 1,
|
|
208
|
+
results: [],
|
|
209
|
+
logs: [],
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
process.exitCode = undefined;
|
|
213
|
+
await runCommand(['specs/ApiInterception'], { target: 'web' });
|
|
214
|
+
expect(process.exitCode).toBe(1);
|
|
215
|
+
process.exitCode = undefined; // reset
|
|
216
|
+
});
|
|
217
|
+
it('incorporates TESTSPECTRA_CARRIED_OVER into summary card calculation', async () => {
|
|
218
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
219
|
+
vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
|
|
220
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
221
|
+
webConfig: { baseUrl: 'http://localhost:3000' },
|
|
222
|
+
androidConfig: {},
|
|
223
|
+
iosConfig: {},
|
|
224
|
+
executionConfig: {},
|
|
225
|
+
});
|
|
226
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async () => {
|
|
227
|
+
return {
|
|
228
|
+
status: 'passed',
|
|
229
|
+
duration: '2.0s',
|
|
230
|
+
passedCount: 1,
|
|
231
|
+
failedCount: 0,
|
|
232
|
+
results: [],
|
|
233
|
+
logs: [],
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
237
|
+
process.env.TESTSPECTRA_CARRIED_OVER = JSON.stringify({
|
|
238
|
+
passed: 3,
|
|
239
|
+
durationMs: 4000,
|
|
240
|
+
});
|
|
241
|
+
await runCommand(['specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts'], { target: 'web' });
|
|
242
|
+
delete process.env.TESTSPECTRA_CARRIED_OVER;
|
|
243
|
+
const logged = consoleSpy.mock.calls.map((c) => c[0]).join('\n');
|
|
244
|
+
expect(logged).toContain('4 passed');
|
|
245
|
+
expect(logged).toContain('3 carried over');
|
|
246
|
+
expect(logged).toContain('4 total');
|
|
247
|
+
expect(logged).toContain('6.0s');
|
|
248
|
+
consoleSpy.mockRestore();
|
|
249
|
+
});
|
|
250
|
+
it('correctly passes multiple specs to RustCoreBridge payload', async () => {
|
|
251
|
+
vi.spyOn(TypeGenerator, 'writeDeclarationFiles').mockImplementation(() => { });
|
|
252
|
+
vi.spyOn(TypeGenerator, 'syncEnvDeclaration').mockResolvedValue(undefined);
|
|
253
|
+
vi.spyOn(ConfigLoader, 'loadConfig').mockResolvedValue({
|
|
254
|
+
webConfig: { baseUrl: 'http://localhost:3000' },
|
|
255
|
+
androidConfig: {},
|
|
256
|
+
iosConfig: {},
|
|
257
|
+
executionConfig: {},
|
|
258
|
+
});
|
|
259
|
+
let bridgeOptionsReceived = null;
|
|
260
|
+
vi.spyOn(RustCoreBridge, 'run').mockImplementation(async (options) => {
|
|
261
|
+
bridgeOptionsReceived = options;
|
|
262
|
+
return {
|
|
263
|
+
status: 'passed',
|
|
264
|
+
duration: '1.0s',
|
|
265
|
+
results: [],
|
|
266
|
+
logs: [],
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
await runCommand(['specs/Auth/TC-0001/web.test.ts', 'specs/Auth/TC-0002/web.test.ts']);
|
|
270
|
+
expect(bridgeOptionsReceived).not.toBeNull();
|
|
271
|
+
expect(bridgeOptionsReceived.specs).toEqual(['specs/Auth/TC-0001/web.test.ts', 'specs/Auth/TC-0002/web.test.ts']);
|
|
272
|
+
});
|
|
197
273
|
});
|