Locator
Cara menemukan elemen: getByRole, getByLabel, getByText, test id, CSS, filter, nth, dan strict mode.
Locator yang baik menunjuk elemen berdasarkan apa elemen itu di mata pengguna, bukan urutan atau posisinya di kode halaman.
Saat menulis langkah test case, kamu menulis "klik tombol Masuk", bukan "klik kotak ketiga dari kiri". Locator yang baik ditulis dengan cara yang sama: menyebut elemen seperti pengguna melihatnya.
Apa itu locator?
Locator adalah petunjuk untuk menemukan elemen di halaman, misalnya tombol, kolom isian, atau judul. Tiga sifatnya:
- Tidak langsung mencari. Elemen baru dicari saat kamu beraksi (
click,fill) atau mengecek sesuatu. - Selalu dicari ulang. Kalau halaman berubah, locator menemukan elemen yang terbaru.
- Tidak mau menebak. Kalau cocok dengan lebih dari satu elemen saat mau beraksi, Playwright berhenti dan memberi tahu.
tombol = page.get_by_role("button", name="Masuk") # belum mencari apa-apa
tombol.click() # baru dicari di sini (dan ditunggu sampai siap)const tombol = page.getByRole('button', { name: 'Masuk' }); // belum mencari apa-apa (tanpa await!)
await tombol.click(); // baru dicari di sini (dan ditunggu sampai siap)Urutan locator yang dianjurkan
Utamakan apa yang dilihat pengguna. Makin atas, makin tahan terhadap perubahan tampilan:
| # | Locator | Kapan dipakai | Contoh di TokoQA |
|---|---|---|---|
| 1 | get_by_role / getByRole | Pilihan pertama. Tombol, link, judul, checkbox, dan sejenisnya | tombol "Masuk" |
| 2 | get_by_label / getByLabel | Kolom isian yang punya label | kolom "Username" |
| 3 | get_by_placeholder / getByPlaceholder | Kolom tanpa label, tapi ada teks contoh di dalamnya | "Misalnya: mouse" |
| 4 | get_by_text / getByText | Teks biasa yang tidak bisa diklik (paragraf, keterangan) | "Stok habis" |
| 5 | get_by_alt_text, get_by_title | Gambar (dari teks alt-nya) atau elemen yang punya title | foto di halaman detail produk ("Foto Mouse Wireless") |
| 6 | get_by_test_id / getByTestId | Kalau cara di atas tidak bisa dipakai | data-testid="cart-count" |
| 7 | locator("css") / XPath | Pilihan terakhir | #students tr |
get_by_role mencari elemen seperti aplikasi pembaca layar untuk tunanetra. Kalau locator role sulit dibuat, biasanya aplikasinya kurang ramah aksesibilitas, dan itu temuan yang layak dilaporkan.
1. getByRole: locator andalan
Rumusnya: role (jenis elemennya) + name (tulisan yang terbaca oleh pengguna).
page.get_by_role("button", name="Masuk") # tombol bertuliskan Masuk
page.get_by_role("heading", name="Masuk ke TokoQA") # judul (h1 sampai h6)
page.get_by_role("link", name="Buka pusat bantuan") # tautan
page.get_by_role("checkbox", name="Berlangganan newsletter") # checkbox dengan label itu
page.get_by_role("textbox", name="Username") # kolom teks berlabel Username
page.get_by_role("button", name="Masuk", exact=True) # harus sama persis (huruf besar/kecil juga)page.getByRole('button', { name: 'Masuk' }); // tombol bertuliskan Masuk
page.getByRole('heading', { name: 'Masuk ke TokoQA' }); // judul (h1 sampai h6)
page.getByRole('link', { name: 'Buka pusat bantuan' }); // tautan
page.getByRole('checkbox', { name: 'Berlangganan newsletter' }); // checkbox dengan label itu
page.getByRole('textbox', { name: 'Username' }); // kolom teks berlabel Username
page.getByRole('button', { name: 'Masuk', exact: true }); // harus sama persis (huruf besar/kecil juga)Role yang paling sering kamu pakai:
| Role | Biasanya elemen HTML ini |
|---|---|
button | <button>, <input type="submit"> |
link | <a href="..."> |
heading | <h1> sampai <h6> (judul) |
textbox | <input type="text/email/tel/url">, <textarea>. Kolom password tidak punya role, jadi cari dengan get_by_label |
checkbox / radio | <input type="checkbox/radio"> |
combobox | <select> (dropdown) |
row / cell | <tr> / <td> (baris dan sel tabel) |
listitem | <li> (butir daftar) |
alert / status / dialog | elemen dengan role="alert" dan sejenisnya |
Secara bawaan, name dicocokkan sebagian dan tidak membedakan huruf besar/kecil. Jadi name="Masuk" juga cocok dengan tombol "Masuk Sekarang". Kalau harus persis, tambahkan exact=True / exact: true.
Locator lainnya (urutan 2 sampai 6)
page.get_by_label("Username") # input yang labelnya "Username"
page.get_by_placeholder("Misalnya: mouse") # input dengan placeholder itu
page.get_by_text("Klik kanan di sini") # elemen berisi teks itu
page.get_by_text("Apel", exact=True) # teks persis "Apel"
page.get_by_alt_text("Foto Mouse Wireless") # gambar dengan alt itu
page.get_by_test_id("cart-count") # elemen dengan data-testid="cart-count"page.getByLabel('Username'); // input yang labelnya "Username"
page.getByPlaceholder('Misalnya: mouse'); // input dengan placeholder itu
page.getByText('Klik kanan di sini'); // elemen berisi teks itu
page.getByText('Apel', { exact: true }); // teks persis "Apel"
page.getByAltText('Foto Mouse Wireless'); // gambar dengan alt itu
page.getByTestId('cart-count'); // elemen dengan data-testid="cart-count"data-testid adalah label khusus test yang ditambahkan developer. Stabil, tapi tidak terlihat pengguna, jadi test bisa lulus walaupun tombolnya tersembunyi. Karena itu test id ada di urutan bawah.
Coba sendiri: Locator Lab
Ketik locator di kotak sebelah kanan, gaya Python atau TypeScript, lalu lihat elemen mana yang diberi bingkai. Selesaikan tantangannya untuk mengumpulkan bintang.
Mempersempit pencarian
Halaman sungguhan punya banyak elemen mirip, misalnya 8 tombol "Tambah ke keranjang". Persempit pencarian: "tombol tambah di kartu Mouse Wireless".
# Chaining: cari di DALAM elemen lain
kartu_mouse = page.get_by_test_id("product-card").filter(has_text="Mouse Wireless")
kartu_mouse.get_by_role("button", name="Tambah ke keranjang").click() # tombol tambah hanya di kartu Mouse
# filter(has=...): elemen yang MEMILIKI elemen tertentu di dalamnya
kartu = page.get_by_test_id("product-card").filter(
has=page.get_by_role("heading", name="Mouse Wireless")
)
# filter(has_not_text=...): kebalikannya
tersedia = page.get_by_test_id("product-card").filter(has_not_text="Stok habis")
# Baris tabel yang mengandung "Citra" → tombol Detail di baris itu
page.get_by_role("row").filter(has_text="Citra").get_by_role("button", name="Detail").click()// Chaining: cari di DALAM elemen lain
const kartuMouse = page.getByTestId('product-card').filter({ hasText: 'Mouse Wireless' });
await kartuMouse.getByRole('button', { name: 'Tambah ke keranjang' }).click(); // tombol tambah hanya di kartu Mouse
// filter({ has }): elemen yang MEMILIKI elemen tertentu di dalamnya
const kartu = page.getByTestId('product-card').filter({
has: page.getByRole('heading', { name: 'Mouse Wireless' }),
});
// filter({ hasNotText }): kebalikannya
const tersedia = page.getByTestId('product-card').filter({ hasNotText: 'Stok habis' });
// Baris tabel yang mengandung "Citra" → tombol Detail di baris itu
await page.getByRole('row').filter({ hasText: 'Citra' }).getByRole('button', { name: 'Detail' }).click();first, last, nth: pakai seperlunya
buah = page.get_by_test_id("fruit") # locator yang cocok dengan 3 elemen
buah.first # elemen pertama
buah.last # elemen terakhir
buah.nth(1) # elemen ke-2 (index mulai dari 0!)
buah.count() # jumlah elemen yang cocokconst buah = page.getByTestId('fruit'); // locator yang cocok dengan 3 elemen
buah.first(); // elemen pertama
buah.last(); // elemen terakhir
buah.nth(1); // elemen ke-2 (index mulai dari 0!)
await buah.count(); // jumlah elemen yang cocoknth(2) artinya "yang ketiga" (hitungannya mulai dari 0). Kalau urutannya berubah, misalnya ada produk baru, test bisa mengklik barang yang salah tanpa ada error sama sekali. Lebih aman memfilter berdasarkan teks. Pakai nth hanya kalau urutannya memang yang mau diuji, misalnya "produk teratas setelah diurutkan".
Strict mode: Playwright tidak mau menebak
Di Arena ada tiga tombol yang diawali kata "Tampilkan". Kalau kamu menulis:
page.get_by_role("button", name="Tampilkan").click() # cocok dengan 3 tombol, jadi gagalawait page.getByRole('button', { name: 'Tampilkan' }).click(); // cocok dengan 3 tombol, jadi gagalPlaywright akan berhenti dan menampilkan pesan seperti ini:
Error: strict mode violation: get_by_role("button", name="Tampilkan") resolved to 3 elements:
1) <button id="btn-alert">Tampilkan alert</button> aka get_by_role("button", name="Tampilkan alert")
2) <button id="btn-confirm">Tampilkan confirm</button> aka get_by_role("button", name="Tampilkan confirm")
3) <button id="btn-prompt">Tampilkan prompt</button> aka get_by_role("button", name="Tampilkan prompt")Bagian aka berisi saran locator yang lebih tepat. Perjelas name, tambahkan filter, atau tulis .first dengan sengaja kalau memang mau yang pertama.
Contoh di PerpusQA (meja pustakawan, tab Katalog): get_by_label("Eksemplar Kosmos") cocok dengan 2 elemen, yaitu kolom jumlah dan tombol berlabel "Simpan eksemplar Kosmos". Pakai exact=True, atau sebut perannya.
page.get_by_label("Eksemplar Kosmos", exact=True) # hanya kolom jumlah
page.get_by_role("spinbutton", name="Eksemplar Kosmos") # alternatif: sebut perannyapage.getByLabel('Eksemplar Kosmos', { exact: true }); // hanya kolom jumlah
page.getByRole('spinbutton', { name: 'Eksemplar Kosmos' }); // alternatif: sebut perannyaCSS dan XPath: pilihan terakhir
page.locator("#students tr") # CSS
page.locator("button.danger") # CSS dengan class
page.locator("xpath=//table//tr[2]") # XPath (hindari kalau bisa)
page.locator("article", has_text="Mouse") # CSS + filter tekspage.locator('#students tr'); // CSS
page.locator('button.danger'); // CSS dengan class
page.locator('xpath=//table//tr[2]'); // XPath (hindari kalau bisa)
page.locator('article', { hasText: 'Mouse' }); // CSS + filter teksdiv > div:nth-child(3) > span.sc-AxjAm atau //*[@id="root"]/div[2]/div/button. Begitu developer mengubah tata letak, semua test-mu gagal padahal fiturnya baik-baik saja.
Semua contoh dalam file asli
Memuat tests/academy/test_m04_locators.py…
uv run pytest tests/academy/test_m04_locators.pyMemuat tests/academy/m04-locators.spec.ts…
npx playwright test m04-locators --project=chromiumCara cepat mendapatkan locator: jalankan codegen (Modul 8), klik Pick locator, lalu arahkan kursor ke elemen. Ekstensi Playwright di VS Code punya fitur yang sama.
Lebih jauh (opsional)Trik komunitas
describe()(v1.53+) memberi nama yang mudah dibaca pada locator. Kalau test gagal, laporannya langsung menyebut "badge jumlah keranjang", bukan kode yang panjang.- Hanya elemen yang terlihat. Python:
locator.visible(tanpa kurung). TypeScript:locator.visible(). Berguna kalau ada elemen kembar yang tersembunyi, misalnya menu versi HP dan versi desktop. - Pengingat otomatis (TypeScript): plugin
eslint-plugin-playwrightpunya aturanplaywright/no-raw-locatorsyang menegur kalau kamu memakai CSS atau XPath mentah. - Tim memakai nama atribut lain? Kalau timmu memakai
data-qa, cukup ubah sekali di konfigurasi, laluget_by_test_idakan membaca atribut itu. - Pick locator di Inspector, UI Mode, atau ekstensi VS Code tetap cara tercepat untuk mendapatkan locator terbaik.
badge = page.get_by_test_id("cart-count").describe("badge jumlah keranjang") # nama yang tampil di laporan kalau gagal
tombol = page.get_by_role("button", name="Masuk").visible # properti, tanpa kurung
# conftest.py: kalau tim memakai data-qa
# playwright.selectors.set_test_id_attribute("data-qa")const badge = page.getByTestId('cart-count').describe('badge jumlah keranjang'); // nama yang tampil di laporan kalau gagal
const tombol = page.getByRole('button', { name: 'Masuk' }).visible(); // hanya tombol yang terlihat
// playwright.config.ts: kalau tim memakai data-qa
// use: { testIdAttribute: 'data-qa' }Latihan
Tulis locator-mu sendiri dulu, jalankan, baru buka jawabannya. Urutannya: role, label, teks, test id, baru CSS.
Kumpulkan semua bintang di Locator Lab
Kerjakan semua tantangan di Locator Lab. Coba locator role atau label dulu sebelum CSS.
Hasilsemua tantangan berbintang
Locator Lab langsung memberi tahu apakah locator-mu cocok dengan tepat satu elemen. Kalau macet:
- Untuk tombol dan link, mulai dari
get_by_role("button", name=...)/getByRole('button', { name: ... }). - Untuk kolom isian, pakai
get_by_label(...)/getByLabel(...)sesuai teks label di atas kolomnya. - Kalau ada beberapa elemen yang sama, persempit dengan
filter(has_text=...)/filter({ hasText: ... }).
Locator untuk halaman toko
Setelah login sebagai customer, tulis locator untuk:
- Kolom Cari produk.
- Dropdown Kategori.
- Angka di ikon keranjang.
- Tombol tambah di kartu Headset Gaming.
Lalu buktikan semuanya bekerja: tambah Headset Gaming ke keranjang, pilih kategori Furnitur, lalu cari meja.
Hasilangka keranjang 1, Furnitur berisi 2 produk, dan pencarian meja di kategori Furnitur menyisakan 1 produk (Meja Lipat)
Memuat tests/latihan/test_m04_latihan.py…
Memuat tests/latihan/m04-latihan.spec.ts…
Pancing strict mode violation, lalu perbaiki
Di Arena, klik get_by_role("button", name="Detail") dan baca errornya. Lalu perbaiki supaya hanya mengklik Detail di baris Dewi.
Hasilpercobaan pertama gagal dengan strict mode violation (5 tombol), perbaikannya menampilkan Detail: Dewi
Memuat tests/latihan/test_m04_latihan.py…
Memuat tests/latihan/m04-latihan.spec.ts…
Cari baris tabel berdasarkan isinya
Di tabel Arena (jurus 6), hitung baris berstatus Remedial, lalu klik Detail di baris Budi. Cari baris dari isinya, bukan posisinya.
Hasil2 baris Remedial, dan Detail: Budi
Memuat tests/latihan/test_m04_latihan.py…
Memuat tests/latihan/m04-latihan.spec.ts…