mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-04 13:07:11 +00:00
* fix(cli): use normalizeErrorMessage so validate/inspect never print "[object Object]" The validate and inspect (layout) commands formatted thrown values with `err instanceof Error ? err.message : String(err)`. When a browser/CDP/ Puppeteer protocol error or a structured page error reaches the formatter as a plain object without a string `message`, `String(obj)` yields the useless literal "[object Object]", hiding the real cause. Route those paths through the existing shared `normalizeErrorMessage` helper, which returns an Error's message, a string as-is, an object's `.message` when present, or a compact JSON serialization otherwise (with a key-list and String fallback for circular/opaque objects). Also fold the duplicated local `errorMessage` helpers in batchRender and preview into the same shared helper. Covered by added assertions in errorMessage.test.ts for the no-message object and Puppeteer-style protocol-error object cases. * fix(cli): route remaining browser/process error sites through normalizeErrorMessage The validate/inspect fix routed only those two commands through the shared normalizeErrorMessage helper. The same err instanceof Error ? err.message : String(err) pattern survived in the other commands that drive a headless browser or an external process (ffmpeg, Docker, CDP) or surface a network API error, so a thrown structured object without a string message would still render as the useless literal [object Object]. Route those sites through the shared helper: snapshot.ts (the closest sibling to validate/inspect, same bug class), render.ts (Chrome launch + Docker build), capture/index.ts and commands/capture.ts (page-driven extraction), auth/browser.ts, browser/manager.ts (Puppeteer browser resolution), and the cloud/lambda paths (cloud/render.ts, cloudrun.ts, lambda/render-batch.ts, lambda/policies.ts, cloud/detectAspectRatio.ts) that surface API/network error objects. Only the message-deriving expression changes; control flow and error propagation are untouched. capture/index.ts keeps appending the stack for real Errors and only routes the non-Error branch. Adds a helper test for a structured CDP-style error object (code + nested data, no message).
79 lines
2.9 KiB
TypeScript
79 lines
2.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { normalizeErrorMessage } from "./errorMessage.js";
|
|
|
|
describe("normalizeErrorMessage", () => {
|
|
it("returns the message of an Error instance", () => {
|
|
expect(normalizeErrorMessage(new Error("render failed"))).toBe("render failed");
|
|
});
|
|
|
|
it("returns the message of an Error subclass", () => {
|
|
expect(normalizeErrorMessage(new TypeError("bad input"))).toBe("bad input");
|
|
});
|
|
|
|
it("passes a plain string through", () => {
|
|
expect(normalizeErrorMessage("already a message")).toBe("already a message");
|
|
});
|
|
|
|
it("prefers a string message property on plain objects", () => {
|
|
expect(normalizeErrorMessage({ message: "from object", code: 500 })).toBe("from object");
|
|
});
|
|
|
|
it("stringifies plain objects without a message property", () => {
|
|
expect(normalizeErrorMessage({ status: 503, retryable: true })).toBe(
|
|
'{"status":503,"retryable":true}',
|
|
);
|
|
});
|
|
|
|
it("stringifies objects whose message property is not a string", () => {
|
|
expect(normalizeErrorMessage({ message: 42 })).toBe('{"message":42}');
|
|
});
|
|
|
|
it("falls back to a key list when JSON.stringify throws on a cycle", () => {
|
|
const cyclic: Record<string, unknown> = { code: "E_LOOP" };
|
|
cyclic["self"] = cyclic;
|
|
expect(normalizeErrorMessage(cyclic)).toBe("{code, self}");
|
|
});
|
|
|
|
it("falls back to String() when an object resists both JSON and key listing", () => {
|
|
const hostile = new Proxy(
|
|
{},
|
|
{
|
|
ownKeys() {
|
|
throw new Error("no keys for you");
|
|
},
|
|
},
|
|
);
|
|
expect(normalizeErrorMessage(hostile)).toBe("[object Object]");
|
|
});
|
|
|
|
it("never yields '[object Object]' for a no-message object (the reported validate/inspect bug)", () => {
|
|
const out = normalizeErrorMessage({ code: 42 });
|
|
expect(out).not.toBe("[object Object]");
|
|
expect(out).toContain("42");
|
|
});
|
|
|
|
it("surfaces a Puppeteer-style protocol error object via its message", () => {
|
|
expect(normalizeErrorMessage({ name: "ProtocolError", message: "Target closed" })).toBe(
|
|
"Target closed",
|
|
);
|
|
});
|
|
|
|
it("surfaces a structured CDP error object (no message) instead of '[object Object]'", () => {
|
|
// The shape snapshot/render can receive when a CDP/protocol rejection is a
|
|
// plain object rather than an Error: no string `message`, only code + data.
|
|
const out = normalizeErrorMessage({ code: -32000, data: { reason: "navigation timeout" } });
|
|
expect(out).not.toBe("[object Object]");
|
|
expect(out).toContain("navigation timeout");
|
|
});
|
|
|
|
it("returns 'unknown error' for null and undefined", () => {
|
|
expect(normalizeErrorMessage(null)).toBe("unknown error");
|
|
expect(normalizeErrorMessage(undefined)).toBe("unknown error");
|
|
});
|
|
|
|
it("stringifies non-object primitives", () => {
|
|
expect(normalizeErrorMessage(42)).toBe("42");
|
|
expect(normalizeErrorMessage(false)).toBe("false");
|
|
});
|
|
});
|