---
title: "Sleepy Test"
type: "test-smell"
slug: "sleepy-test"
url: "http://localhost:3000/en/test-smells/sleepy-test.md"
category: "Erratic Smells"
description: "A Sleepy Test pauses execution with a hard-coded delay (`Thread.sleep`, `setTimeout`, `cy.wait(2000)`, `page.waitForTimeout`) to wait for asynchronous work instead of waiting on the actual condition."
---
# Sleepy Test

> A Sleepy Test pauses execution with a hard-coded delay (`Thread.sleep`, `setTimeout`, `cy.wait(2000)`, `page.waitForTimeout`) to wait for asynchronous work instead of waiting on the actual condition.

## Signs and Symptoms

You see a magic-number delay sitting between an action and its assertion, with a comment apologizing for it:

```js
test('order is processed', async () => {
  submitOrder(order);

  // give the worker time to finish
  await new Promise((r) => setTimeout(r, 2000));

  expect(await getOrderStatus(order.id)).toBe('processed');
});

```

Tell-tale signs:

* Fixed sleeps in test bodies or hooks: `Thread.sleep(500)`, `await sleep(1000)`, `time.sleep(2)`, `cy.wait(3000)`, `await page.waitForTimeout(2000)`, `browser.pause(2000)`.
* Comments like `// wait for the animation`, `// let the DB catch up`, `// flaky without this`.
* The sleep duration drifts upward over time (`1000` becomes `2000` becomes `5000`) as people fight intermittent failures by padding the delay.
* The number is the _only_ synchronization — there is no assertion, poll, or event that the wait is actually keyed to.
* A slow suite where the wall-clock runtime is dominated by sleeps rather than real work.

The smell is about _unconditional_ waiting. `cy.wait('@apiAlias')` or `await expect(locator).toBeVisible()` wait on a condition and are fine; `cy.wait(2000)` and `page.waitForTimeout(2000)` wait on the clock and are not.

## Reasons for the Problem

**Why it happens**

* An asynchronous operation (a queue, a timer, an animation, a network call, a background thread) has no obvious hook to wait on, so a delay is the path of least resistance.
* A test was intermittently failing and someone "fixed" it by inserting or lengthening a sleep until it went green on their machine.
* The test interacts with a real external system (clock, scheduler, filesystem, HTTP service) whose completion isn't observable, so timing is guessed.

**Why it hurts**

* **Reliability / false confidence.** A sleep encodes an assumption — "the work finishes within N ms." Processing time varies across machines, CI load, and runs, so the test is non-deterministic: it passes locally and fails on a loaded CI agent, or worse, the sleep is _too short_ and the assertion races the code, passing only because of timing luck. This is one of the most-cited root causes of flaky tests.
* **Slowness.** A fixed sleep always waits the full duration, even when the work finished in 20 ms. Multiplied across a suite, sleeps turn seconds of real work into minutes of idle waiting, which discourages running tests often.
* **Maintainability.** The magic number is fragile: speeding up or slowing down the system under test silently breaks the timing contract, and the only "fix" people reach for is bumping the number — a ratchet that makes the suite slower and _still_ flaky.
* **Readability.** The delay hides _what_ the test is actually waiting for. A reader can't tell whether `sleep(2000)` guards a DB write, a render, or nothing at all, so the test's intent is obscured.

## Treatment

Replace "wait for a fixed time" with "wait for the condition." Identify the observable signal that the async work is done, then block on _that_ with a generous timeout.

1. **Poll for the condition.** Use a polling/retry helper — Awaitility (JVM), `vi.waitFor` / `waitFor` (Vitest, Testing Library), Jest's `waitFor`, or your test runner's auto-retrying assertions — so the test proceeds the instant the condition is true and only fails after a timeout.
2. **Prefer built-in awaited assertions.** Web E2E tools already retry: Playwright's web-first assertions (`await expect(locator).toBeVisible()`) and Cypress's auto-retrying commands remove the need to wait at all.
3. **Wait on events, not the clock.** Await the promise, `await`\-the network response, or use a `CountDownLatch`/callback/`waitFor('@alias')` that the production code actually signals.
4. **Control time instead of spending it.** When the delay is a real timer in the code under test, use fake timers (`jest.useFakeTimers()`, `vi.useFakeTimers()`, `vi.advanceTimersByTimeAsync`) so you advance the clock deterministically rather than sleeping.

Before:

```js
test('order is processed', async () => {
  submitOrder(order);
  await new Promise((r) => setTimeout(r, 2000)); // sleepy
  expect(await getOrderStatus(order.id)).toBe('processed');
});

```

After (poll the condition with a bounded timeout):

```js
import { vi } from 'vitest';

test('order is processed', async () => {
  submitOrder(order);
  await vi.waitFor(
    async () => expect(await getOrderStatus(order.id)).toBe('processed'),
    { timeout: 5000, interval: 50 },
  );
});

```

Playwright/Cypress equivalents:

```js
// Playwright — web-first assertion auto-retries until visible or timeout
await expect(page.getByText('processed')).toBeVisible();

// Cypress — wait on the request, not a number
cy.intercept('POST', '/orders').as('createOrder');
cy.wait('@createOrder');

```

The result waits no longer than necessary, fails fast with a clear timeout message, and no longer depends on the speed of the machine.

## Detected by

- **sonar** `java:S2925` — "Thread.sleep" should not be used in tests (https://rules.sonarsource.com/java/RSPEC-2925/)
- **eslint-plugin-playwright** `playwright/no-wait-for-timeout` — no-wait-for-timeout (disallow page.waitForTimeout) (https://github.com/playwright-community/eslint-plugin-playwright/blob/main/docs/rules/no-wait-for-timeout.md)
- **eslint-plugin-cypress** `cypress/no-unnecessary-waiting` — no-unnecessary-waiting (disallow cy.wait with a number) (https://github.com/cypress-io/eslint-plugin-cypress/blob/master/docs/rules/no-unnecessary-waiting.md)
- **eslint-plugin-ui-testing** `ui-testing/no-hard-wait` — no-hard-wait (cy.wait, page.waitForTimeout, browser.pause, t.wait) (https://github.com/kwoding/eslint-plugin-ui-testing/blob/master/docs/rules/no-hard-wait.md)
