From 8ac5d11d72f8ccc9bfd46de9fa0eb0d71ff0a51c Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:11:30 -0500 Subject: [PATCH] test: migrate i18n E2E tests to Puppeteer Replaces the Protractor-based `ng e2e` execution with the new Puppeteer `executeBrowserTest` utility in the i18n E2E test suite. Refactors `setup.ts` to export a `browserCheck` helper function for verifying translated content, and removes the legacy Protractor spec file generation and configuration. Updates the following tests to use the new infrastructure: - ivy-localize-es2015-e2e - ivy-localize-basehref - ivy-localize-es2015 - ivy-localize-locale-data-augment - ivy-localize-sub-path --- tests/e2e/tests/i18n/ivy-localize-basehref.ts | 41 +++++------ .../e2e/tests/i18n/ivy-localize-es2015-e2e.ts | 11 +-- tests/e2e/tests/i18n/ivy-localize-es2015.ts | 16 ++--- .../i18n/ivy-localize-locale-data-augment.ts | 37 ++++++---- tests/e2e/tests/i18n/ivy-localize-sub-path.ts | 16 ++--- tests/e2e/tests/i18n/setup.ts | 69 ++++++++----------- 6 files changed, 93 insertions(+), 97 deletions(-) diff --git a/tests/e2e/tests/i18n/ivy-localize-basehref.ts b/tests/e2e/tests/i18n/ivy-localize-basehref.ts index d14c14307ab1..4bbd7dece3ee 100644 --- a/tests/e2e/tests/i18n/ivy-localize-basehref.ts +++ b/tests/e2e/tests/i18n/ivy-localize-basehref.ts @@ -9,7 +9,14 @@ import { expectFileToMatch } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -import { baseHrefs, externalServer, langTranslations, setupI18nConfig } from './setup'; +import { executeBrowserTest } from '../../utils/puppeteer'; +import { + baseHrefs, + browserCheck, + externalServer, + langTranslations, + setupI18nConfig, +} from './setup'; export default async function () { // Setup i18n tests and config. @@ -50,18 +57,12 @@ export default async function () { await expectFileToMatch(`${outputPath}/index.html`, `href="${baseHrefs[lang] || 'https://github.com/'}"`); // Execute Application E2E tests for a production build without dev server - const { server, port, url } = await externalServer( - outputPath, - (baseHrefs[lang] as string) || 'https://github.com/', - ); + const { server, url } = await externalServer(outputPath, (baseHrefs[lang] as string) || 'https://github.com/'); try { - await ng( - 'e2e', - `--port=${port}`, - `--configuration=${lang}`, - '--dev-server-target=', - `--base-url=${url}`, - ); + await executeBrowserTest({ + baseUrl: url, + checkFn: (page) => browserCheck(page, lang), + }); } finally { server.close(); } @@ -81,18 +82,12 @@ export default async function () { await expectFileToMatch(`${outputPath}/index.html`, `href="https://github.com/test${baseHrefs[lang] || 'https://github.com/'}"`); // Execute Application E2E tests for a production build without dev server - const { server, port, url } = await externalServer( - outputPath, - 'https://github.com/test' + (baseHrefs[lang] || 'https://github.com/'), - ); + const { server, url } = await externalServer(outputPath, 'https://github.com/test' + (baseHrefs[lang] || 'https://github.com/')); try { - await ng( - 'e2e', - `--port=${port}`, - `--configuration=${lang}`, - '--dev-server-target=', - `--base-url=${url}`, - ); + await executeBrowserTest({ + baseUrl: url, + checkFn: (page) => browserCheck(page, lang), + }); } finally { server.close(); } diff --git a/tests/e2e/tests/i18n/ivy-localize-es2015-e2e.ts b/tests/e2e/tests/i18n/ivy-localize-es2015-e2e.ts index 3fe337c4c90f..d1f7ac8e28b8 100644 --- a/tests/e2e/tests/i18n/ivy-localize-es2015-e2e.ts +++ b/tests/e2e/tests/i18n/ivy-localize-es2015-e2e.ts @@ -1,13 +1,14 @@ -import { getGlobalVariable } from '../../utils/env'; -import { ng } from '../../utils/process'; -import { langTranslations, setupI18nConfig } from './setup'; +import { executeBrowserTest } from '../../utils/puppeteer'; +import { browserCheck, langTranslations, setupI18nConfig } from './setup'; export default async function () { // Setup i18n tests and config. await setupI18nConfig(); for (const { lang } of langTranslations) { - // Execute Application E2E tests with dev server - await ng('e2e', `--configuration=${lang}`, '--port=0'); + await executeBrowserTest({ + configuration: lang, + checkFn: (page) => browserCheck(page, lang), + }); } } diff --git a/tests/e2e/tests/i18n/ivy-localize-es2015.ts b/tests/e2e/tests/i18n/ivy-localize-es2015.ts index b8425795cab4..cea87a75f2b8 100644 --- a/tests/e2e/tests/i18n/ivy-localize-es2015.ts +++ b/tests/e2e/tests/i18n/ivy-localize-es2015.ts @@ -1,8 +1,9 @@ import { getGlobalVariable } from '../../utils/env'; import { expectFileToMatch } from '../../utils/fs'; import { ng } from '../../utils/process'; +import { executeBrowserTest } from '../../utils/puppeteer'; import { expectToFail } from '../../utils/utils'; -import { externalServer, langTranslations, setupI18nConfig } from './setup'; +import { browserCheck, externalServer, langTranslations, setupI18nConfig } from './setup'; export default async function () { // Setup i18n tests and config. @@ -32,15 +33,12 @@ export default async function () { await expectFileToMatch(`${outputPath}/index.html`, `lang="${lang}"`); // Execute Application E2E tests for a production build without dev server - const { server, port, url } = await externalServer(outputPath, `/${lang}/`); + const { server, url } = await externalServer(outputPath, `/${lang}/`); try { - await ng( - 'e2e', - `--port=${port}`, - `--configuration=${lang}`, - '--dev-server-target=', - `--base-url=${url}`, - ); + await executeBrowserTest({ + baseUrl: url, + checkFn: (page) => browserCheck(page, lang), + }); } finally { server.close(); } diff --git a/tests/e2e/tests/i18n/ivy-localize-locale-data-augment.ts b/tests/e2e/tests/i18n/ivy-localize-locale-data-augment.ts index e2c408a74c69..b4c0ae72ced4 100644 --- a/tests/e2e/tests/i18n/ivy-localize-locale-data-augment.ts +++ b/tests/e2e/tests/i18n/ivy-localize-locale-data-augment.ts @@ -1,14 +1,9 @@ import { getGlobalVariable } from '../../utils/env'; -import { - expectFileToMatch, - prependToFile, - readFile, - replaceInFile, - writeFile, -} from '../../utils/fs'; +import { expectFileToMatch, prependToFile, readFile, writeFile } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -import { langTranslations, setupI18nConfig } from './setup'; +import { executeBrowserTest } from '../../utils/puppeteer'; +import { browserCheck, langTranslations, setupI18nConfig } from './setup'; export default async function () { // Setup i18n tests and config. @@ -20,9 +15,6 @@ export default async function () { appProject.architect['build'].options.localize = ['fr']; }); - // Update E2E test to look for augmented locale data - await replaceInFile('./e2e/src/app.fr.e2e-spec.ts', 'janvier', 'changed-janvier'); - // Augment the locale data and import into the main application file const localeData = await readFile('node_modules/@angular/common/locales/global/fr.js'); await writeFile('src/fr-changed.js', localeData.replace('janvier', 'changed-janvier')); @@ -45,6 +37,27 @@ export default async function () { } // Execute Application E2E tests with dev server - await ng('e2e', `--configuration=${lang}`, '--port=0'); + await executeBrowserTest({ + configuration: lang, + checkFn: async (page) => { + // Run standard checks but expect failure on date + try { + await browserCheck(page, lang); + throw new Error('Expected browserCheck to fail due to modified locale data'); + } catch (e) { + if (!(e instanceof Error) || !e.message.includes("Expected 'date' to be")) { + throw e; + } + } + + // Verify the modified date + const getParagraph = async (id: string) => + page.$eval(`p#${id}`, (el) => el.textContent?.trim()); + const date = await getParagraph('date'); + if (date !== 'changed-janvier') { + throw new Error(`Expected 'date' to be 'changed-janvier', but got '${date}'.`); + } + }, + }); } } diff --git a/tests/e2e/tests/i18n/ivy-localize-sub-path.ts b/tests/e2e/tests/i18n/ivy-localize-sub-path.ts index d6640534c45f..6116c438a49f 100644 --- a/tests/e2e/tests/i18n/ivy-localize-sub-path.ts +++ b/tests/e2e/tests/i18n/ivy-localize-sub-path.ts @@ -10,7 +10,8 @@ import { join } from 'node:path'; import { expectFileToMatch } from '../../utils/fs'; import { ng } from '../../utils/process'; import { updateJsonFile } from '../../utils/project'; -import { baseDir, baseHrefs, externalServer, langTranslations, setupI18nConfig } from './setup'; +import { executeBrowserTest } from '../../utils/puppeteer'; +import { baseDir, browserCheck, externalServer, langTranslations, setupI18nConfig } from './setup'; export default async function () { // Setup i18n tests and config. @@ -56,16 +57,13 @@ export default async function () { await expectFileToMatch(`${outputPath}/index.html`, `href="${baseHref}"`); // Execute Application E2E tests for a production build without dev server - const { server, port, url } = await externalServer(outputPath, baseHref); + const { server, url } = await externalServer(outputPath, baseHref); try { - await ng( - 'e2e', - `--port=${port}`, - `--configuration=${lang}`, - '--dev-server-target=', - `--base-url=${url}`, - ); + await executeBrowserTest({ + baseUrl: url, + checkFn: (page) => browserCheck(page, lang), + }); } finally { server.close(); } diff --git a/tests/e2e/tests/i18n/setup.ts b/tests/e2e/tests/i18n/setup.ts index 99d71f9bbe4b..8eade4e2783c 100644 --- a/tests/e2e/tests/i18n/setup.ts +++ b/tests/e2e/tests/i18n/setup.ts @@ -7,6 +7,7 @@ import { updateJsonFile } from '../../utils/project'; import { readNgVersion } from '../../utils/version'; import { Server } from 'node:http'; import { AddressInfo } from 'node:net'; +import type { Page } from 'puppeteer'; // Configurations for each locale. const translationFile = 'src/locale/messages.xlf'; @@ -61,6 +62,35 @@ export const langTranslations = [ ]; export const sourceLocale = langTranslations[0].lang; +export async function browserCheck(page: Page, lang: string) { + const translation = langTranslations.find((t) => t.lang === lang)?.translation; + if (!translation) { + throw new Error(`Could not find translation for language '${lang}'`); + } + + const getParagraph = async (id: string) => page.$eval(`p#${id}`, (el) => el.textContent?.trim()); + + const hello = await getParagraph('hello'); + if (hello !== translation.hello) { + throw new Error(`Expected 'hello' to be '${translation.hello}', but got '${hello}'.`); + } + + const locale = await getParagraph('locale'); + if (locale !== lang) { + throw new Error(`Expected 'locale' to be '${lang}', but got '${locale}'.`); + } + + const date = await getParagraph('date'); + if (date !== translation.date) { + throw new Error(`Expected 'date' to be '${translation.date}', but got '${date}'.`); + } + + const plural = await getParagraph('plural'); + if (plural !== translation.plural) { + throw new Error(`Expected 'plural' to be '${translation.plural}', but got '${plural}'.`); + } +} + export interface ExternalServer { readonly server: Server; readonly port: number; @@ -173,47 +203,12 @@ export async function setupI18nConfig() { `, ); - // Add e2e specs for each lang. - for (const { lang, translation } of langTranslations) { - await writeFile( - `./e2e/src/app.${lang}.e2e-spec.ts`, - ` - import { browser, logging, element, by } from 'protractor'; - - describe('workspace-project App', () => { - const getParagraph = async (name: string) => element(by.css('app-root p#' + name)).getText(); - beforeEach(() => browser.get(browser.baseUrl)); - afterEach(async () => { - // Assert that there are no errors emitted from the browser - const logs = await browser.manage().logs().get(logging.Type.BROWSER); - expect(logs).not.toContain(jasmine.objectContaining({ - level: logging.Level.SEVERE, - } as logging.Entry)); - }); - - it('should display welcome message', async () => - expect(await getParagraph('hello')).toEqual('${translation.hello}')); - - it('should display locale', async () => - expect(await getParagraph('locale')).toEqual('${lang}')); - - it('should display localized date', async () => - expect(await getParagraph('date')).toEqual('${translation.date}')); - - it('should display pluralized message', async () => - expect(await getParagraph('plural')).toEqual('${translation.plural}')); - }); - `, - ); - } - // Update angular.json to build, serve, and test each locale. await updateJsonFile('angular.json', (workspaceJson) => { const appProject = workspaceJson.projects['test-project']; const appArchitect = workspaceJson.projects['test-project'].architect; const buildConfigs = appArchitect['build'].configurations; const serveConfigs = appArchitect['serve'].configurations; - const e2eConfigs = appArchitect['e2e'].configurations; appArchitect['build'].defaultConfiguration = undefined; @@ -245,10 +240,6 @@ export async function setupI18nConfig() { buildConfigs[lang] = { localize: [lang] }; serveConfigs[lang] = { buildTarget: `test-project:build:${lang}` }; - e2eConfigs[lang] = { - specs: [`./src/app.${lang}.e2e-spec.ts`], - devServerTarget: `test-project:serve:${lang}`, - }; } });