DEV Community

Shefali R
Shefali R

Posted on

Automate Email Verification in Playwright using Microsoft Graph API & TypeScript

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):

  1. Register an App: Go to Azure Portal > App registrations > New registration.
  2. Set Permissions: Navigate to API permissions > Add a permission > Microsoft Graph > Application permissions.
  3. Select Mail.Read (or Mail.ReadWrite if you plan to delete emails post-test).
  4. Grant Admin Consent: Click Grant admin consent for [Your Org].
  5. 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
Enter fullscreen mode Exit fullscreen mode

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];
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
  });
});

Enter fullscreen mode Exit fullscreen mode

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)