Playwright Academy
0 dari 17
Fase 2 · Jurus IntiModul 6 dari 17

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.

Di dunia QA

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.

Python
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 ini
Arti setiap baris
expect(...)
Membungkus yang mau diperiksa: elemen (locator), halaman, atau respons API.
page.get_by_test_id("greeting")
Elemen sapaan di header toko, dicari lewat atribut data-testid.
.to_have_text("Halo, Rina Wulandari")
Expected result: teks elemen harus persis ini. Playwright mencoba ulang sampai 5 detik sebelum menyatakan gagal.
TypeScript
import { expect } from '@playwright/test'; // expect = pemeriksa hasil

await expect(page.getByTestId('greeting')).toHaveText('Halo, Rina Wulandari'); // sapaan harus berisi teks ini
Arti setiap baris
await expect(...)
Membungkus yang mau diperiksa: elemen (locator), halaman, atau respons API. await wajib, karena pemeriksaannya butuh waktu.
page.getByTestId('greeting')
Elemen sapaan di header toko, dicari lewat atribut data-testid.
.toHaveText('Halo, Rina Wulandari')
Expected result: teks elemen harus persis ini. Playwright mencoba ulang sampai 5 detik sebelum menyatakan gagal.

Setiap jenis assertion punya latihan di Arena Latihan, tepat setelah penjelasannya. Semua jawaban lulus test di tiga browser.

Web-first assertion: menunggu dengan pintar

Di dunia QA

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).

cek: belum muncul
cek: belum
cek: muncul, lanjut

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-waiting
await page.getByRole('button', { name: 'Muat data' }).click(); // data muncul 1,5 detik kemudian
await expect(page.getByText('Data berhasil dimuat')).toBeVisible();  // auto-waiting
Latihan 1

Terlihat, lalu hilang

Tekan Simpan di jurus 13, pastikan notifikasi "Data tersimpan" terlihat, lalu pastikan notifikasi itu hilang sendiri, tanpa sleep.

Hasilkedua assertion lulus

assert biasa vs expect

Ini jebakan yang paling sering terjadi di Python:

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 muncul

assert 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:

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 muncul

Kalau ada await di dalam kurung expect(...), assertion itu tidak menunggu. Taruh await di depan expect, dan berikan locator-nya, bukan hasilnya.

Latihan 2

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

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 mengecekPythonTypeScript
Terlihat / tersembunyito_be_visible() / to_be_hidden()toBeVisible() / toBeHidden()
Bisa diklik / tidakto_be_enabled() / to_be_disabled()toBeEnabled() / toBeDisabled()
Sudah dicentangto_be_checked()toBeChecked()
Kolom masih kosongto_be_empty()toBeEmpty()
Sedang aktif (fokus)to_be_focused()toBeFocused()
Teksnya persisto_have_text("…")toHaveText('…')
Teksnya mengandungto_contain_text("…")toContainText('…')
Isi kolomto_have_value("…")toHaveValue('…')
Atributto_have_attribute("href", "…")toHaveAttribute('href', '…')
Class CSSto_have_class(re.compile("aktif"))toHaveClass(/aktif/)
Jumlah elemento_have_count(3)toHaveCount(3)
URL halamanexpect(page).to_have_url(…)expect(page).toHaveURL(…)
Judul halamanexpect(page).to_have_title(…)expect(page).toHaveTitle(…)
Kebalikannyanot_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\.html
Memuat materi…
quot;
)) # URL berakhiran shop.html
await 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.html
Latihan 3

Teks 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

Latihan 4

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

Isi kolom, centang, dan status tombol

Untuk elemen form, yang dicek adalah keadaannya: isi, status centang, dan bisa diklik atau tidak.

Latihan 5

Isi kolom dan kolom kosong

Pastikan Nama lengkap kosong saat halaman dibuka, isi dengan Rina, lalu pastikan isinya tersimpan.

Hasilkosong, lalu Rina

Latihan 6

Status centang

Pastikan Berlangganan newsletter belum dicentang, centang, lalu pastikan statusnya berubah.

Hasiltidak dicentang, lalu dicentang

Latihan 7

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!

Jumlah, atribut, URL, dan judul

Selain teks, yang sering dicek: jumlah elemen, atribut (misalnya href), serta URL dan judul halaman.

Latihan 8

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

Latihan 9

Atribut sebuah link

Pastikan link Unduh laporan di jurus 8 punya href /download/laporan.csv dan atribut download.

Hasilkedua assertion lulus

Latihan 10

URL dan judul halaman

Buka Arena, lalu pastikan URL-nya diakhiri /arena.html dan judulnya Arena Latihan - Playground.

Hasilkedua assertion lulus

Kebalikannya: memakai not

Semua assertion punya versi kebalikannya. Python memakai awalan not_, TypeScript memakai .not. Versi kebalikan juga ikut menunggu.

Latihan 11

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

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 } });
Jangan asal menambah waktu tunggu

Kalau test baru lulus dengan timeout 30 detik, tanya dulu apakah aplikasinya memang selambat itu. Bisa jadi itu masalah kecepatan yang layak dilaporkan.

Latihan 12

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

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.

TypeScript
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 terlihat

pytest-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 Playwright
await page.waitForTimeout(3000);  // hindari: terlalu lama atau terlalu cepat

Sleep kadang terlalu lama, kadang terlalu cepat. Ganti dengan menunggu keadaannya:

SituasiGanti sleep dengan
Menunggu elemen munculexpect(locator).to_be_visible()
Menunggu tanda loading hilangexpect(spinner).to_be_hidden()
Menunggu tombol bisa diklikexpect(tombol).to_be_enabled() (atau langsung klik, karena klik juga menunggu)
Menunggu pindah halamanexpect(page).to_have_url(...) / wait_for_url
Menunggu data dari serverpage.expect_response(...) / page.waitForResponse(...) (Modul 13)

Semua contoh dalam file asli

tests/academy/test_m06_assertions.pytests/academy/m06-assertions.spec.tsLulus test
Memuat tests/academy/test_m06_assertions.py…
uv run pytest tests/academy/test_m06_assertions.py
Memuat tests/academy/m06-assertions.spec.ts…
npx playwright test m06-assertions --project=chromium
Lebih 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() dan toPass() (TypeScript) untuk menunggu sesuatu yang bukan elemen halaman, misalnya status API. Python belum punya padanannya, jadi pakai expect pada locator atau ulangi request secukupnya.
  • Assertion aksesibilitas: to_have_accessible_name dan to_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 atribut class.
  • --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