Skip to main content

Handles

Introduction

Playwright can create handles to the page DOM elements or any other objects inside the page. These handles live in the Playwright process, whereas the actual objects live in the browser. There are two types of handles:

  • JSHandle to reference any JavaScript objects in the page
  • ElementHandle to reference DOM elements in the page, it has extra methods that allow performing actions on the elements and asserting their properties.

Since any DOM element in the page is also a JavaScript object, any ElementHandle is a JSHandle as well.

Handles are used to perform operations on those actual objects in the page. You can evaluate on a handle, get handle properties, pass handle as an evaluation parameter, serialize page object into JSON etc. See the JSHandle class API for these and methods.

API reference

Here is the easiest way to obtain a JSHandle.

var jsHandle = await page.EvaluateHandleAsync("window");
// Use jsHandle for evaluations.

Element Handles

Discouraged

The use of ElementHandle is discouraged, use Locator objects and web-first assertions instead.

When ElementHandle is required, it is recommended to fetch it with the Page.WaitForSelectorAsync() or Frame.WaitForSelectorAsync() methods. These APIs wait for the element to be attached and visible.

// Get the element handle
var jsHandle = await page.WaitForSelectorAsync("#box");
var elementHandle = jsHandle as ElementHandle;

// Assert bounding box for the element
var boundingBox = await elementHandle.BoundingBoxAsync();
Assert.AreEqual(100, boundingBox.Width);

// Assert attribute for the element
var classNames = await elementHandle.GetAttributeAsync("class");
Assert.True(classNames.Contains("highlighted"));

Handles as parameters

Handles can be passed into the Page.EvaluateAsync() and similar methods. The following snippet creates a new array in the page, initializes it with data and returns a handle to this array into Playwright. It then uses the handle in subsequent evaluations:

// Create new array in page.
var myArrayHandle = await page.EvaluateHandleAsync(@"() => {
window.myArray = [1];
return myArray;
}");

// Get the length of the array.
var length = await page.EvaluateAsync<int>("a => a.length", myArrayHandle);

// Add one more element to the array using the handle
await page.EvaluateAsync("arg => arg.myArray.add(arg.newElement)",
new { myArray = myArrayHandle, newElement = 2 });

// Release the object when it is no longer needed.
await myArrayHandle.DisposeAsync();

Handle Lifecycle

Handles can be acquired using the page methods such as Page.EvaluateHandleAsync(), Page.QuerySelectorAsync() or Page.QuerySelectorAllAsync() or their frame counterparts Frame.EvaluateHandleAsync(), Frame.QuerySelectorAsync() or Frame.QuerySelectorAllAsync(). Once created, handles will retain object from garbage collection unless page navigates or the handle is manually disposed via the JsHandle.DisposeAsync() method.

API reference

Locator vs ElementHandle

caution

We only recommend using ElementHandle in the rare cases when you need to perform extensive DOM traversal on a static page. For all user actions and assertions use locator instead.

The difference between the Locator and ElementHandle is that the latter points to a particular element, while Locator captures the logic of how to retrieve that element.

In the example below, handle points to a particular DOM element on page. If that element changes text or is used by React to render an entirely different component, handle is still pointing to that very stale DOM element. This can lead to unexpected behaviors.

var handle = await page.QuerySelectorAsync("text=Submit");
await handle.HoverAsync();
await handle.ClickAsync();

With the locator, every time the locator is used, up-to-date DOM element is located in the page using the selector. So in the snippet below, underlying DOM element is going to be located twice.

var locator = page.GetByText("Submit");
await locator.HoverAsync();
await locator.ClickAsync();