@testspectra/cli 1.1.8-rc.2 → 1.1.8-rc.21
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 +47 -0
- package/bin/.testspectra-runner.hash +1 -1
- package/bin/spectra.js +0 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/commands/__tests__/cloud-run.test.d.ts +1 -0
- package/dist/commands/__tests__/cloud-run.test.js +93 -0
- package/dist/commands/__tests__/run-e2e.test.js +49 -49
- package/dist/commands/add.js +2 -1
- package/dist/commands/cloud-run.d.ts +13 -0
- package/dist/commands/cloud-run.js +120 -0
- 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.js +1 -0
- package/dist/commands/init.js +7 -160
- package/dist/commands/install.js +2 -0
- package/dist/commands/license.js +1 -0
- package/dist/commands/test.js +5 -3
- 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 +82 -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 +18 -11
- 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 +5 -5
- package/dist/linter/schemas/spec.schema.d.ts +2 -2
- package/dist/linter/schemas/suite.schema.d.ts +4 -4
- package/dist/runner/bridge.js +53 -4
- package/package.json +34 -29
- 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-shm} +0 -0
- package/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db-wal → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal} +0 -0
- package/templates/nx/.nx/workspace-data/d/daemon.log +1090 -1577
- package/templates/nx/.nx/workspace-data/d/server-process.json +1 -1
- package/templates/nx/.nx/workspace-data/file-map.json +56 -56
- 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 +185 -6681
- package/templates/nx/.nx/workspace-data/project-graph.json +190 -7434
- 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 +28 -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 +22 -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 +28 -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 +27 -27
- package/templates/react-component/src/components/Badge/Badge.tsx +60 -60
- package/templates/react-component/src/components/Button/Button.tsx +57 -57
- package/templates/react-component/src/components/Input/Input.tsx +41 -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/templates/nx/.nx/workspace-data/{59842386-c7c4-44ca-b2c2-20e1700bd13d.db → 70094CFD-E89B-57A1-9619-2FC5F4963C54.db} +0 -0
package/dist/commands/test.js
CHANGED
|
@@ -15,7 +15,7 @@ import { runSystemChecks } from './doctor.js';
|
|
|
15
15
|
async function resolveBrowserExecutable(cwd) {
|
|
16
16
|
try {
|
|
17
17
|
const { dependencies } = await runSystemChecks(cwd);
|
|
18
|
-
const chrome = dependencies.find((d) => d.category === 'web' &&
|
|
18
|
+
const chrome = dependencies.find((d) => d.category === 'web' && d.name.includes('Chrome'));
|
|
19
19
|
return chrome?.installed && chrome.location ? chrome.location : null;
|
|
20
20
|
}
|
|
21
21
|
catch {
|
|
@@ -81,9 +81,11 @@ export async function testCommand(options = {}) {
|
|
|
81
81
|
concurrency: concurrency !== undefined && !isNaN(concurrency) ? concurrency : undefined,
|
|
82
82
|
browserExecutablePath,
|
|
83
83
|
onLine: (line) => {
|
|
84
|
-
|
|
84
|
+
// Strip ANSI and whitespace since Vitest might prefix or colorize console logs
|
|
85
|
+
const cleanLine = line.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '').trim();
|
|
86
|
+
if (!cleanLine.startsWith('[TESTSPECTRA_'))
|
|
85
87
|
return;
|
|
86
|
-
reporter.addLog({ timestamp: new Date().toLocaleTimeString(), level: 'INFO', message:
|
|
88
|
+
reporter.addLog({ timestamp: new Date().toLocaleTimeString(), level: 'INFO', message: cleanLine });
|
|
87
89
|
if (!reporter.isInteractiveMode()) {
|
|
88
90
|
console.log(line);
|
|
89
91
|
}
|
|
@@ -41,7 +41,8 @@ describe('Platform TSConfigs & Type Isolation (docs/v2/cli/tsconfig-platform-iso
|
|
|
41
41
|
expect(rootConfig.references).toEqual([{ path: './.testspectra/tsconfig.json' }]);
|
|
42
42
|
// Verify dynamic master solution references all 5 platform solution tsconfigs in .testspectra/tsconfig/
|
|
43
43
|
const solutionConfig = JSON.parse(fs.readFileSync(path.join(tmpDir, '.testspectra', 'tsconfig.json'), 'utf-8'));
|
|
44
|
-
expect(solutionConfig.compilerOptions.
|
|
44
|
+
expect(solutionConfig.compilerOptions.composite).toBe(true);
|
|
45
|
+
expect(solutionConfig.compilerOptions.noEmit).toBeUndefined();
|
|
45
46
|
expect(solutionConfig.references).toEqual([
|
|
46
47
|
{ path: './tsconfig/web.json' },
|
|
47
48
|
{ path: './tsconfig/mobile.json' },
|
|
@@ -383,4 +383,42 @@ async function verifyZeroImport() {
|
|
|
383
383
|
expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'web.d.ts'))).toBe(false);
|
|
384
384
|
expect(fs.existsSync(path.join(tmpDir, '.testspectra', 'types', 'common.d.ts'))).toBe(false);
|
|
385
385
|
});
|
|
386
|
+
it('nx monorepo: does NOT treat feature library as shared library and discovers nested e2e subproject', () => {
|
|
387
|
+
// 1. Shared library with testspectra:shared
|
|
388
|
+
const sharedDir = path.join(tmpDir, 'shared', 'testing');
|
|
389
|
+
fs.mkdirSync(path.join(sharedDir, 'page-objects', 'SharedNav'), { recursive: true });
|
|
390
|
+
fs.writeFileSync(path.join(sharedDir, 'project.json'), JSON.stringify({ name: 'shared-testing', projectType: 'library', tags: ['testspectra:shared'] }));
|
|
391
|
+
fs.writeFileSync(path.join(sharedDir, 'page-objects', 'SharedNav', 'common.ts'), 'export default class SharedNav {}');
|
|
392
|
+
// 2. Nx Feature library (projectType: "library", standard Nx tags, NO testspectra tags)
|
|
393
|
+
const featureLibDir = path.join(tmpDir, 'packages', 'features', 'auth');
|
|
394
|
+
fs.mkdirSync(featureLibDir, { recursive: true });
|
|
395
|
+
fs.writeFileSync(path.join(featureLibDir, 'project.json'), JSON.stringify({
|
|
396
|
+
name: '@tagsamurai/feature-auth',
|
|
397
|
+
projectType: 'library',
|
|
398
|
+
sourceRoot: 'packages/features/auth',
|
|
399
|
+
targets: {},
|
|
400
|
+
tags: ['type:feature', 'scope:feature-auth'],
|
|
401
|
+
}));
|
|
402
|
+
// 3. Nested E2E subproject inside feature library
|
|
403
|
+
const featureE2eDir = path.join(featureLibDir, 'e2e');
|
|
404
|
+
fs.mkdirSync(path.join(featureE2eDir, 'specs', 'AuthSuite', 'TC-001'), { recursive: true });
|
|
405
|
+
fs.writeFileSync(path.join(featureE2eDir, 'project.json'), JSON.stringify({
|
|
406
|
+
name: 'feature-auth-e2e',
|
|
407
|
+
projectType: 'application',
|
|
408
|
+
tags: ['testspectra:e2e', 'testspectra:scope:feature-auth'],
|
|
409
|
+
}));
|
|
410
|
+
fs.writeFileSync(path.join(featureE2eDir, 'specs', 'AuthSuite', 'TC-001', 'web.test.ts'), 'it("auth test", async () => {});');
|
|
411
|
+
const scopes = TypeGenerator.getEntityScopes(tmpDir);
|
|
412
|
+
expect(scopes).toHaveLength(2);
|
|
413
|
+
const sharedScope = scopes.find((s) => s.isShared);
|
|
414
|
+
expect(sharedScope).toBeDefined();
|
|
415
|
+
expect(sharedScope?.name).toBe('shared');
|
|
416
|
+
expect(sharedScope?.dir).toBe(path.resolve(sharedDir));
|
|
417
|
+
const featureScope = scopes.find((s) => !s.isShared);
|
|
418
|
+
expect(featureScope).toBeDefined();
|
|
419
|
+
expect(featureScope?.name).toBe('feature-auth');
|
|
420
|
+
expect(featureScope?.dir).toBe(path.resolve(featureE2eDir));
|
|
421
|
+
// Verify packages/features/auth is NOT in scopes
|
|
422
|
+
expect(scopes.some((s) => s.dir === path.resolve(featureLibDir))).toBe(false);
|
|
423
|
+
});
|
|
386
424
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ArchitectureDocResult {
|
|
2
|
+
mode: 'nx' | 'default';
|
|
3
|
+
path: string;
|
|
4
|
+
sharedTestingRelPath?: string;
|
|
5
|
+
featureCount?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildFeatureModulesSection(cwd: string, featureE2eDirs: string[]): string;
|
|
8
|
+
/**
|
|
9
|
+
* (Re)generates `ARCHITECTURE.md` from the current on-disk workspace state — no prompts, no other
|
|
10
|
+
* scaffolding side effects. Nx/monorepo mode is auto-detected via `TsConfigGenerator.getEntityScopes`
|
|
11
|
+
* (the same shared/feature discovery `sync-types` already relies on), so this stays accurate after
|
|
12
|
+
* features are added/removed/renamed without needing to re-run `spectra init`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateArchitectureDoc(cwd: string): ArchitectureDocResult;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { TsConfigGenerator } from './tsconfig-generator.js';
|
|
5
|
+
function resolveDocsTemplateDir() {
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const candidates = [
|
|
9
|
+
path.resolve(__dirname, '../templates/docs'),
|
|
10
|
+
path.resolve(__dirname, '../../templates/docs'),
|
|
11
|
+
path.resolve(__dirname, '../../../templates/docs'),
|
|
12
|
+
];
|
|
13
|
+
return candidates.find((d) => fs.existsSync(d)) ?? candidates[candidates.length - 1];
|
|
14
|
+
}
|
|
15
|
+
function renderArchitectureDoc(templatePath, replacements) {
|
|
16
|
+
let content = fs.readFileSync(templatePath, 'utf-8');
|
|
17
|
+
for (const [token, value] of Object.entries(replacements)) {
|
|
18
|
+
content = content.replaceAll(token, value);
|
|
19
|
+
}
|
|
20
|
+
return content;
|
|
21
|
+
}
|
|
22
|
+
/** Reads a feature e2e dir's project.json for its Nx project name, falling back to `<parentDirName>-<e2eDirName>`. */
|
|
23
|
+
function resolveNxProjectName(featureDir) {
|
|
24
|
+
const parentName = path.basename(path.dirname(featureDir));
|
|
25
|
+
const fallback = `${parentName}-${path.basename(featureDir)}`;
|
|
26
|
+
const projJsonPath = path.join(featureDir, 'project.json');
|
|
27
|
+
if (!fs.existsSync(projJsonPath))
|
|
28
|
+
return fallback;
|
|
29
|
+
try {
|
|
30
|
+
const pObj = JSON.parse(fs.readFileSync(projJsonPath, 'utf-8'));
|
|
31
|
+
return typeof pObj.name === 'string' && pObj.name ? pObj.name : fallback;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return fallback;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function buildFeatureModulesSection(cwd, featureE2eDirs) {
|
|
38
|
+
return featureE2eDirs
|
|
39
|
+
.map((d) => {
|
|
40
|
+
const rel = path.relative(cwd, d).replace(/\\/g, '/');
|
|
41
|
+
const featureName = path.basename(path.dirname(d));
|
|
42
|
+
const e2eName = resolveNxProjectName(d);
|
|
43
|
+
return `- \`./${rel}\` (**${featureName}**, Nx Project: \`${e2eName}\`):
|
|
44
|
+
- \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
45
|
+
- \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
|
|
46
|
+
- \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).`;
|
|
47
|
+
})
|
|
48
|
+
.join('\n');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* (Re)generates `ARCHITECTURE.md` from the current on-disk workspace state — no prompts, no other
|
|
52
|
+
* scaffolding side effects. Nx/monorepo mode is auto-detected via `TsConfigGenerator.getEntityScopes`
|
|
53
|
+
* (the same shared/feature discovery `sync-types` already relies on), so this stays accurate after
|
|
54
|
+
* features are added/removed/renamed without needing to re-run `spectra init`.
|
|
55
|
+
*/
|
|
56
|
+
export function generateArchitectureDoc(cwd) {
|
|
57
|
+
const docsTemplateDir = resolveDocsTemplateDir();
|
|
58
|
+
const destPath = path.join(cwd, 'ARCHITECTURE.md');
|
|
59
|
+
const scopes = TsConfigGenerator.getEntityScopes(cwd);
|
|
60
|
+
const isNxMode = scopes.length > 0 || fs.existsSync(path.join(cwd, 'nx.json'));
|
|
61
|
+
if (!isNxMode) {
|
|
62
|
+
fs.copyFileSync(path.join(docsTemplateDir, 'ARCHITECTURE.default.md'), destPath);
|
|
63
|
+
return { mode: 'default', path: destPath };
|
|
64
|
+
}
|
|
65
|
+
const sharedScope = scopes.find((s) => s.isShared);
|
|
66
|
+
const featureE2eDirs = scopes.filter((s) => !s.isShared).map((s) => s.dir);
|
|
67
|
+
const sharedTestingRelPath = sharedScope
|
|
68
|
+
? path.relative(cwd, sharedScope.dir).replace(/\\/g, '/')
|
|
69
|
+
: 'shared/testing';
|
|
70
|
+
const e2eFolderName = featureE2eDirs.length > 0 ? path.basename(featureE2eDirs[0]) : 'e2e';
|
|
71
|
+
const content = renderArchitectureDoc(path.join(docsTemplateDir, 'ARCHITECTURE.nx.md'), {
|
|
72
|
+
__SHARED_TESTING_REL_PATH__: sharedTestingRelPath,
|
|
73
|
+
__FEATURE_COUNT__: String(featureE2eDirs.length),
|
|
74
|
+
__FEATURE_MODULES_SECTION__: buildFeatureModulesSection(cwd, featureE2eDirs),
|
|
75
|
+
__E2E_FOLDER_NAME__: e2eFolderName,
|
|
76
|
+
__SAMPLE_FEATURE_E2E_PATH__: featureE2eDirs.length > 0
|
|
77
|
+
? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, '/')
|
|
78
|
+
: `modules/auth/${e2eFolderName}`,
|
|
79
|
+
});
|
|
80
|
+
fs.writeFileSync(destPath, content, 'utf-8');
|
|
81
|
+
return { mode: 'nx', path: destPath, sharedTestingRelPath, featureCount: featureE2eDirs.length };
|
|
82
|
+
}
|
package/dist/generator/index.js
CHANGED
|
@@ -9,8 +9,11 @@ export interface EntityScope {
|
|
|
9
9
|
* Discovers all feature directories and shared library directories.
|
|
10
10
|
* Standard discovery hierarchy:
|
|
11
11
|
* 1. Nx / Monorepo Project Descriptors (`project.json`):
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
12
|
+
* - tags: ["testspectra:shared"] => Shared Library Scope (isShared: true)
|
|
13
|
+
* - tags: ["testspectra:e2e"] or ["testspectra:scope:<name>"] => Feature Scope (isShared: false)
|
|
14
|
+
* - Generic Nx libraries/apps without testspectra tags are not TestSpectra scopes and recursion
|
|
15
|
+
* continues into their subdirectories so nested e2e projects (e.g. `packages/features/auth/e2e`)
|
|
16
|
+
* are discovered.
|
|
14
17
|
* 2. Explicit User Configuration (`tsconfig.spectra.shared.json`):
|
|
15
18
|
* - Custom include path for shared testing utilities
|
|
16
19
|
* 3. Dynamic Workspace Filesystem Discovery (Fallback for non-Nx / custom layouts):
|
|
@@ -13,8 +13,11 @@ export const PLATFORM_HIERARCHY = {
|
|
|
13
13
|
* Discovers all feature directories and shared library directories.
|
|
14
14
|
* Standard discovery hierarchy:
|
|
15
15
|
* 1. Nx / Monorepo Project Descriptors (`project.json`):
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
16
|
+
* - tags: ["testspectra:shared"] => Shared Library Scope (isShared: true)
|
|
17
|
+
* - tags: ["testspectra:e2e"] or ["testspectra:scope:<name>"] => Feature Scope (isShared: false)
|
|
18
|
+
* - Generic Nx libraries/apps without testspectra tags are not TestSpectra scopes and recursion
|
|
19
|
+
* continues into their subdirectories so nested e2e projects (e.g. `packages/features/auth/e2e`)
|
|
20
|
+
* are discovered.
|
|
18
21
|
* 2. Explicit User Configuration (`tsconfig.spectra.shared.json`):
|
|
19
22
|
* - Custom include path for shared testing utilities
|
|
20
23
|
* 3. Dynamic Workspace Filesystem Discovery (Fallback for non-Nx / custom layouts):
|
|
@@ -48,16 +51,9 @@ export function discoverEntityScopes(cwd) {
|
|
|
48
51
|
if (fs.existsSync(projectJsonPath)) {
|
|
49
52
|
try {
|
|
50
53
|
const pObj = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8'));
|
|
51
|
-
const projectType = pObj.projectType;
|
|
52
54
|
const tags = Array.isArray(pObj.tags) ? pObj.tags : [];
|
|
53
|
-
const isSharedLib =
|
|
54
|
-
|
|
55
|
-
tags.includes('type:library') ||
|
|
56
|
-
tags.includes('scope:shared');
|
|
57
|
-
const isE2eApp = projectType === 'application' ||
|
|
58
|
-
tags.includes('type:e2e') ||
|
|
59
|
-
tags.includes('type:app') ||
|
|
60
|
-
tags.some((t) => t.startsWith('scope:') && t !== 'scope:shared');
|
|
55
|
+
const isSharedLib = tags.includes('testspectra:shared');
|
|
56
|
+
const isE2eApp = tags.includes('testspectra:e2e') || tags.some((t) => t.startsWith('testspectra:scope:'));
|
|
61
57
|
if (isSharedLib) {
|
|
62
58
|
if (!scopes.some((s) => s.dir === dir)) {
|
|
63
59
|
scopes.push({ name: 'shared', dir, isShared: true });
|
|
@@ -65,9 +61,9 @@ export function discoverEntityScopes(cwd) {
|
|
|
65
61
|
return; // Do not recurse into subdirectories of an identified project
|
|
66
62
|
}
|
|
67
63
|
else if (isE2eApp) {
|
|
68
|
-
// Extract scope name from tags (e.g. "scope:matchers" => "matchers") or project name
|
|
69
|
-
const scopeTag = tags.find((t) => t.startsWith('
|
|
70
|
-
let featureName = scopeTag ? scopeTag.replace('scope:', '') : '';
|
|
64
|
+
// Extract scope name from tags (e.g. "testspectra:scope:matchers" => "matchers") or project name
|
|
65
|
+
const scopeTag = tags.find((t) => t.startsWith('testspectra:scope:'));
|
|
66
|
+
let featureName = scopeTag ? scopeTag.replace('testspectra:scope:', '') : '';
|
|
71
67
|
if (!featureName && pObj.name) {
|
|
72
68
|
featureName = pObj.name.replace(/-e2e$/, '').replace(/^e2e-/, '');
|
|
73
69
|
}
|
|
@@ -329,6 +325,9 @@ export class TypeGenerator {
|
|
|
329
325
|
const hierarchy = PLATFORM_HIERARCHY[platform];
|
|
330
326
|
let content = `// Auto-generated ambient declarations for TestSpectra [${platform.toUpperCase()}]\n`;
|
|
331
327
|
content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
|
|
328
|
+
content += `/// <reference types="@testspectra/matchers" />\n`;
|
|
329
|
+
content += `/// <reference path="./fixtures.d.ts" />\n`;
|
|
330
|
+
content += `/// <reference path="${envRefPath}" />\n\n`;
|
|
332
331
|
// TestSpectra's Rust-native orchestrator worker (core/orchestrator/src/worker/bdd_runner.ts)
|
|
333
332
|
// registers its own `it`/`test`/lifecycle-hook globals directly — there is no WDIO or Mocha
|
|
334
333
|
// runtime involved at all (not just for component testing; this is true across every
|
|
@@ -346,9 +345,7 @@ export class TypeGenerator {
|
|
|
346
345
|
// — the Rust orchestrator's BDD runtime has no `expect` global at all.
|
|
347
346
|
content += `declare const expect: import('@testspectra/react').ExpectStatic;\n`;
|
|
348
347
|
}
|
|
349
|
-
content +=
|
|
350
|
-
content += `/// <reference path="./fixtures.d.ts" />\n`;
|
|
351
|
-
content += `/// <reference path="${envRefPath}" />\n\n`;
|
|
348
|
+
content += `\n`;
|
|
352
349
|
// 1. Page Objects Global Declarations
|
|
353
350
|
const declaredPOMs = new Set();
|
|
354
351
|
for (const poDir of poDirs) {
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { syncTypesCommand } from './commands/sync-types.js';
|
|
|
13
13
|
import { refactorCommand } from './commands/refactor.js';
|
|
14
14
|
import { lintCommand } from './commands/lint.js';
|
|
15
15
|
import { licenseCommand } from './commands/license.js';
|
|
16
|
+
import { docsUpdateCommand } from './commands/docs.js';
|
|
16
17
|
import { skillsInitCommand, skillsListCommand, skillsAddCommand, skillsUpdateCommand, skillsRemoveCommand, } from './commands/skills.js';
|
|
17
18
|
export * from './config/schema.js';
|
|
18
19
|
export * from './config/loader.js';
|
|
@@ -41,13 +42,13 @@ export function createCliProgram() {
|
|
|
41
42
|
.command('install [dependency]')
|
|
42
43
|
.description('Install missing testing dependencies, browser drivers, and workspace packages')
|
|
43
44
|
.option('-f, --force', 'Force re-installation of dependencies')
|
|
44
|
-
.addHelpText('after', `
|
|
45
|
-
Examples:
|
|
46
|
-
$ spectra install Install all missing required dependencies automatically
|
|
47
|
-
$ spectra install chrome Install Google Chrome for Testing & matching Chromedriver
|
|
48
|
-
$ spectra install adb Install Google Android SDK Platform Tools (ADB)
|
|
49
|
-
$ spectra install bun Install Bun runtime
|
|
50
|
-
$ spectra install android-driver Install Spectra Android Native Driver & WDIO Service
|
|
45
|
+
.addHelpText('after', `
|
|
46
|
+
Examples:
|
|
47
|
+
$ spectra install Install all missing required dependencies automatically
|
|
48
|
+
$ spectra install chrome Install Google Chrome for Testing & matching Chromedriver
|
|
49
|
+
$ spectra install adb Install Google Android SDK Platform Tools (ADB)
|
|
50
|
+
$ spectra install bun Install Bun runtime
|
|
51
|
+
$ spectra install android-driver Install Spectra Android Native Driver & WDIO Service
|
|
51
52
|
$ spectra install wdio Install WebDriverIO core packages and matchers`)
|
|
52
53
|
.action(installCommand);
|
|
53
54
|
program
|
|
@@ -105,10 +106,10 @@ Examples:
|
|
|
105
106
|
.command('license [action] [key]')
|
|
106
107
|
.description('Manage TestSpectra commercial license (activate, status, remove)')
|
|
107
108
|
.option('--json', 'Output license details as JSON')
|
|
108
|
-
.addHelpText('after', `
|
|
109
|
-
Examples:
|
|
110
|
-
$ spectra license activate <KEY> Activate license key and auto-configure private driver registry
|
|
111
|
-
$ spectra license status Display active license organization, plan, and expiration
|
|
109
|
+
.addHelpText('after', `
|
|
110
|
+
Examples:
|
|
111
|
+
$ spectra license activate <KEY> Activate license key and auto-configure private driver registry
|
|
112
|
+
$ spectra license status Display active license organization, plan, and expiration
|
|
112
113
|
$ spectra license remove Remove locally activated license credentials`)
|
|
113
114
|
.action(licenseCommand);
|
|
114
115
|
const skills = program
|
|
@@ -147,6 +148,12 @@ Examples:
|
|
|
147
148
|
.option('-w, --cwd <dir>', 'Custom workspace directory')
|
|
148
149
|
.option('-a, --agent <ids>', 'One-off override: comma-separated agent(s) to target, bypassing the saved "init" selection (claude, opencode, kilo, agents, gemini)')
|
|
149
150
|
.action(skillsRemoveCommand);
|
|
151
|
+
const docs = program.command('docs').description('Manage generated TestSpectra workspace documentation');
|
|
152
|
+
docs
|
|
153
|
+
.command('update')
|
|
154
|
+
.description('Regenerate ARCHITECTURE.md from the current workspace state, without re-running init')
|
|
155
|
+
.option('-w, --cwd <dir>', 'Custom workspace directory')
|
|
156
|
+
.action(docsUpdateCommand);
|
|
150
157
|
program
|
|
151
158
|
.command('run [paths...]')
|
|
152
159
|
.description('Execute test suite or individual test case')
|
|
@@ -14,20 +14,20 @@ const __dirname = path.dirname(__filename);
|
|
|
14
14
|
*/
|
|
15
15
|
describe('Component Test (.tsx) Import Exception', () => {
|
|
16
16
|
const tempDir = path.join(__dirname, '__temp_tsx_imports__');
|
|
17
|
-
const specFrontmatter = `---
|
|
18
|
-
id: TC-0200
|
|
19
|
-
title: Sample component test
|
|
20
|
-
priority: High
|
|
21
|
-
caseType: Positive
|
|
22
|
-
status: automated
|
|
23
|
-
coverage:
|
|
24
|
-
web: automated
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
# Sample component test
|
|
28
|
-
|
|
29
|
-
## Test Steps
|
|
30
|
-
1. Mount the component and assert it is visible.
|
|
17
|
+
const specFrontmatter = `---
|
|
18
|
+
id: TC-0200
|
|
19
|
+
title: Sample component test
|
|
20
|
+
priority: High
|
|
21
|
+
caseType: Positive
|
|
22
|
+
status: automated
|
|
23
|
+
coverage:
|
|
24
|
+
web: automated
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Sample component test
|
|
28
|
+
|
|
29
|
+
## Test Steps
|
|
30
|
+
1. Mount the component and assert it is visible.
|
|
31
31
|
`;
|
|
32
32
|
beforeEach(() => {
|
|
33
33
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
@@ -39,23 +39,23 @@ coverage:
|
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
it('does not flag no-manual-import for ordinary component/library imports in .test.tsx', () => {
|
|
42
|
-
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import { Button } from '../../src/components/Button/Button.js';
|
|
43
|
-
import React from 'react';
|
|
44
|
-
|
|
45
|
-
it('Sample component test', async () => {
|
|
46
|
-
await Spectra.mount(<Button label="Save" />);
|
|
47
|
-
});
|
|
42
|
+
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import { Button } from '../../src/components/Button/Button.js';
|
|
43
|
+
import React from 'react';
|
|
44
|
+
|
|
45
|
+
it('Sample component test', async () => {
|
|
46
|
+
await Spectra.mount(<Button label="Save" />);
|
|
47
|
+
});
|
|
48
48
|
`);
|
|
49
49
|
const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
|
|
50
50
|
const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
|
|
51
51
|
expect(importErrors).toHaveLength(0);
|
|
52
52
|
});
|
|
53
53
|
it('still flags no-manual-import when a .test.tsx import resolves into page-objects/', () => {
|
|
54
|
-
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import ButtonComponent from '../../page-objects/ButtonComponent/web.js';
|
|
55
|
-
|
|
56
|
-
it('Sample component test', async () => {
|
|
57
|
-
await ButtonComponent.root.shouldBeVisible();
|
|
58
|
-
});
|
|
54
|
+
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import ButtonComponent from '../../page-objects/ButtonComponent/web.js';
|
|
55
|
+
|
|
56
|
+
it('Sample component test', async () => {
|
|
57
|
+
await ButtonComponent.root.shouldBeVisible();
|
|
58
|
+
});
|
|
59
59
|
`);
|
|
60
60
|
const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
|
|
61
61
|
const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
|
|
@@ -63,50 +63,50 @@ it('Sample component test', async () => {
|
|
|
63
63
|
expect(importErrors[0].message).toContain('page-objects');
|
|
64
64
|
});
|
|
65
65
|
it('still flags no-manual-import when a .test.tsx import resolves into support/steps/', () => {
|
|
66
|
-
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import verifyButtonState from '../../support/steps/verifyButtonState/web.step.js';
|
|
67
|
-
|
|
68
|
-
it('Sample component test', async () => {
|
|
69
|
-
await verifyButtonState('Save', true);
|
|
70
|
-
});
|
|
66
|
+
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import verifyButtonState from '../../support/steps/verifyButtonState/web.step.js';
|
|
67
|
+
|
|
68
|
+
it('Sample component test', async () => {
|
|
69
|
+
await verifyButtonState('Save', true);
|
|
70
|
+
});
|
|
71
71
|
`);
|
|
72
72
|
const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
|
|
73
73
|
const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
|
|
74
74
|
expect(importErrors).toHaveLength(1);
|
|
75
75
|
});
|
|
76
76
|
it('still flags no-manual-import when a .test.tsx import resolves into fixtures/', () => {
|
|
77
|
-
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import users from '../../fixtures/users.json';
|
|
78
|
-
|
|
79
|
-
it('Sample component test', async () => {
|
|
80
|
-
expect(users).toBeDefined();
|
|
81
|
-
});
|
|
77
|
+
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import users from '../../fixtures/users.json';
|
|
78
|
+
|
|
79
|
+
it('Sample component test', async () => {
|
|
80
|
+
expect(users).toBeDefined();
|
|
81
|
+
});
|
|
82
82
|
`);
|
|
83
83
|
const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
|
|
84
84
|
const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
|
|
85
85
|
expect(importErrors).toHaveLength(1);
|
|
86
86
|
});
|
|
87
87
|
it('still enforces single-it-required and no-describe-block in .test.tsx files', () => {
|
|
88
|
-
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import { Button } from '../../src/components/Button/Button.js';
|
|
89
|
-
|
|
90
|
-
describe('Button', () => {
|
|
91
|
-
it('Sample component test', async () => {
|
|
92
|
-
await Spectra.mount(<Button label="Save" />);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('Another test', async () => {
|
|
96
|
-
await Spectra.mount(<Button label="Cancel" />);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
88
|
+
fs.writeFileSync(path.join(tempDir, 'web.test.tsx'), `import { Button } from '../../src/components/Button/Button.js';
|
|
89
|
+
|
|
90
|
+
describe('Button', () => {
|
|
91
|
+
it('Sample component test', async () => {
|
|
92
|
+
await Spectra.mount(<Button label="Save" />);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('Another test', async () => {
|
|
96
|
+
await Spectra.mount(<Button label="Cancel" />);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
99
|
`);
|
|
100
100
|
const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
|
|
101
101
|
expect(diagnostics.some((d) => d.code === 'testspectra/no-describe-block')).toBe(true);
|
|
102
102
|
expect(diagnostics.some((d) => d.code === 'testspectra/single-it-required')).toBe(true);
|
|
103
103
|
});
|
|
104
104
|
it('still forbids all imports (unchanged behavior) in plain .test.ts E2E specs', () => {
|
|
105
|
-
fs.writeFileSync(path.join(tempDir, 'web.test.ts'), `import { LoginPage } from '../../page-objects/LoginPage/web.js';
|
|
106
|
-
|
|
107
|
-
it('Sample component test', async () => {
|
|
108
|
-
await LoginPage.submitButton.click();
|
|
109
|
-
});
|
|
105
|
+
fs.writeFileSync(path.join(tempDir, 'web.test.ts'), `import { LoginPage } from '../../page-objects/LoginPage/web.js';
|
|
106
|
+
|
|
107
|
+
it('Sample component test', async () => {
|
|
108
|
+
await LoginPage.submitButton.click();
|
|
109
|
+
});
|
|
110
110
|
`);
|
|
111
111
|
const { diagnostics } = SpecLinter.validateSpecFile(path.join(tempDir, 'spec.md'));
|
|
112
112
|
const importErrors = diagnostics.filter((d) => d.code === 'testspectra/no-manual-import');
|
|
@@ -17,16 +17,16 @@ describe('Shared Library Boundary (testspectra/no-specs-in-shared-lib)', () => {
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
describe('findSharedLibraryRoots()', () => {
|
|
20
|
-
it('discovers parent folders of project.json files containing
|
|
20
|
+
it('discovers parent folders of project.json files containing testspectra:shared', () => {
|
|
21
21
|
const sharedDir1 = path.join(tempDir, 'shared', 'testing');
|
|
22
22
|
const sharedDir2 = path.join(tempDir, 'libs', 'common-helpers');
|
|
23
23
|
const nonSharedDir = path.join(tempDir, 'packages', 'playground', 'e2e');
|
|
24
24
|
fs.mkdirSync(sharedDir1, { recursive: true });
|
|
25
25
|
fs.mkdirSync(sharedDir2, { recursive: true });
|
|
26
26
|
fs.mkdirSync(nonSharedDir, { recursive: true });
|
|
27
|
-
fs.writeFileSync(path.join(sharedDir1, 'project.json'), JSON.stringify({ name: 'shared-testing', tags: ['
|
|
28
|
-
fs.writeFileSync(path.join(sharedDir2, 'project.json'), JSON.stringify({ name: 'common-helpers', tags: ['
|
|
29
|
-
fs.writeFileSync(path.join(nonSharedDir, 'project.json'), JSON.stringify({ name: 'playground-e2e', tags: ['
|
|
27
|
+
fs.writeFileSync(path.join(sharedDir1, 'project.json'), JSON.stringify({ name: 'shared-testing', tags: ['testspectra:shared'] }));
|
|
28
|
+
fs.writeFileSync(path.join(sharedDir2, 'project.json'), JSON.stringify({ name: 'common-helpers', tags: ['testspectra:shared'] }));
|
|
29
|
+
fs.writeFileSync(path.join(nonSharedDir, 'project.json'), JSON.stringify({ name: 'playground-e2e', tags: ['testspectra:e2e', 'testspectra:scope:playground'] }));
|
|
30
30
|
const roots = findSharedLibraryRoots(tempDir);
|
|
31
31
|
expect(roots).toHaveLength(2);
|
|
32
32
|
expect(roots).toContain(sharedDir1);
|
|
@@ -35,12 +35,12 @@ describe('Shared Library Boundary (testspectra/no-specs-in-shared-lib)', () => {
|
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
describe('isInsideSharedLibrary()', () => {
|
|
38
|
-
it('returns true for files inside a project tagged with
|
|
38
|
+
it('returns true for files inside a project tagged with testspectra:shared in project.json', () => {
|
|
39
39
|
const sharedDir = path.join(tempDir, 'shared', 'testing');
|
|
40
40
|
fs.mkdirSync(sharedDir, { recursive: true });
|
|
41
41
|
fs.writeFileSync(path.join(sharedDir, 'project.json'), JSON.stringify({
|
|
42
42
|
name: 'shared-testing',
|
|
43
|
-
tags: ['
|
|
43
|
+
tags: ['testspectra:shared'],
|
|
44
44
|
}));
|
|
45
45
|
const stepFile = path.join(sharedDir, 'support', 'steps', 'web.step.ts');
|
|
46
46
|
const specFile = path.join(sharedDir, 'specs', 'TC-001', 'spec.md');
|
|
@@ -55,7 +55,7 @@ describe('Shared Library Boundary (testspectra/no-specs-in-shared-lib)', () => {
|
|
|
55
55
|
fs.mkdirSync(customLibDir, { recursive: true });
|
|
56
56
|
fs.writeFileSync(path.join(customLibDir, 'project.json'), JSON.stringify({
|
|
57
57
|
name: 'custom-common-lib',
|
|
58
|
-
tags: ['
|
|
58
|
+
tags: ['testspectra:shared'],
|
|
59
59
|
}));
|
|
60
60
|
const actionFile = path.join(customLibDir, 'support', 'actions', 'click.action.ts');
|
|
61
61
|
expect(isInsideSharedLibrary(actionFile)).toBe(true);
|
|
@@ -65,7 +65,7 @@ describe('Shared Library Boundary (testspectra/no-specs-in-shared-lib)', () => {
|
|
|
65
65
|
fs.mkdirSync(sharedAppDir, { recursive: true });
|
|
66
66
|
fs.writeFileSync(path.join(sharedAppDir, 'project.json'), JSON.stringify({
|
|
67
67
|
name: 'shared-app',
|
|
68
|
-
tags: ['
|
|
68
|
+
tags: ['testspectra:e2e', 'testspectra:scope:shared-app'],
|
|
69
69
|
}));
|
|
70
70
|
const specFile = path.join(sharedAppDir, 'specs', 'TC-001', 'spec.md');
|
|
71
71
|
expect(isInsideSharedLibrary(specFile)).toBe(false);
|
|
@@ -75,7 +75,7 @@ describe('Shared Library Boundary (testspectra/no-specs-in-shared-lib)', () => {
|
|
|
75
75
|
fs.mkdirSync(featureDir, { recursive: true });
|
|
76
76
|
fs.writeFileSync(path.join(featureDir, 'project.json'), JSON.stringify({
|
|
77
77
|
name: 'playground-e2e',
|
|
78
|
-
tags: ['
|
|
78
|
+
tags: ['testspectra:e2e', 'testspectra:scope:playground'],
|
|
79
79
|
}));
|
|
80
80
|
const specFile = path.join(featureDir, 'specs', 'TC-001', 'spec.md');
|
|
81
81
|
expect(isInsideSharedLibrary(specFile)).toBe(false);
|
|
@@ -91,7 +91,7 @@ describe('Shared Library Boundary (testspectra/no-specs-in-shared-lib)', () => {
|
|
|
91
91
|
fs.mkdirSync(sharedLibDir, { recursive: true });
|
|
92
92
|
fs.writeFileSync(path.join(sharedLibDir, 'project.json'), JSON.stringify({
|
|
93
93
|
name: 'shared-testing',
|
|
94
|
-
tags: ['
|
|
94
|
+
tags: ['testspectra:shared'],
|
|
95
95
|
}));
|
|
96
96
|
const sharedSpecDir = path.join(sharedLibDir, 'specs', 'TC-error');
|
|
97
97
|
fs.mkdirSync(sharedSpecDir, { recursive: true });
|
|
@@ -119,7 +119,7 @@ status: draft
|
|
|
119
119
|
fs.mkdirSync(sharedLibDir, { recursive: true });
|
|
120
120
|
fs.writeFileSync(path.join(sharedLibDir, 'project.json'), JSON.stringify({
|
|
121
121
|
name: 'shared-testing',
|
|
122
|
-
tags: ['
|
|
122
|
+
tags: ['testspectra:shared'],
|
|
123
123
|
}));
|
|
124
124
|
const sharedSuiteDir = path.join(sharedLibDir, 'support');
|
|
125
125
|
fs.mkdirSync(sharedSuiteDir, { recursive: true });
|
|
@@ -141,7 +141,7 @@ status: active
|
|
|
141
141
|
fs.mkdirSync(customLibDir, { recursive: true });
|
|
142
142
|
fs.writeFileSync(path.join(customLibDir, 'project.json'), JSON.stringify({
|
|
143
143
|
name: 'my-testing-utils',
|
|
144
|
-
tags: ['
|
|
144
|
+
tags: ['testspectra:shared'],
|
|
145
145
|
}));
|
|
146
146
|
const specDir = path.join(customLibDir, 'specs', 'TC-custom');
|
|
147
147
|
fs.mkdirSync(specDir, { recursive: true });
|
|
@@ -169,7 +169,7 @@ status: draft
|
|
|
169
169
|
fs.mkdirSync(featureDir, { recursive: true });
|
|
170
170
|
fs.writeFileSync(path.join(featureDir, 'project.json'), JSON.stringify({
|
|
171
171
|
name: 'playground-e2e',
|
|
172
|
-
tags: ['
|
|
172
|
+
tags: ['testspectra:e2e', 'testspectra:scope:playground'],
|
|
173
173
|
}));
|
|
174
174
|
const featureSpecDir = path.join(featureDir, 'specs', 'TC-001');
|
|
175
175
|
fs.mkdirSync(featureSpecDir, { recursive: true });
|
|
@@ -195,7 +195,7 @@ status: draft
|
|
|
195
195
|
fs.mkdirSync(sharedE2eDir, { recursive: true });
|
|
196
196
|
fs.writeFileSync(path.join(sharedE2eDir, 'project.json'), JSON.stringify({
|
|
197
197
|
name: 'shared-feature-e2e',
|
|
198
|
-
tags: ['
|
|
198
|
+
tags: ['testspectra:e2e', 'testspectra:scope:shared-feature'],
|
|
199
199
|
}));
|
|
200
200
|
const specDir = path.join(sharedE2eDir, 'specs', 'TC-001');
|
|
201
201
|
fs.mkdirSync(specDir, { recursive: true });
|
|
@@ -241,7 +241,7 @@ status: draft
|
|
|
241
241
|
fs.mkdirSync(sharedLibDir, { recursive: true });
|
|
242
242
|
fs.writeFileSync(path.join(sharedLibDir, 'project.json'), JSON.stringify({
|
|
243
243
|
name: 'shared-testing',
|
|
244
|
-
tags: ['
|
|
244
|
+
tags: ['testspectra:shared'],
|
|
245
245
|
}));
|
|
246
246
|
const sharedDir1 = path.join(sharedLibDir, 'specs', 'TC-001');
|
|
247
247
|
const sharedDir2 = path.join(sharedLibDir, 'specs', 'TC-002');
|