Assertion
Memastikan hasil dengan expect, dan alasan kamu (hampir) tidak pernah butuh sleep.
expect memantau keadaan halaman terus-menerus dan lanjut begitu kondisinya terpenuhi, tanpa menunggu waktu yang ditebak.
Assertion = bagian "periksa hasil"
Assertion adalah pernyataan "seharusnya begini". Kalau kenyataannya berbeda, test gagal dan menunjukkan nilai expected dan received.
Assertion adalah kolom Expected result di test case. Saat test gagal, Playwright mengisi sendiri kolom Actual result (disebut received), jadi bug report-mu langsung punya bukti.
from playwright.sync_api import expect # expect = pemeriksa hasil
expect(page.get_by_test_id("greeting")).to_have_text("Halo, Rina Wulandari") # sapaan harus berisi teks iniimport { expect } from '@playwright/test'; // expect = pemeriksa hasil
await expect(page.getByTestId('greeting')).toHaveText('Halo, Rina Wulandari'); // sapaan harus berisi teks iniSetiap jenis assertion punya latihan di Arena Latihan, tepat setelah penjelasannya. Semua jawaban lulus test di tiga browser.
Web-first assertion: menunggu dengan pintar
Bayangkan menunggu email OTP saat testing manual. Kamu tidak menyerah setelah satu detik, tapi juga tidak menunggu selamanya. Kamu mengecek berkali-kali sampai email datang atau sampai batas waktu yang wajar. Web-first assertion bekerja dengan cara yang sama.
expect(locator) tidak mengecek sekali lalu menyerah. Ia mengecek berulang kali sampai kondisinya terpenuhi, atau sampai timeout-nya habis (bawaannya 5 detik).
Di Arena, hasil tombol "Muat data" baru muncul 1,5 detik kemudian. Tidak perlu sleep:
page.get_by_role("button", name="Muat data").click() # data muncul 1,5 detik kemudian
expect(page.get_by_text("Data berhasil dimuat")).to_be_visible() # auto-waitingawait page.getByRole('button', { name: 'Muat data' }).click(); // data muncul 1,5 detik kemudian
await expect(page.getByText('Data berhasil dimuat')).toBeVisible(); // auto-waitingTerlihat, lalu hilang
Tekan Simpan di jurus 13, pastikan notifikasi "Data tersimpan" terlihat, lalu pastikan notifikasi itu hilang sendiri, tanpa sleep.
Hasilkedua assertion lulus
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Timeout bawaan 5 detik sudah cukup, karena notifikasinya hilang setelah 3 detik. timeout=5000 / timeout: 5000 ditulis supaya niatnya jelas saat dibaca. Kalau notifikasinya tampil lebih lama, naikkan angka ini.
assert biasa vs expect
Ini jebakan yang paling sering terjadi di Python:
# FLAKY: is_visible() mengecek SEKALI, saat itu juga. Kalau data belum muncul → gagal.
assert page.get_by_text("Data berhasil dimuat").is_visible() # cek sekali, langsung gagal kalau belum muncul
# STABIL: expect menunggu sampai muncul (maks 5 detik)
expect(page.get_by_text("Data berhasil dimuat")).to_be_visible() # cek berulang sampai munculassert biasa tetap boleh untuk nilai yang sudah pasti, misalnya hasil API (assert res.status == 200). Untuk apa pun yang tampil di halaman, pakai expect.
Ini jebakan yang paling sering terjadi di TypeScript:
// FLAKY: isVisible() mengecek SEKALI, saat itu juga. Kalau data belum muncul → gagal.
expect(await page.getByText('Data berhasil dimuat').isVisible()).toBe(true); // cek sekali, langsung gagal kalau belum muncul
// STABIL: web-first assertion menunggu sampai muncul (maks 5 detik)
await expect(page.getByText('Data berhasil dimuat')).toBeVisible(); // cek berulang sampai munculKalau ada await di dalam kurung expect(...), assertion itu tidak menunggu. Taruh await di depan expect, dan berikan locator-nya, bukan hasilnya.
Buktikan sendiri kenapa "cek sekali" itu rawan
Tekan Muat data, lalu langsung cek is_visible() / isVisible() dengan assert biasa. Jalankan beberapa kali, lalu ganti dengan expect(...).to_be_visible() / toBeVisible().
Hasilversi cek sekali gagal, versi expect lulus
Versi pertama gagal karena data baru muncul 1,5 detik setelah tombol ditekan, sedangkan is_visible() mengecek saat itu juga lalu langsung memberi jawaban False.
Versi kedua lulus karena expect terus mengecek ulang sampai teksnya muncul, paling lama 5 detik. Inilah alasan utama kenapa test Playwright yang baik hampir tidak pernah butuh sleep.
Daftar assertion yang paling sering dipakai
Menampilkan Python. Ganti bahasa di bagian atas untuk melihat TypeScript.Menampilkan TypeScript. Ganti bahasa di bagian atas untuk melihat Python.
| Mau mengecek | Python | TypeScript |
|---|---|---|
| Terlihat / tersembunyi | to_be_visible() / to_be_hidden() | toBeVisible() / toBeHidden() |
| Bisa diklik / tidak | to_be_enabled() / to_be_disabled() | toBeEnabled() / toBeDisabled() |
| Sudah dicentang | to_be_checked() | toBeChecked() |
| Kolom masih kosong | to_be_empty() | toBeEmpty() |
| Sedang aktif (fokus) | to_be_focused() | toBeFocused() |
| Teksnya persis | to_have_text("…") | toHaveText('…') |
| Teksnya mengandung | to_contain_text("…") | toContainText('…') |
| Isi kolom | to_have_value("…") | toHaveValue('…') |
| Atribut | to_have_attribute("href", "…") | toHaveAttribute('href', '…') |
| Class CSS | to_have_class(re.compile("aktif")) | toHaveClass(/aktif/) |
| Jumlah elemen | to_have_count(3) | toHaveCount(3) |
| URL halaman | expect(page).to_have_url(…) | expect(page).toHaveURL(…) |
| Judul halaman | expect(page).to_have_title(…) | expect(page).toHaveTitle(…) |
| Kebalikannya | not_to_be_visible() dan seterusnya | .not.toBeVisible() dan seterusnya |
Teks: persis, sebagian, pola, atau daftar
import re # alat pencocok teks (regular expression)
expect(salam).to_have_text("Halo, Rina Wulandari") # persis (spasi berlebih dinormalisasi)
expect(salam).to_contain_text("Rina") # sebagian
expect(salam).to_have_text(re.compile(r"^Halo, \w+")) # pola regex
expect(page.locator("#api-list li")).to_have_text(["Keyboard Mekanik", "Mouse Wireless"]) # urutan list
expect(page).to_have_url(re.compile(r"shop\.htmlMemuat materi…quot;)) # URL berakhiran shop.htmlawait expect(salam).toHaveText('Halo, Rina Wulandari'); // persis (spasi berlebih dinormalisasi)
await expect(salam).toContainText('Rina'); // sebagian
await expect(salam).toHaveText(/^Halo, \w+/); // pola regex
await expect(page.locator('#api-list li')).toHaveText(['Keyboard Mekanik', 'Mouse Wireless']); // urutan list
await expect(page).toHaveURL(/shop\.html$/); // URL berakhiran shop.htmlTeks persis, sebagian, dan pola
Klik dua kali Klik dua kali di Arena, lalu cek teks dbl-result dengan tiga cara: persis, mengandung terdeteksi, dan pola yang diawali Double click.
Hasilketiga assertion lulus
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Urutan isi daftar
Tekan Urutkan nilai tertinggi di tabel Arena (jurus 6), lalu cek urutan nama di kolom pertama dengan satu to_have_text([...]).
HasilCitra, Dewi, Andi, Eko, Budi
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Isi kolom, centang, dan status tombol
Untuk elemen form, yang dicek adalah keadaannya: isi, status centang, dan bisa diklik atau tidak.
Isi kolom dan kolom kosong
Pastikan Nama lengkap kosong saat halaman dibuka, isi dengan Rina, lalu pastikan isinya tersimpan.
Hasilkosong, lalu Rina
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Status centang
Pastikan Berlangganan newsletter belum dicentang, centang, lalu pastikan statusnya berubah.
Hasiltidak dicentang, lalu dicentang
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Tombol yang aktif belakangan
Tombol Proses di jurus 5 baru aktif 2 detik setelah halaman dibuka. Pastikan awalnya tidak aktif, tunggu sampai aktif, klik, lalu cek late-result.
Hasiltidak aktif, lalu aktif, lalu Diproses!
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
click() sendiri juga menunggu tombolnya aktif (Modul 5). to_be_enabled() di sini membuat niat test-nya lebih jelas.
Jumlah, atribut, URL, dan judul
Selain teks, yang sering dicek: jumlah elemen, atribut (misalnya href), serta URL dan judul halaman.
Jumlah elemen
Pastikan tabel siswa di Arena berisi 5 baris, lalu tekan Muat data dan pastikan daftarnya berisi 3 butir.
Hasil5 baris dan 3 butir
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Atribut sebuah link
Pastikan link Unduh laporan di jurus 8 punya href /download/laporan.csv dan atribut download.
Hasilkedua assertion lulus
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
URL dan judul halaman
Buka Arena, lalu pastikan URL-nya diakhiri /arena.html dan judulnya Arena Latihan - Playground.
Hasilkedua assertion lulus
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Kebalikannya: memakai not
Semua assertion punya versi kebalikannya. Python memakai awalan not_, TypeScript memakai .not. Versi kebalikan juga ikut menunggu.
Assertion kebalikan
Pastikan tooltip jurus 3 belum terlihat dan penghitung klik belum 1. Lalu klik Klik aku sekali dan pastikan penghitungnya 1.
Hasilsemua assertion lulus
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Mengatur timeout
# Satu assertion saja (toast hilang setelah 3 detik)
expect(page.get_by_text("Data tersimpan")).not_to_be_visible(timeout=6000) # tunggu sampai 6 detik untuk assertion ini saja
# Global untuk semua expect (taruh di conftest.py)
expect.set_options(timeout=10_000) # semua expect menunggu sampai 10 detik// Satu assertion saja (toast hilang setelah 3 detik)
await expect(page.getByText('Data tersimpan')).not.toBeVisible({ timeout: 6000 }); // tunggu sampai 6 detik untuk assertion ini saja
// Global: di playwright.config.ts
// export default defineConfig({ expect: { timeout: 10_000 } });Kalau test baru lulus dengan timeout 30 detik, tanya dulu apakah aplikasinya memang selambat itu. Bisa jadi itu masalah kecepatan yang layak dilaporkan.
Timeout khusus untuk satu assertion
Tekan Mulai unduh di jurus 5, lalu tunggu tulisan Selesai dengan timeout khusus 8 detik untuk assertion itu saja.
Hasiltest lulus tanpa sleep
Memuat tests/latihan/test_m06_latihan.py…
Memuat tests/latihan/m06-latihan.spec.ts…
Soft assertion: tetap lanjut walau ada yang salah
expect.soft mencatat kesalahan tanpa menghentikan test. Test tetap ditandai gagal, tapi semua masalahnya terlihat sekaligus. Cocok untuk mengecek banyak kolom di satu halaman.
await expect.soft(page).toHaveTitle('Login — TokoQA'); // dicatat kalau gagal, test tetap lanjut
await expect.soft(page.getByLabel('Username')).toBeVisible(); // kolom Username terlihat
await expect.soft(page.getByLabel('Password')).toBeVisible(); // kolom Password terlihatpytest-playwright belum punya soft assertion bawaan. Kalau butuh, pakai plugin pytest-check (uv add pytest-check). Biasanya beberapa test kecil yang fokus lebih mudah dirawat daripada satu test besar.
Kenapa sleep sebaiknya dihindari
page.wait_for_timeout(3000) # hindari: terlalu lama atau terlalu cepat
time.sleep(3) # hindari: sama buruknya, dan memblokir Playwrightawait page.waitForTimeout(3000); // hindari: terlalu lama atau terlalu cepatSleep kadang terlalu lama, kadang terlalu cepat. Ganti dengan menunggu keadaannya:
| Situasi | Ganti sleep dengan |
|---|---|
| Menunggu elemen muncul | expect(locator).to_be_visible() |
| Menunggu tanda loading hilang | expect(spinner).to_be_hidden() |
| Menunggu tombol bisa diklik | expect(tombol).to_be_enabled() (atau langsung klik, karena klik juga menunggu) |
| Menunggu pindah halaman | expect(page).to_have_url(...) / wait_for_url |
| Menunggu data dari server | page.expect_response(...) / page.waitForResponse(...) (Modul 13) |
Semua contoh dalam file asli
Memuat tests/academy/test_m06_assertions.py…
uv run pytest tests/academy/test_m06_assertions.pyMemuat tests/academy/m06-assertions.spec.ts…
npx playwright test m06-assertions --project=chromiumLebih jauh (opsional)Trik komunitas
- Aria snapshot (v1.49+) mengecek susunan satu bagian halaman sekaligus, tidak satu per satu. Pencocokannya sebagian, jadi cukup tulis bagian yang penting saja.
expect.poll()dantoPass()(TypeScript) untuk menunggu sesuatu yang bukan elemen halaman, misalnya status API. Python belum punya padanannya, jadi pakaiexpectpada locator atau ulangi request secukupnya.- Assertion aksesibilitas:
to_have_accessible_namedanto_have_role(v1.44+) memastikan halaman juga ramah untuk pengguna pembaca layar. Untuk class CSS,to_contain_class(v1.52+) mengecek satu nama class tanpa harus mencocokkan seluruh atributclass. --fail-on-flaky-tests(TypeScript) di CI: test yang baru lulus setelah diulang dianggap gagal, jadi test yang flaky tidak tersembunyi.
expect(page.locator("#login-form")).to_match_aria_snapshot(""" # struktur form harus seperti di bawah ini
- textbox "Username"
- textbox "Password"
- button "Masuk"
""")await expect(page.locator('#login-form')).toMatchAriaSnapshot(` // struktur form harus seperti di bawah ini
- textbox "Username"
- textbox "Password"
- button "Masuk"
`);
// Menunggu nilai non-DOM sampai sesuai
await expect.poll(async () => (await request.get('/api/health')).status()).toBe(200); // ulangi request sampai statusnya 200