puppeteer/puppeteer · error · Error

Trying to evaluate JSHandle from different global types. Usu

Error message

Trying to evaluate JSHandle from different global types. Usually this means you're using a handle from a worker in a page or vice versa.

What it means

Thrown by Realm.serialize() when a JSHandle/ElementHandle being passed as an evaluation argument belongs to a realm of a different global type than the target realm — specifically when one is a BidiFrameRealm (window) and the other is a BidiWorkerRealm (worker), or one is frame and the other worker. Cross-global-type handle passing is impossible because workers and pages have disjoint object graphs.

Source

Thrown at packages/puppeteer-core/src/bidi/Realm.ts:246

    }
    return BidiJSHandle.from(result, this);
  }

  async serializeAsync(arg: unknown): Promise<Bidi.Script.LocalValue> {
    if (arg instanceof LazyArg) {
      arg = await arg.get(this);
    }
    return this.serialize(arg);
  }

  serialize(arg: unknown): Bidi.Script.LocalValue {
    if (arg instanceof BidiJSHandle || arg instanceof BidiElementHandle) {
      if (arg.realm !== this) {
        if (
          !(arg.realm instanceof BidiFrameRealm) ||
          !(this instanceof BidiFrameRealm)
        ) {
          throw new Error(
            "Trying to evaluate JSHandle from different global types. Usually this means you're using a handle from a worker in a page or vice versa.",
          );
        }
        if (arg.realm.environment !== this.environment) {
          throw new Error(
            "Trying to evaluate JSHandle from different frames. Usually this means you're using a handle from a page on a different page.",
          );
        }
      }
      if (arg.disposed) {
        throw new Error('JSHandle is disposed!');
      }
      return arg.remoteValue() as Bidi.Script.RemoteReference;
    }

    return BidiSerializer.serialize(arg);
  }

View on GitHub (pinned to d484e21c17)

Solutions

  1. Re-acquire the handle inside the correct realm: serialize the value to a primitive in the source realm and pass that, or re-query the DOM in the page.
  2. For DOM access from a worker, message-pass node data via postMessage rather than passing an ElementHandle.
  3. Confirm both handles share the same realm type before combining them in one evaluate.
  4. Use `worker.evaluateHandle(...)` to create handles native to the worker realm.

Example fix

// before
const handle = await page.evaluateHandle(() => window);
await worker.evaluate(h => h.location, handle); // throws: different global types

// after
await worker.evaluate(() => self.location);
Defensive patterns

Strategy: type-guard

Validate before calling

// Compare realm kinds before cross-passing handles (uses public frame mainRealm).
const sameRealmKind = (a: any, b: any) =>
  !!a && !!b && a?.constructor?.name === b?.constructor?.name;

Type guard

// Heuristic: element/JS handles from workers come via WebWorker; page handles via Page.
const isWorkerHandle = (h: any): boolean =>
  !!h?.realm && /Worker/.test(h.realm?.constructor?.name ?? '');

Try / catch

try {
  await worker.evaluate(fn, pageHandle);
} catch (e) {
  if (/different global types/.test(String(e?.message))) {
    // re-acquire or pass primitives instead
  } else { throw e; }
}

Prevention

When it happens

Trigger: Passing a handle obtained from `page.evaluateHandle(...)` (frame realm) into `worker.evaluate(...)` or vice versa; passing an ElementHandle from the page into a WebWorker's evaluate; calling `worker.evaluate(fn, pageHandle)`.

Common situations: Sharing handles across page and worker in test harnesses; refactoring that moved an evaluateHandle call from page to worker without re-acquiring the handle; assuming workers can see page DOM directly.

Related errors


AI-assisted analysis of puppeteer/puppeteer@d484e21c17 (2026-08-12). Data as JSON: /api/errors/3a5d486289916e6f. Report an issue: GitHub.