パスキー(WebAuthn)をテストする
ユースケース: アプリがパスキーによる登録・ログイン(WebAuthn)に対応している。セキュリティキーを挿さず、Touch IDのプロンプトも出さずに、「ユーザーがパスキーを登録し、それで再ログインできる」ことをCIで証明したい。
なぜ難しいのか
Section titled “なぜ難しいのか”WebAuthnは意図的にハードウェア(プラットフォーム認証器やセキュリティキー)と、どんな自動化からもクリックできないブラウザネイティブのプロンプトを介して通信します。Playwrightにはパスキー用のAPIがなく、ヘッドレス環境では navigator.credentials.create() は単に応答を返さなくなります。
ChromiumにはCDP経由で使える仮想認証器が用意されています。テストから制御できるソフトウェア版セキュリティキーです:
import { test, expect } from '@playwright/test';
test('register a passkey, then sign in with it', async ({ page }) => { const cdp = await page.context().newCDPSession(page); await cdp.send('WebAuthn.enable'); const { authenticatorId } = await cdp.send('WebAuthn.addVirtualAuthenticator', { options: { protocol: 'ctap2', transport: 'internal', // プラットフォーム認証器(Touch ID相当) hasResidentKey: true, // パスキーはディスカバラブルクレデンシャル hasUserVerification: true, isUserVerified: true, // 「指紋が一致した」状態 automaticPresenceSimulation: true // プロンプトを自動承認 }, });
// 登録 await page.goto('/account/security'); await page.getByRole('button', { name: 'Add a passkey' }).click(); await expect(page.getByText('Passkey added')).toBeVisible();
// (仮想)デバイス上にクレデンシャルが実際に存在することを確認: const { credentials } = await cdp.send('WebAuthn.getCredentials', { authenticatorId }); expect(credentials).toHaveLength(1);
// サインアウトし、パスキーで再サインイン await page.goto('/logout'); await page.goto('/login'); await page.getByRole('button', { name: 'Sign in with a passkey' }).click(); await expect(page).toHaveURL('/dashboard');});異常系のテスト
Section titled “異常系のテスト”仮想認証器が本領を発揮するのは、実機ではスクリプト化できない異常系です:
// ユーザーがキャンセル / 検証に失敗(違う指でタッチ):await cdp.send('WebAuthn.setUserVerified', { authenticatorId, isUserVerified: false });
// クレデンシャルが入っていないデバイス(買い替え直後のPC):await cdp.send('WebAuthn.removeCredential', { authenticatorId, credentialId });
// プラットフォーム認証器ではなくローミング型セキュリティキー:// transport: 'usb', hasResidentKey: falseUIが適切にフォールバックすること — エラーメッセージ、パスワードへの切り替え、再登録フロー — をアサートしましょう。
- 仮想認証器はChromium限定です。Firefox/WebKitにはPlaywrightから触れる同等機能がありません。サーバー側のWebAuthnセレモニーを下位レイヤーのテスト(例:
@simplewebauthn/serverのテストヘルパー)でカバーしてください。 - WebAuthnにはセキュアコンテキスト(
https://またはlocalhost)が必須です。CIではlocalhostを使うか、開発用証明書を信頼させてください。 - クレデンシャルはRP ID(ドメイン)にスコープされます。
staging.example.comに対するテストが通っても、example.comのオリジン/サブドメイン設定が正しいことの証明にはなりません。
- TOTP二要素ログインをテストする — もう一つの第二要素。