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 page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); page.setDefaultTimeout(10000); const errors = []; page.on('pageerror', (error) => errors.push(error.message)); const base = 'http://127.0.0.1:4181/projects/7/board'; const content = () => page.locator('#main-content'); const visibleColumns = () => content().locator('[id^="board-column-title-"]:visible'); const visit = async (query = '') => { await page.goto(base + query); await page.getByRole('heading', { level: 1 }).waitFor(); await page.evaluate(() => document.fonts.ready); }; const tabTo = async (locator) => { await content().focus(); for (let step = 0; step < 40; step++) { await page.keyboard.press('Tab'); if ( await locator.evaluate( (element) => element === document.activeElement, ) ) { assert.equal( await locator.evaluate((element) => element.matches(':focus-visible'), ), true, ); return; } } assert.fail('Board control is unreachable by keyboard'); }; try { for (const width of [1440, 768, 390, 320]) { await page.setViewportSize({ width, height: 1000 }); for (const theme of ['light', 'dark']) { await page.emulateMedia({ colorScheme: theme, reducedMotion: 'reduce', }); for (const [query, state] of [ ['', 'filled'], ['?empty', 'empty'], ['?one&noTickets', 'one-empty'], ]) { await visit(query); assert.equal( await page .locator('html') .evaluate((el) => el.classList.contains('dark')), theme === 'dark', ); assert.equal( await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth, ), true, `${state} overflow at ${width}/${theme}`, ); assert.equal( await visibleColumns().count(), state === 'empty' ? 0 : width < 768 || state === 'one-empty' ? 1 : 8, ); if (width < 768 && state !== 'empty') { assert.ok( (await page.getByLabel('Choose a column').boundingBox()) .height >= 44, ); } if (state === 'filled' && width >= 768) { const region = content().getByRole('region', { name: 'Workflow columns', exact: true, }); assert.equal( await region.evaluate( (el) => el.scrollWidth > el.clientWidth, ), true, ); await region.focus(); await page.keyboard.press('End'); await content() .locator('#board-column-8') .scrollIntoViewIfNeeded(); assert.ok(await region.evaluate((el) => el.scrollLeft > 0)); await region.evaluate((el) => { el.scrollLeft = 0; }); } await page.evaluate(() => window.scrollTo(0, 0)); await page.screenshot({ path: `/private/tmp/kan27-board-${state}-${width}-${theme}.png`, fullPage: true, }); } } } await page.setViewportSize({ width: 390, height: 1000 }); await visit(); const chooseColumn = page.getByLabel('Choose a column'); await tabTo(chooseColumn); // Native popup selection is handled by macOS outside the browser DOM. await chooseColumn.selectOption('2'); assert.equal(await chooseColumn.inputValue(), '2'); assert.equal(await visibleColumns().innerText(), 'In development'); await chooseColumn.selectOption('8'); assert.equal(await visibleColumns().innerText(), 'Column without statuses'); await content() .getByText('No statuses configured for this column.') .waitFor(); await chooseColumn.selectOption('1'); await content() .getByRole('heading', { name: 'Ready · Planned', exact: true }) .waitFor(); await content() .getByRole('heading', { name: 'Ready · Active', exact: true }) .waitFor(); const terminalSelect = page.getByLabel('Move ENG-43', { exact: true }); assert.equal(await terminalSelect.isDisabled(), true); const moveSelect = page.getByLabel('Move ENG-42', { exact: true }); assert.deepEqual( await moveSelect .locator('option') .evaluateAll((options) => options.map((option) => option.value)), ['20', '11', '99'], ); await tabTo(moveSelect); await moveSelect.selectOption('20'); await page.keyboard.press('Tab'); assert.equal(await page.locator(':focus').innerText(), 'Update status'); await page.keyboard.press('Enter'); await page .getByText('ENG-42 moved successfully.', { exact: true }) .waitFor(); assert.equal(await chooseColumn.inputValue(), '1'); assert.match( await chooseColumn.locator('option').first().innerText(), /\(1\)/, ); await chooseColumn.selectOption('2'); await content() .getByRole('link', { name: 'Open ENG-42: Ship accessible board navigation', exact: true, }) .waitFor(); await visit(); await page.getByLabel('Move ENG-42', { exact: true }).selectOption('99'); await content() .locator('article') .filter({ hasText: 'ENG-42' }) .getByRole('button', { name: 'Update status' }) .click(); await page .getByText('ENG-42 moved successfully.', { exact: true }) .waitFor(); assert.equal( await content() .getByRole('link', { name: 'Open ENG-42: Ship accessible board navigation', exact: true, }) .count(), 0, ); assert.match( await chooseColumn.locator('option').first().innerText(), /\(1\)/, ); await visit('?reject'); await page.getByLabel('Move ENG-42', { exact: true }).selectOption('20'); await content() .locator('article') .filter({ hasText: 'ENG-42' }) .getByRole('button', { name: 'Update status' }) .click(); await page .getByText('This transition is no longer allowed.', { exact: true }) .waitFor(); assert.equal( await page .getByLabel('Move ENG-42', { exact: true }) .getAttribute('aria-invalid'), 'true', ); assert.equal( await content() .getByText('This transition is no longer allowed.', { exact: true }) .count(), 1, ); assert.equal( await page .getByLabel('Move ENG-43', { exact: true }) .getAttribute('aria-invalid'), null, ); await visit('?readonly'); assert.equal(await content().locator('form').count(), 0); const ticketLink = content().getByRole('link', { name: 'Open ENG-42: Ship accessible board navigation', exact: true, }); await tabTo(ticketLink); await page.keyboard.press('Enter'); await page.waitForURL('**/projects/7/tickets/42'); await page .getByRole('heading', { name: 'Destination reached', exact: true }) .waitFor(); await visit('?empty'); await tabTo( content().getByRole('link', { name: 'Open backlog', exact: true }), ); await page.keyboard.press('Enter'); await page.waitForURL('**/projects/7/backlog'); assert.deepEqual(errors, []); console.log( 'Board browser checks passed: responsive columns, keyboard, status changes and validation, permissions, empty states and both themes.', ); } finally { await browser.close(); }