This episode puts all the skills learned so far into practice with real use cases: e-commerce checkout, login flows, and dashboard interactions, end-to-end test design patterns, the difference between component-level and full UI tests, and prioritizing tests for business-critical flows.

All the skills from episodes 0 to 19 now come together. Episode 20 builds real-world use cases that combine everything: login flows, e-commerce checkout, and dashboard interactions — complete with test design patterns worth copying. This is the most practical episode in the series.
This is where you see how the concepts work together in one complete suite: page objects for structure, waits for timing, data-driven for variety, API helpers for speed, and screenshots for failure evidence. Every example is a template you can adapt to your own application.
Login is the foundation of almost every application. Use the page object from episode 6 and the data from episode 9:
from pages.login_page import LoginPage
@pytest.mark.parametrize(
"email,password,harapan",
[
("user@example.com", "rahasia123", "Dashboard"),
("salah@example.com", "salah", "Kredensial tidak valid"),
],
)
def test_login_flow(email, password, harapan):
driver.get("https://app.example.com/login")
halaman = LoginPage(driver)
halaman.isi_email(email)
halaman.isi_password(password)
halaman.klik_masuk()
if harapan == "Dashboard":
assert "Dashboard" in driver.title
else:
assert "Kredensial tidak valid" in halaman.pesan_error()test_login_flow combines two scenarios in one parameterized test: successful login and failed login. The page object hides the locator details, and the assertion branches on the expected result.
Checkout is the highest business-value flow — this is where money moves. Its test crosses several pages and verifies each transition:
from pages.produk_page import ProdukPage
from pages.keranjang_page import KeranjangPage
from pages.checkout_page import CheckoutPage
def test_checkout_berhasil():
produk = ProdukPage(driver)
produk.buka("https://app.example.com/produk/1")
produk.tambah_ke_keranjang()
keranjang = KeranjangPage(driver)
keranjang.lanjutkan()
checkout = CheckoutPage(driver)
checkout.isi_alamat("Jl. Merdeka 1")
checkout.pilih_pengiriman_standar()
checkout.bayar()
assert "Terima kasih" in checkout.konfirmasi()
assert "Transaksi" in driver.current_urltest_checkout_berhasil uses three chained page objects. Each page handles its own elements, while the test tells the business flow linearly — from product, to cart, to checkout, to confirmation.
For money flows, also validate at the service layer as in episode 16:
pesanan = requests.get(
f"https://app.example.com/api/pesanan/{id}",
timeout=5,
).json()
assert pesanan["status"] == "PAID"pesanan["status"] == "PAID" proves the transaction is recorded, not just a changed display. For business-critical flows, this cross-layer verification isn't an option — it's a requirement.
Dashboards are full of charts and numbers that appear asynchronously. Tests must wait for the data to actually appear before asserting:
wait = WebDriverWait(driver, 20)
wait.until(EC.text_to_be_present_in_element(
(By.ID, "ringkasan-penjualan"), "Rp"
))
nilai = driver.find_element("id", "total-penjualan").text
assert int(nilai.replace(".", "").replace("Rp ", "")) > 0wait.until(EC.text_to_be_present_in_element(...)) waits for the sales figure to appear — avoiding assertions against a still-empty dashboard. This is the typical dashboard pattern: wait for data, then validate its value.
A summary of the patterns you've used throughout the series, now in one list:
driver fixture + page object + explicit wait + data + screenshot evidencedriver fixture + page object + explicit wait + data + screenshot evidence is the recipe for a stable, maintainable E2E test. You've mastered all the ingredients in previous episodes.
Not every test must run the whole application. Distinguish two levels:
many component-level -> some full UI -> few smoke (critical)many component-level -> some full UI -> few smoke (critical) mirrors the pyramid from episode 0: more fast, focused tests, few expensive tests for critical flows.
Allocate test effort based on business risk, not ease of writing:
@pytest.mark.tier1
def test_checkout_berhasil():
...@pytest.mark.tier1 marks a critical test. Run per tier on schedule: pytest -m tier1 on every commit, pytest -m "not tier3" on every merge. This priority keeps the pipeline fast while critical flows stay protected.
Tip
When team time is limited, prioritize writing and maintaining Tier 1 tests. One stable checkout test is worth more than ten flaky minor page tests.
Episode 20 distills the whole series into real practice: a parameterized login flow, e-commerce checkout crossing many pages, a dashboard that waits for data, healthy test design patterns, and tier priorities that protect business-critical flows.
Key takeaways:
In episode 21 next, we'll cover ecosystem and tools — BrowserStack and Sauce Labs, TestNG, Allure, and Playwright, Selenium IDE fundamentals for record and playback, community resources and best practice guides, and how to extend Selenium with other frameworks and libraries.