Use Cases (Diperbarui: 3/6/2026)

E2E Testing dengan Claude Code: Panduan Playwright/Cypress dan CI

Rancang E2E Playwright dengan Claude Code: CI, jebakan nyata, contoh runnable, dan cek CTA.

E2E Testing dengan Claude Code: Panduan Playwright/Cypress dan CI

Tentukan dulu apa yang harus dilindungi E2E

Claude Code bisa membuat test login dan terminal bisa hijau, tetapi itu belum berarti rilis aman. E2E testing memeriksa alur pengguna di browser nyata, jadi lebih lambat dan lebih mahal untuk di-debug daripada unit test. Kalau semua detail UI dijadikan E2E, CI akan berisik dan tim berhenti percaya pada kegagalan test.

Pilih alur yang paling mahal jika rusak: login, checkout, formulir konsultasi, aksi admin yang berisiko, dan CTA artikel yang membawa pembaca ke produk atau training. Dalam operasional ClaudeCodeLab, Masa pernah melihat halaman tampil normal, tetapi link akhir menuju training rusak. Secara visual aman, tetapi jalur monetisasi putus.

Panduan ini mengikuti dokumentasi terbaru tentang instalasi Playwright, locators, CI, dan Trace Viewer. Untuk cakupan lain, baca juga otomatisasi API testing dan panduan strategi testing.

flowchart LR
  A["Minta Claude Code"] --> B["Pilih tiga alur kritis"]
  B --> C["Buat test Playwright"]
  C --> D["Debug dengan trace dan report"]
  D --> E["Jadikan CI gate"]
  C --> F["Cek CTA dan jalur revenue"]

Prompt yang lebih jelas untuk Claude Code

Masukkan cakupan, kriteria sukses, kriteria gagal, file yang boleh disentuh, dan command verifikasi. Tanpa itu, Claude Code sering membuat test rapuh yang bergantung pada class CSS. Playwright merekomendasikan locator seperti getByRole, getByLabel, getByText, dan getByTestId karena cocok dengan auto-waiting dan assertion yang bisa retry.

Target: E2E test untuk login, checkout, dan signup newsletter.
Tool: Playwright Test. Tambahkan perbandingan singkat dengan Cypress.
Aturan:
- Utamakan role, label, text, atau testid daripada class CSS.
- Uji error path, bukan hanya happy path.
- Di CI gunakan workers: 1 dan trace: on-first-retry.
- Pastikan href CTA monetisasi masih ada.
File yang boleh diubah:
- tests/e2e/**/*.spec.ts
- playwright.config.ts
Verifikasi:
- npx playwright test
- npx playwright test --headed untuk debug lokal

Tuliskan juga yang di luar cakupan. Pembayaran asli, pengiriman email asli, klik iklan, dan penulisan ke CRM tidak perlu dipanggil di setiap E2E. Gunakan mode test, mock, atau contract test API. Untuk batas integrasi, lihat juga implementasi webhook dan implementasi analytics.

Command setup

Untuk proyek baru, mulai dari command ini. Dokumentasi resmi menjelaskan bahwa setiap versi Playwright memakai binary browser tertentu, jadi setelah update Playwright, install ulang browser. Di CI Linux, pakai --with-deps agar dependensi sistem ikut terpasang.

npm init playwright@latest
npx playwright install
npx playwright test
npx playwright test --headed
npx playwright test --ui
npx playwright show-report

--headed membuka browser terlihat untuk debug lokal. --ui membuka mode interaktif Playwright. CI biasanya berjalan headless. Kalau test lolos lokal tetapi gagal di CI, cek animasi, ukuran viewport, font, zona waktu, kecepatan CPU, dan tunggu API eksternal.

Contoh Playwright yang bisa langsung jalan

Spec berikut berdiri sendiri. Ia membuat halaman kecil dengan page.setContent, jadi tidak butuh server aplikasi. Setelah Playwright terpasang, salin ke tests/e2e/claude-code-e2e.spec.ts. Di aplikasi nyata, ganti renderDemoApp(page) dengan page.goto('/login'), lalu sesuaikan label atau data-testid.

// tests/e2e/claude-code-e2e.spec.ts
import { test, expect, type Page } from '@playwright/test';

async function renderDemoApp(page: Page) {
  await page.setContent(`
    <!doctype html>
    <main>
      <form id="login-form" aria-label="Login form">
        <label>Email <input id="email" aria-label="Email" /></label>
        <label>Password <input id="password" aria-label="Password" type="password" /></label>
        <button type="submit">Log in</button>
        <p id="login-error" role="alert" hidden></p>
      </form>

      <section id="dashboard" hidden>
        <h1>Dashboard</h1>
        <a data-testid="training-cta" href="/training/">Book Claude Code training</a>
        <button id="add-plan">Add Pro plan to cart</button>
        <span data-testid="cart-count">0</span>
        <button id="buy">Complete purchase</button>
        <p data-testid="order-status" role="status"></p>

        <label>Newsletter email <input id="newsletter-email" aria-label="Newsletter email" /></label>
        <button id="newsletter-button" type="button">Join newsletter</button>
        <p id="newsletter-error" role="alert" hidden></p>
        <p data-testid="newsletter-status" role="status"></p>
      </section>
    </main>

    <script>
      const state = { cart: 0 };
      document.querySelector('#login-form').addEventListener('submit', (event) => {
        event.preventDefault();
        const email = document.querySelector('#email').value;
        const password = document.querySelector('#password').value;
        const error = document.querySelector('#login-error');

        if (email === 'masa@example.com' && password === 'password123') {
          document.querySelector('#login-form').hidden = true;
          document.querySelector('#dashboard').hidden = false;
          error.hidden = true;
          return;
        }

        error.textContent = 'Authentication failed';
        error.hidden = false;
      });

      document.querySelector('#add-plan').addEventListener('click', () => {
        state.cart += 1;
        document.querySelector('[data-testid="cart-count"]').textContent = String(state.cart);
      });

      document.querySelector('#buy').addEventListener('click', () => {
        const status = document.querySelector('[data-testid="order-status"]');
        status.textContent = state.cart === 0 ? 'Cart is empty' : 'Order ORD-1001 completed';
      });

      document.querySelector('#newsletter-button').addEventListener('click', () => {
        const email = document.querySelector('#newsletter-email').value;
        const error = document.querySelector('#newsletter-error');
        const status = document.querySelector('[data-testid="newsletter-status"]');

        if (!email.includes('@')) {
          error.textContent = 'Enter a valid email';
          error.hidden = false;
          status.textContent = '';
          return;
        }

        error.hidden = true;
        status.textContent = 'Thanks, we will send the checklist.';
      });
    </script>
  `);
}

async function loginAsDemoUser(page: Page) {
  await page.getByLabel('Email').fill('masa@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Log in' }).click();
}

test.describe('Claude Code E2E starter', () => {
  test('use case 1: login shows dashboard and keeps the training CTA', async ({ page }) => {
    await renderDemoApp(page);
    await loginAsDemoUser(page);

    await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
    await expect(page.getByTestId('training-cta')).toHaveAttribute('href', '/training/');
  });

  test('use case 2: checkout flow creates an order number', async ({ page }) => {
    await renderDemoApp(page);
    await loginAsDemoUser(page);

    await page.getByRole('button', { name: 'Add Pro plan to cart' }).click();
    await expect(page.getByTestId('cart-count')).toHaveText('1');
    await page.getByRole('button', { name: 'Complete purchase' }).click();
    await expect(page.getByTestId('order-status')).toContainText('ORD-1001');
  });

  test('use case 3: newsletter validation blocks invalid leads', async ({ page }) => {
    await renderDemoApp(page);
    await loginAsDemoUser(page);

    await page.getByRole('button', { name: 'Join newsletter' }).click();
    await expect(page.getByRole('alert')).toContainText('Enter a valid email');

    await page.getByLabel('Newsletter email').fill('reader@example.com');
    await page.getByRole('button', { name: 'Join newsletter' }).click();
    await expect(page.getByTestId('newsletter-status')).toContainText('Thanks');
  });
});

File ini mencakup tiga use case konkret: login, checkout, dan lead capture. Ia juga mengecek href CTA training, detail yang sering hilang dari suite E2E tipis.

Konfigurasi Playwright untuk praktik

Konfigurasi harus menyimpan bukti ketika gagal. Dokumentasi CI Playwright merekomendasikan workers: 1 di CI untuk stabilitas dan reproduksi yang lebih mudah.

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  use: {
    baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
});

trace: 'on-first-retry' menunjukkan apa yang terlihat, klik mana yang gagal, dan assertion mana yang timeout. Itu jauh lebih membantu untuk Claude Code daripada satu baris error CI.

GitHub Actions untuk CI

Di CI, banyak kegagalan berasal dari browser atau dependensi Linux. Mulai dengan workflow sederhana, lalu tambahkan cache atau sharding kalau waktunya sudah menjadi masalah.

# .github/workflows/playwright.yml
name: Playwright Tests

on:
  push:
    branches: [main, master]
  pull_request:
    branches: [main, master]

jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v6
        with:
          node-version: lts/*
      - name: Install dependencies
        run: npm ci
      - name: Install Playwright browsers
        run: npx playwright install --with-deps
      - name: Run E2E tests
        run: npx playwright test
      - uses: actions/upload-artifact@v5
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Kalau menguji URL preview, kirim BASE_URL ke workflow. Kalau aplikasi harus dinyalakan di CI, tambahkan webServer di playwright.config.ts.

Use case yang layak diuji E2E

Use caseKenapa perlu E2EEkspektasi penting
Login ke dashboardAuth, redirect, dan permission bertemuHeading, status pengguna, logout, error
Checkout atau form konsultasiBerdampak langsung ke revenue atau leadNomor order, konfirmasi, cegah double submit
Artikel ke PDF gratis atau trainingMelindungi monetisasi di luar iklanCTA href, event, visibilitas mobile
Aksi admin berbahayaMencegah hapus salah dan bug permissionModal konfirmasi, audit log, cek role
Halaman lokalTerjemahan sering memutus linkURL locale, copy natural, link eksternal

Berikan tabel ini ke Claude Code sebelum meminta file. Hasilnya test akan memverifikasi outcome, bukan hanya menekan tombol.

Kegagalan yang sering terjadi

Hindari waitForTimeout(3000). Tunggu tetap bisa lolos di satu mesin dan gagal di CI. Tunggu state nyata: elemen terlihat, URL berubah, atau teks status muncul.

Hindari selector seperti .btn-primary:nth-child(2). Perubahan desain kecil bisa memecahkan test. Pakai role, label, text, dan data-testid yang stabil.

Jangan berbagi state antar-test. Cart, Cookie, atau record database yang dibuat satu test tidak boleh menjadi syarat test berikutnya.

Jangan remehkan perbedaan headed dan headless. Font, GPU, scroll, clipboard, upload file, dan viewport bisa berbeda di CI. Jalankan CI headless, lalu pakai --headed atau --ui untuk investigasi.

Jangan memanggil pembayaran, email, iklan, atau CRM asli di setiap run. Mode test, mock, atau API contract test lebih aman.

Playwright atau Cypress

AspekPlaywrightCypress
BrowserChromium, Firefox, WebKitKeluarga Chrome, Firefox, Edge
ParalelBawaan Playwright TestSering dirancang dengan Dashboard
Banyak tab dan contextKuatLebih terbatas
DebuggingTrace Viewer, UI mode, HTML reportGUI interaktif ramah
Cocok untukCI, multi-browser, auth terisolasiTim frontend dengan Cypress yang sudah ada

Cypress tetap pilihan bagus. Kalau tim sudah punya coverage Cypress, tidak perlu migrasi hanya karena tren. Tambahkan Playwright ke satu alur kritis, lalu bandingkan flake rate, kualitas trace, dan waktu CI. Pastikan keputusan akhir merujuk ke dokumentasi resmi Playwright dan Cypress.

CTA monetisasi dan hasil uji

E2E juga melindungi revenue. Link konsultasi, kartu produk, form gratis, atau CTA training yang rusak bisa lebih mahal daripada bug visual. Developer solo bisa mulai dari cheatsheet Claude Code gratis. Untuk prompt berulang, lihat produk dan template. Tim yang butuh CI gate, CLAUDE.md, aturan review, dan kepemilikan E2E bisa memakai training dan konsultasi Claude Code.

Contoh di artikel ini dicek dengan mengekstrak spec dan konfigurasi sebagai snippet TypeScript untuk validasi sintaks. Kebiasaan paling berguna adalah menulis ekspektasi bisnis langsung di nama test: CTA training tetap ada. Di proyek nyata, mulai dari tiga test saja: login, checkout atau lead form, dan CTA artikel. Perluas setelah ketiganya stabil di CI.

#Claude Code #E2E testing #Playwright #Cypress #CI
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.