SDKs

Playwright Integration

InboxLens pairs naturally with Playwright for end-to-end email testing. Use a custom fixture to share an inbox client across your spec files, then assert on real emails as part of your user flows.

Prerequisites

  • Playwright ^1.40 or later
  • Node.js 18+ (ESM or CommonJS, both supported)
  • An InboxLens API key (see Quick Start)

1. Install the SDK

Add the InboxLens SDK as a dev dependency alongside your existing Playwright install:

bash
npm install --save-dev @inboxlens/sdk

2. Set environment variables

bash
# .env.test (or CI environment variables)
INBOXLENS_API_KEY=il_live_your_key_here
INBOXLENS_BASE_URL=https://testmail.neio.ai   # omit for default

3. Create the inbox fixture

Create a fixture file that wraps the InboxLens client. Playwright fixtures are composable — you can layer the inbox fixture on top of your existing auth fixtures.

typescript
// tests/email.fixture.ts
import { test as base } from '@playwright/test';
import { InboxLens } from '@inboxlens/sdk';

const inbox = new InboxLens({
  apiKey: process.env.INBOXLENS_API_KEY!,
  baseUrl: process.env.INBOXLENS_BASE_URL ?? 'https://testmail.neio.ai',
});

// Extend Playwright's base test with an inbox fixture
export const test = base.extend<{ inbox: InboxLens }>({
  inbox: async ({}, use) => {
    await use(inbox);
  },
});

export { expect } from '@playwright/test';

4. Configure Playwright

typescript
// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    baseURL: 'http://localhost:3000',
  },
  // Pass your InboxLens API key as an env var
  // INBOXLENS_API_KEY=il_live_... npx playwright test
});

Examples

Signup welcome email

The canonical pattern: fill a form, click submit, then assert the resulting email arrived with the expected content.

typescript
// tests/signup.spec.ts
import { test, expect } from './email.fixture';

test('signup flow sends a welcome email', async ({ page, inbox }) => {
  // Generate a unique inbox address for this test run
  const email = `test+${Date.now()}@acme.testmail.neio.ai`;

  // Navigate and submit the signup form
  await page.goto('/signup');
  await page.fill('[name=email]', email);
  await page.fill('[name=password]', 'Password123!');
  await page.click('button[type=submit]');

  // Wait up to 30s for the welcome email
  const message = await inbox.messages.waitForLatest({ to: email, timeout: 30 });

  expect(message.subject).toBe('Welcome to Acme!');
  expect(message.html).toContain('confirm your email');
});

Password reset link

Extract links from the email HTML and follow them in the same Playwright context.

typescript
// tests/password-reset.spec.ts
import { test, expect } from './email.fixture';

test('password reset email contains a working link', async ({ page, inbox }) => {
  const email = `reset+${Date.now()}@acme.testmail.neio.ai`;

  // Request a password reset
  await page.goto('/forgot-password');
  await page.fill('[name=email]', email);
  await page.click('button[type=submit]');

  // Wait for the email
  const message = await inbox.messages.waitForLatest({ to: email, timeout: 30 });
  expect(message.subject).toContain('Reset your password');

  // Extract the reset link from the HTML body
  const linkMatch = message.html?.match(/href="(https?://[^"]+reset[^"]+)"/);
  expect(linkMatch).not.toBeNull();

  const resetLink = linkMatch![1];

  // Follow the link and verify the reset page loads
  await page.goto(resetLink);
  await expect(page.locator('h1')).toContainText('Choose a new password');
});

Team invite flow

typescript
// tests/team-invite.spec.ts
import { test, expect } from './email.fixture';

test('inviting a team member sends an invite email', async ({ page, inbox }) => {
  const inviteeEmail = `invite+${Date.now()}@acme.testmail.neio.ai`;

  // Log in as an admin and invite a new member
  await page.goto('/login');
  await page.fill('[name=email]', 'admin@acme.com');
  await page.fill('[name=password]', 'AdminPass123!');
  await page.click('button[type=submit]');

  await page.goto('/settings/team');
  await page.fill('[placeholder="colleague@company.com"]', inviteeEmail);
  await page.click('text=Send invite');

  // Wait for the invite email
  const message = await inbox.messages.waitForLatest({ to: inviteeEmail, timeout: 30 });
  expect(message.subject).toContain("You've been invited");

  // Extract the invite token and verify it works
  const tokenMatch = message.html?.match(/\/invite\/([a-z0-9-]+)/i);
  expect(tokenMatch).not.toBeNull();
});

Automatic inbox cleanup

Add an inboxAddress fixture that auto-cleans up messages after each test, keeping your inbox hygiene perfect in CI.

typescript
// tests/email.fixture.ts — with automatic inbox cleanup
export const test = base.extend<{
  inbox: InboxLens;
  inboxAddress: string;
}>({
  inbox: async ({}, use) => {
    await use(inbox);
  },

  // Generates a unique address and cleans it up after each test
  inboxAddress: async ({ inbox }, use) => {
    const address = `test+${Date.now()}@acme.testmail.neio.ai`;
    await use(address);
    // Teardown: delete all messages at this address
    await inbox.messages.clearInbox({ to: address });
  },
});

Tips for CI

Use unique addresses per test

Append Date.now() or a UUID to the local part so parallel test runs never cross-contaminate each other's inboxes.

Set a realistic timeout

30 seconds is enough for most transactional email flows. If your app uses a queue, set it higher — but never higher than your CI job timeout.

Clean up in teardown

Use the inboxAddress fixture shown above so each test starts with a clean inbox, even if a previous run crashed mid-test.

Store the API key as a CI secret

Add INBOXLENS_API_KEY to your GitHub Actions / GitLab CI secrets and reference it in your workflow env block.