@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
package/dist/commands/run.d.ts
CHANGED
|
@@ -12,7 +12,6 @@ export interface RunCommandOptions {
|
|
|
12
12
|
workdir?: string;
|
|
13
13
|
output?: string;
|
|
14
14
|
demo?: boolean;
|
|
15
|
-
baseUrl?: string;
|
|
16
15
|
failed?: boolean | string;
|
|
17
16
|
concurrency?: string | number;
|
|
18
17
|
licenseKey?: string;
|
|
@@ -28,5 +27,5 @@ export declare function renderTimingBreakdown(performance: {
|
|
|
28
27
|
wallClockDurationMs?: number;
|
|
29
28
|
totalTests?: number;
|
|
30
29
|
duration?: string;
|
|
31
|
-
}): void;
|
|
32
|
-
export declare function runCommand(
|
|
30
|
+
}, platform?: string): void;
|
|
31
|
+
export declare function runCommand(specPaths?: string[], options?: RunCommandOptions): Promise<void>;
|
package/dist/commands/run.js
CHANGED
|
@@ -5,6 +5,7 @@ import { RustCoreBridge } from '../runner/bridge.js';
|
|
|
5
5
|
import { Reporter } from '../runner/reporter.js';
|
|
6
6
|
import { TypeGenerator } from '../types/generator.js';
|
|
7
7
|
import { DemoServer } from '../utils/demo-server.js';
|
|
8
|
+
import { DemoApiServer } from '../utils/api-server.js';
|
|
8
9
|
function renderSummaryCard(options) {
|
|
9
10
|
const width = 64;
|
|
10
11
|
const borderColor = options.isPassed ? '\x1b[38;2;52;211;153m' : '\x1b[38;2;248;113;113m';
|
|
@@ -46,6 +47,11 @@ function renderSummaryCard(options) {
|
|
|
46
47
|
const statusBadge = options.isPassed ? `${green}✓ PASSED${reset}` : `${red}✗ FAILED${reset}`;
|
|
47
48
|
const passedSuites = options.passedSuites ?? (options.isPassed ? options.suiteCount : 0);
|
|
48
49
|
const failedSuites = options.failedSuites ?? (options.isPassed ? 0 : options.suiteCount);
|
|
50
|
+
const testsPassedText = options.carriedPassed
|
|
51
|
+
? `${options.passedCount > 0 ? `${green}${options.passedCount} passed${reset}` : `${options.passedCount} passed`} ${dim}(${options.carriedPassed} carried over)${reset}`
|
|
52
|
+
: options.passedCount > 0
|
|
53
|
+
? `${green}${options.passedCount} passed${reset}`
|
|
54
|
+
: `${options.passedCount} passed`;
|
|
49
55
|
console.log(`\n${top}`);
|
|
50
56
|
console.log(headerRow(`${boldWhite}✨ TESTSPECTRA EXECUTION SUMMARY${reset}`));
|
|
51
57
|
console.log(mid);
|
|
@@ -53,7 +59,7 @@ function renderSummaryCard(options) {
|
|
|
53
59
|
console.log(row(`${dim}Suites${reset}`, `${passedSuites > 0 ? `${green}${passedSuites} passed${reset}` : `${passedSuites} passed`}, ` +
|
|
54
60
|
`${failedSuites > 0 ? `${red}${failedSuites} failed${reset}` : `${failedSuites} failed`} ` +
|
|
55
61
|
`${dim}(${options.suiteCount} total)${reset}`));
|
|
56
|
-
console.log(row(`${dim}Tests${reset}`, `${
|
|
62
|
+
console.log(row(`${dim}Tests${reset}`, `${testsPassedText}, ` +
|
|
57
63
|
`${options.failedCount > 0 ? `${red}${options.failedCount} failed${reset}` : `${options.failedCount} failed`} ` +
|
|
58
64
|
`${dim}(${options.testCount} total)${reset}`));
|
|
59
65
|
console.log(row(`${dim}Platform${reset}`, `${cyan}${options.platform}${reset}`));
|
|
@@ -64,7 +70,13 @@ function renderSummaryCard(options) {
|
|
|
64
70
|
}
|
|
65
71
|
console.log(`${bot}\n`);
|
|
66
72
|
}
|
|
67
|
-
|
|
73
|
+
const TIMING_PHASE_LABELS = {
|
|
74
|
+
web: { boot: '1. Master Chromium Process Boot', handshake: '3. Incognito Context & CDP Handshake' },
|
|
75
|
+
android: { boot: '1. Android Driver Bootstrap (ADB + Agent)', handshake: '3. Device Session Handshake' },
|
|
76
|
+
ios: { boot: '1. iOS Driver Bootstrap (Appium)', handshake: '3. WDA Session Handshake' },
|
|
77
|
+
};
|
|
78
|
+
export function renderTimingBreakdown(performance, platform = 'web') {
|
|
79
|
+
const phaseLabels = TIMING_PHASE_LABELS[platform] ?? TIMING_PHASE_LABELS.web;
|
|
68
80
|
const yellowBold = '\x1b[1;38;2;251;191;36m';
|
|
69
81
|
const reset = '\x1b[0m';
|
|
70
82
|
const borderColor = '\x1b[38;2;75;85;99m';
|
|
@@ -89,20 +101,31 @@ export function renderTimingBreakdown(performance) {
|
|
|
89
101
|
console.log(`${borderColor}┌──────────────────────────────────────────┬───────────────────┐${reset}`);
|
|
90
102
|
console.log(`${borderColor}│${reset} Execution Phase ${borderColor}│${reset} Duration / Detail ${borderColor}│${reset}`);
|
|
91
103
|
console.log(`${borderColor}├──────────────────────────────────────────┼───────────────────┤${reset}`);
|
|
92
|
-
console.log(`${borderColor}│${reset}
|
|
93
|
-
console.log(`${borderColor}│${reset} 2. Worker Spawning & Runtime Overhead
|
|
94
|
-
console.log(`${borderColor}│${reset}
|
|
104
|
+
console.log(`${borderColor}│${reset} ${padRight(phaseLabels.boot, 40)} ${borderColor}│${reset} ${pad(performance.masterBootLatencyMs ?? 0, 13)} ms ${borderColor}│${reset}`);
|
|
105
|
+
console.log(`${borderColor}│${reset} ${padRight('2. Worker Spawning & Runtime Overhead', 40)} ${borderColor}│${reset} ${pad(performance.runnerOverheadMs ?? 0, 13)} ms ${borderColor}│${reset}`);
|
|
106
|
+
console.log(`${borderColor}│${reset} ${padRight(phaseLabels.handshake, 40)} ${borderColor}│${reset} ${pad(contextHandshakeMs, 10)} ms (avg)${borderColor}│${reset}`);
|
|
95
107
|
console.log(`${borderColor}│${reset} ${padRight(testLabel, 40)} ${borderColor}│${reset} ${pad(testDetail, 17)} ${borderColor}│${reset}`);
|
|
96
|
-
console.log(`${borderColor}│${reset} 5. Context Teardown / Cleanup
|
|
108
|
+
console.log(`${borderColor}│${reset} ${padRight('5. Context Teardown / Cleanup', 40)} ${borderColor}│${reset} ${pad(performance.avgTeardownMs ?? 0, 10)} ms (avg)${borderColor}│${reset}`);
|
|
97
109
|
console.log(`${borderColor}├──────────────────────────────────────────┼───────────────────┤${reset}`);
|
|
98
110
|
console.log(`${borderColor}│${reset} Total Wall-Clock Execution Time ${borderColor}│${reset} ${pad(wallClock, 15)} ${borderColor}│${reset}`);
|
|
99
111
|
console.log(`${borderColor}└──────────────────────────────────────────┴───────────────────┘${reset}\n`);
|
|
100
112
|
}
|
|
101
|
-
export async function runCommand(
|
|
113
|
+
export async function runCommand(specPaths, options = {}) {
|
|
102
114
|
const cwd = process.cwd();
|
|
103
|
-
|
|
115
|
+
// Opt-in duration logging (TESTSPECTRA_DURATION_LOG): resolve to one absolute JSONL path that
|
|
116
|
+
// both this host process (Rust orchestrator) and every spawned Bun worker append to, and stamp a
|
|
117
|
+
// run id so repeated runs can be told apart. Off by default; toggled purely via the env var.
|
|
118
|
+
if (process.env.TESTSPECTRA_DURATION_LOG) {
|
|
119
|
+
const v = process.env.TESTSPECTRA_DURATION_LOG;
|
|
120
|
+
const abs = v === '1' || v === 'true' ? path.join(cwd, '.testspectra', 'logs', 'duration.jsonl') : path.resolve(cwd, v);
|
|
121
|
+
process.env.TESTSPECTRA_DURATION_LOG = abs;
|
|
122
|
+
if (!process.env.TESTSPECTRA_RUN_ID)
|
|
123
|
+
process.env.TESTSPECTRA_RUN_ID = String(Date.now());
|
|
124
|
+
}
|
|
125
|
+
const effectiveSpecPaths = specPaths && specPaths.length > 0 ? specPaths : options.spec ? [options.spec] : undefined;
|
|
104
126
|
// Ensure latest ambient types are up-to-date
|
|
105
127
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
128
|
+
await TypeGenerator.syncEnvDeclaration(cwd);
|
|
106
129
|
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
107
130
|
const globalAppDataPath = path.join(home, '.testspectra');
|
|
108
131
|
const appDataPath = options.workdir ? path.resolve(options.workdir) : globalAppDataPath;
|
|
@@ -135,6 +158,7 @@ export async function runCommand(specPath, options = {}) {
|
|
|
135
158
|
}
|
|
136
159
|
const config = await ConfigLoader.loadConfig(cwd);
|
|
137
160
|
let demoServer = null;
|
|
161
|
+
let apiServer = null;
|
|
138
162
|
try {
|
|
139
163
|
if (options.demo) {
|
|
140
164
|
demoServer = new DemoServer(5173);
|
|
@@ -146,9 +170,14 @@ export async function runCommand(specPath, options = {}) {
|
|
|
146
170
|
catch (err) {
|
|
147
171
|
console.warn(`\x1b[33m[TestSpectra] Warning: Could not start built-in demo server: ${err.message}\x1b[0m`);
|
|
148
172
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
apiServer = new DemoApiServer();
|
|
174
|
+
try {
|
|
175
|
+
const { url, port } = await apiServer.start();
|
|
176
|
+
console.log(`\x1b[36m[TestSpectra]\x1b[0m 🌐 Demo API server automatically started at: \x1b[33m${url}\x1b[0m (port ${port}, self-hosted /api/*)`);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
console.warn(`\x1b[33m[TestSpectra] Warning: Could not start built-in demo API server: ${err.message}\x1b[0m`);
|
|
180
|
+
}
|
|
152
181
|
}
|
|
153
182
|
if (options.headed) {
|
|
154
183
|
config.webConfig.headless = false;
|
|
@@ -207,7 +236,7 @@ export async function runCommand(specPath, options = {}) {
|
|
|
207
236
|
targetDevice,
|
|
208
237
|
licenseKey: options.licenseKey,
|
|
209
238
|
outputJsonPath,
|
|
210
|
-
|
|
239
|
+
specs: effectiveSpecPaths,
|
|
211
240
|
concurrency: userConcurrency,
|
|
212
241
|
failedOnly: isFailedMode,
|
|
213
242
|
failedTarget,
|
|
@@ -216,16 +245,41 @@ export async function runCommand(specPath, options = {}) {
|
|
|
216
245
|
const relReport = path.relative(cwd, outputJsonPath);
|
|
217
246
|
const networkReportFile = path.join(path.dirname(outputJsonPath), 'network-resources.json');
|
|
218
247
|
const relNetworkReport = fs.existsSync(networkReportFile) ? path.relative(cwd, networkReportFile) : undefined;
|
|
248
|
+
let carriedOver;
|
|
249
|
+
if (process.env.TESTSPECTRA_CARRIED_OVER) {
|
|
250
|
+
try {
|
|
251
|
+
carriedOver = JSON.parse(process.env.TESTSPECTRA_CARRIED_OVER);
|
|
252
|
+
}
|
|
253
|
+
catch { }
|
|
254
|
+
}
|
|
255
|
+
const carriedPassed = carriedOver?.passed || 0;
|
|
256
|
+
const carriedDurationMs = carriedOver?.durationMs || 0;
|
|
219
257
|
let suiteCount = 1;
|
|
220
258
|
let passedSuites = isPassed ? 1 : 0;
|
|
221
259
|
let failedSuites = isPassed ? 0 : 1;
|
|
222
|
-
let
|
|
260
|
+
let basePassed = result.passedCount ?? (isPassed ? 1 : 0);
|
|
261
|
+
let baseFailed = result.failedCount ?? (isPassed ? 0 : 1);
|
|
262
|
+
const totalPassed = basePassed + carriedPassed;
|
|
263
|
+
const totalFailed = baseFailed;
|
|
264
|
+
const totalTests = basePassed + baseFailed + carriedPassed;
|
|
265
|
+
let finalDuration = result.duration;
|
|
266
|
+
if (carriedDurationMs > 0) {
|
|
267
|
+
const match = result.duration.match(/([\d.]+)\s*(ms|s|m)/);
|
|
268
|
+
let runMs = 0;
|
|
269
|
+
if (match) {
|
|
270
|
+
const val = parseFloat(match[1]);
|
|
271
|
+
const unit = match[2];
|
|
272
|
+
runMs = unit === 's' ? val * 1000 : unit === 'm' ? val * 60000 : val;
|
|
273
|
+
}
|
|
274
|
+
const totalMs = runMs + carriedDurationMs;
|
|
275
|
+
finalDuration = totalMs < 1000 ? `${Math.round(totalMs)}ms` : `${(totalMs / 1000).toFixed(1)}s`;
|
|
276
|
+
}
|
|
277
|
+
const overallPassed = isPassed && totalFailed === 0;
|
|
223
278
|
if (fs.existsSync(outputJsonPath)) {
|
|
224
279
|
try {
|
|
225
280
|
const fullReport = JSON.parse(fs.readFileSync(outputJsonPath, 'utf8'));
|
|
226
281
|
if (fullReport.summary) {
|
|
227
282
|
suiteCount = fullReport.summary.totalSuites ?? 1;
|
|
228
|
-
testCount = fullReport.summary.totalTests ?? testCount;
|
|
229
283
|
}
|
|
230
284
|
if (fullReport.suites && Array.isArray(fullReport.suites)) {
|
|
231
285
|
failedSuites = fullReport.suites.filter((s) => (typeof s.failed === 'number' && s.failed > 0) ||
|
|
@@ -235,16 +289,17 @@ export async function runCommand(specPath, options = {}) {
|
|
|
235
289
|
}
|
|
236
290
|
if (fullReport.performance) {
|
|
237
291
|
renderSummaryCard({
|
|
238
|
-
isPassed,
|
|
239
|
-
statusText:
|
|
240
|
-
duration:
|
|
292
|
+
isPassed: overallPassed,
|
|
293
|
+
statusText: overallPassed ? 'PASSED' : 'FAILED',
|
|
294
|
+
duration: finalDuration,
|
|
241
295
|
platform,
|
|
242
296
|
suiteCount,
|
|
243
297
|
passedSuites,
|
|
244
298
|
failedSuites,
|
|
245
|
-
testCount,
|
|
246
|
-
passedCount:
|
|
247
|
-
failedCount:
|
|
299
|
+
testCount: totalTests,
|
|
300
|
+
passedCount: totalPassed,
|
|
301
|
+
failedCount: totalFailed,
|
|
302
|
+
carriedPassed,
|
|
248
303
|
reportPath: relReport,
|
|
249
304
|
networkReportPath: relNetworkReport,
|
|
250
305
|
});
|
|
@@ -252,8 +307,11 @@ export async function runCommand(specPath, options = {}) {
|
|
|
252
307
|
...fullReport.performance,
|
|
253
308
|
wallClockDurationMs: fullReport.wallClockDurationMs,
|
|
254
309
|
duration: fullReport.duration,
|
|
255
|
-
totalTests: fullReport.summary?.totalTests ??
|
|
256
|
-
});
|
|
310
|
+
totalTests: fullReport.summary?.totalTests ?? totalTests,
|
|
311
|
+
}, platform);
|
|
312
|
+
if (!overallPassed || totalFailed > 0) {
|
|
313
|
+
process.exitCode = 1;
|
|
314
|
+
}
|
|
257
315
|
return;
|
|
258
316
|
}
|
|
259
317
|
}
|
|
@@ -262,19 +320,23 @@ export async function runCommand(specPath, options = {}) {
|
|
|
262
320
|
}
|
|
263
321
|
}
|
|
264
322
|
renderSummaryCard({
|
|
265
|
-
isPassed,
|
|
266
|
-
statusText:
|
|
267
|
-
duration:
|
|
323
|
+
isPassed: overallPassed,
|
|
324
|
+
statusText: overallPassed ? 'PASSED' : 'FAILED',
|
|
325
|
+
duration: finalDuration,
|
|
268
326
|
platform,
|
|
269
327
|
suiteCount,
|
|
270
328
|
passedSuites,
|
|
271
329
|
failedSuites,
|
|
272
|
-
testCount,
|
|
273
|
-
passedCount:
|
|
274
|
-
failedCount:
|
|
330
|
+
testCount: totalTests,
|
|
331
|
+
passedCount: totalPassed,
|
|
332
|
+
failedCount: totalFailed,
|
|
333
|
+
carriedPassed,
|
|
275
334
|
reportPath: relReport,
|
|
276
335
|
networkReportPath: relNetworkReport,
|
|
277
336
|
});
|
|
337
|
+
if (!overallPassed || totalFailed > 0) {
|
|
338
|
+
process.exitCode = 1;
|
|
339
|
+
}
|
|
278
340
|
}
|
|
279
341
|
catch (e) {
|
|
280
342
|
console.error(`\x1b[31m[TestSpectra] Execution error:\x1b[0m`, e);
|
|
@@ -284,5 +346,8 @@ export async function runCommand(specPath, options = {}) {
|
|
|
284
346
|
if (demoServer) {
|
|
285
347
|
await demoServer.stop();
|
|
286
348
|
}
|
|
349
|
+
if (apiServer) {
|
|
350
|
+
await apiServer.stop();
|
|
351
|
+
}
|
|
287
352
|
}
|
|
288
353
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SkillsCommandOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
agent?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function skillsInitCommand(options?: SkillsCommandOptions): Promise<void>;
|
|
6
|
+
export declare function skillsListCommand(options?: SkillsCommandOptions): void;
|
|
7
|
+
export declare function skillsAddCommand(ids?: string[], options?: SkillsCommandOptions): Promise<void>;
|
|
8
|
+
export declare function skillsUpdateCommand(ids?: string[], options?: SkillsCommandOptions): void;
|
|
9
|
+
export declare function skillsRemoveCommand(ids?: string[], options?: SkillsCommandOptions): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { initSkills, listSkills, addSkills, updateSkills, removeSkills, getKnownAgentIds, resolveAgentSelection, } from '@testspectra/skills';
|
|
4
|
+
function resolveBaseDir(options = {}) {
|
|
5
|
+
return options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
6
|
+
}
|
|
7
|
+
function resolveAgentIds(options = {}) {
|
|
8
|
+
const { ids, unknown } = resolveAgentSelection(options.agent);
|
|
9
|
+
if (unknown.length > 0) {
|
|
10
|
+
console.log(chalk.yellow(`⚠️ Unknown --agent value(s): ${unknown.join(', ')}. Known ids: ${getKnownAgentIds().join(', ')}`));
|
|
11
|
+
}
|
|
12
|
+
return ids.length > 0 ? ids : undefined;
|
|
13
|
+
}
|
|
14
|
+
export async function skillsInitCommand(options = {}) {
|
|
15
|
+
await initSkills(resolveBaseDir(options));
|
|
16
|
+
}
|
|
17
|
+
export function skillsListCommand(options = {}) {
|
|
18
|
+
listSkills(resolveBaseDir(options), resolveAgentIds(options));
|
|
19
|
+
}
|
|
20
|
+
export async function skillsAddCommand(ids = [], options = {}) {
|
|
21
|
+
await addSkills(ids, resolveBaseDir(options), resolveAgentIds(options));
|
|
22
|
+
}
|
|
23
|
+
export function skillsUpdateCommand(ids = [], options = {}) {
|
|
24
|
+
updateSkills(ids, resolveBaseDir(options), resolveAgentIds(options));
|
|
25
|
+
}
|
|
26
|
+
export function skillsRemoveCommand(ids = [], options = {}) {
|
|
27
|
+
removeSkills(ids, resolveBaseDir(options), resolveAgentIds(options));
|
|
28
|
+
}
|
|
@@ -5,6 +5,7 @@ export async function syncTypesCommand(options = {}) {
|
|
|
5
5
|
const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
6
6
|
try {
|
|
7
7
|
TypeGenerator.generateAll(cwd);
|
|
8
|
+
await TypeGenerator.syncEnvDeclaration(cwd);
|
|
8
9
|
console.log(chalk.green('✔ Successfully synchronized TestSpectra ambient type declarations in .testspectra/types/'));
|
|
9
10
|
}
|
|
10
11
|
catch (err) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface TestCommandOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
headed?: boolean;
|
|
4
|
+
headless?: boolean;
|
|
5
|
+
stepDelay?: string | number;
|
|
6
|
+
delay?: string | number;
|
|
7
|
+
spec?: string;
|
|
8
|
+
output?: string;
|
|
9
|
+
concurrency?: string | number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `spectra test` — delegates to `@testspectra/react`'s bundled Vitest browser-mode runner for
|
|
13
|
+
* component testing. Deliberately separate from `spectra run` (E2E via core/orchestrator): see
|
|
14
|
+
* docs/v2/features/component-testing/react/README.md §2 for why these are two independent
|
|
15
|
+
* runners rather than one extended pipeline.
|
|
16
|
+
*/
|
|
17
|
+
export declare function testCommand(options?: TestCommandOptions): Promise<void>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import { renderSummaryView } from '../reporter/summary-view.js';
|
|
5
|
+
import { Reporter } from '../runner/reporter.js';
|
|
6
|
+
import { runSystemChecks } from './doctor.js';
|
|
7
|
+
/**
|
|
8
|
+
* Finds the Chrome-for-Testing binary that `spectra doctor` already installs/manages for the
|
|
9
|
+
* E2E CDP driver (`core/driver-cdp`) — same install, same binary. `@testspectra/react` must not
|
|
10
|
+
* contain any browser-discovery logic of its own (see the package-level comment in
|
|
11
|
+
* tools/react/vitest.config.ts): this is the one place that resolves the path, passed down to
|
|
12
|
+
* the bundled Vitest/Playwright runner as a plain argument (env var), so component testing never
|
|
13
|
+
* requires a separate `playwright install` download alongside TestSpectra's own browser.
|
|
14
|
+
*/
|
|
15
|
+
async function resolveBrowserExecutable(cwd) {
|
|
16
|
+
try {
|
|
17
|
+
const { dependencies } = await runSystemChecks(cwd);
|
|
18
|
+
const chrome = dependencies.find((d) => d.category === 'web' && (d.name.includes('Chrome') || d.name.includes('Chromium')));
|
|
19
|
+
return chrome?.installed && chrome.location ? chrome.location : null;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `spectra test` — delegates to `@testspectra/react`'s bundled Vitest browser-mode runner for
|
|
27
|
+
* component testing. Deliberately separate from `spectra run` (E2E via core/orchestrator): see
|
|
28
|
+
* docs/v2/features/component-testing/react/README.md §2 for why these are two independent
|
|
29
|
+
* runners rather than one extended pipeline.
|
|
30
|
+
*/
|
|
31
|
+
export async function testCommand(options = {}) {
|
|
32
|
+
const cwd = options.cwd ?? process.cwd();
|
|
33
|
+
let reactPkg;
|
|
34
|
+
try {
|
|
35
|
+
reactPkg = await import('@testspectra/react');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
p.log.error(chalk.red('✖ @testspectra/react is not installed in this workspace.'));
|
|
39
|
+
p.log.info(chalk.dim('Component testing requires the @testspectra/react package.'));
|
|
40
|
+
p.log.info(chalk.dim(' 👉 Install it: pnpm add -D @testspectra/react'));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const browserExecutablePath = await resolveBrowserExecutable(cwd);
|
|
45
|
+
if (!browserExecutablePath) {
|
|
46
|
+
p.log.error(chalk.red('✖ TestSpectra-managed Chrome for Testing was not found.'));
|
|
47
|
+
p.log.info(chalk.dim('Component testing reuses the same browser install as `spectra run` (web).'));
|
|
48
|
+
p.log.info(chalk.dim(' 👉 Install it: spectra doctor --fix'));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
let stepDelayMs;
|
|
53
|
+
const rawDelay = options.stepDelay ?? options.delay;
|
|
54
|
+
if (rawDelay !== undefined) {
|
|
55
|
+
const delayNum = parseInt(String(rawDelay), 10);
|
|
56
|
+
if (!isNaN(delayNum) && delayNum >= 0) {
|
|
57
|
+
stepDelayMs = delayNum;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let headless = true;
|
|
61
|
+
if (options.headed) {
|
|
62
|
+
headless = false;
|
|
63
|
+
}
|
|
64
|
+
else if (options.headless !== undefined) {
|
|
65
|
+
headless = options.headless;
|
|
66
|
+
}
|
|
67
|
+
const concurrency = options.concurrency !== undefined ? parseInt(String(options.concurrency), 10) : undefined;
|
|
68
|
+
const outputFile = options.output ? path.resolve(cwd, options.output) : undefined;
|
|
69
|
+
// Reuses the exact same semantic terminal reporter `spectra run` drives (see
|
|
70
|
+
// docs/v2/cli/matchers-semantic-reporter.md) — @testspectra/react emits the identical
|
|
71
|
+
// [TESTSPECTRA_*] protocol lines the E2E Rust orchestrator does (see tools/react/src/prelude.ts
|
|
72
|
+
// / setup.ts), so the same Reporter class parses them with zero changes on either side.
|
|
73
|
+
const reporter = new Reporter();
|
|
74
|
+
const runStartedAt = Date.now();
|
|
75
|
+
const ok = await reactPkg.runComponentTests({
|
|
76
|
+
cwd,
|
|
77
|
+
headless,
|
|
78
|
+
stepDelayMs,
|
|
79
|
+
spec: options.spec,
|
|
80
|
+
outputFile,
|
|
81
|
+
concurrency: concurrency !== undefined && !isNaN(concurrency) ? concurrency : undefined,
|
|
82
|
+
browserExecutablePath,
|
|
83
|
+
onLine: (line) => {
|
|
84
|
+
if (!line.startsWith('[TESTSPECTRA_'))
|
|
85
|
+
return;
|
|
86
|
+
reporter.addLog({ timestamp: new Date().toLocaleTimeString(), level: 'INFO', message: line });
|
|
87
|
+
if (!reporter.isInteractiveMode()) {
|
|
88
|
+
console.log(line);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
const durationMs = Date.now() - runStartedAt;
|
|
93
|
+
const result = reporter.generateResult(ok ? 'passed' : 'failed', `${(durationMs / 1000).toFixed(1)}s`);
|
|
94
|
+
const totalTests = result.passedCount + result.failedCount;
|
|
95
|
+
renderSummaryView({
|
|
96
|
+
totalSuites: totalTests,
|
|
97
|
+
totalTests,
|
|
98
|
+
passedCount: result.passedCount,
|
|
99
|
+
failedCount: result.failedCount,
|
|
100
|
+
durationMs,
|
|
101
|
+
reportLocation: outputFile ? path.relative(cwd, outputFile) : '(use -o/--output to save a report)',
|
|
102
|
+
});
|
|
103
|
+
process.exit(ok ? 0 : 1);
|
|
104
|
+
}
|
package/dist/commands/watch.js
CHANGED
|
@@ -3,15 +3,22 @@ import path from 'path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { TypeGenerator } from '../types/generator.js';
|
|
5
5
|
export function startWatcher(cwd, debounceMs = 60) {
|
|
6
|
-
// Initial generation
|
|
6
|
+
// Initial generation. TypeGenerator.generateAll itself stays synchronous (reading
|
|
7
|
+
// spectra.config.ts is unavoidably async, so that part is handled by the separate
|
|
8
|
+
// syncEnvDeclaration call below); startWatcher stays sync so it can return the watcher handle
|
|
9
|
+
// immediately, with the async env sync fired-and-forgotten.
|
|
7
10
|
TypeGenerator.generateAll(cwd);
|
|
11
|
+
TypeGenerator.syncEnvDeclaration(cwd).catch((err) => {
|
|
12
|
+
console.error(chalk.red(`✖ Error generating initial env.d.ts: ${err?.message}`));
|
|
13
|
+
});
|
|
8
14
|
let debounceTimer = null;
|
|
9
15
|
const triggerRegen = (filePath, eventType) => {
|
|
10
16
|
if (debounceTimer)
|
|
11
17
|
clearTimeout(debounceTimer);
|
|
12
|
-
debounceTimer = setTimeout(() => {
|
|
18
|
+
debounceTimer = setTimeout(async () => {
|
|
13
19
|
try {
|
|
14
20
|
TypeGenerator.generateAll(cwd);
|
|
21
|
+
await TypeGenerator.syncEnvDeclaration(cwd);
|
|
15
22
|
const time = new Date().toLocaleTimeString();
|
|
16
23
|
const rel = path.relative(cwd, filePath) || filePath;
|
|
17
24
|
console.log(chalk.gray(`[${time}] `) + chalk.cyan(`✨ Updated ambient types `) + chalk.gray(`(${eventType}: ${rel})`));
|
|
@@ -34,7 +41,6 @@ export function startWatcher(cwd, debounceMs = 60) {
|
|
|
34
41
|
}
|
|
35
42
|
if (fn.includes('fixtures') ||
|
|
36
43
|
fn.includes('page-objects') ||
|
|
37
|
-
fn.includes('pageobjects') ||
|
|
38
44
|
fn.includes('actions') ||
|
|
39
45
|
fn.includes('steps') ||
|
|
40
46
|
fn.includes('hooks') ||
|
package/dist/config/loader.js
CHANGED
package/dist/config/schema.d.ts
CHANGED
|
@@ -20,19 +20,11 @@ export interface WebConfig {
|
|
|
20
20
|
headless: boolean | 'new';
|
|
21
21
|
/**
|
|
22
22
|
* Implicit wait timeout (in milliseconds) for finding elements before throwing an error.
|
|
23
|
+
* Also the ceiling auto-waiting element assertions/actions poll against (capped at 2000ms for
|
|
24
|
+
* assertions — see `getAssertionTimeoutMs()`).
|
|
23
25
|
* @default "5000"
|
|
24
26
|
*/
|
|
25
27
|
implicitWait: string;
|
|
26
|
-
/**
|
|
27
|
-
* Maximum time (in milliseconds) allowed for a webpage to complete navigation and initial load.
|
|
28
|
-
* @default "30000"
|
|
29
|
-
*/
|
|
30
|
-
pageLoadTimeout: string;
|
|
31
|
-
/**
|
|
32
|
-
* Maximum time (in milliseconds) allowed for asynchronous scripts to finish executing.
|
|
33
|
-
* @default "30000"
|
|
34
|
-
*/
|
|
35
|
-
scriptTimeout: string;
|
|
36
28
|
/**
|
|
37
29
|
* Delay between consecutive actions and commands in milliseconds (slow motion).
|
|
38
30
|
* @default 0
|
|
@@ -46,37 +38,6 @@ export interface WebConfig {
|
|
|
46
38
|
*/
|
|
47
39
|
parallelizationMode: 'testcase' | 'suite';
|
|
48
40
|
}
|
|
49
|
-
/**
|
|
50
|
-
* Browser target definition for web test execution.
|
|
51
|
-
*/
|
|
52
|
-
export interface Browser {
|
|
53
|
-
/**
|
|
54
|
-
* Unique identifier for this browser configuration.
|
|
55
|
-
*/
|
|
56
|
-
id: string;
|
|
57
|
-
/**
|
|
58
|
-
* Browser type/engine: `"chrome"`, `"firefox"`, `"safari"`, `"edge"`.
|
|
59
|
-
* @default "chrome"
|
|
60
|
-
*/
|
|
61
|
-
type: string;
|
|
62
|
-
/**
|
|
63
|
-
* Whether to simulate mobile viewport and touch metrics.
|
|
64
|
-
* @default false
|
|
65
|
-
*/
|
|
66
|
-
mobileEmulation: boolean;
|
|
67
|
-
/**
|
|
68
|
-
* Emulated device name when `mobileEmulation` is true (e.g. `"iPhone 14"`, `"Pixel 7"`).
|
|
69
|
-
*/
|
|
70
|
-
deviceName?: string;
|
|
71
|
-
/**
|
|
72
|
-
* Custom browser window width in pixels.
|
|
73
|
-
*/
|
|
74
|
-
width?: string;
|
|
75
|
-
/**
|
|
76
|
-
* Custom browser window height in pixels.
|
|
77
|
-
*/
|
|
78
|
-
height?: string;
|
|
79
|
-
}
|
|
80
41
|
/**
|
|
81
42
|
* Target Android device specification for multi-device parallel execution.
|
|
82
43
|
*/
|
|
@@ -156,11 +117,6 @@ export interface AndroidConfig {
|
|
|
156
117
|
* Main activity name launched when starting the app.
|
|
157
118
|
*/
|
|
158
119
|
appActivity: string;
|
|
159
|
-
/**
|
|
160
|
-
* Whether to automatically grant Android runtime permissions upon installation.
|
|
161
|
-
* @default true
|
|
162
|
-
*/
|
|
163
|
-
autoGrantPermissions: boolean;
|
|
164
120
|
/**
|
|
165
121
|
* Whether to preserve app state and prevent clearing app data/cache between sessions.
|
|
166
122
|
* @default false
|
|
@@ -265,40 +221,6 @@ export interface IosConfig {
|
|
|
265
221
|
*/
|
|
266
222
|
parallelizationMode: 'testcase' | 'suite';
|
|
267
223
|
}
|
|
268
|
-
/**
|
|
269
|
-
* Load testing stage ramp-up profile.
|
|
270
|
-
*/
|
|
271
|
-
export interface LoadStage {
|
|
272
|
-
/**
|
|
273
|
-
* Stage identifier.
|
|
274
|
-
*/
|
|
275
|
-
id: string;
|
|
276
|
-
/**
|
|
277
|
-
* Duration for this load stage (e.g. `"30s"`, `"2m"`).
|
|
278
|
-
*/
|
|
279
|
-
duration: string;
|
|
280
|
-
/**
|
|
281
|
-
* Target virtual users (VUs) to scale to during this stage.
|
|
282
|
-
*/
|
|
283
|
-
targetVUs: string;
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Performance and pass/fail metric threshold definition.
|
|
287
|
-
*/
|
|
288
|
-
export interface SuccessThreshold {
|
|
289
|
-
/**
|
|
290
|
-
* Unique threshold identifier.
|
|
291
|
-
*/
|
|
292
|
-
id: string;
|
|
293
|
-
/**
|
|
294
|
-
* Performance metric type (e.g. `"http_req_duration"`, `"http_req_failed"`).
|
|
295
|
-
*/
|
|
296
|
-
metricType: string;
|
|
297
|
-
/**
|
|
298
|
-
* Maximum acceptable value (e.g. `"500ms"`, `"0.01"`).
|
|
299
|
-
*/
|
|
300
|
-
maxValue: string;
|
|
301
|
-
}
|
|
302
224
|
/**
|
|
303
225
|
* Specific domain filter for real-time network interception and recording.
|
|
304
226
|
*/
|
|
@@ -312,19 +234,6 @@ export interface MonitoredDomain {
|
|
|
312
234
|
*/
|
|
313
235
|
enabled: boolean;
|
|
314
236
|
}
|
|
315
|
-
/**
|
|
316
|
-
* Custom environment variable key-value pair injected into the test execution sandbox.
|
|
317
|
-
*/
|
|
318
|
-
export interface EnvironmentVariable {
|
|
319
|
-
/**
|
|
320
|
-
* Environment variable name (e.g. `"API_SECRET_KEY"`).
|
|
321
|
-
*/
|
|
322
|
-
key: string;
|
|
323
|
-
/**
|
|
324
|
-
* Environment variable value.
|
|
325
|
-
*/
|
|
326
|
-
value: string;
|
|
327
|
-
}
|
|
328
237
|
/**
|
|
329
238
|
* Test execution, CDP network monitoring, and sandbox environment configuration.
|
|
330
239
|
*/
|
|
@@ -349,9 +258,10 @@ export interface ExecutionConfig {
|
|
|
349
258
|
*/
|
|
350
259
|
monitoredDomains?: MonitoredDomain[];
|
|
351
260
|
/**
|
|
352
|
-
* Custom environment variables injected into the test process runtime
|
|
261
|
+
* Custom environment variables injected into the test process runtime
|
|
262
|
+
* (e.g. `{ API_SECRET_KEY: "..." }`).
|
|
353
263
|
*/
|
|
354
|
-
environmentVariables?:
|
|
264
|
+
environmentVariables?: Record<string, string>;
|
|
355
265
|
}
|
|
356
266
|
/**
|
|
357
267
|
* Root configuration data structure matching TestSpectra project schema.
|
|
@@ -361,10 +271,6 @@ export interface ConfigData {
|
|
|
361
271
|
* Web browser execution settings.
|
|
362
272
|
*/
|
|
363
273
|
webConfig: WebConfig;
|
|
364
|
-
/**
|
|
365
|
-
* List of target browsers for web automation runs.
|
|
366
|
-
*/
|
|
367
|
-
browsers: Browser[];
|
|
368
274
|
/**
|
|
369
275
|
* Android Appium driver and device options.
|
|
370
276
|
*/
|
|
@@ -373,21 +279,6 @@ export interface ConfigData {
|
|
|
373
279
|
* iOS Appium driver and device options.
|
|
374
280
|
*/
|
|
375
281
|
iosConfig: IosConfig;
|
|
376
|
-
/**
|
|
377
|
-
* Load testing parameters.
|
|
378
|
-
*/
|
|
379
|
-
loadConfig: {
|
|
380
|
-
virtualUsers: string;
|
|
381
|
-
duration: string;
|
|
382
|
-
};
|
|
383
|
-
/**
|
|
384
|
-
* Load testing stages.
|
|
385
|
-
*/
|
|
386
|
-
loadStages: LoadStage[];
|
|
387
|
-
/**
|
|
388
|
-
* Pass/fail metric thresholds.
|
|
389
|
-
*/
|
|
390
|
-
thresholds: SuccessThreshold[];
|
|
391
282
|
/**
|
|
392
283
|
* Advanced execution and network recording settings.
|
|
393
284
|
*/
|