Tìm hiểu cách thiết lập kiểm thử E2E cho passkey trên các trình duyệt với Playwright, Nightwatch, Selenium và Puppeteer bằng trình xác thực ảo WebAuthn.
Anders
Created: June 17, 2025
Updated: June 20, 2025
Sứ mệnh của chúng tôi là làm cho Internet trở thành một nơi an toàn hơn, và tiêu chuẩn login mới passkey cung cấp một giải pháp vượt trội để đạt được điều đó. Đó là lý do tại sao chúng tôi muốn giúp bạn hiểu rõ hơn về passkey và các đặc điểm của nó.
Passkey ngày càng được chấp nhận rộng rãi như một phương thức xác thực, dựa trên tiêu chuẩn nền tảng là Web Authentication (WebAuthn). Sự phổ biến của chúng chỉ mới tăng lên gần đây, điều này làm cho tài liệu và các nguồn thông tin khác tương đối khan hiếm. Điều này, cùng với bản chất phức tạp của việc triển khai passkey, có thể gây khó khăn cho các nhà phát triển trong việc tìm kiếm thông tin liên quan về thiết kế, triển khai và đặc biệt là kiểm thử passkey cho các nền tảng và dịch vụ của họ.
Hướng dẫn này nhằm mục đích lấp đầy khoảng trống đó, tập trung vào các khía cạnh của trình xác thực ảo WebAuthn không được đề cập kỹ lưỡng trong tài liệu chính thức của nó. Ví dụ, chúng tôi thảo luận về các tùy chọn cấu hình cho trình xác thực ảo không tự giải thích trong tài liệu, cũng như các giải pháp thay thế cho một số trường hợp sử dụng mà trình xác thực ảo không cung cấp giải pháp tiện lợi. Ngoài ra, hướng dẫn này cũng hữu ích cho các nhà phát triển chỉ đơn giản là đang tìm kiếm các ví dụ dễ thực hiện để sử dụng trình xác thực ảo trong mã kiểm thử.
Hướng dẫn của chúng tôi sử dụng các ví dụ từ Playwright để cung cấp một hướng dẫn đơn giản để kiểm thử hiệu quả việc triển khai passkey trong dự án của bạn. Playwright là một framework kiểm thử end-to-end (E2E) sử dụng Giao thức Công cụ dành cho nhà phát triển của Chrome (CDP) làm giao thức tự động hóa trình duyệt. Nếu bạn đang tìm kiếm cụ thể các ví dụ kỹ thuật về kiểm thử passkey trong Playwright, bạn có thể chuyển thẳng đến Phần 5. Mặt khác, nếu bạn đang sử dụng các framework kiểm thử E2E khác như Puppeteer hoặc Selenium và muốn kiểm thử passkey trên các framework này, việc triển khai mã kiểm thử sẽ giống hệt hoặc rất giống với các ví dụ được cung cấp trong hướng dẫn này, tùy thuộc vào framework bạn đang sử dụng. Trong phần tiếp theo, chúng tôi sẽ cung cấp thông tin nền tảng về các framework kiểm thử E2E khác nhau và mức độ liên quan của hướng dẫn này đối với các framework đó.
Recent Articles
Tự động hóa trình duyệt, như tên gọi, là quá trình tự động hóa các hành động lặp đi lặp lại của người dùng trên trình duyệt cho mục đích thu thập dữ liệu web hoặc trong trường hợp của chúng ta là kiểm thử các ứng dụng web. WebDriver và Giao thức Công cụ dành cho nhà phát triển của Chrome (CDP) là hai trong số các giao thức tự động hóa trình duyệt chính có liên quan đến hướng dẫn này, vì mỗi giao thức đều cung cấp một triển khai của trình xác thực ảo WebAuthn.
WebDriver là một giao diện điều khiển từ xa có thể được xem như một người trung gian trong giao tiếp giữa client và trình duyệt. Trọng tâm của giao thức này là cung cấp một giao diện trung lập về nền tảng và ngôn ngữ, hỗ trợ tất cả các trình duyệt chính, bao gồm cả những trình duyệt không dựa trên Chromium như Firefox và Safari. Vì giao diện WebDriver cần quản lý kết nối với client cũng như với trình duyệt, cách tiếp cận này hy sinh tốc độ và sự ổn định để đổi lấy phạm vi hỗ trợ trình duyệt rộng hơn (tức là độ không ổn định cao hơn). Các client WebDriver đáng chú ý bao gồm Selenium và Nightwatch.
Nguồn: jankaritech
Mặt khác, Giao thức Công cụ dành cho nhà phát triển của Chrome (CDP) không có người trung gian như giao diện WebDriver giữa client và trình duyệt. Ngoài ra, giao tiếp giữa client và trình duyệt diễn ra thông qua kết nối socket, trái ngược với kết nối HTTP chậm hơn giữa client và giao diện WebDriver trong cách tiếp cận trước đó. Những điểm này làm cho CDP nhanh hơn và ít không ổn định hơn WebDriver. Nhược điểm là giao thức này chỉ được hỗ trợ cho các trình duyệt dựa trên Chromium như Chrome và Edge. Playwright và Puppeteer là các client ví dụ sử dụng CDP để giao tiếp với trình duyệt.
Nguồn: jankaritech
Puppeteer, tương tự như Playwright, là một framework E2E được xây dựng trực tiếp trên CDP. Điều này có nghĩa là cả Puppeteer và Playwright đều sử dụng cùng một triển khai của trình xác thực ảo WebAuthn và giao tiếp API sử dụng trình xác thực ảo WebAuthn qua kết nối socket cũng giống hệt nhau.
Để minh họa, chúng tôi so sánh mã kiểm thử trong cả Playwright và Puppeteer để gọi phương thức getCredentials, phương thức này trả về danh sách tất cả các thông tin xác thực đã được đăng ký vào trình xác thực ảo cho đến nay. Chúng tôi cũng đính kèm một trình lắng nghe sự kiện đơn giản cho sự kiện credentialAdded được kích hoạt khi một thông tin xác thực passkey được đăng ký thành công. Đừng lo lắng về các chi tiết của việc triển khai, vì chúng sẽ được giải thích trong các phần sau. Những ví dụ này chỉ đơn giản là để chứng minh sự tương đồng trong việc triển khai giữa hai framework.
Ben Gould
Head of Engineering
I’ve built hundreds of integrations in my time, including quite a few with identity providers and I’ve never been so impressed with a developer experience as I have been with Corbado.
Hơn 10.000 nhà phát triển tin tưởng Corbado và cùng làm cho Internet an toàn hơn với passkey. Bạn có câu hỏi? Chúng tôi đã viết hơn 150 bài blog về passkey.
Tham gia Cộng đồng PasskeyPlaywright:
const client = await page.context().newCDPSession(page); await client.send('WebAuthn.enable'); const authenticatorId = const result = await client.send('WebAuthn.addVirtualAuthenticator', { options }); ... // get all credentials registered in the virtual authenticator const result = await client.send('WebAuthn.getCredentials', { authenticatorId }); console.log(result.credentials); // add a listener to the credentialAdded event to output a log to the console whenever a passkey credential is registered client.on('WebAuthn.credentialAdded', () => { console.log('Credential Added!'); });
Puppeteer:
const client = await page.target().createCDPSession(); await client.send('WebAuthn.enable'); const authenticatorId = const result = await client.send('WebAuthn.addVirtualAuthenticator', { options }); ... // get all credentials registered in the virtual authenticator const result = await client.send('WebAuthn.getCredentials', { authenticatorId }); console.log(result.credentials); // add a listener to the credentialAdded event to output a log to the console whenever a passkey credential is registered client.on('WebAuthn.credentialAdded', () => { console.log('Credential Added!'); });
Mặc dù các phương thức để khởi tạo phiên CDP ở đầu mã kiểm thử có một chút khác biệt, việc gọi các phương thức và xử lý các sự kiện trong API trình xác thực ảo WebAuthn của CDP là giống hệt nhau. Điều này có nghĩa là nếu bạn đang muốn sử dụng trình xác thực ảo WebAuthn trong Puppeteer, bạn có thể theo dõi hướng dẫn này từng dòng một.
Selenium và Nightwatch là các framework kiểm thử E2E dựa vào WebDriver để tự động hóa trình duyệt. Mặc dù việc triển khai trình xác thực ảo WebAuthn cho WebDriver là riêng biệt so với việc triển khai cho CDP, các thông số kỹ thuật API của chúng tương tự nhau. Đối với hầu hết mọi phương thức trong API trình xác thực ảo WebAuthn của CDP, bạn có thể tìm thấy một phương thức tương ứng trong API trình xác thực ảo WebAuthn của WebDriver. Tuy nhiên, một điều cần lưu ý là mặc dù có thể đính kèm các trình lắng nghe sự kiện khi một passkey được thêm hoặc xác nhận thành công trong API trình xác thực ảo WebAuthn của CDP, điều này không thể thực hiện được trong phiên bản WebDriver.
Selenium:
const driver = await new Builder().forBrowser('chrome').build(); const options = new VirtualAuthenticatorOptions(); await driver.addVirtualAuthenticator(options); ... // get all credentials registered in the virtual authenticator const credentials = await driver.getCredentials();
Rõ ràng là cú pháp thiết lập phiên bản trình xác thực ảo và thực hiện các cuộc gọi API khác với việc triển khai CDP tương ứng. Tuy nhiên, vì các thông số kỹ thuật API của hai trình xác thực ảo WebAuthn rất giống nhau, bạn hoàn toàn có thể theo dõi hướng dẫn này để viết một triển khai tương ứng trên một framework kiểm thử E2E dựa trên WebDriver.
Cypress là một framework kiểm thử E2E không được xây dựng chủ yếu trên WebDriver hoặc CDP như các framework đã đề cập ở trên. Nó sử dụng JavaScript gốc để giao tiếp với trình duyệt. Tuy nhiên, nó cung cấp quyền truy cập cấp thấp vào CDP, điều đó có nghĩa là có thể gửi các lệnh CDP thô để sử dụng trình xác thực ảo WebAuthn của CDP.
Bởi vì cú pháp cho quyền truy cập cấp thấp này khá rườm rà và rất khác so với các ví dụ trên, chúng tôi sẽ không đi vào chi tiết trong hướng dẫn này. Tuy nhiên, thông tin thêm về cách gọi các lệnh CDP trong Cypress được giải thích trong hướng dẫn này. Các khái niệm tổng quan về việc sử dụng trình xác thực ảo WebAuthn của CDP được trình bày trong hướng dẫn này vẫn phù hợp cho những ai muốn kiểm thử passkey trên Cypress.
Có nhiều lý do tại sao việc kiểm thử triển khai passkey tự nhiên lại khó khăn hơn so với các hành động người dùng đơn giản khác trong môi trường web. Nhu cầu xử lý các tương tác người dùng động liên quan đến xác thực sinh trắc học, chẳng hạn như quét vân tay hoặc nhận dạng khuôn mặt, thêm một lớp phức tạp có thể không thực tế để giải quyết chi tiết khi viết kiểm thử. Vì bảo mật tự nhiên là một mối quan tâm lớn trong bối cảnh xác thực, cũng cần phải đảm bảo rằng xác thực passkey được tích hợp liền mạch trên các trình duyệt và thiết bị khác nhau mà không có chỗ cho các lỗ hổng bảo mật.
Việc đơn giản hóa sự phức tạp của việc xử lý các tương tác người dùng động liên quan đến các hoạt động passkey, cũng như kiểm thử sự tích hợp của nó vào các trình duyệt và thiết bị khác nhau, trở nên dễ dàng hơn bằng cách sử dụng trình xác thực ảo WebAuthn.
Trình xác thực ảo WebAuthn là một biểu diễn phần mềm của mô hình trình xác thực được chỉ định trong tiêu chuẩn WebAuthn. Nó mô phỏng hành vi của một thiết bị trình xác thực vật lý, chẳng hạn như khóa bảo mật phần cứng (ví dụ: YubiKey) hoặc máy quét sinh trắc học (ví dụ: được sử dụng trong Face ID, Touch ID hoặc Windows Hello), nhưng hoạt động hoàn toàn trong phần mềm (vì vậy không có xác thực vật lý hoặc quét sinh trắc học nào liên quan).
Có hai lợi ích chính của trình xác thực ảo WebAuthn.
Vì WebDriver và CDP là các công cụ tự động hóa trình duyệt, rõ ràng trường hợp sử dụng chính của việc triển khai trình xác thực ảo WebAuthn trong các giao thức này là kiểm thử tự động. Tận dụng các giao thức này, trình xác thực ảo cho phép kiểm thử đơn giản nhưng toàn diện các chức năng của passkey trong các môi trường được kiểm soát như các framework kiểm thử E2E (ví dụ: Playwright, Cypress, Nightwatch).
Trình xác thực ảo WebAuthn của CDP cũng có thể truy cập được thông qua DevTools của trình duyệt Chrome, và nó có thể được sử dụng để kiểm thử thủ công hoặc đơn giản là cho mục đích trình diễn. Với tính năng này, bạn có thể mô phỏng việc nhập passkey trên một thiết bị không hỗ trợ passkey nguyên bản. Tương ứng, cũng có thể mô phỏng một môi trường không hỗ trợ passkey trên một thiết bị có hỗ trợ passkey.
Ảnh chụp màn hình ở trên cho thấy một ví dụ về việc sử dụng trình xác thực ảo trong Chrome cho mục đích kiểm thử thủ công hoặc trình diễn. Bạn có thể thấy rằng có thể có các tùy chọn cấu hình khác nhau cho trình xác thực ảo, và việc thêm và xóa thông tin xác thực cũng có thể được theo dõi. Tham khảo hướng dẫn này từ Google để biết thêm thông tin về việc sử dụng trình xác thực ảo trong trình duyệt của bạn, bao gồm các tùy chọn cấu hình và các giá trị được đề xuất cho mỗi tùy chọn.
Trong khi trình xác thực ảo WebAuthn là một giải pháp thanh lịch để kiểm thử việc triển khai passkey, có một vài nhược điểm đáng chú ý.
Là một giải pháp hoàn toàn dựa trên phần mềm, trình xác thực ảo WebAuthn không thể sao chép các đặc điểm phần cứng độc đáo và các tính năng bảo mật của các trình xác thực vật lý. Sự khác biệt giữa việc sử dụng các trình xác thực nền tảng khác nhau (được tích hợp vào một thiết bị, chẳng hạn như máy quét sinh trắc học trên điện thoại thông minh) và các trình xác thực đa nền tảng khác nhau (là các thiết bị bên ngoài, như khóa bảo mật phần cứng) không thể được mô phỏng bằng trình xác thực ảo WebAuthn. Mặc dù việc đơn giản hóa hộp đen các sự phức tạp liên quan đến các loại trình xác thực nền tảng và đa nền tảng khác nhau là một trong những lợi thế của việc sử dụng trình xác thực ảo WebAuthn, nếu bạn tìm cách mô phỏng và kiểm thử các sắc thái của các loại trình xác thực khác nhau, các giải pháp khác nên được khám phá.
Do việc áp dụng WebAuthn tương đối gần đây và sự mới mẻ của công nghệ passkey, hệ sinh thái xung quanh các trình xác thực ảo vẫn đang trong giai đoạn phát triển. Điều này dẫn đến sự khan hiếm tài liệu toàn diện và các thách thức kỹ thuật chưa được giải quyết, đặc biệt là trong bối cảnh tích hợp các trình xác thực ảo với các framework kiểm thử tự động. Hướng dẫn này nhằm giải quyết vấn đề này bằng cách cung cấp những hiểu biết toàn diện về việc kiểm thử passkey trong môi trường kiểm thử tự động, đồng thời tập trung vào việc giải quyết những bất tiện vẫn còn tồn tại khi sử dụng các công cụ này và trình bày các giải pháp thay thế cho những vấn đề đó.
Sau khi cài đặt thành công Playwright và các phụ thuộc của nó, bạn có thể bắt đầu viết bài kiểm thử đầu tiên của mình ngay lập tức bằng cách tạo một tệp có tên kết thúc bằng .spec.ts hoặc .test.ts với nội dung sau:
import { test, expect } from "@playwright/test"; test("my first test", async ({ page }) => { await page.goto("https://passkeys.eu"); // start simulating user actions });
Để sử dụng trình xác thực ảo WebAuthn trong Playwright, chỉ cần khởi tạo một phiên CDP và đính kèm một trình xác thực ảo vào đầu một trường hợp kiểm thử, như sau:
test('signup with passkey', async ({ page }) => { // Initialize a CDP session for the current page const client = await page.context().newCDPSession(page); // Enable WebAuthn environment in this session await client.send('WebAuthn.enable'); // Attach a virtual authenticator with specific options const result = await client.send('WebAuthn.addVirtualAuthenticator', { options: { protocol: 'ctap2', transport: 'internal', hasResidentKey: true, hasUserVerification: true, isUserVerified: true, automaticPresenceSimulation: false, }, }); const authenticatorId = result.authenticatorId; // Further test steps to simulate user interactions and assertions ... });
Các tùy chọn để cấu hình trình xác thực ảo WebAuthn:
Trong phần này, chúng tôi khám phá việc sử dụng các phương thức và sự kiện của trình xác thực ảo WebAuthn trong bối cảnh của cả các trường hợp sử dụng phổ biến và ít gặp.
Đây có thể là nhiệm vụ quan trọng nhất nhưng cũng khó hiểu nhất khi sử dụng trình xác thực ảo WebAuthn trong mã kiểm thử, vì không có phương thức tích hợp rõ ràng nào để kích hoạt việc nhập passkey. Giải pháp nằm ở các tùy chọn cấu hình của trình xác thực ảo WebAuthn, cụ thể là isUserVerified và automaticPresenceSimulation. Với các tùy chọn này, chúng ta có thể mô phỏng tương tác người dùng này thông qua hai cách tiếp cận khác nhau được mô tả dưới đây.
Trường hợp 1: Mô phỏng việc nhập passkey thành công
test('signup with passkey', async ({ page }) => { ... await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); await page.getByRole('button', { name: 'Login' }).click(); // successful passkey input is automatically simulated (isUserVerified=true) await expect(page.getByRole('heading', { level: 1 })).toHaveText('Welcome!'); ... });
Mô phỏng việc nhập passkey thành công thường không yêu cầu thêm dòng nào trong mã kiểm thử. Dòng cuối cùng (await expect...) chờ cho trang thay đổi (được kích hoạt bởi việc nhập passkey thành công ngầm định).
Trường hợp 2: Mô phỏng việc nhập passkey bị hủy (không kích hoạt thay đổi trong giao diện người dùng)
Kiểm thử việc nhập passkey thất bại hoặc bị hủy phức tạp hơn vì nó có thể không dẫn đến bất kỳ thay đổi nào có thể quan sát được trong giao diện người dùng. Nói cách khác, việc chờ trang thay đổi như trong ví dụ trước là không đủ để đảm bảo rằng việc nhập passkey đã hoàn tất xử lý. Việc kiểm tra rằng trang không thay đổi sau khi nhập passkey ngầm định là vô nghĩa, vì việc kiểm tra gần như chắc chắn sẽ xảy ra trước khi việc nhập passkey hoàn tất xử lý. Mặc dù trình xác thực ảo cung cấp một cách để chờ một lần nhập passkey thành công được xử lý bằng cách lắng nghe phát ra sự kiện (như sẽ được thảo luận trong cách tiếp cận 2), hiện tại không có cách tích hợp nào để phát hiện một lần nhập passkey thất bại hoặc bị hủy. Một giải pháp thay thế là chỉ cần thêm một thời gian chờ cố định để chờ hoạt động passkey hoàn tất trước khi kiểm tra rằng giao diện người dùng thực sự vẫn giữ nguyên.
test('signup with passkey', async ({ page }) => { // Simulate a set of user actions to trigger a passkey prompt ... await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); // Simulate passkey input when prompted in the test await inputPasskey(async () => { await page.waitForTimeout(300); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); }); // Further test steps ... });
Trong cả hai trường hợp, khả năng đọc của mã kiểm thử bị hạn chế bởi tính ngầm định của hoạt động passkey. Như đã đề cập trước đó, cũng sẽ dễ dàng bỏ qua khi Giao diện người dùng có điều kiện (Conditional UI) có thể được nhắc, trong trường hợp đó hoạt động passkey sẽ tự động hoàn thành mà người kiểm thử không biết.
Kích hoạt thủ công việc nhập passkey bằng cách chuyển đổi giá trị của tùy chọn automaticPresenceSimulation giải quyết các vấn đề gặp phải trong cách tiếp cận trước đó, cụ thể là về khả năng đọc của mã kiểm thử.
Trường hợp 1: Mô phỏng việc nhập passkey thành công
Các đoạn mã sau mô phỏng việc nhập passkey thành công:
async simulateSuccessfulPasskeyInput(operationTrigger: () => Promise<void>) { // initialize event listeners to wait for a successful passkey input event const operationCompleted = new Promise<void>(resolve => { client.on('WebAuthn.credentialAdded', () => resolve()); client.on('WebAuthn.credentialAsserted', () => resolve()); }); // set isUserVerified option to true // (so that subsequent passkey operations will be successful) await client.send('WebAuthn.setUserVerified', { authenticatorId: authenticatorId, isUserVerified: true, }); // set automaticPresenceSimulation option to true // (so that the virtual authenticator will respond to the next passkey prompt) await client.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId: authenticatorId, enabled: true, }); // perform a user action that triggers passkey prompt await operationTrigger(); // wait to receive the event that the passkey was successfully registered or verified await operationCompleted; // set automaticPresenceSimulation option back to false await client.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId, enabled: false, }); }
test('signup with passkey', async ({ page }) => { ... // Simulate passkey input with a promise that triggers a passkey prompt as the argument await simulateSuccessfulPasskeyInput(() => page.getByRole('button', { name: 'Create account with passkeys' }).click() ); ... });
Hàm trợ giúp có thể khá đáng sợ khi bạn nhìn vào nó lần đầu tiên. Sẽ hữu ích khi hiểu rằng tất cả các sự phức tạp kỹ thuật của việc mô phỏng một hoạt động passkey đều được trừu tượng hóa vào hàm trợ giúp. Điều này có nghĩa là khi nó được sử dụng bên trong mã kiểm thử, nó làm cho mã trở nên đơn giản và rõ ràng, như có thể thấy trong đoạn mã thứ hai ở trên.
So với cách tiếp cận ngầm định trong Mục 6.1.1, cách tiếp cận rõ ràng này cũng làm tăng khả năng đọc của mã. Điều này sẽ đặc biệt hữu ích khi Giao diện người dùng có điều kiện (Conditional UI) được nhắc, vì cách tiếp cận rõ ràng này ngăn chặn việc hoàn thành hoạt động passkey một cách vô ý, ngầm định mà nhà phát triển không biết.
Bây giờ chúng ta hãy hiểu từng phần của hàm trợ giúp.
Đầu tiên, chúng ta định nghĩa promise operationCompleted, nó chờ đợi sự kiện WebAuthn.credentialAdded hoặc sự kiện WebAuthn.credentialAsserted, như tên gọi, được phát ra khi một thông tin xác thực passkey được đăng ký hoặc xác minh tương ứng. Promise này sẽ được sử dụng sau.
Tiếp theo, tùy chọn isUserVerified được đặt thành true, để hoạt động passkey tiếp theo của trình xác thực ảo WebAuthn sẽ thành công. automaticPresenceSimulation cũng được đặt thành true, để trình xác thực ảo WebAuthn sẽ phản hồi lời nhắc passkey tiếp theo từ trang web.
Việc chờ promise operationTrigger là cần thiết để tránh tình trạng tranh chấp (race condition). Tình trạng tranh chấp xảy ra khi trang web nhắc passkey trước khi automaticPresenceSimulation được đặt thành true. Để ngăn chặn điều này, hành động của người dùng kích hoạt lời nhắc passkey phải được thực hiện sau khi automaticPresenceSimulation được đặt thành true. Trong ví dụ trên, người dùng nhấp vào nút có tên Create account with passkeys để kích hoạt lời nhắc passkey.
Sau khi hành động của người dùng hoàn tất, chúng ta phải chờ cho hoạt động passkey thành công hoàn tất. Điều này được thực hiện bằng cách chờ promise mà chúng ta đã định nghĩa ở đầu hàm trợ giúp. Việc hoàn thành hoạt động passkey thành công được đánh dấu bằng việc phát ra sự kiện WebAuthn.credentialAdded hoặc WebAuthn.credentialAsserted. Trong ví dụ trên, vì người dùng đang đăng ký một passkey, sự kiện WebAuthn.credentialAdded sẽ được phát ra.
Cuối cùng, tùy chọn automaticPresenceSimulation được đặt lại thành false, để ngăn chặn các hoạt động passkey không mong muốn xảy ra sau này trong mã kiểm thử.
Trường hợp 2: Mô phỏng việc nhập passkey bị hủy
Đối với việc nhập passkey bị hủy, chúng ta phải sửa đổi một chút việc triển khai cho trường hợp trước. Trong trường hợp nhập passkey thành công, có các sự kiện, cụ thể là WebAuthn.credentialAdded và WebAuthn.credentialAsserted, được phát ra khi hoàn thành hoạt động. Tuy nhiên, trình xác thực ảo WebAuthn không cung cấp bất kỳ sự kiện nào cho việc nhập passkey bị hủy hoặc thất bại. Do đó, chúng ta phải sử dụng một cách khác để kiểm tra việc hoàn thành một hoạt động passkey bị hủy hoặc thất bại.
Các đoạn mã sau đây mô phỏng một lần nhập passkey thất bại:
async simulateFailedPasskeyInput(operationTrigger: () => Promise<void>, postOperationCheck: () => Promise<void>) { // set isUserVerified option to false // (so that subsequent passkey operations will fail) await client.send('WebAuthn.setUserVerified', { authenticatorId: authenticatorId, isUserVerified: false, }); // set automaticPresenceSimulation option to true // (so that the virtual authenticator will respond to the next passkey prompt) await client.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId: authenticatorId, enabled: true, }); // perform a user action that triggers passkey prompt await operationTrigger(); // wait for an expected UI change that indicates the passkey operation has completed await postOperationCheck(); // set automaticPresenceSimulation option back to false await client.send('WebAuthn.setAutomaticPresenceSimulation', { authenticatorId, enabled: false, }); }
test('signup with passkey', async ({ page }) => { // Simulate a set of user actions to trigger a passkey prompt ... await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); // Simulate passkey input when prompted in the test await inputPasskey(async () => { await page.waitForTimeout(300); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); }); // Further test steps ... });
Trong hàm trợ giúp, các trình lắng nghe sự kiện được thay thế bằng một tham số promise postOperationCheck, nó chờ một thay đổi giao diện người dùng dự kiến xảy ra trước khi automaticPresenceSimulation có thể được đặt lại thành false.
Trong mã kiểm thử, sự khác biệt duy nhất là hàm trợ giúp phải được gọi với một promise bổ sung để kiểm tra sự thay đổi giao diện người dùng dự kiến. Trong ví dụ trên, chúng tôi kiểm tra rằng ứng dụng web đã điều hướng thành công đến một trang trong đó tiêu đề có văn bản Something went wrong....
Như đã thảo luận trong Mục 6.1.1, việc hủy nhập passkey có thể không dẫn đến bất kỳ thay đổi nào có thể quan sát được trên giao diện người dùng. Giống như ví dụ được cung cấp trong phần đó, chúng ta phải thêm một thời gian chờ cố định trước khi kiểm tra rằng giao diện người dùng thực sự vẫn giữ nguyên trong những trường hợp như vậy:
test('signup with passkey', async ({ page }) => { ... await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); // Simulate passkey input with a promise that triggers a passkey prompt as the argument await simulateFailedPasskeyInput( () => page.getByRole('button', { name: 'Create account with passkeys' }).click(), async () => { await page.waitForTimeout(300); await expect(page.getByRole('heading', { level: 1 })).toHaveText('Please log in'); } ); ... });
Sự tiện lợi của việc sử dụng trình xác thực ảo WebAuthn được tăng cường bởi khả năng hoạt động giống như một trình xác thực thực trong trường hợp tạo hoặc xóa passkey bởi ứng dụng web. Một bài kiểm thử chỉ cần thực hiện các hành động của người dùng để mô phỏng việc tạo hoặc xóa một passkey trên ứng dụng web, và trình xác thực ảo WebAuthn tự động sửa đổi thông tin xác thực đã lưu của nó mà không cần thêm bất kỳ công việc nào từ phía mã kiểm thử.
Đây là ví dụ về mã kiểm thử kiểm tra rằng ứng dụng web đăng ký một passkey mới vào trình xác thực một cách chính xác:
test('signup with passkey', async ({ page }) => { ... // Confirm there are currently no registered credentials const result1 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result1.credentials).toHaveLength(0); // Perform user actions to simulate creation of a passkey credential (e.g. user registration with passkey input) ... // Confirm the passkey was successfully registered const result2 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result2.credentials).toHaveLength(1); ... });
Kết hợp đoạn mã này với các đoạn mã từ Mục 6.1, chúng ta có thể kiểm thử luồng đăng ký trên trang web demo của chúng tôi. Video sau đây là một hình ảnh hóa của bài kiểm thử trong chế độ UI của Playwright:
Việc xác minh một thông tin xác thực passkey với trình xác thực ảo WebAuthn hoạt động tương tự như việc tạo một passkey, ở chỗ trình xác thực ảo tự động theo dõi số lần xác minh được thực hiện bằng một thông tin xác thực cụ thể.
test('login with passkey', async ({ page }) => { ... // Confirm there is only one credential, and save its signCount const result1 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result1.credentials).toHaveLength(1); const signCount1 = result1.credentials[0].signCount; // Perform user actions to simulate verification of a passkey credential (e.g. login with passkey input) ... // Confirm the credential's new signCount is greater than the previous signCount const result2 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result2.credentials).toHaveLength(1); expect(result2.credentials[0].signCount).toBeGreaterThan(signCount1); ... });
Video sau đây trình bày một bài kiểm thử cho luồng login trên trang web demo của chúng tôi:
Mặt khác, việc xóa một passkey khỏi một ứng dụng web không nên sửa đổi bất kỳ thông tin nào trong trình xác thực ảo WebAuthn. Ứng dụng web chỉ có thể xóa các thông tin xác thực được lưu trong máy chủ của chính nó. Chỉ có người dùng mới có thể xóa một thông tin xác thực passkey khỏi trình xác thực ảo WebAuthn một cách có ý thức và thủ công.
test('delete a registered passkey credential', async ({ page }) => { ... // Confirm there is currently one registered credential const result1 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result1.credentials).toHaveLength(1); // Perform user actions to simulate deletion of a passkey credential ... // Deleting a passkey credential from a website should not remove the credential from the authenticator const result2 = await client.send('WebAuthn.getCredentials', { authenticatorId }); expect(result2.credentials).toHaveLength(1); ... });
Video sau đây trình bày một bài kiểm thử cho việc xóa một thông tin xác thực passkey trên trang web demo của chúng tôi:
Cách trực quan nhất để mô phỏng xác thực đa thiết bị từ một thiết bị thứ hai (chưa có passkey đã đăng ký) là chỉ cần thêm một phiên bản mới của trình xác thực ảo WebAuthn thông qua lệnh CDP, như sau:
test('signup with passkey', async ({ page }) => { ... // add a virtual authenticator for the first device const authenticatorId1 = await client.send('WebAuthn.addVirtualAuthenticator', { options }); // perform test actions of the first device ... // add a virtual authenticator for the second device const authenticatorId2 = await client.send('WebAuthn.addVirtualAuthenticator', { options }); // perform test actions of the second device .. });
Để tránh sự phức tạp của việc quản lý ID của nhiều trình xác thực ảo, cũng có thể mô phỏng một thiết bị mới bằng cách chỉ cần xóa các thông tin xác thực khỏi một trình xác thực duy nhất, và thêm lại khi cần:
test('signup with passkey', async ({ page }) => { ... const result = await client.send('WebAuthn.getCredentials', { authenticatorId }); const credential = result.credentials[0]; // assuming only one registered passkey const credentialId = credential.credentialId; await client.send('WebAuthn.removeCredential', { authenticatorId, credentialId }); // Perform test actions of the second device which doesn't have a registered passkey ... // Call if it's necessary to simulate the first device which has a registered passkey await client.send('WebAuthn.addCredential', { credential }); // Perform test actions of the first device ... });
Cách tiếp cận này có thể đặc biệt đơn giản hóa việc triển khai trong trường hợp cần mô phỏng một thiết bị mới, nhưng thiết bị cũ không cần được sử dụng nữa. Trong trường hợp này, bạn chỉ cần xóa các thông tin xác thực khỏi trình xác thực ảo và loại bỏ hoàn toàn các thông tin xác thực của nó:
test('signup with passkey', async ({ page }) => { ... const result = await client.send('WebAuthn.getCredentials', { authenticatorId }); const credential = result.credentials[0]; // assuming only one registered passkey const credentialId = credential.credentialId; await client.send('WebAuthn.clearCredentials', { authenticatorId }); // Perform test actions of the second device which doesn't have a registered passkey ... });
Khám phá các giải pháp thay thế cho trình xác thực ảo WebAuthn có thể mang lại sự linh hoạt trong cách kiểm thử các quy trình xác thực passkey / WebAuthn trong các dự án.
Phát triển các dịch vụ hoặc điểm cuối giả lập có thể mô phỏng hiệu quả hành vi xác thực, đơn giản hóa các bài kiểm thử bằng cách trừu tượng hóa sự phức tạp của cơ chế xác thực thực tế. Cách tiếp cận này đặc biệt có lợi khi sử dụng các dịch vụ xác thực bên ngoài, cho phép tập trung vào việc tích hợp và chức năng của các thành phần hệ thống mà không cần đi sâu vào chi tiết xác thực.
Để kiểm tra kỹ lưỡng các chức năng xác thực, việc sử dụng các trình xác thực thực để kiểm thử tích hợp cung cấp một cái nhìn chi tiết về sự tương tác với các khóa bảo mật phần cứng (ví dụ: YubiKeys) hoặc các thiết bị sinh trắc học (ví dụ: được sử dụng trong Face ID, Touch ID hoặc Windows Hello). Mặc dù thường được tiến hành thủ công do tính chất phức tạp của việc tích hợp các thiết bị thực tế vào các bài kiểm thử tự động, nhưng có thể phát triển các kịch bản tự động hóa tùy chỉnh. Các kịch bản này có thể kết nối các trình xác thực thực với các framework kiểm thử end-to-end, cung cấp một sự gần đúng hơn với các kịch bản người dùng thực và nâng cao độ tin cậy của quy trình xác thực trong môi trường thực tế.
Sau khi trình bày các tùy chọn khác nhau và giới thiệu các đoạn mã cụ thể để kiểm thử E2E passkey / WebAuthn với Playwright, chúng tôi còn muốn cung cấp một số khuyến nghị chung hơn cho các nhà phát triển mới làm quen với chủ đề này.
Trước khi đi sâu vào việc kiểm thử passkey hoặc bất kỳ cơ chế xác thực nào khác, điều cần thiết là phải đánh giá các framework kiểm thử E2E có sẵn và chọn tùy chọn phù hợp nhất theo yêu cầu của dự án của bạn. Hãy xem xét sự đánh đổi giữa tốc độ và sự ổn định được cung cấp bởi các framework dựa trên CDP như Playwright và Puppeteer, và khả năng tương thích đa trình duyệt được cung cấp bởi các framework dựa trên WebDriver như Selenium và Nightwatch. Trong khi các framework dựa trên CDP cung cấp tự động hóa trình duyệt nhanh hơn và ổn định hơn, chúng bị giới hạn ở các trình duyệt dựa trên Chromium. Ngược lại, các framework dựa trên WebDriver cung cấp khả năng tương thích đa trình duyệt rộng hơn, bao gồm hỗ trợ cho các trình duyệt không phải Chromium như Firefox và Safari, mặc dù có thể có hiệu suất chậm hơn và kém ổn định hơn. Hiểu rõ những sự đánh đổi này sẽ giúp bạn đưa ra quyết định sáng suốt và chọn framework phù hợp nhất với nhu cầu của dự án.
Trong khi trình xác thực ảo WebAuthn đơn giản hóa quá trình kiểm thử việc triển khai passkey, điều quan trọng đối với các nhà phát triển là phải có một sự hiểu biết vững chắc về các khái niệm cơ bản đằng sau tiêu chuẩn WebAuthn và passkey. Hãy làm quen với các cấu hình khác nhau có sẵn cho trình xác thực ảo WebAuthn, chẳng hạn như protocol, transport, hasResidentKey, hasUserVerification, và isUserVerified. Hiểu rõ các cấu hình này sẽ cho phép bạn tinh chỉnh trình xác thực ảo để mô phỏng các kịch bản xác thực khác nhau một cách chính xác. Ngoài ra, hãy đi sâu vào sự phức tạp của xác thực passkey, bao gồm cả việc tích hợp của nó với các trình duyệt và thiết bị khác nhau, cũng như các cân nhắc bảo mật tiềm ẩn. Kiến thức nền tảng này sẽ giúp bạn thiết kế các chiến lược kiểm thử toàn diện và hiệu quả cho việc xác thực passkey trong các ứng dụng web của mình.
Hướng dẫn này đã đi sâu vào việc sử dụng trình xác thực ảo WebAuthn của CDP với Playwright, làm nổi bật các khái niệm nâng cao và giải quyết các vấn đề không được đề cập trong tài liệu chính thức. Chúng tôi cũng đã khám phá các giải pháp thay thế cho CDP trong Playwright và các framework kiểm thử E2E khác. Mặc dù có các cách triển khai khác nhau, các thông số kỹ thuật được tiêu chuẩn hóa của trình xác thực ảo WebAuthn đảm bảo sự liên quan của hướng dẫn này trên các giao thức tự động hóa web và các framework kiểm thử end-to-end khác nhau. Để tìm hiểu sâu hơn về các khái niệm khác nhau liên quan đến passkey, hãy tham khảo glossary của chúng tôi về các thuật ngữ liên quan có thể giúp bạn tinh chỉnh trình xác thực ảo WebAuthn dựa trên nhu cầu của mình.
Enjoyed this read?
🤝 Join our Passkeys Community
Share passkeys implementation tips and get support to free the world from passwords.
🚀 Subscribe to Substack
Get the latest news, strategies, and insights about passkeys sent straight to your inbox.
Related Articles
Table of Contents