diff --git a/packages/ng-schematics/src/schematics/ng-add/files/common/e2e/tests/utils.ts.template b/packages/ng-schematics/src/schematics/ng-add/files/common/e2e/tests/utils.ts.template index ced9291513c..2136f99a3ac 100644 --- a/packages/ng-schematics/src/schematics/ng-add/files/common/e2e/tests/utils.ts.template +++ b/packages/ng-schematics/src/schematics/ng-add/files/common/e2e/tests/utils.ts.template @@ -28,16 +28,16 @@ export function setupBrowserHooks(path = ''): void { }); afterEach(async () => { - await page.close(); + await page?.close(); }); <% if(testRunner == 'jasmine' || testRunner == 'jest') { %> afterAll(async () => { - await browser.close(); + await browser?.close(); }); <% } %><% if(testRunner == 'mocha' || testRunner == 'node') { %> after(async () => { - await browser.close(); + await browser?.close(); }); <% } %> } diff --git a/packages/ng-schematics/src/schematics/ng-add/files/jest/e2e/jest.config.js b/packages/ng-schematics/src/schematics/ng-add/files/jest/e2e/jest.config.js index ee5fc51695f..ee21c6737e7 100644 --- a/packages/ng-schematics/src/schematics/ng-add/files/jest/e2e/jest.config.js +++ b/packages/ng-schematics/src/schematics/ng-add/files/jest/e2e/jest.config.js @@ -5,6 +5,6 @@ /** @type {import('jest').Config} */ module.exports = { - testMatch: ['/build/**/?(*.)+(e2e).js?(x)'], + testMatch: ['/build/**/*.e2e.js'], testEnvironment: 'node', }; diff --git a/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/.mocharc.js b/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/.mocharc.js index e751ec07450..28c18396741 100644 --- a/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/.mocharc.js +++ b/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/.mocharc.js @@ -1,3 +1,4 @@ module.exports = { spec: './e2e/build/**/*.e2e.js', + timeout: 5000, }; diff --git a/packages/ng-schematics/tools/projects.mjs b/packages/ng-schematics/tools/projects.mjs index 01a8a7657c2..985200881e7 100644 --- a/packages/ng-schematics/tools/projects.mjs +++ b/packages/ng-schematics/tools/projects.mjs @@ -25,25 +25,34 @@ class AngularProject { return port; } - static #scripts = { - // Builds the ng-schematics before running them - 'build:schematics': 'npm run --prefix ../../ build', - // Deletes all files created by Puppeteer Ng-Schematics to avoid errors - 'delete:file': - 'rm -f .puppeteerrc.cjs && rm -f tsconfig.e2e.json && rm -R -f e2e/', - // Runs the Puppeteer Ng-Schematics against the sandbox - schematics: 'schematics ../../:ng-add --dry-run=false', - 'schematics:e2e': 'schematics ../../:e2e --dry-run=false', - 'schematics:config': 'schematics ../../:config --dry-run=false', - 'schematics:smoke': - 'schematics ../../:ng-add --dry-run=false --test-runner="node" && ng e2e', + static #scripts = testRunner => { + return { + // Builds the ng-schematics before running them + 'build:schematics': 'npm run --prefix ../../ build', + // Deletes all files created by Puppeteer Ng-Schematics to avoid errors + 'delete:file': + 'rm -f .puppeteerrc.cjs && rm -f tsconfig.e2e.json && rm -R -f e2e/', + // Runs the Puppeteer Ng-Schematics against the sandbox + schematics: 'schematics ../../:ng-add --dry-run=false', + 'schematics:e2e': 'schematics ../../:e2e --dry-run=false', + 'schematics:config': 'schematics ../../:config --dry-run=false', + 'schematics:smoke': `schematics ../../:ng-add --dry-run=false --test-runner="${testRunner}" && ng e2e`, + }; }; + /** Folder name */ #name; + /** E2E test runner to use */ + #runner; - constructor(name) { + constructor(runner, name) { + this.#runner = runner ?? 'node'; this.#name = name ?? randomUUID(); } + get runner() { + return this.#runner; + } + get name() { return this.#name; } @@ -52,11 +61,18 @@ class AngularProject { const [executable, ...args] = command.split(' '); await new Promise((resolve, reject) => { const createProcess = spawn(executable, args, { - stdio: 'inherit', shell: true, ...options, }); + createProcess.stdout.on('data', data => { + data = data + .toString() + // Replace new lines with a prefix including the test runner + .replace(/(?:\r\n?|\n)(?=.*[\r\n])/g, `\n${this.#runner} - `); + console.log(`${this.#runner} - ${data}`); + }); + createProcess.on('error', message => { console.error(`Running ${command} exited with error:`, message); reject(message); @@ -82,7 +98,7 @@ class AngularProject { const packageJson = JSON.parse(await readFile(packageJsonFile)); packageJson['scripts'] = { ...packageJson['scripts'], - ...AngularProject.#scripts, + ...AngularProject.#scripts(this.#runner), }; await writeFile(packageJsonFile, JSON.stringify(packageJson, null, 2)); } @@ -128,7 +144,7 @@ export class AngularProjectSingle extends AngularProject { export class AngularProjectMulti extends AngularProject { async createProject() { await this.executeCommand( - `ng new ${this.name} --create-application=false --directory=sandbox/${this.name} --skip-git` + `ng new ${this.name} --create-application=false --directory=sandbox/${this.name} --defaults --skip-git` ); await this.executeCommand( diff --git a/packages/ng-schematics/tools/smoke.mjs b/packages/ng-schematics/tools/smoke.mjs index 8aa1c476b16..8ae9907266f 100644 --- a/packages/ng-schematics/tools/smoke.mjs +++ b/packages/ng-schematics/tools/smoke.mjs @@ -4,17 +4,69 @@ * SPDX-License-Identifier: Apache-2.0 */ +import {ok} from 'node:assert'; import {execSync} from 'node:child_process'; +import {parseArgs} from 'node:util'; import {AngularProjectMulti, AngularProjectSingle} from './projects.mjs'; +const {values: args} = parseArgs({ + options: { + testRunner: { + type: 'string', + short: 't', + default: undefined, + }, + name: { + type: 'string', + short: 'n', + default: undefined, + }, + }, +}); + if (process.env.CI) { // Need to install in CI execSync('npm install -g @angular/cli@latest @angular-devkit/schematics-cli'); + const runners = ['node', 'jest', 'jasmine', 'mocha']; + const groups = []; + + for (const runner of runners) { + groups.push([ + new AngularProjectSingle(runner), + new AngularProjectMulti(runner), + ]); + } + + const angularProjects = await Promise.allSettled( + groups.flat().map(async project => { + return await project.create(); + }) + ); + ok( + angularProjects.every(project => { + return project.status === 'fulfilled'; + }), + 'Building of 1 or more projects failed!' + ); + + for await (const runnerGroup of groups) { + const smokeResults = await Promise.allSettled( + runnerGroup.map(async project => { + return await project.runSmoke(); + }) + ); + ok( + smokeResults.every(project => { + return project.status === 'fulfilled'; + }), + `Smoke test for ${runnerGroup[0].runner} failed!` + ); + } +} else { + const single = new AngularProjectSingle(args.testRunner, args.name); + const multi = new AngularProjectMulti(args.testRunner, args.name); + + await Promise.all([single.create(), multi.create()]); + await Promise.all([single.runSmoke(), multi.runSmoke()]); } - -const single = new AngularProjectSingle(); -const multi = new AngularProjectMulti(); - -await Promise.all([single.create(), multi.create()]); -await Promise.all([single.runSmoke(), multi.runSmoke()]);