// End-to-end smoke test against `pnpm preview` (http://localhost:4173). Run: node scripts/smoke.mjs import { chromium } from 'playwright-core'; const URL = process.env.URL ?? 'http://localhost:4173/'; const browser = await chromium.launch(); const page = await browser.newPage({ viewport: { width: 1280, height: 900 }, locale: 'pl-PL' }); const errors = []; page.on('pageerror', (e) => errors.push(`pageerror: ${e.message}`)); page.on('console', (m) => { if (m.type() === 'error') errors.push(`console: ${m.text()}`); }); let step = 0; const ok = (cond, msg) => { step++; if (!cond) { console.error(`FAIL ${step}: ${msg}`); process.exitCode = 1; } else console.log(`ok ${step}: ${msg}`); }; await page.goto(URL, { waitUntil: 'networkidle' }); ok(await page.locator('text=Zaloguj się').first().isVisible(), 'login screen shown'); // resident flow: report -> appears in own place detail as unverified await page.click('text=Zaloguj jako Anna Kowalska'); await page.waitForSelector('#q'); const total = Number(((await page.locator('text=Wyniki:').first().innerText()).match(/\d+/) ?? ['0'])[0]); ok(total > 4000, `places list loaded (${total} places)`); await page.fill('#q', 'Sukiennice'); await page.click('button[id^=place-]'); await page.waitForSelector('text=Szczegóły dostępności'); await page.click('text=Popraw lub uzupełnij >> nth=0'); await page.selectOption('#rep-attr', 'entrance'); await page.click('label[for=rep-v-no]'); await page.click('text=Zapisz zgłoszenie'); ok(await page.locator('text=Zgłoszenie zapisane').isVisible(), 'report saved and flagged unverified'); // settings persist per account await page.click('nav >> text=Profil'); await page.selectOption('#set-size', '125'); await page.reload({ waitUntil: 'networkidle' }); ok((await page.evaluate(() => document.documentElement.style.fontSize)) === '125%', 'text size persisted across reload'); // official flow: queue shows pending reports, accept one -> place becomes verified await page.click('nav >> text=Profil'); await page.click('text=Wyloguj'); await page.click('text=Zaloguj jako Maria Wiśniewska'); await page.waitForSelector('[role=tablist]'); const pending = await page.locator('button:has-text("Zatwierdź")').count(); ok(pending >= 1, `moderation queue (first tab) shows the report filed above (${pending} pending)`); await page.locator('button:has-text("Zatwierdź")').first().click(); ok((await page.locator('button:has-text("Zatwierdź")').count()) === pending - 1, 'accepting removes it from the queue'); await page.click('button:has-text("Cofnij"):not(:has-text("decyzję"))'); ok((await page.locator('button:has-text("Zatwierdź")').count()) === pending, 'undo puts it back'); await page.locator('button:has-text("Zatwierdź")').first().click(); await page.click('[role=tab]:has-text("Jakość")'); await page.waitForSelector('.kbb-kpis'); ok(await page.getByText(/^0 \/ \d+$/).first().isVisible(), 'data tab shows operator data gap (0 / N stops)'); // public transport: toggle in the route form, then either connections or an honest "none" (depends on the time of day) await page.click('nav >> text=Trasa'); await page.fill('#route-from', 'Sukiennice'); await page.fill('#route-to', 'Tauron Arena Kraków'); await page.click('button[type=submit]:has-text("Wyznacz")'); await page.waitForSelector('.kbb-modes'); await page.click('.kbb-mode:has-text("Komunikacja")'); await page.locator('.kbb-jhead').first().or(page.getByText('Brak połączenia')).first().waitFor({ timeout: 20000 }).catch(async () => { console.error('transit result text:', (await page.locator('.kbb-result').innerText()).slice(0, 300), '| errors:', errors.slice(0, 3)); }); const connections = await page.locator('.kbb-jhead').count(); ok(connections > 0 || (await page.getByText('Brak połączenia').count()) > 0, `transit planner answered (${connections} connections)`); await page.click('.kbb-mode:has-text("Pieszo")'); // map pins + route render without worker errors await page.click('nav >> text=Trasa'); await page.fill('#route-from', 'Sukiennice'); await page.fill('#route-to', 'Zamek Królewski na Wawelu'); await page.click('button[type=submit]:has-text("Wyznacz")'); await page.waitForSelector('text=Opis trasy tekstem'); await page.waitForTimeout(1500); const canvas = await page.locator('canvas.maplibregl-canvas').count(); ok(canvas === 1, 'map canvas present'); ok(await page.locator('text=Bariery i uwagi na trasie').isVisible(), 'route summary + barriers listed'); const steps = page.locator('details:has(summary:has-text("Opis trasy tekstem"))'); ok(!(await steps.evaluate((d) => d.open)), 'text route is collapsed by default'); await steps.locator('summary').click(); ok((await steps.locator('li').count()) > 3 && (await steps.locator('li').first().isVisible()), 'text route steps listed once opened'); await page.click('text=Trasa >> nth=0').catch(() => {}); ok(errors.filter((e) => !/GL Driver|favicon/.test(e)).length === 0, `no console errors ${JSON.stringify(errors.slice(0, 3))}`); await browser.close();