Kembali ke blog

Strategi Playwright Buat Solo Developer: Apa yang Perlu di-Test, Apa yang Di-skip

Lo ga punya QA team. Lo SENDIRI QA team-nya. Ini cara gue nentuin mana yang worth di-test pake Playwright, dan mana yang gue skip sengaja.

25 Mei 20264 min readtestingplaywrightworkflowsolo-dev

Lo ga punya QA team. Lo SENDIRI QA team-nya.

Sebagai solo developer, lo nulis kode, review PR, deploy ke production, terus berdoa ga ada yang rusak. Testing kerasa kayak luxury — tapi ship fitur yang broken ke user jauh lebih mahal.

Masalahnya bukan soal mau test atau engga. Tapi apa yang mau di-test. Playwright ngasih lo power buat test semuanya. Tapi bukan berarti lo harus.

Ini framework yang gue pake buat nentuin prioritas.

80/20 E2E Testing

Kebanyakan bug yang kena user itu di surface area yang surprisingly kecil:

  • Auth flow — login, signup, reset password. Kalo user ga bisa masuk, yang lain ga penting.
  • Critical money path — checkout, payment, perubahan subscription. Bug yang ngerusak revenue itu existential.
  • Core CRUD — satu hal yang app lo lakuin. Create, edit, delete entity utama.

Selebihnya nice-to-have. Buat solo dev, "nice-to-have" artinya "bukan sprint ini."

Yang Wajib di-Test Dulu (Tier 1 — Non-Negotiable)

Ini smoke test lo. Jalanin di tiap PR. Kalo ada yang fail, block deploy.

// Auth: user bisa signup dan login?
test('user can sign up and reach dashboard', async ({ page }) => {
  await page.goto('/signup');
  await page.fill('[name="email"]', 'test@example.com');
  await page.fill('[name="password"]', 'securePassword123');
  await page.click('button[type="submit"]');
  await expect(page).toHaveURL('/dashboard');
});

// Core flow: user bisa lakuin hal utama?
test('user can create a project', async ({ page }) => {
  await page.goto('/dashboard');
  await page.click('text=New Project');
  await page.fill('[name="title"]', 'My Project');
  await page.click('button:text("Create")');
  await expect(page.locator('text=My Project')).toBeVisible();
});

Jaga di bawah 5 test. Di bawah 2 menit total runtime. Fast feedback loop ato ga ada feedback loop sama sekali.

Yang Di-test Berikutnya (Tier 2 — Mingguan)

Abis smoke test lo solid, tambahin coverage buat:

  • Form validation — field kosong, email ga valid, max length
  • Permission boundaries — user biasa bisa akses admin route?
  • API error states — apa yang terjadi kalo backend return 500?
test('form nampilin error kalo title kosong', async ({ page }) => {
  await page.goto('/dashboard');
  await page.click('text=New Project');
  await page.click('button:text("Create")');
  await expect(page.locator('text=Title is required')).toBeVisible();
});

test('non-admin ga bisa akses admin panel', async ({ page }) => {
  await loginAs(page, 'regular-user');
  await page.goto('/admin');
  await expect(page).toHaveURL('/dashboard');
});

Jalanin tiap malem atau pas merge ke main. Ini nangkep regresi, bukan blocker.

Yang Lo Skip (Dulu)

Ini bagian yang susah. Test-test ini erasa penting tapi abisin waktu solo dev:

Pixel-perfect UI — Screenshot test itu brittle. Satu tweak CSS bisa pecahin 20 snapshot. Pake visual regression cuma buat pricing page, nothing else.

Edge case di fitur sekunder — "Gimana kalo user upload PNG 50MB ke profile picture?" Sure, tapi apakah itu nge-ship revenue? Engga. Ship dulu, harden belakangan.

Third-party integration — Jangan nulis E2E test buat Stripe checkout, OAuth redirect, atau webhook handler. Mock aja. Percaya Stripe SDK-nya works.

Mobile responsive layout — Playwright bisa emulate devices, tapi test tiap breakpoint itu time sink. Test primary breakpoint lo aja. Lighthouse handle sisanya.

// JANGAN: screenshot comparison di 5 breakpoint
// LAKUKAN: pastiin ga pecah di mobile
test('mobile layout renders without overlap', async ({ page }) => {
  await page.setViewportSize({ width: 375, height: 812 });
  await page.goto('/dashboard');
  await expect(page.locator('nav')).toBeVisible();
});

Config Playwright Ala Solo Dev

Ini playwright.config.ts lean gue:

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  timeout: 30_000,
  retries: 1,
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
  },
  projects: [
    { name: 'chromium', use: { browserName: 'chromium' } },
  ],
});

Satu browser. Satu retry. Trace pas fail. Gitu aja. Tambahin Firefox kalo udah punya team.

Struktur Folder Test Gue

e2e/
├── smoke.spec.ts        # Tier 1: auth + core flow
├── critical.spec.ts     # Tier 2: validation + permissions
└── fixtures/
    └── auth.ts          # shared login helper

Tiga file. Ga ada nested test suite. Ga ada test matrix. Findability beats organization pas jam 2 pagi dan production lagi down.

Mental Model

Tanya ini ke diri sendiri sebelum nulis E2E test:

  1. Kalo ini rusak, user notice dalam 1 jam? → Tulis test-nya.
  2. Kalo ini rusak, gue kehilangan duit? → Tulis test-nya.
  3. Kalo ini rusak, gue keliatan ga profesional? → Mungkin.
  4. Kalo ini rusak, cuma annoying? → Skip. File issue. Move on.

Waktu lo itu bottleneck. Tiap test yang lo tulis itu fitur yang ga lo ship. Optimasi buat test yang mencegah outcome terburuk, bukan test yang bikin coverage report lo keliatan bagus.

Coverage Itu Trap

Solo dev dengan 90% test coverage itu solo dev yang ship setengah fitur dari yang seharusnya. Coverage ngukur lines yang kena sentuh, bukan bugs yang dicegah. Lima smoke test yang ditempatin dengan bener nangkep lebih banyak real bugs daripada lima puluh unit test di utility function.

Ukur dengan cara beda: berapa kali bug sampe ke production? Kalo jawabannya "jarang dan ga pernah catastrophic," strategi test lo udah works.