dom-testing-library: getByRole("progressbar") is pretty slow
React Testing Libraryversion: 10.0.4 (Iām using/reactnot/dombut Iām posting here asgetByRoleis implemented here)nodeversion: 𤷠(codesandbox)npm(oryarn) version: 𤷠(codesandbox)
Iāve noticed that some of my tests were pretty slow to run (more than 2 seconds). After investigating, Iāve noticed that the following pattern was to source of the slowness:
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'))
If I add a data-testid to my loader and switch to:
await waitForElementToBeRemoved(() => screen.getByTestId('loader'))
itās much faster.
Relevant code or config:
Iām sometimes displayed a CircularProgress from material-ui while fetching some data and I use this pattern to wait for it to be removed.
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'))
example of implementation
if(isLoading) {
return <CircularProgress className={classes.loader} data-testid="loader" />
}
What happened:
Here are 2 tests outputs where I only change the pattern mentioned above.
getByRole

getByTestId:

Reproduction:
Iāve create the following Sandbox: https://codesandbox.io/s/musing-gauss-e1ynf?file=/src/Demo.js
The numbers are much smaller but when running the tests several times we can see that the getByRole is slower when used on the CircularProgress.

Problem description:
I think that
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'))
is the best pattern to test that a loader is displayed on screen. However, the slowness prevents us from using in all our tests as it quickly gets slow.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 6
- Comments: 58 (20 by maintainers)
Commits related to this issue
- Replace .filter with within().queryAllByRole() The usage of getAllbyRole().filter seemed to have a cumulative timeout effect where the tests would pass when run in isolation with .only, but when run ... — committed to seas-computing/course-planner by rmainwork 3 years ago
- test: optimize test performance React Testing Library can be slow and some tests time out on CI. It's a tradeoff between testing by user interaction/accessibility and speed. This optimizes the perfor... — committed to artsy/force by starsirius 10 months ago
getByRolecan get quite expensive in large trees since it currently checks every element whileByTestIdis a simplequerySelector. It also includes a lot more a11y related checks. For example it wonāt return inaccessible elements.Since you actually remove the element from the DOM you can ignore the additional a11y tree checks and go with
getByRole('progressbar', { hidden: false }).On slow machines these additional checks can starve your CPU and you should increase the polling interval with
waitForElementToBeRemoved(() => {}, { interval: 200 })(try out which interval works best).And last but not least: What version of
jestor, more specifically,jsdomare you using? In laterjsdomversions (or actual browsers)getByRoleshould be a lot faster.For us: I think we should make the perf impact of
hiddeninByRoleclearer in the docs. Also mention too low polling rate on slow machines.I came here after realizing thereās a huge performance difference between getByRole and other query methods. Using getByText immediately makes my tests run 8x faster. I understand the reasons for recommending getByRole but that much additional time spent waiting for tests adds up fast for a large project. I think the docs should be up front about this so people can make their informed decisions about what method to use.
I personally think a PR to the docs is warranted. Something like: āNOTE: some have experienced poor performance with the role query, learn more: linkā
For those who use
styled-componentsa lot, such wrapper could improvegetByRoleperformance by leaving only a few css rules used by@testing-libraryand thus significantly reducing<style>sizehttps://github.com/eps1lon/dom-accessibility-api/pull/323 This PR improves
getByRoleperformance by up to 20% by removing one extragetComputedStylecallHey guys I donāt know if anybody else opened a new issue but today I had this exact issue, one of my test suite started to have systematic timeouts after updating to new version of testing library and updating my queries to getByRole I run a bit of profiling and those are my findings
Letās say I have an Input component similar to this
In my tests Iām running this:
Those are my results before without
configure({ defaultHidden: true })Those are the results after
configure({ defaultHidden: true })In general for me getByRole is slower itās 1 / 2 sec vs 5ms with the other queries
@arahansen I didnāt take the task at the end, because Iām not sure about how useful is going to be. Even if benchmarks here have some point, after a few more talks about it, turns out that small sized teams or alone individuals are probably going to assume that the doc is talking about a huge performance impact (even if you used the right wording there, or even if you provide the link), and theyāre going to do it maybe without any real performance assessment on the test stages. That means, final result could be people opting-out of getByRole (and all that implies) for not a really outstanding practical reason.
Please open a new issue (since this issue was concerned about ByRole in waitFor) and fill out the template. Having a repro like the one @tommarien provided helps a lot.
Iāve updated the demo with a more complex form and hereās the result:
Sandbox: https://codesandbox.io/s/musing-gauss-e1ynf?file=/src/DemoForm.js
Iāve removed the
CircularProgressso that we donāt focus on it.Iāve just update testing-library/dom in the reproduction repo and see times go from 280ms on average to 38ms š, so thanks a lot š . If next release of testing-libs dom dependency to > 7.6.0 than everyone can benefit from this š. @eps1lon Thanks and iāll remove the repro repo today
š This issue has been resolved in version 7.6.0 š
The release is available on:
npm package (@latest dist-tag)Your semantic-release bot š¦š
@eps1lon I think the demo I shared above is already showing a big difference on a quite complex form: https://codesandbox.io/s/musing-gauss-e1ynf?file=/src/DemoForm.js
Iāve added some content around the form to make the tests even slower though.
This is my real use case except I have a bunch of providers on top.
Ok, so I think the simplest solution is to default
hiddentotrueand make the default configurable. I know the arguments against this that @eps1lon will have (weāve been through this before) but Iām convinced that opting into this kind of a performance drain in exchange for the confidence is better than having to opt out of it. I worry that people will switch from ByRole to a suboptimal query due to performance issues and I donāt think thatās wise.Because this is a breaking change, we should do this in two phases.
Anyone want to work on phase 1?
I think we really need to be careful here. I want to give it a little bit of extra thoughtā¦
If I understand correctly were determining the accessibility of everything before matching. Is that right? If so⦠What if we find the match first and then determine if itās accessible?
I ran into this today while trying to debug why a mocked network request was so slow. Iāll try to put together a minimal reproduction but the Cliffs Notes is:
My app code was making a network request and on success would navigate to another route. I was waiting for the new route to load with
screen.findByRole('heading', {name: 'xxx'}). Usingconsole.timearound thefetchcall was showing ~1900ms elapsed. A similar request that would 400 was taking around ~22ms to display the error message. Swapping toscreen.findByText('xxx')brought the elapsed time for the former down to ~25ms. I took a look at the profiler and it seems that*ByRoleis starving my CPU. I tried both{hidden: true}and{interval: 500}with no improvement. This is in a toy app with a pretty tiny DOM.The only real difference between the tests seems to be the route change. Iāll try to put together a minimal reproduction this weekend to narrow it down.
Funnily, if I do
getByRole('progressbar', { hidden: true })it solves my issue as it does not callisInaccessible. š