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!