@testspectra/cli 1.0.25 → 1.0.27
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/dist/commands/init.js +88 -21
- package/dist/types/generator.js +61 -12
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -415,7 +415,10 @@ export async function initCommand(options = {}) {
|
|
|
415
415
|
for (const e2eDir of featureE2eDirs) {
|
|
416
416
|
TypeGenerator.writeDeclarationFiles(e2eDir);
|
|
417
417
|
}
|
|
418
|
-
|
|
418
|
+
const sharedPoLine = `${sharedTestingRelPath}/page-objects`.padEnd(28, " ");
|
|
419
|
+
const sharedStepsLine = `${sharedTestingRelPath}/steps`.padEnd(28, " ");
|
|
420
|
+
const sharedActLine = `${sharedTestingRelPath}/actions`.padEnd(28, " ");
|
|
421
|
+
const sharedFixLine = `${sharedTestingRelPath}/fixtures`.padEnd(28, " ");
|
|
419
422
|
const archDocContent = `# TestSpectra Enterprise Monorepo Architecture
|
|
420
423
|
|
|
421
424
|
This workspace uses TestSpectra's **"Centralized Configuration, Distributed Implementation"** model for automated cross-platform testing.
|
|
@@ -431,7 +434,7 @@ This workspace uses TestSpectra's **"Centralized Configuration, Distributed Impl
|
|
|
431
434
|
- \`./pnpm-workspace.yaml\`: Monorepo package glob definitions.
|
|
432
435
|
- \`./tsconfig.json\`: Solution-style TypeScript orchestrator linking all distributed E2E projects and shared libraries.
|
|
433
436
|
|
|
434
|
-
### Centralized Shared Library (\`./${sharedTestingRelPath}\`)
|
|
437
|
+
### Centralized Shared Testing Library (\`./${sharedTestingRelPath}\`)
|
|
435
438
|
Provides cross-feature reusable test entities with **100% zero-import global resolution**:
|
|
436
439
|
- \`page-objects/\`: Shared Page Object models (e.g. NavigationBar, AppHeader).
|
|
437
440
|
- \`steps/\`: Cross-feature business step flows (e.g. \`Step.loginAsAdmin()\`).
|
|
@@ -442,45 +445,109 @@ Provides cross-feature reusable test entities with **100% zero-import global res
|
|
|
442
445
|
${featureE2eDirs.map((d) => {
|
|
443
446
|
const rel = path.relative(cwd, d);
|
|
444
447
|
const name = path.basename(path.dirname(d));
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
- \`
|
|
448
|
+
const e2eName = `${name}-${e2eFolderName}`;
|
|
449
|
+
return `- \`./${rel}\` (**${name}**, Nx Project: \`${e2eName}\`):
|
|
450
|
+
- \`specs/\`: Feature test cases (\`specs/<SuiteName>/<CaseId>/\` with \`web.test.ts\`, \`android.test.ts\`, \`ios.test.ts\`).
|
|
451
|
+
- \`page-objects/\`: Feature-specific Page Objects (e.g. \`LoginPage\`, \`ProductPage\`).
|
|
448
452
|
- \`project.json\`: Nx targets (\`e2e\`, \`type-check\`) supporting configurations (\`--configuration=android\`, \`--configuration=ios\`, \`--configuration=headless\`).
|
|
449
453
|
- \`tsconfig.json\`: Solution TypeScript config reference.`;
|
|
450
454
|
}).join("\n")}
|
|
451
455
|
|
|
452
456
|
---
|
|
453
457
|
|
|
454
|
-
## 2. Zero-Import Consumption Flow
|
|
458
|
+
## 2. Zero-Import Consumption Flow & Mechanics
|
|
455
459
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
TestSpectra completely eliminates boilerplate \`import\` statements across all test specs, steps, and page objects.
|
|
461
|
+
|
|
462
|
+
### How It Works:
|
|
463
|
+
1. **Centralized Ambient Aggregation**:
|
|
464
|
+
TestSpectra Language Service Plugin & CLI automatically scan:
|
|
465
|
+
- **Local Feature Entities**: \`<feature>/${e2eFolderName}/page-objects/\`, \`<feature>/${e2eFolderName}/steps/\`, \`<feature>/${e2eFolderName}/actions/\`
|
|
466
|
+
- **Shared Library Entities**: \`./${sharedTestingRelPath}/page-objects/\`, \`./${sharedTestingRelPath}/steps/\`, \`./${sharedTestingRelPath}/actions/\`, \`./${sharedTestingRelPath}/fixtures/\`
|
|
467
|
+
2. **Ambient Typing Generation**:
|
|
468
|
+
These are compiled into \`.testspectra/types/web.d.ts\`, \`android.d.ts\`, \`ios.d.ts\`, \`fixtures.d.ts\`.
|
|
469
|
+
3. **Instant IDE Autocomplete & Zero Clutter**:
|
|
470
|
+
Specs consume both local feature objects and shared library utilities globally.
|
|
471
|
+
|
|
472
|
+
### Code Example:
|
|
473
|
+
\`\`\`typescript
|
|
474
|
+
// modules/auth/e2e/specs/Auth/TC-AUTH-01/web.test.ts
|
|
475
|
+
// 🌟 NOTICE: 100% Zero-Import statement! Everything resolves globally.
|
|
476
|
+
|
|
477
|
+
describe("Authentication Flow", () => {
|
|
478
|
+
it("should navigate via shared header and login with local POM", async () => {
|
|
479
|
+
// 1. Consumed from Shared Library (${sharedTestingRelPath}/page-objects/NavigationBar)
|
|
480
|
+
await NavigationBar.goToLogin();
|
|
481
|
+
|
|
482
|
+
// 2. Consumed from Shared Library (${sharedTestingRelPath}/steps/loginAsAdmin)
|
|
483
|
+
await Step.loginAsAdmin();
|
|
484
|
+
|
|
485
|
+
// 3. Consumed from Local Feature POM (modules/auth/e2e/page-objects/LoginPage)
|
|
486
|
+
await LoginPage.flashAlert.shouldBeVisible();
|
|
487
|
+
await LoginPage.flashAlert.shouldContainText("Welcome");
|
|
488
|
+
|
|
489
|
+
// 4. Consumed from Shared Library Custom Action (${sharedTestingRelPath}/actions/dismissBanner)
|
|
490
|
+
await Spectra.dismissBanner();
|
|
491
|
+
});
|
|
492
|
+
});
|
|
464
493
|
\`\`\`
|
|
465
494
|
|
|
466
495
|
---
|
|
467
496
|
|
|
468
|
-
## 3.
|
|
497
|
+
## 3. How to Run Tests
|
|
498
|
+
|
|
499
|
+
You can execute tests either from the **Monorepo Root via Nx** or directly inside each **Feature Directory via CLI**:
|
|
500
|
+
|
|
501
|
+
### Option A: From Root Monorepo via Nx (Recommended for CI & Team Workflows)
|
|
469
502
|
|
|
470
503
|
\`\`\`bash
|
|
471
|
-
# Run
|
|
504
|
+
# 1. Run E2E for a SPECIFIC feature module (e.g. auth)
|
|
505
|
+
pnpm nx run auth-${e2eFolderName}:e2e
|
|
506
|
+
|
|
507
|
+
# 2. Run E2E for a specific feature on ANDROID or IOS
|
|
508
|
+
pnpm nx run auth-${e2eFolderName}:e2e --configuration=android
|
|
509
|
+
pnpm nx run auth-${e2eFolderName}:e2e --configuration=ios
|
|
510
|
+
|
|
511
|
+
# 3. Run E2E for a specific feature in HEADLESS browser mode
|
|
512
|
+
pnpm nx run auth-${e2eFolderName}:e2e --configuration=headless
|
|
513
|
+
|
|
514
|
+
# 4. Run ALL feature E2E suites across the entire monorepo in parallel
|
|
472
515
|
pnpm nx run-many -t e2e
|
|
473
516
|
|
|
474
|
-
# Run only E2E tests for features
|
|
517
|
+
# 5. Run only E2E tests for features modified in current Git branch / PR
|
|
475
518
|
pnpm nx affected -t e2e
|
|
519
|
+
\`\`\`
|
|
476
520
|
|
|
477
|
-
|
|
478
|
-
|
|
521
|
+
### Option B: From Inside the Feature Directory (Direct CLI)
|
|
522
|
+
|
|
523
|
+
\`\`\`bash
|
|
524
|
+
# Navigate to the feature e2e folder
|
|
525
|
+
cd ${featureE2eDirs.length > 0 ? path.relative(cwd, featureE2eDirs[0]).replace(/\\/g, "/") : `modules/auth/${e2eFolderName}`}
|
|
526
|
+
|
|
527
|
+
# Run all specs in this feature
|
|
528
|
+
pnpm spectra run
|
|
529
|
+
|
|
530
|
+
# Run a specific test case file
|
|
531
|
+
pnpm spectra run specs/Auth/TC-AUTH-01/web.test.ts
|
|
532
|
+
|
|
533
|
+
# Target Android device or emulator
|
|
534
|
+
pnpm spectra run --target android
|
|
479
535
|
|
|
480
|
-
# Run
|
|
536
|
+
# Run in headed / interactive browser mode
|
|
537
|
+
pnpm spectra run --no-headless
|
|
538
|
+
\`\`\`
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## 4. Type Checking & Validation
|
|
543
|
+
|
|
544
|
+
\`\`\`bash
|
|
545
|
+
# Type check all E2E projects via Nx
|
|
481
546
|
pnpm nx run-many -t type-check
|
|
482
|
-
|
|
547
|
+
|
|
548
|
+
# Or standard TypeScript project references build from root
|
|
483
549
|
pnpm type-check
|
|
550
|
+
# or: npx tsc -b
|
|
484
551
|
\`\`\`
|
|
485
552
|
`;
|
|
486
553
|
fs.writeFileSync(path.join(cwd, "ARCHITECTURE.md"), archDocContent, "utf-8");
|
package/dist/types/generator.js
CHANGED
|
@@ -19,12 +19,38 @@ export class TypeGenerator {
|
|
|
19
19
|
// Look for shared directories in monorepo root
|
|
20
20
|
let cur = cwd;
|
|
21
21
|
while (cur !== path.dirname(cur)) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const candidates = [
|
|
23
|
+
path.join(cur, "shared/testing/page-objects"),
|
|
24
|
+
path.join(cur, "shared/page-objects"),
|
|
25
|
+
path.join(cur, "packages/core/testing/page-objects"),
|
|
26
|
+
path.join(cur, "packages/shared/testing/page-objects"),
|
|
27
|
+
path.join(cur, "packages/testing/page-objects"),
|
|
28
|
+
path.join(cur, "core/testing/page-objects"),
|
|
29
|
+
];
|
|
30
|
+
for (const cand of candidates) {
|
|
31
|
+
if (fs.existsSync(cand) && !dirs.includes(cand))
|
|
32
|
+
dirs.push(cand);
|
|
33
|
+
}
|
|
34
|
+
// Also dynamically check tsconfig.json references if in root
|
|
35
|
+
const rootTsConfig = path.join(cur, "tsconfig.json");
|
|
36
|
+
if (fs.existsSync(rootTsConfig)) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(fs.readFileSync(rootTsConfig, "utf-8"));
|
|
39
|
+
if (Array.isArray(parsed.references)) {
|
|
40
|
+
for (const ref of parsed.references) {
|
|
41
|
+
if (ref.path) {
|
|
42
|
+
const poPath1 = path.resolve(cur, ref.path, "page-objects");
|
|
43
|
+
const poPath2 = path.resolve(cur, ref.path, "pageobjects");
|
|
44
|
+
if (fs.existsSync(poPath1) && !dirs.includes(poPath1))
|
|
45
|
+
dirs.push(poPath1);
|
|
46
|
+
if (fs.existsSync(poPath2) && !dirs.includes(poPath2))
|
|
47
|
+
dirs.push(poPath2);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
}
|
|
28
54
|
if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cur, "nx.json")))
|
|
29
55
|
break;
|
|
30
56
|
cur = path.dirname(cur);
|
|
@@ -38,12 +64,35 @@ export class TypeGenerator {
|
|
|
38
64
|
dirs.push(local);
|
|
39
65
|
let cur = cwd;
|
|
40
66
|
while (cur !== path.dirname(cur)) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
const candidates = [
|
|
68
|
+
path.join(cur, "shared/testing", entityType),
|
|
69
|
+
path.join(cur, "shared", entityType),
|
|
70
|
+
path.join(cur, "packages/core/testing", entityType),
|
|
71
|
+
path.join(cur, "packages/shared/testing", entityType),
|
|
72
|
+
path.join(cur, "packages/testing", entityType),
|
|
73
|
+
path.join(cur, "core/testing", entityType),
|
|
74
|
+
];
|
|
75
|
+
for (const cand of candidates) {
|
|
76
|
+
if (fs.existsSync(cand) && !dirs.includes(cand))
|
|
77
|
+
dirs.push(cand);
|
|
78
|
+
}
|
|
79
|
+
// Also dynamically check tsconfig.json references
|
|
80
|
+
const rootTsConfig = path.join(cur, "tsconfig.json");
|
|
81
|
+
if (fs.existsSync(rootTsConfig)) {
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(fs.readFileSync(rootTsConfig, "utf-8"));
|
|
84
|
+
if (Array.isArray(parsed.references)) {
|
|
85
|
+
for (const ref of parsed.references) {
|
|
86
|
+
if (ref.path) {
|
|
87
|
+
const entPath = path.resolve(cur, ref.path, entityType);
|
|
88
|
+
if (fs.existsSync(entPath) && !dirs.includes(entPath))
|
|
89
|
+
dirs.push(entPath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch { }
|
|
95
|
+
}
|
|
47
96
|
if (fs.existsSync(path.join(cur, "pnpm-workspace.yaml")) || fs.existsSync(path.join(cur, "nx.json")))
|
|
48
97
|
break;
|
|
49
98
|
cur = path.dirname(cur);
|