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, prompting an investigation into the root cause. This performance discrepancy, likely stemming from Playwright’s use of querySelectorAll('*') and matching elements by the accessible name, 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:
await page.getByRole('button', { name: 'Enter address manually' }).click();
This command sets off a cascade of actions within Playwright:
- 
Page#getByRole creates a Locator
- 
Locator#click delegates call to Frame#clickpassing theLocator#_selector
- 
Frame#click delegates to Channel#click.Frameinherits_channelfromChannelOwner.ChannelOwner#_channelis a JS Proxy object based on theEventEmitter
- 
Client Framedispatches an event to the serverFrame#click
- 
FrameSelector#resolveInjectedForSelector injects the FrameExecutionContext#injectedScriptscript to the page, controlled by Playwright. TheInjectedScript#constructoradds the engine forgetByRolelocator.
- 
createRoleEngine calls parseAttributeSelectorandqueryRole
- 
queryRole calls querySelectorAll('*')and matches the element
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.
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.
