Kenapa ini penting
Unit test seperti memeriksa setiap komponen mobil di bengkel: mesin dites di meja, rem dites terpisah, lampu dites terpisah. Semuanya lulus. Tapi apakah mobilnya bisa dibawa jalan dari rumah ke kantor? Belum tentu. Bisa saja kabel lampu rem tidak tersambung ke pedal. Test end-to-end (E2E) adalah test drive: aplikasi dijalankan utuh di browser sungguhan, lalu robot berperan sebagai pengguna yang membuka halaman, mengetik, mengklik, dan memeriksa hasilnya.
E2E menangkap masalah yang tidak terlihat di unit test atau jsdom: CSS yang menutupi tombol sehingga tidak bisa diklik, file JavaScript yang gagal dimuat setelah build, perbedaan perilaku antar-browser, dan alur yang melibatkan banyak halaman. Di modul ini kita memakai Playwright (versi 1.64), alat E2E dari Microsoft yang bisa mengendalikan tiga mesin browser utama, yaitu Chromium (Chrome, Edge), Firefox, dan WebKit (mesin Safari), dengan satu API. Ini langsung menjawab kebutuhan compatibility testing di MDN Curriculum.
Konsep inti
Instalasi
Cara resmi adalah npm init playwright@latest. Perintah ini menanyakan bahasa (JavaScript/TypeScript), nama folder test, apakah perlu workflow GitHub Actions, dan apakah browser langsung diunduh. Hasilnya: file playwright.config (.js atau .ts), folder test dengan satu contoh, dan dependensi @playwright/test. Playwright mendukung Node.js 22.x, 24.x, dan 26.x terbaru.
Cara manual (yang kita pakai, agar setiap langkah terlihat):
npm install -D @playwright/test
npx playwright install # unduh Chromium, Firefox, WebKit (beberapa ratus MB)
# atau hanya satu: npx playwright install chromium
Konsep kunci Playwright
| Konsep | Penjelasan |
|---|---|
page |
Satu tab browser. Disediakan otomatis sebagai fixture di setiap test |
| Locator | Cara menemukan elemen, misalnya page.getByRole('button', { name: 'Daftar' }), page.getByLabel('Email'), page.getByText('Berhasil'). Mirip Testing Library, dan juga mengutamakan role & label |
| Aksi | click(), fill(), press('Enter'), check(), selectOption() |
| Auto-waiting | Sebelum mengklik, Playwright otomatis menunggu elemen muncul, terlihat, stabil, dan bisa diterima. Kamu hampir tidak pernah perlu sleep |
| Web-first assertion | await expect(locator).toBeVisible() mengulang pengecekan sampai lulus atau timeout (default 5 detik). Cocok untuk UI yang berubah asinkron |
| Project | Satu konfigurasi browser/perangkat. Satu file test bisa dijalankan di banyak project sekaligus |
webServer |
Playwright menyalakan server aplikasimu sebelum test dan mematikannya setelah selesai |
Daftar assertion yang sering dipakai: toBeVisible(), toHaveText(), toContainText(), toHaveValue(), toBeFocused(), toHaveTitle(), toHaveURL(), toHaveScreenshot().
Cross-browser dan perangkat
Di konfigurasi, objek devices menyediakan preset siap pakai, seperti devices['Desktop Chrome'], devices['Desktop Safari'], devices['Pixel 7'], dan devices['iPhone 15'], lengkap dengan ukuran layar, user agent, dan dukungan sentuh. Saat kami menjalankan test keyboard di tiga browser, Chromium dan Firefox lulus tapi WebKit gagal: di Safari, secara bawaan tombol Tab tidak berhenti di link (pengguna harus menekan Option+Tab atau mengubah pengaturan). Itulah contoh perbedaan yang hanya bisa ditemukan dengan menguji di mesin browser sungguhan.
Screenshot
Ada dua jenis:
- Screenshot biasa dengan
page.screenshot({ path, fullPage: true })untuk dokumentasi, laporan, atau melihat tampilan di berbagai perangkat. Konfigurasiscreenshot: 'only-on-failure'otomatis menyimpan screenshot saat test gagal. - Visual regression dengan
await expect(page).toHaveScreenshot('beranda.png'). Saat pertama dijalankan, test gagal karena belum ada pembanding, dan Playwright menyimpan gambar acuan di folder*-snapshots. Pada run berikutnya, tampilan dibandingkan per piksel. Kalau perubahan memang disengaja, perbarui acuan dengan--update-snapshots. Gambar acuan bergantung pada OS dan browser (nama filenya pun memuat keduanya, misalnyaberanda-chromium-darwin.pngdi macOS), jadi buat dan bandingkan di lingkungan yang sama (misalnya di CI).
Debugging
npx playwright test --headed: lihat browser bergerak.npx playwright test --ui: UI Mode, antarmuka interaktif dengan timeline setiap langkah.npx playwright show-report: laporan HTML setelah test.npx playwright codegen http://localhost:5173: rekam klik-klikmu dan Playwright menuliskan kodenya (bagus untuk belajar locator).trace: 'on-first-retry': rekam trace (DOM, network, console per langkah) saat test diulang setelah gagal.
Contoh kode
Konfigurasi yang membangun aplikasi, menyalakan vite preview, lalu menjalankan test di empat project:
// playwright.config.js
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
use: {
baseURL: 'http://localhost:4173', // jadi cukup page.goto('/')
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'hp', use: { ...devices['Pixel 7'] } },
],
webServer: {
// Uji hasil build (yang akan di-deploy), bukan dev server
command: 'npm run build && npm run preview -- --port 4173 --strictPort',
url: 'http://localhost:4173',
reuseExistingServer: !process.env.CI,
},
});
Test alur pengguna, termasuk keyboard dan audit aksesibilitas otomatis dengan axe:
npm install -D @axe-core/playwright
// e2e/newsletter.spec.js
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('judul halaman benar', async ({ page }) => {
await expect(page).toHaveTitle('Daftar Newsletter');
await expect(page.getByRole('heading', { level: 1 })).toHaveText('Daftar Newsletter');
});
test('email salah memunculkan pesan error', async ({ page }) => {
await page.getByLabel('Email').fill('budi@');
await page.getByRole('button', { name: 'Daftar' }).click();
await expect(page.getByText('Format email belum benar')).toBeVisible();
await expect(page.getByLabel('Email')).toBeFocused();
});
test('pendaftaran sukses', async ({ page }) => {
await page.getByLabel('Email').fill('budi@contoh.id');
await page.getByLabel('Email').press('Enter');
await expect(page.getByRole('status')).toContainText('budi@contoh.id sudah terdaftar');
await expect(page.getByLabel('Email')).toHaveValue(''); // form di-reset
});
test('bisa dipakai dengan keyboard saja', async ({ page, browserName }) => {
// Safari bawaan: Tab melewati link, kecuali ditekan bersama Option/Alt
const tab = browserName === 'webkit' ? 'Alt+Tab' : 'Tab';
await page.keyboard.press(tab);
await expect(page.getByRole('link', { name: 'Lewati ke konten utama' })).toBeFocused();
await page.keyboard.press(tab);
await expect(page.getByLabel('Email')).toBeFocused();
});
test('tidak ada pelanggaran aksesibilitas otomatis', async ({ page }) => {
const hasil = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
.analyze();
expect(hasil.violations).toEqual([]);
});
test('screenshot halaman', async ({ page }, testInfo) => {
await page.screenshot({
path: `screenshots/beranda-${testInfo.project.name}.png`,
fullPage: true,
});
});
Visual regression dan pengecekan prefers-reduced-motion:
// e2e/visual.spec.js
import { test, expect } from '@playwright/test';
test('tampilan beranda tidak berubah', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('beranda.png');
// Run pertama: GAGAL & membuat acuan di e2e/visual.spec.js-snapshots/
// Run berikutnya: dibandingkan dengan acuan
});
test('animasi mati saat pengguna minta reduced motion', async ({ page }) => {
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.goto('/');
const transisi = await page
.getByRole('button', { name: 'Daftar' })
.evaluate((el) => getComputedStyle(el).transitionDuration);
expect(transisi).toBe('0s'); // CSS opt-in dari modul Aksesibilitas bekerja
});
Menjalankan:
npx playwright test
# → Running 24 tests using … workers (6 test × 4 project)
# → 24 passed (9.3s)
npx playwright test --project=chromium # satu browser saja
npx playwright test e2e/newsletter.spec.js # satu file saja
npx playwright test --ui # mode interaktif
npx playwright show-report # buka laporan HTML
npx playwright test --update-snapshots # perbarui acuan screenshot
Tambahkan folder hasil ke .gitignore agar tidak ikut di-commit:
printf 'playwright-report/\ntest-results/\nscreenshots/\n' >> .gitignore
Kesalahan umum
- Memakai
page.waitForTimeout(3000)untuk "menunggu loading". Lambat dan tetap rapuh. Andalkan auto-waiting dan web-first assertion sepertiawait expect(...).toBeVisible(). - Lupa
await. Hampir semua API Playwright asinkron. Tanpaawait, test bisa selesai sebelum aksinya terjadi dan lulus palsu. - Locator rapuh seperti
page.locator('div > div:nth-child(3) > button'). PakaigetByRole,getByLabel,getByText. - Hanya menguji di Chromium. Bug Safari/WebKit justru yang paling sering lolos. Jalankan minimal tiga mesin browser untuk alur penting.
- Menulis E2E untuk semua hal. E2E lambat. Logika seperti validasi email cukup dengan unit test. E2E untuk alur utama: daftar, login, checkout.
Latihan
- Mudah, Tulis test Playwright yang membuka
/, memastikan ada tombol bernama "Daftar", dan atributlangpada<html>bernilaiid(petunjuk:page.locator('html')dantoHaveAttribute). - Sedang, Tulis test yang mengisi email valid, menekan Daftar, lalu memastikan tidak ada pesan error yang terlihat dan status berisi kata "Terima kasih".
- Menantang, Tambahkan project
iphonedengandevices['iPhone 15']ke konfigurasi, lalu tulis test yang hanya berjalan di project mobile (petunjuk: fixtureisMobiledantest.skip) untuk memastikan tinggi tombol Daftar minimal 44 piksel.
Lihat solusi
1.
import { test, expect } from '@playwright/test';
test('struktur dasar halaman', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('button', { name: 'Daftar' })).toBeVisible();
await expect(page.locator('html')).toHaveAttribute('lang', 'id');
});
2.
test('email valid tidak memunculkan error', async ({ page }) => {
await page.goto('/');
await page.getByLabel('Email').fill('sari@contoh.id');
await page.getByRole('button', { name: 'Daftar' }).click();
await expect(page.getByRole('status')).toContainText('Terima kasih');
await expect(page.getByText('Format email belum benar')).toBeHidden();
});
3.
// playwright.config.js → tambahkan ke projects:
// { name: 'iphone', use: { ...devices['iPhone 15'] } },
test('target sentuh cukup besar di HP', async ({ page, isMobile }) => {
test.skip(!isMobile, 'Hanya relevan di perangkat sentuh');
await page.goto('/');
const kotak = await page.getByRole('button', { name: 'Daftar' }).boundingBox();
expect(kotak.height).toBeGreaterThanOrEqual(44);
});
Ringkasan
- E2E menguji aplikasi utuh di browser sungguhan dari sudut pandang pengguna, dan menangkap bug yang lolos dari unit test.
- Playwright 1.64 mengendalikan Chromium, Firefox, dan WebKit dengan satu API. Install dengan
npm init playwright@latestataunpm i -D @playwright/testlalunpx playwright install. - Pakai locator berbasis role/label, andalkan auto-waiting dan web-first assertion, jangan
waitForTimeout. projects+devicesuntuk cross-browser dan perangkat.webServermenyalakan aplikasi otomatis.page.screenshot()untuk dokumentasi,toHaveScreenshot()untuk visual regression.@axe-core/playwrightuntuk audit aksesibilitas otomatis.- Debug dengan
--ui,--headed,show-report, dancodegen.