microsoft/playwright · error · Error
Passed function is not well-serializable!
Error message
Passed function is not well-serializable!
What it means
Thrown by normalizeEvaluationExpression() when a function passed to page.evaluate() or similar cannot be parsed by the JavaScript engine even after wrapping attempts. The function first tries new Function('(' + expression + ')'), and if that fails, prepends 'function ' or 'async function ' and retries. If both attempts produce a SyntaxError, the function is considered not well-serializable.
Source
Thrown at packages/playwright-core/src/server/javascript.ts:323
export function normalizeEvaluationExpression(expression: string, isFunction: boolean | undefined): string {
expression = expression.trim();
if (isFunction) {
try {
new Function('(' + expression + ')');
} catch (e1) {
// This means we might have a function shorthand. Try another
// time prefixing 'function '.
if (expression.startsWith('async '))
expression = 'async function ' + expression.substring('async '.length);
else
expression = 'function ' + expression;
try {
new Function('(' + expression + ')');
} catch (e2) {
// We tried hard to serialize, but there's a weird beast here.
throw new Error('Passed function is not well-serializable!');
}
}
}
if (/^(async)?\s*function(\s|\()/.test(expression))
expression = '(' + expression + ')';
return expression;
}
// Error inside the expression evaluation as opposed to a protocol error.
export class JavaScriptErrorInEvaluate extends Error {
}
export function isJavaScriptErrorInEvaluate(error: Error) {
return error instanceof JavaScriptErrorInEvaluate;
}
export function sparseArrayToString(entries: { name: string, value?: any }[]): string {View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Ensure the function is self-contained with no closure references — pass all needed values as arguments.
- Use page.evaluate() with explicit argument passing: page.evaluate((a, b) => a + b, x, y) instead of () => x + y.
- If the function is complex, define it as a string expression or use addInitScript to pre-load it.
Example fix
// before
const threshold = 100;
await page.evaluate(() => document.querySelectorAll('.item').length > threshold); // closure ref
// after
const threshold = 100;
await page.evaluate(threshold => document.querySelectorAll('.item').length > threshold, threshold); Defensive patterns
Strategy: validation
Validate before calling
// Ensure function is self-contained: no closure references
function isSelfContained(fn) {
try {
new Function('(' + fn.toString() + ')');
return true;
} catch {
try {
new Function('(function ' + fn.toString().replace(/^async ?/, 'async ') + ')');
return true;
} catch { return false; }
}
} Prevention
- Pass all external values as arguments, not via closure.
- Avoid referencing 'this' from outer scope in evaluate functions.
- Test evaluate functions in isolation to catch serialization issues early.
When it happens
Trigger: Passing a function that relies on closure variables (these cannot be serialized), uses non-standard syntax, or is an async generator. Also triggered by functions that use syntax constructs that break when wrapped in new Function(). Arrow functions with implicit returns of objects, or functions using features not supported by the runtime's parser.
Common situations: Passing an arrow function that references an outer variable (closure). Passing a function that uses 'await' without 'async'. Passing a class constructor or generator function. Functions with JSX or TypeScript syntax not transpiled before evaluation. Functions that reference 'this' from the calling context.
Related errors
- Cannot serialize result: object reference chain is too long.
- Cannot serialize result: object reference chain is too long.
- JSHandle is disposed!
- JSHandles can be evaluated only in the context they were cre
- Cannot serialize result: object reference chain is too long.
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/4cccbdf3193186ed.
Report an issue: GitHub.