E2E Testing dengan Claude Code: Panduan Playwright/Cypress dan CI
Rancang E2E Playwright dengan Claude Code: CI, jebakan nyata, contoh runnable, dan cek CTA.
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 case | Kenapa perlu E2E | Ekspektasi penting |
|---|---|---|
| Login ke dashboard | Auth, redirect, dan permission bertemu | Heading, status pengguna, logout, error |
| Checkout atau form konsultasi | Berdampak langsung ke revenue atau lead | Nomor order, konfirmasi, cegah double submit |
| Artikel ke PDF gratis atau training | Melindungi monetisasi di luar iklan | CTA href, event, visibilitas mobile |
| Aksi admin berbahaya | Mencegah hapus salah dan bug permission | Modal konfirmasi, audit log, cek role |
| Halaman lokal | Terjemahan sering memutus link | URL 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
| Aspek | Playwright | Cypress |
|---|---|---|
| Browser | Chromium, Firefox, WebKit | Keluarga Chrome, Firefox, Edge |
| Paralel | Bawaan Playwright Test | Sering dirancang dengan Dashboard |
| Banyak tab dan context | Kuat | Lebih terbatas |
| Debugging | Trace Viewer, UI mode, HTML report | GUI interaktif ramah |
| Cocok untuk | CI, multi-browser, auth terisolasi | Tim 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.
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.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.