September 28, 2026
E2E Testing dengan Playwright.
Menguji aplikasi web dari sudut pandang user dengan Playwright: instalasi, playwright.config.ts dan webServer, locator & web-first assertions, autentikasi dengan storageState, mocking network, codegen, trace viewer, dan menjalankannya di CI.
End-to-end (E2E) test menjalankan aplikasi di browser sungguhan dan berinteraksi seperti user: membuka halaman, mengisi form, menekan tombol, lalu memeriksa apa yang tampil. Playwright (dari Microsoft) adalah pilihan populer karena mendukung Chromium, Firefox, dan WebKit dengan satu API, menunggu elemen secara otomatis, dan punya tooling debugging yang sangat baik.
Di piramida testing , E2E berada di puncak: sedikit jumlahnya, tapi menjaga alur paling kritis — login, checkout, pembayaran.
1. Instalasi
npm init playwright@latest
Wizard akan menanyakan bahasa (TypeScript/JavaScript), folder test, apakah menambahkan workflow GitHub Actions, dan apakah mengunduh browser. Hasilnya:
| File | Isi |
|---|---|
playwright.config.ts | Konfigurasi browser, timeout, reporter, base URL |
tests/example.spec.ts | Contoh test |
.github/workflows/playwright.yml | (opsional) workflow CI |
Playwright membutuhkan Node.js versi LTS terbaru (22.x/24.x saat catatan ini ditulis).
| Perintah | Fungsi |
|---|---|
npx playwright test | Jalankan semua test (headless, paralel) |
npx playwright test --ui | UI Mode: watch, time-travel, pick locator |
npx playwright test --headed | Tampilkan jendela browser |
npx playwright test --project=chromium | Hanya satu browser |
npx playwright test tests/login.spec.ts:12 | Satu test di baris tertentu |
npx playwright test --debug | Step-by-step dengan Inspector |
npx playwright show-report | Buka laporan HTML |
npx playwright codegen localhost:3000 | Rekam interaksi → kode test |
2. Konfigurasi
// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
fullyParallel: true,
forbidOnly: !!process.env.CI, // gagal jika ada test.only di CI
retries: process.env.CI ? 2 : 0,
reporter: "html",
use: {
baseURL: "http://localhost:5173",
trace: "on-first-retry", // rekam trace saat retry pertama
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "mobile", use: { ...devices["Pixel 7"] } },
],
webServer: {
command: "npm run dev",
url: "http://localhost:5173",
reuseExistingServer: !process.env.CI,
},
});
webServer menyalakan aplikasi otomatis sebelum test. Untuk aplikasi Laravel, command bisa berupa php artisan serve --port=8000 dengan url yang sesuai.
3. Test Pertama
// tests/beranda.spec.ts
import { test, expect } from "@playwright/test";
test("beranda menampilkan judul dan navigasi ke blog", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveTitle(/Fanny/);
await page.getByRole("link", { name: "Blog" }).click();
await expect(page).toHaveURL(/\/articles/);
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
});
Tidak ada sleep atau waitForTimeout. Playwright menerapkan auto-waiting: sebelum click(), ia menunggu elemen terlihat, stabil, dan bisa menerima event.
4. Locator
Pilih locator seperti user dan teknologi bantu (screen reader) melihat halaman:
| Prioritas | Locator | Contoh |
|---|---|---|
| 1 | getByRole | page.getByRole("button", { name: "Simpan" }) |
| 2 | getByLabel | page.getByLabel("Email") |
| 3 | getByPlaceholder | page.getByPlaceholder("Cari...") |
| 4 | getByText | page.getByText("Pesanan berhasil") |
| 5 | getByTestId | page.getByTestId("cart-count") (atribut data-testid) |
| Hindari | CSS/XPath | page.locator(".btn-primary > span") |
Locator bisa disaring dan dirangkai:
const baris = page.getByRole("row").filter({ hasText: "Jam Tangan Klasik" });
await baris.getByRole("button", { name: "Hapus" }).click();
await expect(page.getByRole("listitem")).toHaveCount(3);
5. Web-first Assertions
expect pada locator mengulang pengecekan sampai lulus atau timeout (default 5 detik) — cocok untuk UI async.
| Assertion | Cek |
|---|---|
toBeVisible() / toBeHidden() | Tampil / tidak |
toHaveText("...") / toContainText("...") | Teks elemen |
toHaveValue("...") | Nilai input |
toBeEnabled() / toBeDisabled() | State tombol |
toHaveCount(n) | Jumlah elemen |
toHaveURL(/re/), toHaveTitle(/re/) | Halaman |
toHaveScreenshot() | Visual regression |
// ❌ tidak menunggu — rawan flaky
expect(await page.getByTestId("status").textContent()).toBe("Lunas");
// ✅ menunggu sampai teks berubah
await expect(page.getByTestId("status")).toHaveText("Lunas");
6. Alur Form Lengkap
// tests/kontak.spec.ts
import { test, expect } from "@playwright/test";
test.describe("form kontak", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/kontak");
});
test("menampilkan error validasi", async ({ page }) => {
await page.getByRole("button", { name: "Kirim" }).click();
await expect(page.getByText("Email wajib diisi")).toBeVisible();
});
test("mengirim pesan", async ({ page }) => {
await page.getByLabel("Nama").fill("Fanny");
await page.getByLabel("Email").fill("[email protected]");
await page.getByLabel("Pesan").fill("Halo, saya tertarik bekerja sama.");
await page.getByRole("button", { name: "Kirim" }).click();
await expect(page.getByRole("status")).toContainText("Terima kasih");
});
});
7. Autentikasi dengan storageState
Login lewat UI di setiap test itu lambat. Pola yang disarankan: login sekali di project setup, simpan cookie/localStorage, lalu pakai ulang.
// tests/auth.setup.ts
import { test as setup, expect } from "@playwright/test";
const authFile = "playwright/.auth/user.json";
setup("login", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill(process.env.E2E_EMAIL!);
await page.getByLabel("Password").fill(process.env.E2E_PASSWORD!);
await page.getByRole("button", { name: "Masuk" }).click();
await expect(page).toHaveURL(/\/dashboard/);
await page.context().storageState({ path: authFile });
});
// playwright.config.ts (bagian projects)
projects: [
{ name: "setup", testMatch: /.*\.setup\.ts/ },
{
name: "chromium",
use: { ...devices["Desktop Chrome"], storageState: "playwright/.auth/user.json" },
dependencies: ["setup"],
},
],
Tambahkan playwright/.auth ke .gitignore — file itu berisi token sesi. Cara kerja cookie sesi dijelaskan di Autentikasi Sisi Klien
.
8. Mocking Network
Untuk menguji state yang sulit direproduksi (error server, data kosong), cegat request dengan page.route:
test("menampilkan pesan saat API gagal", async ({ page }) => {
await page.route("**/api/produk", (route) =>
route.fulfill({ status: 500, json: { message: "Server error" } })
);
await page.goto("/produk");
await expect(page.getByText("Gagal memuat produk")).toBeVisible();
});
Gunakan secukupnya: E2E paling berharga ketika menyentuh backend sungguhan. Mock cocok untuk layanan pihak ketiga (payment gateway, peta) atau skenario error.
9. Debugging & CI
- Trace Viewer: dengan
trace: "on-first-retry", test yang gagal lalu di-retry menghasilkan trace berisi snapshot DOM, network, dan console per langkah. Buka lewatnpx playwright show-reportataunpx playwright show-trace trace.zip. - UI Mode (
--ui) untuk development sehari-hari. - Codegen untuk mendapatkan locator cepat, lalu rapikan manual.
GitHub Actions minimal:
name: E2E
on: [push, pull_request]
jobs:
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Untuk aplikasi yang butuh database atau layanan lain, jalankan dependensi tersebut sebagai service container atau dengan Docker Compose sebelum langkah test.
Kesalahan Umum
page.waitForTimeout(3000). Menyebabkan test lambat dan tetap flaky. Andalkan auto-waiting dan web-first assertions.- Locator berbasis CSS/struktur (
div:nth-child(3) > .btn). Patah setiap redesign; gunakangetByRole/getByLabel. expect(await locator.textContent())alih-alihawait expect(locator).toHaveText()— kehilangan retry otomatis.- Test bergantung satu sama lain (test B memakai data buatan test A). Setiap test mendapat context browser baru; siapkan datanya sendiri.
- Login via UI di setiap test. Gunakan
storageState+ project setup. - Commit
playwright/.auth/yang berisi cookie sesi. - Menulis E2E untuk semua kasus tepi. Validasi detail lebih murah diuji di level unit/feature (Vitest atau Pest ); E2E cukup untuk alur utama.
- Lupa
npx playwright install --with-depsdi CI → error browser executable tidak ditemukan.
Ringkasan
npm init playwright@latest, lalunpx playwright test/--ui/show-report.webServermenyalakan aplikasi otomatis;baseURLmembuatpage.goto("/")ringkas.- Locator berbasis role + web-first assertions = test yang stabil tanpa sleep.
storageStateuntuk login sekali,page.routeuntuk skenario error, trace untuk debugging.- Simpan E2E untuk alur kritis; sisanya di level unit dan integration.

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.