Lesson 10 / الدرس 10

Waiting, and the flaky test / الانتظار، والاختبار المتقلّب

A flaky test is one that passes and fails without the product changing. It is the single thing most likely to destroy a team's trust in automation, and almost all of it comes from one mistake: acting before the page is ready.

الاختبار المتقلّب هو الذي ينجح ويرسب دون أن يتغير المنتج. وهو أكثر شيء يُرجَّح أن يهدم ثقة فريق بالأتمتة، وجُلّه يأتي من خطأ واحد: التصرّف قبل أن تكون الصفحة جاهزة.

Your code runs in microseconds. The browser needs to fetch, parse, render, and often wait for a request to come back before the thing you want even exists. A test that clicks before the button is there does not fail helpfully — it fails on the machine that happened to be slow that morning, which is how a suite becomes something people re-run instead of read.

Never sleep. Wait for the condition

The instinct is to add a pause — wait two seconds, then click. It works on your machine, and it is the worst possible fix. Two seconds is simultaneously too long on every passing run and too short on the one slow run that matters. A hundred such pauses is three minutes added to every build, and the flakiness is still there. Wait for the thing itself instead: the element to be visible, the URL to change, the request to finish.

// Wrong. Passes on your laptop, fails in the pipeline, and adds 2s either way.
await page.waitForTimeout(2000);
await page.getByRole('button', { name: 'Save' }).click();

// Right. Waits exactly as long as it needs to, and no longer.
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Saved')).toBeVisible();

// Right, when the thing you are waiting for is a request:
await Promise.all([
  page.waitForResponse(r => r.url().includes('/orders') && r.status() === 201),
  page.getByRole('button', { name: 'Place order' }).click(),
]);
The middle block is the everyday answer: modern tools retry the assertion until it holds or a timeout expires, so expecting the result is itself the wait. The third is for when you must know the server answered before you look.

The other causes, in order

  1. Tests that share data. One deletes the account another signs in with. They pass alone and fail together, or fail only when run in a different order.
  2. Time. A test that uses today's date, or breaks at midnight, or assumes a month has 30 days.
  3. Real external services. A payment provider's sandbox is not your test's responsibility, and its bad afternoon becomes your red build.
  4. Animation. An element that is present but still sliding into place can be clicked in the wrong spot.

Check yourself / اختبر نفسك

1. Why is a fixed two-second pause the wrong way to fix a timing problem?

2. Two tests pass alone and fail when run together. What is the likely cause?

3. What is the real cost of leaving a known-flaky test in the suite?