import { expect, test } from '@playwright/test'; import type { Page } from '@playwright/test'; import { readFile } from 'node:fs/promises'; import { decodeState } from '../src/lib/url-state'; import { planFlooring } from '../src/lib/flooring'; import { planSkirting } from '../src/lib/skirting'; import { flooringCuts, skirtingCuts, uncutFlooring } from '../src/lib/cut-list'; import { mm } from '../src/lib/format'; const errors = new WeakMap(); const preview = (page: Page) => page.getByRole('region', { name: 'Floor plan preview' }); const floorRows = (page: Page) => page.getByRole('table', { name: 'Flooring cuts and offcut placements' }).locator('tbody tr'); const skirtRows = (page: Page) => page.getByRole('table', { name: 'Skirting pieces requiring cuts' }).locator('tbody tr'); const floorCodes = (page: Page) => floorRows(page).getByRole('button', { name: /^Preview flooring cut / }); const skirtCodes = (page: Page) => skirtRows(page).getByRole('button', { name: /^Preview skirting cut / }); const state = (page: Page) => decodeState(new URL(page.url()).hash).state; const plans = (page: Page) => { const { project } = state(page); return { project, floor: planFlooring(project.rooms, project.flooring, project.tolerance), skirt: planSkirting(project.rooms, project.skirting, project.tolerance), }; }; async function openWalls(page: Page) { if ( !(await page .locator('.wall-details') .evaluate((element) => (element as HTMLDetailsElement).open)) ) { await page.locator('.wall-details summary').click(); } } async function noHorizontalOverflow(page: Page) { expect( await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1), ).toBe(true); } async function noDrawingScroll(page: Page) { const scrollContainers = await preview(page).evaluate((root) => [root, ...root.querySelectorAll('*')] .filter((element): element is HTMLElement => element instanceof HTMLElement) .filter((element) => { const style = getComputedStyle(element); return ( (/auto|scroll/.test(style.overflowX) && element.scrollWidth > element.clientWidth + 1) || (/auto|scroll/.test(style.overflowY) && element.scrollHeight > element.clientHeight + 1) ); }) .map((element) => element.className), ); expect( scrollContainers, 'The room drawing must use the page scroll, without its own scrollbars', ).toEqual([]); } async function noWallLabelOverlap(page: Page) { const labels = await preview(page) .locator('.wall-label text') .evaluateAll((elements) => elements.map((element) => { const rect = element.getBoundingClientRect(); return { text: element.textContent, x: rect.x, y: rect.y, right: rect.right, bottom: rect.bottom, }; }), ); expect(labels).toHaveLength(8); const overlaps: string[] = []; labels.forEach((label, i) => labels.slice(i + 1).forEach((other) => { if ( Math.min(label.right, other.right) - Math.max(label.x, other.x) > 0.5 && Math.min(label.bottom, other.bottom) - Math.max(label.y, other.y) > 0.5 ) { overlaps.push(`${label.text} overlaps ${other.text}`); } }), ); expect(overlaps, 'Fireplace wall dimensions should remain independently readable').toEqual([]); } test.beforeEach(async ({ page }) => { errors.set(page, []); page.on('pageerror', (error) => errors.get(page)!.push(error.message)); await page.goto('/'); await expect(page.getByRole('heading', { name: 'Room measurements' })).toBeVisible(); }); test.afterEach(async ({ page }) => { expect(errors.get(page), 'The application should not throw browser errors').toEqual([]); }); test('measurements, materials, drawing and both cut lists share one page', async ({ page }) => { await expect(page.getByLabel('Room width', { exact: true })).toHaveValue('4800'); await expect(page.getByLabel('Room length', { exact: true })).toHaveValue('3600'); for (const label of ['Board length', 'Board width', 'Stock length 1', 'Skirting thickness']) { await expect(page.getByLabel(label, { exact: true })).toBeVisible(); } await expect(page.getByRole('heading', { name: 'Materials', exact: true })).toBeVisible(); await expect(page.getByRole('heading', { name: /^Flooring cuts/ })).toBeVisible(); await expect(page.getByRole('heading', { name: /^Skirting cuts/ })).toBeVisible(); await expect(preview(page).getByText('Outline closed', { exact: true })).toBeVisible(); await expect(page.locator('.layer-switch, .step-nav')).toHaveCount(0); await expect(page.locator('.app-header').getByText('mm', { exact: true })).toHaveCount(0); await expect(page.getByRole('button', { name: /^Zoom (in|out)$/ })).toHaveCount(0); await noDrawingScroll(page); await expect( page.getByRole('button', { name: /Choose materials|Get my cut plan|Measure room/ }), ).toHaveCount(0); await expect( page.getByText( /LESS WASTE\. MORE CONFIDENCE|A better floor starts|Thoughtful plans|Saved in your URL|bounded heuristic|inspect narrow tips/i, ), ).toHaveCount(0); expect( await page.locator('#cut-plan').evaluate((element) => element.getBoundingClientRect().top), ).toBeGreaterThan( await page.locator('#materials').evaluate((element) => element.getBoundingClientRect().bottom), ); }); test('drawing includes full boards, while numbered cut thumbnails stay complete and sequential', async ({ page, }) => { const { project, floor, skirt } = plans(page); const cutFloor = flooringCuts(floor); const cutSkirt = skirtingCuts(skirt); const wholeBoards = uncutFlooring(floor, project.flooring).wholeBoards; expect(wholeBoards).toBeGreaterThan(0); await expect(floorCodes(page)).toHaveCount(cutFloor.length); await expect(skirtCodes(page)).toHaveCount(cutSkirt.length); const shownCodes = await floorCodes(page).evaluateAll((buttons) => buttons.map((button) => button.getAttribute('aria-label')!.replace('Preview flooring cut ', ''), ), ); expect(shownCodes.sort()).toEqual(cutFloor.map((piece) => piece.id).sort()); expect(cutFloor.map((piece) => piece.id)).toEqual(cutFloor.map((_, i) => `F${i + 1}`)); expect(cutSkirt.map((piece) => piece.id)).toEqual(cutSkirt.map((_, i) => `A${i + 1}`)); expect(await floorRows(page).count(), 'Repeated dimensions share one cutting batch').toBeLessThan( cutFloor.length, ); const batches = await floorRows(page).evaluateAll((rows) => rows.map((row) => ({ codes: [...row.querySelectorAll('button[aria-label^="Preview flooring cut "]')].map( (button) => button.getAttribute('aria-label')!.replace('Preview flooring cut ', ''), ), size: row.querySelector('.batch-size')?.textContent?.trim(), quantity: row.querySelector('.batch-qty')?.textContent?.trim(), })), ); for (const batch of batches) { expect(batch.quantity).toBe(String(batch.codes.length)); for (const code of batch.codes) { const piece = cutFloor.find((piece) => piece.id === code)!; expect(batch.size).toBe(`${mm(piece.length)} × ${mm(piece.width)}`); } } await expect(preview(page).locator('.floor-piece')).toHaveCount(floor.pieces.length); await expect(preview(page).locator('.skirt-piece')).toHaveCount(skirt.pieces.length); await expect(preview(page).getByRole('button', { name: /^Select flooring cut / })).toHaveCount( cutFloor.length, ); await expect(preview(page).getByRole('button', { name: /^Select skirting cut / })).toHaveCount( cutSkirt.length, ); await expect(page.locator('.flooring-cuts .cut-summary')).toContainText( `${wholeBoards} full boards laid uncut`, ); const drawnCodes = await preview(page).locator('svg text').allTextContents(); for (const piece of floor.pieces.filter((piece) => !piece.cut && !piece.reused)) { expect(shownCodes).not.toContain(piece.id); expect(drawnCodes).not.toContain(piece.id); } const skirtCutIds = new Set(cutSkirt.map((piece) => piece.id)); for (const piece of skirt.pieces.filter((piece) => !skirtCutIds.has(piece.id))) { expect(drawnCodes).not.toContain(piece.id); await expect( preview(page).locator(`.skirt-piece[data-piece-id="${piece.id}"]`), ).toHaveAttribute('aria-hidden', 'true'); } const repeatedCode = batches.find((batch) => batch.codes.length > 1)!.codes.at(-1)!; const repeatedPiece = cutFloor.find((piece) => piece.id === repeatedCode)!; const thumbnail = page.getByRole('button', { name: `Preview flooring cut ${repeatedCode}`, exact: true, }); await expect(thumbnail.locator('svg')).toBeVisible(); await thumbnail.click(); const dialog = page.getByRole('dialog', { name: `Flooring cut ${repeatedCode}`, exact: true }); await expect(dialog).toBeVisible(); await expect(dialog.locator('.cut-origin')).toContainText(`Row ${repeatedPiece.row}`); await expect(dialog.locator('.cut-origin')).toContainText(repeatedPiece.stockId); await expect(dialog.getByRole('img', { name: `Flooring shape ${repeatedCode}` })).toBeVisible(); await page.keyboard.press('Escape'); await expect(page.getByRole('dialog')).toHaveCount(0); await expect(thumbnail).toBeFocused(); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeEnabled(); }); test('inconsistent final measurements block cuts and small residuals show the expected length', async ({ page, }) => { await openWalls(page); await page.getByLabel('Wall 4 length', { exact: true }).fill('3700'); await expect(page.getByText(/The outline is open by 100\.0 mm/).first()).toBeVisible(); await expect(floorRows(page)).toHaveCount(0); await expect(skirtRows(page)).toHaveCount(0); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeDisabled(); await page.getByLabel('Wall 4 length', { exact: true }).fill('3605'); await expect(page.getByText(/non-straight wall/)).toBeVisible(); await expect(page.getByText('Final wall: 3,600 mm expected · 5 mm difference')).toBeVisible(); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeEnabled(); expect(await floorRows(page).count()).toBeGreaterThan(0); }); test('room warnings stay with their room even when two room names match', async ({ page }) => { await openWalls(page); await page.getByLabel('Wall 4 length', { exact: true }).fill('3605'); await expect(page.getByText(/non-straight wall/).first()).toBeVisible(); await page.getByRole('button', { name: 'Add room', exact: true }).click(); await page.getByLabel('Room name', { exact: true }).fill('Living room'); expect(state(page).project.rooms.map((room) => room.name)).toEqual([ 'Living room', 'Living room', ]); await expect(page.getByText(/non-straight wall/)).toHaveCount(0); await page.locator('.room-tab').first().click(); await expect(page.getByText(/non-straight wall/).first()).toBeVisible(); await openWalls(page); await page.getByLabel('Wall 4 length', { exact: true }).fill('3700'); await expect(page.getByText(/outline is open by 100\.0 mm/).first()).toBeVisible(); await page.locator('.room-tab').nth(1).click(); await expect(page.getByText(/outline is open by 100\.0 mm/)).toHaveCount(0); await page.locator('.room-tab').first().click(); await expect(page.getByText(/outline is open by 100\.0 mm/).first()).toBeVisible(); }); test('custom walls close using a calculated wall that can be measured again', async ({ page }) => { await page.getByRole('button', { name: 'Custom', exact: true }).click(); await page.getByRole('button', { name: 'Add a wall', exact: true }).click(); await page.getByLabel('Wall 2 length', { exact: true }).fill('3000'); await page.getByRole('button', { name: 'Add a wall', exact: true }).click(); await page.getByLabel('Wall 3 length', { exact: true }).fill('4000'); await expect(page.getByText('3,000 mm to close')).toBeVisible(); await page.getByRole('button', { name: 'Add closing wall' }).click(); await expect(preview(page).getByText('Outline closed', { exact: true })).toBeVisible(); await openWalls(page); await expect(page.getByLabel('Wall 4 length', { exact: true })).toHaveValue('3000'); await page.getByLabel('Wall 4 length', { exact: true }).fill('3050'); await expect(page.getByText(/3,000 mm expected; length differs by 50 mm/)).toBeVisible(); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeDisabled(); }); test('fireplace thumbnails open true-scale mitres and shaped floor cuts in dialogs', async ({ page, }) => { await page.getByRole('button', { name: 'With fireplace' }).click(); await expect(page.getByLabel('Wall 8 length', { exact: true })).toHaveValue('3600'); await expect(preview(page).getByRole('img', { name: /floor plan with/ })).toHaveAttribute( 'aria-label', /8 walls/, ); await noWallLabelOverlap(page); const { floor, skirt } = plans(page); const mitred = skirtingCuts(skirt).find( (piece) => Math.abs(piece.leftMitre) === 45 || Math.abs(piece.rightMitre) === 45, )!; expect(mitred).toBeDefined(); const thumbnail = page.getByRole('button', { name: `Preview skirting cut ${mitred.id}`, exact: true, }); await expect(thumbnail.locator('svg').first()).toBeVisible(); await thumbnail.click(); const dialog = page.getByRole('dialog', { name: `Skirting cut ${mitred.id}`, exact: true }); await expect(dialog).toBeVisible(); await expect(dialog).toContainText(/long point/i); const end = Math.abs(mitred.leftMitre) === 45 ? 'left' : 'right'; const projected = await dialog .locator(`.skirting-cut-edge[data-end="${end}"]`) .evaluate((element) => { const line = element as SVGLineElement; const matrix = line.getScreenCTM()!; const start = new DOMPoint(line.x1.baseVal.value, line.y1.baseVal.value).matrixTransform( matrix, ); const finish = new DOMPoint(line.x2.baseVal.value, line.y2.baseVal.value).matrixTransform( matrix, ); return { dx: Math.abs(finish.x - start.x), dy: Math.abs(finish.y - start.y) }; }); expect(projected.dx).toBeGreaterThan(5); expect(projected.dy).toBeGreaterThan(5); expect( projected.dx, 'A 45° mitre must have equal horizontal and vertical screen distances', ).toBeCloseTo(projected.dy, 1); await page.getByRole('button', { name: 'Close cut preview', exact: true }).click(); await expect(thumbnail).toBeFocused(); const shaped = flooringCuts(floor).find((piece) => piece.shaped)!; expect(shaped).toBeDefined(); await page .getByRole('button', { name: `Preview flooring cut ${shaped.id}`, exact: true }) .click(); await expect( page.getByRole('dialog', { name: `Flooring cut ${shaped.id}`, exact: true }), ).toBeVisible(); await expect(page.getByRole('img', { name: `Flooring shape ${shaped.id}` })).toBeVisible(); await page.keyboard.press('Escape'); await expect(page.getByRole('dialog')).toHaveCount(0); await expect(page.locator('.selected-cut')).toHaveCount(0); }); test('door positions and material lengths update skirting immediately', async ({ page }) => { const before = await page.locator('.skirting-cuts').innerText(); await page.getByRole('button', { name: 'Add a doorway' }).click(); await expect(page.locator('.door-card')).toHaveCount(2); await page.locator('.door-card').last().getByRole('combobox').selectOption({ label: 'W1' }); const movedDoor = page.locator('.door-card').first(); await movedDoor.getByLabel('Door offset', { exact: true }).fill('1200'); await movedDoor.getByLabel('Door width', { exact: true }).fill('900'); expect(state(page).project.rooms[0].walls[0].doors[0]).toMatchObject({ offset: 1200, width: 900, }); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeEnabled(); expect(await page.locator('.skirting-cuts').innerText()).not.toBe(before); await page.getByLabel('Stock length 1', { exact: true }).fill('3600'); await page.getByLabel('Skirting thickness', { exact: true }).fill('22'); const { skirt } = plans(page); await expect(skirtCodes(page)).toHaveCount(skirtingCuts(skirt).length); await expect(page.locator('.skirting-cuts .cut-summary')).toContainText( `Buy ${skirt.stockCount} lengths`, ); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeEnabled(); await movedDoor.getByLabel('Door width', { exact: true }).fill('6000'); await expect(page.getByText(/must fit within the wall/).first()).toBeVisible(); await expect(skirtRows(page)).toHaveCount(0); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeDisabled(); }); test('invalid skirting settings keep flooring results usable', async ({ page }) => { const floorCount = await floorCodes(page).count(); await page.getByLabel('Stock length 1', { exact: true }).fill('100'); await expect(skirtRows(page)).toHaveCount(0); await expect(floorCodes(page)).toHaveCount(floorCount); await expect(page.locator('.issue.error').first()).toBeVisible(); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeDisabled(); await page.getByLabel('Stock length 1', { exact: true }).fill('2400'); expect(await skirtRows(page).count()).toBeGreaterThan(0); await expect(page.getByRole('button', { name: 'Export CSV' })).toBeEnabled(); }); test('pattern, direction and spacers persist without steps, layers or stored results', async ({ page, }) => { await page.getByLabel('Room length', { exact: true }).fill('3650'); await page.getByRole('button', { name: 'Random', exact: true }).click(); await page.getByRole('button', { name: 'Across first wall', exact: true }).click(); await page.getByLabel('Wall spacer', { exact: true }).fill('12'); expect(state(page).project.flooring).toMatchObject({ pattern: 'random', gap: 12 }); expect(state(page).project.rooms[0].direction).toBe(90); expect(Object.keys(state(page).view).sort()).toEqual(['roomId', 'selectedPiece']); expect(Object.keys(state(page)).sort()).toEqual(['project', 'view']); const table = await page.locator('.flooring-cuts tbody').innerText(); await page.reload(); await expect(page.getByLabel('Wall spacer', { exact: true })).toHaveValue('12'); await expect(page.getByRole('button', { name: 'Random', exact: true })).toHaveAttribute( 'aria-pressed', 'true', ); await expect( page.getByRole('button', { name: 'Across first wall', exact: true }), ).toHaveAttribute('aria-pressed', 'true'); await expect(page.locator('.flooring-cuts tbody')).toHaveText(table, { useInnerText: true }); await noHorizontalOverflow(page); expect(await page.evaluate(() => Object.keys(localStorage))).toEqual([]); }); test('a narrow remainder is shared between the first and last flooring rows', async ({ page }) => { // 3650 minus two 10 mm spacers leaves 19 full rows and a 20 mm sliver. await page.getByLabel('Room length', { exact: true }).fill('3650'); const { floor } = plans(page); for (const row of [1, 20]) { const piece = flooringCuts(floor).find((piece) => piece.row === row); expect(piece).toBeDefined(); const group = floorRows(page).filter({ has: page.getByRole('button', { name: `Preview flooring cut ${piece!.id}`, exact: true }), }); await expect(group.locator('[data-label="Cut size · mm"]')).toContainText('× 105'); } }); test('multiple rooms retain independent measurements with combined cut lists', async ({ page }) => { await page.getByRole('button', { name: 'Add room', exact: true }).click(); await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Room 2'); await page.getByLabel('Room name', { exact: true }).fill('Study'); await page.getByLabel('Room width', { exact: true }).fill('3200'); expect(state(page).project.rooms).toHaveLength(2); expect(state(page).project.rooms[0].walls[0].length).toBe(4800); expect(state(page).project.rooms[1].walls[0].length).toBe(3200); expect(await floorRows(page).filter({ hasText: 'Living room' }).count()).toBeGreaterThan(0); expect(await skirtRows(page).filter({ hasText: 'Study' }).count()).toBeGreaterThan(0); await page.getByRole('button', { name: /^Living room.*m²/ }).click(); await expect(page.getByLabel('Room width', { exact: true })).toHaveValue('4800'); await page.getByRole('button', { name: /^Study.*m²/ }).click(); await expect(page.getByLabel('Room width', { exact: true })).toHaveValue('3200'); await page.getByRole('button', { name: 'Remove this room' }).click(); expect(state(page).project.rooms).toHaveLength(1); await page.reload(); await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Living room'); }); test('shared URLs reopen the selected preview and CSV contains every cut once', async ({ page, context, }) => { await page.getByLabel('Room name', { exact: true }).fill('Café 北'); const { floor, skirt } = plans(page); const pieceCode = flooringCuts(floor)[0].id; await page .getByRole('button', { name: `Preview flooring cut ${pieceCode}`, exact: true }) .click(); await expect( page.getByRole('dialog', { name: `Flooring cut ${pieceCode}`, exact: true }), ).toBeVisible(); expect(state(page).view.selectedPiece).toBe(pieceCode); const shareUrl = page.url(); const originalTable = await page.locator('.flooring-cuts tbody').innerText(); const shared = await context.newPage(); await shared.goto(shareUrl); await expect( shared.getByRole('dialog', { name: `Flooring cut ${pieceCode}`, exact: true }), ).toBeVisible(); await expect(shared.locator('.flooring-cuts tbody')).toHaveText(originalTable, { useInnerText: true, }); await shared.reload(); await expect( shared.getByRole('dialog', { name: `Flooring cut ${pieceCode}`, exact: true }), ).toBeVisible(); await shared.close(); await page.keyboard.press('Escape'); expect(state(page).view.selectedPiece).toBeNull(); const downloadPromise = page.waitForEvent('download'); await page.getByRole('button', { name: 'Export CSV' }).click(); const download = await downloadPromise; expect(download.suggestedFilename()).toBe('flooring-cut-plan.csv'); const csv = await readFile((await download.path())!, 'utf8'); expect(csv).toContain('"Material","Room","Piece","Stock"'); expect(csv).toContain(`"Flooring","Café 北","${pieceCode}"`); expect(csv.split('\r\n').filter((line) => line.startsWith('"Flooring",'))).toHaveLength( flooringCuts(floor).length, ); expect(csv.split('\r\n').filter((line) => line.startsWith('"Skirting",'))).toHaveLength( skirtingCuts(skirt).length, ); for (const piece of floor.pieces.filter((piece) => !piece.cut && !piece.reused)) expect(csv).not.toContain(`"${piece.id}"`); expect(csv).not.toContain('bounded heuristic'); await context.grantPermissions(['clipboard-read', 'clipboard-write']); const closedUrl = page.url(); await page.getByRole('button', { name: 'Copy link', exact: true }).click(); await expect(page.getByText('Link copied', { exact: true })).toBeVisible(); expect(await page.evaluate(() => navigator.clipboard.readText())).toBe(closedUrl); await page.reload(); await expect(page.locator('.flooring-cuts tbody')).toHaveText(originalTable, { useInnerText: true, }); }); test('saw kerf help works with keyboard and touch or mouse', async ({ page, isMobile }) => { for (const name of ['What is saw kerf?', 'What is skirting saw kerf?']) { const button = page.getByRole('button', { name, exact: true }); await button.focus(); await button.press('Space'); await expect(button).toHaveAttribute('aria-expanded', 'true'); await expect(page.getByRole('tooltip')).toContainText( 'Width of material removed by the saw blade.', ); await expect(page.getByRole('tooltip')).toContainText('600 mm + 397 mm'); await button.press('Escape'); await expect(page.getByRole('tooltip')).toHaveCount(0); if (isMobile) await button.tap(); else await button.click(); await expect(page.getByRole('tooltip')).toBeVisible(); await noHorizontalOverflow(page); await page.getByLabel('Board length', { exact: true }).focus(); await expect(page.getByRole('tooltip')).toHaveCount(0); } }); test('invalid URLs recover when measurements are edited', async ({ page }) => { await page.goto('/#plan=broken'); await expect(page.getByText(/Could not load this plan/)).toBeVisible(); await page.getByLabel('Room name', { exact: true }).fill('Recovered plan'); await expect(page.getByText(/Could not load this plan/)).toHaveCount(0); expect(decodeState(new URL(page.url()).hash).error).toBeNull(); await page.reload(); await expect(page.getByLabel('Room name', { exact: true })).toHaveValue('Recovered plan'); }); for (const width of [320, 390]) { test(`phone layout works at ${width} px with readable inputs and touch targets`, async ({ page, }) => { await page.setViewportSize({ width, height: 844 }); await openWalls(page); await page.getByRole('button', { name: 'What is saw kerf?', exact: true }).click(); await noHorizontalOverflow(page); await expect(page.getByRole('tooltip')).toBeVisible(); const smallControls = await page .locator('button, input, select, summary') .evaluateAll((elements) => elements.flatMap((element) => { const rect = element.getBoundingClientRect(); return rect.width > 0 && rect.height > 0 && (rect.width < 39.5 || rect.height < 39.5) ? [ { label: element.getAttribute('aria-label') || element.textContent?.trim(), width: rect.width, height: rect.height, }, ] : []; }), ); expect(smallControls, 'Every visible form control should have a usable touch target').toEqual( [], ); const smallInputs = await page .locator('input, select') .evaluateAll((elements) => elements .filter( (element) => element.getBoundingClientRect().height > 0 && parseFloat(getComputedStyle(element).fontSize) < 16, ) .map((element) => element.getAttribute('aria-label') || element.id), ); expect(smallInputs, '16 px input text avoids phone browser zoom').toEqual([]); expect( await floorRows(page) .first() .evaluate((element) => getComputedStyle(element).display), ).toMatch(/block|grid/); await page.getByRole('button', { name: 'With fireplace' }).click(); await noWallLabelOverlap(page); await noDrawingScroll(page); await skirtCodes(page).first().click(); const dialog = page.getByRole('dialog'); await expect(dialog).toBeVisible(); const box = await dialog.boundingBox(); expect(box!.x).toBeGreaterThanOrEqual(0); expect(box!.y).toBeGreaterThanOrEqual(0); expect(box!.x + box!.width).toBeLessThanOrEqual(width); expect(box!.y + box!.height).toBeLessThanOrEqual(844); await noHorizontalOverflow(page); await page.mouse.click(1, 1); await expect(page.getByRole('dialog')).toHaveCount(0); expect(state(page).view.selectedPiece).toBeNull(); await page.getByText('Source lengths for skirting cuts', { exact: true }).click(); await noHorizontalOverflow(page); }); }