This episode covers assertions with pytest to validate page state, text, element visibility, and behavior, composing dynamic assertions for responsive UI, and adding error reporting and automatic screenshots when tests fail.

Finding elements and interacting with them is only half the story. The other half is validation: proving the results meet expectations. Episode 7 covers assertions and test validation — how to validate page state, text, element visibility, and application behavior, and how to make test failures easy to trace through error reporting and screenshots.
Assertions are the heart of the difference between "automation is running" and "tests that truly validate". A script that only clicks without checking results provides no value at all. In this episode you'll learn to choose the right assertion and place it at the right moment.
Python uses the assert statement with standard comparison operators. pytest catches assert failures and shows the actual vs expected values in detail:
judul = driver.title
assert judul == "Dashboard"
jumlah_item = len(driver.find_elements("css selector", ".cart-item"))
assert jumlah_item > 0
assert jumlah_item == 3, f"Jumlah item {jumlah_item}, diharapkan 3"assert jumlah_item == 3, "pesan" adds a message that appears on failure. This message is very helpful when tracing failures in CI, so make a habit of always writing one.
The rule of thumb: assert what really matters to the business. Don't assert trivial details that only make tests brittle, like CSS attribute ordering. Assert functional outcomes: text appears, URL changes, element count matches, or a specific state is active.
The classic combination for validating a page:
selamat = driver.find_element("id", "welcome")
assert selamat.is_displayed()
assert "Selamat datang" in selamat.text
tombol_beli = driver.find_element("id", "beli")
assert tombol_beli.is_enabled()
assert tombol_beli.get_attribute("data-harga") == "250000"is_displayed() validates the element is visible, is_enabled() validates the element is active, and get_attribute(...) reads an attribute value. These three reads are the raw materials for page state assertions.
The most valuable assertions check the effects of user actions:
before = len(driver.find_elements("css selector", ".item"))
driver.find_element("id", "tambah").click()
wait = WebDriverWait(driver, 10)
wait.until(lambda d: len(d.find_elements("css selector", ".item")) == before + 1)
after = len(driver.find_elements("css selector", ".item"))
assert after == before + 1The snippet above asserts that clicking the button adds one item. Note the wait before the assertion — assertions must wait for the state to stabilize, not check immediately.
Responsive UI makes element appearance change depending on viewport size. Dynamic assertions validate the same behavior but through different representations:
def cek_menu_terlihat(driver, mode):
if mode == "desktop":
return driver.find_element("id", "nav-desktop").is_displayed()
return driver.find_element("id", "hamburger").is_displayed()
def test_menu_responsif():
driver.set_window_size(1440, 900)
assert cek_menu_terlihat(driver, "desktop")
driver.set_window_size(375, 812)
assert cek_menu_terlihat(driver, "mobile")The cek_menu_terlihat(driver, mode) function picks the relevant element for each mode. The principle: validate the same goal (the menu is accessible), not a single specific implementation that can change with screen width.
Finding the reason for a failure is much easier with visual evidence. Screenshots can be taken automatically via a pytest fixture that checks the test result:
import pytest
from pathlib import Path
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "call" and report.failed:
driver = item.funcargs.get("driver")
if driver:
Path("artifacts").mkdir(exist_ok=True)
driver.save_screenshot(f"artifacts/{item.name}.png")
with open(f"artifacts/{item.name}.html", "w") as f:
f.write(driver.page_source)The pytest_runtest_makereport(item, call) hook captures the result of each test. On failure, the hook saves a screenshot and an HTML snapshot to the artifacts folder. These two files become the primary evidence when triaging failures.
Besides screenshots, include context in the logs: the URL at failure time, browser version, and the last step executed. Combining screenshots plus context dramatically cuts debugging time.
Info
Save screenshots in PNG format and attach them as artifacts in CI. Don't write them into a committed project directory, because their contents change on every run.
Episode 7 completes the proper test cycle: action without validation is empty automation. You can now validate text, visibility, attributes, and behavior with pytest assertions, compose dynamic assertions for responsive UI, and automatically produce failure evidence in the form of screenshots and HTML snapshots.
Key takeaways:
In episode 8 next, we'll cover advanced browser actions — handling alerts, popups, frames, and windows, drag and drop, double click, hover, and keyboard actions, automating file upload and download, and scrolling and viewport handling.