@testspectra/cli 1.1.8-rc.3 → 1.1.8-rc.30
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/README.md +50 -0
- package/bin/.testspectra-runner.hash +1 -1
- package/bin/spectra.js +0 -0
- package/dist/commands/__tests__/doctor-filter.test.js +37 -0
- package/dist/commands/__tests__/doctor-scope.test.d.ts +1 -0
- package/dist/commands/__tests__/doctor-scope.test.js +24 -0
- package/dist/commands/__tests__/run-e2e.test.js +49 -49
- package/dist/commands/__tests__/test-error-streaming.test.d.ts +1 -0
- package/dist/commands/__tests__/test-error-streaming.test.js +85 -0
- package/dist/commands/__tests__/test-scaffolding.test.d.ts +1 -0
- package/dist/commands/__tests__/test-scaffolding.test.js +32 -0
- package/dist/commands/__tests__/upgrade.test.d.ts +1 -0
- package/dist/commands/__tests__/upgrade.test.js +95 -0
- package/dist/commands/add.js +5 -4
- package/dist/commands/devices.js +1 -0
- package/dist/commands/docs.d.ts +4 -0
- package/dist/commands/docs.js +17 -0
- package/dist/commands/doctor.d.ts +9 -1
- package/dist/commands/doctor.js +17 -3
- package/dist/commands/init.js +7 -160
- package/dist/commands/install.js +5 -0
- package/dist/commands/license.js +1 -0
- package/dist/commands/test.d.ts +13 -0
- package/dist/commands/test.js +115 -21
- package/dist/commands/upgrade.d.ts +12 -0
- package/dist/commands/upgrade.js +197 -0
- package/dist/generator/__tests__/tsconfig-isolation.test.js +2 -1
- package/dist/generator/__tests__/type-generator.test.js +38 -0
- package/dist/generator/architecture-doc-generator.d.ts +14 -0
- package/dist/generator/architecture-doc-generator.js +80 -0
- package/dist/generator/index.d.ts +1 -0
- package/dist/generator/index.js +1 -0
- package/dist/generator/tsconfig-generator.js +1 -1
- package/dist/generator/type-generator.d.ts +5 -2
- package/dist/generator/type-generator.js +14 -17
- package/dist/index.js +54 -26
- package/dist/linter/__tests__/component-test-imports.test.js +51 -51
- package/dist/linter/__tests__/shared-lib-boundary.test.js +15 -15
- package/dist/linter/__tests__/spec-schema.test.js +167 -167
- package/dist/linter/discovery.d.ts +3 -3
- package/dist/linter/discovery.js +7 -7
- package/dist/linter/schemas/spec.schema.d.ts +8 -8
- package/dist/linter/schemas/suite.schema.d.ts +4 -4
- package/dist/runner/bridge.d.ts +1 -1
- package/dist/runner/bridge.js +101 -11
- package/dist/runner/reporter.d.ts +1 -0
- package/dist/runner/reporter.js +12 -6
- package/dist/utils/background-update-check.d.ts +1 -0
- package/dist/utils/background-update-check.js +43 -0
- package/dist/utils/dependency-updates.d.ts +15 -0
- package/dist/utils/dependency-updates.js +107 -0
- package/dist/utils/update-notifier.d.ts +2 -0
- package/dist/utils/update-notifier.js +67 -0
- package/package.json +34 -30
- package/templates/default/CLAUDE.md +16 -16
- 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 +2 -2
- package/templates/default/page-objects/AuthPage/mobile.ts +5 -5
- package/templates/default/page-objects/AuthPage/web.ts +10 -10
- package/templates/default/page-objects/MatchersPage/mobile.ts +70 -70
- package/templates/default/page-objects/MatchersPage/web.ts +43 -43
- 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 -42
- package/templates/default/page-objects/PlaygroundPage/web.ts +31 -31
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -53
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -19
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -31
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -3
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -3
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -27
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -19
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -26
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -30
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -22
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -27
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -20
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -24
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -26
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -37
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -21
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -29
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -16
- package/templates/default/specs/ApiInterception/suite.md +10 -10
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -30
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -23
- package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -16
- package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -28
- package/templates/default/specs/ApiSeeding/suite.md +9 -9
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -7
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -19
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -9
- package/templates/default/specs/Authentication/suite.md +9 -9
- 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 -17
- 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 -17
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -28
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -26
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -26
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +19 -19
- 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 -17
- 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 -14
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -21
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -28
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -29
- package/templates/default/specs/DeepLink/suite.md +9 -9
- 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 -17
- 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 -19
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +29 -29
- package/templates/default/specs/ElementMatchers/suite.md +10 -10
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -5
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
- package/templates/default/specs/EnvironmentConfig/suite.md +9 -9
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -26
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -22
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -13
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -20
- package/templates/default/specs/PermissionRationale/suite.md +9 -9
- 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 -18
- 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 -18
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +6 -6
- package/templates/default/spectra.config.ts +52 -52
- 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 -9
- package/templates/default/support/actions/seedSession/web.action.ts +13 -13
- 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/docs/ARCHITECTURE.default.md +26 -0
- package/templates/docs/ARCHITECTURE.nx.md +119 -0
- package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db-shm → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db} +0 -0
- package/templates/nx/.nx/workspace-data/d/daemon.log +1012 -1612
- package/templates/nx/.nx/workspace-data/file-map.json +123 -137
- package/templates/nx/.nx/workspace-data/lockfile.hash +1 -1
- package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
- package/templates/nx/.nx/workspace-data/parsed-lock-file.json +535 -535
- package/templates/nx/.nx/workspace-data/project-graph.json +592 -591
- package/templates/nx/.nx/workspace-data/source-maps.json +336 -0
- package/templates/nx/CLAUDE.md +10 -10
- package/templates/nx/package.json +3 -3
- package/templates/nx/packages/matchers/e2e/project.json +2 -2
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -27
- 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 -24
- 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 -5
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -16
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -9
- package/templates/nx/packages/playground/e2e/project.json +2 -2
- 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 -91
- 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 +18 -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 -52
- package/templates/react-component/.cursorrules +17 -17
- package/templates/react-component/.vscode/extensions.json +5 -5
- package/templates/react-component/CLAUDE.md +14 -14
- package/templates/react-component/README.md +69 -69
- package/templates/react-component/fixtures/component-mock.json +12 -12
- package/templates/react-component/global-hooks/after/web.hook.ts +3 -3
- package/templates/react-component/global-hooks/before/web.hook.ts +3 -3
- package/templates/react-component/package.json +27 -27
- package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -23
- package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -23
- package/templates/react-component/page-objects/InputComponent/web.ts +13 -13
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -18
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -11
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -18
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -10
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -18
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -16
- package/templates/react-component/specs/BadgeComponent/suite.md +12 -12
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -19
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +25 -28
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -19
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +13 -22
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -18
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -8
- package/templates/react-component/specs/ButtonComponent/suite.md +12 -12
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -20
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +24 -28
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -18
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -9
- package/templates/react-component/specs/InputComponent/suite.md +12 -12
- package/templates/react-component/spectra.config.ts +17 -27
- package/templates/react-component/src/components/Badge/Badge.tsx +60 -60
- package/templates/react-component/src/components/Button/Button.tsx +59 -57
- package/templates/react-component/src/components/Input/Input.tsx +40 -41
- package/templates/react-component/src/test-utils.tsx +23 -23
- package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -9
- package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -12
- package/bin/testspectra-runner.exe +0 -0
- package/dist/.tsbuildinfo +0 -1
- package/dist/commands/update.d.ts +0 -3
- package/dist/commands/update.js +0 -109
- package/dist/lifecycle/__tests__/lifecycle-engine.test.js +0 -290
- package/dist/lifecycle/hook-discovery.d.ts +0 -24
- package/dist/lifecycle/hook-discovery.js +0 -60
- package/dist/lifecycle/hook-dispatcher.d.ts +0 -34
- package/dist/lifecycle/hook-dispatcher.js +0 -190
- package/dist/lifecycle/index.d.ts +0 -3
- package/dist/lifecycle/index.js +0 -3
- package/dist/lifecycle/parallel-grouping.d.ts +0 -27
- package/dist/lifecycle/parallel-grouping.js +0 -151
- 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-wal +0 -0
- package/templates/nx/.nx/workspace-data/d/server-process.json +0 -3
- /package/dist/{lifecycle/__tests__/lifecycle-engine.test.d.ts → commands/__tests__/doctor-filter.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -37,6 +37,56 @@ pnpm --filter @testspectra/cli dev
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
+
## 🌍 Native Binary Distribution (Multiplatform Releases)
|
|
41
|
+
|
|
42
|
+
`spectra run` shells out to `testspectra-runner`, a native Rust binary (`core/`) — it isn't
|
|
43
|
+
portable JS, so one npm package can't ship a single binary that works on every OS/arch. Instead,
|
|
44
|
+
`@testspectra/cli` follows the same pattern as esbuild/swc/turbo:
|
|
45
|
+
|
|
46
|
+
- `@testspectra/cli` itself ships **no native binary** — only the JS launcher (`bin/spectra.js`).
|
|
47
|
+
- The binary is published as three separate, platform-locked packages under `cli/npm/`:
|
|
48
|
+
- `@testspectra/cli-darwin-arm64`
|
|
49
|
+
- `@testspectra/cli-win32-x64`
|
|
50
|
+
- `@testspectra/cli-linux-x64`
|
|
51
|
+
|
|
52
|
+
(Windows arm64 has no native build — `resolveBinaryPath()` throws a clear "not supported"
|
|
53
|
+
error there instead of pointing at a package that was never published.)
|
|
54
|
+
|
|
55
|
+
Each has `os`/`cpu` fields in its `package.json`, so npm/pnpm/yarn only ever download the one
|
|
56
|
+
matching the installing machine.
|
|
57
|
+
|
|
58
|
+
- `@testspectra/cli` lists all three as `optionalDependencies`, pinned to an exact version (not
|
|
59
|
+
`^`) that always matches the `@testspectra/cli` release it ships with.
|
|
60
|
+
- At runtime, `RustCoreBridge.resolveBinaryPath()` (`cli/src/runner/bridge.ts`) resolves the
|
|
61
|
+
binary from whichever platform package got installed (`require.resolve('@testspectra/cli-<platform>/package.json')`),
|
|
62
|
+
falling back to the monorepo's local build output (`core/target/release/...`) for local dev
|
|
63
|
+
where no optional package is installed.
|
|
64
|
+
- If a user's platform/arch isn't one of the three above, install succeeds (optional deps just
|
|
65
|
+
get skipped) but `resolveBinaryPath()` throws a clear "not supported" error at run time.
|
|
66
|
+
|
|
67
|
+
### Building & releasing all platforms
|
|
68
|
+
|
|
69
|
+
`scripts/build-cross-platform-binaries.js` builds the binary for all three platforms from a
|
|
70
|
+
single macOS machine and copies each into `cli/npm/<platform>/bin/`:
|
|
71
|
+
|
|
72
|
+
- **macOS arm64** — native `cargo build` (this machine only builds arm64; there's no x64 macOS
|
|
73
|
+
package).
|
|
74
|
+
- **Windows x64** — cross-compiled via [`cargo-xwin`](https://github.com/rust-cross/cargo-xwin)
|
|
75
|
+
(`cargo install cargo-xwin`), which downloads the MSVC CRT/Windows SDK bits needed for linking.
|
|
76
|
+
reqwest's default `native-tls` backend uses Schannel on Windows, so no OpenSSL cross-compile
|
|
77
|
+
headaches here.
|
|
78
|
+
- **Linux x64 (glibc)** — cross-compiled via [`cross`](https://github.com/cross-rs/cross)
|
|
79
|
+
(`cargo install cross --git https://github.com/cross-rs/cross`), which builds inside a Docker
|
|
80
|
+
container that already has a matching glibc + OpenSSL for the target — required because
|
|
81
|
+
`native-tls` needs OpenSSL on Linux and cross-compiling OpenSSL from macOS directly is painful.
|
|
82
|
+
Docker must be running for this step.
|
|
83
|
+
|
|
84
|
+
`scripts/release-cli-local.sh` runs this automatically, then stamps every `cli/npm/*/package.json`
|
|
85
|
+
and `cli/package.json`'s `optionalDependencies` with the target version, packs all four tarballs,
|
|
86
|
+
and (with `--publish`) publishes the three platform packages before `@testspectra/cli` itself.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
40
90
|
## 💻 CLI Commands Summary
|
|
41
91
|
|
|
42
92
|
| Command | Usage | Description |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
2db95c618fc201b3c5e4b2f56e93c5e5fca36ba0d31250c65c98aabed5385361
|
package/bin/spectra.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
const { execFileSyncMock } = vi.hoisted(() => ({ execFileSyncMock: vi.fn() }));
|
|
3
|
+
vi.mock('child_process', () => ({
|
|
4
|
+
execFileSync: execFileSyncMock,
|
|
5
|
+
spawn: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
vi.mock('../../runner/bridge.js', () => ({
|
|
8
|
+
RustCoreBridge: { resolveBinaryPath: () => '/fake/testspectra-runner' },
|
|
9
|
+
}));
|
|
10
|
+
import { runSystemChecks } from '../doctor.js';
|
|
11
|
+
const EMPTY = JSON.stringify({ all_ready: true, missing_count: 0, dependencies: [] });
|
|
12
|
+
describe('runSystemChecks filter forwarding', () => {
|
|
13
|
+
it('forwards a category scope to the runner', async () => {
|
|
14
|
+
execFileSyncMock.mockReturnValueOnce(EMPTY);
|
|
15
|
+
await runSystemChecks('/tmp/ws', { scope: 'web' });
|
|
16
|
+
const [, args] = execFileSyncMock.mock.calls.at(-1);
|
|
17
|
+
expect(args).toEqual(['doctor', '--json', '--scope', 'web']);
|
|
18
|
+
});
|
|
19
|
+
it('forwards a fine-grained --only filter (single dependency)', async () => {
|
|
20
|
+
execFileSyncMock.mockReturnValueOnce(EMPTY);
|
|
21
|
+
await runSystemChecks('/tmp/ws', { only: 'chrome' });
|
|
22
|
+
const [, args] = execFileSyncMock.mock.calls.at(-1);
|
|
23
|
+
expect(args).toEqual(['doctor', '--json', '--only', 'chrome']);
|
|
24
|
+
});
|
|
25
|
+
it('forwards both scope and only when provided', async () => {
|
|
26
|
+
execFileSyncMock.mockReturnValueOnce(EMPTY);
|
|
27
|
+
await runSystemChecks('/tmp/ws', { scope: 'web', only: 'chrome,adb' });
|
|
28
|
+
const [, args] = execFileSyncMock.mock.calls.at(-1);
|
|
29
|
+
expect(args).toEqual(['doctor', '--json', '--scope', 'web', '--only', 'chrome,adb']);
|
|
30
|
+
});
|
|
31
|
+
it('omits filters entirely when none are requested (full doctor)', async () => {
|
|
32
|
+
execFileSyncMock.mockReturnValueOnce(EMPTY);
|
|
33
|
+
await runSystemChecks('/tmp/ws');
|
|
34
|
+
const [, args] = execFileSyncMock.mock.calls.at(-1);
|
|
35
|
+
expect(args).toEqual(['doctor', '--json']);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
const { execFileSyncMock } = vi.hoisted(() => ({ execFileSyncMock: vi.fn() }));
|
|
3
|
+
vi.mock('child_process', () => ({
|
|
4
|
+
execFileSync: execFileSyncMock,
|
|
5
|
+
spawn: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
vi.mock('../../runner/bridge.js', () => ({
|
|
8
|
+
RustCoreBridge: { resolveBinaryPath: () => '/fake/testspectra-runner' },
|
|
9
|
+
}));
|
|
10
|
+
import { runSystemChecks } from '../doctor.js';
|
|
11
|
+
describe('runSystemChecks scope forwarding', () => {
|
|
12
|
+
it('forwards a category scope to the runner (efficient partial doctor)', async () => {
|
|
13
|
+
execFileSyncMock.mockReturnValueOnce(JSON.stringify({ all_ready: true, missing_count: 0, dependencies: [] }));
|
|
14
|
+
await runSystemChecks('/tmp/ws', 'web');
|
|
15
|
+
const [, args] = execFileSyncMock.mock.calls.at(-1);
|
|
16
|
+
expect(args).toEqual(['doctor', '--json', '--scope', 'web']);
|
|
17
|
+
});
|
|
18
|
+
it('omits --scope entirely when no scope is requested (full doctor)', async () => {
|
|
19
|
+
execFileSyncMock.mockReturnValueOnce(JSON.stringify({ all_ready: true, missing_count: 0, dependencies: [] }));
|
|
20
|
+
await runSystemChecks('/tmp/ws');
|
|
21
|
+
const [, args] = execFileSyncMock.mock.calls.at(-1);
|
|
22
|
+
expect(args).toEqual(['doctor', '--json']);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
@@ -146,10 +146,10 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
146
146
|
// Inject a failing step / invalid selector in a new test case
|
|
147
147
|
const failingDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0099-failing');
|
|
148
148
|
fs.mkdirSync(failingDir, { recursive: true });
|
|
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
|
-
});
|
|
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
|
+
});
|
|
153
153
|
`, 'utf-8');
|
|
154
154
|
// Mock exit to prevent process termination during unit test assertion
|
|
155
155
|
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
@@ -166,13 +166,13 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
166
166
|
// 1. Test a valid spec using pure Spectra static commands
|
|
167
167
|
const spectraStaticDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0100-spectra-static');
|
|
168
168
|
fs.mkdirSync(spectraStaticDir, { recursive: true });
|
|
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
|
-
});
|
|
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
|
+
});
|
|
176
176
|
`, 'utf-8');
|
|
177
177
|
await runCommand(['specs/Playground/TC-0100-spectra-static/web.test.ts'], { target: 'web', demo: true });
|
|
178
178
|
const resultStatic = JSON.parse(fs.readFileSync(path.join(tmpDir, '.testspectra', 'reports', 'result.json'), 'utf-8'));
|
|
@@ -181,11 +181,11 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
181
181
|
// 2. Test that assertion mismatch on existing element strictly FAILS (no false positive)
|
|
182
182
|
const mismatchDir = path.join(tmpDir, 'specs', 'Playground', 'TC-0101-mismatch');
|
|
183
183
|
fs.mkdirSync(mismatchDir, { recursive: true });
|
|
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
|
-
});
|
|
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
|
+
});
|
|
189
189
|
`, 'utf-8');
|
|
190
190
|
const exitSpy = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
191
191
|
await runCommand(['specs/Playground/TC-0101-mismatch/web.test.ts'], { target: 'web', demo: true });
|
|
@@ -375,39 +375,39 @@ describe('End-to-End (E2E) Test Plan: Native Orchestrator Execution (docs/v2/cor
|
|
|
375
375
|
it('E2E-10: Spectra.intercept FIFO respondOnce sequential polling and abort simulation in real Chromium', async () => {
|
|
376
376
|
const fifoAbortDir = path.join(tmpDir, 'specs', 'ApiInterception', 'TC-0010-fifo-abort');
|
|
377
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
|
-
});
|
|
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
411
|
`, 'utf-8');
|
|
412
412
|
await runCommand(['specs/ApiInterception/TC-0010-fifo-abort/web.test.ts'], { target: 'web' });
|
|
413
413
|
const resultJsonPath = path.join(tmpDir, '.testspectra', 'reports', 'result.json');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { isVitestNoise, sanitizeErrorMessage } from '@testspectra/react';
|
|
3
|
+
import { Reporter } from '../../runner/reporter.js';
|
|
4
|
+
describe('spectra test error streaming and formatting', () => {
|
|
5
|
+
it('sanitizes Vitest/Vite error messages without leaking behind-the-scenes tooling', () => {
|
|
6
|
+
const rawError = 'Failed to fetch dynamically imported module: http://localhost:63315/@fs/D:/wangs-ui-react/packages/testing-library/page-objects/AccordionComponent/web.ts?import';
|
|
7
|
+
const sanitized = sanitizeErrorMessage(rawError);
|
|
8
|
+
expect(sanitized).toBe('Failed to load module: D:/wangs-ui-react/packages/testing-library/page-objects/AccordionComponent/web.ts');
|
|
9
|
+
expect(sanitized).not.toContain('localhost');
|
|
10
|
+
expect(sanitized).not.toContain('@fs');
|
|
11
|
+
expect(sanitized).not.toContain('?import');
|
|
12
|
+
});
|
|
13
|
+
it('filters Vitest CLI runner noise while allowing actual errors through', () => {
|
|
14
|
+
expect(isVitestNoise('RUN v2.1.8 /Volumes/Home/TestSpectra')).toBe(true);
|
|
15
|
+
expect(isVitestNoise('Test Files 1 failed (1)')).toBe(true);
|
|
16
|
+
expect(isVitestNoise('⎯⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯')).toBe(true);
|
|
17
|
+
expect(isVitestNoise('Re-optimizing dependencies because vite config has changed')).toBe(true);
|
|
18
|
+
expect(isVitestNoise('Error: Cannot find module "./Button"')).toBe(false);
|
|
19
|
+
expect(isVitestNoise('SyntaxError: Unexpected token (10:15)')).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it('formats multi-line errors with clean indentation in Reporter', () => {
|
|
22
|
+
const reporter = new Reporter();
|
|
23
|
+
reporter.addLog({
|
|
24
|
+
timestamp: '12:00:00',
|
|
25
|
+
level: 'INFO',
|
|
26
|
+
message: '[TESTSPECTRA_TEST_START] {"title":"specs/Accordion/TC-0009/web.test.tsx"}',
|
|
27
|
+
});
|
|
28
|
+
reporter.addLog({
|
|
29
|
+
timestamp: '12:00:00',
|
|
30
|
+
level: 'INFO',
|
|
31
|
+
message: '[TESTSPECTRA_TEST_ERROR] Failed to load module: D:/wangs-ui-react/page-objects/Accordion/web.ts',
|
|
32
|
+
});
|
|
33
|
+
reporter.addLog({
|
|
34
|
+
timestamp: '12:00:00',
|
|
35
|
+
level: 'INFO',
|
|
36
|
+
message: '[TESTSPECTRA_TEST_FAIL] specs/Accordion/TC-0009/web.test.tsx (0ms)',
|
|
37
|
+
});
|
|
38
|
+
const result = reporter.generateResult('failed', '0.1s');
|
|
39
|
+
expect(result.failedCount).toBe(1);
|
|
40
|
+
expect(result.passedCount).toBe(0);
|
|
41
|
+
expect(result.status).toBe('failed');
|
|
42
|
+
});
|
|
43
|
+
it('prints error message below test title and steps on failure', () => {
|
|
44
|
+
const reporter = new Reporter();
|
|
45
|
+
const consoleLogs = [];
|
|
46
|
+
const spy = vi.spyOn(console, 'log').mockImplementation((msg) => {
|
|
47
|
+
if (typeof msg === 'string')
|
|
48
|
+
consoleLogs.push(msg);
|
|
49
|
+
});
|
|
50
|
+
reporter.addLog({
|
|
51
|
+
timestamp: '12:00:00',
|
|
52
|
+
level: 'INFO',
|
|
53
|
+
message: '[TESTSPECTRA_TEST_START] {"title":"ApiInterception / TC-0009-post-mocking"}',
|
|
54
|
+
});
|
|
55
|
+
reporter.addLog({
|
|
56
|
+
timestamp: '12:00:00',
|
|
57
|
+
level: 'INFO',
|
|
58
|
+
message: '[TESTSPECTRA_STEP_PASS] {"type":"action","key":"navigate","args":["/#api-post"],"test":"ApiInterception / TC-0009-post-mocking"} (8ms)',
|
|
59
|
+
});
|
|
60
|
+
reporter.addLog({
|
|
61
|
+
timestamp: '12:00:00',
|
|
62
|
+
level: 'INFO',
|
|
63
|
+
message: '[TESTSPECTRA_STEP_FAIL] {"type":"assertion","target":"#post-status-badge","args":["201 Created"],"test":"ApiInterception / TC-0009-post-mocking"} (2005ms)',
|
|
64
|
+
});
|
|
65
|
+
reporter.addLog({
|
|
66
|
+
timestamp: '12:00:00',
|
|
67
|
+
level: 'INFO',
|
|
68
|
+
message: '[TESTSPECTRA_TEST_ERROR] Expected element "#post-status-badge" to have text "201 Created", but got ""',
|
|
69
|
+
});
|
|
70
|
+
reporter.addLog({
|
|
71
|
+
timestamp: '12:00:00',
|
|
72
|
+
level: 'INFO',
|
|
73
|
+
message: '[TESTSPECTRA_TEST_FAIL] ApiInterception / TC-0009-post-mocking (2066ms)',
|
|
74
|
+
});
|
|
75
|
+
spy.mockRestore();
|
|
76
|
+
const titleIndex = consoleLogs.findIndex((log) => log.includes('ApiInterception / TC-0009-post-mocking'));
|
|
77
|
+
const stepPassIndex = consoleLogs.findIndex((log) => log.includes('Navigate to'));
|
|
78
|
+
const stepFailIndex = consoleLogs.findIndex((log) => log.includes('#post-status-badge'));
|
|
79
|
+
const errorIndex = consoleLogs.findIndex((log) => log.includes('↳ Error:'));
|
|
80
|
+
expect(titleIndex).toBeGreaterThanOrEqual(0);
|
|
81
|
+
expect(stepPassIndex).toBeGreaterThan(titleIndex);
|
|
82
|
+
expect(stepFailIndex).toBeGreaterThan(stepPassIndex);
|
|
83
|
+
expect(errorIndex).toBeGreaterThan(stepFailIndex);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { ensureTestSpectraScaffolding } from '../test.js';
|
|
6
|
+
describe('spectra test pre-flight scaffolding (docs/v2/features/component-testing/react/README.md)', () => {
|
|
7
|
+
let tmpDir;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'testspectra-test-scaffold-'));
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
try {
|
|
13
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
14
|
+
}
|
|
15
|
+
catch { }
|
|
16
|
+
});
|
|
17
|
+
it('generates the .testspectra solution tsconfig when it is missing (clean clone / CI)', () => {
|
|
18
|
+
const masterConfig = path.join(tmpDir, '.testspectra', 'tsconfig.json');
|
|
19
|
+
expect(fs.existsSync(masterConfig)).toBe(false);
|
|
20
|
+
ensureTestSpectraScaffolding(tmpDir);
|
|
21
|
+
// The exact file `vite:esbuild` tries to read via the root tsconfig's `references` entry.
|
|
22
|
+
expect(fs.existsSync(masterConfig)).toBe(true);
|
|
23
|
+
expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'tsconfig', 'web.json'))).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('leaves an existing scaffolding untouched', () => {
|
|
26
|
+
const masterConfig = path.join(tmpDir, '.testspectra', 'tsconfig.json');
|
|
27
|
+
fs.mkdirSync(path.dirname(masterConfig), { recursive: true });
|
|
28
|
+
fs.writeFileSync(masterConfig, '{"custom":true}\n', 'utf-8');
|
|
29
|
+
ensureTestSpectraScaffolding(tmpDir);
|
|
30
|
+
expect(fs.readFileSync(masterConfig, 'utf-8')).toBe('{"custom":true}\n');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execFileSync, execSync } from 'child_process';
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
6
|
+
import { upgradeCommand } from '../upgrade.js';
|
|
7
|
+
const DOCTOR_JSON = JSON.stringify({
|
|
8
|
+
all_ready: true,
|
|
9
|
+
missing_count: 0,
|
|
10
|
+
dependencies: [
|
|
11
|
+
{
|
|
12
|
+
name: 'Google Chrome',
|
|
13
|
+
category: 'web',
|
|
14
|
+
installed: true,
|
|
15
|
+
version: 'Google Chrome 123.0.0.0 (Chrome for Testing)',
|
|
16
|
+
required: true,
|
|
17
|
+
required_version: 'Stable / CfT',
|
|
18
|
+
depends_on: [],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: 'Bun',
|
|
22
|
+
category: 'core',
|
|
23
|
+
installed: true,
|
|
24
|
+
version: 'v1.0.0',
|
|
25
|
+
required: true,
|
|
26
|
+
required_version: '>=1.0.0',
|
|
27
|
+
depends_on: [],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
vi.mock('child_process', () => ({
|
|
32
|
+
execFileSync: vi.fn((_cmd, args) => Array.isArray(args) && args[0] === 'doctor' ? DOCTOR_JSON : '"9.9.9"'),
|
|
33
|
+
execSync: vi.fn(),
|
|
34
|
+
}));
|
|
35
|
+
describe('upgradeCommand self-upgrade mechanism (docs/v2/features/upgrade-mechanism-and-notifications.md)', () => {
|
|
36
|
+
let tmpDir;
|
|
37
|
+
const originalCwd = process.cwd();
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'testspectra-upgrade-test-'));
|
|
40
|
+
process.chdir(tmpDir);
|
|
41
|
+
vi.clearAllMocks();
|
|
42
|
+
vi.stubGlobal('fetch', vi.fn(async (url) => {
|
|
43
|
+
const href = String(url);
|
|
44
|
+
if (href.includes('chrome-for-testing')) {
|
|
45
|
+
return { ok: true, json: async () => ({ channels: { Stable: { version: '999.0.0.0' } } }) };
|
|
46
|
+
}
|
|
47
|
+
if (href.includes('github')) {
|
|
48
|
+
return { ok: true, json: async () => ({ tag_name: 'bun-v2.0.0' }) };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
text: async () => '<remotePackage path="platform-tools"><revision><major>99</major><minor>0</minor><micro>0</micro></revision></remotePackage>',
|
|
53
|
+
};
|
|
54
|
+
}));
|
|
55
|
+
});
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
vi.unstubAllGlobals();
|
|
58
|
+
process.chdir(originalCwd);
|
|
59
|
+
try {
|
|
60
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
63
|
+
});
|
|
64
|
+
it('emits a pure JSON payload covering packages and drivers in --json mode', async () => {
|
|
65
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
66
|
+
await upgradeCommand([], { json: true });
|
|
67
|
+
expect(logSpy).toHaveBeenCalledTimes(1);
|
|
68
|
+
const payload = JSON.parse(logSpy.mock.calls[0][0]);
|
|
69
|
+
expect(payload.packages).toHaveLength(3);
|
|
70
|
+
expect(payload.packages.every((p) => p.latest === '9.9.9')).toBe(true);
|
|
71
|
+
expect(payload.drivers).toHaveLength(2);
|
|
72
|
+
expect(payload.drivers.find((d) => d.name === 'Google Chrome')?.latest).toBe('999.0.0.0');
|
|
73
|
+
expect(payload.updatesAvailable).toBeGreaterThan(0);
|
|
74
|
+
expect(execSync).not.toHaveBeenCalled();
|
|
75
|
+
logSpy.mockRestore();
|
|
76
|
+
});
|
|
77
|
+
it('restricts to a single package (no drivers) when package names are passed', async () => {
|
|
78
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
79
|
+
await upgradeCommand(['@testspectra/cli'], { json: true });
|
|
80
|
+
const payload = JSON.parse(logSpy.mock.calls[0][0]);
|
|
81
|
+
expect(payload.packages).toHaveLength(1);
|
|
82
|
+
expect(payload.packages[0].pkg).toBe('@testspectra/cli');
|
|
83
|
+
expect(payload.drivers).toHaveLength(0);
|
|
84
|
+
logSpy.mockRestore();
|
|
85
|
+
});
|
|
86
|
+
it('reports available updates in --check mode without running the package manager', async () => {
|
|
87
|
+
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
88
|
+
await upgradeCommand([], { check: true });
|
|
89
|
+
expect(execFileSync).toHaveBeenCalled();
|
|
90
|
+
expect(execSync).not.toHaveBeenCalled();
|
|
91
|
+
const output = logSpy.mock.calls.map((c) => String(c[0])).join('\n');
|
|
92
|
+
expect(output).toContain('package update(s) available');
|
|
93
|
+
logSpy.mockRestore();
|
|
94
|
+
});
|
|
95
|
+
});
|
package/dist/commands/add.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { TypeGenerator } from '../types/generator.js';
|
|
7
|
+
import { ConfigLoader } from '../config/loader.js';
|
|
7
8
|
function parsePnpmWorkspaceGlobs(content) {
|
|
8
9
|
const lines = content.split('\n');
|
|
9
10
|
const globs = [];
|
|
@@ -107,9 +108,8 @@ export async function addCommand(moduleNameArg, options = {}) {
|
|
|
107
108
|
let rootDir = cwd;
|
|
108
109
|
let cur = cwd;
|
|
109
110
|
while (cur !== path.dirname(cur)) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
fs.existsSync(path.join(cur, 'nx.json'))) {
|
|
111
|
+
const hasConfig = ConfigLoader.CONFIG_FILE_NAMES.some((name) => fs.existsSync(path.join(cur, name)));
|
|
112
|
+
if (hasConfig || fs.existsSync(path.join(cur, 'pnpm-workspace.yaml')) || fs.existsSync(path.join(cur, 'nx.json'))) {
|
|
113
113
|
rootDir = cur;
|
|
114
114
|
break;
|
|
115
115
|
}
|
|
@@ -289,6 +289,7 @@ export async function addCommand(moduleNameArg, options = {}) {
|
|
|
289
289
|
copyRecursive(sampleE2eSrc, targetE2eDir, {
|
|
290
290
|
'playground-e2e': e2eProjectName,
|
|
291
291
|
'packages/playground/e2e': path.relative(rootDir, targetE2eDir).replace(/\\/g, '/'),
|
|
292
|
+
'testspectra:scope:playground': `testspectra:scope:${featureName}`,
|
|
292
293
|
'scope:playground': `scope:${featureName}`,
|
|
293
294
|
});
|
|
294
295
|
// Adjust project.json
|
|
@@ -300,7 +301,7 @@ export async function addCommand(moduleNameArg, options = {}) {
|
|
|
300
301
|
pObj.sourceRoot = path.relative(rootDir, targetE2eDir).replace(/\\/g, '/');
|
|
301
302
|
pObj.targets.e2e.options.cwd = path.relative(rootDir, targetE2eDir).replace(/\\/g, '/');
|
|
302
303
|
pObj.targets['type-check'].options.cwd = '.';
|
|
303
|
-
pObj.tags = [`scope:${featureName}
|
|
304
|
+
pObj.tags = ['testspectra:e2e', `testspectra:scope:${featureName}`];
|
|
304
305
|
fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), 'utf-8');
|
|
305
306
|
}
|
|
306
307
|
catch { }
|
package/dist/commands/devices.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as p from '@clack/prompts';
|
|
4
|
+
import { generateArchitectureDoc } from '../generator/architecture-doc-generator.js';
|
|
5
|
+
function resolveBaseDir(options = {}) {
|
|
6
|
+
return options.cwd ? path.resolve(options.cwd) : process.cwd();
|
|
7
|
+
}
|
|
8
|
+
export function docsUpdateCommand(options = {}) {
|
|
9
|
+
const cwd = resolveBaseDir(options);
|
|
10
|
+
const result = generateArchitectureDoc(cwd);
|
|
11
|
+
if (result.mode === 'nx') {
|
|
12
|
+
p.log.success(chalk.green(`ARCHITECTURE.md regenerated (Nx/monorepo mode, ${result.featureCount ?? 0} feature module(s) detected).`));
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
p.log.success(chalk.green('ARCHITECTURE.md regenerated (standalone mode).'));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -13,9 +13,17 @@ export interface SystemCheckResult {
|
|
|
13
13
|
missing_count: number;
|
|
14
14
|
dependencies: DependencyStatus[];
|
|
15
15
|
}
|
|
16
|
-
export
|
|
16
|
+
export interface SystemCheckOptions {
|
|
17
|
+
/** Category filter: `core`, `web`, or `mobile` (default: all). */
|
|
18
|
+
scope?: string;
|
|
19
|
+
/** Comma-separated specific dependency keys, e.g. `chrome` or `chrome,adb` (takes precedence over `scope`). */
|
|
20
|
+
only?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function runSystemChecks(cwd?: string, options?: SystemCheckOptions): Promise<SystemCheckResult>;
|
|
17
23
|
export declare function doctorCommand(options?: {
|
|
18
24
|
fix?: boolean;
|
|
19
25
|
json?: boolean;
|
|
20
26
|
verbose?: boolean;
|
|
27
|
+
scope?: string;
|
|
28
|
+
only?: string;
|
|
21
29
|
}): Promise<void>;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -9,9 +9,16 @@ function getRunnerEnv() {
|
|
|
9
9
|
TEST_SPECTRA_DRIVER_CACHE: globalDriversDir,
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
export async function runSystemChecks(cwd = process.cwd()) {
|
|
12
|
+
export async function runSystemChecks(cwd = process.cwd(), options = {}) {
|
|
13
13
|
const binPath = RustCoreBridge.resolveBinaryPath();
|
|
14
|
-
const
|
|
14
|
+
const args = ['doctor', '--json'];
|
|
15
|
+
if (options.scope) {
|
|
16
|
+
args.push('--scope', options.scope);
|
|
17
|
+
}
|
|
18
|
+
if (options.only) {
|
|
19
|
+
args.push('--only', options.only);
|
|
20
|
+
}
|
|
21
|
+
const stdout = execFileSync(binPath, args, {
|
|
15
22
|
cwd,
|
|
16
23
|
env: getRunnerEnv(),
|
|
17
24
|
encoding: 'utf8',
|
|
@@ -29,11 +36,17 @@ export async function doctorCommand(options = {}) {
|
|
|
29
36
|
if (options.verbose) {
|
|
30
37
|
args.push('--verbose');
|
|
31
38
|
}
|
|
39
|
+
if (options.scope) {
|
|
40
|
+
args.push('--scope', options.scope);
|
|
41
|
+
}
|
|
42
|
+
if (options.only) {
|
|
43
|
+
args.push('--only', options.only);
|
|
44
|
+
}
|
|
32
45
|
if (options.fix) {
|
|
33
46
|
args.push('--fix');
|
|
34
47
|
}
|
|
35
48
|
if (options.json) {
|
|
36
|
-
const result = await runSystemChecks(cwd);
|
|
49
|
+
const result = await runSystemChecks(cwd, { scope: options.scope, only: options.only });
|
|
37
50
|
console.log(JSON.stringify(result, null, 2));
|
|
38
51
|
return;
|
|
39
52
|
}
|
|
@@ -42,6 +55,7 @@ export async function doctorCommand(options = {}) {
|
|
|
42
55
|
cwd,
|
|
43
56
|
env: getRunnerEnv(),
|
|
44
57
|
stdio: 'inherit',
|
|
58
|
+
windowsHide: true,
|
|
45
59
|
});
|
|
46
60
|
child.on('close', (code) => {
|
|
47
61
|
if (code === 0 || options.fix) {
|