Playwright’s getByRole is 1.5x slower than CSS selectors
Tags: performance, serpapi, webscraping
Published: 2023-11-14 19:05:15

---
description: Playwright’s getByRole uses of querySelectorAll('*') and matches elements by the accessible name.
canonical_url: https://serpapi.com/blog/css-selectors-faster-than-getbyrole-playwright/
---

_Playwright’s getByRole uses of querySelectorAll('*') and matches elements by the accessible name._

## Introduction
In the realm of web automation and testing with Playwright, understanding the performance of various locator strategies is key. This article delves into the `getByRole` locator's efficiency compared to CSS selectors, offering insights into the technical workings and practical implications of these choices.

I wanted to use `Page#getByRole`'s underlying CSS selectors in [SerpApi](https://serpapi.com) code base. But the `getByRole` locator was 1.5 times slower compared to the [standard CSS selectors](https://serpapi.com/blog/web-scraping-with-css-selectors-using-python/#selectors_types), prompting an investigation into the root cause. This performance discrepancy, likely stemming from Playwright’s use of [`querySelectorAll('*')`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/injected/roleSelectorEngine.ts#L163-L173) and [matching elements by the accessible name](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/injected/roleUtils.ts#L402-L711), raises essential considerations for developers prioritizing speed in their automation scripts.

## Deep Dive: How `getByRole` Works

The `getByRole` function in Playwright is more than just a method to locate web elements; it's a complex mechanism with multiple layers of interaction within the Playwright architecture. Let's demystify this process with an example. Consider this code:

```js
await page.getByRole('button', { name: 'Enter address manually' }).click();
```

This command sets off a cascade of actions within Playwright:

* Page#getByRole [creates a `Locator`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/locator.ts#L175-L177)

* Locator#click delegates call to [`Frame#click` passing the `Locator#_selector`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/locator.ts#L95-L97)

* Frame#click delegates to [`Channel#click`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/frame.ts#L284-L286). `Frame` inherits `_channel` from `ChannelOwner`. [`ChannelOwner#_channel`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/client/channelOwner.ts#L138-L160) is a JS Proxy object based on the `EventEmitter`

* Client `Frame` dispatches an event to the server [`Frame#click`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/frames.ts#L1144-L1149)

* FrameSelector#resolveInjectedForSelector injects the [`FrameExecutionContext#injectedScript`](https://github.com/microsoft/playwright/blob/2afd857642c26980e56f269d05df72d4d69f57e7/packages/playwright-core/src/server/dom.ts#L83-L109) script to the page, controlled by Playwright. The `InjectedScript#constructor` adds the [engine for `getByRole` locator](https://github.com/microsoft/playwright/blob/2afd857642c26980e56f269d05df72d4d69f57e7/packages/playwright-core/src/server/injected/injectedScript.ts#L125).

* createRoleEngine calls [`parseAttributeSelector` and `queryRole`](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/injected/roleSelectorEngine.ts#L179-L195)

* queryRole calls [`querySelectorAll('*')` and matches the element](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/injected/roleSelectorEngine.ts#L129-L161)

Compared to the `getByRole`, the locator with the regular CSS selector just traverses DOM and is 1.5 times faster.

## Performance

Comparative tests reveal that a regular locator using CSS selectors outperforms `getByRole` by 1.5 times. Interestingly, the `$.then` method trailed, being 2x slower in our tests.

```js
console.time("getByRole");
for (let i = 0; i < 100; i++) {
  const textContent = await page.getByRole('button', { name: 'Enter address manually' }).textContent()
}
console.timeEnd("getByRole");

console.time("locator");
for (let i = 0; i < 100; i++) {
  const textContent = await page.locator('.ektjNL').textContent()
}
console.timeEnd("locator");

console.time("$.then");
for (let i = 0; i < 100; i++) {
  const textContent = await page.$('.ektjNL').then(e => e.textContent())
}
console.timeEnd("$.then");

// Output:
// getByRole: 677.5ms
// locator: 497.306ms
// $.then: 1.135s

```

## Conclusion

In web automation, understanding Playwright's locators &mdash; `getByRole` and CSS selectors &mdash; is key. `getByRole` excels in clarity, while CSS selectors win in speed. This matters in large test suites where every second counts. Choose wisely: `getByRole` for readability, CSS selectors for efficiency.