microsoft/playwright · error
Only element handles can be evaluated in the
Error message
Only element handles can be evaluated in the "${params.world}" world What it means
JSHandleDispatcher.evaluateExpression throws when a world ('main' or 'utility') is requested for an expression evaluated on a plain JSHandle. Execution worlds only make sense for ElementHandle, whose element can be moved into a specific world; the base JSHandle dispatcher unconditionally rejects params.world.
Solutions
- Omit the world option when evaluating on a plain JSHandle.
- Use locator.evaluate or elementHandle.evaluateExpression with the world option instead — worlds are only supported for element handles.
- If you need the value in a specific world, evaluate on the page/frame and pass the handle as an argument.
Example fix
// before
const handle = await page.evaluateHandle('window.foo');
await handle.evaluateExpression('el => el', { world: 'utility' });
// after
await page.locator('#target').evaluate('el => el', undefined, { world: 'utility' }); Defensive patterns
Strategy: type-guard
Type guard
function canUseWorld(handle: unknown): handle is import('playwright-core').ElementHandle {
return !!handle && typeof (handle as any).evaluateExpression === 'function' && (handle as any)._element !== undefined;
}
// simpler: only pass world when the subject is a Locator / ElementHandle Try / catch
try {
return await handle.evaluateExpression(expr, { world: 'utility' });
} catch (e) {
if (String(e.message).includes('Only element handles')) return await handle.evaluateExpression(expr);
throw e;
} Prevention
- Only attach world options to locator/elementHandle evaluations, never to JSHandle evaluations.
- Keep a single evaluate helper that conditionally includes world based on subject type.
When it happens
Trigger: Calling jsHandle.evaluateExpression(expression, { world: 'utility' }) or passing a world option through an evaluate call routed at a non-element handle (e.g. a page.evaluateHandle result).
Common situations: Reusing an evaluate helper written for locators/element handles and passing a plain JSHandle; adding world options to generic handle evaluation for isolation reasons.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- JSHandle is disposed!
- JSHandles can be evaluated only in the context they were…
- Cannot serialize result: object reference chain is too long.
- Cannot serialize result: object reference chain is too long.
- Cannot serialize result: object reference chain is too long.
AI-assisted analysis of microsoft/playwright@f1d33b5029 (2026-09-21).
Data as JSON: /api/errors/7ed38c20b2665a17.
Report an issue: GitHub.
Appendix: source
Thrown at packages/playwright-core/src/server/dispatchers/jsHandleDispatcher.ts:47
export class JSHandleDispatcher<ParentScope extends JSHandleDispatcherParentScope = JSHandleDispatcherParentScope> extends Dispatcher<js.JSHandle, channels.JSHandleChannel, ParentScope> implements channels.JSHandleChannel {
_type_JSHandle = true;
static fromJSHandle(scope: JSHandleDispatcherParentScope, handle: js.JSHandle): JSHandleDispatcher {
return scope.connection.existingDispatcher<JSHandleDispatcher>(handle) || new JSHandleDispatcher(scope, handle);
}
protected constructor(scope: ParentScope, jsHandle: js.JSHandle) {
// Do not call this directly, use createHandle() instead.
super(scope, jsHandle, jsHandle.asElement() ? 'ElementHandle' : 'JSHandle', {
preview: jsHandle.toString(),
});
jsHandle._setPreviewCallback(preview => this._dispatchEvent('previewUpdated', { preview }));
}
async evaluateExpression(params: channels.JSHandleEvaluateExpressionParams, progress: Progress): Promise<channels.JSHandleEvaluateExpressionResult> {
if (params.world)
throw new Error(`Only element handles can be evaluated in the "${params.world}" world`);
const jsHandle = await this._object.evaluateExpression(progress, params.expression, { isFunction: params.isFunction, serialize: params.serialize }, parseArgument(params.arg));
return { value: serializeResult(jsHandle, params) };
}
async evaluateExpressionHandle(params: channels.JSHandleEvaluateExpressionHandleParams, progress: Progress): Promise<channels.JSHandleEvaluateExpressionHandleResult> {
const jsHandle = await this._object.evaluateExpressionHandle(progress, params.expression, { isFunction: params.isFunction, serialize: params.serialize }, parseArgument(params.arg));
// If "jsHandle" is an ElementHandle, it belongs to the same frame as "this".
return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this.parentScope() as FrameDispatcher, jsHandle) };
}
async getProperty(params: channels.JSHandleGetPropertyParams, progress: Progress): Promise<channels.JSHandleGetPropertyResult> {
const jsHandle = await this._object.getProperty(progress, params.name);
// If "jsHandle" is an ElementHandle, it belongs to the same frame as "this".
return { handle: ElementHandleDispatcher.fromJSOrElementHandle(this.parentScope() as FrameDispatcher, jsHandle) };
}
async getPropertyList(params: channels.JSHandleGetPropertyListParams, progress: Progress): Promise<channels.JSHandleGetPropertyListResult> {
const map = await this._object.getProperties(progress);View on GitHub (pinned to f1d33b5029)