testErrorsGroupByWhatThrewThem
Errors grouped by the definition that threw them (found from their stacks), what they said alike (no post 1,
no post 2), once a trace however many spans they went up through, each with the version it began with and the
history entry that made it; a page's uncaught errors too, where tcm dev serves the module that threw.
import { CHROME, withPage } from "#codebase/tcmts/cli/test/browser"; import { okIn, plainEnv, strip, tcmBin, tempDir, withChildren } from "#codebase/tcmts/cli/test/e2e/harness"; import { hashOf } from "#codebase/tcmts/cli/test/e2e/jobs"; import { FAILING_BOARD } from "#codebase/tcmts/cli/test/e2e/traces"; import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; export const testErrorsGroupByWhatThrewThem = () => withChildren(async (keep) => { if (!CHROME) return console.log("skipped: no Chrome"); const dir = tempDir("traces-errors"); const tcm = (...args: string[]) => strip(okIn(dir, ...args)); const waitFor = async (cond: () => boolean | Promise<boolean>, what: string, ms = 20000) => { for ( const end = Date.now() + ms; Date.now() < end; await new Promise((r) => setTimeout(r, 200)) ) if (await cond()) return; throw new Error(`timed out waiting for ${what}`); }; tcm("init"); tcm("lib", "add", "tcm", "server", "--at", "lib.server"); fs.writeFileSync(path.join(dir, "scratch.ts"), FAILING_BOARD); tcm("add", "-m", "Board: posts that fail"); const child = keep( spawn(process.execPath, [tcmBin(), "dev", "board.main", "--port", "0"], { cwd: dir, env: plainEnv(), }), ); let out = ""; child.stdout.setEncoding("utf8").on("data", (d: string) => (out += d)); child.stderr.setEncoding("utf8").on("data", (d: string) => (out += d)); await waitFor(() => /traces\s+kept/.test(out), "tcm dev"); const url = /url\s+(http:\/\/\S+)/.exec(out)![1]!; for (const n of [1, 2, 3]) { const r = await fetch(new URL(`/__tcm/rpc/${hashOf(dir, "board.fail")}`, url), { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify([n]), }); assert.equal(r.status, 500); } await withPage(url, async (page) => { await page.waitFor<boolean>("!!document.getElementById('boom')", (v) => v); await page.evaluate("document.getElementById('call').click()"); await page.waitFor<string>("document.body.dataset.failed", (v) => v === "1"); await page.evaluate("document.getElementById('boom').click()"); await page.waitFor<boolean>("new Promise((r) => setTimeout(() => r(true), 100))", (v) => v); await page.evaluate("dispatchEvent(new Event('pagehide'))"); }); await waitFor(() => /board\.press/.test(tcm("traces", "--errors")), "the page's error"); const errors = tcm("traces", "--errors"); assert.match( errors, /\nboard\.parse {2}Error: no post 1\n {2}4 time\(s\), [^\n]+\n {2}began with #[a-z2-7]{8}: "Board: posts that fail" [^\n]+\n {2}through board\.fail \(call\)/, "thrown in board.parse: four calls, three from here and one from the page, said alike", ); assert.match(errors, /\nboard\.press in the browser {2}Error: pressed \d+\n {2}1 time\(s\)/); })
uses
- tcmts.cli.test.browser.CHROME
- tcmts.cli.test.browser.withPage
- tcmts.cli.test.e2e.harness.okIn
- tcmts.cli.test.e2e.harness.plainEnv
- tcmts.cli.test.e2e.harness.strip
- tcmts.cli.test.e2e.harness.tcmBin
- tcmts.cli.test.e2e.harness.tempDir
- tcmts.cli.test.e2e.harness.withChildren
- tcmts.cli.test.e2e.jobs.hashOf
- tcmts.cli.test.e2e.traces.FAILING_BOARD