import assert from 'node:assert/strict'; import process from 'node:process'; const { chromium } = await import( process.env.PLAYWRIGHT_MODULE ?? 'playwright' ); const browser = await chromium.launch({ channel: 'chrome', headless: true }); const base = 'http://127.0.0.1:4185'; const context = await browser.newContext({ viewport: { width: 1440, height: 1000 }, permissions: ['clipboard-read', 'clipboard-write'], }); const page = await context.newPage(); page.setDefaultTimeout(10000); const errors = []; page.on('pageerror', (error) => errors.push(error.message)); page.on('dialog', (dialog) => dialog.accept()); const result = (value) => context.addCookies([{ name: 'security_test_result', value, url: base }]); const enabled = (value) => context.addCookies([ { name: 'security_enabled', value: String(value), url: base }, ]); const button = (name) => page.getByRole('button', { name, exact: true }); const dialog = () => page.getByRole('dialog'); let recoveryFetches = 0; page.on('request', (request) => { if ( request.url().endsWith('/user/two-factor-recovery-codes') && request.method() === 'GET' ) { recoveryFetches++; } }); try { for (const width of [1440, 390, 320]) { await page.setViewportSize({ width, height: 1000 }); for (const theme of ['light', 'dark']) { await page.emulateMedia({ colorScheme: theme }); for (const path of ['security', 'applications']) { await page.goto(`${base}/settings/${path}`); await page .getByRole('heading', { name: path === 'security' ? 'Update password' : 'Applications', exact: true, }) .waitFor(); assert.equal( await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth, ), true, `${path} overflow at ${width}/${theme}`, ); assert.equal( await page .locator('html') .evaluate((el) => el.classList.contains('dark')), theme === 'dark', ); assert.ok( (await page.locator('section').first().boundingBox()) .width <= width, `${path} content is clipped at ${width}/${theme}`, ); } await page.goto(`${base}/settings/security`); await button('Enable 2FA').click(); await page.getByLabel('Manual setup key').waitFor(); assert.equal( await page.getByLabel('Manual setup key').inputValue(), 'MANUALSETUPTESTKEY', ); assert.equal( await dialog().evaluate( (el) => el.scrollWidth <= el.clientWidth, ), true, ); await button('Continue').click(); const code = page.getByLabel('Authentication code', { exact: true, }); await code.waitFor(); assert.equal(await code.getAttribute('inputmode'), 'numeric'); assert.equal( await code.getAttribute('autocomplete'), 'one-time-code', ); await code.focus(); await page.keyboard.press('Tab'); assert.equal( await button('Back').evaluate( (el) => el === document.activeElement, ), true, ); await page.keyboard.press('Escape'); await dialog().waitFor({ state: 'hidden' }); assert.equal(await page.getByLabel('Manual setup key').count(), 0); assert.match( await page.locator('body').innerText(), /Awaiting confirmation/, ); } } await page.setViewportSize({ width: 390, height: 1000 }); await button('Continue setup').click(); await button('Continue').click(); await result('validation'); await page .getByLabel('Authentication code', { exact: true }) .fill('000000'); await button('Confirm').click(); await page .getByText('The provided two factor authentication code was invalid.', { exact: true, }) .waitFor(); assert.equal( await page .getByLabel('Authentication code', { exact: true }) .getAttribute('aria-invalid'), 'true', ); assert.equal( await page .getByLabel('Authentication code', { exact: true }) .inputValue(), '', ); await result('success'); await page .getByLabel('Authentication code', { exact: true }) .fill('123456'); await button('Confirm').click(); await dialog().waitFor({ state: 'hidden' }); await button('Disable 2FA').waitFor(); assert.equal( recoveryFetches, 0, 'Recovery codes are fetched only on request', ); await button('View recovery codes').click(); await page.getByText('old-code-0001', { exact: true }).waitFor(); await button('Hide recovery codes').click(); assert.equal( await page.getByText('old-code-0001', { exact: true }).count(), 0, ); await button('View recovery codes').click(); await page.getByText('old-code-0001', { exact: true }).waitFor(); assert.equal(recoveryFetches, 2); await button('Regenerate codes').click(); await dialog().getByRole('button', { name: 'Cancel', exact: true }).click(); assert.equal( await page.getByText('old-code-0001', { exact: true }).count(), 1, ); await button('Regenerate codes').click(); await dialog() .getByRole('button', { name: 'Regenerate codes', exact: true }) .click(); await dialog().waitFor({ state: 'hidden' }); await page.getByText('old-code-0001', { exact: true }).waitFor(); assert.equal(recoveryFetches, 3); await button('Hide recovery codes').click(); await page.route('**/user/two-factor-recovery-codes', async (route) => { if (route.request().method() === 'GET') { await new Promise((resolve) => setTimeout(resolve, 350)); await route.fulfill({ json: ['late-secret-code'] }); } else { await route.continue(); } }); await button('View recovery codes').click(); await button('Hide recovery codes').click(); await page.waitForTimeout(450); assert.equal( await page.getByText('late-secret-code', { exact: true }).count(), 0, ); await page.unroute('**/user/two-factor-recovery-codes'); await page.route('**/user/two-factor-recovery-codes', (route) => route.fulfill({ status: 423, json: {} }), ); await button('View recovery codes').click(); await page .getByText( 'Your session or password confirmation has expired. Reload this page and confirm your password to continue.', { exact: true }, ) .waitFor(); await page.unroute('**/user/two-factor-recovery-codes'); await button('Try again').click(); await page.getByText('old-code-0001', { exact: true }).waitFor(); await button('Disable 2FA').click(); await dialog().getByRole('button', { name: 'Cancel', exact: true }).click(); await button('Disable 2FA').click(); await dialog() .getByRole('button', { name: 'Disable 2FA', exact: true }) .click(); await dialog().waitFor({ state: 'hidden' }); await button('Enable 2FA').waitFor(); assert.equal( await page.getByText('old-code-0001', { exact: true }).count(), 0, ); await page.route('**/user/two-factor-secret-key', (route) => route.abort()); await button('Enable 2FA').click(); await page .getByText( 'Authenticator setup could not be loaded. Please try again.', { exact: true }, ) .waitFor(); assert.equal(await button('Continue').count(), 0); await page.unroute('**/user/two-factor-secret-key'); await button('Try again').click(); await page.getByLabel('Manual setup key').waitFor(); await page.locator('[data-slot="dialog-close"]').click(); await dialog().waitFor({ state: 'hidden' }); await page.getByLabel('Current password', { exact: true }).fill('wrong'); await page.getByLabel('New password', { exact: true }).fill('new-password'); await page .getByLabel('Confirm password', { exact: true }) .fill('new-password'); await result('validation'); await button('Save password').click(); await page .getByText('The password is incorrect.', { exact: true }) .waitFor(); assert.equal( await page.getByLabel('Current password', { exact: true }).inputValue(), '', ); assert.equal( await page.getByLabel('New password', { exact: true }).inputValue(), '', ); assert.equal( await page .getByLabel('Current password', { exact: true }) .getAttribute('aria-invalid'), 'true', ); assert.doesNotMatch( await page.evaluate(() => JSON.stringify(history.state)), /MANUALSETUPTESTKEY|123456|new-password/, ); await result('success'); await page.goto(`${base}/settings/applications`); assert.equal(await button('Copy key').count(), 0); await page.getByLabel('Application name').fill('My tool'); await page.route('**/settings/applications', (route) => route.request().method() === 'POST' ? route.abort() : route.continue(), ); await button('Create application').click(); await page.locator('[data-form-state="network"]').waitFor(); assert.equal( await page.getByLabel('Application name').inputValue(), 'My tool', ); await page.unroute('**/settings/applications'); await button('Try again').click(); await page.getByLabel('API key', { exact: true }).waitFor(); assert.ok( (await page.locator('section').first().boundingBox()).width <= 390, 'One-time key content is clipped on mobile', ); await button('Copy key').click(); assert.equal( await page.evaluate(() => navigator.clipboard.readText()), 'kg_app_one_time_test_key', ); await page.reload(); assert.equal(await button('Copy key').count(), 0); assert.equal(await page.getByLabel('API key', { exact: true }).count(), 0); await page .getByRole('button', { name: 'Delete Desktop tool', exact: true }) .click(); await dialog().getByRole('button', { name: 'Cancel', exact: true }).click(); await page .getByRole('button', { name: 'Delete Desktop tool', exact: true }) .click(); await result('expired'); await dialog().getByRole('button', { name: 'Delete', exact: true }).click(); await dialog() .getByText('Your session has expired', { exact: true }) .waitFor(); assert.equal( await page.locator('button[aria-label="Delete Desktop tool"]').count(), 1, ); await dialog().getByRole('button', { name: 'Cancel', exact: true }).click(); await result('success'); await page .getByRole('button', { name: 'Delete Desktop tool', exact: true }) .click(); await dialog().getByRole('button', { name: 'Delete', exact: true }).click(); await page .getByText('No applications have been created yet.', { exact: true }) .waitFor(); assert.deepEqual(errors, []); console.log( 'Security browser behavior checks passed: 2FA setup, confirmation, cancellation, recovery hiding and regeneration, late responses, errors, password reset, one-time key copy, deletion, keyboard, mobile and both themes.', ); } finally { await browser.close(); }