{"record":{"id":"bd9d1a78bb718223","repo":"jackwener/OpenCLI","slug":"page-evaluate-arguments-must-be-json-serializable-bd9d1a","errorCode":null,"errorMessage":"page.evaluate arguments must be JSON-serializable","messagePattern":"page\\.evaluate arguments must be JSON-serializable","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/browser/utils.ts","lineNumber":32,"sourceCode":" * Functions execute in the browser page context, so they cannot close over\n * Node-side variables. Pass external values as JSON-serializable args instead.\n */\nexport function serializeFunctionForEval(fn: EvaluateFunction, args: readonly unknown[] = []): string {\n  const source = fn.toString().trim();\n  const isFunctionSource = /^(async\\s+)?function[\\s(]/.test(source)\n    || /^(async\\s*)?(\\([^)]*\\)|[A-Za-z_$][\\w$]*)\\s*=>/.test(source);\n  if (!isFunctionSource || source.includes('[native code]')) {\n    throw new Error('page.evaluate(fn) requires a serializable arrow/function expression');\n  }\n\n  let serializedArgs: string;\n  try {\n    serializedArgs = JSON.stringify(args);\n  } catch (err) {\n    throw new Error(`page.evaluate arguments must be JSON-serializable: ${describeJsonError(err)}`);\n  }\n  if (serializedArgs === undefined) {\n    throw new Error('page.evaluate arguments must be JSON-serializable');\n  }\n\n  return `(${source})(...${serializedArgs})`;\n}\n\n/**\n * Wrap JS code for CDP Runtime.evaluate:\n * - Already an IIFE `(...)()` → send as-is\n * - Arrow/function literal → wrap as IIFE `(code)()`\n * - `new Promise(...)` or raw expression → send as-is (expression)\n */\nexport function wrapForEval(js: string): string {\n  if (typeof js !== 'string') return 'undefined';\n  const code = js.trim();\n  if (!code) return 'undefined';\n\n  // Already an IIFE: `(async () => { ... })()` or `(function() {...})()`\n  if (/^\\([\\s\\S]*\\)\\s*\\(.*\\)\\s*$/.test(code)) return code;","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/browser/utils.ts#L14-L50","documentation":"serializeFunctionForEval JSON.stringify's the caller-supplied args array before embedding them into a page.evaluate expression. JSON.stringify throws on values the structured-clone-free JSON format cannot represent (functions, circular structures, BigInt, symbols in objects). The library throws this error so the failure is reported before any browser evaluation happens, naming the offending argument.","triggerScenarios":"Calling buildEvaluateExpression(fn, args) or page.evaluate(fn, ...args) where any arg is a DOM node, a function, a class instance with circular references, a BigInt, or contains a symbol-keyed property that breaks JSON.stringify; also calling with a single non-serializable arg.","commonSituations":"Passing a Playwright/Puppeteer ElementHandle or Locator as an argument; passing a function like setTimeout or a callback; passing a Date-heavy object graph with cycles; passing values returned from other non-serializable APIs.","solutions":["Inspect each argument and ensure it is plain JSON data (string, number, boolean, null, plain arrays/objects).","Replace handles/functions with serializable selectors or primitive identifiers (e.g. pass the selector string and query inside the evaluated function).","Remove circular references (JSON.parse(JSON.stringify(x)) for plain data) and convert BigInt to string/number.","If you truly need DOM access, use locator APIs or evaluate with element handles via the driver's native evaluate, not this eval-string serializer.","Read describeJsonError in the thrown message to find the exact argument position that failed."],"exampleFix":"// before\nawait page.evaluate((el) => el.textContent, someElementHandle);\n// after\nconst text = await page.evaluate((sel) => document.querySelector(sel)?.textContent, '.item');","handlingStrategy":"validation","validationCode":"function isJsonSerializable(v, seen = new Set()) {\n  if (v === undefined || typeof v === 'function' || typeof v === 'symbol' || typeof v === 'bigint') return false;\n  if (typeof v !== 'object' || v === null) return true;\n  if (seen.has(v)) return false; // circular\n  seen.add(v);\n  return Object.values(v).every(x => isJsonSerializable(x, seen));\n}\nif (!args.every(a => isJsonSerializable(a))) throw new Error('args must be JSON-serializable');","typeGuard":"const isJsonSerializable = (v: unknown, seen = new Set<unknown>()): v is JsonValue =>\n  v === null || typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' ||\n  (Array.isArray(v) && v.every(x => isJsonSerializable(x, seen))) ||\n  (typeof v === 'object' && v !== null && !seen.has(v) && Object.values(v).every(x => isJsonSerializable(x, new Set(seen).add(v))));","tryCatchPattern":"try {\n  const expr = buildEvaluateExpression(fn, args);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('JSON-serializable')) {\n    console.error('Non-serializable evaluate argument:', args);\n  }\n  throw err;\n}","preventionTips":["Only pass plain JSON data (strings, numbers, booleans, null, plain objects/arrays) into evaluate.","Pass selector strings, not ElementHandles, when using this eval-string serializer.","Strip circular refs with JSON.parse(JSON.stringify(data)) for plain payloads.","Never pass functions, DOM nodes, BigInts, or Symbols as evaluate arguments."],"tags":["serialization","json","browser","evaluate"],"backgroundTag":"json-serialization-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}