{"record":{"id":"1a72358f76f1b33e","repo":"jackwener/OpenCLI","slug":"page-evaluate-arguments-must-be-json-serializable","errorCode":null,"errorMessage":"page.evaluate arguments must be JSON-serializable: ${describeJsonError(err)}","messagePattern":"page\\.evaluate arguments must be JSON-serializable: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/browser/utils.ts","lineNumber":29,"sourceCode":"/**\n * Serialize a function-form page.evaluate call for CDP Runtime.evaluate.\n *\n * 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';","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/browser/utils.ts#L11-L47","documentation":"After validating the function source, `serializeFunctionForEval` JSON-stringifies the evaluate arguments. If an argument contains values JSON cannot represent — functions, Symbols, undefined at object positions, circular references, BigInt, class instances with such fields — `JSON.stringify` throws and this error wraps the underlying cause via `describeJsonError`. Browser evaluate boundaries only carry structured-clone/JSON data, so arguments must be plain serializable values.","triggerScenarios":"Passing a callback or function inside args (`page.evaluate(fn, { onDone: () => {} })`); passing DOM nodes, Date is OK but Map/Set/BigInt/circular structures are not; passing class instances containing Symbol keys or circular references; accidentally passing `undefined` as a lone argument (hits the `serializedArgs === undefined` branch).","commonSituations":"React/testing code passing element handles or state objects with embedded functions; configs carrying logger functions; passing Error objects (non-enumerable message/stack) into the page; migrating from APIs that accepted a serialize parameter (like puppeteer's deprecated option) that this library does not support.","solutions":["Strip non-serializable fields before the call: pass only plain objects, strings, numbers, booleans, arrays, null.","Convert special types explicitly: Date → ISO string, Map/Set → arrays, BigInt → string.","For circular structures, pick the plain fields you need or use a replacer in your own preprocessing.","For functions inside args, send a name/enum string and branch inside the evaluated function instead.","Inspect the wrapped `describeJsonError` message to locate the offending argument and path."],"exampleFix":"// before\nawait page.evaluate(fn, { el: domNode, done: () => {} });\n// after\nawait page.evaluate(fn, { selector: '#target', doneEvent: 'ready' });","handlingStrategy":"validation","validationCode":"function assertJsonSafe(value: unknown, path = 'args'): void {\n  if (value === undefined) throw new Error(`${path} is undefined`);\n  if (typeof value === 'function' || typeof value === 'symbol' || typeof value === 'bigint') throw new Error(`${path} is ${typeof value}`);\n  if (value === null || typeof value !== 'object') return;\n  if (seen.has(value)) throw new Error(`${path} is circular`);\n  seen.add(value);\n  for (const [k, v] of Object.entries(value)) assertJsonSafe(v, `${path}.${k}`);\n  seen.delete(value);\n}\nconst seen = new Set();\nassertJsonSafe(args);","typeGuard":"const isJsonSafe = (v: unknown): v is string | number | boolean | null | JsonSafe[] | { [k: string]: JsonSafe } => {\n  try { JSON.stringify(v); return true; } catch { return false; }\n};","tryCatchPattern":"try {\n  return await page.evaluate(fn, args);\n} catch (e) {\n  if (String(e.message).includes('JSON-serializable')) {\n    console.error('Non-serializable evaluate args:', e.message);\n    throw new Error('Strip functions/DOM nodes/circular refs from evaluate args');\n  }\n  throw e;\n}","preventionTips":["Validate args with JSON.stringify(args) in a try/catch before calling evaluate.","Convert Date→ISO string, Map/Set→array, BigInt→string at the boundary.","Never pass callbacks, DOM nodes, or class instances with methods inside args.","Send function selectors as strings and branch inside the page function."],"tags":["serialization","json","evaluate"],"backgroundTag":"non-serializable-evaluate-arguments","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}