javascript

Автоматизированное тестирование Webauthn с помощью Playwright

  • среда, 12 августа 2026 г. в 00:00:07
https://habr.com/ru/articles/1067938/

Авторизация — это сложно… Хотя казалось бы — самая часто используемая фича в мире. Очень многие процессы завязаны на авторизации и аутентификации. Начиная от ваших любимых соцсетей и заканчивая редакторами кода.

Часть 1. Описание проблемы

Обычной почты/пароля должно было хватать. А потом у нас появился social login (login via google, yandex, vk, facebook*). А совсем недавно начался тренд на использование 2-х факторной аутентификации, ибо безопасность — ничего не имею против. Я, как и все мы, надеюсь, начал замечать то, что у нас появился ещё один способ авторизации (кроме одноразового кода на телефоне) — через биометрию на вашем компьютере или через специальную флешку. Такая авторизация называется webauthn. Грубо говоря, вставляя такую флешку у друга на компьютере и входя под своими учётными данными (username/password), ваш друг не сможет получить доступ к вашим данным, поскольку флешка-то у вас. Только получив такую флешку лично, можно использовать ваши данные. Если интересно больше почитать про этот способ входа, то коллеги сделали это за меня [1] и [2].

Часть 2. Как тестировать это руками

Во-первых, само приложение должно поддерживать U2F [прим. U2F — Universal 2nd Factor, универсальный 2-й фактор] авторизацию.

Во-вторых, чтобы тестировать руками, нужно желательно физически иметь несколько таких флешек. Либо можно использовать биометрию на вашем устройстве (например, на macOS есть сканер отпечатков, а у Windows-ноутбуков есть Windows Hello и сканер, и то не у всех моделей). По итогу в качестве физического способа входа флешка — самый универсальный вариант, и вы можете попросить у вашей организации закупить такие флешки. Ну а ещё в самом DevTools есть специальные возможности для тестирования webauthn. Находятся они в settings -> more tools -> webauthn.

settings -> more tools -> webauthn
settings -> more tools -> webauthn

Попав на эту страницу, мы можем создать «виртуальное» устройство для входа.

Ну и далее, при вызове на сайте navigator.credentials.create() мы увидим наше устройство в списке способов входа. И далее, уже при авторизации мы также увидим наше устройство.

Часть 3. Как автоматизировать?

Кажется, что сценарий с вставлением флешки не подразумевает автоматизацию как таковую. Однако спешу обрадовать: в Chrome есть соответствующий модуль, который работает с webauthn. И если вы используете Playwright, то вам крупно повезло, и у вас есть несколько опций:

Вызывать webauthn напрямую.

Но в таком случае кода будет очень много: включить webauthn, создать виртуальное устройство, удалить после теста, делать поддержку для разных страниц (если актуально несколько страниц). Плюс на GitHub есть код для webauthn на чистом @playwright/test (кода там много, поэтому в статью не включал, но это не рокет-саенс).

А если у вас playwright>=1.61.0 то у вас есть возможность напрямую взаимодействовать с webauthn через объект credentials. Примеры из официального сайта playwright.dev

// setup test: let the app register a passkey, then save the storage state with it.
const context = await browser.newContext();
await context.credentials.install();

const page = await context.newPage();
await page.goto('https://example.com/register');
await page.getByRole('button', { name: 'Create a passkey' }).click();

// Read back the passkey the page registered — it includes the private key.
const [credential] = await context.credentials.get({ rpId: 'example.com' });
fs.writeFileSync('playwright/.auth/passkey.json', JSON.stringify(credential));

И далее уже использовать ключ по назначению

// seed the captured passkey so the app starts already enrolled.
const credential = JSON.parse(fs.readFileSync('playwright/.auth/passkey.json', 'utf8'));
const context = await browser.newContext();
await context.credentials.create(credential.rpId, credential);
await context.credentials.install();

const page = await context.newPage();
await page.goto('https://example.com/login');
// navigator.credentials.get() resolves the captured passkey — already signed in.

Использовать готовое решение

playwright-labs/fixture-webauthn [github] [npm] - готовый модуль по работе с webauthn. Код выглядит примерно так:

import { test } from '@playwright-labs/fixture-webauthn';

test('login via passkey', async ( { useWebauthn } ) => { 
  const webauthn = useWebauthn();
  await webauthn.enable() // включаем webauthn
  const authenticator = await webauthn.addVirtualAuthenticator({
    protocol: 'ctap2',
    transport: 'internal',
    hasResidentKey: true,
    hasUserVerification: true,
    isUserVerified: true,
  });
  
  await page.goto('/login'); // открываем login страницу
  await page.getByRole('button', { 
    name: 'Sign in with a passkey'
  }).click(); // клик на кнопку "авторизоваться через ключ доступа"
  
  await webauthn.waitForCredentialAsserted(); // ждем окончания авторизации
})

Что использовать мне, если есть выбор: На самом деле все просто: если у вас простое приложение, где авторизация это лишь часть чего-то большего, т.е. авторизация через webauthn у вас проходная, то используйте встроенный context.credentials . А в случае, если вам надо работать с конкретным транспортом, или использовать разные независимые authenticators на каждый контекст - то используйте webauthn. Он максимально приближен к вызовам chrome CDP, насколько это возможно. Ниже представлена таблица, касательно того, когда лучше использовать именно fixture-webauthn

Тестируем

context.credentials

fixture-webauthn

Конкретный protocol/transport/ctap2Version

(он зафиксирован)

Симуляцию “пользователь никогда не подствеждает ключ” (automaticPresenceSimulation: false)

Значение isUserVerified on/off

Имитацию для неверных ответов с подписью (подпись не валидная)

Много независимых authenticators для каждого browser context

(один явный authenticator)

Ожидание событий для waitForCredentialAdded/Asserted/Updated/Deleted

Кстати, пару слов по поводу transport и protocol

- protocol - способ авторизации. Может быть u2f или ctap2. U2f - универсальный второй фактор (флешка или биометрия). ctap2 (Client-to-Authenticator Protocol 2) - это новый формат, который еще умеет общаться с hardware чипом шифрования. (поправьте, если не прав)

- transport - способ авторизации и он бывает таких типов: usb | nfc | ble | cable | internal. USB понятно. NFC — это если ваш девайс (компьютер или телефон) поддерживает NFC-передачу между устройствами. BLE — Bluetooth Low Energy (тут, думаю, что понятно). Cable — это в случае проприетарных шнуров и подключения устройства (т. е. не USB, могу ошибаться тут, никогда не сталкивался с таким) через этот [прим. проприетарный] шнур. Internal — это внутренний чип или биометрия. Кстати, есть ещё один способ, который Chrome не поддерживает (возможно, пока не поддерживает), — это Hybrid. Например, через QR-код можно создать ссылку на компьютере, и телефон при сканировании может добавить сразу несколько ключей для другого устройства (например, BLE и биометрию).

Послесловие

В процессе написания статьи у меня возникло чувство дежавю: кажется, что система оплаты в банках делает что-то подобное, что и webauthn. Ведь у нас есть(загибайте пальцы):

  • qr code (выше уже писалось про QR код)

  • nfc (бесконтактная оплата)

  • ble - вжух от сбера [Сбер] [Хабр] [РБК] работает через BLE, поэтому я сюда его включил

  • smart card(оплата физической картой).

Про Playwright-labs

Этот проект начинался как набор утилит для @playwright/test, чтобы не приходилось переизобретать велосипед, которые я лично использую в проектах, однако он быстро вышел из под контроля разросся до многих незафисимых фич. Среди них есть такие фичи, как:

  • OTEL - open telemetry (и репортер и fixture для кастомных метрик)

  • Prometheus - через prometheus remote write протокол

  • S3 - s3 совместимый движок специально для playwright и фикстур.

  • Email - email репортер, позволяет создавать свои шаблоны для отправки.

  • Slack-buildkit - Да, это не готовый репортер(хотя шаблоны есть заготовленные), а именно строительные блоки самого slack block kit, которые вы можете использовать для создания собственных репортеров, в том числе интерактивных репортеров с кнопочками, таблицами и прочим.

  • QRCode - для чтения\создания qr кодов прямо из теста

  • Barcode - для чтения\создания bar кодов прямо из теста

  • SQL - для работы с SQL запросами(есть поддержка автодополнения прямо из кода для ваших таблиц - но это делает отдельный TS plugin)

  • Lock - Lock сервер для того, чтобы не стучаться в ресурсы одновременно между разными workers(например самый частый случай - у нас общий пользователь, который используется между независимыми workers). Поддерживаются HTTP\WebSocket\IPC\FS транспорты для синхронизации.

  • Env - строгая валидация для environment variables (есть поддержка окружений: github и gitlab)

  • Allure - фичи для Аллюра, такие как декораторы для классов и тест фикстур

  • Selectors-(React/Angular/Vue) - поддержка селекторов для ваших e2e тестов, которая позволяет искать элементы ваших фреимворков на странице(поддерживается props, state и прочее. Но внимание!, не работает для production сборок)

Ну а если было полезно, я предлагаю обсудить, что было бы вам интересно узнать касательно тех фич в playwright-labs, которые проект предоставляет. Или у вас есть идеи, что было бы неплохо добавить в playwright-labs

P.S. эта статья изначально была написана в моем TG канале, но поскольку у меня статьи в канале выходят редко. То оставляю только тем, кто дочитал до конца

*facebook как и кампания meta внесена в список террористических организации на территории РФ