Automating end-to-end flows like user registration, password resets, or OTP verifications often hits a wall when dealing with emails. Automating UI login for webmail providers like Gmail or Outlook is flaky and frequently blocked by 2FA or CAPTCHAs.
Using Microsoft Graph API with an Azure App Registration (Client Credentials Flow) lets you fetch target user inbox messages programmatically in your Playwright tests without any UI interaction.
1. Prerequisites & Azure Setup
Before writing test code, configure your Azure Entra ID (formerly Azure AD):
- Register an App: Go to Azure Portal > App registrations > New registration.
- Set Permissions: Navigate to API permissions > Add a permission > Microsoft Graph > Application permissions.
- Select Mail.Read (or Mail.ReadWrite if you plan to delete emails post-test).
- Grant Admin Consent: Click Grant admin consent for [Your Org].
- Create Credentials: Go to Certificates & secrets > New client secret. Store the secret securely.
Collect the following environment variables:
- AZURE_TENANT_ID
- AZURE_CLIENT_ID
- AZURE_CLIENT_SECRET
2. Install Required Dependencies
Install the Azure Identity SDK and Microsoft Graph Client:
npm install @azure/identity @microsoft/microsoft-graph-client
3. Build the Graph Email Service
Create a dedicated helper service (graphService.ts) to manage authentication, inbox polling, and token extraction.
import { ClientSecretCredential } from '@azure/identity';
import { Client } from '@microsoft/microsoft-graph-client';
import { TokenCredentialAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials';
export class GraphService {
private graphClient: Client;
constructor() {
const tenantId = process.env.AZURE_TENANT_ID!;
const clientId = process.env.AZURE_CLIENT_ID!;
const clientSecret = process.env.AZURE_CLIENT_SECRET!;
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
scopes: ['[https://graph.microsoft.com/.default](https://graph.microsoft.com/.default)'],
});
this.graphClient = Client.initWithMiddleware({ authProvider });
}
/**
* Polls the inbox for an email matching specific subject criteria within a timeout.
*/
async getLatestEmail(targetEmail: string, subjectContains: string, timeoutMs = 30000): Promise<string> {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
const response = await this.graphClient
.api(/users/${targetEmail}/messages)
.filter(contains(subject, '${subjectContains}'))
.select('subject,body,receivedDateTime')
.orderby('receivedDateTime desc')
.top(1)
.get();
if (response.value && response.value.length > 0) {
return response.value[0].body.content;
}
// Wait 3 seconds before polling again
await new Promise((resolve) => setTimeout(resolve, 3000));
}
throw new Error(Email with subject containing "${subjectContains}" was not received within ${timeoutMs}ms.);
}
/**
* Helper to extract links or OTP codes using regular expressions.
*/
extractPattern(htmlContent: string, pattern: RegExp): string {
const match = htmlContent.match(pattern);
if (!match) {
throw new Error(Pattern ${pattern} not found in the email content.);
}
return match[1] || match[0];
}
}
4. Integrate with Playwright Tests
Use the service directly inside your Playwright test file (emailValidation.spec.ts):
import { test, expect } from '@playwright/test';
import { GraphService } from './graphService';
test.describe('Registration & Email Verification Flow', () => {
const graphService = new GraphService();
const testUserEmail = 'qa-automation@yourdomain.com';
test('User registers and verifies OTP from email', async ({ page }) => {
// 1. Trigger signup action in web app
await page.goto('[https://example.com/register](https://example.com/register)');
await page.fill('#email', testUserEmail);
await page.click('#submit-btn');
// 2. Fetch latest email and extract 6-digit OTP
const emailBody = await graphService.getLatestEmail(testUserEmail, 'Your Verification Code');
const otpCode = graphService.extractPattern(emailBody, /\b\d{6}\b/);
// 3. Complete verification on page
await page.fill('#otp-input', otpCode);
await page.click('#verify-btn');
// 4. Assert successful navigation
await expect(page.locator('#dashboard')).toBeVisible();
});
});
Key Takeaways
- No Flaky UI: API-level retrieval eliminates UI dependency for email checking.
- Polling Strategy: Always implement retry loops with timeouts to account for network latency in email delivery.
- Security: Limit application permissions in Azure using Application Access Policies if you need to restrict access to specific automated QA mailboxes.
Top comments (0)