Z ZPI 文档 v0.3.8
介绍页

源码

tests/desktop/error-log.spec.ts

typescript77 行查看原文
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import metadata from "../../package.json" with { type: "json" };
import { runCleanup } from "../helpers/cleanup.ts";
import { launchDesktop } from "../helpers/desktop.ts";
import { expect, test } from "../helpers/test.ts";

test("persists IPC, renderer and main-process errors, rejects malformed reports, and records renderer crashes", async () => {
  const dir = await mkdtemp(join(tmpdir(), "zpi-error-log-desktop-"));
  let app: Awaited<ReturnType<typeof launchDesktop>>;
  const file = join(dir, "agent", "logs", "error.log");
  try {
    app = await launchDesktop({ dir, url: "" });
    const page = await app.firstWindow();
    await expect(page.locator(".shell")).toBeVisible();
    await page.evaluate(async () => {
      await window.zpi.getSessionSnapshot("missing-session");
      window.zpi.logError({ source: "manual", message: "synthetic handled error", stack: "synthetic stack" });
      window.zpi.logError({ source: "oversized", message: "x".repeat(32_001) });
      window.zpi.logError({ source: "invalid", message: 123 } as unknown as Parameters<
        typeof window.zpi.logError
      >[0]);
      setTimeout(() => {
        throw new Error("synthetic renderer exception");
      }, 0);
      void Promise.reject(new Error("synthetic renderer rejection"));
      console.error("synthetic console error");
    });
    await app.evaluate(({ dialog }) => {
      // Keep Electron's original exception handler, but suppress its native modal during this test.
      dialog.showErrorBox = () => {};
      void Promise.reject(new Error("synthetic main rejection"));
      setImmediate(() => {
        throw new Error("synthetic main exception");
      });
    });
    await expect.poll(async () => await readFile(file, "utf8")).toContain("synthetic main rejection");
    await expect.poll(async () => await readFile(file, "utf8")).toContain("synthetic main exception");
    await expect.poll(async () => await readFile(file, "utf8")).toContain("renderer.unhandled-rejection");
    const records = (await readFile(file, "utf8"))
      .trim()
      .split("\n")
      .map((line) => JSON.parse(line));
    expect(records).toContainEqual(
      expect.objectContaining({ source: "ipc", method: "getSessionSnapshot", stack: expect.any(String) }),
    );
    expect(records).toContainEqual(
      expect.objectContaining({
        source: "renderer.manual",
        message: "synthetic handled error",
        stack: "synthetic stack",
      }),
    );
    expect(records).toContainEqual(
      expect.objectContaining({
        source: "renderer.uncaught",
        message: "synthetic renderer exception",
        stack: expect.any(String),
      }),
    );
    expect(records).toContainEqual(
      expect.objectContaining({ source: "renderer.console", message: "synthetic console error" }),
    );
    expect(records.some((record) => ["renderer.oversized", "renderer.invalid"].includes(record.source))).toBe(
      false,
    );
    expect(records.find((record) => record.source === "ipc").version).toBe(metadata.version);
    await app.evaluate(({ BrowserWindow }) =>
      BrowserWindow.getAllWindows()[0].webContents.forcefullyCrashRenderer(),
    );
    await expect.poll(async () => await readFile(file, "utf8")).toContain("renderer.process-gone");
  } finally {
    await runCleanup([() => rm(dir, { recursive: true, force: true }), () => app?.close()]);
  }
});