@testspectra/cli 1.0.70 ā 1.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +48 -0
- package/README.md +36 -270
- package/bin/.testspectra-runner.hash +1 -0
- package/bin/spectra.js +1 -1
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-BhlW-eXG.css +1 -0
- package/demo-app/assets/index-DOtRypCa.js +154 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/commands/__tests__/doctor.test.d.ts +1 -0
- package/dist/commands/__tests__/doctor.test.js +39 -0
- package/dist/commands/__tests__/init.test.d.ts +1 -0
- package/dist/commands/__tests__/init.test.js +148 -0
- package/dist/commands/__tests__/license.test.d.ts +1 -0
- package/dist/commands/__tests__/license.test.js +13 -0
- package/dist/commands/__tests__/refactor.test.d.ts +1 -0
- package/dist/commands/__tests__/refactor.test.js +64 -0
- package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
- package/dist/commands/__tests__/run-e2e.test.js +422 -0
- package/dist/commands/__tests__/run.test.d.ts +1 -0
- package/dist/commands/__tests__/run.test.js +273 -0
- package/dist/commands/__tests__/watch.test.d.ts +1 -0
- package/dist/commands/__tests__/watch.test.js +155 -0
- package/dist/commands/add.js +86 -85
- package/dist/commands/demo.js +21 -11
- package/dist/commands/devices.d.ts +14 -2
- package/dist/commands/devices.js +44 -34
- package/dist/commands/doctor.d.ts +3 -1
- package/dist/commands/doctor.js +43 -264
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +211 -196
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.js +59 -0
- package/dist/commands/license.d.ts +4 -0
- package/dist/commands/license.js +30 -0
- package/dist/commands/lint.d.ts +5 -0
- package/dist/commands/lint.js +55 -0
- package/dist/commands/refactor.d.ts +9 -0
- package/dist/commands/refactor.js +172 -0
- package/dist/commands/run.d.ts +23 -2
- package/dist/commands/run.js +322 -111
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.js +28 -0
- package/dist/commands/sync-types.d.ts +4 -0
- package/dist/commands/sync-types.js +15 -0
- package/dist/commands/test.d.ts +17 -0
- package/dist/commands/test.js +76 -0
- package/dist/commands/update.js +39 -43
- package/dist/commands/watch.d.ts +6 -0
- package/dist/commands/watch.js +55 -45
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +40 -25
- package/dist/config/schema.d.ts +104 -145
- package/dist/config/schema.js +36 -45
- package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
- package/dist/generator/__tests__/tsconfig-isolation.test.js +282 -0
- package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
- package/dist/generator/__tests__/type-generator.test.js +386 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/tsconfig-generator.d.ts +27 -0
- package/dist/generator/tsconfig-generator.js +419 -0
- package/dist/generator/type-generator.d.ts +59 -0
- package/dist/generator/type-generator.js +740 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +165 -51
- package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
- package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
- package/dist/lifecycle/hook-discovery.d.ts +24 -0
- package/dist/lifecycle/hook-discovery.js +60 -0
- package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
- package/dist/lifecycle/hook-dispatcher.js +190 -0
- package/dist/lifecycle/index.d.ts +3 -0
- package/dist/lifecycle/index.js +3 -0
- package/dist/lifecycle/parallel-grouping.d.ts +27 -0
- package/dist/lifecycle/parallel-grouping.js +151 -0
- package/dist/linter/__tests__/anonymous-export.test.d.ts +1 -0
- package/dist/linter/__tests__/anonymous-export.test.js +184 -0
- package/dist/linter/__tests__/component-test-imports.test.d.ts +1 -0
- package/dist/linter/__tests__/component-test-imports.test.js +115 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
- package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
- package/dist/linter/__tests__/spec-schema.test.js +369 -0
- package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
- package/dist/linter/__tests__/step-parity.test.js +372 -0
- package/dist/linter/discovery.d.ts +30 -0
- package/dist/linter/discovery.js +249 -0
- package/dist/linter/extractor.d.ts +67 -0
- package/dist/linter/extractor.js +245 -0
- package/dist/linter/index.d.ts +7 -0
- package/dist/linter/index.js +7 -0
- package/dist/linter/parser.d.ts +24 -0
- package/dist/linter/parser.js +219 -0
- package/dist/linter/schemas/spec.schema.d.ts +83 -0
- package/dist/linter/schemas/spec.schema.js +150 -0
- package/dist/linter/schemas/suite.schema.d.ts +59 -0
- package/dist/linter/schemas/suite.schema.js +83 -0
- package/dist/linter/spec-linter.d.ts +23 -0
- package/dist/linter/spec-linter.js +630 -0
- package/dist/linter/types.d.ts +28 -0
- package/dist/linter/types.js +1 -0
- package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
- package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
- package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
- package/dist/mobile/__tests__/device-pool.test.js +97 -0
- package/dist/mobile/device-pool.d.ts +23 -0
- package/dist/mobile/device-pool.js +1 -0
- package/dist/mobile/index.d.ts +1 -0
- package/dist/mobile/index.js +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.js +873 -0
- package/dist/refactor/index.d.ts +1 -0
- package/dist/refactor/index.js +1 -0
- package/dist/refactor/refactor-engine.d.ts +50 -0
- package/dist/refactor/refactor-engine.js +587 -0
- package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
- package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
- package/dist/reporter/semantic-formatter.d.ts +6 -0
- package/dist/reporter/semantic-formatter.js +206 -0
- package/dist/reporter/summary-view.d.ts +12 -0
- package/dist/reporter/summary-view.js +19 -0
- package/dist/reporter/terminal-stream.d.ts +32 -0
- package/dist/reporter/terminal-stream.js +82 -0
- package/dist/reporter/types.d.ts +2 -0
- package/dist/reporter/types.js +1 -0
- package/dist/runner/bridge.d.ts +10 -5
- package/dist/runner/bridge.js +101 -45
- package/dist/runner/reporter.d.ts +32 -2
- package/dist/runner/reporter.js +427 -13
- package/dist/server/demo.js +43 -44
- package/dist/types/generator.d.ts +1 -17
- package/dist/types/generator.js +1 -443
- package/dist/utils/__tests__/api-server.test.d.ts +1 -0
- package/dist/utils/__tests__/api-server.test.js +62 -0
- package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
- package/dist/utils/__tests__/demo-server.test.js +35 -0
- package/dist/utils/api-server.d.ts +19 -0
- package/dist/utils/api-server.js +179 -0
- package/dist/utils/demo-server.d.ts +5 -0
- package/dist/utils/demo-server.js +46 -36
- package/package.json +25 -13
- package/templates/default/CLAUDE.md +12 -5
- package/templates/default/fixtures/apiData.json +24 -0
- package/templates/default/package.json +12 -3
- package/templates/default/page-objects/AuthPage/mobile.ts +5 -0
- package/templates/default/page-objects/AuthPage/web.ts +10 -0
- package/templates/default/page-objects/MatchersPage/mobile.ts +66 -17
- package/templates/default/page-objects/MatchersPage/web.ts +22 -2
- package/templates/default/page-objects/NavigationPage/mobile.ts +11 -2
- package/templates/default/page-objects/NavigationPage/web.ts +1 -1
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +34 -18
- package/templates/default/page-objects/PlaygroundPage/web.ts +4 -2
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +30 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
- package/templates/default/specs/ApiInterception/suite.md +10 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
- package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
- package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
- package/templates/default/specs/ApiSeeding/suite.md +9 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/mobile.test.ts +7 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/spec.md +19 -0
- package/templates/default/specs/Authentication/TC-0021-seed-authenticated-session/web.test.ts +9 -0
- package/templates/default/specs/Authentication/suite.md +9 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +5 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/mobile.test.ts +17 -0
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/spec.md +28 -0
- package/templates/default/specs/BrowserContext/TC-0022-history-navigation/web.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +5 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/common.test.ts +14 -0
- package/templates/default/specs/ContentMatchers/TC-0020-scoped-element-access/spec.md +21 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
- package/templates/default/specs/DeepLink/suite.md +9 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +5 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +7 -0
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/default/specs/ElementMatchers/suite.md +2 -1
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
- package/templates/default/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
- package/templates/default/specs/EnvironmentConfig/suite.md +9 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
- package/templates/default/specs/PermissionRationale/suite.md +9 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
- package/templates/default/spectra.config.ts +22 -31
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
- package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
- package/templates/default/support/actions/seedSession/mobile.action.ts +9 -0
- package/templates/default/support/actions/seedSession/web.action.ts +13 -0
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -1
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +3 -3
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -2
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +4 -4
- package/templates/default/tsconfig.json +3 -1
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
- package/templates/nx/.nx/workspace-data/d/daemon.log +5366 -0
- package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
- package/templates/nx/.nx/workspace-data/file-map.json +464 -0
- package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
- package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
- package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
- package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
- package/templates/nx/CLAUDE.md +2 -2
- package/templates/nx/package.json +6 -4
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +15 -5
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/common.test.ts +5 -0
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/TC-0019-read-custom-environment-variable/spec.md +16 -0
- package/templates/nx/packages/matchers/e2e/specs/EnvironmentConfig/suite.md +9 -0
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
- package/templates/nx/pnpm-lock.yaml +6663 -0
- package/templates/nx/pnpm-workspace.yaml +2 -1
- package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +13 -1
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +85 -17
- package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +4 -2
- package/templates/nx/shared/testing/project.json +19 -0
- package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
- package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +3 -3
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +2 -2
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +4 -4
- package/templates/nx/spectra.config.ts +20 -29
- package/templates/nx/tsconfig.json +3 -1
- package/templates/react-component/.cursorrules +17 -0
- package/templates/react-component/.vscode/extensions.json +5 -0
- package/templates/react-component/CLAUDE.md +14 -0
- package/templates/react-component/README.md +69 -0
- package/templates/react-component/fixtures/component-mock.json +12 -0
- package/templates/react-component/global-hooks/after/web.hook.ts +3 -0
- package/templates/react-component/global-hooks/before/web.hook.ts +3 -0
- package/templates/react-component/package.json +27 -0
- package/templates/react-component/page-objects/BadgeComponent/web.ts +23 -0
- package/templates/react-component/page-objects/ButtonComponent/web.ts +23 -0
- package/templates/react-component/page-objects/InputComponent/web.ts +13 -0
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0002-badge-severities-and-dot/web.test.tsx +11 -0
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0006-badge-success-with-dot/web.test.tsx +10 -0
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/spec.md +18 -0
- package/templates/react-component/specs/BadgeComponent/TC-0007-badge-by-severity-lookup/web.test.tsx +16 -0
- package/templates/react-component/specs/BadgeComponent/suite.md +12 -0
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/spec.md +19 -0
- package/templates/react-component/specs/ButtonComponent/TC-0001-button-rendering-and-click/web.test.tsx +28 -0
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/spec.md +19 -0
- package/templates/react-component/specs/ButtonComponent/TC-0004-disabled-button-not-clickable/web.test.tsx +22 -0
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/spec.md +18 -0
- package/templates/react-component/specs/ButtonComponent/TC-0005-button-renders-icon/web.test.tsx +8 -0
- package/templates/react-component/specs/ButtonComponent/suite.md +12 -0
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/spec.md +20 -0
- package/templates/react-component/specs/InputComponent/TC-0003-input-typing-and-clear/web.test.tsx +28 -0
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/spec.md +18 -0
- package/templates/react-component/specs/InputComponent/TC-0008-disabled-input-not-editable/web.test.tsx +9 -0
- package/templates/react-component/specs/InputComponent/suite.md +12 -0
- package/templates/react-component/spectra.config.ts +27 -0
- package/templates/react-component/src/components/Badge/Badge.tsx +60 -0
- package/templates/react-component/src/components/Button/Button.tsx +57 -0
- package/templates/react-component/src/components/Input/Input.tsx +41 -0
- package/templates/react-component/src/test-utils.tsx +23 -0
- package/templates/react-component/support/actions/fillInputField/web.action.ts +9 -0
- package/templates/react-component/support/steps/verifyButtonState/web.step.ts +12 -0
- package/templates/react-component/tsconfig.json +8 -0
- package/demo-app/assets/index-DAUlrHTr.css +0 -1
- package/demo-app/assets/index-DN2Kb0A8.js +0 -100
- package/dist/index.cjs +0 -101
- package/dist/plugin.cjs +0 -575
- package/dist/plugin.d.ts +0 -16
- package/dist/plugin.js +0 -184
- package/dist/step/index.d.ts +0 -6
- package/dist/step/index.js +0 -6
- package/dist/step/matchers.d.ts +0 -14
- package/dist/step/matchers.js +0 -114
- package/dist/step/proto.d.ts +0 -46
- package/dist/step/proto.js +0 -114
- package/dist/step/runner/collection.d.ts +0 -13
- package/dist/step/runner/collection.js +0 -51
- package/dist/step/runner/single.d.ts +0 -21
- package/dist/step/runner/single.js +0 -138
- package/dist/step/spectra.d.ts +0 -34
- package/dist/step/spectra.js +0 -156
- package/dist/step/types.d.ts +0 -32
- package/dist/step/types.js +0 -5
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
- package/templates/default/tsconfig.spectra.android.json +0 -46
- package/templates/default/tsconfig.spectra.ios.json +0 -46
- package/templates/default/tsconfig.spectra.json +0 -22
- package/templates/default/tsconfig.spectra.web.json +0 -53
- package/templates/nx/fixtures/playgroundData.json +0 -7
- package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/after/web.hook.ts +0 -3
- package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/before/web.hook.ts +0 -3
- package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
- package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
- package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
- package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
- package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
- package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
- package/templates/nx/specs/BrowserContext/suite.md +0 -9
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
- package/templates/nx/specs/ContentMatchers/suite.md +0 -9
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
- package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/suite.md +0 -9
- package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
- package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/suite.md +0 -9
- package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
- package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
- package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
- package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
- package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
- package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
- package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
- package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
- package/templates/nx/tsconfig.spectra.android.json +0 -39
- package/templates/nx/tsconfig.spectra.ios.json +0 -39
- package/templates/nx/tsconfig.spectra.json +0 -25
- package/templates/nx/tsconfig.spectra.shared.json +0 -25
- package/templates/nx/tsconfig.spectra.web.json +0 -45
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext ā default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers ā packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
package/dist/commands/init.js
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import { fileURLToPath } from
|
|
4
|
-
import * as p from
|
|
5
|
-
import chalk from
|
|
6
|
-
import { ConfigLoader } from
|
|
7
|
-
import { TypeGenerator } from
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import * as p from '@clack/prompts';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { ConfigLoader } from '../config/loader.js';
|
|
7
|
+
import { TypeGenerator } from '../types/generator.js';
|
|
8
|
+
import { TsConfigGenerator } from '../generator/tsconfig-generator.js';
|
|
8
9
|
function parsePnpmWorkspaceGlobs(content) {
|
|
9
|
-
const lines = content.split(
|
|
10
|
+
const lines = content.split('\n');
|
|
10
11
|
const globs = [];
|
|
11
12
|
let inPackages = false;
|
|
12
13
|
for (const line of lines) {
|
|
13
14
|
const trimmed = line.trim();
|
|
14
|
-
if (trimmed.startsWith(
|
|
15
|
+
if (trimmed.startsWith('packages:')) {
|
|
15
16
|
inPackages = true;
|
|
16
17
|
continue;
|
|
17
18
|
}
|
|
18
19
|
if (inPackages) {
|
|
19
|
-
if (trimmed.startsWith(
|
|
20
|
-
const item = trimmed.replace(/^-\s*['"]?/,
|
|
20
|
+
if (trimmed.startsWith('-')) {
|
|
21
|
+
const item = trimmed.replace(/^-\s*['"]?/, '').replace(/['"]?\s*$/, '');
|
|
21
22
|
if (item)
|
|
22
23
|
globs.push(item);
|
|
23
24
|
}
|
|
24
|
-
else if (trimmed && !trimmed.startsWith(
|
|
25
|
+
else if (trimmed && !trimmed.startsWith('#')) {
|
|
25
26
|
break;
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -29,27 +30,28 @@ function parsePnpmWorkspaceGlobs(content) {
|
|
|
29
30
|
return globs;
|
|
30
31
|
}
|
|
31
32
|
function ensurePnpmWorkspaceIncludes(pnpmWorkspacePath, targetPath) {
|
|
32
|
-
const normalizedTarget = targetPath.replace(/\\/g,
|
|
33
|
+
const normalizedTarget = targetPath.replace(/\\/g, '/');
|
|
33
34
|
if (!fs.existsSync(pnpmWorkspacePath)) {
|
|
34
35
|
const newContent = `packages:\n - '${normalizedTarget}'\n`;
|
|
35
|
-
fs.writeFileSync(pnpmWorkspacePath, newContent,
|
|
36
|
+
fs.writeFileSync(pnpmWorkspacePath, newContent, 'utf-8');
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
|
-
const content = fs.readFileSync(pnpmWorkspacePath,
|
|
39
|
+
const content = fs.readFileSync(pnpmWorkspacePath, 'utf-8');
|
|
39
40
|
const currentGlobs = parsePnpmWorkspaceGlobs(content);
|
|
40
41
|
// Check if target is already covered by an existing glob or exact path
|
|
41
42
|
const isAlreadyCovered = currentGlobs.some((glob) => {
|
|
42
|
-
const normGlob = glob.replace(/\\/g,
|
|
43
|
+
const normGlob = glob.replace(/\\/g, '/');
|
|
43
44
|
if (normGlob === normalizedTarget)
|
|
44
45
|
return true;
|
|
45
46
|
if (normGlob === `${normalizedTarget}/*` || normGlob === `${normalizedTarget}/**`)
|
|
46
47
|
return true;
|
|
47
|
-
if (normGlob.endsWith(
|
|
48
|
+
if (normGlob.endsWith('/*')) {
|
|
48
49
|
const parent = normGlob.slice(0, -2);
|
|
49
|
-
if (normalizedTarget.startsWith(`${parent}/`) &&
|
|
50
|
+
if (normalizedTarget.startsWith(`${parent}/`) &&
|
|
51
|
+
normalizedTarget.split('/').length === parent.split('/').length + 1)
|
|
50
52
|
return true;
|
|
51
53
|
}
|
|
52
|
-
if (normGlob.endsWith(
|
|
54
|
+
if (normGlob.endsWith('/**')) {
|
|
53
55
|
const parent = normGlob.slice(0, -3);
|
|
54
56
|
if (normalizedTarget.startsWith(`${parent}/`))
|
|
55
57
|
return true;
|
|
@@ -58,42 +60,42 @@ function ensurePnpmWorkspaceIncludes(pnpmWorkspacePath, targetPath) {
|
|
|
58
60
|
});
|
|
59
61
|
if (!isAlreadyCovered) {
|
|
60
62
|
// Insert new package pattern right under packages:
|
|
61
|
-
const lines = content.split(
|
|
63
|
+
const lines = content.split('\n');
|
|
62
64
|
let insertIndex = -1;
|
|
63
65
|
for (let i = 0; i < lines.length; i++) {
|
|
64
|
-
if (lines[i].trim().startsWith(
|
|
66
|
+
if (lines[i].trim().startsWith('packages:')) {
|
|
65
67
|
insertIndex = i + 1;
|
|
66
68
|
break;
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
if (insertIndex !== -1) {
|
|
70
72
|
lines.splice(insertIndex, 0, ` - '${normalizedTarget}'`);
|
|
71
|
-
fs.writeFileSync(pnpmWorkspacePath, lines.join(
|
|
73
|
+
fs.writeFileSync(pnpmWorkspacePath, lines.join('\n'), 'utf-8');
|
|
72
74
|
}
|
|
73
75
|
else {
|
|
74
76
|
const newContent = `packages:\n - '${normalizedTarget}'\n` + content;
|
|
75
|
-
fs.writeFileSync(pnpmWorkspacePath, newContent,
|
|
77
|
+
fs.writeFileSync(pnpmWorkspacePath, newContent, 'utf-8');
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
function ensureVsCodeExtensions(cwd) {
|
|
80
|
-
const vscodeDir = path.join(cwd,
|
|
81
|
-
const extFile = path.join(vscodeDir,
|
|
82
|
+
const vscodeDir = path.join(cwd, '.vscode');
|
|
83
|
+
const extFile = path.join(vscodeDir, 'extensions.json');
|
|
82
84
|
if (!fs.existsSync(vscodeDir))
|
|
83
85
|
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
84
86
|
let config = { recommendations: [] };
|
|
85
87
|
if (fs.existsSync(extFile)) {
|
|
86
88
|
try {
|
|
87
|
-
config = JSON.parse(fs.readFileSync(extFile,
|
|
89
|
+
config = JSON.parse(fs.readFileSync(extFile, 'utf-8'));
|
|
88
90
|
}
|
|
89
91
|
catch { }
|
|
90
92
|
}
|
|
91
93
|
if (!Array.isArray(config.recommendations))
|
|
92
94
|
config.recommendations = [];
|
|
93
|
-
if (!config.recommendations.includes(
|
|
94
|
-
config.recommendations.push(
|
|
95
|
+
if (!config.recommendations.includes('testspectra.testspectra-vscode')) {
|
|
96
|
+
config.recommendations.push('testspectra.testspectra-vscode');
|
|
95
97
|
}
|
|
96
|
-
fs.writeFileSync(extFile, JSON.stringify(config, null, 2) +
|
|
98
|
+
fs.writeFileSync(extFile, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
97
99
|
}
|
|
98
100
|
function scanDirectoriesForProjects(cwd, patterns) {
|
|
99
101
|
const foundProjects = [];
|
|
@@ -102,11 +104,11 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
102
104
|
if (currentDepth > maxDepth || !fs.existsSync(dir))
|
|
103
105
|
return;
|
|
104
106
|
const base = path.basename(dir);
|
|
105
|
-
if (base ===
|
|
107
|
+
if (base === 'node_modules' || base === 'dist' || base === '.testspectra' || base === '.git' || base === 'shared') {
|
|
106
108
|
return;
|
|
107
109
|
}
|
|
108
|
-
const hasPkg = fs.existsSync(path.join(dir,
|
|
109
|
-
const hasNx = fs.existsSync(path.join(dir,
|
|
110
|
+
const hasPkg = fs.existsSync(path.join(dir, 'package.json'));
|
|
111
|
+
const hasNx = fs.existsSync(path.join(dir, 'project.json'));
|
|
110
112
|
if ((hasPkg || hasNx) && dir !== cwd) {
|
|
111
113
|
const rel = path.relative(cwd, dir);
|
|
112
114
|
if (!visited.has(rel)) {
|
|
@@ -114,7 +116,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
114
116
|
let name = path.basename(dir);
|
|
115
117
|
if (hasPkg) {
|
|
116
118
|
try {
|
|
117
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(dir,
|
|
119
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
118
120
|
if (pkg.name)
|
|
119
121
|
name = pkg.name;
|
|
120
122
|
}
|
|
@@ -122,7 +124,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
122
124
|
}
|
|
123
125
|
else if (hasNx) {
|
|
124
126
|
try {
|
|
125
|
-
const nxJson = JSON.parse(fs.readFileSync(path.join(dir,
|
|
127
|
+
const nxJson = JSON.parse(fs.readFileSync(path.join(dir, 'project.json'), 'utf-8'));
|
|
126
128
|
if (nxJson.name)
|
|
127
129
|
name = nxJson.name;
|
|
128
130
|
}
|
|
@@ -150,7 +152,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
150
152
|
// If patterns exist from pnpm-workspace.yaml, search those roots
|
|
151
153
|
if (patterns.length > 0) {
|
|
152
154
|
for (const pat of patterns) {
|
|
153
|
-
const cleanPat = pat.replace(/\/\*\*?$/,
|
|
155
|
+
const cleanPat = pat.replace(/\/\*\*?$/, '').replace(/\/\*$/, '');
|
|
154
156
|
const searchRoot = path.join(cwd, cleanPat);
|
|
155
157
|
if (fs.existsSync(searchRoot)) {
|
|
156
158
|
const stat = fs.statSync(searchRoot);
|
|
@@ -167,7 +169,7 @@ function scanDirectoriesForProjects(cwd, patterns) {
|
|
|
167
169
|
}
|
|
168
170
|
// Fallback search in standard directories if nothing found
|
|
169
171
|
if (foundProjects.length === 0) {
|
|
170
|
-
for (const fallback of [
|
|
172
|
+
for (const fallback of ['modules', 'packages', 'apps', 'libs', 'features']) {
|
|
171
173
|
const fbPath = path.join(cwd, fallback);
|
|
172
174
|
if (fs.existsSync(fbPath)) {
|
|
173
175
|
search(fbPath, 1, 3);
|
|
@@ -183,16 +185,16 @@ export async function initCommand(options = {}) {
|
|
|
183
185
|
p.log.warn(chalk.yellow(`Config already exists at ${path.basename(existingConfig)}. Use --force to overwrite.`));
|
|
184
186
|
return;
|
|
185
187
|
}
|
|
186
|
-
p.intro(chalk.bold.cyan(
|
|
188
|
+
p.intro(chalk.bold.cyan('⨠TestSpectra Enterprise Scaffolder'));
|
|
187
189
|
// Detect monorepo environment
|
|
188
190
|
let isMonorepo = false;
|
|
189
191
|
let pnpmPatterns = [];
|
|
190
|
-
const pnpmWorkspacePath = path.join(cwd,
|
|
191
|
-
const nxJsonPath = path.join(cwd,
|
|
192
|
+
const pnpmWorkspacePath = path.join(cwd, 'pnpm-workspace.yaml');
|
|
193
|
+
const nxJsonPath = path.join(cwd, 'nx.json');
|
|
192
194
|
if (fs.existsSync(pnpmWorkspacePath)) {
|
|
193
195
|
isMonorepo = true;
|
|
194
196
|
try {
|
|
195
|
-
const content = fs.readFileSync(pnpmWorkspacePath,
|
|
197
|
+
const content = fs.readFileSync(pnpmWorkspacePath, 'utf-8');
|
|
196
198
|
pnpmPatterns = parsePnpmWorkspaceGlobs(content);
|
|
197
199
|
}
|
|
198
200
|
catch { }
|
|
@@ -204,28 +206,33 @@ export async function initCommand(options = {}) {
|
|
|
204
206
|
let selectedTemplate = options.template;
|
|
205
207
|
if (!selectedTemplate && !options.force) {
|
|
206
208
|
const templateChoice = await p.select({
|
|
207
|
-
message:
|
|
208
|
-
initialValue: isMonorepo ?
|
|
209
|
+
message: 'Select TestSpectra setup mode:',
|
|
210
|
+
initialValue: isMonorepo ? 'nx' : 'default',
|
|
209
211
|
options: [
|
|
210
212
|
{
|
|
211
|
-
label:
|
|
212
|
-
value:
|
|
213
|
-
hint:
|
|
213
|
+
label: 'Nx Monorepo (Recommended)',
|
|
214
|
+
value: 'nx',
|
|
215
|
+
hint: 'Interactive workspace feature discovery & centralized config',
|
|
214
216
|
},
|
|
215
217
|
{
|
|
216
|
-
label:
|
|
217
|
-
value:
|
|
218
|
-
hint:
|
|
218
|
+
label: 'Standard E2E Project',
|
|
219
|
+
value: 'default',
|
|
220
|
+
hint: 'Standalone single-suite E2E testing project',
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
label: 'React Component Testing',
|
|
224
|
+
value: 'react-component',
|
|
225
|
+
hint: 'Real-browser React component testing with Vite & Testing Library',
|
|
219
226
|
},
|
|
220
227
|
],
|
|
221
228
|
});
|
|
222
229
|
if (p.isCancel(templateChoice)) {
|
|
223
|
-
p.cancel(
|
|
230
|
+
p.cancel('Setup cancelled.');
|
|
224
231
|
return;
|
|
225
232
|
}
|
|
226
233
|
selectedTemplate = templateChoice;
|
|
227
234
|
}
|
|
228
|
-
const templateName = selectedTemplate || (isMonorepo ?
|
|
235
|
+
const templateName = selectedTemplate || (isMonorepo ? 'nx' : 'default');
|
|
229
236
|
// 1. Resolve template directory
|
|
230
237
|
const __filename = fileURLToPath(import.meta.url);
|
|
231
238
|
const __dirname = path.dirname(__filename);
|
|
@@ -241,14 +248,14 @@ export async function initCommand(options = {}) {
|
|
|
241
248
|
return;
|
|
242
249
|
}
|
|
243
250
|
// 2. Resolve CLI dependency version
|
|
244
|
-
let cliVersion =
|
|
251
|
+
let cliVersion = '^1.0.65';
|
|
245
252
|
try {
|
|
246
253
|
let pDir = __dirname;
|
|
247
254
|
while (pDir !== path.dirname(pDir)) {
|
|
248
|
-
const cand = path.join(pDir,
|
|
255
|
+
const cand = path.join(pDir, 'package.json');
|
|
249
256
|
if (fs.existsSync(cand)) {
|
|
250
|
-
const cliPkg = JSON.parse(fs.readFileSync(cand,
|
|
251
|
-
if (cliPkg.name ===
|
|
257
|
+
const cliPkg = JSON.parse(fs.readFileSync(cand, 'utf-8'));
|
|
258
|
+
if (cliPkg.name === '@testspectra/cli' && cliPkg.version) {
|
|
252
259
|
cliVersion = `^${cliPkg.version}`;
|
|
253
260
|
break;
|
|
254
261
|
}
|
|
@@ -258,11 +265,11 @@ export async function initCommand(options = {}) {
|
|
|
258
265
|
}
|
|
259
266
|
catch { }
|
|
260
267
|
const projectName = path.basename(cwd);
|
|
261
|
-
const cliDepVersion = cliVersion.startsWith(
|
|
268
|
+
const cliDepVersion = cliVersion.startsWith('^') ? cliVersion : `^${cliVersion}`;
|
|
262
269
|
// Helper: Copy directory contents recursively
|
|
263
270
|
function copyRecursive(src, dest, replacements = {}) {
|
|
264
271
|
const base = path.basename(src);
|
|
265
|
-
if (base ===
|
|
272
|
+
if (base === 'node_modules' || base === 'dist' || base === '.testspectra' || base === '.git') {
|
|
266
273
|
return;
|
|
267
274
|
}
|
|
268
275
|
const stat = fs.statSync(src);
|
|
@@ -279,26 +286,30 @@ export async function initCommand(options = {}) {
|
|
|
279
286
|
if (fs.existsSync(dest) && !options.force) {
|
|
280
287
|
return;
|
|
281
288
|
}
|
|
282
|
-
let content = fs.readFileSync(src,
|
|
289
|
+
let content = fs.readFileSync(src, 'utf-8');
|
|
283
290
|
for (const [k, v] of Object.entries(replacements)) {
|
|
284
291
|
content = content.replaceAll(k, v);
|
|
285
292
|
}
|
|
286
|
-
if (dest.endsWith(
|
|
293
|
+
if (dest.endsWith('package.json')) {
|
|
287
294
|
try {
|
|
288
295
|
const pkg = JSON.parse(content);
|
|
289
|
-
if (!pkg.name || pkg.name.startsWith(
|
|
296
|
+
if (!pkg.name || pkg.name.startsWith('@testspectra/template-') || pkg.name === 'spectra-monorepo') {
|
|
290
297
|
pkg.name = projectName;
|
|
291
298
|
}
|
|
292
299
|
if (pkg.devDependencies) {
|
|
293
300
|
for (const key of Object.keys(pkg.devDependencies)) {
|
|
294
|
-
if (key.startsWith(
|
|
301
|
+
if (key.startsWith('@testspectra/') ||
|
|
302
|
+
pkg.devDependencies[key] === 'workspace:*' ||
|
|
303
|
+
(typeof pkg.devDependencies[key] === 'string' && pkg.devDependencies[key].startsWith('link:'))) {
|
|
295
304
|
pkg.devDependencies[key] = cliDepVersion;
|
|
296
305
|
}
|
|
297
306
|
}
|
|
298
307
|
}
|
|
299
308
|
if (pkg.dependencies) {
|
|
300
309
|
for (const key of Object.keys(pkg.dependencies)) {
|
|
301
|
-
if (key.startsWith(
|
|
310
|
+
if (key.startsWith('@testspectra/') ||
|
|
311
|
+
pkg.dependencies[key] === 'workspace:*' ||
|
|
312
|
+
(typeof pkg.dependencies[key] === 'string' && pkg.dependencies[key].startsWith('link:'))) {
|
|
302
313
|
pkg.dependencies[key] = cliDepVersion;
|
|
303
314
|
}
|
|
304
315
|
}
|
|
@@ -307,11 +318,11 @@ export async function initCommand(options = {}) {
|
|
|
307
318
|
}
|
|
308
319
|
catch { }
|
|
309
320
|
}
|
|
310
|
-
fs.writeFileSync(dest, content,
|
|
321
|
+
fs.writeFileSync(dest, content, 'utf-8');
|
|
311
322
|
}
|
|
312
323
|
}
|
|
313
324
|
// --- MONOREPO INTERACTIVE FLOW ---
|
|
314
|
-
if (templateName ===
|
|
325
|
+
if (templateName === 'nx') {
|
|
315
326
|
let chosenProjects = [];
|
|
316
327
|
if (options.force) {
|
|
317
328
|
chosenProjects = detectedProjects.map((p) => p.relPath);
|
|
@@ -319,86 +330,81 @@ export async function initCommand(options = {}) {
|
|
|
319
330
|
else if (detectedProjects.length > 0) {
|
|
320
331
|
p.log.step(chalk.cyan(`Monorepo Auto-Discovery: Found ${detectedProjects.length} workspace feature package(s)`));
|
|
321
332
|
const projectChoices = await p.multiselect({
|
|
322
|
-
message:
|
|
333
|
+
message: 'Select workspace features to initialize with TestSpectra:',
|
|
323
334
|
options: detectedProjects.map((proj) => ({
|
|
324
335
|
value: proj.relPath,
|
|
325
336
|
label: `${proj.relPath} (${proj.name})`,
|
|
326
|
-
hint: proj.hasNxProject ?
|
|
337
|
+
hint: proj.hasNxProject ? 'Nx project.json' : 'package.json',
|
|
327
338
|
})),
|
|
328
339
|
initialValues: detectedProjects.map((p) => p.relPath),
|
|
329
340
|
required: true,
|
|
330
341
|
});
|
|
331
342
|
if (p.isCancel(projectChoices)) {
|
|
332
|
-
p.cancel(
|
|
343
|
+
p.cancel('Setup cancelled.');
|
|
333
344
|
return;
|
|
334
345
|
}
|
|
335
346
|
chosenProjects = projectChoices;
|
|
336
347
|
}
|
|
337
348
|
else {
|
|
338
349
|
// No packages auto-detected ā ask user to manually enter feature names
|
|
339
|
-
p.log.warn(chalk.yellow(
|
|
350
|
+
p.log.warn(chalk.yellow('No feature packages found in workspace. You can enter feature names to scaffold.'));
|
|
340
351
|
const featureNamesInput = await p.text({
|
|
341
|
-
message:
|
|
342
|
-
placeholder:
|
|
343
|
-
defaultValue:
|
|
352
|
+
message: 'Enter feature module names (comma-separated), or leave blank to skip:',
|
|
353
|
+
placeholder: 'playground, matchers, navigation',
|
|
354
|
+
defaultValue: '',
|
|
344
355
|
});
|
|
345
356
|
if (p.isCancel(featureNamesInput)) {
|
|
346
|
-
p.cancel(
|
|
357
|
+
p.cancel('Setup cancelled.');
|
|
347
358
|
return;
|
|
348
359
|
}
|
|
349
360
|
const raw = featureNamesInput.trim();
|
|
350
361
|
if (raw) {
|
|
351
362
|
// Build relPaths from feature names using a sensible default location
|
|
352
363
|
const featureBaseInput = await p.text({
|
|
353
|
-
message:
|
|
354
|
-
placeholder:
|
|
355
|
-
defaultValue:
|
|
364
|
+
message: 'Enter base directory for feature modules:',
|
|
365
|
+
placeholder: 'packages',
|
|
366
|
+
defaultValue: 'packages',
|
|
356
367
|
});
|
|
357
368
|
if (p.isCancel(featureBaseInput)) {
|
|
358
|
-
p.cancel(
|
|
369
|
+
p.cancel('Setup cancelled.');
|
|
359
370
|
return;
|
|
360
371
|
}
|
|
361
|
-
const featureBase = featureBaseInput.trim() ||
|
|
362
|
-
chosenProjects = raw
|
|
372
|
+
const featureBase = featureBaseInput.trim() || 'packages';
|
|
373
|
+
chosenProjects = raw
|
|
374
|
+
.split(',')
|
|
375
|
+
.map((n) => `${featureBase}/${n.trim()}`)
|
|
376
|
+
.filter(Boolean);
|
|
363
377
|
}
|
|
364
378
|
}
|
|
365
|
-
let e2eFolderName =
|
|
366
|
-
let sharedTestingRelPath =
|
|
379
|
+
let e2eFolderName = 'e2e';
|
|
380
|
+
let sharedTestingRelPath = 'shared/testing';
|
|
367
381
|
if (!options.force) {
|
|
368
382
|
const e2eFolderNameInput = await p.text({
|
|
369
|
-
message:
|
|
370
|
-
placeholder:
|
|
371
|
-
defaultValue:
|
|
383
|
+
message: 'Enter E2E test folder name for each selected feature:',
|
|
384
|
+
placeholder: 'e2e',
|
|
385
|
+
defaultValue: 'e2e',
|
|
372
386
|
});
|
|
373
387
|
if (p.isCancel(e2eFolderNameInput)) {
|
|
374
|
-
p.cancel(
|
|
388
|
+
p.cancel('Setup cancelled.');
|
|
375
389
|
return;
|
|
376
390
|
}
|
|
377
391
|
const sharedTestingPathInput = await p.text({
|
|
378
|
-
message:
|
|
379
|
-
placeholder:
|
|
380
|
-
defaultValue:
|
|
392
|
+
message: 'Enter path for Shared Testing library (POMs, Steps, Actions, Fixtures):',
|
|
393
|
+
placeholder: 'shared/testing',
|
|
394
|
+
defaultValue: 'shared/testing',
|
|
381
395
|
});
|
|
382
396
|
if (p.isCancel(sharedTestingPathInput)) {
|
|
383
|
-
p.cancel(
|
|
397
|
+
p.cancel('Setup cancelled.');
|
|
384
398
|
return;
|
|
385
399
|
}
|
|
386
|
-
e2eFolderName = e2eFolderNameInput.trim() ||
|
|
387
|
-
sharedTestingRelPath = sharedTestingPathInput.trim() ||
|
|
400
|
+
e2eFolderName = e2eFolderNameInput.trim() || 'e2e';
|
|
401
|
+
sharedTestingRelPath = sharedTestingPathInput.trim() || 'shared/testing';
|
|
388
402
|
}
|
|
389
403
|
const sharedTestingAbsPath = path.join(cwd, sharedTestingRelPath);
|
|
390
404
|
const s = p.spinner();
|
|
391
|
-
s.start(
|
|
405
|
+
s.start('Scaffolding distributed monorepo structure and ambient types...');
|
|
392
406
|
// 1. Centralized Root Configs & Orchestration
|
|
393
|
-
const rootConfigFiles = [
|
|
394
|
-
"spectra.config.ts",
|
|
395
|
-
"tsconfig.spectra.json",
|
|
396
|
-
"tsconfig.spectra.shared.json",
|
|
397
|
-
"tsconfig.spectra.web.json",
|
|
398
|
-
"tsconfig.spectra.android.json",
|
|
399
|
-
"tsconfig.spectra.ios.json",
|
|
400
|
-
"nx.json",
|
|
401
|
-
];
|
|
407
|
+
const rootConfigFiles = ['spectra.config.ts', 'nx.json'];
|
|
402
408
|
for (const cf of rootConfigFiles) {
|
|
403
409
|
const src = path.join(templateDir, cf);
|
|
404
410
|
const dest = path.join(cwd, cf);
|
|
@@ -406,31 +412,33 @@ export async function initCommand(options = {}) {
|
|
|
406
412
|
fs.copyFileSync(src, dest);
|
|
407
413
|
}
|
|
408
414
|
}
|
|
415
|
+
// Write tsconfig.json and auto-generate .testspectra/tsconfig.json and sub-configs
|
|
416
|
+
TsConfigGenerator.scaffoldConfigs(cwd, options.force);
|
|
409
417
|
// Ensure root package.json has TestSpectra devDependencies
|
|
410
|
-
const rootPkgPath = path.join(cwd,
|
|
418
|
+
const rootPkgPath = path.join(cwd, 'package.json');
|
|
411
419
|
if (!fs.existsSync(rootPkgPath)) {
|
|
412
|
-
const templatePkgSrc = path.join(templateDir,
|
|
420
|
+
const templatePkgSrc = path.join(templateDir, 'package.json');
|
|
413
421
|
if (fs.existsSync(templatePkgSrc)) {
|
|
414
422
|
try {
|
|
415
|
-
const tPkg = JSON.parse(fs.readFileSync(templatePkgSrc,
|
|
416
|
-
if (!tPkg.name || tPkg.name.startsWith(
|
|
423
|
+
const tPkg = JSON.parse(fs.readFileSync(templatePkgSrc, 'utf-8'));
|
|
424
|
+
if (!tPkg.name || tPkg.name.startsWith('@testspectra/template-') || tPkg.name === 'spectra-monorepo') {
|
|
417
425
|
tPkg.name = projectName;
|
|
418
426
|
}
|
|
419
427
|
if (tPkg.devDependencies) {
|
|
420
428
|
for (const key of Object.keys(tPkg.devDependencies)) {
|
|
421
|
-
if (key.startsWith(
|
|
429
|
+
if (key.startsWith('@testspectra/') || tPkg.devDependencies[key] === 'workspace:*') {
|
|
422
430
|
tPkg.devDependencies[key] = cliDepVersion;
|
|
423
431
|
}
|
|
424
432
|
}
|
|
425
433
|
}
|
|
426
434
|
if (tPkg.dependencies) {
|
|
427
435
|
for (const key of Object.keys(tPkg.dependencies)) {
|
|
428
|
-
if (key.startsWith(
|
|
436
|
+
if (key.startsWith('@testspectra/') || tPkg.dependencies[key] === 'workspace:*') {
|
|
429
437
|
tPkg.dependencies[key] = cliDepVersion;
|
|
430
438
|
}
|
|
431
439
|
}
|
|
432
440
|
}
|
|
433
|
-
fs.writeFileSync(rootPkgPath, JSON.stringify(tPkg, null, 2),
|
|
441
|
+
fs.writeFileSync(rootPkgPath, JSON.stringify(tPkg, null, 2), 'utf-8');
|
|
434
442
|
}
|
|
435
443
|
catch {
|
|
436
444
|
fs.copyFileSync(templatePkgSrc, rootPkgPath);
|
|
@@ -439,107 +447,116 @@ export async function initCommand(options = {}) {
|
|
|
439
447
|
}
|
|
440
448
|
else {
|
|
441
449
|
try {
|
|
442
|
-
const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath,
|
|
450
|
+
const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf-8'));
|
|
443
451
|
if (!rootPkg.devDependencies)
|
|
444
452
|
rootPkg.devDependencies = {};
|
|
445
|
-
if (!rootPkg.devDependencies[
|
|
446
|
-
rootPkg.devDependencies[
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
rootPkg.devDependencies[
|
|
453
|
-
|
|
454
|
-
rootPkg.devDependencies[
|
|
455
|
-
if (!rootPkg.devDependencies[
|
|
456
|
-
rootPkg.devDependencies[
|
|
457
|
-
if (!rootPkg.devDependencies[
|
|
458
|
-
rootPkg.devDependencies[
|
|
459
|
-
if (!rootPkg.devDependencies[
|
|
460
|
-
rootPkg.devDependencies[
|
|
461
|
-
if (!rootPkg.devDependencies[
|
|
462
|
-
rootPkg.devDependencies[
|
|
463
|
-
if (!rootPkg.devDependencies[
|
|
464
|
-
rootPkg.devDependencies[
|
|
465
|
-
if (!rootPkg.devDependencies[
|
|
466
|
-
rootPkg.devDependencies[
|
|
467
|
-
|
|
453
|
+
if (!rootPkg.devDependencies['@testspectra/cli'] ||
|
|
454
|
+
rootPkg.devDependencies['@testspectra/cli'] === 'workspace:*' ||
|
|
455
|
+
(typeof rootPkg.devDependencies['@testspectra/cli'] === 'string' &&
|
|
456
|
+
rootPkg.devDependencies['@testspectra/cli'].startsWith('link:')))
|
|
457
|
+
rootPkg.devDependencies['@testspectra/cli'] = cliDepVersion;
|
|
458
|
+
if (!rootPkg.devDependencies['@testspectra/matchers'] ||
|
|
459
|
+
rootPkg.devDependencies['@testspectra/matchers'] === 'workspace:*' ||
|
|
460
|
+
(typeof rootPkg.devDependencies['@testspectra/matchers'] === 'string' &&
|
|
461
|
+
rootPkg.devDependencies['@testspectra/matchers'].startsWith('link:')))
|
|
462
|
+
rootPkg.devDependencies['@testspectra/matchers'] = cliDepVersion;
|
|
463
|
+
if (!rootPkg.devDependencies['typescript'])
|
|
464
|
+
rootPkg.devDependencies['typescript'] = '^5.4.5';
|
|
465
|
+
if (!rootPkg.devDependencies['@wdio/cli'])
|
|
466
|
+
rootPkg.devDependencies['@wdio/cli'] = '^9.2.8';
|
|
467
|
+
if (!rootPkg.devDependencies['@wdio/local-runner'])
|
|
468
|
+
rootPkg.devDependencies['@wdio/local-runner'] = '^9.2.8';
|
|
469
|
+
if (!rootPkg.devDependencies['@wdio/spec-reporter'])
|
|
470
|
+
rootPkg.devDependencies['@wdio/spec-reporter'] = '^9.2.8';
|
|
471
|
+
if (!rootPkg.devDependencies['@wdio/devtools-service'])
|
|
472
|
+
rootPkg.devDependencies['@wdio/devtools-service'] = '^8.46.0';
|
|
473
|
+
if (!rootPkg.devDependencies['@wdio/globals'])
|
|
474
|
+
rootPkg.devDependencies['@wdio/globals'] = '^9.2.8';
|
|
475
|
+
if (!rootPkg.devDependencies['@wdio/mocha-framework'])
|
|
476
|
+
rootPkg.devDependencies['@wdio/mocha-framework'] = '^9.2.8';
|
|
477
|
+
if (!rootPkg.devDependencies['@types/node'])
|
|
478
|
+
rootPkg.devDependencies['@types/node'] = '^20.14.0';
|
|
479
|
+
if (!rootPkg.devDependencies['webdriverio'])
|
|
480
|
+
rootPkg.devDependencies['webdriverio'] = '^9.2.8';
|
|
481
|
+
if (!rootPkg.scripts)
|
|
482
|
+
rootPkg.scripts = {};
|
|
483
|
+
if (!rootPkg.scripts['postinstall'])
|
|
484
|
+
rootPkg.scripts['postinstall'] = 'spectra sync-types';
|
|
485
|
+
if (!rootPkg.scripts['type-check'])
|
|
486
|
+
rootPkg.scripts['type-check'] = 'tsc -b';
|
|
487
|
+
fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2), 'utf-8');
|
|
468
488
|
}
|
|
469
489
|
catch { }
|
|
470
490
|
}
|
|
471
491
|
// 2. Centralized Root .testspectra folder for cache/logs
|
|
472
|
-
const rootTestDataDir = path.join(cwd,
|
|
492
|
+
const rootTestDataDir = path.join(cwd, '.testspectra');
|
|
473
493
|
if (!fs.existsSync(rootTestDataDir)) {
|
|
474
494
|
fs.mkdirSync(rootTestDataDir, { recursive: true });
|
|
475
495
|
}
|
|
476
496
|
// 3. Centralized Shared Testing Library
|
|
477
|
-
const sharedTestingSrc = path.join(templateDir,
|
|
497
|
+
const sharedTestingSrc = path.join(templateDir, 'shared/testing');
|
|
478
498
|
if (fs.existsSync(sharedTestingSrc)) {
|
|
479
499
|
copyRecursive(sharedTestingSrc, sharedTestingAbsPath);
|
|
480
500
|
}
|
|
481
501
|
// 4. Distribute E2E Folders to Selected Feature Modules
|
|
482
|
-
const sampleE2eSrc = path.join(templateDir,
|
|
502
|
+
const sampleE2eSrc = path.join(templateDir, 'packages/playground/e2e');
|
|
483
503
|
const featureE2eDirs = [];
|
|
484
504
|
for (const projRel of chosenProjects) {
|
|
485
505
|
const targetE2eDir = path.join(cwd, projRel, e2eFolderName);
|
|
486
506
|
const featureName = path.basename(projRel);
|
|
487
507
|
const e2eProjectName = `${featureName}-${e2eFolderName}`;
|
|
488
508
|
copyRecursive(sampleE2eSrc, targetE2eDir, {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
509
|
+
'playground-e2e': e2eProjectName,
|
|
510
|
+
'packages/playground/e2e': path.relative(cwd, targetE2eDir).replace(/\\/g, '/'),
|
|
511
|
+
'scope:playground': `scope:${featureName}`,
|
|
492
512
|
});
|
|
493
513
|
// Adjust project.json
|
|
494
|
-
const projJsonPath = path.join(targetE2eDir,
|
|
514
|
+
const projJsonPath = path.join(targetE2eDir, 'project.json');
|
|
495
515
|
if (fs.existsSync(projJsonPath)) {
|
|
496
516
|
try {
|
|
497
|
-
const pObj = JSON.parse(fs.readFileSync(projJsonPath,
|
|
517
|
+
const pObj = JSON.parse(fs.readFileSync(projJsonPath, 'utf-8'));
|
|
498
518
|
pObj.name = e2eProjectName;
|
|
499
|
-
pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g,
|
|
500
|
-
pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g,
|
|
501
|
-
pObj.targets[
|
|
502
|
-
pObj.tags = [`scope:${featureName}`,
|
|
503
|
-
fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2),
|
|
519
|
+
pObj.sourceRoot = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
|
|
520
|
+
pObj.targets.e2e.options.cwd = path.relative(cwd, targetE2eDir).replace(/\\/g, '/');
|
|
521
|
+
pObj.targets['type-check'].options.cwd = '.';
|
|
522
|
+
pObj.tags = [`scope:${featureName}`, 'type:e2e'];
|
|
523
|
+
fs.writeFileSync(projJsonPath, JSON.stringify(pObj, null, 2), 'utf-8');
|
|
504
524
|
}
|
|
505
525
|
catch { }
|
|
506
526
|
}
|
|
507
527
|
// Ensure no local spectra.config.ts (centralized in root)
|
|
508
|
-
const localConfig = path.join(targetE2eDir,
|
|
528
|
+
const localConfig = path.join(targetE2eDir, 'spectra.config.ts');
|
|
509
529
|
if (fs.existsSync(localConfig))
|
|
510
530
|
fs.unlinkSync(localConfig);
|
|
511
531
|
featureE2eDirs.push(targetE2eDir);
|
|
512
532
|
}
|
|
513
533
|
// 5. Ensure pnpm-workspace.yaml includes shared library (feature modules are already part of existing workspace globs)
|
|
514
|
-
const pnpmWorkspaceFilePath = path.join(cwd,
|
|
534
|
+
const pnpmWorkspaceFilePath = path.join(cwd, 'pnpm-workspace.yaml');
|
|
515
535
|
ensurePnpmWorkspaceIncludes(pnpmWorkspaceFilePath, sharedTestingRelPath);
|
|
516
|
-
// 6. Update Root Solution tsconfig.json to reference tsconfig.
|
|
517
|
-
const rootTsConfigPath = path.join(cwd,
|
|
536
|
+
// 6. Update Root Solution tsconfig.json to reference .testspectra/tsconfig.json
|
|
537
|
+
const rootTsConfigPath = path.join(cwd, 'tsconfig.json');
|
|
518
538
|
let rootTsConfig = { files: [], references: [] };
|
|
519
539
|
if (fs.existsSync(rootTsConfigPath)) {
|
|
520
540
|
try {
|
|
521
|
-
rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath,
|
|
541
|
+
rootTsConfig = JSON.parse(fs.readFileSync(rootTsConfigPath, 'utf-8'));
|
|
522
542
|
if (!rootTsConfig.references)
|
|
523
543
|
rootTsConfig.references = [];
|
|
524
|
-
|
|
525
|
-
|
|
544
|
+
rootTsConfig.references = rootTsConfig.references.filter((r) => r.path !== './tsconfig.spectra.json');
|
|
545
|
+
if (!rootTsConfig.references.some((r) => r.path === './.testspectra/tsconfig.json')) {
|
|
546
|
+
rootTsConfig.references.push({ path: './.testspectra/tsconfig.json' });
|
|
526
547
|
}
|
|
527
548
|
}
|
|
528
549
|
catch {
|
|
529
|
-
rootTsConfig = { files: [], references: [{ path:
|
|
550
|
+
rootTsConfig = { files: [], references: [{ path: './.testspectra/tsconfig.json' }] };
|
|
530
551
|
}
|
|
531
552
|
}
|
|
532
553
|
else {
|
|
533
|
-
rootTsConfig = { files: [], references: [{ path:
|
|
554
|
+
rootTsConfig = { files: [], references: [{ path: './.testspectra/tsconfig.json' }] };
|
|
534
555
|
}
|
|
535
|
-
fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2),
|
|
556
|
+
fs.writeFileSync(rootTsConfigPath, JSON.stringify(rootTsConfig, null, 2), 'utf-8');
|
|
536
557
|
// 7. Generate Ambient Types in ROOT only
|
|
537
558
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
538
559
|
ensureVsCodeExtensions(cwd);
|
|
539
|
-
const sharedPoLine = `${sharedTestingRelPath}/page-objects`.padEnd(28, " ");
|
|
540
|
-
const sharedStepsLine = `${sharedTestingRelPath}/steps`.padEnd(28, " ");
|
|
541
|
-
const sharedActLine = `${sharedTestingRelPath}/actions`.padEnd(28, " ");
|
|
542
|
-
const sharedFixLine = `${sharedTestingRelPath}/fixtures`.padEnd(28, " ");
|
|
543
560
|
const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
|
|
544
561
|
|
|
545
562
|
This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
|
|
@@ -550,14 +567,10 @@ This workspace uses TestSpectra's **"Centralized Configuration, Distributed Impl
|
|
|
550
567
|
|
|
551
568
|
### Centralized Root Elements
|
|
552
569
|
- \`./spectra.config.ts\`: Single source of truth for runtime configurations (Base URL, Appium devices, browser targets, timeouts).
|
|
553
|
-
- \`./.testspectra/\`: Local test project cache
|
|
554
|
-
- \`./tsconfig.spectra.json\`: Solution TypeScript entry point equipped with TestSpectra language service plugin.
|
|
555
|
-
- \`./tsconfig.spectra.web.json\`: Strongly-typed composite configuration for Web platform tests.
|
|
556
|
-
- \`./tsconfig.spectra.android.json\`: Strongly-typed composite configuration for Android platform tests.
|
|
557
|
-
- \`./tsconfig.spectra.ios.json\`: Strongly-typed composite configuration for iOS platform tests.
|
|
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/\`.
|
|
558
571
|
- \`./nx.json\`: Nx target defaults with execution caching and inputs.
|
|
559
572
|
- \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
|
|
560
|
-
- \`./tsconfig.json\`: Solution-style TypeScript orchestrator
|
|
573
|
+
- \`./tsconfig.json\`: Solution-style TypeScript orchestrator referencing \`./.testspectra/tsconfig.json\`.
|
|
561
574
|
|
|
562
575
|
### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
|
|
563
576
|
Provides cross-feature reusable test entities with **100% zero-import global resolution**:
|
|
@@ -567,7 +580,8 @@ Provides cross-feature reusable test entities with **100% zero-import global res
|
|
|
567
580
|
- \`fixtures/\`: Common test data and environment configurations (e.g. \`Fixture.appConfig\`).
|
|
568
581
|
|
|
569
582
|
### Distributed Feature E2E Modules (${featureE2eDirs.length} Features)
|
|
570
|
-
${featureE2eDirs
|
|
583
|
+
${featureE2eDirs
|
|
584
|
+
.map((d) => {
|
|
571
585
|
const rel = path.relative(cwd, d);
|
|
572
586
|
const name = path.basename(path.dirname(d));
|
|
573
587
|
const e2eName = `${name}-${e2eFolderName}`;
|
|
@@ -575,7 +589,8 @@ ${featureE2eDirs.map((d) => {
|
|
|
575
589
|
- \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
576
590
|
- \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
|
|
577
591
|
- \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
|
|
578
|
-
})
|
|
592
|
+
})
|
|
593
|
+
.join('\n')}
|
|
579
594
|
|
|
580
595
|
---
|
|
581
596
|
|
|
@@ -644,7 +659,7 @@ pnpm nx affected -t e2e
|
|
|
644
659
|
|
|
645
660
|
\`\`\`bash
|
|
646
661
|
# Navigate to the feature e2e folder
|
|
647
|
-
cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g,
|
|
662
|
+
cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/') : `modules/auth/${e2eFolderName}`}
|
|
648
663
|
|
|
649
664
|
# Run all specs in this feature
|
|
650
665
|
pnpm spectra run
|
|
@@ -672,12 +687,12 @@ pnpm type-check
|
|
|
672
687
|
# or: npx tsc -b
|
|
673
688
|
\`\`\`
|
|
674
689
|
`;
|
|
675
|
-
fs.writeFileSync(path.join(cwd,
|
|
690
|
+
fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), archDocContent, 'utf-8');
|
|
676
691
|
// 8. Ensure root .gitignore ignores .testspectra/ and *.tsbuildinfo
|
|
677
|
-
const rootGitignorePath = path.join(cwd,
|
|
678
|
-
const ignoreEntries = [
|
|
692
|
+
const rootGitignorePath = path.join(cwd, '.gitignore');
|
|
693
|
+
const ignoreEntries = ['.testspectra/', '*.tsbuildinfo', '.nx/cache'];
|
|
679
694
|
if (fs.existsSync(rootGitignorePath)) {
|
|
680
|
-
let giContent = fs.readFileSync(rootGitignorePath,
|
|
695
|
+
let giContent = fs.readFileSync(rootGitignorePath, 'utf-8');
|
|
681
696
|
let added = false;
|
|
682
697
|
for (const entry of ignoreEntries) {
|
|
683
698
|
if (!giContent.includes(entry)) {
|
|
@@ -686,42 +701,42 @@ pnpm type-check
|
|
|
686
701
|
}
|
|
687
702
|
}
|
|
688
703
|
if (added) {
|
|
689
|
-
fs.writeFileSync(rootGitignorePath, giContent.trim() +
|
|
704
|
+
fs.writeFileSync(rootGitignorePath, giContent.trim() + '\n', 'utf-8');
|
|
690
705
|
}
|
|
691
706
|
}
|
|
692
707
|
else {
|
|
693
|
-
fs.writeFileSync(rootGitignorePath, `node_modules/\ndist/\n${ignoreEntries.join(
|
|
708
|
+
fs.writeFileSync(rootGitignorePath, `node_modules/\ndist/\n${ignoreEntries.join('\n')}\n`, 'utf-8');
|
|
694
709
|
}
|
|
695
710
|
// 9. Safely append to root README.md (do not overwrite existing content)
|
|
696
|
-
const rootReadmePath = path.join(cwd,
|
|
711
|
+
const rootReadmePath = path.join(cwd, 'README.md');
|
|
697
712
|
const archSection = `\n## E2E Testing Architecture (TestSpectra)\n\nThis workspace uses TestSpectra's **Centralized Configuration, Distributed Implementation** architecture.\nDetailed architecture, folder map, and zero-import mechanics are documented in: \nš **[ARCHITECTURE.md](./ARCHITECTURE.md)**\n\n- **Run all E2E tests**: \`pnpm nx run-many -t e2e\`\n- **Run affected E2E tests**: \`pnpm nx affected -t e2e\`\n- **Type-check E2E suites**: \`pnpm type-check\`\n`;
|
|
698
713
|
if (fs.existsSync(rootReadmePath)) {
|
|
699
|
-
const existing = fs.readFileSync(rootReadmePath,
|
|
700
|
-
if (!existing.includes(
|
|
701
|
-
fs.appendFileSync(rootReadmePath, archSection,
|
|
714
|
+
const existing = fs.readFileSync(rootReadmePath, 'utf-8');
|
|
715
|
+
if (!existing.includes('ARCHITECTURE.md')) {
|
|
716
|
+
fs.appendFileSync(rootReadmePath, archSection, 'utf-8');
|
|
702
717
|
}
|
|
703
718
|
}
|
|
704
719
|
else {
|
|
705
720
|
const readmeContent = `# ${projectName}\n\nAutomated cross-platform E2E testing powered by [TestSpectra](https://github.com/testspectra).\n${archSection}`;
|
|
706
|
-
fs.writeFileSync(rootReadmePath, readmeContent,
|
|
721
|
+
fs.writeFileSync(rootReadmePath, readmeContent, 'utf-8');
|
|
707
722
|
}
|
|
708
|
-
s.stop(
|
|
709
|
-
p.log.success(chalk.green(
|
|
710
|
-
p.log.success(chalk.green(
|
|
723
|
+
s.stop('Monorepo scaffolding complete!');
|
|
724
|
+
p.log.success(chalk.green('Centralized Configuration: ./spectra.config.ts'));
|
|
725
|
+
p.log.success(chalk.green('Centralized App Data/Cache: ./.testspectra/'));
|
|
711
726
|
p.log.success(chalk.green(`Shared Testing Library: ./${sharedTestingRelPath}`));
|
|
712
|
-
p.log.success(chalk.green(
|
|
713
|
-
p.log.message(chalk.cyan(`Initialized Features (${featureE2eDirs.length}):\n${featureE2eDirs.map((d) => ` - ./${path.relative(cwd, d)}`).join(
|
|
714
|
-
p.outro(chalk.bold.green(
|
|
727
|
+
p.log.success(chalk.green('Architecture Documentation: ./ARCHITECTURE.md'));
|
|
728
|
+
p.log.message(chalk.cyan(`Initialized Features (${featureE2eDirs.length}):\n${featureE2eDirs.map((d) => ` - ./${path.relative(cwd, d)}`).join('\n')}`));
|
|
729
|
+
p.outro(chalk.bold.green('š Enterprise Nx Monorepo ready for testing!'));
|
|
715
730
|
return;
|
|
716
731
|
}
|
|
717
732
|
// --- STANDALONE / DEFAULT PROJECT FLOW ---
|
|
718
733
|
const s = p.spinner();
|
|
719
|
-
s.start(
|
|
734
|
+
s.start('Scaffolding standalone TestSpectra project...');
|
|
720
735
|
copyRecursive(templateDir, cwd);
|
|
721
|
-
const standaloneWsPath = path.join(cwd,
|
|
736
|
+
const standaloneWsPath = path.join(cwd, 'pnpm-workspace.yaml');
|
|
722
737
|
if (!fs.existsSync(standaloneWsPath) || options.force) {
|
|
723
738
|
const pnpmWsContent = `allowBuilds:\n chromedriver: true\n edgedriver: true\n geckodriver: true\n`;
|
|
724
|
-
fs.writeFileSync(standaloneWsPath, pnpmWsContent,
|
|
739
|
+
fs.writeFileSync(standaloneWsPath, pnpmWsContent, 'utf-8');
|
|
725
740
|
}
|
|
726
741
|
TypeGenerator.writeDeclarationFiles(cwd);
|
|
727
742
|
ensureVsCodeExtensions(cwd);
|
|
@@ -753,22 +768,22 @@ pnpm spectra run --target ios
|
|
|
753
768
|
pnpm type-check
|
|
754
769
|
\`\`\`
|
|
755
770
|
`;
|
|
756
|
-
fs.writeFileSync(path.join(cwd,
|
|
771
|
+
fs.writeFileSync(path.join(cwd, 'ARCHITECTURE.md'), standaloneArchDoc, 'utf-8');
|
|
757
772
|
// Safely update root README.md (do not overwrite existing content)
|
|
758
|
-
const standaloneReadmePath = path.join(cwd,
|
|
773
|
+
const standaloneReadmePath = path.join(cwd, 'README.md');
|
|
759
774
|
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`;
|
|
760
775
|
if (fs.existsSync(standaloneReadmePath)) {
|
|
761
|
-
const existing = fs.readFileSync(standaloneReadmePath,
|
|
762
|
-
if (!existing.includes(
|
|
763
|
-
fs.appendFileSync(standaloneReadmePath, standaloneArchSection,
|
|
776
|
+
const existing = fs.readFileSync(standaloneReadmePath, 'utf-8');
|
|
777
|
+
if (!existing.includes('ARCHITECTURE.md')) {
|
|
778
|
+
fs.appendFileSync(standaloneReadmePath, standaloneArchSection, 'utf-8');
|
|
764
779
|
}
|
|
765
780
|
}
|
|
766
781
|
else {
|
|
767
782
|
const standaloneReadme = `# ${projectName}\n\nTestSpectra automated testing project.\n${standaloneArchSection}`;
|
|
768
|
-
fs.writeFileSync(standaloneReadmePath, standaloneReadme,
|
|
783
|
+
fs.writeFileSync(standaloneReadmePath, standaloneReadme, 'utf-8');
|
|
769
784
|
}
|
|
770
|
-
s.stop(
|
|
771
|
-
p.log.success(chalk.green(
|
|
772
|
-
p.log.success(chalk.green(
|
|
773
|
-
p.outro(chalk.bold.green(
|
|
785
|
+
s.stop('Standalone project ready!');
|
|
786
|
+
p.log.success(chalk.green('Generated ambient multi-platform types in .testspectra/types/'));
|
|
787
|
+
p.log.success(chalk.green('Architecture documentation created at ./ARCHITECTURE.md'));
|
|
788
|
+
p.outro(chalk.bold.green('š Project initialized successfully!'));
|
|
774
789
|
}
|