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.40or 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:
npm install --save-dev @inboxlens/sdk2. Set environment variables
# .env.test (or CI environment variables)
INBOXLENS_API_KEY=il_live_your_key_here
INBOXLENS_BASE_URL=https://testmail.neio.ai # omit for default3. 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.
// 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
// 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.
// 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.
// 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
// 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.
// 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.