Skip to content

Commit 99458ee

Browse files
committed
test: migrate build and misc E2E tests to Puppeteer
Replaces the Protractor-based `ng e2e` execution with the new Puppeteer `executeBrowserTest` utility in various build and misc E2E tests. The following tests were updated: - app-shell-with-service-worker - auto-csp - worker - trusted-types This migration involves implementing custom `checkFn` logic to replicate the assertions previously handled by Protractor, such as verifying server-rendered content, console logs, and service worker states.
1 parent 164e7db commit 99458ee

File tree

4 files changed

+109
-111
lines changed

4 files changed

+109
-111
lines changed

tests/e2e/tests/build/app-shell/app-shell-with-service-worker.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { getGlobalVariable } from '../../../utils/env';
2-
import { appendToFile, expectFileToMatch, writeFile } from '../../../utils/fs';
2+
import { appendToFile, expectFileToMatch } from '../../../utils/fs';
33
import { installPackage } from '../../../utils/packages';
44
import { ng } from '../../../utils/process';
55
import { updateJsonFile } from '../../../utils/project';
6+
import { executeBrowserTest } from '../../../utils/puppeteer';
67

78
const snapshots = require('../../../ng-snapshot/package.json');
89

@@ -31,26 +32,27 @@ export default async function () {
3132
}
3233
}
3334

34-
await writeFile(
35-
'e2e/app.e2e-spec.ts',
36-
`
37-
import { browser, by, element } from 'protractor';
35+
await ng('build');
36+
await expectFileToMatch('dist/test-project/browser/index.html', /app-shell works!/);
3837

39-
it('should have ngsw in normal state', () => {
40-
browser.get('/');
38+
await executeBrowserTest({
39+
configuration: 'production',
40+
checkFn: async (page) => {
4141
// Wait for service worker to load.
42-
browser.sleep(2000);
43-
browser.waitForAngularEnabled(false);
44-
browser.get('/ngsw/state');
45-
// Should have updated, and be in normal state.
46-
expect(element(by.css('pre')).getText()).not.toContain('Last update check: never');
47-
expect(element(by.css('pre')).getText()).toContain('Driver state: NORMAL');
48-
});
49-
`,
50-
);
42+
await new Promise((resolve) => setTimeout(resolve, 2000));
43+
await page.waitForSelector('h1');
5144

52-
await ng('build');
53-
await expectFileToMatch('dist/test-project/browser/index.html', /app-shell works!/);
45+
const baseUrl = page.url();
46+
await page.goto(new URL('/ngsw/state', baseUrl).href);
5447

55-
await ng('e2e', '--configuration=production');
48+
// Should have updated, and be in normal state.
49+
const preText = await page.$eval('pre', (el) => el.textContent);
50+
if (preText?.includes('Last update check: never')) {
51+
throw new Error(`Expected service worker to have checked for updates, but got: ${preText}`);
52+
}
53+
if (!preText?.includes('Driver state: NORMAL')) {
54+
throw new Error(`Expected service worker driver state to be NORMAL, but got: ${preText}`);
55+
}
56+
},
57+
});
5658
}

tests/e2e/tests/build/auto-csp.ts

Lines changed: 46 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import assert from 'node:assert';
2+
import { setTimeout } from 'node:timers/promises';
23
import { getGlobalVariable } from '../../utils/env';
34
import { expectFileToMatch, writeFile, writeMultipleFiles } from '../../utils/fs';
45
import { findFreePort } from '../../utils/network';
56
import { execAndWaitForOutputToMatch, ng } from '../../utils/process';
67
import { updateJsonFile } from '../../utils/project';
8+
import { executeBrowserTest } from '../../utils/puppeteer';
79

810
const CSP_META_TAG = /<meta http-equiv="Content-Security-Policy"/;
911

@@ -67,55 +69,6 @@ export default async function () {
6769
</body>
6870
</html>
6971
`,
70-
'e2e/src/app.e2e-spec.ts': `
71-
import { browser, by, element } from 'protractor';
72-
import * as webdriver from 'selenium-webdriver';
73-
74-
function allConsoleWarnMessagesAndErrors() {
75-
return browser
76-
.manage()
77-
.logs()
78-
.get('browser')
79-
.then(function (browserLog: any[]) {
80-
const warnMessages: any[] = [];
81-
browserLog.filter((logEntry) => {
82-
const msg = logEntry.message;
83-
console.log('>> ' + msg);
84-
if (logEntry.level.value >= webdriver.logging.Level.INFO.value) {
85-
warnMessages.push(msg);
86-
}
87-
});
88-
return warnMessages;
89-
});
90-
}
91-
92-
describe('Hello world E2E Tests', () => {
93-
beforeAll(async () => {
94-
await browser.waitForAngularEnabled(true);
95-
});
96-
97-
it('should display: Welcome and run all scripts in order', async () => {
98-
// Load the page without waiting for Angular since it is not bootstrapped automatically.
99-
await browser.driver.get(browser.baseUrl);
100-
101-
// Test the contents.
102-
expect(await element(by.css('h1')).getText()).toMatch('Hello');
103-
104-
// Make sure all scripts ran and there were no client side errors.
105-
const consoleMessages = await allConsoleWarnMessagesAndErrors();
106-
expect(consoleMessages.length).toEqual(4); // No additional errors
107-
// Extract just the printed messages from the console data.
108-
const printedMessages = consoleMessages.map(m => m.match(/"(.*?)"/)[1]);
109-
expect(printedMessages).toEqual([
110-
// All messages printed in order because execution order is preserved.
111-
"Inline Script Head",
112-
"Inline Script Body: 1339",
113-
"First External Script: 1338",
114-
"Second External Script: 1337",
115-
]);
116-
});
117-
});
118-
`,
11972
});
12073

12174
async function spawnServer(): Promise<number> {
@@ -137,7 +90,49 @@ export default async function () {
13790
// Make sure if contains the critical CSS inlining CSP code.
13891
await expectFileToMatch('dist/test-project/browser/index.html', 'ngCspMedia');
13992

140-
// Make sure that our e2e protractor tests run to confirm that our angular project runs.
93+
// Make sure that our e2e tests run to confirm that our angular project runs.
14194
const port = await spawnServer();
142-
await ng('e2e', `--base-url=http://localhost:${port}`, '--dev-server-target=');
95+
await executeBrowserTest({
96+
baseUrl: `http://localhost:${port}/`,
97+
checkFn: async (page) => {
98+
const warnMessages: string[] = [];
99+
page.on('console', (msg) => {
100+
if (msg.type() === 'warning') {
101+
warnMessages.push(msg.text());
102+
}
103+
});
104+
105+
// Reload to ensure we capture messages from the start if needed,
106+
// although executeBrowserTest already navigated.
107+
await page.reload();
108+
109+
// Wait for the expected number of warnings
110+
let retries = 50;
111+
while (warnMessages.length < 4 && retries > 0) {
112+
await setTimeout(100);
113+
retries--;
114+
}
115+
116+
if (warnMessages.length !== 4) {
117+
throw new Error(
118+
`Expected 4 console warnings, but got ${warnMessages.length}:\n${warnMessages.join('\n')}`,
119+
);
120+
}
121+
122+
const expectedMessages = [
123+
'Inline Script Head',
124+
'Inline Script Body: 1339',
125+
'First External Script: 1338',
126+
'Second External Script: 1337',
127+
];
128+
129+
for (let i = 0; i < expectedMessages.length; i++) {
130+
if (!warnMessages[i].includes(expectedMessages[i])) {
131+
throw new Error(
132+
`Expected warning ${i} to include '${expectedMessages[i]}', but got '${warnMessages[i]}'`,
133+
);
134+
}
135+
}
136+
},
137+
});
143138
}

tests/e2e/tests/build/worker.ts

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88

99
import assert from 'node:assert/strict';
1010
import { readdir } from 'node:fs/promises';
11+
import { setTimeout } from 'node:timers/promises';
1112
import { getGlobalVariable } from '../../utils/env';
1213
import { expectFileToExist, expectFileToMatch, replaceInFile, writeFile } from '../../utils/fs';
1314
import { ng } from '../../utils/process';
1415
import { expectToFail } from '../../utils/utils';
16+
import { executeBrowserTest } from '../../utils/puppeteer';
1517

1618
export default async function () {
1719
const useWebpackBuilder = !getGlobalVariable('argv')['esbuild'];
@@ -55,24 +57,34 @@ export default async function () {
5557
// https://github.com/angular/protractor/issues/2207
5658
await replaceInFile('src/app/app.ts', 'console.log', 'console.warn');
5759

58-
await writeFile(
59-
'e2e/app.e2e-spec.ts',
60-
`
61-
import { AppPage } from './app.po';
62-
import { browser, logging } from 'protractor';
63-
describe('worker bundle', () => {
64-
it('should log worker messages', async () => {
65-
const page = new AppPage();;
66-
page.navigateTo();
67-
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
68-
expect(logs.length).toEqual(1);
69-
expect(logs[0].message).toContain('page got message: worker response to hello');
60+
await executeBrowserTest({
61+
checkFn: async (page) => {
62+
const messages: string[] = [];
63+
page.on('console', (msg) => {
64+
messages.push(msg.text());
7065
});
71-
});
72-
`,
73-
);
7466

75-
await ng('e2e');
67+
// Reload to ensure we capture messages from the start if needed,
68+
// although executeBrowserTest already navigated.
69+
await page.reload();
70+
71+
// Wait for the worker message
72+
let retries = 50;
73+
while (
74+
!messages.some((m) => m.includes('page got message: worker response to hello')) &&
75+
retries > 0
76+
) {
77+
await setTimeout(100);
78+
retries--;
79+
}
80+
81+
if (!messages.some((m) => m.includes('page got message: worker response to hello'))) {
82+
throw new Error(
83+
`Expected worker message not found in console. Got:\n${messages.join('\n')}`,
84+
);
85+
}
86+
},
87+
});
7688
}
7789

7890
async function getWorkerOutputFile(useWebpackBuilder: boolean): Promise<string> {

tests/e2e/tests/misc/trusted-types.ts

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import { replaceInFile, writeFile } from '../../utils/fs';
9+
import { replaceInFile } from '../../utils/fs';
1010
import { ng } from '../../utils/process';
1111
import { updateJsonFile } from '../../utils/project';
12+
import { executeBrowserTest } from '../../utils/puppeteer';
1213

1314
export default async function () {
1415
// Add lazy route.
@@ -19,29 +20,6 @@ export default async function () {
1920
`routes: Routes = [{path: 'lazy', loadComponent: () => import('./lazy/lazy').then(c => c.Lazy)}];`,
2021
);
2122

22-
// Add lazy route e2e
23-
await writeFile(
24-
'e2e/src/app.e2e-spec.ts',
25-
`
26-
import { browser, logging, element, by } from 'protractor';
27-
28-
describe('workspace-project App', () => {
29-
it('should display lazy route', async () => {
30-
await browser.get(browser.baseUrl + '/lazy');
31-
expect(await element(by.css('app-lazy p')).getText()).toEqual('lazy works!');
32-
});
33-
34-
afterEach(async () => {
35-
// Assert that there are no errors emitted from the browser
36-
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
37-
expect(logs).not.toContain(jasmine.objectContaining({
38-
level: logging.Level.SEVERE,
39-
}));
40-
});
41-
});
42-
`,
43-
);
44-
4523
const testCases = [
4624
{
4725
aot: false,
@@ -64,7 +42,18 @@ export default async function () {
6442
});
6543

6644
try {
67-
await ng('e2e');
45+
await executeBrowserTest({
46+
checkFn: async (page) => {
47+
const baseUrl = page.url();
48+
await page.goto(new URL('/lazy', baseUrl).href);
49+
50+
await page.waitForSelector('app-lazy p');
51+
const lazyText = await page.$eval('app-lazy p', (el) => el.textContent);
52+
if (lazyText !== 'lazy works!') {
53+
throw new Error(`Expected lazy text to be 'lazy works!', but got '${lazyText}'`);
54+
}
55+
},
56+
});
6857
} catch (error) {
6958
console.error(`Test case AOT ${aot} with CSP header ${csp} failed.`);
7059
throw error;

0 commit comments

Comments
 (0)