lecture 1 of 160 completed

Waits, and the mistake that hides in them

Explicit waits are the right tool. Mixing them with implicit waits creates timing bugs that are very hard to find.

You already know not to use driver.sleep. Now let's talk about the mistake that replaces it, because I have seen it in almost every Selenium project I have joined.

Selenium has two kinds of waiting, and they do not work well together.

An implicit wait is a global setting. You set it once, and every findElement will keep retrying for that long before giving up:

await driver.manage().setTimeouts({ implicit: 10000 });

An explicit wait waits for one specific condition, in one specific place:

const toast = await driver.wait(
  until.elementLocated(By.css('[data-testid="toast"]')),
  5000
);

Both look reasonable. The problem starts when a project has both, which is very common because someone adds the implicit wait early and forgets it.

Here is what happens. Your explicit wait asks "is this element there?" every half second. But each of those checks now goes through the implicit wait, which itself retries for 10 seconds before answering. The waits multiply instead of adding. A test you expected to fail after 5 seconds takes 50 seconds to fail, and sometimes it times out in a place that makes no sense.

I joined a team whose suite took 40 minutes and nobody knew why. They had an implicit wait of 30 seconds set in a base class three years earlier. Removing that one line brought the suite down to 11 minutes.

So the rule is simple: pick explicit waits, and set the implicit wait to zero.

Explicit waits are also more precise, because you say what you are waiting for:

await driver.wait(until.elementIsVisible(el), 5000);    // visible to the user
await driver.wait(until.elementIsEnabled(el), 5000);    // can be clicked
await driver.wait(until.urlContains('/dashboard'), 5000); // navigation finished

And when nothing built in fits, you can wait for your own condition. This waits until a table has exactly one row:

const rows = await driver.wait(async () => {
  const found = await driver.findElements(By.css('[data-testid="contact-row"]'));
  return found.length === 1 ? found : null;
}, 5000);

Return null to keep waiting. Return a value to finish. That pattern will solve most waiting problems you meet.

Remember this: use explicit waits everywhere, and never set an implicit wait as well.

Reference: Waits.