Wait for a Loading Spinner to Disappear
Wait for an overlay or spinner to be gone before interacting, including the case where it never appeared.
Problem
After clicking, the app shows a full-screen spinner. Clicking the next button while it is up produces ElementClickInterceptedException. Sometimes the spinner is so fast it is gone before your wait starts, and a “wait for visible, then wait for invisible” sequence times out.
Solution
Wait only for invisibility. The condition is satisfied both when the spinner is hidden and when it does not exist, so it handles the fast-spinner case.
By spinner = By.cssSelector(".loading-overlay, .spinner");WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
driver.findElement(By.id("apply-filters")).click();wait.until(ExpectedConditions.invisibilityOfElementLocated(spinner)); // true if hidden OR absentdriver.findElement(By.id("export")).click();
// Reusable helper on a base pageprotected void waitForLoading() { wait.until(ExpectedConditions.invisibilityOfElementLocated(spinner));}spinner = (By.CSS_SELECTOR, ".loading-overlay, .spinner")wait = WebDriverWait(driver, 15)
driver.find_element(By.ID, "apply-filters").click()wait.until(EC.invisibility_of_element_located(spinner)) # true if hidden OR absentdriver.find_element(By.ID, "export").click()const spinner = By.css('.loading-overlay, .spinner');
await driver.findElement(By.id('apply-filters')).click();await driver.wait(async () => {const els = await driver.findElements(spinner);for (const el of els) if (await el.isDisplayed()) return false;return true;}, 15000, 'spinner still visible');await driver.findElement(By.id('export')).click();var spinner = By.CssSelector(".loading-overlay, .spinner");var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15));
driver.FindElement(By.Id("apply-filters")).Click();wait.Until(d => d.FindElements(spinner).All(e => !e.Displayed)); // true if hidden OR absentdriver.FindElement(By.Id("export")).Click();Why It Works
invisibilityOfElementLocated returns true when the element is not displayed, throws-and-ignores NoSuchElement (treated as invisible), and handles stale references the same way. It never requires the spinner to have been seen.
Gotchas
- Match all spinner variants in one selector; apps often have several.
- Some overlays fade out with CSS transitions and remain “displayed” at 1% opacity. If clicks still get intercepted after the wait, wait for the element to be removed (
findElementsreturns empty) or foropacityto be 0 viagetCssValue. - Put the wait inside page object methods that trigger loading, so tests never have to remember it.