This episode dissects the difference between implicit and explicit waits, using WebDriverWait with ExpectedConditions, handling stale element exceptions and timing issues, and applying best practices so test execution stays stable and reliable.

In episode 4 you got acquainted with WebDriverWait for waiting for elements to appear. Episode 5 completes that understanding into a full strategy: synchronization and waits. The questions we'll answer: when to use implicit wait, when explicit wait, how to read ExpectedConditions, and how to escape the stale element trap that so often destroys tests in the middle of the night.
The modern web loads content asynchronously — components appear after a request completes, animations finish, or data re-renders. Without proper synchronization, your tests will argue with the browser: the test looks for an element too early, the browser hasn't finished loading, and NoSuchElementException appears. This episode gives you the keys to win that argument.
Implicit wait is a timeout applied globally to every element-finding command. Once set, Selenium will wait that long before throwing an error if the element isn't found.
from selenium import webdriver
driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get("https://example.com")
judul = driver.find_element("tag name", "h1")
driver.quit()driver.implicitly_wait(10) sets a default polling timeout of 10 seconds. If the h1 element appears within 3 seconds, the command returns immediately; if not, Selenium waits until the timeout then throws an error.
Explicit wait waits for a specific condition on a specific element, with its own timeout. This is more precise because you express what you're waiting for — not just "element exists", but "element is clickable" or "text appears".
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
tombol = WebDriverWait(driver, 15).until(
EC.element_to_be_clickable((By.ID, "submit"))
)
tombol.click()The rule of thumb: use implicit wait for common element lookups, and explicit wait for functionally important conditions. Both can run together, but be careful because their effects stack.
expected_conditions provides dozens of ready-to-use conditions. The ones we use most often:
presence_of_element_located — the element is in the DOM, not necessarily visible.visibility_of_element_located — the element is visible and has size.element_to_be_clickable — the element is visible and enabled.text_to_be_present_in_element — a specific text appears in the element.url_contains and title_contains — navigation has completed.wait = WebDriverWait(driver, 20)
wait.until(EC.url_contains("/checkout"))
wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME, "cart"), "Rp 250.000"))The WebDriverWait(driver, 20) pattern is stored once as an object then reused. This is cleaner than writing WebDriverWait(...) on every line.
For conditions that aren't available, you can create a function that returns a boolean or object. As long as it isn't False, the wait continues until timeout:
from selenium.webdriver.support.ui import WebDriverWait
def tabel_selesai(driver):
sel = driver.find_element("id", "spinner")
return not sel.is_displayed()
WebDriverWait(driver, 15).until(tabel_selesai)The tabel_selesai(driver) function is a simple condition that waits for the spinner to disappear. This is a great technique for states that don't have a built-in EC.
A stale element occurs when an element reference in memory is no longer connected to the DOM. Common causes: the page is reloaded, the element is re-rendered by a JavaScript framework, or part of the page is replaced after a certain action. Selenium throws StaleElementReferenceException when you use the old reference.
The simplest strategy: re-find the element each time you interact, or reload it in a loop when the exception occurs.
from selenium.common.exceptions import StaleElementReferenceException
for _ in range(3):
try:
driver.find_element("id", "list").find_elements("tag name", "li")[2].click()
break
except StaleElementReferenceException:
continueThe snippet above tries the click up to three times. The re-find on every action pattern is the main foundation for avoiding stale elements in apps that re-render frequently.
Several habits keep test execution stable:
time.sleep() — waiting with a fixed number makes tests slow and brittle.klik_saat_siap(driver, by, value) so all tests use them consistently.explicit wait -> short implicit -> retry stale -> static sleep (last resort)The order above shows the priority: start with explicit conditions, don't make sleep your mainstay.
Warning
time.sleep() is not a synchronization solution. It waits a fixed amount of time that knows nothing about the application's state, slows down the suite, and still fails if the app is slower than the number you guessed.
Episode 5 resolves the timing problems introduced in episode 4: you can now distinguish implicit wait as a global safety net and explicit wait as precision control, read ExpectedConditions, overcome stale elements by re-finding, and apply the correct strategy order.
Key takeaways:
time.sleep(); use application-based conditions.In episode 6 next, we'll cover page object pattern and test structure — implementing the Page Object Model, encapsulating page interactions and locator abstraction, organizing the tests, pages, and utils folders, and building reusable helpers so your tests are cleaner and easier to maintain.