You push a small change. Your Selenium suite passes on your laptop. Then CI/CD runs it and half the tests turn red - no code changed, nothing broke, the app works fine when you check it manually.
Welcome to test flakiness: the single biggest productivity killer for QA teams running Selenium at scale. The good news is that flaky tests almost always trace back to the same handful of causes. Here they are, along with the fixes that actually work.
1. Race Conditions and Timing Issues
The problem: Selenium looks for an element before the page has finished rendering it.
The bad fix: Thread.sleep(2000). It slows your entire suite down and still fails the moment the app is a little slower than usual.
The real fix: Wait for the specific condition you actually care about.
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("modal")));
wait.until(ExpectedConditions.numberOfWindowsToBe(1));
2. Brittle Selectors
The problem: A developer nudges the DOM structure and your tests collapse.
// Breaks the moment ANY parent div changes
By.xpath("//div[@class='container']/div[2]/button")
The fix: Anchor to selectors that are meant to be stable:
data-testid="save-button" — get your frontend team to commit to this convention
aria-label="Save changes" — accessible names double as reliable hooks
Avoid deep, structural DOM paths entirely
3. Environment and Data Drift
The problem: Tests pass locally, fail in CI. Or pass in Chrome, fail in Firefox. Usually it’s shared test data, browser version mismatches, or timezone differences colliding.
The fix:
Generate unique test data per run: test-${Date.now()}@example.com
Clean up whatever data your test creates
Standardize environments with Docker so “works on my machine” stops being a thing
4. Stale Element References
The problem: A React or Vue re-render invalidates the element reference you’re holding.
WebElement button = driver.findElement(By.id("save"));
// Page re-renders...
button.click(); // ERROR: Element is stale!
The fix: Find and act in the same breath — don’t cache references across renders.
driver.findElement(By.id("save")).click(); // ✓ Better
5. Network and API Delays
The problem: Your test clicks “Load more,” the API takes two seconds, and your test checks for the new content before it’s arrived.
The fix: Wait for the actual outcome, not a guessed duration.
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("post-123")));
// Not: wait 2 seconds and hope it's done
6. Conflicting Wait Configurations
The problem: Mixing implicit and explicit waits creates unpredictable delays that are miserable to debug.
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); // ✗ Bad
WebDriverWait wait = new WebDriverWait(driver, 5); // Conflicts!
The fix: Pick one strategy — explicit waits, always.
driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);
wait.until(ExpectedConditions.elementToBeClickable(By.id("save")));
driver.findElement(By.id("save")).click();
7. Browser and WebDriver Version Mismatches
The problem: Chrome auto-updates to v120 overnight, but your ChromeDriver is still pinned to v100.
The fix: Stop managing driver versions by hand.
WebDriverManager.chromedriver().setup();
WebDriver driver = new ChromeDriver();
The Quick Checklist
✅ Explicit waits, never fixed sleeps
✅ Stable selectors (data-testid, aria-label)
✅ Isolated test data per test
✅ Wait for real conditions, not arbitrary timers
✅ One wait strategy, not two fighting each other
✅ Driver version matched to browser version
✅ Isolated environments (Docker/CI grid)
The Bottom Line
Flakiness is not a bug in your tests - it’s a gap between what your test code assumes and what your application actually does. Close that gap by waiting intelligently, anchoring to stable selectors, and isolating your test data, and you’ll eliminate roughly 95% of the flakiness plaguing your suite.
Still Drowning in Maintenance?
If you are spending more hours babysitting flaky tests than writing new ones, it might be time to stop patching the symptoms and fix the underlying problem at the framework level.
That’s exactly what QAlity is built for. Instead of asking your team to manually apply all seven fixes above across every test, every sprint, QAlity handles it automatically:
Auto-healing selectors - when the DOM changes, QAlity adapts instead of breaking your tests
Intelligent waiting - no more manually tuning explicit waits for every new component
Isolated cloud execution - every test run gets a clean, consistent environment, so “works on my machine” stops being a debugging category
Flaky tests are not a fact of life. They are a solvable engineering problem - and QAlity solves it so your team can get back to shipping.
Tired of chasing flaky tests instead of writing new ones? Give QAlity a try and see what a self-healing test suite feels like.
Top comments (0)