Get your free and exclusive 80-page Banking Passkey Report
passkeys e2e playwright testing webauthn virtual authenticator

WebAuthn 가상 인증기를 사용한 패스키 E2E Playwright 테스트

WebAuthn 가상 인증기를 사용하여 Playwright, Nightwatch, Selenium, Puppeteer로 여러 브라우저에서 패스키에 대한 E2E 테스트를 설정하는 방법을 알아보세요.

Blog-Post-Author

Anders

Created: June 17, 2025

Updated: July 8, 2025


See the original blog version in English here.

우리의 미션은 인터넷을 더 안전한 곳으로 만드는 것이며, 새로운 로그인 표준인 패스키는 이를 달성하기 위한 탁월한 솔루션을 제공합니다. 이것이 바로 저희가 여러분이 패스키와 그 특성을 더 잘 이해하도록 돕고 싶은 이유입니다.

1. 소개: 패스키 E2E 테스트#

패스키는 기본 표준으로 웹 인증(WebAuthn)에 의존하며 인증 방법으로 점점 더 널리 받아들여지고 있습니다. 최근에 인기가 높아지면서 문서나 기타 자료가 상대적으로 부족합니다. 이는 패스키 구현의 복잡한 특성과 더불어 개발자들이 자신의 플랫폼과 서비스를 위한 패스키 설계, 구현, 특히 테스트에 대한 관련 정보를 찾는 것을 어렵게 만들 수 있습니다.

이 가이드는 WebAuthn 가상 인증기의 공식 문서에서 자세히 다루지 않는 측면에 초점을 맞춰 그 격차를 메우는 것을 목표로 합니다. 예를 들어, 문서에서 자명하지 않은 가상 인증기의 구성 옵션과 가상 인증기가 편리한 해결책을 제공하지 않는 특정 사용 사례에 대한 해결 방법을 논의합니다. 그 외에도 이 가이드는 테스트 코드에서 가상 인증기를 사용하는 따라하기 쉬운 예제를 찾는 개발자에게도 유용합니다.

저희 가이드는 Playwright의 예제를 사용하여 프로젝트에서 패스키 구현을 효과적으로 테스트하기 위한 간단한 안내를 제공합니다. Playwright는 브라우저 자동화를 위한 프로토콜로 Chrome DevTools Protocol(CDP)을 사용하는 종단간(E2E) 테스트 프레임워크입니다. Playwright에서 패스키를 테스트하는 기술적인 예제를 특별히 찾고 있다면 5장으로 바로 넘어가도 좋습니다. 반면에 PuppeteerSelenium과 같은 다른 E2E 테스트 프레임워크를 사용하고 이러한 프레임워크에서 패스키를 테스트하려는 경우, 테스트 코드 구현은 사용 중인 프레임워크에 따라 이 가이드에서 제공하는 예제와 동일하거나 매우 유사할 것입니다. 다음 장에서는 다양한 E2E 프레임워크에 대한 배경과 이 가이드가 이러한 프레임워크에 얼마나 관련이 있는지에 대해 설명합니다.

2. 배경: 브라우저 자동화 및 E2E 테스트 프레임워크#

2.1. 브라우저 자동화란 무엇인가요?#

브라우저 자동화는 이름에서 알 수 있듯이, 웹에서 데이터를 스크래핑하거나 저희의 경우처럼 웹 애플리케이션을 테스트하기 위해 브라우저에서 반복적인 사용자 작업을 자동화하는 과정입니다. WebDriver와 Chrome DevTools Protocol(CDP)은 각각 WebAuthn 가상 인증기 구현을 제공하므로 이 가이드와 관련된 두 가지 주요 브라우저 자동화 프로토콜입니다.

2.2. WebDriver란 무엇인가요?#

WebDriver는 클라이언트와 브라우저 간의 통신에서 중개자 역할을 하는 원격 제어 인터페이스로 볼 수 있습니다. 이 프로토콜의 초점은 Firefox 및 Safari와 같이 Chromium 기반이 아닌 브라우저를 포함한 모든 주요 브라우저를 지원하는 플랫폼 및 언어 중립적인 인터페이스를 제공하는 것입니다. WebDriver 인터페이스는 클라이언트 및 브라우저와의 연결을 모두 관리해야 하므로, 이 접근 방식은 더 넓은 범위의 브라우저 지원(즉, 더 높은 불안정성)을 위해 속도와 안정성을 희생합니다. 주목할 만한 WebDriver 클라이언트로는 SeleniumNightwatch가 있습니다.

출처: jankaritech

2.3. Chrome DevTools Protocol(CDP)이란 무엇인가요?#

반면에 Chrome DevTools Protocol(CDP)은 클라이언트와 브라우저 사이에 WebDriver 인터페이스와 같은 중개자가 없습니다. 또한, 이전 접근 방식에서 클라이언트와 WebDriver 인터페이스 간의 느린 HTTP 연결과 대조적으로, 클라이언트와 브라우저 간의 통신은 소켓 연결을 통해 이루어집니다. 이러한 점들로 인해 CDP는 WebDriver보다 훨씬 빠르고 덜 불안정합니다. 단점은 이 프로토콜이 Chrome 및 Edge와 같은 Chromium 기반 브라우저에서만 지원된다는 것입니다. Playwright와 Puppeteer는 CDP를 사용하여 브라우저와 통신하는 클라이언트의 예입니다.

출처: jankaritech

2.4. CDP 기반 E2E 테스트 프레임워크로서의 Puppeteer와 Playwright#

Puppeteer는 Playwright와 마찬가지로 CDP 위에 직접 구축된 E2E 프레임워크입니다. 이는 Puppeteer와 Playwright가 동일한 WebAuthn 가상 인증기 구현을 사용하며, 소켓 연결을 통한 WebAuthn 가상 인증기를 사용한 API 통신도 동일하다는 것을 의미합니다.

이를 보여주기 위해, 지금까지 가상 인증기에 등록된 모든 자격 증명 목록을 반환하는 getCredentials 메서드를 호출하는 Playwright와 Puppeteer의 테스트 코드를 비교해 보겠습니다. 또한 패스키 자격 증명이 성공적으로 등록될 때 트리거되는 credentialAdded 이벤트에 대한 간단한 이벤트 리스너를 첨부합니다. 구현의 세부 사항에 겁먹지 마세요. 나중에 설명할 것입니다. 이 예제들은 단지 두 프레임워크 간의 구현이 얼마나 유사한지를 보여주기 위한 것입니다.

Ben Gould Testimonial

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.

10,000+ devs trust Corbado & make the Internet safer with passkeys. Got questions? We've written 150+ blog posts on passkeys.

Join Passkeys Community

Playwright:

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!'); });

테스트 코드 시작 부분에서 CDP 세션을 초기화하는 방법은 약간 달랐지만, CDP WebAuthn 가상 인증기 API에서 메서드를 호출하고 이벤트를 처리하는 것은 동일합니다. 이는 Puppeteer에서 WebAuthn 가상 인증기를 사용하려는 경우 이 가이드를 한 줄 한 줄 따라갈 수 있음을 의미합니다.

Slack Icon

Become part of our Passkeys Community for updates & support.

Join

2.5. WebDriver 기반 E2E 테스트 프레임워크로서의 Selenium과 Nightwatch#

SeleniumNightwatch는 브라우저 자동화를 위해 WebDriver에 의존하는 E2E 테스트 프레임워크입니다. WebDriver용 WebAuthn 가상 인증기 구현은 CDP용 구현과 별개이지만, API 사양은 유사합니다. CDP WebAuthn 가상 인증기 API의 거의 모든 메서드에 대해 WebDriver WebAuthn 가상 인증기 API에서 해당 메서드를 찾을 수 있습니다. 그러나 한 가지 주목할 점은 CDP WebAuthn 가상 인증기 API에서는 패스키가 성공적으로 추가되거나 확인될 때 이벤트 리스너를 첨부할 수 있었지만, 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();

가상 인증기 인스턴스를 설정하고 API를 호출하는 구문이 해당 CDP 구현과 다르다는 것이 분명합니다. 그러나 두 WebAuthn 가상 인증기의 API 사양이 매우 유사하기 때문에, 이 가이드를 따라 WebDriver 기반 E2E 테스트 프레임워크에서 해당 구현을 작성하는 것이 가능할 것입니다.

2.6. 네이티브 스크립팅을 사용하는 E2E 테스트 프레임워크로서의 Cypress#

Cypress는 위에서 언급한 프레임워크처럼 주로 WebDriver나 CDP를 기반으로 구축되지 않은 E2E 테스트 프레임워크입니다. 브라우저와 통신하기 위해 네이티브 JavaScript를 사용합니다. 그러나 CDP에 대한 저수준 액세스를 제공하므로, 원시 CDP 명령을 보내 CDP의 WebAuthn 가상 인증기를 활용할 수 있습니다.

Substack Icon

Subscribe to our Passkeys Substack for the latest news.

Subscribe

이 저수준 액세스를 위한 구문은 지루하고 위 예제와 매우 다르기 때문에 이 가이드에서는 자세히 다루지 않겠습니다. 그러나 Cypress에서 CDP 명령을 호출하는 방법에 대한 자세한 정보는 이 가이드에서 설명합니다. 이 가이드에서 제시된 CDP WebAuthn 가상 인증기 사용에 대한 큰 그림 개념은 Cypress에서 패스키를 테스트하려는 사람들에게 여전히 관련이 있습니다.

3. Playwright로 패스키 E2E 테스트를 어렵게 만드는 요인은 무엇인가요?#

웹 환경에서 다른 간단한 사용자 작업보다 패스키 구현 테스트가 본질적으로 더 어려운 이유는 여러 가지가 있습니다. 지문 스캔이나 얼굴 인식과 같은 생체 인증과 관련된 동적 사용자 상호 작용을 처리해야 할 필요성은 테스트를 작성하는 동안 자세히 다루기에는 실용적이지 않을 수 있는 복잡성을 더합니다. 인증의 맥락에서 보안은 당연히 주요 관심사이므로, 보안 취약점의 여지 없이 다양한 브라우저와 장치에서 패스키 인증이 원활하게 통합되도록 보장하는 것도 필요합니다.

4. WebAuthn 가상 인증기로 E2E 패스키 테스트 가능#

패스키 작업과 관련된 동적 사용자 상호 작용 처리의 복잡성을 단순화하고, 다양한 브라우저 및 장치에 대한 통합을 테스트하는 것은 WebAuthn 가상 인증기를 사용하여 더 쉬워집니다.

4.1. WebAuthn 가상 인증기란 무엇인가요?#

WebAuthn 가상 인증기는 WebAuthn 표준에 명시된 인증기 모델의 소프트웨어 표현입니다. 하드웨어 보안 키(예: YubiKey)나 생체 인식 스캐너(예: Face ID, Touch ID 또는 Windows Hello에서 사용)와 같은 물리적 인증기 장치의 동작을 에뮬레이트하지만, 전적으로 소프트웨어 내에서 작동합니다(따라서 물리적 인증이나 생체 인식 스캔은 포함되지 않음).

4.2. WebAuthn 가상 인증기의 이점은 무엇인가요?#

WebAuthn 가상 인증기에는 두 가지 주요 이점이 있습니다.

4.2.1. WebAuthn 가상 인증기를 사용한 자동화된 테스트#

WebDriver와 CDP가 브라우저 자동화 도구이므로, 이 프로토콜에서 WebAuthn 가상 인증기 구현의 주요 사용 사례가 자동화된 테스트라는 것은 분명합니다. 이러한 프로토콜을 활용하여 가상 인증기는 E2E 테스트 프레임워크(예: Playwright, Cypress, Nightwatch)와 같은 제어된 환경에서 패스키 기능의 간단하면서도 포괄적인 테스트를 가능하게 합니다.

4.2.2. WebAuthn 가상 인증기를 사용한 수동 테스트 및 시연#

CDP의 WebAuthn 가상 인증기는 Chrome 브라우저의 DevTools를 통해서도 접근할 수 있으며, 수동 테스트나 단순히 시연 목적으로 사용할 수 있습니다. 이 기능을 사용하면 기본적으로 패스키를 지원하지 않는 장치에서 패스키 입력을 시뮬레이션할 수 있습니다. 마찬가지로, 패스키를 지원하는 장치에서 패스키 미지원 환경을 시뮬레이션하는 것도 가능합니다.

위 스크린샷은 수동 테스트나 시연 목적으로 Chrome에서 가상 인증기를 사용하는 예시를 보여줍니다. 가상 인증기에 대한 다양한 구성 옵션이 가능하며, 자격 증명의 추가 및 삭제도 추적할 수 있음을 볼 수 있습니다. 구성 옵션 및 각 권장 값을 포함하여 브라우저에서 가상 인증기 사용에 대한 자세한 내용은 Google의 이 가이드를 참조하세요.

4.3. WebAuthn 가상 인증기의 단점은 무엇인가요?#

WebAuthn 가상 인증기는 패스키 구현을 테스트하기 위한 우아한 솔루션이지만, 주목할 만한 몇 가지 단점이 있습니다.

4.3.1. 하드웨어별 기능 시뮬레이션 불가#

순전히 소프트웨어 기반 솔루션이기 때문에 WebAuthn 가상 인증기는 물리적 인증기의 고유한 하드웨어 특성과 보안 기능을 복제할 수 없습니다. 다양한 플랫폼 인증기(장치에 내장된 것, 예: 스마트폰의 생체 인식 스캐너)와 다양한 교차 플랫폼 인증기(외부 장치, 예: 하드웨어 보안 키) 사용 간의 구별은 WebAuthn 가상 인증기를 사용하여 시뮬레이션할 수 없습니다. 다양한 유형의 플랫폼 및 교차 플랫폼 인증기와 관련된 복잡성을 블랙박스로 단순화하는 것이 WebAuthn 가상 인증기 사용의 장점 중 하나이지만, 다양한 유형의 인증기의 미묘한 차이를 시뮬레이션하고 테스트하려는 경우 다른 솔루션을 탐색해야 합니다.

4.3.2. 부족한 문서와 해결되지 않은 기술적 문제#

WebAuthn의 비교적 최근 채택과 패스키 기술의 새로움을 고려할 때, 가상 인증기를 둘러싼 생태계는 아직 성숙 단계에 있습니다. 이로 인해 포괄적인 문서가 부족하고, 특히 자동화된 테스트 프레임워크와 가상 인증기를 통합하는 맥락에서 해결되지 않은 기술적 과제가 발생합니다. 이 가이드는 자동화된 테스트 환경에서 패스키를 테스트하는 데 대한 포괄적인 통찰력을 제공하고, 이러한 도구를 사용하는 데 여전히 존재하는 불편함을 해결하고 이러한 문제에 대한 해결 방법을 제시함으로써 이 문제를 해결하는 것을 목표로 합니다.

5. Playwright에서 WebAuthn 가상 인증기를 설정하는 방법#

Playwright와 그 종속성을 성공적으로 설치한 후, .spec.ts 또는 .test.ts로 끝나는 이름의 파일을 다음 내용으로 생성하여 즉시 첫 번째 테스트 작성을 시작할 수 있습니다.

import { test, expect } from "@playwright/test"; test("my first test", async ({ page }) => { await page.goto("https://passkeys.eu"); // start simulating user actions });

Playwright에서 WebAuthn 가상 인증기를 사용하려면, 테스트 케이스 시작 부분에서 CDP 세션을 시작하고 가상 인증기를 첨부하기만 하면 충분합니다.

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 ... });

WebAuthn 가상 인증기 구성 옵션:

  • protocol: 이 옵션은 가상 인증기가 사용하는 프로토콜을 지정합니다. 가능한 값은 "ctap2"와 "u2f"입니다.
  • transport: 이 옵션은 가상 인증기가 시뮬레이션하는 인증기 유형을 지정합니다. 가능한 값은 "usb", "nfc", "ble", "internal"입니다. "internal"로 설정하면 플랫폼 인증기를 시뮬레이션하고, 다른 값은 교차 플랫폼 인증기를 시뮬레이션합니다.
  • hasResidentKey: true로 설정하면 레지던트 키(Resident Key, 즉 클라이언트 측 발견 가능한 자격 증명)를 지원합니다.
  • hasUserVerification: true로 설정하면 사용자 확인(User Verification)을 지원합니다. 성공 및 실패한 패스키 입력을 시뮬레이션할 수 있으므로 이 값을 true로 설정하는 것이 좋습니다.
  • isUserVerified: true로 설정하면 성공적인 인증 시나리오를 에뮬레이트하고, false는 사용자가 패스키 입력을 취소하는 경우와 같은 인증 실패를 모방합니다. 이 설정은 hasUserVerification이 true로 설정된 경우에만 유효합니다.
  • automaticPresenceSimulation: true로 설정하면 인증 프롬프트가 있을 때마다 패스키 입력이 자동으로 즉시 발생합니다. 반대로 false로 설정하면 테스트 코드에서 패스키 인증 시뮬레이션을 수동으로 시작해야 합니다. 수동 시뮬레이션(false)을 선택하는 것이 두 가지 이유로 권장됩니다:
    • 테스트 코드 가독성 향상: 자동 시뮬레이션은 테스트 코드에 명시적인 트리거 없이 인증 시도가 시뮬레이션되므로 테스트 흐름의 이해를 모호하게 할 수 있습니다.
    • 의도하지 않은 동작 방지: 자동 시뮬레이션은 테스터가 패스키가 프롬프트되었음을 인지하지 못하더라도 패스키 입력이 트리거된다는 것을 의미합니다. 이는 테스터가 간과하기 쉬운 조건부 UI(Conditional UI)의 경우 특히 문제가 됩니다.

6. WebAuthn 가상 인증기 사용 사례#

이 섹션에서는 일반적인 사용 사례와 특이한 사용 사례 모두의 맥락에서 WebAuthn 가상 인증기 메서드 및 이벤트의 사용법을 살펴봅니다.

6.1. Playwright에서 패스키 입력 시도를 시뮬레이션하는 방법#

이는 테스트 코드에서 WebAuthn 가상 인증기를 사용할 때 가장 중요하면서도 혼란스러운 작업일 수 있습니다. 패스키 입력을 트리거하는 명시적인 내장 메서드가 없기 때문입니다. 해결책은 WebAuthn 가상 인증기 구성 옵션, 즉 isUserVerified와 automaticPresenceSimulation에 있습니다. 이 옵션들을 사용하여 아래에 설명된 두 가지 다른 접근 방식으로 이 사용자 상호 작용을 시뮬레이션할 수 있습니다.

6.1.1. 접근법 1: automaticPresenceSimulation을 true로 설정한 자동 시뮬레이션#

사례 1: 성공적인 패스키 입력 시뮬레이션

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!'); ... });

성공적인 패스키 입력을 시뮬레이션하는 것은 일반적으로 테스트 코드에 추가적인 줄이 필요하지 않습니다. 마지막 줄(await expect...)은 (암시적인 성공적인 패스키 입력에 의해 트리거된) 페이지 변경을 기다립니다.

사례 2: UI 변경을 트리거하지 않는 취소된 패스키 입력 시뮬레이션

실패하거나 취소된 패스키 입력을 테스트하는 것은 UI에 관찰 가능한 변경을 일으키지 않을 수 있으므로 더 복잡합니다. 즉, 이전 예제처럼 페이지가 변경되기를 기다리는 것은 패스키 입력 처리가 완료되었는지 확인하기에 적절하지 않습니다. 암시적 패스키 입력 후 페이지가 변경되지 않았는지 확인하는 것은 의미가 없습니다. 왜냐하면 그 확인은 거의 확실히 패스키 입력 처리가 완료되기 전에 발생할 것이기 때문입니다. 가상 인증기는 이벤트 발생을 수신하여 성공적인 패스키 입력이 처리되기를 기다리는 방법을 제공하지만(접근법 2에서 논의될 것임), 현재 실패하거나 취소된 패스키 입력을 감지하는 내장된 방법은 없습니다. 해결 방법은 UI가 실제로 동일하게 유지되었는지 확인하기 전에 패스키 작업이 완료되기를 기다리는 하드 타임아웃을 추가하는 것입니다.

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 ... });

어느 경우든, 테스트 코드의 가독성은 패스키 작업의 암시성에 의해 제한됩니다. 앞서 언급했듯이, 조건부 UI(Conditional UI)가 프롬프트될 때를 간과하기 쉬우며, 이 경우 패스키 작업은 테스터의 인지 없이 자동으로 완료될 것입니다.

6.1.2. 접근법 2: automaticPresenceSimulation을 false로 설정한 수동 시뮬레이션#

automaticPresenceSimulation 옵션의 값을 전환하여 패스키 입력을 수동으로 트리거하면 이전 접근법에서 발생했던 문제, 즉 테스트 코드 가독성 측면의 문제를 해결할 수 있습니다.

사례 1: 성공적인 패스키 입력 시뮬레이션

다음 코드 스니펫은 성공적인 패스키 입력을 시뮬레이션합니다.

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() ); ... });

헬퍼 함수를 처음 보면 상당히 위협적으로 보일 수 있습니다. 패스키 작업 시뮬레이션의 모든 기술적 복잡성이 헬퍼 함수에 추상화되어 있다는 것을 이해하는 데 도움이 됩니다. 이는 테스트 코드 내에서 사용될 때, 위 두 번째 코드 스니펫에서 볼 수 있듯이 코드를 간단하고 명확하게 만든다는 것을 의미합니다.

6.1.1절의 암시적 접근법과 비교하여, 이 명시적 접근법은 코드의 가독성을 높입니다. 이는 특히 조건부 UI(Conditional UI)가 프롬프트될 때 유용할 것입니다. 이 명시적 접근법은 개발자의 인지 없이 의도하지 않은 암시적 패스키 작업 완료를 방지하기 때문입니다.

이제 헬퍼 함수의 각 부분을 이해해 봅시다.

먼저, operationCompleted 프로미스를 정의합니다. 이 프로미스는 WebAuthn.credentialAdded 이벤트 또는 WebAuthn.credentialAsserted 이벤트를 기다립니다. 이름에서 알 수 있듯이, 이 이벤트들은 패스키 자격 증명이 각각 등록되거나 확인될 때 발생합니다. 이 프로미스는 나중에 사용될 것입니다.

다음으로, isUserVerified 옵션이 true로 설정되어 WebAuthn 가상 인증기에 의한 후속 패스키 작업이 성공적으로 이루어지도록 합니다. automaticPresenceSimulation도 true로 설정되어 WebAuthn 가상 인증기가 웹페이지의 다음 패스키 프롬프트에 응답하도록 합니다.

operationTrigger 프로미스를 기다리는 것은 경쟁 상태(race condition)를 피하기 위해 필요합니다. 경쟁 상태는 웹페이지가 automaticPresenceSimulation이 true로 설정되기 전에 패스키를 프롬프트할 때 발생합니다. 이를 방지하려면, 패스키 프롬프트를 트리거하는 사용자 작업이 automaticPresenceSimulation이 true로 설정된 후에 수행되어야 합니다. 위 예제에서 사용자는 'Create account with passkeys'라는 이름의 버튼을 클릭하여 패스키 프롬프트를 트리거합니다.

사용자 작업이 완료된 후, 성공적인 패스키 작업이 완료되기를 기다려야 합니다. 이는 헬퍼 함수 시작 부분에서 정의한 프로미스를 기다림으로써 수행됩니다. 성공적인 패스키 작업의 완료는 WebAuthn.credentialAdded 또는 WebAuthn.credentialAsserted 이벤트의 발생으로 표시됩니다. 위 예제에서 사용자가 패스키를 등록하고 있으므로 WebAuthn.credentialAdded 이벤트가 발생할 것입니다.

마지막으로, automaticPresenceSimulation 옵션은 테스트 코드에서 나중에 의도하지 않은 패스키 작업이 발생하는 것을 방지하기 위해 다시 false로 설정됩니다.

사례 2: 취소된 패스키 입력 시뮬레이션

취소된 패스키 입력의 경우, 이전 사례의 구현을 약간 수정해야 합니다. 성공적인 패스키 입력의 경우, 작업 완료 시 발생하는 WebAuthn.credentialAdded 및 WebAuthn.credentialAsserted와 같은 이벤트가 있습니다. 그러나 WebAuthn 가상 인증기는 취소되거나 실패한 패스키 입력에 대한 이벤트를 제공하지 않습니다. 따라서 취소되거나 실패한 패스키 작업의 완료를 확인하기 위해 다른 방법을 사용해야 합니다.

다음 코드 스니펫은 실패한 패스키 입력을 시뮬레이션합니다.

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 ... });

헬퍼 함수에서 이벤트 리스너는 postOperationCheck라는 프로미스 매개변수로 대체됩니다. 이 매개변수는 automaticPresenceSimulation이 다시 false로 설정되기 전에 예상되는 UI 변경이 발생하기를 기다립니다.

테스트 코드에서 유일한 차이점은 의도된 UI 변경을 확인하는 추가 프로미스와 함께 헬퍼 함수를 호출해야 한다는 것입니다. 위 예제에서는 웹 애플리케이션이 헤더에 'Something went wrong...' 텍스트가 있는 페이지로 성공적으로 이동했는지 확인합니다.

6.1.1절에서 논의했듯이, 패스키 입력을 취소해도 UI에 관찰 가능한 변경이 없을 수 있습니다. 해당 섹션에서 제공된 예제처럼, 이러한 경우 UI가 실제로 동일하게 유지되었는지 확인하기 전에 하드 웨이트를 추가해야 합니다.

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'); } ); ... });

6.2. 패스키 생성을 테스트하는 방법#

WebAuthn 가상 인증기 사용의 편리함은 웹 애플리케이션에 의한 패스키 생성 또는 삭제 시 실제 인증기처럼 작동하는 능력에 의해 향상됩니다. 테스트는 웹 애플리케이션에서 패스키의 생성 또는 삭제를 시뮬레이션하기 위해 사용자 작업을 수행하기만 하면 되며, WebAuthn 가상 인증기는 테스트 코드 측의 추가 작업 없이 저장된 자격 증명 정보를 자동으로 수정합니다.

다음은 웹 애플리케이션이 인증기에 새 패스키를 올바르게 등록하는지 확인하는 테스트 코드의 예입니다.

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); ... });

이 코드 스니펫을 6.1절의 코드 스니펫과 결합하여 저희 데모 웹페이지에서 가입 흐름을 테스트할 수 있습니다. 다음 비디오는 Playwright의 UI 모드에서 테스트를 시각화한 것입니다.

6.3. 패스키 확인을 테스트하는 방법#

WebAuthn 가상 인증기로 패스키 자격 증명을 확인하는 것은 패스키를 생성하는 것과 유사하게 작동합니다. 가상 인증기는 특정 자격 증명을 사용하여 수행된 확인 횟수를 자동으로 추적합니다.

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); ... });

다음 비디오는 저희 데모 웹페이지의 로그인 흐름에 대한 테스트를 보여줍니다.

6.4. 패스키 삭제를 테스트하는 방법#

반면에 웹 애플리케이션에서 패스키를 삭제하는 것은 WebAuthn 가상 인증기 내의 어떤 정보도 수정해서는 안 됩니다. 웹 애플리케이션은 자체 서버에 저장된 자격 증명만 삭제할 수 있어야 합니다. 사용자 자신만이 의식적으로 수동으로 WebAuthn 가상 인증기에서 패스키 자격 증명을 삭제할 수 있어야 합니다.

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); ... });

다음 비디오는 저희 데모 웹페이지에서 패스키 자격 증명 삭제 테스트를 보여줍니다.

6.5. 교차 장치 인증을 시뮬레이션하는 방법#

두 번째 장치(아직 등록된 패스키가 없는)에서 교차 장치 인증을 시뮬레이션하는 가장 직관적인 방법은 CDP 명령을 통해 WebAuthn 가상 인증기의 새 인스턴스를 추가하는 것입니다.

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 .. });

여러 가상 인증기의 ID를 관리하는 복잡성을 피하기 위해, 단일 인증기에서 자격 증명을 삭제하고 필요할 때 다시 추가하여 새 장치를 시뮬레이션하는 것도 가능합니다.

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 ... });

이 접근 방식은 새 장치를 시뮬레이션해야 하지만 이전 장치를 더 이상 사용할 필요가 없는 경우 구현을 특히 단순화할 수 있습니다. 이 경우 가상 인증기에서 자격 증명을 지우고 해당 자격 증명을 완전히 폐기하기만 하면 됩니다.

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 ... });

7. WebAuthn 가상 인증기의 대안#

WebAuthn 가상 인증기의 대안을 탐색하면 프로젝트 내에서 패스키 / WebAuthn 인증 프로세스를 테스트하는 방법에 유연성을 제공할 수 있습니다.

7.1. 모의 서비스로 테스트하기#

모의 서비스나 엔드포인트를 개발하면 실제 인증 메커니즘의 복잡성을 추상화하여 테스트를 단순화하고 인증 동작을 효과적으로 시뮬레이션할 수 있습니다. 이 접근 방식은 외부 인증 서비스가 사용될 때 특히 유용하며, 인증 세부 사항에 깊이 들어가지 않고 시스템 구성 요소의 통합 및 기능에 집중할 수 있게 해줍니다.

7.2. 실제 인증기를 사용한 통합 테스트#

인증 기능을 철저히 검사하기 위해 통합 테스트에 실제 인증기를 사용하면 하드웨어 보안 키(예: YubiKeys) 또는 생체 인식 장치(예: Face ID, Touch ID 또는 Windows Hello에서 사용)와의 상호 작용에 대한 상세한 통찰력을 얻을 수 있습니다. 실제 장치를 자동화된 테스트에 통합하는 복잡한 특성 때문에 일반적으로 수동으로 수행되지만, 맞춤형 자동화 스크립트를 개발하는 것은 가능합니다. 이러한 스크립트는 실제 인증기를 종단간 테스트 프레임워크와 연결하여 실제 사용자 시나리오에 더 가까운 근사치를 제공하고 라이브 환경에서 인증 프로세스의 신뢰성을 향상시킬 수 있습니다.

8. 개발자를 위한 권장 사항#

다양한 옵션을 시연하고 Playwright로 패스키 / WebAuthn을 E2E 테스트하기 위한 특정 코드 스니펫을 보여준 후, 이 주제에 새로 입문하는 개발자를 위해 좀 더 일반적인 권장 사항을 추가로 제공하고자 합니다.

8.1. E2E 테스트 프레임워크 환경 연구하기#

패스키나 다른 인증 메커니즘을 테스트하기 전에, 사용 가능한 E2E 테스트 프레임워크를 평가하고 프로젝트의 요구 사항에 따라 가장 적절한 옵션을 선택하는 것이 중요합니다. Playwright 및 Puppeteer와 같은 CDP 기반 프레임워크가 제공하는 속도와 안정성, 그리고 Selenium 및 Nightwatch와 같은 WebDriver 기반 프레임워크가 제공하는 교차 브라우저 호환성 간의 장단점을 고려하십시오. CDP 기반 프레임워크는 더 빠르고 안정적인 브라우저 자동화를 제공하지만 Chromium 기반 브라우저로 제한됩니다. 반면, WebDriver 기반 프레임워크는 Firefox 및 Safari와 같은 비-Chromium 브라우저를 포함하여 더 넓은 교차 브라우저 호환성을 제공하지만, 잠재적으로 더 느리고 덜 안정적인 성능을 보일 수 있습니다. 이러한 장단점을 이해하면 정보에 입각한 결정을 내리고 프로젝트의 요구에 가장 적합한 프레임워크를 선택하는 데 도움이 될 것입니다.

8.2. WebAuthn 및 패스키의 기본 개념 이해하기#

WebAuthn 가상 인증기는 패스키 구현 테스트 프로세스를 단순화하지만, 개발자가 WebAuthn 표준 및 패스키의 기본 개념을 확실히 이해하는 것이 중요합니다. 프로토콜, 전송, hasResidentKey, hasUserVerification, isUserVerified와 같은 WebAuthn 가상 인증기에서 사용할 수 있는 다양한 구성에 익숙해지십시오. 이러한 구성을 이해하면 다양한 인증 시나리오를 정확하게 시뮬레이션하도록 가상 인증기를 미세 조정할 수 있습니다. 또한, 다양한 브라우저 및 장치와의 통합, 잠재적인 보안 고려 사항을 포함하여 패스키 인증의 복잡성을 깊이 파고드십시오. 이 기초 지식은 웹 애플리케이션에서 패스키 인증을 위한 포괄적이고 효과적인 테스트 전략을 설계하는 데 힘이 될 것입니다.

9. 요약#

이 가이드는 Playwright와 함께 CDP WebAuthn 가상 인증기를 사용하는 방법을 심도 있게 다루었으며, 고급 개념을 강조하고 공식 문서에서 다루지 않은 문제를 해결했습니다. 또한 Playwright 및 기타 E2E 테스트 프레임워크 내에서 CDP의 대안을 탐색했습니다. 구현 방식은 다양하지만, 표준화된 WebAuthn 가상 인증기 사양은 이 가이드가 다양한 웹 자동화 프로토콜 및 종단간 테스트 프레임워크 전반에 걸쳐 관련성을 갖도록 보장합니다. 패스키에 관한 다양한 개념에 대해 더 깊이 배우려면, 필요에 따라 WebAuthn 가상 인증기를 미세 조정하는 데 도움이 될 수 있는 관련 용어집을 참조하세요.

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start for free

Share this article


LinkedInTwitterFacebook

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