@testspectra/cli 1.0.70 → 1.1.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-DPfv5_EF.js +116 -0
- package/demo-app/assets/index-gIwu2EMV.css +1 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -0
- 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__/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 +181 -0
- package/dist/commands/__tests__/run.test.d.ts +1 -0
- package/dist/commands/__tests__/run.test.js +197 -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/cloud-run.d.ts +11 -0
- package/dist/commands/cloud-run.js +118 -0
- package/dist/commands/demo.js +7 -7
- 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 +206 -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/refactor.d.ts +8 -0
- package/dist/commands/refactor.js +78 -0
- package/dist/commands/run.d.ts +22 -1
- package/dist/commands/run.js +252 -109
- package/dist/commands/sync-types.d.ts +4 -0
- package/dist/commands/sync-types.js +14 -0
- package/dist/commands/update.js +39 -43
- package/dist/commands/watch.d.ts +6 -0
- package/dist/commands/watch.js +47 -43
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +40 -25
- package/dist/config/schema.d.ts +117 -49
- package/dist/config/schema.js +41 -34
- package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
- package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -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 +391 -0
- package/dist/generator/type-generator.d.ts +36 -0
- package/dist/generator/type-generator.js +644 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +124 -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/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 +175 -0
- package/dist/refactor/index.d.ts +1 -0
- package/dist/refactor/index.js +1 -0
- package/dist/refactor/refactor-engine.d.ts +25 -0
- package/dist/refactor/refactor-engine.js +255 -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 +191 -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 +69 -44
- package/dist/runner/reporter.d.ts +30 -2
- package/dist/runner/reporter.js +419 -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/demo-server.js +38 -35
- package/package.json +20 -12
- package/templates/default/CLAUDE.md +7 -5
- package/templates/default/fixtures/apiData.json +24 -0
- package/templates/default/package.json +13 -4
- package/templates/default/page-objects/MatchersPage/mobile.ts +41 -16
- package/templates/default/page-objects/MatchersPage/web.ts +14 -0
- package/templates/default/page-objects/NavigationPage/mobile.ts +10 -1
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +27 -17
- package/templates/default/page-objects/PlaygroundPage/web.ts +2 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +11 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +16 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/default/specs/ApiInterception/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/web.test.ts +17 -3
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +15 -1
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
- 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/web.test.ts +8 -3
- 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/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/web.test.ts +27 -6
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
- package/templates/default/spectra.config.ts +21 -14
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +2 -2
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +2 -0
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +2 -2
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +3 -1
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +3 -3
- package/templates/default/tsconfig.json +3 -1
- package/templates/nx/.nx/workspace-data/d/daemon.log +168 -0
- package/templates/nx/CLAUDE.md +2 -2
- package/templates/nx/package.json +7 -5
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +3 -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 +3 -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/web.test.ts +17 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +14 -4
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +8 -3
- 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/web.test.ts +27 -6
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +3 -3
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +2 -2
- 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 +12 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +14 -0
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +2 -0
- package/templates/nx/shared/testing/project.json +19 -0
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +2 -2
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +3 -3
- package/templates/nx/spectra.config.ts +19 -12
- package/templates/nx/tsconfig.json +3 -1
- 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/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/types/generator.js
CHANGED
|
@@ -1,443 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import path from "path";
|
|
3
|
-
const PLATFORM_HIERARCHY = {
|
|
4
|
-
web: ["web", "common"],
|
|
5
|
-
ios: ["ios", "mobile", "common", "web"],
|
|
6
|
-
android: ["android", "mobile", "common", "web"],
|
|
7
|
-
mobile: ["mobile", "common", "android", "ios", "web"],
|
|
8
|
-
common: ["common", "web", "mobile", "android", "ios"],
|
|
9
|
-
};
|
|
10
|
-
export class TypeGenerator {
|
|
11
|
-
/**
|
|
12
|
-
* Discovers all feature directories and shared library directories.
|
|
13
|
-
* Returns a map of scope -> base directory (e.g. { "shared": ".../shared/testing", "auth": ".../packages/features/auth/e2e" })
|
|
14
|
-
*/
|
|
15
|
-
static getEntityScopes(cwd) {
|
|
16
|
-
const scopes = [];
|
|
17
|
-
// 1. Scan for shared directories
|
|
18
|
-
const sharedCandidates = [
|
|
19
|
-
path.join(cwd, "shared", "testing"),
|
|
20
|
-
path.join(cwd, "shared"),
|
|
21
|
-
path.join(cwd, "testing"),
|
|
22
|
-
];
|
|
23
|
-
for (const cand of sharedCandidates) {
|
|
24
|
-
if (fs.existsSync(cand) &&
|
|
25
|
-
(fs.existsSync(path.join(cand, "page-objects")) ||
|
|
26
|
-
fs.existsSync(path.join(cand, "support")) ||
|
|
27
|
-
fs.existsSync(path.join(cand, "steps")) ||
|
|
28
|
-
fs.existsSync(path.join(cand, "actions")) ||
|
|
29
|
-
fs.existsSync(path.join(cand, "fixtures")))) {
|
|
30
|
-
if (!scopes.some((s) => s.dir === cand)) {
|
|
31
|
-
scopes.push({ name: "shared", dir: cand, isShared: true });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
// 2. Scan recursively for feature e2e directories
|
|
36
|
-
function scanForFeatures(dir, depth) {
|
|
37
|
-
if (depth <= 0 || !fs.existsSync(dir))
|
|
38
|
-
return;
|
|
39
|
-
const base = path.basename(dir);
|
|
40
|
-
if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
|
|
41
|
-
return;
|
|
42
|
-
if (base === "e2e" || base === "tests" || base === "specs") {
|
|
43
|
-
const featureDir = path.dirname(dir);
|
|
44
|
-
const featureName = path.basename(featureDir);
|
|
45
|
-
if (!scopes.some((s) => s.dir === dir)) {
|
|
46
|
-
scopes.push({ name: featureName, dir: dir, isShared: false });
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
52
|
-
for (const entry of entries) {
|
|
53
|
-
if (entry.isDirectory()) {
|
|
54
|
-
scanForFeatures(path.join(dir, entry.name), depth - 1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch { }
|
|
59
|
-
}
|
|
60
|
-
scanForFeatures(cwd, 6);
|
|
61
|
-
return scopes;
|
|
62
|
-
}
|
|
63
|
-
static getPageObjectsDirsForScope(scopeDir, sharedDir) {
|
|
64
|
-
const dirs = [];
|
|
65
|
-
function scanDir(dir, maxDepth) {
|
|
66
|
-
if (maxDepth <= 0 || !fs.existsSync(dir))
|
|
67
|
-
return;
|
|
68
|
-
const base = path.basename(dir);
|
|
69
|
-
if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
|
|
70
|
-
return;
|
|
71
|
-
if (base === "page-objects" || base === "pageobjects") {
|
|
72
|
-
if (!dirs.includes(dir))
|
|
73
|
-
dirs.push(dir);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
78
|
-
for (const entry of entries) {
|
|
79
|
-
if (entry.isDirectory()) {
|
|
80
|
-
scanDir(path.join(dir, entry.name), maxDepth - 1);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
catch { }
|
|
85
|
-
}
|
|
86
|
-
if (!sharedDir && (fs.existsSync(path.join(scopeDir, "pnpm-workspace.yaml")) || fs.existsSync(path.join(scopeDir, "nx.json")))) {
|
|
87
|
-
scanDir(scopeDir, 6);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
const candidates = [
|
|
91
|
-
path.join(scopeDir, "page-objects"),
|
|
92
|
-
path.join(scopeDir, "pageobjects"),
|
|
93
|
-
];
|
|
94
|
-
if (sharedDir) {
|
|
95
|
-
candidates.push(path.join(sharedDir, "page-objects"));
|
|
96
|
-
candidates.push(path.join(sharedDir, "pageobjects"));
|
|
97
|
-
}
|
|
98
|
-
for (const c of candidates) {
|
|
99
|
-
if (fs.existsSync(c) && !dirs.includes(c))
|
|
100
|
-
dirs.push(c);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return dirs;
|
|
104
|
-
}
|
|
105
|
-
static getEntityDirsForScope(entityType, scopeDir, sharedDir) {
|
|
106
|
-
const dirs = [];
|
|
107
|
-
function scanDir(dir, maxDepth) {
|
|
108
|
-
if (maxDepth <= 0 || !fs.existsSync(dir))
|
|
109
|
-
return;
|
|
110
|
-
const base = path.basename(dir);
|
|
111
|
-
if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
|
|
112
|
-
return;
|
|
113
|
-
if (base === entityType) {
|
|
114
|
-
if (!dirs.includes(dir))
|
|
115
|
-
dirs.push(dir);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
120
|
-
for (const entry of entries) {
|
|
121
|
-
if (entry.isDirectory()) {
|
|
122
|
-
scanDir(path.join(dir, entry.name), maxDepth - 1);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
catch { }
|
|
127
|
-
}
|
|
128
|
-
if (!sharedDir && (fs.existsSync(path.join(scopeDir, "pnpm-workspace.yaml")) || fs.existsSync(path.join(scopeDir, "nx.json")))) {
|
|
129
|
-
scanDir(scopeDir, 6);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
const candidates = [
|
|
133
|
-
path.join(scopeDir, entityType),
|
|
134
|
-
path.join(scopeDir, "support", entityType),
|
|
135
|
-
path.join(scopeDir, "support", "testing", entityType),
|
|
136
|
-
];
|
|
137
|
-
if (sharedDir) {
|
|
138
|
-
candidates.push(path.join(sharedDir, entityType));
|
|
139
|
-
candidates.push(path.join(sharedDir, "support", entityType));
|
|
140
|
-
candidates.push(path.join(sharedDir, "support", "testing", entityType));
|
|
141
|
-
}
|
|
142
|
-
for (const c of candidates) {
|
|
143
|
-
if (fs.existsSync(c) && !dirs.includes(c))
|
|
144
|
-
dirs.push(c);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return dirs;
|
|
148
|
-
}
|
|
149
|
-
static generateDeclarationsForScope(cwd, outputTypesDir, scopeDir, sharedDir) {
|
|
150
|
-
const poDirs = this.getPageObjectsDirsForScope(scopeDir, sharedDir);
|
|
151
|
-
const actionsDirs = this.getEntityDirsForScope("actions", scopeDir, sharedDir);
|
|
152
|
-
const stepsDirs = this.getEntityDirsForScope("steps", scopeDir, sharedDir);
|
|
153
|
-
const platforms = ["web", "android", "ios", "mobile", "common"];
|
|
154
|
-
const results = {};
|
|
155
|
-
for (const platform of platforms) {
|
|
156
|
-
const hierarchy = PLATFORM_HIERARCHY[platform];
|
|
157
|
-
let content = `// Auto-generated ambient declarations for TestSpectra [${platform.toUpperCase()}]\n`;
|
|
158
|
-
content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
|
|
159
|
-
content += `/// <reference types="@wdio/globals/types" />\n`;
|
|
160
|
-
content += `/// <reference types="@wdio/mocha-framework" />\n`;
|
|
161
|
-
content += `/// <reference types="@testspectra/matchers" />\n`;
|
|
162
|
-
content += `/// <reference path="./fixtures.d.ts" />\n\n`;
|
|
163
|
-
// 1. Page Objects Global Declarations
|
|
164
|
-
const declaredPOMs = new Set();
|
|
165
|
-
for (const poDir of poDirs) {
|
|
166
|
-
if (!fs.existsSync(poDir))
|
|
167
|
-
continue;
|
|
168
|
-
const poEntries = fs.readdirSync(poDir, { withFileTypes: true });
|
|
169
|
-
for (const entry of poEntries) {
|
|
170
|
-
if (entry.name.startsWith("."))
|
|
171
|
-
continue;
|
|
172
|
-
if (entry.isDirectory()) {
|
|
173
|
-
const className = entry.name;
|
|
174
|
-
if (declaredPOMs.has(className))
|
|
175
|
-
continue;
|
|
176
|
-
const classFolder = path.join(poDir, className);
|
|
177
|
-
let matchedFile = null;
|
|
178
|
-
for (const stem of hierarchy) {
|
|
179
|
-
const candidates = [
|
|
180
|
-
path.join(classFolder, `${stem}.ts`),
|
|
181
|
-
path.join(classFolder, `${stem}.page.ts`),
|
|
182
|
-
path.join(classFolder, `${stem}.po.ts`),
|
|
183
|
-
path.join(classFolder, "index.ts"),
|
|
184
|
-
path.join(classFolder, "common.ts"),
|
|
185
|
-
];
|
|
186
|
-
for (const cand of candidates) {
|
|
187
|
-
if (fs.existsSync(cand)) {
|
|
188
|
-
matchedFile = path.relative(outputTypesDir, cand).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
189
|
-
if (!matchedFile.startsWith("."))
|
|
190
|
-
matchedFile = `./${matchedFile}`;
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (matchedFile)
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
if (matchedFile) {
|
|
198
|
-
content += `declare const ${className}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
199
|
-
declaredPOMs.add(className);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
203
|
-
const parts = entry.name.split(".");
|
|
204
|
-
const className = parts[0];
|
|
205
|
-
if (declaredPOMs.has(className))
|
|
206
|
-
continue;
|
|
207
|
-
let isMatch = false;
|
|
208
|
-
if (parts.length === 2) {
|
|
209
|
-
// E.g. ProfilePage.ts (applies to all platforms)
|
|
210
|
-
isMatch = true;
|
|
211
|
-
}
|
|
212
|
-
else if (parts.length > 2) {
|
|
213
|
-
// E.g. ProfilePage.web.ts or ProfilePage.android.ts
|
|
214
|
-
const stem = parts[1];
|
|
215
|
-
isMatch = hierarchy.includes(stem);
|
|
216
|
-
}
|
|
217
|
-
if (isMatch) {
|
|
218
|
-
let rel = path.relative(outputTypesDir, path.join(poDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
219
|
-
if (!rel.startsWith("."))
|
|
220
|
-
rel = `./${rel}`;
|
|
221
|
-
content += `declare const ${className}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
222
|
-
declaredPOMs.add(className);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// 2. Built-in Spectra Static Methods & Custom Actions
|
|
228
|
-
content += `interface TestSpectraCustomActions {\n`;
|
|
229
|
-
const declaredActions = new Set();
|
|
230
|
-
for (const actionsDir of actionsDirs) {
|
|
231
|
-
if (!fs.existsSync(actionsDir))
|
|
232
|
-
continue;
|
|
233
|
-
const actionEntries = fs.readdirSync(actionsDir, { withFileTypes: true });
|
|
234
|
-
for (const entry of actionEntries) {
|
|
235
|
-
if (entry.name.startsWith("."))
|
|
236
|
-
continue;
|
|
237
|
-
if (entry.isDirectory()) {
|
|
238
|
-
const actionName = entry.name;
|
|
239
|
-
if (declaredActions.has(actionName))
|
|
240
|
-
continue;
|
|
241
|
-
const actionFolder = path.join(actionsDir, actionName);
|
|
242
|
-
let matchedFile = null;
|
|
243
|
-
for (const stem of hierarchy) {
|
|
244
|
-
const cand1 = path.join(actionFolder, `${stem}.action.ts`);
|
|
245
|
-
const cand2 = path.join(actionFolder, `${stem}.ts`);
|
|
246
|
-
if (fs.existsSync(cand1)) {
|
|
247
|
-
matchedFile = path.relative(outputTypesDir, cand1).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
else if (fs.existsSync(cand2)) {
|
|
251
|
-
matchedFile = path.relative(outputTypesDir, cand2).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (matchedFile) {
|
|
256
|
-
if (!matchedFile.startsWith("."))
|
|
257
|
-
matchedFile = `./${matchedFile}`;
|
|
258
|
-
const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
259
|
-
content += ` "${actionName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
260
|
-
if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
261
|
-
content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
262
|
-
}
|
|
263
|
-
declaredActions.add(actionName);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
267
|
-
const parts = entry.name.split(".");
|
|
268
|
-
const stem = parts.length > 2 ? parts[parts.length - 2] : parts[0];
|
|
269
|
-
const actionName = parts[0];
|
|
270
|
-
if (hierarchy.includes(stem) || hierarchy.includes(parts[1])) {
|
|
271
|
-
if (declaredActions.has(actionName))
|
|
272
|
-
continue;
|
|
273
|
-
let rel = path.relative(outputTypesDir, path.join(actionsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
274
|
-
if (!rel.startsWith("."))
|
|
275
|
-
rel = `./${rel}`;
|
|
276
|
-
const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
277
|
-
content += ` "${actionName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
278
|
-
if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
279
|
-
content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
280
|
-
}
|
|
281
|
-
declaredActions.add(actionName);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
content += `}\n`;
|
|
287
|
-
content += `type TestSpectraActions = import('@testspectra/matchers').SpectraStatic & TestSpectraCustomActions;\n`;
|
|
288
|
-
content += `declare const Spectra: TestSpectraActions;\n\n`;
|
|
289
|
-
// 3. Built-in Steps Declaration
|
|
290
|
-
content += `interface TestSpectraSteps {\n`;
|
|
291
|
-
const declaredSteps = new Set();
|
|
292
|
-
for (const stepsDir of stepsDirs) {
|
|
293
|
-
if (!fs.existsSync(stepsDir))
|
|
294
|
-
continue;
|
|
295
|
-
const stepEntries = fs.readdirSync(stepsDir, { withFileTypes: true });
|
|
296
|
-
for (const entry of stepEntries) {
|
|
297
|
-
if (entry.name.startsWith("."))
|
|
298
|
-
continue;
|
|
299
|
-
if (entry.isDirectory()) {
|
|
300
|
-
const stepName = entry.name;
|
|
301
|
-
if (declaredSteps.has(stepName))
|
|
302
|
-
continue;
|
|
303
|
-
const stepFolder = path.join(stepsDir, stepName);
|
|
304
|
-
let matchedFile = null;
|
|
305
|
-
for (const stem of hierarchy) {
|
|
306
|
-
const cand1 = path.join(stepFolder, `${stem}.step.ts`);
|
|
307
|
-
const cand2 = path.join(stepFolder, `${stem}.ts`);
|
|
308
|
-
if (fs.existsSync(cand1)) {
|
|
309
|
-
matchedFile = path.relative(outputTypesDir, cand1).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
else if (fs.existsSync(cand2)) {
|
|
313
|
-
matchedFile = path.relative(outputTypesDir, cand2).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
if (matchedFile) {
|
|
318
|
-
if (!matchedFile.startsWith("."))
|
|
319
|
-
matchedFile = `./${matchedFile}`;
|
|
320
|
-
const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
321
|
-
content += ` "${stepName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
322
|
-
if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
323
|
-
content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
324
|
-
}
|
|
325
|
-
declaredSteps.add(stepName);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
329
|
-
const parts = entry.name.split(".");
|
|
330
|
-
const stem = parts.length > 2 ? parts[parts.length - 2] : parts[0];
|
|
331
|
-
const stepName = parts[0];
|
|
332
|
-
if (hierarchy.includes(stem) || hierarchy.includes(parts[1])) {
|
|
333
|
-
if (declaredSteps.has(stepName))
|
|
334
|
-
continue;
|
|
335
|
-
let rel = path.relative(outputTypesDir, path.join(stepsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
336
|
-
if (!rel.startsWith("."))
|
|
337
|
-
rel = `./${rel}`;
|
|
338
|
-
const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
339
|
-
content += ` "${stepName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
340
|
-
if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
341
|
-
content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
342
|
-
}
|
|
343
|
-
declaredSteps.add(stepName);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
content += `}\n`;
|
|
349
|
-
content += `declare const Step: TestSpectraSteps;\n\n`;
|
|
350
|
-
results[platform] = content;
|
|
351
|
-
}
|
|
352
|
-
return results;
|
|
353
|
-
}
|
|
354
|
-
static generateFixturesDeclarationForScope(cwd, outputTypesDir, scopeDir, sharedDir) {
|
|
355
|
-
const fixturesDirs = this.getEntityDirsForScope("fixtures", scopeDir, sharedDir);
|
|
356
|
-
let content = `// Auto-generated ambient fixture declarations for TestSpectra\n`;
|
|
357
|
-
content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
|
|
358
|
-
content += `interface TestSpectraFixtures {\n`;
|
|
359
|
-
const declaredFixtures = new Set();
|
|
360
|
-
function scanFixtureDir(dir) {
|
|
361
|
-
if (!fs.existsSync(dir))
|
|
362
|
-
return;
|
|
363
|
-
try {
|
|
364
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
365
|
-
for (const entry of entries) {
|
|
366
|
-
if (entry.name.startsWith("."))
|
|
367
|
-
continue;
|
|
368
|
-
if (entry.isDirectory()) {
|
|
369
|
-
scanFixtureDir(path.join(dir, entry.name));
|
|
370
|
-
}
|
|
371
|
-
else if (entry.isFile()) {
|
|
372
|
-
const parsed = path.parse(entry.name);
|
|
373
|
-
const varName = parsed.name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
374
|
-
if (declaredFixtures.has(varName))
|
|
375
|
-
continue;
|
|
376
|
-
if (entry.name.endsWith(".json")) {
|
|
377
|
-
let rel = path.relative(outputTypesDir, path.join(dir, entry.name)).replace(/\\/g, "/");
|
|
378
|
-
if (!rel.startsWith("."))
|
|
379
|
-
rel = `./${rel}`;
|
|
380
|
-
content += ` readonly ${varName}: typeof import('${rel}');\n`;
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
content += ` readonly ${varName}: string;\n`;
|
|
384
|
-
}
|
|
385
|
-
declaredFixtures.add(varName);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
catch { }
|
|
390
|
-
}
|
|
391
|
-
for (const fixturesDir of fixturesDirs) {
|
|
392
|
-
scanFixtureDir(fixturesDir);
|
|
393
|
-
}
|
|
394
|
-
content += `}\n`;
|
|
395
|
-
content += `declare const Fixture: TestSpectraFixtures;\n`;
|
|
396
|
-
return content;
|
|
397
|
-
}
|
|
398
|
-
static writeDeclarationFiles(cwd) {
|
|
399
|
-
const rootTypesDir = path.join(cwd, ".testspectra", "types");
|
|
400
|
-
if (!fs.existsSync(rootTypesDir)) {
|
|
401
|
-
fs.mkdirSync(rootTypesDir, { recursive: true });
|
|
402
|
-
}
|
|
403
|
-
const scopes = this.getEntityScopes(cwd);
|
|
404
|
-
const sharedScope = scopes.find((s) => s.isShared);
|
|
405
|
-
// 1. Generate Global / Standalone Root Types (or fallback)
|
|
406
|
-
const rootFixtures = this.generateFixturesDeclarationForScope(cwd, rootTypesDir, cwd);
|
|
407
|
-
fs.writeFileSync(path.join(rootTypesDir, "fixtures.d.ts"), rootFixtures, "utf-8");
|
|
408
|
-
const rootDeclarations = this.generateDeclarationsForScope(cwd, rootTypesDir, cwd);
|
|
409
|
-
for (const [platform, content] of Object.entries(rootDeclarations)) {
|
|
410
|
-
fs.writeFileSync(path.join(rootTypesDir, `${platform}.d.ts`), content, "utf-8");
|
|
411
|
-
}
|
|
412
|
-
// 2. Generate Scoped Types for Shared Library: .testspectra/types/shared/
|
|
413
|
-
if (sharedScope) {
|
|
414
|
-
const sharedTypesDir = path.join(rootTypesDir, "shared");
|
|
415
|
-
if (!fs.existsSync(sharedTypesDir))
|
|
416
|
-
fs.mkdirSync(sharedTypesDir, { recursive: true });
|
|
417
|
-
const sharedFixtures = this.generateFixturesDeclarationForScope(cwd, sharedTypesDir, sharedScope.dir);
|
|
418
|
-
fs.writeFileSync(path.join(sharedTypesDir, "fixtures.d.ts"), sharedFixtures, "utf-8");
|
|
419
|
-
// Shared scope has NO feature dependencies (only shared itself)
|
|
420
|
-
const sharedDeclarations = this.generateDeclarationsForScope(cwd, sharedTypesDir, sharedScope.dir);
|
|
421
|
-
for (const [platform, content] of Object.entries(sharedDeclarations)) {
|
|
422
|
-
fs.writeFileSync(path.join(sharedTypesDir, `${platform}.d.ts`), content, "utf-8");
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
// 3. Generate Scoped Types for Feature Modules: .testspectra/types/features/<featureName>/
|
|
426
|
-
const featureScopes = scopes.filter((s) => !s.isShared);
|
|
427
|
-
for (const feature of featureScopes) {
|
|
428
|
-
const featureTypesDir = path.join(rootTypesDir, "features", feature.name);
|
|
429
|
-
if (!fs.existsSync(featureTypesDir))
|
|
430
|
-
fs.mkdirSync(featureTypesDir, { recursive: true });
|
|
431
|
-
const featureFixtures = this.generateFixturesDeclarationForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
|
|
432
|
-
fs.writeFileSync(path.join(featureTypesDir, "fixtures.d.ts"), featureFixtures, "utf-8");
|
|
433
|
-
// Feature scope gets (feature + shared) ONLY
|
|
434
|
-
const featureDeclarations = this.generateDeclarationsForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
|
|
435
|
-
for (const [platform, content] of Object.entries(featureDeclarations)) {
|
|
436
|
-
fs.writeFileSync(path.join(featureTypesDir, `${platform}.d.ts`), content, "utf-8");
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// Universal ambient index
|
|
440
|
-
const universalPath = path.join(rootTypesDir, "index.d.ts");
|
|
441
|
-
fs.writeFileSync(universalPath, `/// <reference path="./common.d.ts" />\n`, "utf-8");
|
|
442
|
-
}
|
|
443
|
-
}
|
|
1
|
+
export * from '../generator/type-generator.js';
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import http from
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import { fileURLToPath } from
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
5
|
const MIME_TYPES = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
6
|
+
'.html': 'text/html',
|
|
7
|
+
'.js': 'application/javascript',
|
|
8
|
+
'.css': 'text/css',
|
|
9
|
+
'.json': 'application/json',
|
|
10
|
+
'.png': 'image/png',
|
|
11
|
+
'.jpg': 'image/jpeg',
|
|
12
|
+
'.gif': 'image/gif',
|
|
13
|
+
'.svg': 'image/svg+xml',
|
|
14
|
+
'.ico': 'image/x-icon',
|
|
15
|
+
'.woff': 'font/woff',
|
|
16
|
+
'.woff2': 'font/woff2',
|
|
17
|
+
'.ttf': 'font/ttf',
|
|
18
18
|
};
|
|
19
19
|
export class DemoServer {
|
|
20
20
|
server = null;
|
|
@@ -26,14 +26,14 @@ export class DemoServer {
|
|
|
26
26
|
const __filename = fileURLToPath(import.meta.url);
|
|
27
27
|
const __dirname = path.dirname(__filename);
|
|
28
28
|
const candidates = [
|
|
29
|
-
path.resolve(__dirname,
|
|
30
|
-
path.resolve(__dirname,
|
|
31
|
-
path.resolve(__dirname,
|
|
32
|
-
path.resolve(__dirname,
|
|
33
|
-
path.resolve(__dirname,
|
|
29
|
+
path.resolve(__dirname, '../../demo-app'),
|
|
30
|
+
path.resolve(__dirname, '../demo-app'),
|
|
31
|
+
path.resolve(__dirname, '../../../demo-app'),
|
|
32
|
+
path.resolve(__dirname, '../../../apps/demo-web-app/dist'),
|
|
33
|
+
path.resolve(__dirname, '../../../../apps/demo-web-app/dist'),
|
|
34
34
|
];
|
|
35
35
|
for (const cand of candidates) {
|
|
36
|
-
if (fs.existsSync(cand) && fs.existsSync(path.join(cand,
|
|
36
|
+
if (fs.existsSync(cand) && fs.existsSync(path.join(cand, 'index.html'))) {
|
|
37
37
|
return cand;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -46,32 +46,32 @@ export class DemoServer {
|
|
|
46
46
|
}
|
|
47
47
|
return new Promise((resolve, reject) => {
|
|
48
48
|
this.server = http.createServer((req, res) => {
|
|
49
|
-
const reqUrl = req.url ? req.url.split(
|
|
50
|
-
let safePath = path.normalize(reqUrl).replace(/^(\.\.[
|
|
51
|
-
if (safePath ===
|
|
52
|
-
safePath =
|
|
49
|
+
const reqUrl = req.url ? req.url.split('?')[0] : '/';
|
|
50
|
+
let safePath = path.normalize(reqUrl).replace(/^(\.\.[/\\])+/, '');
|
|
51
|
+
if (safePath === '/' || safePath === '\\')
|
|
52
|
+
safePath = '/index.html';
|
|
53
53
|
let filePath = path.join(demoDir, safePath);
|
|
54
54
|
// Fallback to index.html for SPA routes
|
|
55
55
|
if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
|
|
56
|
-
filePath = path.join(demoDir,
|
|
56
|
+
filePath = path.join(demoDir, 'index.html');
|
|
57
57
|
}
|
|
58
58
|
const ext = path.extname(filePath).toLowerCase();
|
|
59
|
-
const contentType = MIME_TYPES[ext] ||
|
|
59
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
60
60
|
try {
|
|
61
61
|
const content = fs.readFileSync(filePath);
|
|
62
62
|
res.writeHead(200, {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
'Content-Type': contentType,
|
|
64
|
+
'Access-Control-Allow-Origin': '*',
|
|
65
65
|
});
|
|
66
66
|
res.end(content);
|
|
67
67
|
}
|
|
68
68
|
catch {
|
|
69
|
-
res.writeHead(404, {
|
|
70
|
-
res.end(
|
|
69
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
70
|
+
res.end('Not Found');
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
|
-
this.server.on(
|
|
74
|
-
if (err.code ===
|
|
73
|
+
this.server.on('error', (err) => {
|
|
74
|
+
if (err.code === 'EADDRINUSE') {
|
|
75
75
|
// Port already in use (e.g. user already running server)
|
|
76
76
|
resolve({ url: `http://localhost:${this.port}`, port: this.port });
|
|
77
77
|
}
|
|
@@ -79,13 +79,16 @@ export class DemoServer {
|
|
|
79
79
|
reject(err);
|
|
80
80
|
}
|
|
81
81
|
});
|
|
82
|
-
this.server.listen(this.port,
|
|
82
|
+
this.server.listen(this.port, '0.0.0.0', () => {
|
|
83
83
|
resolve({ url: `http://localhost:${this.port}`, port: this.port });
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
async stop() {
|
|
88
88
|
if (this.server) {
|
|
89
|
+
if (typeof this.server.closeAllConnections === 'function') {
|
|
90
|
+
this.server.closeAllConnections();
|
|
91
|
+
}
|
|
89
92
|
return new Promise((resolve) => {
|
|
90
93
|
this.server?.close(() => {
|
|
91
94
|
this.server = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testspectra/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "TestSpectra Zero-Config Cross-Platform Test Runner CLI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,32 +16,40 @@
|
|
|
16
16
|
"spectra": "./bin/spectra.js",
|
|
17
17
|
"testspectra": "./bin/spectra.js"
|
|
18
18
|
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsc && node scripts/copy-demo.js",
|
|
21
|
-
"watch": "tsc -w",
|
|
22
|
-
"prepublishOnly": "npm run build"
|
|
23
|
-
},
|
|
24
19
|
"files": [
|
|
25
20
|
"dist",
|
|
26
21
|
"bin",
|
|
27
22
|
"templates",
|
|
28
23
|
"demo-app",
|
|
29
|
-
"README.md"
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE.md"
|
|
30
26
|
],
|
|
31
27
|
"dependencies": {
|
|
32
28
|
"@clack/prompts": "^1.7.0",
|
|
33
|
-
"@testspectra/matchers": "^1.0.70",
|
|
34
29
|
"chalk": "^5.3.0",
|
|
35
30
|
"commander": "^12.1.0",
|
|
36
31
|
"dotenv": "^16.4.5",
|
|
37
32
|
"ora": "^8.0.1",
|
|
38
|
-
"zod": "^3.23.8"
|
|
33
|
+
"zod": "^3.23.8",
|
|
34
|
+
"@testspectra/matchers": "1.1.0"
|
|
39
35
|
},
|
|
40
36
|
"devDependencies": {
|
|
41
37
|
"@types/node": "^20.14.0",
|
|
38
|
+
"@wdio/globals": "^9.24.0",
|
|
39
|
+
"@wdio/mocha-framework": "^9.24.0",
|
|
40
|
+
"@wdio/types": "^9.24.0",
|
|
42
41
|
"esbuild": "^0.28.2",
|
|
43
|
-
"typescript": "^5.
|
|
42
|
+
"typescript": "^5.6.3",
|
|
43
|
+
"vitest": "^2.1.8"
|
|
44
44
|
},
|
|
45
45
|
"type": "module",
|
|
46
|
-
"license": "
|
|
47
|
-
|
|
46
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build:rust": "node scripts/ensure-runner-binary.js",
|
|
49
|
+
"prebuild": "node scripts/ensure-runner-binary.js",
|
|
50
|
+
"build": "tsc && node scripts/copy-demo.js",
|
|
51
|
+
"watch": "tsc -w",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"test:watch": "vitest"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# CLAUDE.md - TestSpectra Framework Conventions
|
|
2
2
|
|
|
3
3
|
## Commands
|
|
4
|
-
- Run all Web tests: `pnpm spectra run -t web`
|
|
5
|
-
- Run
|
|
6
|
-
- Run
|
|
4
|
+
- Run all Web tests: `pnpm test:web` (or `spectra run -t web`)
|
|
5
|
+
- Run all Mobile tests: `pnpm test:mobile` (or `spectra run -t mobile`)
|
|
6
|
+
- Run Android tests: `pnpm test:android` (or `spectra run -t android`)
|
|
7
|
+
- Run iOS tests: `pnpm test:ios` (or `spectra run -t ios`)
|
|
8
|
+
- Run specific spec: `spectra run "specs/Playground/TC-0001-form-controls/mobile.test.ts" -t mobile`
|
|
7
9
|
- Type checking: `pnpm type-check` (or `tsc -b`)
|
|
8
|
-
- Preflight doctor: `pnpm spectra doctor`
|
|
9
|
-
-
|
|
10
|
+
- Preflight doctor: `pnpm doctor` (or `spectra doctor`)
|
|
11
|
+
- Sync ambient types: `pnpm sync-types`
|