-
Notifications
You must be signed in to change notification settings - Fork 467
waitForElementToBeRemoved is not compatible with elements queried by findBy* #876
Description
Elements returned from asynchronous findBy queries are not 100% compatible with waitForElementToBeRemoved.
@testing-library/domversion:7.29.2- Testing Framework and version:
- fresh project generated with CRA 4.0.1
jest@26.6.0
- DOM Environment:
jest-environment-jsdom@26.6.2jsdom@16.4.0
Relevant code or config:
Also available in codesandbox below.
import React, { useLayoutEffect, useState } from "react";
import { render, waitForElementToBeRemoved, screen } from "@testing-library/react";
function Component() {
const [visible, setVisible] = useState(false);
useLayoutEffect(() => {
const timer1 = setTimeout(() => setVisible(true), 10);
const timer2 = setTimeout(() => setVisible(false), 15);
return () => {
clearTimeout(timer1);
clearTimeout(timer2);
};
}, []);
return (
<div>
{visible && <span id="test-id">Content</span>}
</div>
);
}
for(const i of Array(200).fill(null).map((_, i) => i)) {
test(`unstable test ${i}`, async () => {
render(<Component />);
const content = await screen.findByText("Content");
await waitForElementToBeRemoved(content);
});
}Above is only a minimal repro. My real use case includes querying "Loading" text which is visible while MSW is handling the request. It does not use setTimeout at all. Something like:
// Click submit button
userEvent.click(screen.getByRole("button", { name: "Submit" }));
// Loader should appear
const loader = await screen.findByText("Loading");
// Loader should disappear once request resolves
await waitForElementToBeRemoved(loader);What you did:
Queried element with asynchronous query findBy*. I would expect element returned to be present in the DOM. If it wasn't the query should throw error.
Passed the queried element to waitForElementToBeRemoved.
What happened:
Element returned from findByText was not in DOM when waitForElementToBeRemoved started handling it.
FAIL src/App.test.js
× unstable test (66 ms)
● unstable test
The element(s) given to waitForElementToBeRemoved are already removed. waitForElementToBeRemoved requires that the element(s) exist(s) before waiting for removal.
33 | render(<Component />);
34 | const content = await screen.findByText("Content");
> 35 | await waitForElementToBeRemoved(content);
| ^
36 | });
37 |
38 |
at initialCheck (node_modules/@testing-library/dom/dist/wait-for-element-to-be-removed.js:16:11)
at waitForElementToBeRemoved (node_modules/@testing-library/dom/dist/wait-for-element-to-be-removed.js:39:3)
at Object.<anonymous> (src/App.test.js:35:11)
Most of the time these issues come up really randomly. We might have successful builds for months on CI and then this error occurs. Triggering new build usually fixes this. 😄
Reproduction:
The codesandbox is still showing incorrect error message Cannot read property 'parentElement' of null but this was fixed in #871.
https://codesandbox.io/s/dom-testing-libraryissues876-2-fe3fi?file=/src/React.test.js
I'm unable to reproduce this with DTL only though: https://codesandbox.io/s/dom-testing-libraryissues876-2-fe3fi?file=/src/Dom.test.js
Problem description:
I was hoping the initialCheck done by waitForElementToBeRemoved would have been the cause but it looks like the parentElement is not even present before element is given to it.
const content = await screen.findByText("Content");
console.log(content.outerHTML); // <span id="test-id">Content</span>
console.log(content.parentElement); // null
await waitForElementToBeRemoved(content); // The element(s) given to waitForElementToBeRemoved are already removed. waitForElementToBeRemoveds that the element(s) exist(s) before waiting for removal.Suggested solution:
- At minimum this should be documented at https://testing-library.com/docs/dom-testing-library/api-async/#waitforelementtoberemoved.
- I'm not sure whether
waitForElementToBeRemovedshould care if element is present in the DOM initially. - Mark elements returned from async queries and warn if passed to
waitForElementToBeRemoved?element.RTL_TYPE = Symbol.for("FINDBY")etc.
There is a workaround for this: Don't use waitForElementToBeRemoved.
const content = await screen.findByText("Content");
await waitFor(() => expect(content).not.toBeInTheDocument());