Testing WebAuthn in CI: E2E Automation with Virtual Authenticators and Mailpit [Part 2]

What You’ll Learn How to test WebAuthn (passkey) authentication in CI environments Automating OTP email retrieval with Mailpit API Preventing email race conditions in parallel E2E tests Locale-specific testing for multilingual UIs Introduction In Part 1, I introduced the overall architecture and automation strategy for “Saru,” a multi-tenant SaaS platform. This article dives deeper into the E2E testing implementation that forms the core of that automation. The most challenging aspect is testing authentication flows. Saru uses two authentication methods: ...

January 13, 2026 · 8 分 · ko-chan

WebAuthn認証をCIで自動テスト:仮想認証器とMailpit連携で実現するE2E【第2回】

この記事で得られること WebAuthn(パスキー)認証をCI環境でテストする方法 OTPメール取得の自動化(Mailpit API連携) 並列E2Eテストでのメール競合を防ぐテクニック 日本語UIを直接テストするlocale-specific testing はじめに 第1回では、マルチテナントSaaS「Saru」の全体像と自動化戦略を紹介した。今回は、その自動化の核となるE2Eテストの実装詳細を掘り下げる。 特に難しいのが認証フローのテストだ。Saruでは2種類の認証方式を採用している: ポータル 認証方式 難しさ System / Provider OTP + パスキー メール取得、WebAuthn Reseller / Consumer Keycloak OAuth 外部IdP連携 これらをすべてCIで自動テストする方法を解説する。 1. WebAuthn仮想認証器:パスキーをCIでテスト パスキー認証の課題 WebAuthn(パスキー)は物理的なセキュリティキーや生体認証を使う。普通に考えると、CI環境でテストするのは不可能に思える。 解決策:Chrome DevTools Protocol (CDP) の仮想認証器 Playwrightでは、CDPを通じて仮想的な認証器を作成できる。これにより、物理デバイスなしでWebAuthnのフルフローをテストできる。 注意: CDP仮想認証器はChromium系ブラウザ限定の機能。Safari(WebKit)やFirefoxでは使用できない。クロスブラウザ対応が必要な場合、WebAuthnテストはChromiumでのみ実行し、他ブラウザでは認証済み状態をモックする等の対策が必要になる。 実装コード 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import { test, expect, type BrowserContext } from '@playwright/test'; test('should complete signup with Passkey registration', async ({ page, context }) => { // 仮想認証器を有効化 const cdpSession = await context.newCDPSession(page); await cdpSession.send('WebAuthn.enable'); // 仮想認証器を追加 await cdpSession.send('WebAuthn.addVirtualAuthenticator', { options: { protocol: 'ctap2', // CTAP2プロトコル transport: 'usb', // USB接続をエミュレート hasResidentKey: true, // パスキー対応 hasUserVerification: true, // 生体認証をエミュレート isUserVerified: true, // 常に認証成功 automaticPresenceSimulation: true, // 自動応答 }, }); // ... サインアップフローを実行 ... // Passkey登録ボタンをクリック await page.getByRole('button', { name: 'Passkey' }).click(); // 仮想認証器が自動的に応答し、登録が完了する await expect(page.getByText('Passkey registered')).toBeVisible(); // クリーンアップ await cdpSession.send('WebAuthn.disable'); }); transport設定とサーバー設定の整合性 WebAuthn仮想認証器を設定する際、サーバー側の設定との整合性が重要になる。 ...

January 13, 2026 · 4 分 · ko-chan