{"record":{"id":"0b770a80e29d55b1","repo":"jackwener/OpenCLI","slug":"page-evaluate-fn-requires-a-serializable-arrow-fu","errorCode":null,"errorMessage":"page.evaluate(fn) requires a serializable arrow/function expression","messagePattern":"page\\.evaluate\\(fn\\) requires a serializable arrow/function expression","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/browser/utils.ts","lineNumber":22,"sourceCode":"\ntype EvaluateFunction = (...args: never[]) => unknown;\n\nfunction describeJsonError(err: unknown): string {\n  return err instanceof Error ? err.message : String(err);\n}\n\n/**\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","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/src/browser/utils.ts#L4-L40","documentation":"`serializeFunctionForEval` converts a JS function to source text for `page.evaluate`. It validates the source with regexes for `function` / arrow syntax and rejects native functions (`[native code]`). If `fn.toString()` is not a parseable arrow/function expression — e.g. a bound method, a native builtin, a class method stripped of context, or minified/transpiled output the regex misses — this error is thrown. The library demands serializable source because the function must be stringified and re-parsed in the browser.","triggerScenarios":"Passing `Math.max.bind(null, 1)` or another bound function (toString yields `function () { [native code] }`); passing a native builtin like `parseInt` directly; passing a class method reference whose source doesn't match the arrow/function patterns; passing a function that went through a proxy or was defined via `new Function` in a way that serializes to native code.","commonSituations":"TypeScript code where `await page.evaluate(fn)` received a method reference instead of an inline arrow; wrappers that forward `arguments`-style callables; bundler output where helper functions became native or exotic; accidentally passing a variable holding `console.log` or similar builtin.","solutions":["Pass an inline arrow function written in the caller: `page.evaluate((x) => x * 2, 5)`.","If you must pass a method, wrap it: `(...args) => obj.method(...args)` so its own source is an arrow.","Never pass bound functions (`fn.bind(...)`) or native builtins directly — wrap them in an arrow.","Move any closure values into evaluate's args array (they must be JSON-serializable) instead of relying on scope capture.","Check for `[native code]` in `fn.toString()` before calling to fail fast with a clearer message."],"exampleFix":"// before\nawait page.evaluate(handler.bind(ctx)); // [native code] source\n// after\nawait page.evaluate((arg) => ctx.handler(arg), arg);","handlingStrategy":"validation","validationCode":"function isSerializableFn(fn: Function): boolean {\n  const src = fn.toString().trim();\n  return (/^(async\\s+)?function[\\s(]/.test(src) || /^(async\\s*)?(\\([^)]*\\)|[A-Za-z_$][\\w$]*)\\s*=>/.test(src)) && !src.includes('[native code]');\n}\nif (!isSerializableFn(fn)) throw new Error('pass an inline arrow function to evaluate');","typeGuard":"const isPlainFunction = (fn: unknown): fn is (...args: unknown[]) => unknown =>\n  typeof fn === 'function' && !fn.toString().includes('[native code]');","tryCatchPattern":"try {\n  return await page.evaluate(fn, args);\n} catch (e) {\n  if (String(e.message).includes('serializable arrow/function')) {\n    throw new Error('Wrap bound/native functions: page.evaluate((x) => obj.method(x), arg)');\n  }\n  throw e;\n}","preventionTips":["Always author evaluate callbacks as inline arrows in the calling code.","Never pass .bind() results or native builtins directly to evaluate.","Move closure-captured values into the args array.","Lint for evaluate(fn) calls where fn is a method reference."],"tags":["serialization","browser","evaluate"],"backgroundTag":"non-serializable-evaluate-function","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}