// Real-browser pass (Playwright Chromium): the zero-build page and the // SSR + hydration page, with console output treated as part of the spec — // no errors, no hydration warnings. // // Run with: npm run test:e2e (needs `npx playwright install chromium-headless-shell`) import { test, before, after } from 'node:test' import assert from 'node:assert' import { chromium } from 'playwright' import { startServer } from './serve.js' let browser, ctx, srv before(async () => { srv = await startServer() browser = await chromium.launch() ctx = await browser.newContext() }) after(async () => { await browser?.close() await srv?.close() }) // renders are raf-batched, so poll instead of reading synchronously function waitForCount (page, text) { return page.waitForFunction( (t) => document.querySelector('h1')?.textContent === t, text, { timeout: 5000 } ) } async function openPage (path) { const page = await ctx.newPage() const console_ = { errors: [], warnings: [] } page.on('console', (msg) => { if (msg.type() === 'error') console_.errors.push(msg.text()) if (msg.type() === 'warning') console_.warnings.push(msg.text()) }) page.on('pageerror', (err) => console_.errors.push(String(err))) await page.goto(srv.origin + path) return { page, console_ } } test('zero-build page: import map + native ESM, no bundler', async () => { const { page, console_ } = await openPage('/examples/counter/') await waitForCount(page, 'count is 0') await page.click('button') await waitForCount(page, 'count is 1') await page.click('button') await page.click('button') await waitForCount(page, 'count is 3') assert.deepStrictEqual(console_.errors, [], 'no console errors') await page.close() }) test('SSR page: content before JavaScript, then live after hydration', async () => { // the raw response must already contain the rendered view const res = await fetch(srv.origin + '/ssr') const rawHtml = await res.text() assert.match(rawHtml, /count is 0/, 'server sent rendered markup') const { page, console_ } = await openPage('/ssr') await waitForCount(page, 'count is 0') // hydrated: the server DOM must now respond to clicks await page.click('button') await waitForCount(page, 'count is 1') assert.deepStrictEqual(console_.errors, [], 'no console errors') const mismatches = console_.warnings.filter((w) => w.includes('markup differ')) assert.deepStrictEqual(mismatches, [], 'no hydration mismatch warnings') await page.close() }) test('SSR page: server DOM is adopted, not replaced', async () => { const { page } = await openPage('/ssr') await waitForCount(page, 'count is 0') // Prove adoption by a surviving expando: mark the node, force a real // render, and confirm the same node object still holds the marker. await page.evaluate(() => { document.querySelector('h1').__marker = 'server-node' }) await page.click('button') await waitForCount(page, 'count is 1') const marker = await page.evaluate(() => document.querySelector('h1').__marker) assert.strictEqual(marker, 'server-node', 'h1 survived that render in place') await page.close() })