This episode covers data-driven testing: using parametrize for many test cases, separating input and expected results, reading data from CSV, JSON, and Excel, and expanding test coverage across many browsers and environments.

Writing one test for one scenario is easy. The problem starts when scenarios multiply: validating a form with ten input combinations, or testing login with five account types. Copying tests ten times is a path to chaos. Episode 9 introduces data-driven testing — separating data from test logic so one test serves many cases.
This approach doesn't just reduce duplication; it also makes tests easier to update: when data changes, you just update the data file instead of rewriting code. This episode covers parameterization with pytest, external data sources like CSV, JSON, and Excel, and expanding coverage across many browsers and environments.
pytest provides parametrize to run the same test function with many sets of data:
import pytest
@pytest.mark.parametrize(
"email,password,harapan",
[
("user@example.com", "rahasia123", "Selamat datang"),
("salah@example.com", "salah", "Email atau password salah"),
("", "", "Kolom wajib diisi"),
],
)
def test_login(email, password, harapan):
halaman = LoginPage(driver)
halaman.isi_email(email)
halaman.isi_password(password)
halaman.klik_masuk()
assert harapan in halaman.ambil_pesan()@pytest.mark.parametrize(...) produces three separate tests whose names include the arguments. When one case fails, pytest shows which case failed and what data was used — exactly what you need for debugging.
For large data sets, give ids so reports are easy to read:
@pytest.mark.parametrize(
"email,password,harapan",
[...],
ids=["login-berhasil", "login-salah", "field-kosong"],
)
def test_login_kasus(email, password, harapan):
...The ids=["login-berhasil", "login-salah", "field-kosong"] parameter renames the cases in the output. In CI, these descriptive names make failure reports much easier to understand.
Test data often lives outside code so non-engineers can change it. Reading from a JSON file is easy:
import json
import pytest
def data_login():
with open("data/login_cases.json") as f:
return [(c["email"], c["password"], c["harapan"]) for c in json.load(f)]
@pytest.mark.parametrize("email,password,harapan", data_login())
def test_login_dari_json(email, password, harapan):
...The data_login() function reads the case list from JSON and converts it into tuples for parametrize. The data file format is also easy to change from CSV to Excel without touching test logic.
For CSV, use the csv module from the standard library; for Excel, the openpyxl library:
pip install openpyxlAfter pip install openpyxl, reading a sheet is just a matter of loading the workbook then iterating rows. Choose the format based on team needs: CSV is the lightest, JSON is the most expressive, and Excel is the friendliest for non-technical teams managing data.
Pair input with expected result in a single tuple, as we've been doing. This pattern has two benefits: the test case is complete (input plus expectation), and the failure message shows both sides — which input was used and what result was expected.
kasus = [
("Jakarta", "DKI Jakarta"),
("Surabaya", "Jawa Timur"),
("Bandung", "Jawa Barat"),
]Each line ("Jakarta", "DKI Jakarta") is one complete scenario: type the city, check the province that appears. Keeping input and expectation close together makes test data auditable at a glance.
Data-driven isn't just for inputs — it's also a pattern for expanding test reach:
@pytest.mark.parametrize("browser", ["chrome", "firefox", "edge"])
def test_beranda(browser, buat_driver):
driver = buat_driver(browser)
driver.get("https://example.com")
assert "Example" in driver.title@pytest.mark.parametrize("browser", ["chrome", "firefox", "edge"]) runs the same test in three browsers. Combined with environment parameters (staging, production), you get a comprehensive test matrix without writing a single extra line per combination.
Tip
Don't parameterize everything. If a case needs very different setup steps, split it into a separate test — forced parameterization actually makes tests hard to read.
Episode 9 lets your suite grow without duplicating code: one test function serves many cases via parametrize, data is stored separately in JSON, CSV, or Excel, and the browser plus environment matrix expands just by adding parameters.
Key takeaways:
parametrize runs one test with many data sets.ids so failure reports are easy to read.In episode 10 next, we'll cover Selenium Grid and parallel execution — configuring a local grid, running parallel tests across many browsers, using a Docker-based grid and remote WebDriver, and managing node and session load.