Skip to content

Commit 7fe62bb

Browse files
authored
Upgrade to Puppeteer 17 (#5145)
* update to puppeteer 17 * Update tests * update tests * update angular tests * remove animations from angular samples * skip b2c MSA tests * resolve typescript error * skip B2C MSA tests * add polling for storage * Skip B2C MSA tests * Fix intermittent failures on refresh test * Use a time constant * set default timeout for puppeteer APIs
1 parent ba3d798 commit 7fe62bb

File tree

62 files changed

+415
-504
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+415
-504
lines changed

samples/e2eTestUtils/TestUtils.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import{ Page, HTTPResponse } from "puppeteer";
33
import { LabConfig } from "./LabConfig";
44
import { LabClient } from "./LabClient";
55

6+
export const ONE_SECOND_IN_MS = 1000;
7+
68
export class Screenshot {
79
private folderName: string;
810
private screenshotNum: number;
@@ -24,6 +26,28 @@ export function createFolder(foldername: string) {
2426
}
2527
}
2628

29+
export async function storagePoller(callback: ()=>Promise<void>, timeoutMs: number): Promise<void> {
30+
return new Promise((resolve, reject) => {
31+
const startTime = Date.now();
32+
let lastError: Error;
33+
const interval = setInterval(async () => {
34+
if ((Date.now() - startTime) > timeoutMs) {
35+
clearInterval(interval);
36+
console.error(lastError);
37+
reject(new Error("Timed out while polling storage"));
38+
}
39+
await callback().then(() => {
40+
// If callback resolves - success
41+
clearInterval(interval);
42+
resolve();
43+
}).catch((e: Error)=>{
44+
// If callback throws storage hasn't been updated yet - check again on next interval
45+
lastError = e;
46+
});
47+
}, 200);
48+
});
49+
};
50+
2751
export async function retrieveAppConfiguration(labConfig: LabConfig, labClient: LabClient, isConfidentialClient: boolean): Promise<[string, string, string]> {
2852
let clientID = "";
2953
let clientSecret = "";
@@ -196,25 +220,6 @@ export async function b2cMsaAccountEnterCredentials(page: Page, screenshot: Scre
196220
return;
197221
}
198222

199-
try {
200-
await page.waitForSelector('input#iLandingViewAction', {timeout: 1000});
201-
await screenshot.takeScreenshot(page, "securityInfoPage");
202-
await Promise.all([
203-
page.click('input#iLandingViewAction'),
204-
205-
// Wait either for another navigation to Keep me signed in page or back to redirectUri
206-
Promise.race([
207-
page.waitForNavigation({ waitUntil: "networkidle0" }),
208-
page.waitForResponse((response: HTTPResponse) => response.url().startsWith("http://localhost"), { timeout: 0 })
209-
])
210-
]).catch(async (e) => {
211-
await screenshot.takeScreenshot(page, "errorPage").catch(() => {});
212-
throw e;
213-
});
214-
} catch (e) {
215-
console.log(e) // catch the error but do not throw if the selector is not found
216-
}
217-
218223
await page.waitForSelector('input#KmsiCheckboxField', {timeout: 1000});
219224
await page.waitForSelector("input#idSIButton9");
220225
await screenshot.takeScreenshot(page, "kmsiPage");

samples/e2eTestUtils/jest-puppeteer-utils/jestSetup.js

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,31 +37,19 @@ async function startServer(jestConfig) {
3737
throw new Error ();
3838
}
3939

40-
// Optional __TIMEOUT__ parameter or default to 30 seconds
41-
const timeoutMs = jestConfig.globals.__TIMEOUT__ || 30000;
40+
// Optional __TIMEOUT__ parameter or default to 60 seconds
41+
const timeoutMs = jestConfig.globals.__TIMEOUT__ || 60000;
4242

4343
console.log(`Starting Server for: ${sampleName} on port ${port}`);
4444
process.env.PORT = port;
45-
let serverOutput = "";
46-
const serverCallback = (error, stdout, stderr) => {
47-
if (error) {
48-
serverOutput += error;
49-
}
50-
if (stderr) {
51-
serverOutput += stderr;
52-
}
53-
if (stdout) {
54-
serverOutput += stdout;
55-
}
56-
};
57-
serverUtils.startServer(startCommand, jestConfig.rootDir, serverCallback);
45+
46+
serverUtils.startServer(startCommand, jestConfig.rootDir);
5847

5948
const serverUp = await serverUtils.isServerUp(port, timeoutMs);
6049
if (serverUp) {
6150
console.log(`Server for ${sampleName} running on port ${port}`);
6251
} else {
6352
console.error(`Unable to start server for ${sampleName} on port ${port}`);
64-
console.log(serverOutput);
6553
throw new Error();
6654
}
6755
}

samples/e2eTestUtils/jest-puppeteer-utils/serverUtils.js

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,87 @@
33
* Licensed under the MIT License.
44
*/
55

6-
const { exec } = require('child_process');
7-
const waitOn = require('wait-on');
6+
const { spawn } = require('child_process');
7+
const http = require("http");
88
const find = require("find-process");
99
const path = require("path");
1010

1111
/**
1212
* Returns boolean true/false if the given port is currently serving
1313
*/
1414
async function isServerUp(port, timeout) {
15-
try {
16-
await waitOn({ resources: [`http://localhost:${port}`], timeout: timeout});
17-
} catch (e) {
18-
return false;
19-
}
15+
return new Promise((resolve) => {
16+
const startTime = Date.now();
17+
const interval = setInterval(() => {
18+
if ((Date.now() - startTime) > timeout) {
19+
resolve(false);
20+
clearInterval(interval);
21+
return;
22+
}
23+
24+
const requestIPv4 = {
25+
protocol: "http:",
26+
host: "localhost",
27+
port: port,
28+
family: 4
29+
};
30+
31+
http.get(requestIPv4, (res) => {
32+
const { statusCode } = res;
33+
34+
if (statusCode === 200) {
35+
resolve(true);
36+
clearInterval(interval);
37+
}
38+
}).on('error', (e) => {
39+
// errors will be raised until the server is up. Ignore errors
40+
});
2041

21-
return true;
42+
// Sometimes samples may be hosted in IPv6 instead - check that too
43+
const requestIPv6 = {
44+
protocol: "http:",
45+
host: "localhost",
46+
port: port,
47+
family: 6
48+
};
49+
50+
http.get(requestIPv6, (res) => {
51+
const { statusCode } = res;
52+
53+
if (statusCode === 200) {
54+
resolve(true);
55+
clearInterval(interval);
56+
}
57+
}).on('error', (e) => {
58+
// errors will be raised until the server is up. Ignore errors
59+
});
60+
}, 100);
61+
});
2262
}
2363

2464
/**
2565
* Spawns a child process to serve the sample
2666
*/
27-
function startServer(cmd, directory, callback) {
28-
exec(cmd, {cwd: directory}, callback);
67+
function startServer(cmd, directory) {
68+
const serverProcess = spawn(cmd, {shell: true, cwd: directory});
69+
70+
serverProcess.on('error', (err) => {
71+
console.error('Failed to start sample.');
72+
throw err;
73+
});
74+
75+
serverProcess.stdout.on('data', (data) => {
76+
console.log(`stdout: ${data}`);
77+
});
78+
79+
serverProcess.stderr.on('data', (data) => {
80+
console.error(`stderr: ${data}`);
81+
});
82+
83+
serverProcess.on('close', (code) => {
84+
console.log(`child process exited with code ${code}`);
85+
});
86+
2987
}
3088

3189
/**

samples/msal-angular-v2-samples/angular-b2c-sample-app/src/app/app.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BrowserModule } from '@angular/platform-browser';
2-
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
2+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
33
import { NgModule } from '@angular/core';
44

55
import { MatButtonModule } from '@angular/material/button';
@@ -80,7 +80,7 @@ export function MSALGuardConfigFactory(): MsalGuardConfiguration {
8080
],
8181
imports: [
8282
BrowserModule,
83-
BrowserAnimationsModule,
83+
NoopAnimationsModule, // Animations cause delay which interfere with E2E tests
8484
AppRoutingModule,
8585
MatButtonModule,
8686
MatToolbarModule,

samples/msal-angular-v2-samples/angular-b2c-sample-app/test/local-account.spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,10 @@ describe('B2C user-flow tests (local account)', () => {
5353
await screenshot.takeScreenshot(page, "Page loaded");
5454

5555
// Initiate Login
56-
const signInButton = await page.waitForXPath("//button[contains(., 'Login')]");
56+
const signInButton = await page.waitForSelector("xpath=//button[contains(., 'Login')]");
5757
if (signInButton) {
5858
await signInButton.click();
5959
}
60-
await page.waitForTimeout(50);
6160
await screenshot.takeScreenshot(page, "Login button clicked");
6261

6362
await b2cLocalAccountEnterCredentials(page, screenshot, username, accountPwd);
@@ -77,7 +76,7 @@ describe('B2C user-flow tests (local account)', () => {
7776
expect(await BrowserCache.accessTokenForScopesExists(tokenStoreBeforeEdit.accessTokens, ["https://msidlabb2c.onmicrosoft.com/msidlabb2capi/read"])).toBeTruthy;
7877

7978
// initiate edit profile flow
80-
const editProfileButton = await page.waitForXPath("//span[contains(., 'Edit Profile')]");
79+
const editProfileButton = await page.waitForSelector("xpath=//span[contains(., 'Edit Profile')]");
8180
if (editProfileButton) {
8281
await editProfileButton.click();
8382
}
@@ -89,7 +88,6 @@ describe('B2C user-flow tests (local account)', () => {
8988
]);
9089
await page.click("#continue");
9190
await page.waitForFunction(`window.location.href.startsWith("http://localhost:${port}")`);
92-
await page.waitForTimeout(50);
9391
await page.waitForSelector("#idTokenClaims");
9492
const htmlBody = await page.evaluate(() => document.body.innerHTML);
9593
expect(htmlBody).toContain(`${displayName}`);

samples/msal-angular-v2-samples/angular-b2c-sample-app/test/msa-account.spec.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { BrowserCacheUtils } from "../../../e2eTestUtils/BrowserCacheTestUtils";
77

88
const SCREENSHOT_BASE_FOLDER_NAME = `${__dirname}/screenshots/msa-account-tests`;
99

10-
describe('B2C user-flow tests (msa account)', () => {
10+
// Problem with MSA logins displaying a security info notice - re-enable these tests when that gets resolved
11+
describe.skip('B2C user-flow tests (msa account)', () => {
1112
jest.retryTimes(1);
1213
let browser: puppeteer.Browser;
1314
let context: puppeteer.BrowserContext;
@@ -53,11 +54,10 @@ describe('B2C user-flow tests (msa account)', () => {
5354
await screenshot.takeScreenshot(page, "Page loaded");
5455

5556
// Initiate Login
56-
const signInButton = await page.waitForXPath("//button[contains(., 'Login')]");
57+
const signInButton = await page.waitForSelector("xpath=//button[contains(., 'Login')]");
5758
if (signInButton) {
5859
await signInButton.click();
5960
}
60-
await page.waitForTimeout(50);
6161
await screenshot.takeScreenshot(page, "Login button clicked");
6262

6363
await b2cMsaAccountEnterCredentials(page, screenshot, username, accountPwd);
@@ -77,7 +77,7 @@ describe('B2C user-flow tests (msa account)', () => {
7777
expect(await BrowserCache.accessTokenForScopesExists(tokenStoreBeforeEdit.accessTokens, ["https://msidlabb2c.onmicrosoft.com/msidlabb2capi/read"])).toBeTruthy;
7878

7979
// initiate edit profile flow
80-
const editProfileButton = await page.waitForXPath("//span[contains(., 'Edit Profile')]");
80+
const editProfileButton = await page.waitForSelector("xpath=//span[contains(., 'Edit Profile')]");
8181
if (editProfileButton) {
8282
await editProfileButton.click();
8383
}
@@ -89,7 +89,6 @@ describe('B2C user-flow tests (msa account)', () => {
8989
]);
9090
await page.click("#continue");
9191
await page.waitForFunction(`window.location.href.startsWith("http://localhost:${port}")`);
92-
await page.waitForTimeout(50);
9392
await page.waitForSelector("#idTokenClaims");
9493
const htmlBody = await page.evaluate(() => document.body.innerHTML);
9594
expect(htmlBody).toContain(`${displayName}`);

samples/msal-angular-v2-samples/angular-b2c-sample-app/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
"lib": [
2222
"es2020",
2323
"dom"
24+
],
25+
"types": [
26+
"jest"
2427
]
2528
},
2629
"angularCompilerOptions": {

samples/msal-angular-v2-samples/angular10-sample-app/jest.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module.exports = {
33
globals: {
44
__PORT__: 4201,
55
__STARTCMD__: "npm start -- --port 4201",
6-
__TIMEOUT__: 45000
6+
__TIMEOUT__: 90000
77
},
88
preset: "../../e2eTestUtils/jest-puppeteer-utils/jest-preset.js"
99
};

samples/msal-angular-v2-samples/angular10-sample-app/src/app/app.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BrowserModule } from '@angular/platform-browser';
2-
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
2+
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
33
import { NgModule } from '@angular/core';
44

55
import { MatButtonModule } from '@angular/material/button';
@@ -74,7 +74,7 @@ export function MSALGuardConfigFactory(): MsalGuardConfiguration {
7474
],
7575
imports: [
7676
BrowserModule,
77-
BrowserAnimationsModule,
77+
NoopAnimationsModule, // Animations cause delay which interfere with E2E tests
7878
AppRoutingModule,
7979
MatButtonModule,
8080
MatToolbarModule,

samples/msal-angular-v2-samples/angular10-sample-app/test/home.spec.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,17 @@ describe('/ (Home Page)', () => {
6464
await screenshot.takeScreenshot(page, "Page loaded");
6565

6666
// Initiate Login
67-
const signInButton = await page.waitForXPath("//button[contains(., 'Login')]");
67+
const signInButton = await page.waitForSelector("xpath=//button[contains(., 'Login')]");
6868
await signInButton.click();
69-
await page.waitForTimeout(70);
7069
await screenshot.takeScreenshot(page, "Login button clicked");
71-
const loginRedirectButton = await page.waitForXPath("//div//button[contains(., 'Login using Redirect')]");
70+
const loginRedirectButton = await page.waitForSelector("xpath=//div//button[contains(., 'Login using Redirect')]");
7271
await loginRedirectButton.click();
7372

7473
await enterCredentials(page, screenshot, username, accountPwd);
7574

7675
// Verify UI now displays logged in content
7776
page.waitForXPath("//p[contains(., 'Login successful!')]");
78-
const logoutButton = await page.waitForXPath("//button[contains(., 'Logout')]");
77+
const logoutButton = await page.waitForSelector("xpath=//button[contains(., 'Logout')]");
7978
await logoutButton.click();
8079
const logoutButtons = await page.$x("//div//button[contains(., 'Logout using')]");
8180
expect(logoutButtons.length).toBe(2);
@@ -86,7 +85,7 @@ describe('/ (Home Page)', () => {
8685
await verifyTokenStore(BrowserCache, ["User.Read"]);
8786

8887
// Navigate to profile page
89-
const [profileButton] = await page.$x("//span[contains(., 'Profile')]");
88+
const profileButton = await page.waitForSelector("xpath=//span[contains(., 'Profile')]");
9089
await profileButton.click();
9190
await screenshot.takeScreenshot(page, "Profile page loaded");
9291

@@ -100,11 +99,10 @@ describe('/ (Home Page)', () => {
10099
await screenshot.takeScreenshot(page, "Page loaded");
101100

102101
// Initiate Login
103-
const signInButton = await page.waitForXPath("//button[contains(., 'Login')]");
102+
const signInButton = await page.waitForSelector("xpath=//button[contains(., 'Login')]");
104103
await signInButton.click();
105-
await page.waitForTimeout(70);
106104
await screenshot.takeScreenshot(page, "Login button clicked");
107-
const loginPopupButton = await page.waitForXPath("//button[contains(., 'Login using Popup')]");
105+
const loginPopupButton = await page.waitForSelector("xpath=//button[contains(., 'Login using Popup')]");
108106
const newPopupWindowPromise = new Promise<puppeteer.Page>(resolve => page.once("popup", resolve));
109107
await loginPopupButton.click();
110108
const popupPage = await newPopupWindowPromise;
@@ -118,7 +116,7 @@ describe('/ (Home Page)', () => {
118116

119117
// Verify UI now displays logged in content
120118
await page.waitForXPath("//p[contains(., 'Login successful!')]");
121-
const logoutButton = await page.waitForXPath("//button[contains(., 'Logout')]");
119+
const logoutButton = await page.waitForSelector("xpath=//button[contains(., 'Logout')]");
122120
await logoutButton.click();
123121
const logoutButtons = await page.$x("//button[contains(., 'Logout using')]");
124122
expect(logoutButtons.length).toBe(2);
@@ -129,7 +127,7 @@ describe('/ (Home Page)', () => {
129127
await verifyTokenStore(BrowserCache, ["User.Read"]);
130128

131129
// Navigate to profile page
132-
const [profileButton] = await page.$x("//span[contains(., 'Profile')]");
130+
const profileButton = await page.waitForSelector("xpath=//span[contains(., 'Profile')]");
133131
await profileButton.click();
134132
await screenshot.takeScreenshot(page, "Profile page loaded");
135133

0 commit comments

Comments
 (0)