dom-testing-library: infinite loop when DOM mutation happens in waitFor callback
Jest hangs on the following sample test:
const { waitFor } = require("@testing-library/dom");
describe("test", () => {
it("test", async () => {
let value = 1;
setTimeout(() => {
value = 2;
});
await waitFor(() => {
// both these lines are important: it's necessary to do a mutation and to fail in the first iteration
// It works normally, if we comment one of these lines
document.body.setAttribute("data-something", 'whatever');
expect(value).toEqual(2);
});
console.log("execution never comes here");
});
});
Two conditions have to be met:
- there should be a DOM mutation in waitFor callback
- first execution of waitFor callback should fail
Expected result: waitFor should resolve the promise after a successful iteration regardless whether there were DOM mutations or not
Current result: waitFor calls the callback infinitely even if subsequent iterations don’t throw any exception.
Environment: @testing-library/dom@7.26.4 jest@26.6.2
About this issue
- Original URL
- State: open
- Created 4 years ago
- Reactions: 1
- Comments: 18 (14 by maintainers)
Because ATL invokes an Angular detection cycle within the
waitForcallback, we might end up in an infinite loop because this leads to a mutation of the DOM in some cases (https://github.com/testing-library/angular-testing-library/issues/230).Would it be an option to keep track of the execution time inside
waitForourselves (when fake timers aren’t used) in this case. If the timeout limit is reached, we could then throw an error. This won’t be as precise as the currentsetTimeout, but it will provide a fallback to prevent infinite loops in cases when DOM mutations are happening inside thewaitForcallback.If we want to add this to DTL, I can create a PR for this and we can take it from there. I tried this implementation before creating this comment, and it doesn’t affect the current tests.
The very first step of waitFor with or without fake timers is to check the callback. (https://github.com/testing-library/dom-testing-library/blob/master/src/wait-for.js#L56 or https://github.com/testing-library/dom-testing-library/blob/master/src/wait-for.js#L96) waitFor doesn’t consider booleans, its either callback throws (try again) or doesn’t throw (success). (https://github.com/testing-library/dom-testing-library/blob/master/src/wait-for.js#L146) If you need to verify against boolean then your callback should use
toBeTruthy()eg.I do not really understand how
await waitFor(() => false); console.log('Foo')work. In my case waitFor waits for nothing. No matter if the result of the callback is true or false, the promise (waitFor) will always be resolved andFoois logged. Have I understood something wrong here?some env infos:
I agree that it’s better not to put interactions and we should mention it in the docs. But IMO
waitForneeds to ensure that it will stop checking results after the provided timeout. In this case the timeout is being ignored for the moment. Moreover it blocks the whole Jest to finish and it’s hard to figure out where the problem is.Maybe I will try to go deeper and prepare some solution for the problem later.
Maybe I should provide some details on how we came to this problem. There was the following code in our tests:
At the first glance it doesn’t do any DOM mutations. But there was a listener for click events that cause changes in the DOM. Since we were using Jest 24 with MutationObserver shim, the problem occurred only sometimes and led to failures in
waits of other tests in the module. We were trying to catch it for few months because we were not able to stably reproduce it and most of the time it was failing in CI. After switching to Jest 26 with the latest JSDOM which supports MutationObserver it always fails in that block, so we were able to identity the problem and fix it with:However, it’s strange that such innocent code (although I agree it’s not great that it produces DOM mutations as a side effect) hangs the whole Jest.
Hi guys. I have tested before the release and it doesn’t work too. I wonder if waitFor is made for making mutation. Or just assert things. Because making the mutation outside of the waitFor works well. The following code works:
(The waitFor fails the first time and pass the second one) In my projects, I just used waitFor for waitings things to appeared (just expect), that I can’t do with find* queries. What do you think @kentcdodds ?