microsoft/playwright · error · JavaScriptErrorInEvaluate
JSHandle is disposed!
Error message
JSHandle is disposed!
What it means
Thrown as a JavaScriptErrorInEvaluate by evaluateExpression() when serializing a call argument that is a JSHandle with an _objectId (i.e., a remote object reference) but whose _disposed flag is true. This means the handle was previously disposed (via handle.dispose() or context/page navigation) but was still passed as an argument to an evaluate call.
Source
Thrown at packages/playwright-core/src/server/javascript.ts:267
export async function evaluate(context: ExecutionContext, returnByValue: boolean, pageFunction: Function | string, ...args: any[]): Promise<any> {
return evaluateExpression(context, String(pageFunction), { returnByValue, isFunction: typeof pageFunction === 'function' }, ...args);
}
export async function evaluateExpression(context: ExecutionContext, expression: string, options: { returnByValue?: boolean, isFunction?: boolean }, ...args: any[]): Promise<any> {
expression = normalizeEvaluationExpression(expression, options.isFunction);
const handles: (Promise<JSHandle>)[] = [];
const toDispose: Promise<JSHandle>[] = [];
const pushHandle = (handle: Promise<JSHandle>): number => {
handles.push(handle);
return handles.length - 1;
};
args = args.map(arg => serializeAsCallArgument(arg, handle => {
if (handle instanceof JSHandle) {
if (!handle._objectId)
return { fallThrough: handle._value };
if (handle._disposed)
throw new JavaScriptErrorInEvaluate('JSHandle is disposed!');
const adopted = context.adoptIfNeeded(handle);
if (adopted === null)
return { h: pushHandle(Promise.resolve(handle)) };
toDispose.push(adopted);
return { h: pushHandle(adopted) };
}
return { fallThrough: handle };
}));
const utilityScriptObjects: JSHandle[] = [];
for (const handle of await Promise.all(handles)) {
if (handle._context !== context)
throw new JavaScriptErrorInEvaluate('JSHandles can be evaluated only in the context they were created!');
utilityScriptObjects.push(handle);
}
// See UtilityScript for arguments.
const utilityScriptValues = [options.isFunction, options.returnByValue, expression, args.length, ...args];View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Do not pass disposed handles to evaluate. Re-query the element or re-create the handle before use.
- Avoid storing JSHandles across navigation boundaries; re-query after page.goto() or click-induced navigation.
- Remove explicit dispose() calls if the handle is still needed downstream — Playwright GCs handles automatically.
- Use page.evaluate() with serializable arguments instead of JSHandles when possible.
Example fix
// before
const handle = await page.evaluateHandle('document');
await handle.dispose();
await page.evaluate(h => h.title, handle); // throws [334]
// after
const handle = await page.evaluateHandle('document');
await page.evaluate(h => h.title, handle);
await handle.dispose(); // dispose after last use Defensive patterns
Strategy: validation
Validate before calling
// Check handle before use
if (handle._disposed) // or track disposal manually
throw new Error('Handle already disposed');
// Better: re-query the handle
const freshHandle = await page.evaluateHandle('document'); Type guard
function isHandleAlive(handle: import('@playwright/test').JSHandle): boolean {
// There is no public isDisposed; track disposal via try/catch on jsonValue()
return true; // best practice: re-query instead of checking
} Try / catch
try {
await page.evaluate(fn, handle);
} catch (e) {
if (e.message === 'JSHandle is disposed!') {
// Re-query the element/handle and retry
const fresh = await page.locator(selector).elementHandle();
return page.evaluate(fn, fresh);
}
throw e;
} Prevention
- Never pass disposed handles to evaluate.
- Re-query elements after navigation instead of reusing old handles.
- Avoid premature dispose() calls; let Playwright manage handle lifecycle.
When it happens
Trigger: Passing a JSHandle to page.evaluate(), element.evaluate(), or similar after calling handle.dispose() on it. Also occurs when the handle's execution context was destroyed by a navigation and the handle was implicitly disposed. The serialization callback checks _disposed before attempting to use the handle.
Common situations: Storing a JSHandle in a variable, disposing it, then later passing it to another evaluate call. Navigation invalidates handles created before the navigation. Using a handle from a previous test step after the page navigated. Explicitly calling dispose() too early in a test helper.
Related errors
- JSHandles can be evaluated only in the context they were cre
- Passed function is not well-serializable!
- The object has been collected to prevent unbounded heap grow
- Cannot find object to "${method}": ${guid}
- Unknown new child: ${params.guid}
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/421d1c975659cb3a.
Report an issue: GitHub.