69 lines
3.3 KiB
JavaScript
69 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Production-build smoke (QA_PLAN B15). Drives the REAL built artifact — `node
|
|
* server.js` serving dist/ — not the Vite dev server, so it validates that the
|
|
* split vendor chunks (QA-B0-01) actually load and the app boots in production.
|
|
*
|
|
* Usage: node scripts/prod-smoke.js (expects `npm run build` already run and a
|
|
* server already listening on PROD_SMOKE_URL — the shell wrapper handles both).
|
|
*/
|
|
const { chromium } = require('@playwright/test');
|
|
|
|
const URL = process.env.PROD_SMOKE_URL || 'http://localhost:3098';
|
|
const USER = process.env.E2E_USER || 'e2e_user';
|
|
const PASS = process.env.E2E_PASS || 'e2e_pass_1234';
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage();
|
|
const errors = [];
|
|
const failed = [];
|
|
page.on('console', (m) => { if (m.type() === 'error') errors.push(m.text()); });
|
|
page.on('pageerror', (e) => errors.push(String(e)));
|
|
page.on('requestfailed', (r) => failed.push(`${r.url()} ${r.failure()?.errorText || ''}`));
|
|
|
|
let ok = true;
|
|
const fail = (msg) => { ok = false; console.error(' ✖', msg); };
|
|
|
|
try {
|
|
// 1. App shell + split chunks load; login page renders.
|
|
await page.goto(URL + '/login', { waitUntil: 'networkidle' });
|
|
const heading = page.getByRole('heading', { name: /sign in/i });
|
|
if (await heading.isVisible().catch(() => false)) console.log(' ✔ login page renders (vendor chunks loaded)');
|
|
else fail('login page did not render — a chunk may have failed to load');
|
|
|
|
// 2. Log in and reach the authenticated app (lazy route chunks load).
|
|
await page.locator('#username').fill(USER);
|
|
await page.locator('#password').fill(PASS);
|
|
await page.getByRole('button', { name: /sign in/i }).click();
|
|
await page.waitForURL((u) => !u.pathname.startsWith('/login'), { timeout: 15000 });
|
|
const gotIt = page.getByRole('button', { name: 'Got it' });
|
|
await gotIt.click({ timeout: 8000 }).catch(() => {});
|
|
if (await page.getByRole('button', { name: 'Add Bill' }).isVisible().catch(() => false)) {
|
|
console.log(' ✔ authenticated Tracker renders on the production build');
|
|
} else fail('authenticated Tracker did not render');
|
|
|
|
// 3. A couple of lazy-loaded routes render (their chunks resolve).
|
|
for (const path of ['/bills', '/analytics', '/spending']) {
|
|
await page.goto(URL + path, { waitUntil: 'networkidle' });
|
|
const empty = await page.locator('body').innerText().catch(() => '');
|
|
if (empty && empty.trim().length > 0) console.log(` ✔ ${path} rendered`);
|
|
else fail(`${path} rendered blank (lazy chunk failure?)`);
|
|
}
|
|
} catch (e) {
|
|
fail('exception: ' + e.message);
|
|
}
|
|
|
|
const chunkErrors = failed.filter((f) => /\.js|\.css|assets/.test(f));
|
|
if (chunkErrors.length) fail('failed asset requests:\n ' + chunkErrors.join('\n '));
|
|
// The pre-login page probes /api/auth/session and gets 401 by design (then shows
|
|
// login) — that's expected, not a defect. Everything else is a real error.
|
|
const benign = /session check failed|Not authenticated|auth\/session|status of 401/i;
|
|
const realErrors = errors.filter((e) => !benign.test(e));
|
|
if (realErrors.length) fail('console/page errors:\n ' + realErrors.slice(0, 5).join('\n '));
|
|
|
|
await browser.close();
|
|
console.log(ok ? '\nPROD SMOKE: PASS' : '\nPROD SMOKE: FAIL');
|
|
process.exit(ok ? 0 : 1);
|
|
})();
|