diff --git a/package-lock.json b/package-lock.json
index 2ec655cc25..ebb905d10a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -55799,6 +55799,7 @@
}
},
"samples/msal-browser-samples/COOP": {
+ "name": "msal-browser-popup-coop",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
@@ -55808,7 +55809,127 @@
"path": "^0.11.14"
},
"devDependencies": {
- "@playwright/test": "^1.30.0"
+ "@playwright/test": "^1.31.1",
+ "@types/jest": "^29.5.0",
+ "@types/node": "^24.10.0",
+ "@types/react": "^19.1.3",
+ "@types/react-dom": "^19.1.3",
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
+ "@typescript-eslint/parser": "^5.0.0",
+ "@vercel/webpack-asset-relocator-loader": "1.7.3",
+ "autoprefixer": "^10.4.13",
+ "css-loader": "^6.0.0",
+ "e2e-test-utils": "file:../../e2eTestUtils",
+ "electron": "22.3.25",
+ "eslint": "^8.0.1",
+ "eslint-plugin-import": "^2.25.0",
+ "fork-ts-checker-webpack-plugin": "^7.2.1",
+ "jest": "^29.5.0",
+ "node-loader": "^2.0.0",
+ "postcss": "^8.4.31",
+ "postcss-loader": "^4.2.0",
+ "sass": "^1.55.0",
+ "sass-loader": "^10.1.1",
+ "style-loader": "^3.0.0",
+ "ts-jest": "^29.1.0",
+ "ts-loader": "^9.2.2",
+ "ts-node": "^10.0.0",
+ "typescript": "~4.5.4"
+ }
+ },
+ "samples/msal-browser-samples/COOP/node_modules/@types/node": {
+ "version": "24.10.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
+ "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "samples/msal-browser-samples/COOP/node_modules/@vercel/webpack-asset-relocator-loader": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@vercel/webpack-asset-relocator-loader/-/webpack-asset-relocator-loader-1.7.3.tgz",
+ "integrity": "sha512-vizrI18v8Lcb1PmNNUBz7yxPxxXoOeuaVEjTG9MjvDrphjiSxFZrRJ5tIghk+qdLFRCXI5HBCshgobftbmrC5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve": "^1.10.0"
+ }
+ },
+ "samples/msal-browser-samples/COOP/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "samples/msal-browser-samples/COOP/node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"samples/msal-browser-samples/COOP/node_modules/path": {
@@ -55816,6 +55937,71 @@
"resolved": "https://registry.npmjs.org/path/-/path-0.11.14.tgz",
"integrity": "sha512-CzEXTDgcEfa0yqMe+DJCSbEB5YCv4JZoic5xulBNFF2ifIMjNrTWbNSPNhgKfSo0MjneGIx9RLy4pCFuZPaMSQ=="
},
+ "samples/msal-browser-samples/COOP/node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "samples/msal-browser-samples/COOP/node_modules/typescript": {
+ "version": "4.5.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz",
+ "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "samples/msal-browser-samples/COOP/node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "dev": true,
+ "license": "MIT"
+ },
"samples/msal-browser-samples/ExpressSample": {
"name": "express-sample",
"version": "1.0.0",
diff --git a/samples/msal-browser-samples/COOP/app/auth.js b/samples/msal-browser-samples/COOP/app/auth.js
index bd99f1bd08..bc9465699d 100644
--- a/samples/msal-browser-samples/COOP/app/auth.js
+++ b/samples/msal-browser-samples/COOP/app/auth.js
@@ -39,7 +39,7 @@ function handleResponse(resp) {
const successDiv = document.getElementById("successAuthCode");
if (successDiv) {
successDiv.innerHTML = `
-
+
✅ Authentication Successful!
User: ${resp.account.name || resp.account.username}
ID Token: ${resp.idToken.substring(0, 30)}...
@@ -64,14 +64,18 @@ function handleResponse(resp) {
}
}
-function logoutPopup(interactionType) {
+function signOut(interactionType) {
const logoutRequest = {
- account: myMSALObj.getAccountByHomeId(accountId)
+ account: myMSALObj.getAccount({accountId})
};
- myMSALObj.logoutPopup(logoutRequest).then(() => {
- window.location.reload();
- });
+ if (interactionType === "popup") {
+ myMSALObj.logoutPopup(logoutRequest).then(() => {
+ window.location.reload();
+ });
+ } else {
+ myMSALObj.logoutRedirect(logoutRequest);
+ }
}
async function loginPopup(request, account) {
diff --git a/samples/msal-browser-samples/COOP/app/index.html b/samples/msal-browser-samples/COOP/app/index.html
index 8f1b28a1a3..e1613788ae 100644
--- a/samples/msal-browser-samples/COOP/app/index.html
+++ b/samples/msal-browser-samples/COOP/app/index.html
@@ -59,8 +59,8 @@
diff --git a/samples/msal-browser-samples/COOP/app/ui.js b/samples/msal-browser-samples/COOP/app/ui.js
index 9de6b2416a..90e83fd5d7 100644
--- a/samples/msal-browser-samples/COOP/app/ui.js
+++ b/samples/msal-browser-samples/COOP/app/ui.js
@@ -1,5 +1,4 @@
// Select DOM elements to work with
-const welcomeDiv = document.getElementById("WelcomeMessage");
const signInButton = document.getElementById("SignIn");
const popupButton = document.getElementById("popup");
const redirectButton = document.getElementById("redirect");
@@ -7,7 +6,6 @@ const cardDiv = document.getElementById("card-div");
function showWelcomeMessage(account) {
// Reconfiguring DOM elements
- //welcomeDiv.innerHTML = `Welcome ${account.username}`;
signInButton.setAttribute('class', "btn btn-success dropdown-toggle");
signInButton.innerHTML = "Sign Out";
popupButton.setAttribute('onClick', "signOut(this.id)");
diff --git a/samples/msal-browser-samples/COOP/package.json b/samples/msal-browser-samples/COOP/package.json
index 931f08c88a..50a9c3864e 100644
--- a/samples/msal-browser-samples/COOP/package.json
+++ b/samples/msal-browser-samples/COOP/package.json
@@ -18,6 +18,31 @@
"path": "^0.11.14"
},
"devDependencies": {
- "@playwright/test": "^1.30.0"
+ "e2e-test-utils": "file:../../e2eTestUtils",
+ "@playwright/test": "^1.31.1",
+ "@types/node": "^24.10.0",
+ "@types/jest": "^29.5.0",
+ "@types/react": "^19.1.3",
+ "@types/react-dom": "^19.1.3",
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
+ "@typescript-eslint/parser": "^5.0.0",
+ "@vercel/webpack-asset-relocator-loader": "1.7.3",
+ "autoprefixer": "^10.4.13",
+ "css-loader": "^6.0.0",
+ "electron": "22.3.25",
+ "eslint": "^8.0.1",
+ "eslint-plugin-import": "^2.25.0",
+ "fork-ts-checker-webpack-plugin": "^7.2.1",
+ "jest": "^29.5.0",
+ "node-loader": "^2.0.0",
+ "postcss": "^8.4.31",
+ "postcss-loader": "^4.2.0",
+ "sass": "^1.55.0",
+ "sass-loader": "^10.1.1",
+ "style-loader": "^3.0.0",
+ "ts-jest": "^29.1.0",
+ "ts-loader": "^9.2.2",
+ "ts-node": "^10.0.0",
+ "typescript": "~4.5.4"
}
}
diff --git a/samples/msal-browser-samples/COOP/playwright.config.ts b/samples/msal-browser-samples/COOP/playwright.config.ts
new file mode 100644
index 0000000000..81beb49b34
--- /dev/null
+++ b/samples/msal-browser-samples/COOP/playwright.config.ts
@@ -0,0 +1,96 @@
+import { PlaywrightTestConfig, devices } from "@playwright/test";
+import { RETRY_TIMES } from "e2e-test-utils";
+
+/**
+ * Read environment variables from file.
+ * https://github.com/motdotla/dotenv
+ */
+// import dotenv from 'dotenv';
+// import path from 'path';
+// dotenv.config({ path: path.resolve(__dirname, '.env') });
+
+/**
+ * See https://playwright.dev/docs/test-configuration.
+ */
+const config: PlaywrightTestConfig = {
+ testDir: "./test",
+ maxFailures: 2,
+ /* Run tests in files in parallel */
+ //fullyParallel: true,
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
+ //forbidOnly: !!process.env.CI,
+ /* Retry on CI only */
+ retries: RETRY_TIMES,
+ /* Opt out of parallel tests on CI. */
+ //workers: process.env.CI ? 1 : undefined,
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
+ reporter: "html",
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
+ use: {
+ /* Base URL to use in actions like `await page.goto('')`. */
+ baseURL: "https://localhost:30662",
+
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
+ trace: "on-first-retry",
+ headless: false,
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: "chromium",
+ use: { ...devices["Desktop Chrome"] },
+ },
+
+ // {
+ // name: "firefox",
+ // use: { ...devices["Desktop Firefox"] },
+ // },
+
+ // {
+ // name: "webkit",
+ // use: { ...devices["Desktop Safari"] },
+ // },
+
+ /* Test against mobile viewports. */
+ // {
+ // name: 'Mobile Chrome',
+ // use: { ...devices['Pixel 5'] },
+ // },
+ // {
+ // name: 'Mobile Safari',
+ // use: { ...devices['iPhone 12'] },
+ // },
+
+ /* Test against branded browsers. */
+ // {
+ // name: 'Microsoft Edge',
+ // use: { ...devices['Desktop Edge'], channel: 'msedge' },
+ // },
+ // {
+ // name: 'Google Chrome',
+ // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
+ // },
+ ],
+
+ timeout: 50000,
+ globalTimeout: 5400000,
+
+ /* Run your local dev servers before starting the tests */
+ webServer: [
+ {
+ command: "npm run start:https",
+ url: "https://localhost:30662",
+ reuseExistingServer: !process.env.CI,
+ ignoreHTTPSErrors: true,
+ },
+ {
+ command: "npm run start:server:https",
+ url: "https://localhost:30663",
+ reuseExistingServer: !process.env.CI,
+ ignoreHTTPSErrors: true,
+ },
+ ],
+};
+
+export default config;
diff --git a/samples/msal-browser-samples/COOP/sts/ui.js b/samples/msal-browser-samples/COOP/sts/ui.js
index ad220cbb87..3d5d5752f1 100644
--- a/samples/msal-browser-samples/COOP/sts/ui.js
+++ b/samples/msal-browser-samples/COOP/sts/ui.js
@@ -2,7 +2,17 @@ window.name = "STS Window";
const channel = new BroadcastChannel('sts-channel');
function performAuthentication() {
+
console.log("STS: Performing authentication (simulated)");
+ console.log("STS: Adding 3 second delay...");
+
+ // Add 3 second delay
+ setTimeout(() => {
+ continueAuthentication();
+ }, 2000);
+}
+
+function continueAuthentication() {
console.log("STS: window.opener", window.opener);
console.log("STS: window.location.search", window.location.search);
diff --git a/samples/msal-browser-samples/COOP/test/home.spec.ts b/samples/msal-browser-samples/COOP/test/home.spec.ts
new file mode 100644
index 0000000000..8b84f7fdb9
--- /dev/null
+++ b/samples/msal-browser-samples/COOP/test/home.spec.ts
@@ -0,0 +1,194 @@
+import { chromium, Browser, Page, test, expect, Frame } from "@playwright/test";
+import { ScreenShotElectron } from "e2e-test-utils";
+
+const LOCAL_SCREENSHOT_FOLDER = `${__dirname}/screenshots`;
+
+let browser: Browser;
+let browserPage: Page;
+
+test.beforeEach(async () => {
+ browser = await chromium.launch({
+ args: ["--allow-insecure-localhost"],
+ });
+ browserPage = await browser.newPage();
+ await browserPage.goto("/");
+});
+
+test.afterEach(async () => {
+ await browserPage.close();
+});
+
+test.afterAll(async () => {
+ await browser.close();
+});
+
+test("Home page - Popup and Sso Silent buttons are loaded on home-page", async () => {
+ const testName = "homePageLoad";
+ console.log(`${LOCAL_SCREENSHOT_FOLDER}/${testName}`);
+ const screenshot = new ScreenShotElectron(
+ `${LOCAL_SCREENSHOT_FOLDER}/${testName}`
+ );
+ await screenshot.takeScreenshot(browserPage, "Page loaded");
+
+ const popupButton = await browserPage.waitForSelector("#loginPopup");
+ const ssoButton = await browserPage.waitForSelector("#sso");
+
+ expect(popupButton).not.toBeNull();
+ expect(ssoButton).not.toBeNull();
+});
+
+test("Popup Login Flow - Successful authentication and token acquisition", async () => {
+ const testName = "popupLoginFlow";
+ const screenshot = new ScreenShotElectron(
+ `${LOCAL_SCREENSHOT_FOLDER}/${testName}`
+ );
+
+ await screenshot.takeScreenshot(browserPage, "App loaded");
+
+ // Click the popup login button
+ const loginButton = await browserPage.waitForSelector("#loginPopup");
+ const newPopupWindowPromise = new Promise
((resolve) =>
+ browserPage.once("popup", resolve)
+ );
+ await loginButton.click();
+ await screenshot.takeScreenshot(browserPage, "Login button clicked");
+ await browserPage.waitForTimeout(1000);
+
+ const popupPage = await newPopupWindowPromise;
+ if (!popupPage) {
+ throw new Error("Popup window was not opened");
+ }
+ const popupWindowClosed = popupPage?.waitForEvent("close");
+
+ await screenshot.takeScreenshot(popupPage, "Popup opened");
+
+ // Wait for popup to close (indicates successful authentication) and return to app to verify login success
+ await popupWindowClosed;
+
+ expect(popupPage.isClosed()).toBeTruthy();
+ await browserPage.waitForSelector("#successAuthCode", { timeout: 3000 });
+ await browserPage.waitForSelector("#successMsg", { timeout: 3000 });
+
+ await screenshot.takeScreenshot(
+ browserPage,
+ "Login successful - Welcome message displayed"
+ );
+
+ // Verify account info is displayed
+ const successMessage = await browserPage.textContent("#successAuthCode");
+ console.log("Welcome message:", successMessage);
+ expect(successMessage).toContain("Authentication Successful");
+ expect(successMessage).toContain("Test User");
+});
+
+test("ssoSilent Token Acquisition", async () => {
+ const testName = "ssoSilentTokenAcquisition";
+ const screenshot = new ScreenShotElectron(
+ `${LOCAL_SCREENSHOT_FOLDER}/${testName}`
+ );
+
+ await screenshot.takeScreenshot(browserPage, "Initial page load");
+
+ //add iframe listener
+ const silentIframe = new Promise((resolve) => {
+ browserPage.once("frameattached", (frame) => {
+ resolve(frame);
+ console.log("Frame attached:", frame.url());
+ });
+ });
+
+ // Click the SSO silent button
+ const ssoButton = await browserPage.waitForSelector("#sso");
+ await ssoButton.click();
+ await screenshot.takeScreenshot(browserPage, "SSO button clicked");
+
+ //wait for the iframe to be detected
+ const frame = await silentIframe;
+
+ if (!frame) {
+ throw new Error("Silent iframe was not opened");
+ }
+ // Verify the iframe exists
+ expect(frame).not.toBeNull();
+ console.log("Silent iframe frame object:", frame.url());
+ expect(frame.url()).toContain("/authorize");
+
+ await browserPage.waitForSelector("#successAuthCode", { timeout: 3000 });
+ await browserPage.waitForSelector("#successMsg", { timeout: 3000 });
+
+ await screenshot.takeScreenshot(
+ browserPage,
+ "Silent token acquisition completed"
+ );
+
+ // Verify account info is displayed
+ const successMessage = await browserPage.textContent("#successAuthCode");
+ console.log("Welcome message:", successMessage);
+ expect(successMessage).toContain("Authentication Successful");
+ expect(successMessage).toContain("Test User");
+});
+
+test("COOP Header Validation - Verify Cross-Origin-Opener-Policy is set", async () => {
+ const testName = "coopHeaderValidation";
+ const screenshot = new ScreenShotElectron(
+ `${LOCAL_SCREENSHOT_FOLDER}/${testName}`
+ );
+
+ await screenshot.takeScreenshot(browserPage, "Initial page load");
+
+ // Check for COOP header in the response
+ const response = await browserPage.goto("/", { waitUntil: "networkidle" });
+ const coopHeader = response?.headers()["cross-origin-opener-policy"];
+
+ await screenshot.takeScreenshot(
+ browserPage,
+ "Page loaded with COOP header check"
+ );
+
+ console.log("App COOP Header value:", coopHeader);
+
+ // Verify COOP header is present and has expected value
+ expect(coopHeader).toBeUndefined();
+
+ // Click the popup login button
+ const loginButton = await browserPage.waitForSelector("#loginPopup");
+ const newPopupWindowPromise = new Promise((resolve) =>
+ browserPage.once("popup", resolve)
+ );
+ await loginButton.click();
+ await screenshot.takeScreenshot(browserPage, "Login button clicked");
+
+ const popupPage = await newPopupWindowPromise;
+ if (!popupPage) {
+ throw new Error("Popup window was not opened");
+ }
+
+ // Wait for popup to navigate and get the response to check COOP header
+ const popupResponse = await popupPage.waitForResponse(
+ (response) => response.url().includes("localhost:30663"),
+ { timeout: 10000 }
+ );
+
+ const popupCoopHeader =
+ popupResponse.headers()["cross-origin-opener-policy"];
+ console.log("Popup COOP Header value:", popupCoopHeader);
+
+ // Verify popup COOP header
+ expect(popupCoopHeader).toBeTruthy();
+ expect(popupCoopHeader).toContain("same-origin");
+
+ const popupWindowClosed = popupPage?.waitForEvent("close");
+ await screenshot.takeScreenshot(popupPage, "Popup opened");
+
+ // Wait for popup to close (indicates successful authentication) and return to app to verify login success
+ await popupWindowClosed;
+
+ expect(popupPage.isClosed()).toBeTruthy();
+ await browserPage.waitForSelector("#successAuthCode", { timeout: 3000 });
+ await browserPage.waitForSelector("#successMsg", { timeout: 3000 });
+
+ await screenshot.takeScreenshot(
+ browserPage,
+ "Login successful - Welcome message displayed"
+ );
+});