Skip to content

Conversation

@LayZeeDK
Copy link
Member

@LayZeeDK LayZeeDK commented Nov 1, 2025

Parent Issue: #339 - Optimize e2e test execution time (shared workspace architecture)
Base Branch: test/319-adopt-new-end-to-end-test-plan

Summary

Implements Phases 1-5 of the e2e test optimization plan to reduce test execution time by ~88% through shared workspace architecture, async infrastructure improvements, and descriptive library naming. Includes timeout protection and Nx 19 compatibility fixes.

Current Status: Infrastructure complete (Phases 1, 3, 3b, 3c, 4, 5) ✅ | Nx 19 compatibility fixed ✅ | Scenario implementation in progress (Phase 2: 2/14) ⏳

Implementation Checklist

  • Phase 1: Add create-nx-workspace to devDependencies ✅ Complete
  • Phase 2: Implement shared workspace pattern ⚠️ Partially Complete (2/14 scenarios)
  • Phase 3: Add async infrastructure ✅ Complete
  • Phase 3b: Enable parallel library generation ✅ Complete
  • Phase 3c: Add timeout protection ✅ Complete
  • Phase 3d: Fix Nx 19 compatibility ✅ Complete
  • Phase 4: Optimize INSTALL scenario ✅ Complete
  • Phase 5: Add libPrefix option for descriptive library names ✅ Complete

Changes

Phase 1: Add create-nx-workspace to devDependencies ✅

  • Add create-nx-workspace to devDependencies
  • Update workspace-scaffold.ts to use --prefer-offline
  • Eliminates ~5-10s npx download time

Phase 2: Implement Shared Workspace Pattern ⚠️ (2/14 scenarios)

  • ✅ Create shared workspace with 37 libraries
  • ✅ Install plugin once
  • ✅ Define LIBRARY_ALLOCATION map
  • ✅ 2/14 scenarios implemented (MOVE-SMALL, APP-TO-LIB)
  • ⏸️ 12/14 scenarios remain as stubs

Phase 3: Async Infrastructure ✅

  • ✅ execAsync utility
  • ✅ Batched library generation
  • ✅ All minimal flags (--unitTestRunner=none --bundler=none --skipFormat)

Phase 3b: Enable Parallel Library Generation ✅

Problem: Nx generators race to modify tsconfig.base.json, causing corruption

Solution: Two-phase approach

  1. Generate libraries in parallel (CONCURRENCY=4)
  2. Rebuild tsconfig.base.json paths after generation

Performance:

  • Before: ~93s (sequential)
  • After: ~26s (parallel + rebuild)
  • Savings: ~67s (~72% faster) 🚀

Phase 3c: Add Timeout Protection ✅

Problem: Parallel Nx generators can hang indefinitely, blocking test execution

Solution: Multi-layer timeout protection

  1. Add timeout to execAsync() with process cleanup (120s default)
  2. Implement graceful SIGTERM then SIGKILL for hung processes
  3. Add global testTimeout (120s) to Jest config
  4. Wrap registry operations with timeout protection

Key Fix: The execAsync() timeout actively kills hung spawned processes, preventing infinite waits when parallel generators stall.

Benefits:

  • Tests complete in ~137s instead of hanging indefinitely
  • Clear error messages when operations timeout
  • Automatic process cleanup prevents resource leaks
  • Future-proof against similar timeout issues

Phase 3d: Fix Nx 19 Compatibility ✅

Problem: Redundant --directory flag caused Nx 19 to exit with code 1

Root Cause: When generating nx g @nx/js:library lib-a --directory lib-a, Nx 19 treats this as deprecated syntax and exits with error: "In Nx 20, generating projects will no longer derive the name and root."

Solution: Remove redundant --directory flag

  • Nx automatically creates library "lib-a" in directory "lib-a" by default
  • The --directory flag is only needed when the directory differs from the name
  • This fixes all 17 CI test failures

Before: nx g @nx/js:library lib-a --directory lib-a ❌ Exit code 1
After: nx g @nx/js:library lib-a ✅ Works correctly

Phase 4: INSTALL Scenario Optimization ✅

  • Add --prefer-offline flag
  • Benefits from Phase 3 infrastructure
  • Execution time: ~38s

Phase 5: Descriptive Library Names ✅

  • Add libPrefix option to WorkspaceConfig
  • Backward compatible (default: 'lib')

Performance Impact

Before: ~1,785s (~30 minutes)

After (current): ~127s (~2.1 minutes) ✅ ~93% faster

Projected (all scenarios): ~211s (~3.5 minutes) ✅ ~88% faster

Test Results

  • ✅ Infrastructure tests passing (REG-START, PUBLISH, INSTALL)
  • ✅ No timeout issues - tests complete in ~137s
  • ✅ No Nx 19 deprecation warnings
  • ✅ All 71 unit tests passed

Commits

  1. Phase 1: Add create-nx-workspace to devDependencies
  2. Phase 2: Implement shared workspace pattern (2/14)
  3. Phase 3: Add async infrastructure
  4. Phase 4: Optimize INSTALL scenario
  5. Phase 5: Add libPrefix option
  6. Phase 3: Add --skipFormat flag
  7. Phase 3b: Enable parallel generation with path rebuild
  8. Fix: Add --directory flag for Nx 19 (reverted in commit 10)
  9. Phase 3c: Add timeout protection
  10. Fix: Remove redundant --directory flag for Nx 19 compatibility

Remaining Work

Related

…space creation

- Add create-nx-workspace@^19.8.14 to devDependencies
- Update workspace-scaffold.ts to use npx --prefer-offline
- Eliminates ~5-10s npx download time per workspace creation
- Ensures consistent version across CI and local development

Part of #339 Phase 1
- Create single shared workspace in beforeAll with 37 pre-generated libraries
- Install plugin once instead of per-scenario
- Define LIBRARY_ALLOCATION map assigning unique libraries to each scenario
- Update MOVE-SMALL and APP-TO-LIB to use shared workspace
- Add cleanup in afterAll for shared workspace
- Reduce test timeouts from 120s to 60s (no workspace creation needed)

Performance improvement:
- MOVE-SMALL: 120s → ~8s (93% faster)
- APP-TO-LIB: 120s → ~8s (93% faster)
- Eliminates 14+ redundant workspace creations (~7-14 minutes saved)

Part of #339 Phase 2
@LayZeeDK LayZeeDK linked an issue Nov 1, 2025 that may be closed by this pull request
23 tasks
Phase 3 implementation:
- Add execAsync utility for promise-based command execution
- Implement batched library generation with configurable concurrency
- Improve error reporting with full stdout/stderr capture

Current status:
- CONCURRENCY=1 (sequential) to avoid race conditions
- Multiple Nx generators modifying shared config files simultaneously
  causes file conflicts (tsconfig.base.json, nx.json)
- Infrastructure ready for parallel execution once locking is solved

Benefits:
- Cleaner async/await code structure
- Better error messages with full command output
- Batching logic in place for future optimization
- All 17 e2e tests passing

Future work:
- Investigate Nx generator file locking mechanisms
- Test CONCURRENCY > 1 with proper synchronization
- Potential speedup: 37 libs @ 3s = 111s → ~30-40s (60-70s saved)

Part of #339 Phase 3
Phase 4 implementation:
- Add documentation explaining INSTALL benefits from Phase 3 async infrastructure
- Add --prefer-offline flag to npm install (Phase 1 optimization)
- INSTALL automatically uses batched async library generation from Phase 3
- No code changes to workspace creation - benefits are automatic

Performance results:
- INSTALL scenario: ~38s execution time
- Benefits from:
  - Phase 1: --prefer-offline reduces npm download time
  - Phase 3: Async batched library generation infrastructure
  - create-nx-workspace cached from devDependencies

Testing:
- ✅ INSTALL scenario passed (38286ms)
- ✅ Workspace created with 2 libraries
- ✅ Plugin installed from local registry
- ✅ Generator registration verified
- ⚠️ Cleanup warning on Windows (non-critical file locking)

Part of #339 Phase 4
LayZeeDK and others added 7 commits November 1, 2025 13:23
- Remove legacy scenario files (app-to-lib.ts, move-small.ts) that caused lint errors
- Add spawn mock to workspace-scaffold.spec.ts to fix test failures
- Update library generator test to check spawn instead of execSync
- All 65 e2e-util tests now passing

Fixes lint error:
  Parsing error: app-to-lib.ts and move-small.ts were not found by the project service

Fixes test error:
  TypeError: Cannot read properties of undefined (reading 'stdout')

The issue was that Phase 3 introduced execAsync (using spawn) for library generation,
but tests only mocked execSync. Now properly mocking spawn with EventEmitter that
simulates successful process completion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…ures

The 'nul' file is a reserved device name on Windows (like CON, PRN, AUX).
Git cannot check out repositories containing files with these names on Windows,
causing CI failures with:
  error: invalid path 'nul'

Removed the file from the repository index to fix Windows CI compatibility.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Replace `as any` with `as unknown as ChildProcess` to satisfy TypeScript
strict type checking and ESLint rules. This properly types the mock child
process while maintaining test functionality.

Fixes lint error:
  error  Unexpected any. Specify a different type  @typescript-eslint/no-explicit-any

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Prevent accidental commits of the 'nul' file, which is a Windows reserved
device name (like CON, PRN, AUX, etc.) that cannot be checked out on Windows
systems, causing CI failures.

This ensures the file won't be tracked by git even if accidentally created
during development or testing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
… library names

Implements Phase 5 of the e2e optimization plan. Adds a configurable `libPrefix`
option to `WorkspaceConfig` that allows generating libraries with custom name
prefixes for improved debugging and clarity.

**Changes:**
- Add `libPrefix?: string` option to `WorkspaceConfig` interface (default: 'lib')
- Update library name generation logic to use configurable prefix
- Add 4 comprehensive tests for libPrefix functionality
  - Default behavior (lib-a, lib-b, lib-c)
  - Custom prefix (scenario-a, scenario-b, scenario-c)
  - Custom prefix with numeric suffixes (test-a1, test-b1)
  - Generator command validation with custom prefix

**Benefits:**
- Clearer library names when debugging (e.g., 'scenario-a' vs 'lib-a')
- No performance impact
- Backward compatible (defaults to 'lib' prefix)

**Example usage:**
```typescript
const workspace = await createWorkspace({
  libs: 5,
  libPrefix: 'scenario'  // Generates: scenario-a, scenario-b, ...
});
```

**Test Results:**
- All 69 tests passing (65 existing + 4 new)
- Lint passed
- Formatter applied

Related: #339 Phase 5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…timization

Completes Phase 3 acceptance criteria by adding the `--skipFormat` flag to library
generation commands. This reduces file I/O overhead during workspace creation.

**Changes:**
- Add `--skipFormat` flag to library generator command in workspace-scaffold.ts
- Update test expectations to verify --skipFormat is passed

**Clarification on flags:**
- ✅ `--unitTestRunner=none` - Prevents test file generation (equivalent to --skipTests)
- ✅ `--bundler=none` - Skips bundler configuration
- ✅ `--skipFormat` - Skips formatting generated files (NEW)
- ✅ `--no-interactive` - Non-interactive mode

Note: `--skipTests` flag doesn't exist for @nx/js:library generator. We already use
`--unitTestRunner=none` which accomplishes the same goal (no test files created).

**Phase 3 Status:**
- ✅ execAsync utility added
- ✅ Batched library generation implemented
- ✅ Error reporting with stdout/stderr
- ✅ All minimal generation flags applied
- ⏸️ CONCURRENCY=1 (parallel blocked by Nx race conditions)
- ⏸️ Performance gains pending CONCURRENCY > 1 (Phase 3b)

**Test Results:**
- All 69 e2e-util tests passing
- Lint passed

Related: #339 Phase 3

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…th rebuild

Resolves Nx race conditions by using a two-phase approach:
1. Generate libraries in parallel (CONCURRENCY=4)
2. Rebuild tsconfig.base.json paths after generation completes

**Problem:**
Multiple Nx generators running in parallel race to modify tsconfig.base.json,
causing file corruption where only the last write survives. This loses path
mappings for other libraries, breaking the build.

**Solution:**
Accept the race condition and fix it afterward. After all libraries are generated,
`rebuildTsConfigPaths()` deterministically reconstructs the entire paths section
by scanning what was actually created.

**Changes:**
- Add `rebuildTsConfigPaths()` function to workspace-scaffold.ts
- Update CONCURRENCY from 1 to 4 for parallel library generation
- Call `rebuildTsConfigPaths()` after library generation completes
- Call `rebuildTsConfigPaths()` again after app generation to include app path
- Add 2 comprehensive tests for path rebuild functionality

**Performance Improvement:**
- Before: 37 libs × 2.5s = ~93s (sequential, CONCURRENCY=1)
- After: 37 libs ÷ 4 × 2.5s + ~1s rebuild = ~26s (parallel)
- **Savings: ~67 seconds (~72% faster workspace setup)** 🚀

**Test Results:**
- All 71 tests passing (69 existing + 2 new)
- Lint passed
- Format applied

Completes Phase 3b optimization goals.

Related: #339 Phase 3b

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@LayZeeDK LayZeeDK changed the title test(e2e): optimize execution time with shared workspace pattern (Phases 1-2) test(e2e): optimize execution time with shared workspace pattern Nov 1, 2025
@LayZeeDK LayZeeDK marked this pull request as ready for review November 1, 2025 13:24
LayZeeDK and others added 5 commits November 1, 2025 14:30
…ation warning

Adds explicit --directory flag to library generator command to satisfy Nx 19
deprecation warning that was causing CI failures with exit code 1.

**Problem:**
Nx 19.8.x shows deprecation warning:
"In Nx 20, generating projects will no longer derive the name and root.
Please provide the exact project name and root in the future."

This warning causes the generator command to exit with code 1, failing tests.

**Solution:**
Add `--directory ${libName}` flag to explicitly specify the directory path,
which satisfies the deprecation warning and prepares for Nx 20 compatibility.

**Changes:**
- Add --directory flag to library generator command in workspace-scaffold.ts
- Update test to verify --directory flag is passed correctly

**Test Results:**
- All 71 e2e-util tests passing
- Lint passed
- Format applied

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Prevents E2E tests from hanging indefinitely when parallel Nx generators
stall. The parallel library generation (CONCURRENCY=4) introduced in d9ca597
exposed this issue when any spawned process would hang.

Key changes:
- Add timeout parameter to execAsync() with process cleanup (120s default)
- Implement graceful SIGTERM then SIGKILL for hung processes
- Add global testTimeout (120s) to Jest config with proper typing
- Reduce beforeAll timeout from 300s to 180s
- Wrap registry operations with timeout protection

Resolves timeout issues introduced by parallel execution optimization.

Related: #339

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…ion warning

The --directory flag was redundant when the directory name matched the
library name, causing Nx 19 to exit with code 1 and the deprecation warning:
"In Nx 20, generating projects will no longer derive the name and root."

When generating a library named "lib-a", Nx automatically creates it in a
directory called "lib-a" by default, so the --directory flag is unnecessary.

Fixes CI test failures where all 17 tests were failing with exit code 1.

Related: #339

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
…test

Updates test to match the implementation change in commit b20c276 where
the redundant --directory flag was removed from library generation.

The test was asserting that the command contained "--directory lib-a",
but this flag is no longer used since Nx automatically creates the
library in a directory matching its name.

Fixes CI test failure in workspace-scaffold.spec.ts:210.

Related: #339

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Prevents "Could not load plugin @nx/eslint/plugin" errors when generating
libraries in fresh workspaces. The ESLint plugin may not be fully installed
when parallel library generation attempts to use it.

Adding --linter=none:
- Skips ESLint setup during library generation
- Prevents race conditions with ESLint plugin installation
- Aligns with other minimal flags (--unitTestRunner=none, --bundler=none)
- Speeds up library generation (no ESLint configuration needed)

Fixes CI failure in INSTALL scenario where lib-b generation failed with
exit code 1 due to missing @nx/eslint/plugin.

Related: #339

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@LayZeeDK LayZeeDK merged commit 2e44fe1 into test/319-adopt-new-end-to-end-test-plan Nov 1, 2025
13 checks passed
@LayZeeDK LayZeeDK deleted the test/339-optimize-e2e-execution-time branch November 1, 2025 19:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize e2e test execution time (shared workspace architecture)

2 participants