import assert from 'node:assert/strict'; import process from 'node:process'; const playwright = await import(process.env.PLAYWRIGHT_MODULE ?? 'playwright'); const { chromium } = playwright.default ?? playwright; const browser = await chromium.launch({ channel: 'chrome', headless: true }); const base = 'http://127.0.0.1:4192'; const context = await browser.newContext(); const page = await context.newPage(); page.setDefaultTimeout(10000); const errors = []; page.on('pageerror', (error) => errors.push(error.message)); const focusWithTab = async (link) => { await page.locator('body').focus(); for (let step = 0; step < 12; step++) { await page.keyboard.press('Tab'); if ( await link.evaluate((element) => element === document.activeElement) ) { assert.equal( await link.evaluate((element) => element.matches(':focus-visible'), ), true, ); return; } } assert.fail('Primary welcome action was not reachable by keyboard'); }; 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, reducedMotion: 'reduce', }); for (const [path, action] of [ ['/', 'Sign in to KanbanGit'], ['/?auth=1', 'Open dashboard'], ]) { await page.goto(base + path); await page .getByRole('heading', { name: 'Plan the work. Keep Git context close.', exact: true, level: 1, }) .waitFor(); await page.evaluate(() => document.fonts.ready); assert.equal( await page .locator('html') .evaluate((element) => element.classList.contains('dark'), ), theme === 'dark', ); assert.equal( await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth, ), true, `${path} overflows at ${width}px in ${theme} mode`, ); const clippedElements = await page .locator( 'header a, main h1, main p, main section, main article, main li, footer p', ) .evaluateAll((elements) => elements .filter((element) => { const bounds = element.getBoundingClientRect(); return ( bounds.width > 0 && (bounds.left < -0.5 || bounds.right > window.innerWidth + 0.5) ); }) .map((element) => element.outerHTML.slice(0, 120)), ); assert.deepEqual( clippedElements, [], `${path} clips content at ${width}px in ${theme} mode`, ); assert.equal(await page.locator('main h1').count(), 1); const primaryAction = page .locator('main') .getByRole('link', { name: action, exact: true }); assert.ok( (await primaryAction.evaluate( (element) => element.getBoundingClientRect().height, )) >= 44, ); await page.screenshot({ path: `/private/tmp/kan50-welcome-${path.includes('auth') ? 'authenticated' : 'guest'}-${width}-${theme}.png`, fullPage: true, }); } } } await page.setViewportSize({ width: 390, height: 1000 }); await page.goto(`${base}/`); const signIn = page .locator('main') .getByRole('link', { name: 'Sign in to KanbanGit', exact: true }); await focusWithTab(signIn); await page.keyboard.press('Enter'); await page.waitForURL('**/login'); await page .getByRole('heading', { name: 'Destination reached', exact: true }) .waitFor(); await page.goto(`${base}/?auth=1`); const dashboard = page .locator('main') .getByRole('link', { name: 'Open dashboard', exact: true }); await focusWithTab(dashboard); await page.keyboard.press('Enter'); await page.waitForURL('**/dashboard'); await page .getByRole('heading', { name: 'Destination reached', exact: true }) .waitFor(); assert.deepEqual(errors, []); console.log( 'Welcome browser checks passed: keyboard, guest and authenticated actions, 320/390/1440px and both themes.', ); } finally { await browser.close(); }