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:

FileIsi
playwright.config.tsKonfigurasi browser, timeout, reporter, base URL
tests/example.spec.tsContoh test
.github/workflows/playwright.yml(opsional) workflow CI

Playwright membutuhkan Node.js versi LTS terbaru (22.x/24.x saat catatan ini ditulis).

PerintahFungsi
npx playwright testJalankan semua test (headless, paralel)
npx playwright test --uiUI Mode: watch, time-travel, pick locator
npx playwright test --headedTampilkan jendela browser
npx playwright test --project=chromiumHanya satu browser
npx playwright test tests/login.spec.ts:12Satu test di baris tertentu
npx playwright test --debugStep-by-step dengan Inspector
npx playwright show-reportBuka laporan HTML
npx playwright codegen localhost:3000Rekam 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:

PrioritasLocatorContoh
1getByRolepage.getByRole("button", { name: "Simpan" })
2getByLabelpage.getByLabel("Email")
3getByPlaceholderpage.getByPlaceholder("Cari...")
4getByTextpage.getByText("Pesanan berhasil")
5getByTestIdpage.getByTestId("cart-count") (atribut data-testid)
HindariCSS/XPathpage.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.

AssertionCek
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 lewat npx playwright show-report atau npx 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

  1. page.waitForTimeout(3000). Menyebabkan test lambat dan tetap flaky. Andalkan auto-waiting dan web-first assertions.
  2. Locator berbasis CSS/struktur (div:nth-child(3) > .btn). Patah setiap redesign; gunakan getByRole/getByLabel.
  3. expect(await locator.textContent()) alih-alih await expect(locator).toHaveText() — kehilangan retry otomatis.
  4. Test bergantung satu sama lain (test B memakai data buatan test A). Setiap test mendapat context browser baru; siapkan datanya sendiri.
  5. Login via UI di setiap test. Gunakan storageState + project setup.
  6. Commit playwright/.auth/ yang berisi cookie sesi.
  7. Menulis E2E untuk semua kasus tepi. Validasi detail lebih murah diuji di level unit/feature (Vitest atau Pest ); E2E cukup untuk alur utama.
  8. Lupa npx playwright install --with-deps di CI → error browser executable tidak ditemukan.

Ringkasan

  • npm init playwright@latest, lalu npx playwright test / --ui / show-report.
  • webServer menyalakan aplikasi otomatis; baseURL membuat page.goto("/") ringkas.
  • Locator berbasis role + web-first assertions = test yang stabil tanpa sleep.
  • storageState untuk login sekali, page.route untuk 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.

Comments