End-to-end testing (E2E) menguji aplikasi dari ujung ke ujung, persis seperti user: membuka browser sungguhan, mengisi form, mengklik tombol, lalu memeriksa apa yang tampil. Playwright adalah alat untuk menulis dan menjalankan test seperti itu di Chromium, Firefox, dan WebKit (mesin di balik Safari). Bayangin mystery shopper: orang yang disewa toko untuk belanja seperti pembeli biasa, dari pintu masuk sampai kasir, lalu melaporkan bagian mana yang macet. Test E2E adalah mystery shopper untuk aplikasimu.
🧪 Tebakan cepat: Sesudah tombol Masuk diklik, server butuh 1,5 detik sebelum tulisan "Selamat datang, Budi" muncul. Baris
await expect(page.getByText('Selamat datang, Budi')).toBeVisible()langsung jalan begitu kliknya selesai. Hasilnya langsung gagal, langsung lulus, atau menunggu dulu? Tebak dulu, jawabannya ada di bagian "Assertion dan auto-wait".
Test pertama: login sampai dashboard
import { test, expect } from '@playwright/test'
test('login berhasil masuk ke dashboard', async ({ page }) => {
await page.goto('/login')
await page.getByLabel('Email').fill('[email protected]')
await page.getByLabel('Password').fill('rahasia123')
await page.getByRole('button', { name: 'Masuk' }).click()
await expect(page).toHaveURL('/dashboard')
await expect(page.getByText('Selamat datang, Budi')).toBeVisible()
})
async ({ page }): Playwright memberi tiap test satu tab browser baru bernamapage.page.goto('/login')membuka halaman. Alamat pendek seperti/logindisambung kebaseURLdari config.page.getByLabel('Email')mencari input lewat teks label-nya, lalu.fill(...)mengetik isinya.page.getByRole('button', { name: 'Masuk' })mencari tombol bertuliskan Masuk, lalu.click()menekannya.await expect(page).toHaveURL('/dashboard')dantoBeVisible()memeriksa hasilnya. Semua langkah di-await, karena tiap langkah bicara dengan browser.
Menyiapkan dan menjalankan Playwright
npm init playwright@latest memasang Playwright, membuat folder tests, dan file config:
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests',
use: { baseURL: 'http://localhost:3000' },
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
})
npx playwright testmenjalankan semua test di ketiga browser.--project=chromiumcuma di satu browser.npx playwright show-reportmembuka laporan HTML, dannpx playwright codegen http://localhost:3000merekam klikmu jadi kode test.- Dengan
--reporter=list, tiap test jadi satu baris:✓ 1 [chromium] › tests/login.spec.ts:3:5 › login berhasil masuk ke dashboard (2.4s), lalu ringkasan1 passed (3.8s).
Locator: cara mencari elemen
Locator adalah alamat sebuah elemen. Urutan yang disarankan, dari yang paling mirip cara user melihat halaman:
getByRole('button', { name: 'Masuk' }): peran elemen dan nama yang dibaca screen reader.getByLabel('Email'),getByPlaceholder(...),getByText('Halo'): teks yang terlihat user.getByTestId('login-button'): atributdata-testidyang sengaja dipasang untuk test.locator('.btn-primary'): selector CSS. Paling rapuh, karena class sering berubah waktu desain diganti, padahal tombolnya tetap sama di mata user.
Kalau locator-nya gak ketemu, aksinya menunggu terus sampai batas waktu test (30 detik) habis: Error: locator.click: Test timeout of 30000ms exceeded. dengan call log - waiting for locator('.btn-primary').
Aksi: goto, fill, click
await page.goto(url)membuka alamat.await locator.fill('teks')menghapus isi input lalu mengetik teks baru.await locator.click()mengklik. Sebelum mengklik, Playwright menunggu elemennya ada, terlihat, aktif, dan gak bergerak.- Bentuk lama yang masih jalan:
page.fill('#email', '...'),page.click('button[type="submit"]'), danpage.waitForSelector('.loading', { state: 'hidden' }). Sekarang yang dianjurkan locator danexpect, misalnyaawait expect(page.locator('.loading')).toBeHidden().
Assertion dan auto-wait
| Assertion | Lulus kalau |
|---|---|
await expect(page).toHaveURL('/dashboard') |
alamat halaman cocok |
await expect(locator).toBeVisible() |
elemennya ada dan terlihat |
await expect(locator).toHaveText('Dashboard') |
teks elemennya cocok |
Ini jawaban tebakannya: menunggu dulu. Assertion Playwright mengecek berulang (di 0, 100, 350, 850, 1.850 ms, lalu tiap detik) sampai cocok atau batas 5 detik habis. Teksnya muncul di 1,5 detik, jadi pengecekan di 1.850 ms lulus. Ini yang disebut auto-wait, dan bersama dukungan tiga browser bawaan, ini keunggulan Playwright dibanding Selenium. Kalau server butuh 6 detik, barulah gagal:
Running 1 test using 1 worker
✘ 1 [chromium] › tests/login.spec.ts:3:5 › login berhasil masuk ke dashboard (5.2s)
1) [chromium] › tests/login.spec.ts:3:5 › login berhasil masuk ke dashboard ──────────────────────
Error: expect(locator).toBeVisible() failed
Locator: getByText('Selamat datang, Budi')
Expected: visible
Timeout: 5000ms
Error: element(s) not found
Call log:
- Expect "toBeVisible" with timeout 5000ms
- waiting for getByText('Selamat datang, Budi')
9 |
10 | await expect(page).toHaveURL('/dashboard')
> 11 | await expect(page.getByText('Selamat datang, Budi')).toBeVisible()
| ^
12 | })
13 |
at /home/budi/toko/tests/login.spec.ts:11:56
Error Context: test-results/login-login-berhasil-masuk-ke-dashboard-chromium/error-context.md
1 failed
[chromium] › tests/login.spec.ts:3:5 › login berhasil masuk ke dashboard ───────────────────────
Di latihan: page tiruan
Sandbox latihan gak punya browser. Jadi kamu menulis objek page tiruan dengan method yang dipanggil test-nya:
const page = {
url: '',
async goto(target) { this.url = target },
async click(selector) { if (selector.includes('login')) { this.url = '/dashboard' } },
async waitForURL(target) { return this.url === target },
}
it('pindah ke dashboard setelah klik login', async () => {
await page.goto('/login')
await page.click('#login-button')
await page.waitForURL('/dashboard')
expect(page.url).toBe('/dashboard')
})
Latihan pertama malah gak butuh page sama sekali: kamu menulis teks locator-nya, misalnya getLocator("role", "button") mengembalikan getByRole("button"), dan bestLocator memilih role dulu, lalu text, baru testId.
⚠️ Jebakan yang sering ketemu
- Lupa
await.page.getByRole(...).click()tanpaawaitgak ditunggu, jadi langkah berikutnya jalan duluan dan test-nya gagal secara acak. - Selector CSS untuk semuanya.
locator('.btn-primary')patah begitu class-nya diganti. Mulai darigetByRole. - Mengecek tanpa menunggu.
expect(await locator.isVisible()).toBe(true)cuma melihat sekali, saat itu juga. Pakaiawait expect(locator).toBeVisible()yang menunggu. - Menunggu dengan jeda tetap.
page.waitForTimeout(3000)bikin test lambat dan tetap bisa gagal kalau server lebih lambat. Biarkan auto-wait yang menunggu.
TL;DR: Test E2E memakai browser sungguhan seperti user. Di Playwright, test('...', async ({ page }) => { ... }) membuka satu tab: await page.goto('/login'), await page.getByLabel('Email').fill(...), await page.getByRole('button', { name: 'Masuk' }).click(), lalu await expect(page).toHaveURL('/dashboard') dan await expect(locator).toBeVisible() atau toHaveText(...). Pilih locator berurutan: role, lalu label atau teks, lalu test id, dan CSS paling akhir. Assertion dan aksi menunggu sendiri (auto-wait, 5 detik untuk expect, 30 detik untuk test), dan test bisa jalan di Chromium, Firefox dan WebKit sekaligus. Jalankan dengan npx playwright test. Di sandbox latihan, page ditiru dengan objek biasa.