Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsA hard wait, such as Thread.sleep(5000) or cy.wait(5000), pauses the test for a fixed time without checking whether the application is ready. That is why it is unreliable: if the app needs longer than the pause, the next step runs too early and fails; if it needs less, the test still sits out the whole delay. The fix is to wait for the specific state the next step needs, and to use timeouts only as upper limits.
What a hard wait actually does
A fixed sleep encodes a guess about duration, not a condition about the application. Selenium’s documentation describes the underlying problem as a race: the automation command can execute before a dynamic page has reached the state the test needs. A page’s readyState does not necessarily mean that JavaScript-driven changes have finished. Selenium calls this one of the primary causes of flaky tests
(Selenium Project, Waiting Strategies, page last modified September 3, 2024). A sleep tries to win that race by guessing, and a guess fails in both directions:
As an Amazon Associate I earn from qualifying purchases.
- Too short: the race remains. The test passes on a fast machine and fails on a loaded CI runner.
- Too long: you pay the full delay every time that line runs, even when the app was ready in 200 ms. Across a suite, these fixed costs add up.
Cypress’s performance guidance is blunt about the remedy: if you find yourself reaching for cy.wait(number), the right fix is almost always to add an explicit assertion that Cypress can retry (Cypress, Optimizing test performance). Both statements are project documentation, not individual opinions.
Why the failures look random
A hard wait makes the outcome depend on timing you do not control: server load, cold caches, CPU contention on shared CI, network latency, and test order. The test is correct on one run and wrong on the next without any code change, which is the definition of a flaky test. Raising the sleep value usually just moves the failure rate lower while making every run slower. No public statistic quantifying the effect of hard waits was found in the official documentation, so treat any specific percentage you see quoted elsewhere with caution unless it cites a primary study.
#1 Best Overall
What to wait for instead
Ask what the next step really requires, then wait for exactly that. Typical states are an element appearing, becoming visible, becoming actionable, or showing expected text.
Selenium: explicit waits
Selenium supports implicit and explicit waits. An explicit wait polls for a condition and returns as soon as it is true, up to a timeout.
// Java
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement toast = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("saved-toast")));
assertEquals("Saved", toast.getText());
# Python
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
toast = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "saved-toast")))
assert toast.text == "Saved"
Here 10 seconds is a ceiling. If the toast shows after 300 ms, the test continues after about 300 ms.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #2
Do not mix implicit and explicit waits. Selenium warns that combining them can cause unpredictable wait times, and its documentation gives an example where the combined settings can exceed the apparent explicit timeout. Pick explicit waits and leave the implicit wait at zero. This warning is specific to Selenium/WebDriver.
Cypress: retryable queries and assertions
Cypress retries queries and assertions automatically, so the assertion itself is the wait:
// Instead of: cy.wait(3000)
cy.get('[data-test=results] li').should('have.length.greaterThan', 0)
cy.contains('[data-test=status]', 'Saved').should('be.visible')
Cypress actions also wait for elements to be actionable, as described in its core concepts. For a known slow operation, adjust the relevant timeout rather than adding a delay:
Rank #3
cy.get('[data-test=report]', { timeout: 30000 }).should('be.visible')
Cypress’s performance page gives a default command timeout of four seconds; that figure is framework-specific and can change between versions, so check your own version.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePlaywright: auto-waiting and web-first assertions
Playwright checks actionability conditions before performing actions such as clicks (see Auto-waiting), and its web-first assertions retry until the expected state is reached (see Writing tests).
await page.getByRole('button', { name: 'Save' }).click(); // waits until actionable
await expect(page.getByText('Saved')).toBeVisible(); // retries until true or timeout
Avoid replacing these with page.waitForTimeout(), which reintroduces the fixed pause.
Rank #4
Waiting on a network request
Sometimes the real synchronization point is a specific request. Cypress documents aliasing a route and waiting for that alias (see Migrate from Selenium to Cypress):
cy.intercept('GET', '/api/orders*').as('getOrders')
cy.visit('/orders')
cy.wait('@getOrders')
cy.get('[data-test=order-row]').should('have.length', 10)
Note the last line. A completed request is not proof that the page rendered the right content, so follow the wait with an assertion on what the user sees. That pairing is our reading of how Cypress’s own examples are structured.
Framework comparison
| Question | Selenium | Cypress | Playwright |
|---|---|---|---|
| Condition-based wait | Explicit waits (WebDriverWait) |
Queries and assertions retry | Web-first assertions retry |
| Readiness before actions | You must wait explicitly | Actions wait for actionability | Actions check actionability |
| Waiting on requests | Not built into the wait API; wait on the resulting UI | Alias a route, then cy.wait('@alias') |
Possible, but pair with a UI assertion (not covered by the sources used here) |
| Global setting risk | Implicit wait; do not mix with explicit | Timeouts are upper bounds | Timeouts are upper bounds |
Their semantics are not interchangeable, so learn how your tool defines “ready” before porting a wait pattern.
Best Value
Are all fixed delays wrong?
The official sources strongly discourage arbitrary waits as a way to establish UI readiness. They do not publish a universal list of exceptions. If a test truly must model elapsed time and the app offers no observable signal, keep that delay narrow, named, and commented, and never use it as general synchronization. In most cases a signal exists: a spinner disappearing, a row count, a status text, or a response.
Migrating a suite away from sleeps
- Search the codebase for
sleep,Thread.sleep,cy.wait(with a number, andwaitForTimeout. - For each, write down what the next line needs: visible element, enabled button, text, row count, or a finished request.
- Replace the delay with that condition, as an assertion or explicit wait.
- If the operation is legitimately slow, raise the timeout for that one condition, not globally.
- Run the test repeatedly, ideally on a throttled CI-like runner, to confirm it no longer depends on timing.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Explicit wait takes far longer than its timeout in Selenium | Implicit wait also set | Set implicit wait to 0 and use only explicit waits |
| Test passes after the request finishes but asserts wrong data | Request completion treated as rendered result | Assert on the visible outcome after the wait |
| Condition never becomes true | Wrong locator or state (visible vs present) | Check the exact condition; inspect the DOM at failure |
| Suite slow despite no obvious sleeps | Timeouts set very high globally, or retries on failing conditions | Use per-operation timeouts; fix the cause of failure |
| Wait removed but test still flaky | Asserting a state that precedes the real one (e.g. element exists but is still re-rendering) | Wait for a later, user-visible signal |
Visual tests and screenshots: the same trap
Screenshot checks are a common place for hidden sleeps (“wait 3 seconds, then capture”). The cure is the same: wait for a selector, network idle, or a specific state, then capture. A screenshot taken during a cookie banner or half-loaded page is just another flaky result.
Or skip the browser setup
If you only need a clean capture of a page, ScreenshotNeo is a screenshot API with options to wait for a selector, a delay or network idle, so you don’t need your own browser and sleeps. Cookie and consent banners, newsletter popups and chat widgets are removed before the shot, each step can be turned off. See the docs for all options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Bot checks, blank pages, timeouts, failed loads and cache hits are never billed; the X-Page-Verdict and X-Billed response headers tell you which it was.
- An MCP server (tools take_screenshot, get_page_info, capture_pdf) lets AI agents such as Claude or Cursor take screenshots.
- 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.
Start free with 1,000 screenshots a month, no card required.
Frequently Asked Questions
What should I use instead of Thread.sleep in Selenium?
Use WebDriverWait with an ExpectedCondition that matches what the next step needs, such as visibility or clickability of a specific element.
Is a long timeout the same as a hard wait?
No. A timeout is an upper limit on observing a condition and the test continues as soon as the condition is met. A hard wait always consumes its full duration.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




