Resource library

QA How-To

Selenium implicit wait pitfalls in Python (2026)

Learn selenium implicit wait pitfalls python teams face, why mixed waits cause delays, and how to build reliable explicit waits with runnable examples.

22 min read | 3,112 words

TL;DR

Implicit waits only retry element discovery and can silently slow negative checks or distort explicit-wait timing. In modern Python Selenium suites, use a zero implicit wait plus targeted WebDriverWait conditions tied to observable UI states.

Key Takeaways

  • Implicit wait is a session-wide element lookup timeout, not a general readiness wait.
  • Keep implicit wait at zero when using explicit waits to avoid nested and unpredictable timing.
  • Choose conditions for visibility, clickability, staleness, text, or absence based on the next action.
  • Measure negative lookups because find_elements can consume the full implicit timeout.
  • Store locators instead of long-lived WebElement references in frequently re-rendered interfaces.
  • Migrate legacy suites by workflow and capture diagnostics before reducing global timeouts.

The phrase selenium implicit wait pitfalls python describes a problem that looks simple but causes surprisingly expensive test failures: a single global timeout changes how every element lookup behaves. An implicit wait can help with brief DOM delays, but it does not wait for visibility, clickability, text, network completion, or business readiness. It can also make missing-element assertions slow and produce confusing total wait times when combined with explicit waits.

The practical recommendation for Python suites in 2026 is straightforward. Keep the implicit wait at zero unless a team has a measured reason to use a small, documented value. Model important UI states with WebDriverWait and a precise condition, keep element lookups close to the action, and record enough diagnostics to distinguish slow rendering from a bad locator.

This guide explains the mechanics, demonstrates the failure modes with runnable Selenium code, and provides a migration plan for existing suites. If synchronization is a broader weakness in your framework, also review this Selenium waits and synchronization guide.

TL;DR

Situation Implicit wait behavior Better choice
Element is not yet in the DOM Polls until it exists or the global timeout expires Explicit wait for presence when presence is sufficient
Element exists but is hidden Returns the hidden element immediately Wait for visibility
Button exists but is disabled or covered Returns the element, then the action can fail Wait for clickability plus the relevant application state
Assert that an element is absent Can spend the full implicit timeout Temporarily avoid global waiting and use a short explicit absence check
Explicit wait calls find_element Every poll may inherit the implicit timeout Do not mix nonzero implicit and explicit waits
Collection lookup finds no matches Polls, then returns an empty list Use an explicit condition when the collection count matters

A good default is:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait

driver = webdriver.Chrome()
driver.implicitly_wait(0)
wait = WebDriverWait(driver, 10)

The zero is explicit documentation. It tells maintainers that synchronization belongs to targeted conditions rather than a hidden session-wide delay.

1. Selenium Implicit Wait Pitfalls Python Teams Need to Understand

An implicit wait is a WebDriver session timeout for finding elements. In Python, driver.implicitly_wait(5) tells the remote end to keep trying eligible element-location commands for up to five seconds when no matching element is immediately available. It is not a five-second pause before every command. Successful lookups can return immediately.

That limited definition matters. The setting applies to the session, persists until changed, and influences later calls such as find_element and find_elements. It does not understand why the application is changing. It cannot tell whether a React component is hydrating, an API request is still pending, an animation is covering a button, or validation is about to enable a control.

Consider a checkout button that is already in the DOM but has a disabled attribute. An implicit wait considers the locator successful because the element exists. Calling click() may then raise an exception or have no useful application effect. The wait solved element discovery, not readiness.

The first pitfall is therefore semantic mismatch. Teams often say, "Selenium waited for the element," when the real requirement was, "Wait until the checkout action is safe and meaningful." Reliable automation starts by naming that observable state. Presence, visibility, enabled state, text, URL, window count, and application-specific attributes are different conditions.

Treat the implicit wait as a locator retry policy, not a general synchronization engine. Once that boundary is clear, the remaining pitfalls become easier to predict and test.

2. How the Timeout Changes Find Behavior

The following script is self-contained apart from a local Chrome installation and the selenium package. It loads a data URL, inserts a button after 1.2 seconds, and measures the lookup. Selenium Manager, included with current Selenium releases, can resolve a compatible driver in standard environments.

from time import perf_counter
from urllib.parse import quote

from selenium import webdriver
from selenium.webdriver.common.by import By

html = """
<!doctype html>
<button id="start" onclick="
  setTimeout(() => {
    const b = document.createElement('button');
    b.id = 'delayed';
    b.textContent = 'Ready';
    document.body.appendChild(b);
  }, 1200)
">Start</button>
"""

driver = webdriver.Chrome()
try:
    driver.implicitly_wait(3)
    driver.get("data:text/html;charset=utf-8," + quote(html))
    driver.find_element(By.ID, "start").click()

    started = perf_counter()
    delayed = driver.find_element(By.ID, "delayed")
    elapsed = perf_counter() - started

    assert delayed.text == "Ready"
    assert elapsed >= 1.0
    print(f"Lookup completed in {elapsed:.2f} seconds")
finally:
    driver.quit()

This is an appropriate use of the mechanism only because existence is the complete requirement. Now change the target ID to never-created. The lookup takes approximately the configured timeout before raising NoSuchElementException. Exact timing varies by browser, driver, machine load, and transport latency, so a test should not assert a narrow duration.

find_elements deserves special attention. When no elements match, Python returns an empty list after the implicit lookup behavior has completed. Code that appears harmless, such as if not driver.find_elements(...), may therefore consume the global timeout. Put that check inside a polling loop and the cost compounds.

The timeout is also mutable. Calling implicitly_wait(1) replaces the previous value for subsequent commands. It is not scoped to the next lookup. A helper that changes it and forgets to restore it silently changes behavior for every test sharing that driver.

3. Why Mixing Implicit and Explicit Waits Is Dangerous

Selenium's official guidance warns against mixing implicit and explicit waits because the resulting timeout can be unpredictable. The reason is nesting. An explicit wait polls a condition. If that condition calls find_element, each poll can itself wait up to the implicit timeout before returning control to the explicit wait.

Suppose the implicit timeout is 10 seconds and an explicit wait has a 4-second timeout. A reader may expect failure near four seconds. The first condition evaluation can remain inside find_element beyond that boundary. Poll frequency, command duration, browser implementation, and the condition's work all influence the observed time.

This is a problematic but runnable example:

from time import perf_counter
from urllib.parse import quote

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException

driver = webdriver.Chrome()
try:
    driver.get("data:text/html," + quote("<h1>Wait demo</h1>"))
    driver.implicitly_wait(5)

    started = perf_counter()
    try:
        WebDriverWait(driver, 2).until(
            EC.visibility_of_element_located((By.ID, "missing"))
        )
    except TimeoutException:
        print(f"Observed timeout: {perf_counter() - started:.2f}s")
finally:
    driver.quit()

Do not turn the printed value into a universal formula. The lesson is that a nonzero implicit wait is work performed inside an explicit condition. The outer timeout is checked between condition evaluations, not by interrupting a WebDriver command that is already in progress.

The clean policy is one synchronization model per driver session. Set implicit wait to zero, then use explicit conditions. If legacy constraints require an implicit wait, do not add explicit waits casually. First measure the interaction in the browsers and Grid topology that the suite actually uses.

4. Presence Is Not Visibility, Clickability, or Stability

A robust test asks what must be true before the next action. Selenium's built-in expected conditions cover common browser-observable states, but each has a precise meaning.

Condition What it establishes What it does not guarantee
presence_of_element_located At least one matching element exists in the DOM Visible, enabled, stable, or useful
visibility_of_element_located Matching element is displayed and has nonzero size Uncovered, animation finished, or business data loaded
element_to_be_clickable Element is visible and enabled No overlay intercepts it, or click triggers the expected result
text_to_be_present_in_element Specified text is present Final text, unique text, or backend consistency
invisibility_of_element_located Element is absent or not visible All background work is complete
staleness_of A known element is detached from the DOM Its replacement is ready

Here is a runnable explicit-wait example for a button that is created disabled, then enabled:

from urllib.parse import quote

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

html = """
<!doctype html>
<button id="save" disabled>Saving...</button>
<script>
  setTimeout(() => {
    const button = document.querySelector('#save');
    button.disabled = false;
    button.textContent = 'Save';
  }, 800);
</script>
"""

driver = webdriver.Chrome()
try:
    driver.implicitly_wait(0)
    driver.get("data:text/html;charset=utf-8," + quote(html))

    wait = WebDriverWait(driver, 5)
    save = wait.until(EC.element_to_be_clickable((By.ID, "save")))
    assert save.text == "Save"
    save.click()
finally:
    driver.quit()

Even clickability is not a promise that the entire workflow is ready. If an overlay can cover the control, wait for that overlay to disappear. If clicking starts a request, assert the resulting URL, message, row, or state transition. Synchronization should follow the user's observable journey.

5. Negative Assertions Become Quietly Expensive

Positive paths often hide implicit-wait cost because elements exist quickly. Negative paths expose it. Tests routinely verify that an error banner is absent, a deleted row no longer exists, a menu item is unavailable, or a loading indicator has disappeared. A global ten-second lookup timeout can turn each absence check into a ten-second tax.

This pattern looks concise but can be slow:

assert driver.find_elements(By.CSS_SELECTOR, "[role='alert']") == []

With a nonzero implicit wait, the empty result may arrive only after polling. Repeating the assertion after five form actions multiplies suite time without improving confidence.

An explicit negative condition communicates intent:

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

wait = WebDriverWait(driver, 3)
wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".spinner")))

For an element that should never appear, decide what observation window is justified. A zero-second check proves only that it is absent now. A short wait proves it did not become visible during that interval, which may be appropriate for a transient error. A long arbitrary delay rarely makes the assertion more meaningful.

Be careful with helpers that temporarily set the implicit wait to zero. They can work in strictly sequential code if the old value is restored in finally, but they create shared mutable state. Parallel code, nested helpers, and fixture reuse make them fragile. Migrating the driver session to explicit waits is safer than building a timeout stack around a global setting.

For more locator-focused failure analysis, see how to fix Selenium NoSuchElementException.

6. Stale Elements and Re-rendered Interfaces

Implicit waits help locate an element. They do not make a stored WebElement reference durable. Modern interfaces often replace a node after a state update, even when the replacement looks identical. Code that finds a row, triggers a refresh, and then reuses the old row can raise StaleElementReferenceException.

The reliable approach is to wait for the old reference to become stale, then locate the replacement:

from urllib.parse import quote

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

html = """
<!doctype html>
<div id="status">Old</div>
<button id="refresh" onclick="
  const oldNode = document.querySelector('#status');
  const newNode = document.createElement('div');
  newNode.id = 'status';
  newNode.textContent = 'New';
  setTimeout(() => oldNode.replaceWith(newNode), 500);
">Refresh</button>
"""

driver = webdriver.Chrome()
try:
    driver.implicitly_wait(0)
    driver.get("data:text/html;charset=utf-8," + quote(html))
    wait = WebDriverWait(driver, 5)

    old_status = driver.find_element(By.ID, "status")
    driver.find_element(By.ID, "refresh").click()

    wait.until(EC.staleness_of(old_status))
    new_status = wait.until(
        EC.visibility_of_element_located((By.ID, "status"))
    )
    assert new_status.text == "New"
finally:
    driver.quit()

This example models two different events: detachment and replacement readiness. An implicit wait cannot act on the stored element, and it cannot detect the transition by itself.

Page objects should therefore store locators more often than long-lived element instances. Resolve a locator when the action needs it. That choice narrows the time between discovery and interaction, reduces stale references, and lets each action apply the correct condition.

Do not respond to staleness by retrying every exception. Blind retries can click twice, hide real application churn, or convert deterministic defects into intermittent passes. Retry only an idempotent observation, and pair it with a clear state transition.

7. Debugging Selenium Implicit Wait Pitfalls Python Suites Expose

Timing bugs need evidence. Before changing a timeout, capture the condition, locator, current URL, elapsed time, exception type, screenshot, and relevant DOM fragment. On Grid, include browser capabilities and session identifiers in test artifacts. Without that context, teams tend to increase a global wait and postpone the same failure.

A small wrapper can attach timing to a targeted condition:

from time import perf_counter
from selenium.webdriver.support.ui import WebDriverWait

def wait_until(driver, condition, timeout, label):
    started = perf_counter()
    try:
        return WebDriverWait(driver, timeout).until(condition)
    finally:
        elapsed = perf_counter() - started
        print(f"wait={label!r} elapsed={elapsed:.2f}s timeout={timeout}s")

Use it with a real condition:

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

toast = wait_until(
    driver,
    EC.visibility_of_element_located((By.CSS_SELECTOR, "[role='status']")),
    timeout=8,
    label="checkout confirmation toast",
)

When a failure occurs, ask these questions in order:

  1. Did the locator match the intended element in the failing DOM?
  2. Was the required state presence, visibility, enabled state, absence, text, or a transition?
  3. Was the element replaced between lookup and action?
  4. Did an overlay, frame, window, or shadow boundary change the browsing context?
  5. Did the explicit condition run under a nonzero implicit wait?
  6. Was the application slow, or did it enter a state where the condition could never succeed?

Measure distributions in CI rather than optimizing from a single laptop run. A timeout is a failure boundary, not a target duration. If successful waits typically resolve quickly, a larger explicit ceiling may tolerate rare environment variance without slowing normal passes. If they cluster near the ceiling, investigate the application or condition.

8. A Better Python Wait Architecture

A good framework keeps waits visible and domain-oriented. Test methods should read like behavior, while page or component objects translate that behavior into explicit browser conditions. Avoid a generic wait_and_click that guesses the correct state for every control.

The following component uses locators, explicit conditions, and a post-action assertion:

from dataclasses import dataclass

from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait


@dataclass
class LoginPage:
    driver: WebDriver
    timeout: float = 10

    USERNAME = (By.ID, "username")
    PASSWORD = (By.ID, "password")
    SUBMIT = (By.CSS_SELECTOR, "button[type='submit']")
    ERROR = (By.CSS_SELECTOR, "[role='alert']")

    @property
    def wait(self) -> WebDriverWait:
        return WebDriverWait(self.driver, self.timeout)

    def login_as(self, username: str, password: str) -> None:
        self.wait.until(
            EC.visibility_of_element_located(self.USERNAME)
        ).send_keys(username)
        self.driver.find_element(*self.PASSWORD).send_keys(password)
        self.wait.until(
            EC.element_to_be_clickable(self.SUBMIT)
        ).click()

    def error_message(self) -> str:
        return self.wait.until(
            EC.visibility_of_element_located(self.ERROR)
        ).text

The username uses a visibility wait because the test must type into it. The password is found directly after the first field is ready, which may be sufficient for a stable form. The submit button uses clickability. The error method waits for an observable outcome.

A framework may also define application-specific callables. A condition can return a useful value when successful and False while waiting. Keep custom conditions side-effect free. Do not click, submit, or mutate data from a condition because WebDriverWait can call it repeatedly.

Readability is a reliability feature. A failure labeled "login error visible" is easier to diagnose than a generic ten-second pause ending in a locator exception.

9. Migrating a Legacy Suite Without Creating New Flakes

A large suite cannot always switch from a 20-second implicit wait to zero in one commit. Migrate by behavior and measure the result.

First, inventory where the driver timeout is set. Search fixtures, base classes, factories, hooks, and helper methods. Confirm whether tests change it during execution. Then identify the slowest and flakiest workflows using test reports, not memory.

Second, convert one vertical slice. Choose a representative flow such as login, search, or checkout. Replace global assumptions with explicit conditions around real transitions. Keep locators separate from element resolution. Add failure artifacts before lowering the implicit timeout, so new failures provide evidence.

Third, reduce the implicit wait in stages only if the framework still depends on it. Track failure categories. A rise in NoSuchElementException may reveal missing presence waits, but ElementClickInterceptedException points to overlays or timing after discovery. StaleElementReferenceException points to node replacement. Each requires a different fix.

Fourth, set the implicit wait to zero for the converted driver scope. Parallelize migration by fixture, module, or application area rather than toggling the value inside individual tests. Global mutation inside a shared session is difficult to reason about.

Finally, enforce the policy in review. New page methods should name their conditions. Hard sleeps need justification. Timeout increases should include failure evidence. This Selenium flaky tests guide provides a broader triage model for instability beyond waits.

A successful migration usually produces clearer failures before it produces shorter runtimes. That is valuable. When a condition says what the application should do, a timeout becomes a useful defect report instead of a generic missing-element symptom.

10. When a Small Implicit Wait Can Be Acceptable

Zero is the cleanest default, but engineering rules need context. A small implicit wait can be acceptable in a compact, sequential suite where nearly every interaction requires only DOM presence, the team does not use explicit waits, and the behavior is documented. It may also be a temporary compatibility measure during migration.

Use a decision record:

Question Evidence supporting a small implicit wait Warning sign
Do most lookups need only presence? Static server-rendered pages Dynamic controls, overlays, hydration
Are negative checks rare? Almost no absence assertions Frequent optional-element probes
Are explicit waits prohibited or absent? One consistent model Mixed wait styles across helpers
Is the driver isolated? One driver per test worker Shared driver or concurrent access
Is timing measured? CI duration and failure categories tracked Timeout increased after anecdotes

If the team keeps an implicit wait, choose the smallest value that covers measured locator delay, not the slowest complete workflow. Document that it does not replace readiness checks. Do not hide changes inside convenience methods.

Also distinguish browser timeouts. Page-load timeout, script timeout, and implicit element lookup timeout represent different operations. Increasing one does not fix a failure governed by another. A page that has loaded can still contain a component that is not ready. Conversely, an element wait cannot rescue a navigation that exceeded the page-load boundary.

For new automation, start with explicit waits and state-based assertions. It is easier to preserve a clear model than to remove a global timing dependency later.

Interview Questions and Answers

Q: What does implicitly_wait(10) do in Selenium Python?

It configures a session-wide timeout used when locating elements. A lookup can return as soon as a match is found, so it is not an unconditional ten-second sleep. The setting remains active until it is changed.

Q: Why should implicit and explicit waits not be mixed?

An explicit condition often calls find_element on every poll. With a nonzero implicit timeout, each poll can block inside that lookup, making the outer explicit timeout difficult to predict. Use one model, preferably explicit waits with implicit wait set to zero.

Q: Does implicit wait make an element clickable?

No. It helps locate an element, but an existing element may be hidden, disabled, moving, or covered. Use a condition that represents the required state and verify the result after clicking.

Q: Does implicit wait apply to find_elements?

Yes, locating a collection can poll when there are no matches. The eventual Python result is an empty list rather than NoSuchElementException, which can make absence checks quietly expensive.

Q: Can implicit wait fix stale element references?

No. Staleness means a previously located element is no longer attached to the DOM. Wait for the old reference to become stale if relevant, then locate the replacement.

Q: Is time.sleep() ever useful in debugging?

A temporary sleep can help confirm that a failure is timing-related, but it should not be the final synchronization strategy. Replace it with a condition tied to an observable application state.

Q: What timeout should a team choose?

Choose from measured CI behavior and the business operation, not a universal number. The timeout is a ceiling for failure, while successful explicit waits return as soon as their condition succeeds.

Q: How do you test that an element is absent?

Define the intended observation. Use an immediate collection check for "absent now," or a short explicit invisibility condition for a transition. Avoid paying a large global implicit timeout for every negative assertion.

Common Mistakes

  • Setting a 30-second implicit wait in a base class to hide all timing failures.
  • Assuming presence means visible, enabled, uncovered, and ready.
  • Combining a nonzero implicit wait with WebDriverWait.
  • Using find_elements as a cheap absence probe without measuring the delay.
  • Caching elements across a re-render instead of caching locators.
  • Changing the implicit timeout in a helper and not restoring it.
  • Retrying clicks inside wait conditions, which can cause duplicate actions.
  • Increasing timeouts without saving the URL, locator, screenshot, and DOM evidence.
  • Using a hard sleep after every action, which slows passes and still fails on slower runs.
  • Treating every timeout as an infrastructure issue when the condition or locator is wrong.

A useful code-review question is: "What exact application state makes the next line safe?" If the answer is only "we waited ten seconds," the synchronization is incomplete.

Conclusion

The central lesson behind selenium implicit wait pitfalls python is that an implicit wait solves only delayed element discovery. Its global scope, cost on negative lookups, inability to model readiness, and interaction with explicit polling make it a weak default for modern dynamic applications.

Set the implicit wait to zero for new Python suites, use precise explicit conditions, and assert the outcome of each important action. For legacy suites, migrate one workflow at a time with timing and failure diagnostics. The next practical step is to choose one flaky test, write down the state it actually needs, and replace its global timing assumption with that state.

Interview Questions and Answers

What exactly does implicitly_wait configure in Python Selenium?

It sets the WebDriver session's implicit element-location timeout. Matching element lookups can return immediately, while missing matches may be polled until the timeout. The value persists until another call changes it.

Why is mixing implicit and explicit waits risky?

Explicit conditions often call find_element during each poll. A nonzero implicit timeout can block each of those calls, so the explicit timeout is no longer an intuitive upper bound. I keep implicit wait at zero when a suite uses WebDriverWait.

What is the difference between presence and visibility?

Presence means a matching node exists in the DOM. Visibility additionally requires the element to be displayed with nonzero size. Neither alone guarantees that an overlay is absent or that the business workflow is ready.

Does an implicit wait run before every Selenium command?

No. It governs applicable element location behavior and is not an unconditional delay. Commands unrelated to finding an element are controlled by their own behavior and timeouts.

How does implicit wait affect find_elements?

If no elements match, the command may poll until the implicit timeout and then return an empty list. This is important because code that looks like a quick absence check can become a repeated delay.

Can implicit wait prevent StaleElementReferenceException?

No. An implicit wait helps obtain an element reference, but it does not refresh a reference after the DOM replaces that node. I wait for the transition when needed and then locate the element again.

How would you wait for a spinner to disappear?

I would use WebDriverWait with invisibility_of_element_located for the spinner locator. Then I would wait for or assert the next meaningful state, because spinner absence alone may not prove the operation succeeded.

When would you write a custom expected condition?

I use one when the required state is application-specific, such as an attribute reaching a stable value or a table count matching a request result. The condition should be observable, side-effect free, and return a useful value or False.

How do you choose an explicit-wait timeout?

I use CI timing evidence, the expected business operation, and the cost of a false failure. The value is a maximum failure boundary, not a sleep duration, because WebDriverWait returns as soon as the condition succeeds.

Why are negative assertions slow under implicit wait?

A missing element is exactly the case that consumes the lookup timeout. Repeated find_elements checks for optional or absent UI can therefore pay the global timeout each time.

What diagnostics do you capture for a wait timeout?

I capture the named condition, locator, elapsed time, configured timeout, URL, screenshot, relevant DOM, browser capabilities, and session context. Those artifacts separate a bad locator from a slow render or an incorrect expected state.

What synchronization policy would you recommend for a new framework?

Use one driver per isolated test scope, keep implicit wait at zero, and centralize explicit, state-based waits in page or component objects. Store locators rather than long-lived elements and assert outcomes after actions.

Frequently Asked Questions

What is the biggest problem with implicit wait in Selenium Python?

Its global scope hides which operations are actually waiting. It also treats element presence as success even when the test needs visibility, clickability, text, or another application state.

Can I use implicit and explicit waits together in Selenium?

Selenium guidance says not to mix them because an explicit condition can invoke element lookups governed by the implicit timeout. The resulting total duration can exceed what a reader expects from the explicit timeout.

Does Selenium implicit wait apply to find_elements in Python?

Yes. When there are no matches, the lookup can wait before returning an empty list. This makes repeated optional-element and absence checks slower than they appear.

What should implicit wait be when using WebDriverWait?

Set it to zero so each explicit condition controls its own timeout. Zero is also the WebDriver default, but setting it explicitly can document your framework policy.

Is element_to_be_clickable enough for every click?

No. It checks visibility and enabled state, but an overlay or application transition can still interfere. Wait for relevant blockers to disappear and assert the observable result after clicking.

How can I replace time.sleep in Selenium Python?

Identify the state the sleep was hoping to cover, then use WebDriverWait with a built-in or custom side-effect-free condition. Examples include visibility, URL change, staleness, text, and invisibility.

How do I migrate a suite away from implicit waits?

Inventory timeout mutations, add failure artifacts, and convert one end-to-end workflow at a time. Lower or remove the implicit wait at a fixture or driver scope rather than toggling it around individual lookups.

Related Guides