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:4178'; const context = await browser.newContext({ viewport: { width: 1440, height: 1000 }, }); const page = await context.newPage(); page.setDefaultTimeout(10000); const errors = []; page.on('pageerror', (error) => errors.push(error.message)); const setRole = (value) => context.addCookies([{ name: 'project_test_role', value, url: base }]); const content = () => page.locator('#main-content'); const focusWithTab = async (link) => { await content().focus(); for (let step = 0; step < 30; 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('Project 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', }); await setRole('admin'); for (const [path, title, label] of [ ['/projects', 'Projects', 'index'], [ '/projects/7?template', 'Engineering · Platform and developer experience', 'hub', ], ['/projects?empty', 'Projects', 'empty'], ]) { await page.goto(base + path); await page .getByRole('heading', { name: title, 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, `${label} overflows at ${width} in ${theme}`, ); assert.equal( await content().locator('a a, a button').count(), 0, ); await page.screenshot({ path: `/private/tmp/kan23-${label}-${width}-${theme}.png`, fullPage: true, }); } await page.goto(`${base}/projects/7`); const board = content().getByRole('link', { name: 'Open board', exact: true, }); await focusWithTab(board); await page.keyboard.press('Enter'); await page.waitForURL('**/projects/7/board'); await page .getByRole('heading', { name: 'Destination reached' }) .waitFor(); } } await setRole('reader'); await page.goto(`${base}/projects`); assert.equal( await content() .getByRole('link', { name: 'New project', exact: true }) .count(), 0, ); const overview = content().getByRole('link', { name: 'Open Engineering · Platform and developer experience overview', exact: true, }); await focusWithTab(overview); await page.keyboard.press('Enter'); await page.waitForURL('**/projects/7'); assert.equal( await content() .locator( 'a[href*="/settings/"], a[href$="/access"], a[href*="/templates/"]', ) .count(), 0, ); await content() .getByRole('link', { name: 'Open backlog', exact: true }) .click(); await page.waitForURL('**/projects/7/backlog'); await setRole('admin'); await page.goto(`${base}/projects/7`); const settings = content().getByRole('region', { name: 'Project settings', exact: true, }); assert.equal(await settings.getByRole('link').count(), 6); const general = settings.getByRole('link', { name: 'General', exact: true, }); await focusWithTab(general); await page.keyboard.press('Enter'); await page.waitForURL('**/projects/7/settings/general'); await setRole('creator'); await page.goto(`${base}/projects?empty`); const create = content().getByRole('link', { name: 'Create your first project', exact: true, }); await focusWithTab(create); await page.keyboard.press('Enter'); await page.waitForURL('**/projects/create'); await page.goto(`${base}/projects/7`); await content() .getByRole('link', { name: 'Save as new template', exact: true }) .click(); await page.waitForURL('**/projects/7/templates/create'); assert.deepEqual(errors, []); console.log( 'Project overview browser checks passed: keyboard, navigation, permissions, empty states, 320/390/1440px and both themes.', ); } finally { await browser.close(); }