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

  1. Ensure the function is self-contained with no closure references — pass all needed values as arguments.
  2. Use page.evaluate() with explicit argument passing: page.evaluate((a, b) => a + b, x, y) instead of () => x + y.
  3. 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

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


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/4cccbdf3193186ed. Report an issue: GitHub.