> ## Content Index
> Fetch the complete content index at: https://serpapi.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Playwright’s getByRole is 1.5x slower than CSS selectors
- URL: https://serpapi.com/blog/css-selectors-faster-than-getbyrole-playwright/
- Published: 2023-11-14T19:00:15.000Z
- Updated: 2023-11-16T15:33:19.000Z
- Description: Playwright’s getByRole uses of querySelectorAll('*') and matches elements by the accessible name.
- Author: Illia Zub
- Tags: Tips&Tricks

## 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 our codebase. 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%5Ftypes), 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 — `getByRole` and CSS selectors — is key. `getByRole` excels in clarity, while CSS selectors win in speed. Choose wisely: `getByRole` for testing accessibility and/or user-facing attributes of elements, CSS selectors for efficiency.