Page Object
Mengumpulkan locator dan aksi ke dalam class, supaya test rapi dan gampang dirawat.
Page Object mengumpulkan locator dan aksi sebuah halaman dalam satu class. Test cukup memanggil login_page.login("customer", "rahasia123"), sementara detail locator-nya disimpan di class.
Page Object mirip dokumen "daftar elemen per halaman" yang dipakai tim QA. Kalau tombol Masuk berganti nama, cukup ubah di satu dokumen itu, bukan di puluhan test case.
Kenapa butuh POM?
Misalnya 40 test login dengan cara seperti ini:
page.get_by_label("Username").fill("customer") # isi Username
page.get_by_label("Password").fill("rahasia123") # isi Password
page.get_by_role("button", name="Masuk").click() # klik Masukawait page.getByLabel('Username').fill('customer'); // isi Username
await page.getByLabel('Password').fill('rahasia123'); // isi Password
await page.getByRole('button', { name: 'Masuk' }).click(); // klik MasukKalau tombol "Masuk" diganti menjadi "Login", kamu harus mengedit 40 file. Dengan POM, cukup ubah 1 baris di class LoginPage.
| Tanpa POM | Dengan POM |
|---|---|
| Locator tersebar di banyak test | Locator terkumpul di satu tempat |
| Test panjang dan teknis | Test pendek dan mudah dibaca |
| Tampilan berubah → edit banyak file | Tampilan berubah → edit satu class |
| Susah dipakai ulang | Aksi bisa dipakai di test mana saja |
Struktur folder
playwright-python/
├─ pages/
│ ├─ login_page.py ← LoginPage
│ ├─ shop_page.py ← ShopPage
│ └─ checkout_page.py ← CheckoutPage
└─ tests/
├─ conftest.py ← fixture yang membuat page object
└─ academy/test_m10_pom.pySupaya from pages.login_page import LoginPage bisa dipakai dari folder tests, tambahkan pythonpath = ["."] di bagian [tool.pytest.ini_options] pada pyproject.toml.
playwright-typescript/
├─ pages/
│ ├─ LoginPage.ts
│ ├─ ShopPage.ts
│ └─ CheckoutPage.ts
├─ fixtures.ts ← custom fixture yang membuat page object
└─ tests/academy/
└─ m10-pom.spec.tsPage object pertamamu: LoginPage
Memuat pages/login_page.py…
Memuat pages/LoginPage.ts…
Isi sebuah page object biasanya ada tiga bagian:
- Constructor menerima
pagedan menyiapkan semua locator. Aman, karena locator baru mencari elemen saat dipakai (Modul 4). - Method menggambarkan apa yang dilakukan pengguna:
open(),login(), bukanklik_tombol_biru(). - Assertion utama tetap di test. Page object cukup menyediakan locator (misalnya
error) untuk di-expect.
Page object yang lebih lengkap: ShopPage
Memuat pages/shop_page.py…
Memuat pages/ShopPage.ts…
Method product(name) mengembalikan locator, bukan langsung mengklik, jadi test bebas memakainya, misalnya mengecek tombol yang tidak aktif di kartu "Tas Laptop".
Lihat juga CheckoutPage
Memuat pages/checkout_page.py…
Memuat pages/CheckoutPage.ts…
Menyiapkan page object lewat fixture
Supaya test tidak menulis LoginPage(page) berkali-kali, siapkan lewat fixture:
Memuat tests/conftest.py…
Memuat fixtures.ts…
Fixture logged_in_shop memakai dua fixture lain (login_page, shop_page) untuk login lewat UI, lalu memberikan ShopPage yang siap dipakai. Jadi test yang butuh kondisi "sudah login" tinggal meminta logged_in_shop.
test.extend<Fixtures>({...}) membuat versi test baru yang punya fixture tambahan. Jadi test meng-import test dan expect dari fixtures.ts, bukan dari @playwright/test. Fixture loggedInShop memakai fixture lain (loginPage, shopPage) untuk login, lalu memberikan ShopPage yang siap dipakai.
Hasilnya: test yang enak dibaca
Memuat tests/academy/test_m10_pom.py…
uv run pytest tests/academy/test_m10_pom.py -vMemuat tests/academy/m10-pom.spec.ts…
npx playwright test m10-pom --project=chromiumDibanding test login di Modul 7, test ini bisa dibaca orang yang tidak paham kode.
Kebiasaan baik saat memakai POM
| Lakukan | Hindari |
|---|---|
| Satu class per halaman (atau per bagian besar) | Satu class raksasa untuk seluruh aplikasi |
Nama method sesuai keinginan pengguna (add_to_cart) | Nama teknis (click_button_3) |
| Locator disiapkan sekali di constructor | Locator ditulis ulang di setiap method |
| Method kecil yang bisa digabung-gabung | Satu method "do_everything()" yang panjang |
| Assertion utama ada di test | expect tersembunyi di setiap method |
| Bagian yang muncul di banyak halaman (header, keranjang) jadi class sendiri | Menyalin locator header ke setiap page |
Bagian yang muncul di banyak halaman, misalnya header dan panel keranjang, bisa jadi class sendiri (CartPanel) lalu dipasang di page object: self.cart = CartPanel(page). Cara ini disebut komposisi.
Untuk project dengan 5 test, POM lengkap bisa berlebihan. Mulai sederhana, lalu pindahkan ke page object begitu kode yang sama mulai berulang.
Lebih jauh (opsional)Trik komunitas
- Pakai fixture, bukan warisan bertingkat. Banyak tim lebih suka menyiapkan page object lewat fixture daripada membuat
BasePageyang diwariskan berlapis-lapis dan susah diikuti. - Pecah jadi komponen kecil:
Header,CartPanel,Modal. Page object tinggal menyusunnya. - Pakai
describe()di constructor supaya setiap locator punya nama yang jelas saat dilihat di trace. mergeTests()(TypeScript) menggabungkan beberapa file fixture, misalnya fixture UI dan fixture API, menjadi satutest.- Siapkan kondisi lewat API (Modul 13), lalu pakai page object hanya untuk bagian yang memang diuji lewat UI. Test jadi lebih cepat dan stabil.
Latihan
Buat page object untuk formulir Arena
- Buat
ArenaFormPage(Python:pages/arena_form_page.py, TypeScript:pages/ArenaFormPage.ts) dengan locator nama, bahasa, newsletter, tombol kirim, dan hasil. - Tambahkan method
fill_form(nama, level, bahasa, newsletter)/fillForm(...)dansubmit(). - Daftarkan fixture-nya di
tests/conftest.py/fixtures.ts. - Tulis 2 test yang memakai fixture itu: satu tanpa newsletter, satu dengan newsletter.
Hasilkedua test lulus, dan test-nya tidak berisi locator sama sekali
Page object-nya:
Memuat pages/arena_form_page.py…
Memuat pages/ArenaFormPage.ts…
Fixture-nya:
Memuat tests/conftest.py…
Memuat fixtures.ts…
Test yang memakainya:
Memuat tests/latihan/test_m10_latihan.py…
Memuat tests/latihan/m10-latihan.spec.ts…
Page object untuk KlinikQA
Brief: booking di KlinikQA punya tiga langkah: pilih dokter, pilih jadwal dari kalender, lalu konfirmasi dengan keluhan. Page object menyembunyikannya di balik satu method.
- Buat
KlinikPagedengan methodlogin(...),choose_doctor(...)/chooseDoctor(...),choose_schedule(tanggal, jam)/chooseSchedule(...),book(dokter, tanggal, jam, keluhan)yang memakai keduanya, danappointment(nomor)untuk mencari janji di halaman Janji temu saya. - Tulis test: login sebagai pasien, buat janji temu dengan dr. Sari Lestari jam 10:30, lalu buka Janji temu saya dan pastikan janji itu berstatus Terjadwal.
Hasilnomor janji temu berawalan KQA-, antrean U-06 (jam 10:30 adalah slot ke-6), dan statusnya Terjadwal
Memuat pages/klinik_page.py…
Memuat pages/KlinikPage.ts…
Memuat tests/latihan/test_m10_latihan.py…
Memuat tests/latihan/m10-latihan.spec.ts…
Fungsi next_open_day() / nextOpenDay() memilih tanggal yang bukan hari Minggu, karena klinik tutup hari Minggu. Dengan begitu test tetap lulus kapan pun dijalankan.
Perhatikan method day(): tombol tanggal di kalender dicari lewat atribut data-date (format YYYY-MM-DD), bukan lewat teks "Kamis, 8 Oktober 2026". Ini contoh kapan locator CSS justru pilihan yang paling stabil: test tidak perlu menyusun nama hari dan bulan dalam bahasa Indonesia.