managarten/apps/mana/apps/web/scripts/smoke-prod-load.mjs
Till JS 32c95a3780
Some checks failed
CD Mac Mini / Detect Changes (push) Has been cancelled
CI / Detect Changes (push) Has been cancelled
CI / Validate (push) Has been cancelled
CI / Auth flow integration test (push) Has been cancelled
Docker Validate / Validate Dockerfiles (push) Has been cancelled
Mirror to Forgejo / Push to Forgejo (push) Has been cancelled
CD Mac Mini / Deploy (push) Has been cancelled
CI / Build mana-auth (push) Has been cancelled
CI / Build mana-search (push) Has been cancelled
CI / Build mana-sync (push) Has been cancelled
CI / Build mana-notify (push) Has been cancelled
CI / Build mana-api-gateway (push) Has been cancelled
CI / Build mana-crawler (push) Has been cancelled
CI / Build mana-media (push) Has been cancelled
CI / Build mana-credits (push) Has been cancelled
CI / Build mana-web (push) Has been cancelled
CI / Build chat-backend (push) Has been cancelled
CI / Build chat-web (push) Has been cancelled
CI / Build todo-backend (push) Has been cancelled
CI / Build todo-web (push) Has been cancelled
CI / Build calendar-backend (push) Has been cancelled
CI / Build calendar-web (push) Has been cancelled
CI / Build clock-web (push) Has been cancelled
CI / Build contacts-backend (push) Has been cancelled
CI / Build contacts-web (push) Has been cancelled
CI / Build presi-web (push) Has been cancelled
CI / Build storage-backend (push) Has been cancelled
CI / Build storage-web (push) Has been cancelled
CI / Build telegram-stats-bot (push) Has been cancelled
CI / Build food-backend (push) Has been cancelled
CI / Build food-web (push) Has been cancelled
CI / Build skilltree-web (push) Has been cancelled
Docker Validate / Build calendar-web (push) Has been cancelled
Docker Validate / Build quotes-web (push) Has been cancelled
Docker Validate / Build todo-backend (push) Has been cancelled
Docker Validate / Build todo-web (push) Has been cancelled
Docker Validate / Build mana-auth (push) Has been cancelled
Docker Validate / Build mana-sync (push) Has been cancelled
Docker Validate / Build mana-media (push) Has been cancelled
chore(diagnostics): headless prod smoke scripts
Two Playwright-based diagnostic scripts for investigating
production-only browser issues that curl can't reproduce:

- scripts/smoke-prod.mjs: loads mana.how like a fresh incognito
  tab, waits a configurable budget, reports every console error,
  request failure, still-pending request, and slow resource.
- scripts/smoke-prod-load.mjs: measures DOMContentLoaded + load
  event timing explicitly. Distinguishes "app interactive" from
  "browser tab spinner stops".

Run: `node apps/mana/apps/web/scripts/smoke-prod.mjs`
     MANA_URL=https://mana.how/login MANA_WAIT_MS=45000 node ...

Used today to rule out server-side issues in a loader-hang report
that reproduced only in one specific browser profile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 13:42:52 +02:00

100 lines
3.3 KiB
JavaScript

// Same as smoke-prod.mjs but explicitly waits for the 'load' event — the
// one that controls whether Chrome's tab spinner keeps spinning. If the
// tab spinner hangs in a real browser but Playwright's .goto({waitUntil:
// 'load'}) resolves quickly, the hang isn't about 'load' — it's about
// some async op kicked off after load that browsers still count as
// "loading" (e.g. service-worker install, web-manifest sub-fetches,
// deferred analytics).
//
// Also dumps ALL non-200 or slow requests to see which ones the browser
// is actually waiting on.
import { chromium } from 'playwright';
const URL = process.env.MANA_URL ?? 'https://mana.how/';
const GOTO_TIMEOUT = Number(process.env.MANA_GOTO_TIMEOUT ?? 60_000);
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0 Safari/537.36',
});
const page = await context.newPage();
const requests = new Map();
const errors = [];
page.on('request', (req) => {
requests.set(req.url() + '#' + req.method(), {
url: req.url(),
method: req.method(),
startedAt: Date.now(),
resourceType: req.resourceType(),
});
});
page.on('response', (res) => {
const k = res.url() + '#' + res.request().method();
const e = requests.get(k);
if (e) {
e.status = res.status();
e.duration = Date.now() - e.startedAt;
}
});
page.on('requestfailed', (req) => {
const k = req.url() + '#' + req.method();
const e = requests.get(k);
if (e) {
e.failure = req.failure()?.errorText ?? 'unknown';
e.duration = Date.now() - e.startedAt;
}
});
page.on('pageerror', (err) => errors.push(err.message));
const t0 = Date.now();
let domContentLoadedAt = null;
let loadedAt = null;
page.on('domcontentloaded', () => (domContentLoadedAt = Date.now() - t0));
page.on('load', () => (loadedAt = Date.now() - t0));
try {
console.log(`→ goto ${URL} (waitUntil=load, timeout=${GOTO_TIMEOUT}ms)`);
await page.goto(URL, { waitUntil: 'load', timeout: GOTO_TIMEOUT });
console.log(`✓ load event fired`);
} catch (e) {
console.log(`✗ goto failed: ${e.message}`);
}
const gotoEnded = Date.now() - t0;
// After load, wait up to 10s more to see if anything starts hanging.
await page.waitForTimeout(10_000);
const pending = [...requests.values()].filter((r) => r.status === undefined && !r.failure);
const failed = [...requests.values()].filter((r) => r.failure);
console.log('');
console.log(`DOMContentLoaded: ${domContentLoadedAt ?? 'never'} ms`);
console.log(`load event: ${loadedAt ?? 'never'} ms`);
console.log(`goto returned at: ${gotoEnded} ms`);
console.log(`total requests: ${requests.size}`);
console.log(`still pending after 10s post-load: ${pending.length}`);
if (pending.length) {
console.log('');
console.log('─── HANGING ───');
for (const r of pending) {
const age = Date.now() - r.startedAt;
console.log(` ${age}ms pending ${r.method} ${r.resourceType} ${r.url}`);
}
}
if (failed.length) {
console.log('');
console.log('─── FAILED ───');
for (const r of failed) console.log(` ${r.failure} ${r.url}`);
}
console.log('');
console.log(`uncaught pageerrors: ${errors.length}`);
for (const e of errors) console.log(` ${e}`);
await browser.close();