Skip to content
  • Testing
  • Playwright

Test passwordless flows

This guide shows you how to automate passwordless authentication testing with Playwright using Mailosaur. Learn more about testing passwordless flows with Kinde.

Setup Mailosaur

Link to this section
  1. Sign in to your Mailosaur account.

  2. Copy the Server ID from the dashboard.

  3. Go to API Keys > select Create standard key and copy the value.

  4. Install the Mailosaur client in your Playwright project:

    Terminal window
    npm install mailosaur --save-dev
  5. Add the following to your .env file:

    # ... other environment variables ...
    MAILOSAUR_API_KEY=your-api-key
    MAILOSAUR_SERVER_ID=your-server-id
  6. Create a helper utility file tests/utils/mailosaur.ts:

    Terminal window
    mkdir -p tests/utils
    touch tests/utils/mailosaur.ts
  7. Add the following code to mailosaur.ts:

    tests/utils/mailosaur.ts
    import MailosaurClient from "mailosaur"
    require("dotenv").config()
    const mailosaur = new MailosaurClient(process.env.MAILOSAUR_API_KEY!)
    const serverId = process.env.MAILOSAUR_SERVER_ID!
    export async function getOTPFromEmail(
    email: string,
    ): Promise<{
    emailId: string | undefined
    otpCode: string
    deleteEmail: () => Promise<void>
    }> {
    const message = await mailosaur.messages.get(serverId, { sentTo: email })
    const otpMatch = message.text?.body?.match(/\b(\d{6})\b/)
    const otpCode = otpMatch?.[1]
    if (!otpCode) {
    throw new Error("Could not extract OTP code from email")
    }
    return {
    emailId: message.id,
    otpCode,
    deleteEmail: async () => {
    await mailosaur.messages.del(message.id!)
    },
    }
    }

Test passwordless sign-up flow

Link to this section

To test the passwordless sign-up flow, enable the Email + code option in authentication methods in your Kinde application.

  1. Create a new test file:

    Terminal window
    touch tests/passwordless-signup.spec.ts
  2. Add the following code:

    tests/passwordless-signup.spec.ts
    import { test, expect } from "@playwright/test"
    import { getOTPFromEmail } from "./utils/mailosaur"
    const serverId = process.env.MAILOSAUR_SERVER_ID!
    test.describe("Passwordless Authentication Flows", () => {
    test("user can sign up with email and code", async ({ page }) => {
    // Generate unique email for this test run
    const timestamp = Date.now()
    const testEmail = `test-${timestamp}@${serverId}.mailosaur.net`
    await page.goto(process.env.TEST_APP_URL!)
    await page.click('[data-testid="sign-up-button"]')
    // Wait for Kinde sign-up page
    await expect(page).toHaveURL(/kinde\.com/)
    // Fill sign-up form
    await page.fill('input[name="p_first_name"]', "John")
    await page.fill('input[name="p_last_name"]', "Doe")
    await page.fill('input[name="p_email"]', testEmail)
    await page.check('input[name="p_has_clickwrap_accepted"]') // Accept terms and conditions
    await page.click('button[type="submit"]')
    // Wait for OTP email and extract code
    const { otpCode, deleteEmail } = await getOTPFromEmail(testEmail)
    try {
    // Enter OTP
    await page.fill('input[name="p_confirmation_code"]', otpCode)
    await page.click('button[type="submit"]')
    // Wait for successful auth
    const appUrl = new URL(process.env.TEST_APP_URL!)
    await expect(page).toHaveURL(
    new RegExp(appUrl.hostname.replace(".", "\\.")),
    )
    await expect(page.locator('[data-testid="user-profile"]')).toBeVisible()
    } finally {
    // Clean up OTP email
    await deleteEmail()
    }
    })
    })

Test passwordless sign-in flow

Link to this section

To test the passwordless sign-in flow:

  1. Enable the Email + code option in Authentication methods.

  2. Sign up for a new test user using the Mailosaur domain (e.g., test-123@your-server-id.mailosaur.net).

  3. Save the test user email to your .env file as TEST_USER_EMAIL.

  4. Create a new test file:

    Terminal window
    touch tests/passwordless-signin.spec.ts
  5. Add the following code:

    tests/passwordless-signin.spec.ts
    import { test, expect } from '@playwright/test'
    import { getOTPFromEmail } from './utils/mailosaur'
    test.describe('Passwordless Authentication Flows', () => {
    test('user can sign in with email and code', async ({ page }) => {
    await page.goto(process.env.TEST_APP_URL!)
    await page.click('[data-testid="sign-in-button"]')
    // Wait for Kinde sign-in page
    await expect(page).toHaveURL(/kinde\.com/)
    // Fill sign-in form
    await page.fill('input[name="p_email"]', process.env.TEST_USER_EMAIL!)
    await page.click('button[type="submit"]')
    // Wait for OTP email and extract code
    const { otpCode, deleteEmail } = await getOTPFromEmail(
    process.env.TEST_USER_EMAIL!
    )
    try {
    // Enter OTP
    await page.fill('input[name="p_confirmation_code"]', otpCode)
    await page.click('button[type="submit"]')
    // Wait for successful auth
    const appUrl = new URL(process.env.TEST_APP_URL!)
    await expect(page).toHaveURL(
    new RegExp(appUrl.hostname.replace('.', '\\.'))
    )
    await expect(page.locator('[data-testid="user-profile"]')).toBeVisible()
    } finally {
    // Clean up OTP email
    await deleteEmail()
    }
    })
    })

Test sign-up flow with email and password

Link to this section

We have included this example because OTP verification is required for all new users in Kinde regardless of the authentication method used.

This example uses the Kinde email + password authentication method to sign up a new test user.

  1. Create a new test file:

    Terminal window
    touch tests/email-password-signup.spec.ts
  2. Add the following code:

    tests/email-password-signup.spec.ts
    import { test, expect } from '@playwright/test'
    import { getOTPFromEmail } from './utils/mailosaur'
    const serverId = process.env.MAILOSAUR_SERVER_ID!
    test.describe('Authentication Flows', () => {
    test('user can sign up with email and password', async ({ page }) => {
    // Generate unique email for this test run
    const timestamp = Date.now()
    const testEmail = `test-${timestamp}@${serverId}.mailosaur.net`
    await page.goto(process.env.TEST_APP_URL!)
    await page.click('[data-testid="sign-up-button"]')
    // Wait for Kinde sign-up page
    await expect(page).toHaveURL(/kinde\.com/)
    // Fill sign-up form
    await page.fill('input[name="p_first_name"]', 'John')
    await page.fill('input[name="p_last_name"]', 'Doe')
    await page.fill('input[name="p_email"]', testEmail)
    await page.check('input[name="p_has_clickwrap_accepted"]') // Accept terms and conditions
    await page.click('button[type="submit"]')
    // Wait for OTP email
    const { otpCode, deleteEmail } = await getOTPFromEmail(testEmail)
    try {
    // Enter OTP
    await page.fill('input[name="p_confirmation_code"]', otpCode)
    await page.click('button[type="submit"]')
    // Enter password
    await page.fill(
    'input[name="p_first_password"]',
    process.env.TEST_USER_PASSWORD!
    )
    await page.fill(
    'input[name="p_second_password"]',
    process.env.TEST_USER_PASSWORD!
    )
    await page.click('button[type="submit"]')
    // Wait for successful auth
    const appUrl = new URL(process.env.TEST_APP_URL!)
    await expect(page).toHaveURL(
    new RegExp(appUrl.hostname.replace('.', '\\.'))
    )
    await expect(page.locator('[data-testid="user-profile"]')).toBeVisible()
    } finally {
    // Clean up OTP email
    await deleteEmail()
    }
    })
    })
  1. Use the following command:

    Terminal window
    npx playwright test --ui
  2. Select the test spec to run.

    You should now see the Playwright tests passing in the browser.

    user can sign up with email and code

  3. Go to your Kinde dashboard > Users to find the test users you created.

    Test users in Kinde dashboard