1. Vì sao passkey trở thành bài toán kiểm thử của ngân hàng
Passkey là chuẩn đăng nhập không mật khẩu dựa trên WebAuthn: thay vì gõ mật khẩu, người dùng xác thực bằng khoá riêng lưu trên thiết bị (vân tay, Face ID, khoá bảo mật). Với ngân hàng, passkey giảm mạnh rủi ro lừa đảo và nhồi thông tin đăng nhập, nên nhiều tổ chức đang chuyển sang bắt buộc passkey cho 2FA hoặc đăng nhập chính. Nhưng điều này đặt ra bài toán kiểm thử hóc búa: làm sao test luồng đăng nhập passkey một cách tự động, lặp lại được, mà không cần vân tay thật hay thiết bị vật lý? Playwright 1.61 trả lời bằng WebAuthn virtual authenticator.
- Passkey dùng cặp khoá công khai/riêng, khoá riêng không rời thiết bị.
- Không có mật khẩu để đánh cắp hay nhồi — chống phishing tốt hơn.
- Ngân hàng bắt buộc passkey cho giao dịch giá trị cao hoặc đổi thông tin.
- Thách thức test: tự động hoá xác thực sinh trắc mà không có thiết bị thật.
2. WebAuthn virtual authenticator: khái niệm cốt lõi
Virtual authenticator là một trình xác thực WebAuthn ảo do trình duyệt cung cấp qua giao thức CDP, cho phép test tạo passkey trong bộ nhớ và ký thử thách như một thiết bị thật, nhưng hoàn toàn bằng phần mềm. Playwright 1.61 gói tính năng này thành API tiện dụng. Bạn khai báo loại authenticator (ctap2 cho khoá bảo mật, internal cho sinh trắc gắn máy), giao thức truyền (usb, nfc, internal), và các cờ như userVerified hay hasResidentKey. Từ đó, khi trang gọi navigator.credentials.create hay get, trình duyệt dùng authenticator ảo để đáp ứng, không hề bật hộp thoại sinh trắc thật.
// Bật WebAuthn virtual authenticator (Playwright 1.61)
import { test, expect } from '@playwright/test';
test('đăng nhập bằng passkey', async ({ page }) => {
// Tạo trình xác thực ảo qua CDP session
const client = await page.context().newCDPSession(page);
await client.send('WebAuthn.enable');
const { authenticatorId } = await client.send('WebAuthn.addVirtualAuthenticator', {
options: {
protocol: 'ctap2',
transport: 'internal',
hasResidentKey: true,
hasUserVerification: true,
isUserVerified: true, // giả lập vân tay/Face ID đã xác minh
automaticPresenceSimulation: true,
},
});
await page.goto('/login');
await page.getByRole('button', { name: 'Đăng nhập bằng passkey' }).click();
await expect(page.getByTestId('dashboard')).toBeVisible();
});3. Đăng ký passkey mới (registration) trong test
Trước khi test đăng nhập, ứng dụng phải có ít nhất một passkey đã đăng ký cho tài khoản. Có hai cách: một là để chính test thực hiện luồng đăng ký (gọi navigator.credentials.create thông qua UI), authenticator ảo sẽ tự tạo và lưu credential; hai là chèn sẵn credential vào authenticator qua lệnh addCredential nếu bạn muốn tách bước đăng ký khỏi test đăng nhập. Cách đầu kiểm được cả luồng đăng ký end-to-end; cách sau nhanh và cô lập hơn khi bạn chỉ quan tâm tới đăng nhập. Cả hai đều không cần thiết bị thật.
// Cách 1: chạy luồng đăng ký qua UI, authenticator ảo tự lưu credential
test('đăng ký passkey rồi đăng nhập lại', async ({ page }) => {
const client = await page.context().newCDPSession(page);
await client.send('WebAuthn.enable');
await client.send('WebAuthn.addVirtualAuthenticator', {
options: { protocol: 'ctap2', transport: 'internal',
hasResidentKey: true, hasUserVerification: true, isUserVerified: true },
});
await page.goto('/settings/security');
await page.getByRole('button', { name: 'Thêm passkey' }).click();
await expect(page.getByText('Passkey đã được tạo')).toBeVisible();
// Đăng xuất rồi đăng nhập lại bằng passkey vừa tạo
await page.getByRole('button', { name: 'Đăng xuất' }).click();
await page.getByRole('button', { name: 'Đăng nhập bằng passkey' }).click();
await expect(page.getByTestId('dashboard')).toBeVisible();
});❓ Nên test đăng ký và đăng nhập passkey trong cùng một test hay tách ra?
Tuỳ mục tiêu. Có một test end-to-end đi qua cả đăng ký lẫn đăng nhập để chứng minh luồng thật liền mạch. Nhưng cho các test đăng nhập biến thể (thất bại, đổi thiết bị, khoá bị thu hồi), hãy chèn sẵn credential để cô lập, tránh phụ thuộc vào bước đăng ký và giữ test nhanh, ổn định.
💬 Bình luận (0)
Chưa có bình luận nào. Hãy là người đầu tiên!