riprap-nyc / web /sveltekit /tests /e2e /compare.spec.ts
seriffic's picture
fix(compare): two-column layout with delta summary row, shape test
9dff1b6
/**
* E2E test for the compare-intent two-column layout.
*
* Navigates to a compare query, waits for the SSE pipeline to finish
* (both single_address sub-runs + Mellea reconcile), then asserts:
* 1. Two `.address-header` elements are in the DOM — one per target.
* 2. The comparison delta summary bar is present.
* 3. No ErrorCard is rendered.
* 4. No JS console errors.
*
* Budget: 360 s (two full single_address pipelines in series).
*
* Run: npm run test:e2e -- --grep compare
*/
import { test, expect } from '@playwright/test';
import { mkdirSync } from 'node:fs';
const COMPARE_QUERY = 'Compare 80 Pioneer Street Brooklyn to 100 Gold Street Manhattan';
const SCREENSHOT_DIR = 'test-results/demo-screenshots';
mkdirSync(SCREENSHOT_DIR, { recursive: true });
test.describe('@compare two-column layout', () => {
test.describe.configure({ mode: 'serial', timeout: 360_000 });
test('compare renders two address headers and delta bar', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') consoleErrors.push(msg.text());
});
await page.goto(`/q/${encodeURIComponent(COMPARE_QUERY)}`, {
waitUntil: 'domcontentloaded'
});
// Wait until the compare layout renders (two address headers) or an
// error card surfaces.
await page.waitForFunction(
() => {
const errCard = document.querySelector('.error-card');
const headers = document.querySelectorAll('.address-header');
return Boolean(errCard) || headers.length >= 2;
},
undefined,
{ timeout: 360_000 }
);
// No error card should appear for two valid NYC addresses.
await expect(page.locator('.error-card'), 'no ErrorCard for valid compare query')
.toHaveCount(0);
// Two address headers — one per compare target.
const headers = page.locator('.address-header');
await expect(headers, 'exactly two address-header elements').toHaveCount(2);
// Each header should contain non-empty text.
const textA = (await headers.nth(0).textContent())?.trim();
const textB = (await headers.nth(1).textContent())?.trim();
expect(textA, 'PLACE A header has address text').toBeTruthy();
expect(textB, 'PLACE B header has address text').toBeTruthy();
expect(textA, 'PLACE A and PLACE B headers differ').not.toBe(textB);
// Delta summary bar should be present (if any numbers differ between
// the two addresses, which is expected for two distinct NYC locations).
const deltaBar = page.locator('.compare-delta-bar');
const deltaCount = await deltaBar.count();
// Delta bar is conditional — present when numbers differ; just log if absent.
console.log(`[compare] delta bar present: ${deltaCount > 0}`);
// Briefing prose sections rendered in both columns.
const sectionHeads = page.locator('.briefing-section-head');
const headCount = await sectionHeads.count();
expect(headCount, 'at least two section heads (one per column)').toBeGreaterThanOrEqual(2);
// No console errors.
const filteredErrors = consoleErrors.filter(
(e) => !e.includes('favicon') && !e.includes('maplibre-gl')
);
expect(filteredErrors, `console errors: ${filteredErrors.join(' | ')}`).toEqual([]);
await page.screenshot({
path: `${SCREENSHOT_DIR}/compare.png`,
fullPage: true
});
console.log(
`[compare] headers="${textA}" / "${textB}" · sections=${headCount} · delta=${deltaCount > 0}`
);
});
test('compare two-column layout stacks on narrow viewport', async ({ page }) => {
// 800 px < 900 px breakpoint — columns should stack vertically.
await page.setViewportSize({ width: 800, height: 900 });
await page.goto(`/q/${encodeURIComponent(COMPARE_QUERY)}`, {
waitUntil: 'domcontentloaded'
});
await page.waitForFunction(
() => document.querySelectorAll('.address-header').length >= 2 ||
Boolean(document.querySelector('.error-card')),
undefined,
{ timeout: 360_000 }
);
// On narrow viewport the two columns exist in the DOM but stack.
const headers = page.locator('.address-header');
await expect(headers).toHaveCount(2);
// Verify the compare-cols grid is single-column (stacked).
const cols = page.locator('.compare-cols');
if (await cols.count() > 0) {
const gridCols = await cols.evaluate(
(el) => window.getComputedStyle(el).gridTemplateColumns
);
// On single-column, gridTemplateColumns is a single track value.
expect(gridCols, 'stacked: single grid column track').not.toContain('1px');
}
});
});