Authentication Testing Guide
How to Test Azure AD Login with Playwright: Complete 2026 Guide
A scenario-by-scenario walkthrough of testing Azure AD (Microsoft Entra ID) login flows with Playwright. MSAL.js redirect and popup modes, conditional access policies, tenant switching, B2C custom policies, MFA enforcement, and the pitfalls that break real enterprise auth test suites.
“Microsoft Entra ID (formerly Azure AD) processes over 720 million authentications per day across enterprise and consumer workloads, according to Microsoft's 2024 Digital Defense Report.”
Azure AD Login Flow (MSAL.js Redirect Mode)
1. Why Testing Azure AD Login Is Harder Than It Looks
Azure Active Directory, now branded as Microsoft Entra ID, is the identity backbone for most enterprise applications. When your app uses MSAL.js (the Microsoft Authentication Library for JavaScript) to initiate login, the browser leaves your domain, navigates to login.microsoftonline.com, renders a multi-step form (email entry, then password, sometimes organization selection), and finally redirects back to your application with an authorization code or tokens. That cross-domain redirect is the first obstacle, but it is far from the last.
MSAL.js supports two interaction modes: redirect and popup. In redirect mode the entire page navigates away. In popup mode a new browser window opens, completes authentication, and passes tokens back to the opener via postMessage. Each mode requires a different Playwright strategy. Redirect mode needs waitForURL across domain boundaries. Popup mode needs context.waitForEvent('page') to capture the new window and interact with it separately.
Conditional access policies add another layer. An Azure AD administrator can require MFA for specific applications, block sign-ins from certain locations, mandate compliant devices, or enforce session controls that limit token lifetimes. Your test account may pass through cleanly today and hit an MFA challenge tomorrow because someone updated a policy. B2C tenants with custom policies (written in the TrustFramework XML schema) introduce entirely custom page layouts, identity provider federation, and multi-step user journeys that look nothing like the standard Azure AD login form.
There are five structural reasons this flow is hard to test reliably. First, the Microsoft login page is a multi-step form where the email and password fields appear on separate screens, not a single form submission. Second, MSAL.js popup mode creates a new browser window that Playwright must intercept before it closes automatically. Third, conditional access evaluation happens server-side after credential validation, so MFA challenges appear unpredictably from the test's perspective. Fourth, tenant switching (common in multi-tenant SaaS apps) means the login flow changes based on which organization the user belongs to. Fifth, Azure AD B2C custom policies can inject arbitrary steps, external API calls, and custom UI pages into the authentication pipeline.
Azure AD Multi-Step Login Flow
Your App
User clicks Sign In
MSAL.js
loginRedirect() or loginPopup()
Email Screen
login.microsoftonline.com
Password Screen
Separate step after email
CA Evaluation
Conditional access policies
MFA (if required)
Authenticator, SMS, or FIDO2
Your App
Tokens received, user signed in
MSAL.js Popup Mode Flow
Main Window
Calls loginPopup()
Popup Opens
New window to MS login
User Authenticates
Email, password, MFA
Popup Closes
postMessage with tokens
Main Window
MSAL receives tokens
A thorough Azure AD test suite covers all of these surfaces. The sections below walk through each scenario with runnable Playwright TypeScript you can paste into a real project.
2. Setting Up Your Test Environment
Before writing any tests, configure a dedicated Azure AD test tenant or use a dedicated app registration in your existing tenant. Microsoft provides free developer tenants through the Microsoft 365 Developer Program. Never run end-to-end tests against production Azure AD, because conditional access policies, account lockout thresholds, and audit log noise will cause problems for your security team.
Azure AD Test Environment Setup Checklist
- Create a dedicated Azure AD test tenant or app registration
- Register a Single Page Application with redirect URIs for localhost and CI
- Create test user accounts with known passwords in the test tenant
- Disable or exclude test users from conditional access policies that require device compliance
- Configure an MFA-enabled test user with a TOTP app for MFA scenarios
- For B2C: upload custom policy files and configure identity experience framework keys
- Grant admin consent for API permissions on the test app registration
- Record the tenant ID, client ID, and authority URL in environment variables
Environment Variables
Test User Management with Microsoft Graph
Use the Microsoft Graph API to create and clean up test users programmatically. You need an app registration with User.ReadWrite.All application permission and admin consent.
Playwright Configuration for Azure AD
The Microsoft login page at login.microsoftonline.com uses a multi-step form with animations between the email and password screens. Set generous timeouts and ensure your configuration handles cross-origin navigation cleanly.
3. Scenario: MSAL.js Redirect Login Happy Path
The most common Azure AD integration uses MSAL.js in redirect mode. When the user clicks “Sign In,” your app calls msalInstance.loginRedirect(), which navigates the entire page to the Microsoft login endpoint. The user enters their email on the first screen, their password on the second screen, and then Microsoft redirects back to your configured redirect URI with an authorization code. MSAL.js intercepts the redirect, exchanges the code for tokens using PKCE, and your app renders the authenticated state.
The critical detail for Playwright is that the Microsoft login page is a multi-step form. After entering the email and clicking “Next,” the page animates to reveal the password field. You cannot fill both fields at once. You must wait for the password input to appear after the email submission step completes.
MSAL.js Redirect Login Happy Path
ModerateGoal
Starting from your app's sign-in button, complete a full Azure AD redirect login, land on the authenticated dashboard, and verify the access token is present.
Preconditions
- App running at
APP_BASE_URLwith MSAL.js configured for redirect mode - Test user exists in the Azure AD tenant with a known password
- No conditional access policies requiring MFA for this user
Playwright Implementation
What to Assert Beyond the UI
- MSAL token cache in
sessionStoragecontains access token, ID token, and refresh token entries - The ID token claims include the expected
oid,tid, andpreferred_usernamevalues - No MSAL error entries in the browser console (filter for
BrowserAuthErrororInteractionRequiredAuthError)
Redirect Login: Playwright vs Assrt
import { test, expect } from '@playwright/test';
test('Azure AD redirect login', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: /sign in/i }).click();
await page.waitForURL(/login\.microsoftonline\.com/);
await page.getByPlaceholder('Email, phone, or Skype')
.fill(process.env.TEST_USER_EMAIL!);
await page.getByRole('button', { name: 'Next' }).click();
await page.getByPlaceholder('Password').waitFor({ state: 'visible' });
await page.getByPlaceholder('Password')
.fill(process.env.TEST_USER_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
const staySignedIn = page.getByRole('button', { name: 'Yes' });
if (await staySignedIn.isVisible({ timeout: 5000 }).catch(() => false)) {
await staySignedIn.click();
}
await page.waitForURL(/\/dashboard/, { timeout: 30_000 });
await expect(page.getByRole('heading', { name: /dashboard/i }))
.toBeVisible();
});4. Scenario: MSAL.js Popup Login
Some applications use msalInstance.loginPopup() instead of redirect mode. This opens a new browser window that navigates to the Microsoft login page, completes authentication, and then closes itself. The tokens are passed back to the main window via window.postMessage. This mode avoids the full-page navigation, which is better for user experience but significantly harder to test with Playwright.
The challenge is that Playwright needs to capture the popup window before it appears, interact with the login form inside it, and then verify that the main window receives the tokens after the popup closes. You use context.waitForEvent('page') to intercept the popup, but you must set up the listener before triggering the popup, or you will miss it entirely.
MSAL.js Popup Login
ComplexGoal
Trigger an MSAL.js popup login, complete authentication inside the popup window, and verify the main window receives valid tokens after the popup closes.
Preconditions
- App configured with
loginPopup()as the interaction type - Popup redirect URI registered in the Azure AD app registration
Playwright Implementation
What to Assert Beyond the UI
- The popup window closes on its own (do not manually close it)
- The main window's
sessionStoragecontains an access token entry after the popup closes - No
BrowserAuthError: popup_window_errorin the console, which indicates the popup was blocked
5. Scenario: Conditional Access MFA Challenge
Conditional access policies are the defining feature of Azure AD in enterprise environments. An administrator can configure policies that require MFA based on user group membership, application sensitivity, sign-in risk level, device compliance state, or network location. When a conditional access policy triggers an MFA challenge, the user sees an additional screen after entering their password, typically prompting for a code from the Microsoft Authenticator app, an SMS code, or a FIDO2 security key.
For automated testing, TOTP (time-based one-time password) is the most practical MFA method. You configure the test user with a TOTP secret in the Microsoft Authenticator app or any TOTP-compatible authenticator, record the shared secret in your test environment, and generate valid codes at runtime using a library like otpauth.
Conditional Access MFA Challenge
ComplexGoal
Log in with a user subject to a conditional access MFA policy, complete the TOTP challenge, and verify the session is established with MFA claims.
Preconditions
- Test user with MFA enrolled (TOTP method) and the secret stored in
TEST_MFA_TOTP_SECRET - Conditional access policy requiring MFA for the test app
Playwright Implementation
Conditional Access MFA: Playwright vs Assrt
import { test, expect } from '@playwright/test';
import * as OTPAuth from 'otpauth';
test('Azure AD MFA challenge', async ({ page }) => {
const totp = new OTPAuth.TOTP({
secret: process.env.TEST_MFA_TOTP_SECRET!,
digits: 6, period: 30, algorithm: 'SHA1',
});
await page.goto('/');
await page.getByRole('button', { name: /sign in/i }).click();
await page.waitForURL(/login\.microsoftonline\.com/);
await page.getByPlaceholder('Email, phone, or Skype')
.fill(process.env.TEST_MFA_USER_EMAIL!);
await page.getByRole('button', { name: 'Next' }).click();
await page.getByPlaceholder('Password').waitFor({ state: 'visible' });
await page.getByPlaceholder('Password')
.fill(process.env.TEST_MFA_USER_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByText(/enter code/i).waitFor({ state: 'visible' });
await page.getByPlaceholder(/code/i).fill(totp.generate());
await page.getByRole('button', { name: /verify/i }).click();
await page.waitForURL(/\/dashboard/, { timeout: 30_000 });
});6. Scenario: Multi-Tenant Login with Tenant Switching
Multi-tenant Azure AD applications allow users from any Azure AD tenant (or personal Microsoft accounts) to sign in. The MSAL.js configuration uses the common or organizations authority endpoint instead of a specific tenant ID. This means the login flow may include an additional step where the user selects their organization or where Azure AD automatically routes them to their home tenant based on the email domain.
Testing this flow is tricky because the login page behavior changes depending on whether the user has previously signed in, whether multiple accounts are cached in the browser, and which tenant the user belongs to. The “Pick an account” screen appears when the browser has cached credentials from a previous session. Your test must handle both the fresh login path and the account picker path.
Multi-Tenant Login with Account Picker
ModerateGoal
Sign in with a user from a different tenant, handling the account picker and tenant routing correctly.
Playwright Implementation
7. Scenario: Azure AD B2C Custom Policy Flow
Azure AD B2C is a separate service from Azure AD (Entra ID), designed for customer-facing identity scenarios. B2C tenants use a different login endpoint (yourtenant.b2clogin.com) and can be configured with custom policies that define entirely custom user journeys. These custom policies are written in XML using the Identity Experience Framework (IEF) TrustFramework schema. They can include multi-step registration flows, identity provider federation (Google, Facebook, custom SAML providers), custom page layouts loaded from your own CDN, and REST API calls to external services during the authentication pipeline.
Testing B2C custom policies is challenging because the page layout, field names, and flow steps are entirely determined by the policy XML and the custom HTML templates you provide. The selectors on a B2C page are not the same as the standard Azure AD login page. You need to inspect your specific custom policy templates to find the correct field IDs and button selectors.
Azure AD B2C Custom Policy Sign-Up and Sign-In
ComplexGoal
Complete a sign-up and sign-in flow through a B2C custom policy, including email verification during sign-up, and verify the B2C tokens are returned correctly.
Playwright Implementation
What to Assert Beyond the UI
- The B2C token contains the custom claims defined in your policy (e.g.,
extension_CustomAttribute) - The issuer in the ID token matches your B2C tenant (
https://yourtenant.b2clogin.com/tenant-id/v2.0) - The user is created in the B2C directory with the correct attributes (verify via Graph API)
8. Scenario: Session Reuse with MSAL Token Cache
Running the full Azure AD login flow before every test is slow and fragile. The Microsoft login page is already a multi-step form with animations; adding conditional access evaluation and MFA challenges on top makes each login take 5 to 15 seconds. Multiply that by 50 tests and your suite takes over 10 minutes just on authentication. The solution is to authenticate once in a setup project, save the session state, and reuse it across all subsequent tests.
MSAL.js stores tokens in sessionStorage by default (or localStorageif configured). Playwright's storageStatecaptures both cookies and storage entries, making it perfect for preserving the MSAL token cache. For even faster CI, you can bypass the browser entirely by using the ROPC (Resource Owner Password Credential) flow or the client credentials flow to obtain tokens directly via API, then inject them into the browser's storage.
Session Persistence with MSAL Token Cache
ModeratePlaywright Implementation: Browser-Based Setup
API-Based Token Injection (ROPC Flow)
The Resource Owner Password Credential (ROPC) flow lets you obtain tokens without a browser. Note that Microsoft discourages ROPC for production, but it is acceptable for test environments where you control the tenant and the user accounts. ROPC does not support MFA, conditional access, or federated users, so it only works for simple test accounts.
Assrt Equivalent
# scenarios/azure-ad-session-reuse.assrt
describe: Reuse authenticated Azure AD session across tests
given:
- I have a saved session from a previous Azure AD login
steps:
- load the saved session state
- navigate to /dashboard
expect:
- I am on the dashboard without being redirected to Microsoft login
- the MSAL token cache is still valid9. Common Pitfalls That Break Azure AD Test Suites
The Multi-Step Form Timing Trap
The Microsoft login page at login.microsoftonline.com renders the email field first, then transitions to the password field with a CSS animation after the user clicks “Next.” If your test fills the password field too early (before the animation completes), the input will be silently ignored or the test will throw a “locator resolved to hidden element” error. Always use waitFor({ state: 'visible' }) on the password placeholder before filling it.
“Stay Signed In?” Prompt Inconsistency
The “Stay signed in?” (KMSI) prompt appears after successful authentication, but only when the tenant has it enabled and only on certain flows. Some tenants disable it entirely, some show it only on the first sign-in, and some show it every time. Your test must handle the prompt conditionally. Use a try/catch pattern with a short timeout to check for the “Yes” button and click it if present, without failing the test if it does not appear.
Popup Mode Failures
MSAL.js popup login is notoriously fragile in automated browsers. The popup can be blocked by browser settings, the window.open call can fail silently, and the popup may close before your test captures it. Common errors include BrowserAuthError: popup_window_error and BrowserAuthError: monitor_popup_timeout. Always set up the context.waitForEvent('page') listener before triggering the popup, and ensure your Playwright context allows popups (the default does allow them, but custom configurations may block them).
Conditional Access Policy Drift
Conditional access policies are managed by Azure AD administrators, often separately from the development team. A policy change that adds MFA requirements, blocks certain locations, or enforces device compliance can break your entire test suite overnight with no code changes on your side. Mitigate this by excluding your test user accounts from aggressive policies, or by creating a dedicated conditional access exclusion group for test automation. Document which policies apply to test users and set up alerts for policy modifications.
Token Lifetime and Silent Renewal
Azure AD access tokens have configurable lifetimes (default is 60 to 90 minutes). If your saved session tokens expire between the global setup and the last test, MSAL.js will attempt a silent token renewal using an iframe. This can fail if third-party cookies are blocked or if the browser context does not support iframe-based token renewal. For long test suites, consider refreshing tokens midway through the suite or using the ROPC flow to obtain fresh tokens for individual test groups.
Azure AD Testing Anti-Patterns
- Filling the password field before the email step animation completes
- Hardcoding the 'Stay signed in?' prompt as always present or always absent
- Setting up the popup listener after clicking the sign-in button
- Ignoring conditional access policy changes that affect test accounts
- Using ROPC flow for accounts with MFA or federated identity
- Running parallel workers that hit Azure AD rate limits (429 responses)
- Assuming B2C custom policy element IDs match standard Azure AD selectors
- Skipping the consent prompt check for multi-tenant applications
10. Writing These Scenarios in Plain English with Assrt
Every scenario above is 30 to 60 lines of Playwright TypeScript. The multi-step login form, the conditional “Stay signed in?” handling, the popup interception, the MFA TOTP generation, and the B2C custom policy selectors all add up to a substantial test file that breaks the moment Microsoft updates the login page layout, renames a placeholder, or changes the KMSI prompt behavior. Assrt lets you describe the scenario intent in plain English, generates the equivalent Playwright code, and regenerates the selectors automatically when the underlying page changes.
The conditional access MFA scenario from Section 5 demonstrates this clearly. In raw Playwright, you need to know the exact placeholder for the code input, the correct button name for verification, and the conditional logic for switching MFA methods. In Assrt, you describe the intent and let the framework resolve the selectors at runtime.
Assrt compiles each scenario block into the same Playwright TypeScript you saw in the preceding sections, committed to your repo as real tests you can read, run, and modify. When Microsoft renames a placeholder from “Email, phone, or Skype” to “Email address” or restructures the MFA challenge page, Assrt detects the failure, analyzes the new DOM, and opens a pull request with the updated locators. Your scenario files stay untouched.
Full Suite: Playwright vs Assrt
// 5 test files across redirect, popup, MFA, multi-tenant, B2C
// Total: ~250 lines of TypeScript
// Each file handles multi-step form, conditional prompts,
// popup interception, TOTP generation, B2C-specific selectors
// Must be updated when Microsoft changes login page DOM
import { test, expect } from '@playwright/test';
import * as OTPAuth from 'otpauth';
test('redirect login', async ({ page }) => {
// 35 lines of form interaction + waitForURL + conditional prompts
});
test('popup login', async ({ page, context }) => {
// 40 lines of popup interception + form interaction
});
test('MFA challenge', async ({ page }) => {
// 45 lines of TOTP + multi-step form + MFA method switching
});
test('multi-tenant', async ({ page }) => {
// 50 lines of account picker + consent + tenant verification
});
test('B2C custom policy', async ({ page }) => {
// 30 lines of B2C-specific selectors + email verification
});Start with the redirect login happy path. Once it is green in your CI, add the popup login scenario, then the conditional access MFA challenge, then multi-tenant login with account picker, then the B2C custom policy flow, then the session reuse pattern. In a single afternoon you can have complete Azure AD login coverage that most enterprise applications never manage to achieve by hand.
Related Guides
How to Test Auth0 Universal Login
A practical, scenario-by-scenario guide to testing Auth0 Universal Login with Playwright....
How to Test Clerk Sign-In
A practical, scenario-by-scenario guide to testing Clerk authentication with Playwright....
How to Test Firebase Auth
A practical guide to testing Firebase Authentication with Playwright. Covers the emulator...
Ready to automate your testing?
Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.