45 lines
4.7 KiB
JavaScript
45 lines
4.7 KiB
JavaScript
async (page) => {
|
|
const origin = new URL(page.url()).origin;
|
|
const context = await page.context().browser().newContext(); const owner = await context.newPage();
|
|
const cdp = await context.newCDPSession(owner); const results = [];
|
|
const username = `passkey_${Date.now()}`; const password = 'Passkey-test-password-2026';
|
|
const errors = []; const calls = [];
|
|
owner.on('pageerror', error => errors.push(error.message));
|
|
owner.on('request', request => { if (request.url().includes('/api/v1/')) calls.push(new URL(request.url()).pathname); });
|
|
await cdp.send('WebAuthn.enable');
|
|
const { authenticatorId } = await cdp.send('WebAuthn.addVirtualAuthenticator', { options: { protocol: 'ctap2', ctap2Version: 'ctap2_1', transport: 'internal', hasResidentKey: true,
|
|
hasUserVerification: true, isUserVerified: true, automaticPresenceSimulation: true, hasPrf: true } });
|
|
let secretExtensionSent = false;
|
|
owner.on('request', request => { if (request.url().includes('/accounts/passkeys/') && request.postData()) {
|
|
const body = JSON.parse(request.postData()); if (body.credential && JSON.parse(body.credential).clientExtensionResults?.prf?.results) secretExtensionSent = true;
|
|
} });
|
|
try {
|
|
await owner.goto(origin); await owner.getByLabel('用户名', { exact: true }).fill(username); await owner.getByLabel('密码', { exact: true }).fill(password);
|
|
await owner.getByRole('button', { name: '创建账号', exact: true }).click(); await owner.getByRole('button', { name: '新建空间', exact: true }).waitFor();
|
|
await owner.getByLabel('Passkey名称', { exact: true }).fill('测试恢复凭据'); await owner.getByLabel('验证账号密码', { exact: true }).fill(password);
|
|
await owner.getByRole('button', { name: '绑定Passkey', exact: true }).click();
|
|
await owner.getByText('测试恢复凭据 · 登录与解密恢复', { exact: true }).waitFor();
|
|
if (secretExtensionSent) throw new Error('PRF results were sent to the API');
|
|
results.push('verified WebAuthn binding enables encrypted recovery without sending PRF outputs to the server');
|
|
await owner.getByRole('button', { name: '新建空间', exact: true }).click(); await owner.getByLabel('空间名称(加入者和服务端可见)').fill('Passkey恢复测试');
|
|
await owner.getByRole('button', { name: '创建', exact: true }).click(); await owner.getByRole('button', { name: '常规', exact: true }).waitFor();
|
|
await owner.getByRole('region', { name: '常规 聊天', exact: true }).locator('textarea').fill('Passkey恢复后的私密内容'); await owner.getByRole('button', { name: '发送消息', exact: true }).click();
|
|
await owner.getByRole('article').getByText('Passkey恢复后的私密内容', { exact: true }).waitFor();
|
|
const path = new URL(owner.url()).pathname;
|
|
await owner.goto(origin); await owner.getByRole('button', { name: '退出登录', exact: true }).click();
|
|
await owner.getByRole('button', { name: '使用Passkey登录', exact: true }).waitFor(); await owner.getByRole('button', { name: '使用Passkey登录', exact: true }).click();
|
|
await owner.getByRole('button', { name: '新建空间', exact: true }).waitFor(); await owner.goto(origin + path);
|
|
await owner.getByRole('article').getByText('Passkey恢复后的私密内容', { exact: true }).waitFor();
|
|
results.push('Passkey restores the vault and decrypts resources without entering the password');
|
|
await owner.goto(origin); await owner.getByRole('button', { name: '撤销 测试恢复凭据', exact: true }).click(); await owner.getByText('Passkey已撤销', { exact: true }).waitFor();
|
|
await owner.getByRole('button', { name: '退出登录', exact: true }).click();
|
|
await owner.getByLabel('用户名', { exact: true }).fill(username); await owner.getByLabel('密码', { exact: true }).fill(password);
|
|
await owner.getByRole('button', { name: '登录并同步', exact: true }).click(); await owner.getByRole('button', { name: '新建空间', exact: true }).waitFor();
|
|
results.push('revocation removes the credential while password recovery remains available');
|
|
return { passed: results };
|
|
} catch (error) {
|
|
const inputs = await owner.locator('input').evaluateAll(inputs => inputs.map(input => ({ name: input.name, type: input.type, length: input.value.length, valid: input.validity.valid, message: input.validationMessage })));
|
|
throw new Error(`Passkey smoke failed after ${results.length} checks: ${error.message}; body=${(await owner.locator('body').innerText()).slice(0, 800)}; inputs=${JSON.stringify(inputs)}; errors=${JSON.stringify(errors)}; calls=${JSON.stringify(calls)}`);
|
|
} finally { await cdp.send('WebAuthn.removeVirtualAuthenticator', { authenticatorId }).catch(() => {}); await context.close(); }
|
|
}
|