Episode ini membahas implementasi Page Object Model untuk merapikan test, meng-enkapsulasi interaksi halaman dan abstraksi locator, mengorganisasi folder tests, pages, dan utils, serta membuat helper yang bisa dipakai ulang demi test yang mudah dibaca dan dipelihara.

Episode 5 sudah membuat test kalian stabil secara timing. Episode 6 menangani masalah kedua yang sama krusialnya: kerapian. Test yang semua logika menempel di satu fungsi akan sulit dibaca, sulit diubah, dan cepat rusak ketika aplikasi berubah. Solusi yang sudah teruji selama lebih dari satu dekade adalah Page Object Model (POM).
POM adalah pola di mana setiap halaman direpresentasikan oleh satu class. Class itu menyimpan locator dan method interaksi khas halaman tersebut, sementara test hanya memanggil method dengan nama yang mudah dibaca. Hasilnya: ketika struktur HTML berubah, kalian cukup memperbaiki satu tempat, bukan ratusan baris test.
Episode ini membawa kalian membangun struktur project yang menjadi tulang punggung semua episode berikutnya: folder pages, tests, dan utils yang terpisah, plus contoh implementasi POM yang bisa langsung dipakai.
Tanpa POM, test seperti ini adalah pemandangan umum:
driver.find_element("id", "email").send_keys("user@example.com")
driver.find_element("id", "password").send_keys("rahasia123")
driver.find_element("id", "submit").click()Setiap test login mengulang tiga baris locator yang sama. Jika tim mengubah id submit menjadi btn-masuk, semua test harus diubah. POM mengumpulkan locator ini di satu tempat.
Prinsip POM: satu class mewakili satu halaman atau satu komponen besar. Class berisi dua hal — atribut locator dan method interaksi. Test tidak boleh melihat find_element langsung; dia cukup memanggil method.
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class LoginPage:
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
EMAIL = (By.ID, "email")
PASSWORD = (By.ID, "password")
SUBMIT = (By.ID, "submit")
ERROR = (By.CLASS_NAME, "alert-error")
def isi_email(self, teks):
self.wait.until(EC.visibility_of_element_located(self.EMAIL)).send_keys(teks)
def isi_password(self, teks):
self.driver.find_element(*self.PASSWORD).send_keys(teks)
def klik_masuk(self):
self.wait.until(EC.element_to_be_clickable(self.SUBMIT)).click()
def pesan_error(self):
return self.driver.find_element(*self.ERROR).textPerhatikan self.EMAIL = (By.ID, "email") — locator ditulis sebagai tuple dan dipakai ulang dengan *self.EMAIL. Ini membuat seluruh locator halaman terpusat di atas class, mudah diubah bila struktur HTML berubah.
from selenium import webdriver
from pages.login_page import LoginPage
def test_login_gagal():
driver = webdriver.Chrome()
try:
driver.get("https://app.example.com/login")
halaman = LoginPage(driver)
halaman.isi_email("salah@example.com")
halaman.isi_password("salah")
halaman.klik_masuk()
assert "Email atau password salah" in halaman.pesan_error()
finally:
driver.quit()Test test_login_gagal(driver) di atas tidak lagi berisi locator. Alur test menjadi narasi: isi email, isi password, klik masuk, cek error. Ini adalah tujuan utama POM — test terbaca seperti dokumen bisnis.
Struktur yang kita gunakan sejak episode 2 kini diisi dengan POM:
belajar-selenium/
├── conftest.py -> fixture driver bersama
├── pages/ -> satu file per halaman
│ ├── __init__.py
│ ├── login_page.py
│ └── dashboard_page.py
├── tests/ -> file test pytest
│ ├── test_login.py
│ └── test_dashboard.py
├── utils/ -> helper dan wrapper
├── data/ -> data test (episode 9)
└── requirements.txt -> dependencyAturan singkatnya: file pages/*_page.py hanya berisi locator dan interaksi, tests/ hanya berisi skenario, dan utils/ berisi kode yang dipakai lintas halaman.
Helper wait disimpan di utils agar konsisten:
from selenium.webdriver.support.ui import WebDriverWait
def tunggu_klik(driver, locator, timeout=10):
return WebDriverWait(driver, timeout).until(
lambda d: d.find_element(*locator)
)Fungsi tunggu_klik(driver, locator, timeout=10) dipakai oleh semua page object. Dengan cara ini, perubahan kebijakan wait cukup dilakukan satu kali di satu file.
Method page object sebaiknya mengembalikan objek halaman berikutnya, sehingga test bisa dirantai:
from pages.dashboard_page import DashboardPage
class LoginPage:
def login_berhasil(self, email, password):
self.isi_email(email)
self.isi_password(password)
self.klik_masuk()
return DashboardPage(self.driver)Dengan login_berhasil(...) yang mengembalikan DashboardPage, test menulis dashboard = halaman.login_berhasil(...) dan langsung melanjutkan interaksi di halaman baru. Pola ini menjaga alur test tetap linear dan mudah dibaca.
Tip
Jangan membuat page object menjadi terlalu besar. Jika sebuah halaman punya banyak bagian, pecah menjadi komponen terpisah — misalnya NavbarComponent — lalu gunakan dari page object utama.
Episode 6 mengubah cara kalian menulis test: dari script linear yang menumpuk locator menjadi arsitektur halaman yang rapi. Kalian memahami mengapa POM dibutuhkan, bagaimana membuat class page object, bagaimana mengorganisasi folder pages, tests, dan utils, serta bagaimana merancang method yang bisa dirantai.
Inti yang harus dibawa pulang:
find_element langsung.Di episode 7 selanjutnya kita akan membahas assertions dan test validation — memakai assertion library, memvalidasi state halaman, teks, visibilitas elemen, dan perilaku, menyusun dynamic assertions untuk UI responsif, serta menambahkan error reporting dan screenshot otomatis saat test gagal.