Playwright Academy
0 dari 17
Fase 3 · Dunia NyataModul 9 dari 17

Dialog & iframe

Dialog, iframe, tab baru, download, modal buatan aplikasi, dan jam browser dengan page.clock.

Modul ini membahas situasi yang butuh cara khusus: dialog browser, iframe, tab baru, download, tabel yang berubah, dan tanda loading. Semuanya ada di Arena, jurus 4, 5, 6, 8, 10, dan 11.

Di dunia QA

Ini kasus yang sering membuat tester manual juga berhenti sejenak: pop-up konfirmasi, form di dalam form, link yang membuka tab baru, atau file yang diunduh. Playwright punya cara khusus untuk masing-masing.

1. Dialog browser: alert, confirm, prompt

Dialog bawaan browser (alert(), confirm(), prompt()) bukan bagian dari halaman, jadi tidak bisa dicari dengan locator. Playwright memberi tahu kemunculannya lewat event dialog.

Dua aturan penting soal dialog
  1. Pasang handler sebelum aksi yang memunculkan dialog.
  2. Tanpa handler, Playwright otomatis menolak dialognya. Untuk confirm(), itu sama dengan menekan "Batal".
# Terima alert / confirm
page.once("dialog", lambda dialog: dialog.accept())
page.get_by_role("button", name="Tampilkan confirm").click()  # munculkan confirm
expect(page.get_by_test_id("dialog-result")).to_have_text("Kamu memilih: OK")  # confirm diterima (OK)

# Isi prompt dan periksa pesannya
def jawab(dialog):
    assert dialog.type == "prompt"  # jenis dialognya prompt
    assert dialog.message == "Siapa namamu?"  # teks pertanyaannya
    dialog.accept("Rina")  # isi jawaban lalu tekan OK

page.once("dialog", jawab)  # pakai fungsi jawab untuk dialog berikutnya
page.get_by_role("button", name="Tampilkan prompt").click()  # munculkan prompt
expect(page.get_by_test_id("dialog-result")).to_have_text("Halo, Rina!")  # halaman memakai jawaban tadi
// Terima alert / confirm
page.once('dialog', (dialog) => dialog.accept());
await page.getByRole('button', { name: 'Tampilkan confirm' }).click(); // munculkan confirm
await expect(page.getByTestId('dialog-result')).toHaveText('Kamu memilih: OK'); // confirm diterima (OK)

// Isi prompt dan periksa pesannya
page.once('dialog', async (dialog) => {
  expect(dialog.type()).toBe('prompt');
  expect(dialog.message()).toBe('Siapa namamu?'); // teks pertanyaannya
  await dialog.accept('Rina'); // isi jawaban lalu tekan OK
});
await page.getByRole('button', { name: 'Tampilkan prompt' }).click(); // munculkan prompt
await expect(page.getByTestId('dialog-result')).toHaveText('Halo, Rina!'); // halaman memakai jawaban tadi
  • page.once menangani dialog berikutnya saja. page.on menangani semua dialog sampai dimatikan dengan page.remove_listener / page.off. Lebih aman pakai once.
  • Pop-up buatan aplikasi (misalnya <div role="dialog">) adalah bagian dari halaman. Cari dengan get_by_role("dialog"), tanpa event.

2. iframe: halaman di dalam halaman

iframe adalah halaman lain yang ditempel di dalam halaman, misalnya form pembayaran atau kotak chat. Locator biasa tidak masuk ke iframe, jadi masuki iframe-nya dulu.

frame = page.frame_locator('iframe[title="Formulir newsletter"]')  # masuk ke dalam iframe lewat judulnya
frame.get_by_label("Email").fill("rina@contoh.com")  # isi Email di dalam iframe
frame.get_by_role("button", name="Daftar").click()  # klik Daftar di dalam iframe
expect(frame.get_by_role("status")).to_have_text("Terdaftar: rina@contoh.com")  # konfirmasi muncul di dalam iframe

# Alternatif yang lebih baru: dari locator elemen iframe → content_frame
frame = page.get_by_title("Formulir newsletter").content_frame
const frame = page.frameLocator('iframe[title="Formulir newsletter"]'); // masuk ke dalam iframe lewat judulnya
await frame.getByLabel('Email').fill('rina@contoh.com'); // isi Email di dalam iframe
await frame.getByRole('button', { name: 'Daftar' }).click(); // klik Daftar di dalam iframe
await expect(frame.getByRole('status')).toHaveText('Terdaftar: rina@contoh.com'); // konfirmasi muncul di dalam iframe

// Alternatif yang lebih baru: dari locator elemen iframe → contentFrame()
const frame2 = page.getByTitle('Formulir newsletter').contentFrame();

3. Tab baru dan jendela pop-up

Link dengan target="_blank" membuka tab baru, yaitu Page baru. Mulai menunggu tab baru, klik linknya, lalu ambil tab-nya.

with page.context.expect_page() as info:  # tunggu tab baru...
    page.get_by_role("link", name="Buka pusat bantuan").click()  # ...setelah link ini diklik
tab_baru = info.value  # tab barunya

expect(tab_baru).to_have_title("Pusat Bantuan — Arena")  # judul tab baru
tab_baru.close()   # kembali bekerja dengan `page` seperti biasa
const tabBaruPromise = page.context().waitForEvent('page');   // JANGAN di-await dulu
await page.getByRole('link', { name: 'Buka pusat bantuan' }).click(); // klik link yang membuka tab baru
const tabBaru = await tabBaruPromise; // tab barunya

await expect(tabBaru).toHaveTitle('Pusat Bantuan — Arena'); // judul tab baru
await tabBaru.close();   // kembali bekerja dengan `page` seperti biasa

Pola with page.expect_xxx() as info: dipakai untuk tab baru, pop-up, download, respons server, dan file chooser: mulai menunggu, jalankan aksi di dalam blok, lalu ambil hasilnya dari info.value.

Pola "siapkan promise, lakukan aksi, lalu await hasilnya" dipakai untuk tab baru, pop-up, download, respons server, dan file chooser. Kalau waitForEvent di-await sebelum klik, test menunggu selamanya karena kliknya tidak pernah terjadi.

4. Download file

with page.expect_download() as info:  # tunggu unduhan...
    page.get_by_role("link", name="Unduh laporan").click()  # ...setelah link ini diklik
download = info.value  # unduhannya

assert download.suggested_filename == "laporan.csv"  # nama file yang disarankan server
download.save_as(tmp_path / download.suggested_filename)   # tmp_path = fixture folder sementara pytest
const downloadPromise = page.waitForEvent('download'); // mulai menunggu unduhan
await page.getByRole('link', { name: 'Unduh laporan' }).click(); // klik link unduhan
const download = await downloadPromise; // unduhannya

expect(download.suggestedFilename()).toBe('laporan.csv'); // nama file yang disarankan server
await download.saveAs(testInfo.outputPath(download.suggestedFilename())); // folder output milik test

Setelah disimpan, isi file bisa dibaca dan diperiksa.

5. Tabel dan daftar yang berubah-ubah

baris = page.locator("#students").get_by_role("row")  # semua baris tabel siswa
expect(baris).to_have_count(5)  # ada 5 baris

# Baris tertentu berdasarkan isi
expect(baris.filter(has_text="Remedial")).to_have_count(2)

# Setelah diurutkan, cek urutan kolom pertama
page.get_by_role("button", name="Urutkan nilai tertinggi").click()
expect(page.locator("#students tr td:first-child")).to_have_text(["Citra", "Dewi", "Andi", "Eko", "Budi"])  # isi kolom nama, sesuai urutan

# Ambil teks semua sel untuk diolah sendiri (tidak menunggu, jadi pastikan tabel sudah siap)
nama = page.locator("#students tr td:first-child").all_inner_texts()
const baris = page.locator('#students').getByRole('row'); // semua baris tabel siswa
await expect(baris).toHaveCount(5); // ada 5 baris

// Baris tertentu berdasarkan isi
await expect(baris.filter({ hasText: 'Remedial' })).toHaveCount(2);

// Setelah diurutkan, cek urutan kolom pertama
await page.getByRole('button', { name: 'Urutkan nilai tertinggi' }).click();
await expect(page.locator('#students tr td:first-child')).toHaveText(['Citra', 'Dewi', 'Andi', 'Eko', 'Budi']); // isi kolom nama, sesuai urutan

// Ambil teks semua sel untuk diolah sendiri (tidak menunggu, jadi pastikan tabel sudah siap)
const nama = await page.locator('#students tr td:first-child').allInnerTexts();

Daftar di to_have_text/toHaveText mengecek jumlah dan urutan sekaligus, sambil menunggu. Lebih aman daripada all_inner_texts() lalu dicek sendiri.

6. Tanda loading

page.get_by_role("button", name="Muat data").click()  # data muncul 1,5 detik kemudian
expect(page.get_by_text("Memuat...")).to_be_hidden()          # tunggu loading selesai
expect(page.get_by_test_id("loaded-list").get_by_role("listitem")).to_have_count(3)  # expect menunggu sampai 3 butir data muncul
await page.getByRole('button', { name: 'Muat data' }).click(); // data muncul 1,5 detik kemudian
await expect(page.getByText('Memuat...')).toBeHidden();       // tunggu loading selesai
await expect(page.getByTestId('loaded-list').getByRole('listitem')).toHaveCount(3); // expect menunggu sampai 3 butir data muncul

Komponen di dalam Shadow DOM tetap bisa dicari dengan locator role, text, label, dan CSS. Pengecualiannya XPath dan shadow root dengan mode closed.

File asli Modul 9

tests/academy/test_m09_tricky.pytests/academy/m09-tricky.spec.tsLulus test
Memuat tests/academy/test_m09_tricky.py…
uv run pytest tests/academy/test_m09_tricky.py
Memuat tests/academy/m09-tricky.spec.ts…
npx playwright test m09-tricky --project=chromium
Lebih jauh (opsional)Trik komunitas
  • page.clock (v1.45+) bisa mengatur waktu di halaman: melompati hitung mundur, menguji sesi yang habis, atau membekukan jam, tanpa menunggu sungguhan.
  • page.route_web_socket() (v1.48+) meng-intercept koneksi realtime, misalnya untuk fitur chat dan notifikasi.
  • page.console_messages() dan page.page_errors() (v1.56+) mengambil catatan pesan dan error di halaman, tanpa harus memasang listener dari awal.
  • Pop-up dari window.open: page.expect_popup() / page.waitForEvent('popup') lebih tepat daripada menunggu sembarang tab baru.
page.clock.install()  # ambil alih jam browser
page.goto("/arena.html")  # buka halaman SETELAH jam dipasang
page.clock.fast_forward("00:03")   # lompat 3 detik, tanpa menunggu
expect(page.get_by_role("button", name="Proses")).to_be_enabled()  # tombol aktif setelah jam dimajukan
print(page.page_errors())          # daftar error JavaScript di halaman
await page.clock.install(); // ambil alih jam browser
await page.goto('/arena.html'); // buka halaman SETELAH jam dipasang
await page.clock.fastForward('00:03'); // lompat 3 detik, tanpa menunggu
await expect(page.getByRole('button', { name: 'Proses' })).toBeEnabled(); // tombol aktif setelah jam dimajukan
console.log(await page.pageErrors()); // daftar error JavaScript di halaman

Latihan

Latihan KlinikQA dan PerpusQA dipasangkan untuk membedakan dialog bawaan browser dengan modal buatan aplikasi. Latihan terakhir memakai page.clock untuk menguji jatuh tempo tanpa menunggu berhari-hari.

Latihan 1

Batalkan dialog prompt

Pasang handler yang membatalkan (dismiss), tekan "Tampilkan prompt" di Arena, lalu cek teks hasilnya.

HasilPrompt dibatalkan

Latihan 2

Email salah di dalam iframe

Di iframe "Formulir newsletter", isi email tanpa @, tekan Daftar, lalu cek pesan statusnya.

HasilEmail tidak valid

Latihan 3

Buka tab baru, lalu tutup lagi

Tangkap tab baru saat mengklik "Buka pusat bantuan", pastikan ada 2 tab, tutup tab baru, lalu pastikan tinggal 1 tab dan Arena masih terlihat.

Hasiljumlah tab 2, lalu 1

Latihan 4

Periksa isi file unduhan

Unduh laporan.csv dari Arena, simpan, lalu hitung barisnya dan cek baris pertamanya.

Hasil3 baris, baris pertama id,produk,terjual

Latihan 5

Terima dialog konfirmasi di KlinikQA

Brief: di KlinikQA, tombol Batalkan janji temu memunculkan dialog confirm() bawaan browser.

  1. Login sebagai pasien / sehat123.
  2. Siapkan satu janji temu (lewat UI atau lewat page.request), lalu buka halaman detailnya: /klinik-detail.html?id=<nomor janji>.
  3. Tekan Batalkan janji temu, lalu terima dialognya.
  4. Pastikan statusnya berubah.

Hasilstatus janji temu menjadi Dibatalkan

Latihan 6

Modal buatan sendiri di PerpusQA

Brief: di meja pustakawan PerpusQA, menerima pengembalian buku memakai modal buatan aplikasi (role="dialog"), bukan dialog bawaan browser.

  1. Siapkan data lewat page.request: buka perpustakaan baru (username acak), daftarkan satu anggota, lalu login sebagai anggota itu dan pinjam buku "Atomic Habits" (bookId 13).
  2. Login di halaman PerpusQA sebagai pustakawan perpustakaan tadi.
  3. Tekan Terima pada pinjaman tadi, temukan modalnya dengan get_by_role("dialog"), lalu tekan Terima buku.
  4. Pastikan pinjaman itu hilang dari daftar "Sedang dipinjam".

Hasilmodal menyebut Tepat waktu, tanpa denda, lalu tertutup, dan pinjaman itu hilang dari daftar

Latihan 7

Lompati waktu dengan page.clock

Brief: masa pinjam di PerpusQA 7 hari, dan statusnya dihitung dari jam browser. Majukan jam browser dengan page.clock.

  1. Siapkan anggota baru di perpustakaan sendiri (lewat page.request atau lewat meja pustakawan), login sebagai anggota itu, lalu pinjam buku "Clean Code" dari katalog. Buka halaman Pinjaman saya (/perpus-pinjaman.html) dan pastikan statusnya Sisa 7 hari.
  2. Bekukan jam browser tepat 7 hari lagi dengan page.clock.set_fixed_time() / page.clock.setFixedTime(), lalu muat ulang halaman.
  3. Ulangi untuk 9 hari lagi.

Hasilstatus menjadi Jatuh tempo hari ini, lalu Terlambat 2 hari, denda Rp 2.000