MS Login is not redirecting after authenticated

Hi all,

I am expecting to test my app UI from K6 and get the MS Login popup (it’s happening), which should automatically send DFA to PingID (not hapening), and after inserted the matching # in my phone then got globalaccess url (it’s happening without entering any # in the phone) and automatically redirects to http://localhost:8080#code… (not happening) (same as a regular login process).

My logs:

INFO[0005] Popup URL: https//login.microsoftonline.com/{audience-guid}/oauth2/v2.0/authorize?client_id=….

INFO[0011] Popup URL after email: https//globalaccess.mycompany.com/adfs/ls/?client-request-id={user.guid}…

My code:

import { browser } from “k6/browser”;

export const options = {
scenarios: {
ui: {
executor: “shared-iterations”,
options: {
browser: {
type: “chromium”
}
}
}
}
};

export default async function () {
const context = await browser.newContext();
const page = await context.newPage();

try {
	// Navigate to landing page
	await page.goto("http://localhost:8080");

	// Wait a bit for potential popup to appear
	await page.waitForTimeout(2000);

	// Get all pages (including popups) in the context
	const pages = context.pages();
	console.log(`Number of pages: ${pages.length}`);

	// Check if a popup was opened (more than 1 page means popup exists)
	let popup = pages.length > 1 ? pages[1] : null;

	if (popup) {
		console.log("✓ Login popup detected");
		await popup.waitForLoadState("domcontentloaded");
		const popupUrl = popup.url();
		console.log(`Popup URL: ${popupUrl}`);

		// If popup is already at localhost, user is likely already authenticated
		if (popupUrl.startsWith("http://localhost:8080")) {
			console.log("✓ Popup opened at localhost - likely already authenticated");
			console.log("Closing popup to continue with authenticated session...");
			try {
				await popup.close();
			} catch (e) {
				console.log(`Error closing popup: ${e.message}`);
			}
			popup = null; // Clear popup reference
		}
	} else {
		console.log("⚠ No popup opened, checking if already authenticated...");
	}

	// Check if we got a popup and it's on the Microsoft login page
	if (popup && popup.url().includes("login.microsoftonline.com")) {
		console.log("Attempting to authenticate in popup...");

		await popup.waitForLoadState("networkidle", { timeout: 5000 });

		console.log("Attempting manual email entry...");
		const emailInput = popup.locator('input[name="loginfmt"]');

		const inputCount = await emailInput.count();
		console.log(`Email input elements found: ${inputCount}`);

		const inputVisible = await emailInput.isVisible({ timeout: 5000 });

		if (inputVisible) {
			console.log("✓ Email input found, filling...");
			await emailInput.fill("myemail@mycompany.com");

			// Check if there's a "Next" button to click instead of pressing Enter
			const nextButton = popup.locator('input[type="submit"][value="Next"]');
			const nextButtonCount = await nextButton.count();
			console.log(`Next button found: ${nextButtonCount}`);

			if (
				nextButtonCount > 0 &&
				(await nextButton.isVisible({ timeout: 1000 }).catch(() => false))
			) {
				console.log("Clicking Next button...");
				await nextButton.click();
			} else {
				console.log("Pressing Enter on email input...");
				await emailInput.press("Enter");
			}

			// Wait for the next step (password or account selection)
			console.log("Waiting for next authentication step...");
			// await page.waitForTimeout(2000);
			const submitButton = page.locator("#next");
			await Promise.all([
				page.waitForNavigation({ waitUntil: "networkidle" }),
				submitButton.click()
			]);

			// Refresh popup reference in case it navigated
			const pagesAfterEmail = context.pages();
			popup = pagesAfterEmail.length > 1 ? pagesAfterEmail[1] : null;

			if (!popup) {
				console.log("Popup closed after email entry");
			} else {
				try {
					// Wait for popup to load the next screen
					await popup.waitForLoadState("load", { timeout: 10000 }).catch(() => {
						console.log("Popup load timeout");
					});

					// Wait a bit more for dynamic content
					await page.waitForTimeout(2000);

					const popupUrlAfterEmail = popup.url();
					console.log(`Popup URL after email: ${popupUrlAfterEmail}`);

					// If on ADFS/GlobalAccess, trigger PingID by navigating to the URL
					if (popupUrlAfterEmail.includes("globalaccess.mycompany.com")) {
						console.log("✓ On GlobalAccess - PingID authentication required");

						// The URL itself should trigger PingID, but let's ensure the page is ready
						await popup.waitForLoadState("domcontentloaded", { timeout: 10000 }).catch(() => {
							console.log("DOMContentLoaded timeout on GlobalAccess");
						});

						console.log("Please approve the PingID authentication request on your mobile device");
						console.log("Monitoring popup for authentication completion...");

						// Poll popup URL to detect when PingID completes and redirects to localhost
						let pingIdCompleted = false;
						for (let attempt = 0; attempt < 60; attempt++) {
							await page.waitForTimeout(1000);

							// Check if popup still exists
							const pagesNow = context.pages();
							const currentPopup = pagesNow.length > 1 ? pagesNow[1] : null;

							if (!currentPopup) {
								console.log("Popup closed during PingID authentication");
								pingIdCompleted = true;
								break;
							}

							const currentPopupUrl = currentPopup.url();

							// Check if redirected to localhost (auth complete)
							if (currentPopupUrl.startsWith("http://localhost:8080/#code")) {
								console.log(`✓ PingID authentication completed! (after ${attempt + 1} seconds)`);
								console.log(`Popup redirected to: ${currentPopupUrl.substring(0, 100)}...`);
								pingIdCompleted = true;
								// Don't break here - let the main loop handle the localhost URL
								break;
							}

							// Log progress every 5 seconds
							if (attempt % 5 === 0 && attempt > 0) {
								console.log(`Still waiting for PingID approval... (${attempt} seconds elapsed)`);
							}
						}

						if (!pingIdCompleted) {
							console.log("⚠ PingID authentication timeout after 60 seconds");
						}
					}

					// Check for error messages
					const errorElement = popup.locator('#error, .error, [role="alert"]');
					const errorCount = await errorElement.count();
					if (errorCount > 0) {
						const errorText = await errorElement
							.first()
							.textContent()
							.catch(() => "");
						console.log(`❌ Error on page: ${errorText}`);
					}

					// Check for account picker (multiple selectors)
					const accountSelectors = [
						'[data-test-id="tile"]',
						".tile",
						'[data-test-id*="account"]',
						".account-tile",
						'div[role="button"]'
					];

					let accountTileCount = 0;
					let workingSelector = null;
					for (const selector of accountSelectors) {
						const element = popup.locator(selector);
						const count = await element.count();
						if (count > 0) {
							accountTileCount = count;
							workingSelector = selector;
							console.log(`Account tiles found with "${selector}": ${count}`);
							break;
						}
					}

					if (accountTileCount > 0 && workingSelector) {
						console.log("Found account tile, clicking it...");
						await popup.locator(workingSelector).first().click();
						await page.waitForTimeout(2000);
					}

					// Check if we're on ADFS (Company's login page)
					const isADFS = popup.url().includes("globalaccess.mycompamy.com");
					if (isADFS) {
						console.log("✓ On ADFS login page");
					}

					// Check for password input with multiple selectors (MS and ADFS)
					const passwordSelectors = [
						'input[name="passwd"]', // MS login
						'input[type="password"]', // Generic
						'input[name="Password"]', // ADFS
						'input[id="passwordInput"]' // ADFS alternate
					];

					let passwordInput = null;
					let passwordCount = 0;
					for (const selector of passwordSelectors) {
						const element = popup.locator(selector);
						const count = await element.count();
						if (count > 0) {
							passwordInput = element;
							passwordCount = count;
							console.log(`Password input found with "${selector}": ${count}`);
							break;
						}
					}

					if (!passwordInput) {
						console.log(`Password input found: 0`);
						// Give time for manual password entry or other auth method
						console.log(
							"Please copy GlobalAccess url in the browser and authenticate with PingId. (You have 20 seconds to complete it)"
						);
						await page.waitForTimeout(20000);
					}

					if (
						passwordCount > 0 &&
						passwordInput &&
						(await passwordInput.isVisible({ timeout: 2000 }).catch(() => false))
					) {
						console.log("✓ Password input found, entering password...");

						await passwordInput.fill("mypassword$");

						// Look for submit button
						const submitSelectors = [
							'input[type="submit"]',
							'button[type="submit"]',
							'input[value="Sign in"]',
							'button:has-text("Sign in")'
						];

						let submitButton = null;
						for (const selector of submitSelectors) {
							const element = popup.locator(selector);
							const count = await element.count();
							if (count > 0 && (await element.isVisible({ timeout: 1000 }).catch(() => false))) {
								submitButton = element;
								console.log(`Submit button found with "${selector}"`);
								break;
							}
						}

						if (submitButton) {
							console.log("Clicking submit button...");
							await submitButton.click();
						} else {
							console.log("No submit button found, pressing Enter...");
							await passwordInput.press("Enter");
						}

						// Wait for authentication to process
						console.log("Waiting for authentication to process...");
						await page.waitForTimeout(5000);

						// Check popup URL after password submission
						const urlAfterPassword = popup.url();
						console.log(`Popup URL after password: ${urlAfterPassword}`);

						// Check for error messages
						const errorCheck = popup.locator('#error, .error, [role="alert"], .alert-error');
						const hasError = await errorCheck.count();
						if (hasError > 0) {
							const errorText = await errorCheck
								.first()
								.textContent()
								.catch(() => "");
							console.log(`❌ Authentication error detected: ${errorText}`);
						}
					}

					// Check for "Stay signed in?" prompt
					const staySignedInButton = popup.locator(
						'input[type="submit"][value="Yes"], button:has-text("Yes")'
					);
					const staySignedInCount = await staySignedInButton.count();
					console.log(`Stay signed in button found: ${staySignedInCount}`);

					if (staySignedInCount > 0) {
						console.log("Clicking 'Stay signed in' button...");
						await staySignedInButton.first().click();
						await page.waitForTimeout(2000);
					}
				} catch (popupError) {
					console.log(`Popup interaction error: ${popupError.message}`);
				}

				// Wait for popup to close or check if it redirected back to localhost
				console.log("Waiting for popup to close...");
				let popupClosed = false;
				for (let i = 0; i < 15; i++) {
					const pagesNow = context.pages();

					// Check if popup is still open
					if (pagesNow.length === 1) {
						console.log("✓ Popup closed successfully");
						popupClosed = true;
						break;
					}

					// If popup still exists, check its URL
					const currentPopup = pagesNow.length > 1 ? pagesNow[1] : null;
					if (currentPopup) {
						const popupUrl = currentPopup.url();
						console.log(`Attempt ${i + 1}: Popup URL: ${popupUrl}`);

						// If popup has redirected to localhost, extract the auth code
						if (popupUrl.startsWith("http://localhost:8080")) {
							console.log("✓ Popup redirected to localhost");

							// Check if popup URL has the auth code hash
							if (popupUrl.includes("#code=")) {
								console.log("✓ Popup has auth code in URL");
								console.log(`Full popup URL: ${popupUrl.substring(0, 100)}...`);

								// Extract the hash fragment from popup URL
								const hashFragment = popupUrl.split("#")[1];
								console.log(`Hash fragment: ${hashFragment?.substring(0, 80)}...`);

								// WORKAROUND: Since MSAL postMessage doesn't work in K6,
								// manually navigate main page to the URL with auth code
								console.log("Navigating main page to URL with auth code...");
								const targetUrl = `http://localhost:8080/#${hashFragment}`;
								await page.goto(targetUrl);
								console.log("✓ Main page navigated to auth URL");

								// Close the popup now that we've extracted the code
								try {
									await currentPopup.close();
									console.log("✓ Popup closed");
								} catch (e) {
									console.log(`Could not close popup: ${e.message}`);
								}

								popupClosed = true;
								break;
							} else {
								console.log("⚠ Popup URL doesn't contain auth code hash");
								console.log("Waiting for auth code to appear...");
								await page.waitForTimeout(2000);
							}
						} else {
							console.log(
								"Popup has not redirected to localhost yet. Authenticate with PingId if needed."
							);
							await page.waitForTimeout(2000);
						}
					}

					await page.waitForTimeout(1000);
				}

				if (!popupClosed) {
					console.log("⚠ Popup did not close after 15 seconds, forcing close...");
					const remainingPages = context.pages();
					if (remainingPages.length > 1) {
						try {
							await remainingPages[1].close();
							console.log("✓ Popup force-closed");
						} catch (e) {
							console.log(`Could not force-close popup: ${e.message}`);
						}
					}
				}
			}
		} else {
			console.error("✗ Could not find email input field");
		}

		console.log("✓ Authentication flow completed");
	}

	// Wait for main page to process the auth token from URL hash
	console.log("Waiting for main page to process authentication...");
	await page.waitForTimeout(3000);

	// Check if URL has changed (should have #code= in it)
	let urlAfterAuth = page.url();
	console.log(`Main page URL after auth: ${urlAfterAuth}`);

	if (urlAfterAuth.includes("#code=") || urlAfterAuth.includes("#state=")) {
		console.log("✓ Auth parameters received in URL");
	} else {
		console.log(
			"⚠ No auth parameters in URL - authentication may already be complete or session exists"
		);
		// Try reloading the page to trigger app load
		console.log("Reloading page to ensure app loads...");
		await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {
			console.log("Page reload failed");
		});
		await page.waitForTimeout(2000);
		urlAfterAuth = page.url();
		console.log(`URL after reload: ${urlAfterAuth}`);
	}

	// Wait for the app to process the auth token and load content
	console.log("Waiting for app to load after authentication...");

	// Wait for DOM to be fully loaded
	await page.waitForLoadState("domcontentloaded", { timeout: 30000 }).catch(() => {
		console.log("DOM content loaded timeout");
	});

	// Wait for network activity to settle
	await page.waitForLoadState("networkidle", { timeout: 30000 }).catch(() => {
		console.log("Network idle timeout, continuing...");
	});

	// Give extra time for React/MSAL to process auth and render
	console.log("Waiting for React app to initialize...");
	await page.waitForTimeout(10000);

	// Check if page context is still valid
	try {
		const currentUrl = page.url();
		console.log(`Current URL: ${currentUrl}`);
	} catch (urlError) {
		console.error(`Error getting page URL: ${urlError.message}`);
		throw new Error("Page context is no longer valid");
	}

	// Check authentication status by looking at page content
	try {
		// Wait for body to have content
		console.log("Checking page content...");
		await page.waitForSelector("body", { timeout: 5000 }).catch(() => {
			console.log("Body selector timeout");
		});

		const bodyText = await page
			.locator("body")
			.textContent()
			.catch(() => "");
		const bodyLength = bodyText.length;
		console.log(`Page body length: ${bodyLength} characters`);

		if (bodyLength === 0) {
			console.error("✗ Page body is empty - app may not have loaded");
			throw new Error("Page content not loaded");
		}

		// Check for "....", it means failed to authenticate
		const hasFourDots = bodyText.includes("....");
		console.log(`Page body contains '....': ${hasFourDots}`);

		if (hasFourDots) {
			console.error("✗ Authentication appears to have failed (found '....' on page)");
			throw new Error("Authentication failed");
		}

		// Check for user name to confirm authentication
		const hasUserName = bodyText.includes("Mariano") || bodyText.includes("mariano");
		console.log(`Page body contains user name: ${hasUserName}`);

		if (hasUserName) {
			console.log("✓ Authentication appears successful (found user name on page)");
		} else {
			console.log("⚠ User name not found in page - authentication may not be complete");
		}
	} catch (bodyCheckError) {
		console.error(`Error checking page body: ${bodyCheckError.message}`);
		throw bodyCheckError;
	}

	// Check for accordion and expand if needed
	console.log("Checking for accordion...");
	try {
		const accordionSelectors = [
			"motif-accordion",
			"motif-accordion-trigger",
			'[slot="trigger"]',
			"button[aria-expanded]",
			'[class*="accordion"]'
		];

		let accordionExpanded = false;
		for (const selector of accordionSelectors) {
			const element = page.locator(selector).first();
			const count = await element.count();
			console.log(`Accordion selector "${selector}" found: ${count}`);
			if (count > 0) {
				const isVisible = await element.isVisible({ timeout: 2000 }).catch(() => false);
				if (isVisible) {
					console.log(`✓ Found accordion with selector: ${selector}, clicking to expand...`);
					await element.click();
					await page.waitForTimeout(1500);
					accordionExpanded = true;
					break;
				}
			}
		}

		if (!accordionExpanded) {
			console.log("No accordion found or already expanded");
		}

		// Look for risk radar cards
		console.log("Looking for risk radar cards...");
		const cardSelectors = [
			'[data-testid="risk-radar-card"]',
			"motif-card",
			"motif-card-body",
			'[class*="riskRadarCard"]',
			'[class*="card"]'
		];

		let clickableElement = null;
		let workingSelector = null;

		for (const selector of cardSelectors) {
			const element = page.locator(selector).first();
			const count = await element.count();
			console.log(`Card selector "${selector}" count: ${count}`);
			if (count > 0) {
				const isVisible = await element.isVisible({ timeout: 3000 }).catch(() => false);
				if (isVisible) {
					console.log(`✓ Found visible card with selector: ${selector}`);
					clickableElement = element;
					workingSelector = selector;
					break;
				}
			}
		}

		if (!clickableElement) {
			console.error("✗ Could not find any risk radar cards on the page");
			console.log("Page may not have any risk radars, or cards are not visible");
			throw new Error("Risk radar cards not found");
		}

		console.log(`Clicking on risk radar card with selector: ${workingSelector}`);
		await clickableElement.click();

		// Wait for navigation to risk radar details page
		await page.waitForTimeout(2000);
		await page.waitForLoadState("networkidle", { timeout: 15000 }).catch(() => {
			console.log("Navigation networkidle timeout");
		});

		console.log("✓ Successfully clicked on risk radar");
		console.log(`New URL: ${page.url()}`);
	} catch (interactionError) {
		console.error(`Error during page interaction: ${interactionError.message}`);
		throw interactionError;
	}
} catch (error) {
	console.error("Test failed:", error.message);
} finally {
	await page.close();
	await context.close();
}

}

Any clue on why it fails?

Thanks!