@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/dist/commands/init.js
CHANGED
|
@@ -6,6 +6,7 @@ import chalk from 'chalk';
|
|
|
6
6
|
import { ConfigLoader } from '../config/loader.js';
|
|
7
7
|
import { TypeGenerator } from '../types/generator.js';
|
|
8
8
|
import { TsConfigGenerator } from '../generator/tsconfig-generator.js';
|
|
9
|
+
import { generateArchitectureDoc } from '../generator/architecture-doc-generator.js';
|
|
9
10
|
function parsePnpmWorkspaceGlobs(content) {
|
|
10
11
|
const lines = content.split('\n');
|
|
11
12
|
const globs = [];
|
|
@@ -460,6 +461,8 @@ export async function initCommand(options = {}) {
|
|
|
460
461
|
(typeof rootPkg.devDependencies['@testspectra/matchers'] === 'string' &&
|
|
461
462
|
rootPkg.devDependencies['@testspectra/matchers'].startsWith('link:')))
|
|
462
463
|
rootPkg.devDependencies['@testspectra/matchers'] = cliDepVersion;
|
|
464
|
+
if (!rootPkg.devDependencies['nx'])
|
|
465
|
+
rootPkg.devDependencies['nx'] = '^20.8.4';
|
|
463
466
|
if (!rootPkg.devDependencies['typescript'])
|
|
464
467
|
rootPkg.devDependencies['typescript'] = '^5.4.5';
|
|
465
468
|
if (!rootPkg.devDependencies['@wdio/cli'])
|
|
@@ -508,6 +511,7 @@ export async function initCommand(options = {}) {
|
|
|
508
511
|
copyRecursive(sampleE2eSrc, targetE2eDir, {
|
|
509
512
|
'playground-e2e': e2eProjectName,
|
|
510
513
|
'packages/playground/e2e': path.relative(cwd, targetE2eDir).replace(/\\/g, '/'),
|
|
514
|
+
'testspectra:scope:playground': `testspectra:scope:${featureName}`,
|
|
511
515
|
'scope:playground': `scope:${featureName}`,
|
|
512
516
|
});
|
|
513
517
|
// Adjust project.json
|
|
@@ -519,7 +523,7 @@ export async function initCommand(options = {}) {
|
|
|
519
523
|
pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
|
|
520
524
|
pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
|
|
521
525
|
pObj.targets['type-check'].options.cwd = '.';
|
|
522
|
-
pObj.tags = [`scope:${featureName}
|
|
526
|
+
pObj.tags = ['testspectra:e2e', `testspectra:scope:${featureName}`];
|
|
523
527
|
fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), 'utf-8');
|
|
524
528
|
}
|
|
525
529
|
catch { }
|
|
@@ -557,137 +561,7 @@ export async function initCommand(options = {}) {
|
|
|
557
561
|
// 7. Generate Ambient Types in ROOT only
|
|
558
562
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
559
563
|
ensureVsCodeExtensions(cwd);
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
|
|
563
|
-
|
|
564
|
-
---
|
|
565
|
-
|
|
566
|
-
## 1. Directory & File Overview
|
|
567
|
-
|
|
568
|
-
### Centralized Root Elements
|
|
569
|
-
- \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
|
|
570
|
-
- \`./.testspectra/\`: Local test project cache, runtime metadata, auto-generated master TSConfig (\`.testspectra/tsconfig.json\`), platform sub-configs (\`.testspectra/tsconfig/\`), and universal ambient types (\`.testspectra/types/\`). Global binaries and drivers are managed at \`~/.testspectra/drivers/\`.
|
|
571
|
-
- \`./nx.json\`: Nx target defaults with execution caching and inputs.
|
|
572
|
-
- \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
|
|
573
|
-
- \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
|
|
574
|
-
|
|
575
|
-
### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
|
|
576
|
-
Provides cross-feature reusable test entities with **100% zero-import global resolution**:
|
|
577
|
-
- \`page-objects/\`: Shared Page Object models (e.g. NavigationBar, AppHeader).
|
|
578
|
-
- \`steps/\`: Cross-feature business step flows (e.g. \`Step.loginAsAdmin()\`).
|
|
579
|
-
- \`actions/\`: Custom atomic actions (e.g. \`Spectra.dismissBanner()\`).
|
|
580
|
-
- \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
|
|
581
|
-
|
|
582
|
-
### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
|
|
583
|
-
${featureE2eDirs
|
|
584
|
-
.map((d) => {
|
|
585
|
-
const rel = path.relative(cwd, d);
|
|
586
|
-
const name = path.basename(path.dirname(d));
|
|
587
|
-
const e2eName = `${name}-${e2eFolderName}`;
|
|
588
|
-
return `- \`./${rel}\` (**${name}**, Nx Project: \`${e2eName}\`):
|
|
589
|
-
- \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
590
|
-
- \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
|
|
591
|
-
- \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
|
|
592
|
-
})
|
|
593
|
-
.join('\n')}
|
|
594
|
-
|
|
595
|
-
---
|
|
596
|
-
|
|
597
|
-
## 2. Zero-Import Consumption Flow & Mechanics
|
|
598
|
-
|
|
599
|
-
TestSpectra completely eliminates boilerplate \`import\` statements across all test specs, steps, and page objects.
|
|
600
|
-
|
|
601
|
-
### How It Works:
|
|
602
|
-
1. **Centralized Ambient Aggregation**:
|
|
603
|
-
TestSpectra Language Service Plugin & CLI automatically scan:
|
|
604
|
-
- **Local Feature Entities**: \`<feature>/${e2eFolderName}/page-objects/\`, \`<feature>/${e2eFolderName}/steps/\`, \`<feature>/${e2eFolderName}/actions/\`
|
|
605
|
-
- **Shared Library Entities**: \`./${sharedTestingRelPath}/page-objects/\`, \`./${sharedTestingRelPath}/steps/\`, \`./${sharedTestingRelPath}/actions/\`, \`./${sharedTestingRelPath}/fixtures/\`
|
|
606
|
-
2. **Ambient Typing Generation**:
|
|
607
|
-
These are compiled into \`.testspectra/types/web.d.ts\`, \`android.d.ts\`, \`ios.d.ts\`, \`fixtures.d.ts\`.
|
|
608
|
-
3. **Instant IDE Autocomplete & Zero Clutter**:
|
|
609
|
-
Specs consume both local feature objects and shared library utilities globally.
|
|
610
|
-
|
|
611
|
-
### Code Example:
|
|
612
|
-
\`\`\`typescript
|
|
613
|
-
// packages/features/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts
|
|
614
|
-
// 🌟 NOTICE: 100% Zero-Import & Flat Execution! (No import statement, no describe wrapper)
|
|
615
|
-
|
|
616
|
-
it("should navigate via shared header and login with local POM", async () => {
|
|
617
|
-
// 1. Consumed from Shared Testing Library (${sharedTestingRelPath}/page-objects/NavigationBar)
|
|
618
|
-
await NavigationBar.goToLogin();
|
|
619
|
-
|
|
620
|
-
// 2. Consumed from Shared Testing Library (${sharedTestingRelPath}/steps/loginAsAdmin)
|
|
621
|
-
await Step.loginAsAdmin();
|
|
622
|
-
|
|
623
|
-
// 3. Consumed from Local Feature POM (page-objects/LoginPage)
|
|
624
|
-
await LoginPage.flashAlert.shouldBeVisible();
|
|
625
|
-
await LoginPage.flashAlert.shouldContainText("Welcome");
|
|
626
|
-
|
|
627
|
-
// 4. Consumed from Shared Testing Library Custom Action (${sharedTestingRelPath}/actions/dismissBanner)
|
|
628
|
-
await Spectra.dismissBanner();
|
|
629
|
-
});
|
|
630
|
-
\`\`\`
|
|
631
|
-
|
|
632
|
-
---
|
|
633
|
-
|
|
634
|
-
## 3. How to Run Tests
|
|
635
|
-
|
|
636
|
-
You can execute tests either from the **Monorepo Root via Nx** or directly inside each **Feature Directory via CLI**:
|
|
637
|
-
|
|
638
|
-
### Option A: From Root Monorepo via Nx (Recommended for CI & Team Workflows)
|
|
639
|
-
|
|
640
|
-
\`\`\`bash
|
|
641
|
-
# 1. Run E2E for a SPECIFIC feature module (e.g. auth)
|
|
642
|
-
pnpm nx run auth-${e2eFolderName}:e2e
|
|
643
|
-
|
|
644
|
-
# 2. Run E2E for a specific feature on ANDROID or IOS
|
|
645
|
-
pnpm nx run auth-${e2eFolderName}:e2e --configuration=android
|
|
646
|
-
pnpm nx run auth-${e2eFolderName}:e2e --configuration=ios
|
|
647
|
-
|
|
648
|
-
# 3. Run E2E for a specific feature in HEADLESS browser mode
|
|
649
|
-
pnpm nx run auth-${e2eFolderName}:e2e --configuration=headless
|
|
650
|
-
|
|
651
|
-
# 4. Run ALL feature E2E suites across the entire monorepo in parallel
|
|
652
|
-
pnpm nx run-many -t e2e
|
|
653
|
-
|
|
654
|
-
# 5. Run only E2E tests for features modified in current Git branch / PR
|
|
655
|
-
pnpm nx affected -t e2e
|
|
656
|
-
\`\`\`
|
|
657
|
-
|
|
658
|
-
### Option B: From Inside the Feature Directory (Direct CLI)
|
|
659
|
-
|
|
660
|
-
\`\`\`bash
|
|
661
|
-
# Navigate to the feature e2e folder
|
|
662
|
-
cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
|
|
663
|
-
|
|
664
|
-
# Run all specs in this feature
|
|
665
|
-
pnpm spectra run
|
|
666
|
-
|
|
667
|
-
# Run a specific test case file
|
|
668
|
-
pnpm spectra run specs/Auth/TC-AUTH-01/web.test.ts
|
|
669
|
-
|
|
670
|
-
# Target Android device or emulator
|
|
671
|
-
pnpm spectra run --target android
|
|
672
|
-
|
|
673
|
-
# Run in headed / interactive browser mode
|
|
674
|
-
pnpm spectra run --no-headless
|
|
675
|
-
\`\`\`
|
|
676
|
-
|
|
677
|
-
---
|
|
678
|
-
|
|
679
|
-
## 4. Type Checking & Validation
|
|
680
|
-
|
|
681
|
-
\`\`\`bash
|
|
682
|
-
# Type check all E2E projects via Nx
|
|
683
|
-
pnpm nx run-many -t type-check
|
|
684
|
-
|
|
685
|
-
# Or standard TypeScript project references build from root
|
|
686
|
-
pnpm type-check
|
|
687
|
-
# or: npx tsc -b
|
|
688
|
-
\`\`\`
|
|
689
|
-
`;
|
|
690
|
-
fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), archDocContent, 'utf-8');
|
|
564
|
+
generateArchitectureDoc(cwd);
|
|
691
565
|
// 8. Ensure root .gitignore ignores .testspectra/ and *.tsbuildinfo
|
|
692
566
|
const rootGitignorePath = path.join(cwd, '.gitignore');
|
|
693
567
|
const ignoreEntries = ['.testspectra/', '*.tsbuildinfo', '.nx/cache'];
|
|
@@ -741,34 +615,7 @@ pnpm type-check
|
|
|
741
615
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
742
616
|
ensureVsCodeExtensions(cwd);
|
|
743
617
|
// Standalone Architecture Documentation
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
This project is built with the TestSpectra testing framework.
|
|
747
|
-
|
|
748
|
-
## Structure & File Map
|
|
749
|
-
- \`./spectra.config.ts\`: Typed runner configuration.
|
|
750
|
-
- \`./specs/\`: Layered test cases (\`specs/<Suite>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
751
|
-
- \`./page-objects/\`: Page Object models per platform.
|
|
752
|
-
- \`./steps/\`: Business flows accessible globally via \`Step.*\`.
|
|
753
|
-
- \`./actions/\`: Atomic actions accessible globally via \`Spectra.*\`.
|
|
754
|
-
- \`./fixtures/\`: JSON fixtures accessible globally via \`Fixture.*\`.
|
|
755
|
-
- \`./global-hooks/\`: Global suite lifecycle hooks.
|
|
756
|
-
- \`./.testspectra/\`: Auto-generated ambient typings and local runtime cache.
|
|
757
|
-
|
|
758
|
-
## Execution
|
|
759
|
-
\`\`\`bash
|
|
760
|
-
# Run web test suite
|
|
761
|
-
pnpm test
|
|
762
|
-
|
|
763
|
-
# Run Android / iOS
|
|
764
|
-
pnpm spectra run --target android
|
|
765
|
-
pnpm spectra run --target ios
|
|
766
|
-
|
|
767
|
-
# Type check
|
|
768
|
-
pnpm type-check
|
|
769
|
-
\`\`\`
|
|
770
|
-
`;
|
|
771
|
-
fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), standaloneArchDoc, 'utf-8');
|
|
618
|
+
generateArchitectureDoc(cwd);
|
|
772
619
|
// Safely update root README.md (do not overwrite existing content)
|
|
773
620
|
const standaloneReadmePath = path.join(cwd, 'README.md');
|
|
774
621
|
const standaloneArchSection = `\n## E2E Testing Architecture (TestSpectra)\n\nDetailed test architecture and folder map are documented in: \n👉 **[ARCHITECTURE.md](./ARCHITECTURE.md)**\n\n- **Run tests**: \`pnpm test\`\n- **Type-check**: \`pnpm type-check\`\n`;
|
package/dist/commands/install.js
CHANGED
|
@@ -17,6 +17,9 @@ export async function installCommand(item, options = {}) {
|
|
|
17
17
|
if (item) {
|
|
18
18
|
args.push(item);
|
|
19
19
|
}
|
|
20
|
+
if (options.force) {
|
|
21
|
+
args.push('--force');
|
|
22
|
+
}
|
|
20
23
|
return new Promise((resolve) => {
|
|
21
24
|
let child;
|
|
22
25
|
if (binPath === 'cargo') {
|
|
@@ -34,6 +37,7 @@ export async function installCommand(item, options = {}) {
|
|
|
34
37
|
cwd: workspaceRoot,
|
|
35
38
|
env: runnerEnv,
|
|
36
39
|
stdio: 'inherit',
|
|
40
|
+
windowsHide: true,
|
|
37
41
|
});
|
|
38
42
|
}
|
|
39
43
|
else {
|
|
@@ -41,6 +45,7 @@ export async function installCommand(item, options = {}) {
|
|
|
41
45
|
cwd,
|
|
42
46
|
env: runnerEnv,
|
|
43
47
|
stdio: 'inherit',
|
|
48
|
+
windowsHide: true,
|
|
44
49
|
});
|
|
45
50
|
}
|
|
46
51
|
child.on('close', (code) => {
|
package/dist/commands/license.js
CHANGED
package/dist/commands/test.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export interface TestCommandOptions {
|
|
2
2
|
cwd?: string;
|
|
3
|
+
runner?: 'spectra' | 'vitest';
|
|
3
4
|
headed?: boolean;
|
|
4
5
|
headless?: boolean;
|
|
5
6
|
stepDelay?: string | number;
|
|
@@ -8,6 +9,18 @@ export interface TestCommandOptions {
|
|
|
8
9
|
output?: string;
|
|
9
10
|
concurrency?: string | number;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Guarantees the `.testspectra` solution-tsconfig scaffolding exists before Vitest starts.
|
|
14
|
+
*
|
|
15
|
+
* The bundled Vitest browser runner transpiles raw TypeScript specs/page-objects Just-In-Time.
|
|
16
|
+
* When it does, `vite:esbuild` walks the nearest `tsconfig.json` and tries to read every entry in
|
|
17
|
+
* its `references` — including `./.testspectra/tsconfig.json`. That directory is gitignored, so on
|
|
18
|
+
* a clean clone / fresh CI checkout the reference points at a file that does not exist, esbuild
|
|
19
|
+
* throws `ENOENT`, Vite answers HTTP 500, and every suite dies with "Failed to fetch dynamically
|
|
20
|
+
* imported module" before a single assertion runs. Regenerating the scaffolding up front (same
|
|
21
|
+
* output as `spectra sync-types`) keeps the reference resolvable.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ensureTestSpectraScaffolding(cwd: string): void;
|
|
11
24
|
/**
|
|
12
25
|
* `spectra test` — delegates to `@testspectra/react`'s bundled Vitest browser-mode runner for
|
|
13
26
|
* component testing. Deliberately separate from `spectra run` (E2E via core/orchestrator): see
|
package/dist/commands/test.js
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import chalk from 'chalk';
|
|
3
4
|
import * as p from '@clack/prompts';
|
|
5
|
+
import { ConfigLoader } from '../config/loader.js';
|
|
4
6
|
import { renderSummaryView } from '../reporter/summary-view.js';
|
|
5
7
|
import { Reporter } from '../runner/reporter.js';
|
|
8
|
+
import { RustCoreBridge } from '../runner/bridge.js';
|
|
9
|
+
import { TypeGenerator } from '../generator/type-generator.js';
|
|
6
10
|
import { runSystemChecks } from './doctor.js';
|
|
11
|
+
/**
|
|
12
|
+
* Guarantees the `.testspectra` solution-tsconfig scaffolding exists before Vitest starts.
|
|
13
|
+
*
|
|
14
|
+
* The bundled Vitest browser runner transpiles raw TypeScript specs/page-objects Just-In-Time.
|
|
15
|
+
* When it does, `vite:esbuild` walks the nearest `tsconfig.json` and tries to read every entry in
|
|
16
|
+
* its `references` — including `./.testspectra/tsconfig.json`. That directory is gitignored, so on
|
|
17
|
+
* a clean clone / fresh CI checkout the reference points at a file that does not exist, esbuild
|
|
18
|
+
* throws `ENOENT`, Vite answers HTTP 500, and every suite dies with "Failed to fetch dynamically
|
|
19
|
+
* imported module" before a single assertion runs. Regenerating the scaffolding up front (same
|
|
20
|
+
* output as `spectra sync-types`) keeps the reference resolvable.
|
|
21
|
+
*/
|
|
22
|
+
export function ensureTestSpectraScaffolding(cwd) {
|
|
23
|
+
const masterConfigPath = path.join(cwd, '.testspectra', 'tsconfig.json');
|
|
24
|
+
if (fs.existsSync(masterConfigPath))
|
|
25
|
+
return;
|
|
26
|
+
TypeGenerator.generateAll(cwd);
|
|
27
|
+
}
|
|
7
28
|
/**
|
|
8
29
|
* Finds the Chrome-for-Testing binary that `spectra doctor` already installs/manages for the
|
|
9
30
|
* E2E CDP driver (`core/driver-cdp`) — same install, same binary. `@testspectra/react` must not
|
|
@@ -14,8 +35,10 @@ import { runSystemChecks } from './doctor.js';
|
|
|
14
35
|
*/
|
|
15
36
|
async function resolveBrowserExecutable(cwd) {
|
|
16
37
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
// `--only chrome` runs exactly one check — no adb/bun/git spawns, no Matchers lookup — since
|
|
39
|
+
// this call exists solely to locate the managed browser binary.
|
|
40
|
+
const { dependencies } = await runSystemChecks(cwd, { only: 'chrome' });
|
|
41
|
+
const chrome = dependencies.find((d) => d.category === 'web' && d.name.includes('Chrome'));
|
|
19
42
|
return chrome?.installed && chrome.location ? chrome.location : null;
|
|
20
43
|
}
|
|
21
44
|
catch {
|
|
@@ -30,6 +53,11 @@ async function resolveBrowserExecutable(cwd) {
|
|
|
30
53
|
*/
|
|
31
54
|
export async function testCommand(options = {}) {
|
|
32
55
|
const cwd = options.cwd ?? process.cwd();
|
|
56
|
+
// User-facing phase log. Deliberately describes TestSpectra's own stages only — never the
|
|
57
|
+
// bundled runtime/toolchain underneath — so the CLI surface stays implementation-agnostic.
|
|
58
|
+
const logPhase = (message) => p.log.step(chalk.dim(message));
|
|
59
|
+
logPhase('Preparing component test workspace…');
|
|
60
|
+
ensureTestSpectraScaffolding(cwd);
|
|
33
61
|
let reactPkg;
|
|
34
62
|
try {
|
|
35
63
|
reactPkg = await import('@testspectra/react');
|
|
@@ -41,6 +69,7 @@ export async function testCommand(options = {}) {
|
|
|
41
69
|
process.exit(1);
|
|
42
70
|
return;
|
|
43
71
|
}
|
|
72
|
+
logPhase('Resolving managed test browser…');
|
|
44
73
|
const browserExecutablePath = await resolveBrowserExecutable(cwd);
|
|
45
74
|
if (!browserExecutablePath) {
|
|
46
75
|
p.log.error(chalk.red('✖ TestSpectra-managed Chrome for Testing was not found.'));
|
|
@@ -64,6 +93,21 @@ export async function testCommand(options = {}) {
|
|
|
64
93
|
else if (options.headless !== undefined) {
|
|
65
94
|
headless = options.headless;
|
|
66
95
|
}
|
|
96
|
+
// Fallback to spectra.config.ts if options were not explicitly provided on CLI
|
|
97
|
+
try {
|
|
98
|
+
const config = await ConfigLoader.loadConfig(cwd);
|
|
99
|
+
if (stepDelayMs === undefined && config?.webConfig?.stepDelay !== undefined) {
|
|
100
|
+
const parsed = parseInt(String(config.webConfig.stepDelay), 10);
|
|
101
|
+
if (!isNaN(parsed) && parsed >= 0)
|
|
102
|
+
stepDelayMs = parsed;
|
|
103
|
+
}
|
|
104
|
+
if (!options.headed && options.headless === undefined && config?.webConfig?.headless !== undefined) {
|
|
105
|
+
headless = String(config.webConfig.headless) !== 'false' && Boolean(config.webConfig.headless);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
/* ignore config load failures */
|
|
110
|
+
}
|
|
67
111
|
const concurrency = options.concurrency !== undefined ? parseInt(String(options.concurrency), 10) : undefined;
|
|
68
112
|
const outputFile = options.output ? path.resolve(cwd, options.output) : undefined;
|
|
69
113
|
// Reuses the exact same semantic terminal reporter `spectra run` drives (see
|
|
@@ -72,31 +116,81 @@ export async function testCommand(options = {}) {
|
|
|
72
116
|
// / setup.ts), so the same Reporter class parses them with zero changes on either side.
|
|
73
117
|
const reporter = new Reporter();
|
|
74
118
|
const runStartedAt = Date.now();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
let ok = false;
|
|
120
|
+
if (options.runner === 'vitest') {
|
|
121
|
+
logPhase('Starting component test runner (Vitest Browser Mode)…');
|
|
122
|
+
ok = await reactPkg.runComponentTests({
|
|
123
|
+
runner: 'vitest',
|
|
124
|
+
cwd,
|
|
125
|
+
headless,
|
|
126
|
+
stepDelayMs,
|
|
127
|
+
spec: options.spec,
|
|
128
|
+
outputFile,
|
|
129
|
+
concurrency: concurrency !== undefined && !isNaN(concurrency) ? concurrency : undefined,
|
|
130
|
+
browserExecutablePath,
|
|
131
|
+
onLine: (line) => {
|
|
132
|
+
const cleanLine = line.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '').trim();
|
|
133
|
+
if (!cleanLine)
|
|
134
|
+
return;
|
|
135
|
+
if (cleanLine.startsWith('[TESTSPECTRA_')) {
|
|
136
|
+
reporter.addLog({ timestamp: new Date().toLocaleTimeString(), level: 'INFO', message: cleanLine });
|
|
137
|
+
if (!reporter.isInteractiveMode()) {
|
|
138
|
+
console.log(cleanLine);
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (reactPkg.isVitestNoise?.(cleanLine)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const sanitized = reactPkg.sanitizeErrorMessage?.(cleanLine) ?? cleanLine;
|
|
146
|
+
reporter.addLog({ timestamp: new Date().toLocaleTimeString(), level: 'ERROR', message: sanitized });
|
|
147
|
+
if (!reporter.isInteractiveMode()) {
|
|
148
|
+
console.error(chalk.red(sanitized));
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
logPhase('Starting component test runner (Spectra Native CDP Engine)…');
|
|
155
|
+
const bundleServer = await reactPkg.startBundleServer({
|
|
156
|
+
testRoot: cwd,
|
|
157
|
+
stepDelayMs: stepDelayMs ?? 0,
|
|
158
|
+
});
|
|
159
|
+
try {
|
|
160
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
161
|
+
const globalAppDataPath = path.join(home, '.testspectra');
|
|
162
|
+
const appDataPath = globalAppDataPath;
|
|
163
|
+
const runResult = await RustCoreBridge.run({
|
|
164
|
+
baseDir: cwd,
|
|
165
|
+
appDataPath,
|
|
166
|
+
platform: 'component',
|
|
167
|
+
config: {
|
|
168
|
+
bundleServerUrl: bundleServer.url,
|
|
169
|
+
webConfig: {
|
|
170
|
+
headless,
|
|
171
|
+
stepDelay: stepDelayMs,
|
|
172
|
+
browserExecutablePath,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
specs: options.spec ? [options.spec] : undefined,
|
|
176
|
+
concurrency: concurrency !== undefined && !isNaN(concurrency) ? concurrency : undefined,
|
|
177
|
+
outputJsonPath: outputFile,
|
|
178
|
+
}, reporter);
|
|
179
|
+
ok = runResult.status.toLowerCase() === 'passed';
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
bundleServer.stop();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
92
185
|
const durationMs = Date.now() - runStartedAt;
|
|
93
186
|
const result = reporter.generateResult(ok ? 'passed' : 'failed', `${(durationMs / 1000).toFixed(1)}s`);
|
|
94
|
-
const
|
|
187
|
+
const failedCount = !ok && result.failedCount === 0 ? 1 : result.failedCount;
|
|
188
|
+
const totalTests = result.passedCount + failedCount;
|
|
95
189
|
renderSummaryView({
|
|
96
190
|
totalSuites: totalTests,
|
|
97
191
|
totalTests,
|
|
98
192
|
passedCount: result.passedCount,
|
|
99
|
-
failedCount
|
|
193
|
+
failedCount,
|
|
100
194
|
durationMs,
|
|
101
195
|
reportLocation: outputFile ? path.relative(cwd, outputFile) : '(use -o/--output to save a report)',
|
|
102
196
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PackageUpdate {
|
|
2
|
+
pkg: string;
|
|
3
|
+
current: string | null;
|
|
4
|
+
latest: string | null;
|
|
5
|
+
needsUpdate: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function upgradeCommand(requestedPackages?: string[], options?: {
|
|
8
|
+
check?: boolean;
|
|
9
|
+
packagesOnly?: boolean;
|
|
10
|
+
driversOnly?: boolean;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { execSync, execFileSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { RustCoreBridge } from '../runner/bridge.js';
|
|
6
|
+
import { checkDriverUpdates } from '../utils/dependency-updates.js';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const PACKAGES = ['@testspectra/cli', '@testspectra/matchers', '@testspectra/react'];
|
|
10
|
+
/** Filters requested package names against the known catalog; empty means "all". */
|
|
11
|
+
function selectPackages(requested) {
|
|
12
|
+
if (requested.length === 0)
|
|
13
|
+
return [...PACKAGES];
|
|
14
|
+
const known = new Set(PACKAGES);
|
|
15
|
+
const unknown = requested.filter((p) => !known.has(p));
|
|
16
|
+
if (unknown.length > 0) {
|
|
17
|
+
console.warn(`\x1b[33mIgnoring unknown TestSpectra package(s): ${unknown.join(', ')}\x1b[0m`);
|
|
18
|
+
}
|
|
19
|
+
return requested.filter((p) => known.has(p));
|
|
20
|
+
}
|
|
21
|
+
function getLatestVersion(pkg) {
|
|
22
|
+
try {
|
|
23
|
+
const out = execFileSync('npm', ['view', pkg, 'version', '--json'], {
|
|
24
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
25
|
+
timeout: 10000,
|
|
26
|
+
})
|
|
27
|
+
.toString()
|
|
28
|
+
.trim()
|
|
29
|
+
.replace(/"/g, '');
|
|
30
|
+
return out || null;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function getCurrentVersion(pkg, cwd) {
|
|
37
|
+
let cur = path.resolve(cwd);
|
|
38
|
+
while (cur && cur !== path.dirname(cur)) {
|
|
39
|
+
const pkgJson = path.join(cur, 'node_modules', pkg, 'package.json');
|
|
40
|
+
if (fs.existsSync(pkgJson)) {
|
|
41
|
+
try {
|
|
42
|
+
const p = JSON.parse(fs.readFileSync(pkgJson, 'utf-8'));
|
|
43
|
+
return p.version ?? null;
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
}
|
|
47
|
+
cur = path.dirname(cur);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function detectPackageManager(cwd) {
|
|
52
|
+
if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml')))
|
|
53
|
+
return 'pnpm';
|
|
54
|
+
if (fs.existsSync(path.join(cwd, 'bun.lockb')) || fs.existsSync(path.join(cwd, 'bun.lock')))
|
|
55
|
+
return 'bun';
|
|
56
|
+
return 'npm';
|
|
57
|
+
}
|
|
58
|
+
function collectPackageUpdates(cwd, packages) {
|
|
59
|
+
return packages.map((pkg) => {
|
|
60
|
+
const current = getCurrentVersion(pkg, cwd);
|
|
61
|
+
const latest = getLatestVersion(pkg);
|
|
62
|
+
return { pkg, current, latest, needsUpdate: !!latest && latest !== current };
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function printUpdateRow(label, current, latest, needsUpdate) {
|
|
66
|
+
const currentStr = current ? `\x1b[33mv${current}\x1b[0m` : '\x1b[90m(not installed)\x1b[0m';
|
|
67
|
+
const latestStr = latest ? `\x1b[32mv${latest}\x1b[0m` : '\x1b[90m(unknown)\x1b[0m';
|
|
68
|
+
const arrow = needsUpdate ? ` → ${latestStr}` : '';
|
|
69
|
+
const status = needsUpdate ? '\x1b[33m↑\x1b[0m' : '\x1b[32m✓\x1b[0m';
|
|
70
|
+
console.log(`\x1b[36m│\x1b[0m ${status} ${label.padEnd(28)} ${currentStr}${arrow}`);
|
|
71
|
+
}
|
|
72
|
+
function buildInstallCmd(pm, pkgSpecs, cwd) {
|
|
73
|
+
const list = pkgSpecs.join(' ');
|
|
74
|
+
switch (pm) {
|
|
75
|
+
case 'pnpm': {
|
|
76
|
+
const isWorkspaceRoot = fs.existsSync(path.join(cwd, 'pnpm-workspace.yaml'));
|
|
77
|
+
return isWorkspaceRoot ? `pnpm add -D -w ${list}` : `pnpm add -D ${list}`;
|
|
78
|
+
}
|
|
79
|
+
case 'bun':
|
|
80
|
+
return `bun add -d ${list}`;
|
|
81
|
+
case 'npm':
|
|
82
|
+
default:
|
|
83
|
+
return `npm install -D ${list}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export async function upgradeCommand(requestedPackages = [], options = {}) {
|
|
87
|
+
const cwd = process.cwd();
|
|
88
|
+
const pm = detectPackageManager(cwd);
|
|
89
|
+
const selected = selectPackages(requestedPackages);
|
|
90
|
+
const targeted = requestedPackages.length > 0;
|
|
91
|
+
if (targeted && selected.length === 0) {
|
|
92
|
+
console.error(`\x1b[31mNo known TestSpectra package matched: ${requestedPackages.join(', ')}. Known packages: ${PACKAGES.join(', ')}\x1b[0m`);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Machine-readable mode for the VS Code Preflight panel: implies `--check`, never mutates.
|
|
97
|
+
if (options.json) {
|
|
98
|
+
const packages = collectPackageUpdates(cwd, selected);
|
|
99
|
+
const drivers = targeted ? [] : await checkDriverUpdates();
|
|
100
|
+
const updatesAvailable = [...packages, ...drivers].filter((u) => u.needsUpdate).length;
|
|
101
|
+
console.log(JSON.stringify({ packages, drivers, updatesAvailable }, null, 2));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
console.log('\x1b[36m┌ 🚀 TestSpectra Self-Upgrade Mechanism\x1b[0m');
|
|
105
|
+
console.log('\x1b[36m│\x1b[0m');
|
|
106
|
+
const updates = [];
|
|
107
|
+
let packagesUpdated = false;
|
|
108
|
+
// Phase 1: NPM Packages
|
|
109
|
+
if (!options.driversOnly) {
|
|
110
|
+
console.log(`\x1b[36m│\x1b[0m \x1b[1mPhase 1: Workspace Packages\x1b[0m (Manager: \x1b[33m${pm}\x1b[0m)`);
|
|
111
|
+
console.log('\x1b[36m│\x1b[0m Fetching latest versions from npm registry...');
|
|
112
|
+
updates.push(...collectPackageUpdates(cwd, selected));
|
|
113
|
+
for (const { pkg, current, latest, needsUpdate } of updates) {
|
|
114
|
+
const currentStr = current ? `\x1b[33mv${current}\x1b[0m` : '\x1b[90m(not installed)\x1b[0m';
|
|
115
|
+
const latestStr = latest ? `\x1b[32mv${latest}\x1b[0m` : '\x1b[31m(unavailable)\x1b[0m';
|
|
116
|
+
const arrow = needsUpdate ? ` → ${latestStr}` : '';
|
|
117
|
+
const status = !latest ? '\x1b[31m✗\x1b[0m' : needsUpdate ? '\x1b[33m↑\x1b[0m' : '\x1b[32m✓\x1b[0m';
|
|
118
|
+
console.log(`\x1b[36m│\x1b[0m ${status} ${pkg.padEnd(28)} ${currentStr}${arrow}`);
|
|
119
|
+
}
|
|
120
|
+
const toUpdate = updates.filter((u) => u.needsUpdate && u.latest);
|
|
121
|
+
console.log('\x1b[36m│\x1b[0m');
|
|
122
|
+
if (options.check) {
|
|
123
|
+
console.log(`\x1b[36m│\x1b[0m \x1b[33m${toUpdate.length} package update(s) available. Run \x1b[36mspectra upgrade\x1b[33m to install.\x1b[0m`);
|
|
124
|
+
}
|
|
125
|
+
else if (toUpdate.length > 0) {
|
|
126
|
+
const pkgSpecs = toUpdate.map((u) => `${u.pkg}@${u.latest}`);
|
|
127
|
+
const installCmd = buildInstallCmd(pm, pkgSpecs, cwd);
|
|
128
|
+
console.log(`\x1b[36m│\x1b[0m Running: \x1b[90m${installCmd}\x1b[0m`);
|
|
129
|
+
try {
|
|
130
|
+
execSync(installCmd, { cwd, stdio: 'inherit' });
|
|
131
|
+
packagesUpdated = true;
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
console.error(`\x1b[31m│ ✗ Package upgrade failed: ${err.message}\x1b[0m`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.log('\x1b[36m│\x1b[0m \x1b[32m✓ All TestSpectra packages are already up-to-date!\x1b[0m');
|
|
140
|
+
}
|
|
141
|
+
console.log('\x1b[36m│\x1b[0m');
|
|
142
|
+
}
|
|
143
|
+
if (options.check) {
|
|
144
|
+
if (!targeted) {
|
|
145
|
+
const drivers = await checkDriverUpdates();
|
|
146
|
+
if (drivers.length > 0) {
|
|
147
|
+
console.log('\x1b[36m│\x1b[0m \x1b[1mDriver & Agent Status\x1b[0m');
|
|
148
|
+
for (const dep of drivers) {
|
|
149
|
+
printUpdateRow(dep.name, dep.current, dep.latest, dep.needsUpdate);
|
|
150
|
+
}
|
|
151
|
+
console.log('\x1b[36m│\x1b[0m');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
console.log('\x1b[36m└──────────────────────────────────────────────────────────\x1b[0m\n');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Phase 2: Drivers and Binaries
|
|
158
|
+
if (!options.packagesOnly && !targeted) {
|
|
159
|
+
console.log(`\x1b[36m│\x1b[0m \x1b[1mPhase 2: System Binaries & Drivers\x1b[0m`);
|
|
160
|
+
try {
|
|
161
|
+
const binPath = RustCoreBridge.resolveBinaryPath();
|
|
162
|
+
// Using doctor --fix as the underlying mechanism for pulling drivers
|
|
163
|
+
// In the future, this could be passed a specific --force-latest flag
|
|
164
|
+
console.log(`\x1b[36m│\x1b[0m Forcing update for ADB and Spectra Android Agent...`);
|
|
165
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
166
|
+
const globalDriversDir = path.join(home, '.testspectra', 'drivers');
|
|
167
|
+
execSync(`"${binPath}" doctor --fix`, {
|
|
168
|
+
cwd,
|
|
169
|
+
stdio: 'inherit',
|
|
170
|
+
env: {
|
|
171
|
+
...process.env,
|
|
172
|
+
TEST_SPECTRA_DRIVER_CACHE: globalDriversDir,
|
|
173
|
+
TEST_SPECTRA_FORCE_LATEST: '1',
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
console.log('\x1b[36m│\x1b[0m \x1b[32m✓ Drivers updated successfully.\x1b[0m');
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
console.error(`\x1b[31m│ ✗ Driver upgrade failed: ${err.message}\x1b[0m`);
|
|
180
|
+
// We don't exit 1 here so we can still print the post-upgrade report
|
|
181
|
+
}
|
|
182
|
+
console.log('\x1b[36m│\x1b[0m');
|
|
183
|
+
}
|
|
184
|
+
// Phase 3: Post-Upgrade Report
|
|
185
|
+
console.log('\x1b[36m└──────────────────────────────────────────────────────────\x1b[0m');
|
|
186
|
+
console.log('\x1b[32m🎉 TestSpectra upgrade complete!\x1b[0m');
|
|
187
|
+
if (!options.driversOnly && packagesUpdated) {
|
|
188
|
+
const toUpdate = updates.filter((u) => u.needsUpdate && u.latest);
|
|
189
|
+
for (const u of toUpdate) {
|
|
190
|
+
console.log(` \x1b[32m✓\x1b[0m ${u.pkg}: v${u.current ?? '?'} → v${u.latest}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!options.packagesOnly) {
|
|
194
|
+
console.log(` \x1b[32m✓\x1b[0m System drivers and agents synchronized to latest versions`);
|
|
195
|
+
}
|
|
196
|
+
console.log('');
|
|
197
|
+
}
|