From b2d69dbe107721eca8dec03ad9204700058a608a Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:08:04 +0000 Subject: [PATCH] test: optimize and stabilize e2e tests - Generate test artifacts directly via asynchronous file writes in `tests/vitest/larger-project.ts` instead of executing 500 `ng generate` CLI child processes. - Remove redundant initial `ng test` execution in `tests/test/test-scripts.ts`. - Fix regex matching in `tests/vite/reuse-dep-optimization-cache.ts` to directly await `/dependencies optimized/`, eliminating race conditions and timeouts. - Add `--disable-dev-shm-usage` and `--disable-gpu` flags to Chromium launch arguments in `tests/utils/puppeteer.ts` for container and sandbox stability. Benchmark results: - `tests/vitest/larger-project`: 155.60s -> 42.34s (-113.26s / -72.8%) - `tests/test/test-scripts`: 12.62s -> 8.84s (-3.78s / -29.9%) - `tests/vite/reuse-dep-optimization-cache`: timeout/failure -> 12.53s (passed) --- tests/e2e/tests/test/test-scripts.ts | 4 - .../vite/reuse-dep-optimization-cache.ts | 15 +-- tests/e2e/tests/vitest/larger-project.ts | 109 +++++++++++++++--- tests/e2e/utils/puppeteer.ts | 2 +- 4 files changed, 99 insertions(+), 31 deletions(-) diff --git a/tests/e2e/tests/test/test-scripts.ts b/tests/e2e/tests/test/test-scripts.ts index 1537cdddf349..953ed52cfbd7 100644 --- a/tests/e2e/tests/test/test-scripts.ts +++ b/tests/e2e/tests/test/test-scripts.ts @@ -5,10 +5,6 @@ import { updateJsonFile } from '../../utils/project'; import { expectToFail } from '../../utils/utils'; export default async function () { - // TODO(architect): Delete this test. It is now in devkit/build-angular. - - await ng('test', '--watch=false'); - // prepare global scripts test files await writeMultipleFiles({ 'src/string-script.js': `globalThis.stringScriptGlobal = 'string-scripts.js';`, diff --git a/tests/e2e/tests/vite/reuse-dep-optimization-cache.ts b/tests/e2e/tests/vite/reuse-dep-optimization-cache.ts index 56ecdfee8cd0..07be3f2a911b 100644 --- a/tests/e2e/tests/vite/reuse-dep-optimization-cache.ts +++ b/tests/e2e/tests/vite/reuse-dep-optimization-cache.ts @@ -1,29 +1,20 @@ import assert from 'node:assert'; import { findFreePort } from '../../utils/network'; -import { - execAndWaitForOutputToMatch, - killAllProcesses, - ng, - waitForAnyProcessOutputToMatch, -} from '../../utils/process'; +import { execAndWaitForOutputToMatch, killAllProcesses, ng } from '../../utils/process'; export default async function () { await ng('cache', 'clean'); await ng('cache', 'on'); const port = await findFreePort(); - const serveReady = execAndWaitForOutputToMatch( + await execAndWaitForOutputToMatch( 'ng', ['serve', '--port', `${port}`], - /Application bundle generation complete/, + /dependencies optimized/, // Use CI:0 to force caching { ...process.env, DEBUG: 'vite:deps', CI: '0', NO_COLOR: 'true' }, ); - // Note: Don't await `serveReady` before, as otherwise we might not see - // the dependencies optimized output. There is some debouncing for `ng serve` - // going on that could cause this. - await Promise.all([serveReady, waitForAnyProcessOutputToMatch(/dependencies optimized/, 10_000)]); const response = await fetch(`http://localhost:${port}/main.js`); assert(response.ok, `Expected 'response.ok' to be 'true'.`); diff --git a/tests/e2e/tests/vitest/larger-project.ts b/tests/e2e/tests/vitest/larger-project.ts index 90bb283f2d8a..7b8db953c30c 100644 --- a/tests/e2e/tests/vitest/larger-project.ts +++ b/tests/e2e/tests/vitest/larger-project.ts @@ -1,7 +1,9 @@ -import { ng } from '../../utils/process'; -import { applyVitestBuilder } from '../../utils/vitest'; import assert from 'node:assert'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname } from 'node:path'; import { installPackage } from '../../utils/packages'; +import { ng } from '../../utils/process'; +import { applyVitestBuilder } from '../../utils/vitest'; export default async function () { await applyVitestBuilder(); @@ -39,31 +41,110 @@ export default async function () { } async function generateArtifactsInBatches(artifactCount: number): Promise { - const BATCH_SIZE = 5; - let commands: Promise[] = []; + const files: { [path: string]: string } = {}; for (let i = 0; i < artifactCount; i++) { const type = i % 3; const name = `test-artifact-${i}`; - let generateType: string; switch (type) { case 0: - generateType = 'component'; + files[`src/app/${name}/${name}.ts`] = ` +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-${name}', + template: '', +}) +export class TestArtifact${i}Component {} +`; + files[`src/app/${name}/${name}.spec.ts`] = ` +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestArtifact${i}Component } from './${name}'; + +describe('TestArtifact${i}Component', () => { + let component: TestArtifact${i}Component; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TestArtifact${i}Component], + }).compileComponents(); + + fixture = TestBed.createComponent(TestArtifact${i}Component); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); +`; break; case 1: - generateType = 'service'; + files[`src/app/${name}.ts`] = ` +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class TestArtifact${i}Service {} +`; + files[`src/app/${name}.spec.ts`] = ` +import { TestBed } from '@angular/core/testing'; +import { TestArtifact${i}Service } from './${name}'; + +describe('TestArtifact${i}Service', () => { + let service: TestArtifact${i}Service; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(TestArtifact${i}Service); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); +`; break; default: - generateType = 'pipe'; - break; - } + files[`src/app/${name}-pipe.ts`] = ` +import { Pipe, PipeTransform } from '@angular/core'; - commands.push(ng('generate', generateType, name, '--skip-tests=false')); +@Pipe({ + name: 'testArtifact${i}', +}) +export class TestArtifact${i}Pipe implements PipeTransform { + transform(value: unknown): unknown { + return null; + } +} +`; + files[`src/app/${name}-pipe.spec.ts`] = ` +import { TestArtifact${i}Pipe } from './${name}-pipe'; - if (commands.length === BATCH_SIZE || i === artifactCount - 1) { - await Promise.all(commands); - commands = []; +describe('TestArtifact${i}Pipe', () => { + it('create an instance', () => { + const pipe = new TestArtifact${i}Pipe(); + expect(pipe).toBeTruthy(); + }); +}); +`; + break; } } + + const entries = Object.entries(files); + const CONCURRENCY_LIMIT = 100; + for (let i = 0; i < entries.length; i += CONCURRENCY_LIMIT) { + const chunk = entries.slice(i, i + CONCURRENCY_LIMIT); + await Promise.all( + chunk.map(async ([filePath, content]) => { + await mkdir(dirname(filePath), { recursive: true }); + await writeFile(filePath, content.trim()); + }), + ); + } } diff --git a/tests/e2e/utils/puppeteer.ts b/tests/e2e/utils/puppeteer.ts index d33411938639..ae8aae45761a 100644 --- a/tests/e2e/utils/puppeteer.ts +++ b/tests/e2e/utils/puppeteer.ts @@ -40,7 +40,7 @@ export async function executeBrowserTest(options: BrowserTestOptions = {}) { const browser = await launch({ executablePath: process.env['CHROME_BIN'], headless: true, - args: ['--no-sandbox'], + args: ['--no-sandbox', '--disable-dev-shm-usage', '--disable-gpu'], }); try { const page = await browser.newPage();