E2E Testing dengan Claude Code dan Playwright untuk Produksi
Gunakan Claude Code dan Playwright untuk E2E, mobile screenshot, auth state, Trace Viewer, selector, dan retry CI.
Meminta Claude Code “tambahkan test Playwright” saja belum cukup untuk produksi. Hasilnya bisa lulus sekali, tetapi bergantung pada class CSS yang rapuh, login lewat UI di setiap test, tidak menyimpan screenshot mobile, dan tidak punya trace yang berguna ketika CI gagal.
Anggap Claude Code sebagai partner desain test. Kamu menentukan risiko bisnis, route, aturan selector, dan command validasi; Claude Code membaca project lalu membuat konfigurasi Playwright dan spec yang sesuai. Panduan ini membahas E2E untuk jalur monetisasi, screenshot mobile, QA layout code block, auth state, Trace Viewer, retry di CI, dan cara menghindari flaky test.
Jadikan dokumentasi resmi sebagai acuan: Claude Code overview, Claude Code common workflows, serta Playwright untuk Locators, Authentication, Screenshots, Trace Viewer, Retries, dan CI. Untuk konteks ClaudeCodeLab, baca juga testing strategy, CI/CD setup, dan responsive design.
Pilih flow yang perlu dilindungi
E2E menjalankan browser sungguhan, jadi lebih lambat daripada unit test. Fokuskan pada flow yang hanya bisa dibuktikan di browser.
| Use case | Yang dilindungi | Bukti Playwright |
|---|---|---|
| Artikel ke produk | Pembaca bisa lanjut ke /products/ | CTA, URL, tap target mobile |
| Dashboard setelah login | User terautentikasi masuk ke halaman private | storageState, redirect, permission |
| Layout artikel kode | Code block dan tabel tidak merusak mobile | Mobile screenshot, tanpa overflow, trace |
flowchart LR
A["Jalur revenue atau signup"] --> B["Playwright E2E"]
C["Risiko layout mobile"] --> B
D["Validasi murni"] --> E["Unit tests"]
F["Batas API atau component"] --> G["Integration tests"]
Untuk situs konten yang dimonetisasi, CTA yang tersembunyi di mobile, baris kode yang membuat halaman melebar, atau halaman pelanggan yang tidak pernah diuji di CI bisa langsung memengaruhi konversi.
Beri batas jelas ke Claude Code
Prompt harus menjelaskan route target, selector yang boleh dipakai, file yang boleh diubah, dan command pembuktian.
Baca situs Astro yang ada dan tambahkan test E2E Playwright.
Tujuan:
- Pastikan `/id/blog/claude-code-playwright-testing/` menuju `/products/` dan `/training/`
- Di viewport mobile 390px, pastikan artikel, tabel, dan code block tidak overflow horizontal
- Untuk test yang butuh login, gunakan `storageState`, bukan login UI di setiap test
- Di CI gunakan 2 retries dan `trace: "on-first-retry"`
Batasan:
- Jangan gunakan `page.waitForTimeout()`
- Prioritaskan role, label, text, atau test id dibanding rantai class CSS
- Ubah hanya `playwright.config.ts` dan `tests/e2e/**`
- Jalankan `npx playwright test` dan jelaskan failure dengan Trace Viewer
Saat review, lihat apakah failure bisa dijelaskan, selector mencerminkan niat user, dan data test bisa diulang.
Setup yang bisa dicopy
cd site
npm i -D @playwright/test
npx playwright install
mkdir tests/e2e
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
const baseURL = process.env.BASE_URL ?? 'http://127.0.0.1:4321';
const hasAuth = Boolean(process.env.TEST_EMAIL && process.env.TEST_PASSWORD);
const authFile = 'playwright/.auth/user.json';
export default defineConfig({
testDir: './tests/e2e',
timeout: 30_000,
expect: { timeout: 5_000 },
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: process.env.CI ? [['html'], ['github']] : 'html',
use: {
baseURL,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
...(process.env.PLAYWRIGHT_WEB_SERVER === '1'
? {
webServer: {
command: 'npm run preview -- --host 127.0.0.1 --port 4321',
url: baseURL,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
}
: {}),
projects: [
...(hasAuth
? [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
]
: []),
{
name: 'desktop-chrome',
use: {
...devices['Desktop Chrome'],
storageState: hasAuth ? authFile : undefined,
},
dependencies: hasAuth ? ['setup'] : [],
},
{
name: 'mobile-safari',
use: {
...devices['iPhone 13'],
storageState: hasAuth ? authFile : undefined,
},
dependencies: hasAuth ? ['setup'] : [],
},
],
});
Di lokal, retry dimatikan agar error terlihat jelas. Di CI, retry berguna jika trace, screenshot, dan HTML report ikut disimpan.
Simpan authentication state
Jangan login lewat UI di setiap test. storageState menyimpan cookie dan localStorage dari user test. Folder playwright/.auth harus masuk .gitignore.
// tests/e2e/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
import fs from 'node:fs';
import path from 'node:path';
const authFile = path.resolve('playwright/.auth/user.json');
const email = process.env.TEST_EMAIL;
const password = process.env.TEST_PASSWORD;
setup('save signed-in browser state', async ({ page }) => {
setup.skip(!email || !password, 'Set TEST_EMAIL and TEST_PASSWORD to record auth state.');
await page.goto('/login');
await page.getByLabel(/email|メール|e-mail/i).fill(email!);
await page.getByLabel(/password|パスワード/i).fill(password!);
await page.getByRole('button', { name: /log in|sign in|ログイン/i }).click();
await expect(page).toHaveURL(/dashboard|account|admin/);
await expect(page.locator('body')).toBeVisible();
fs.mkdirSync(path.dirname(authFile), { recursive: true });
await page.context().storageState({ path: authFile });
});
Minta Claude Code memisahkan test login dari test fitur yang hanya membutuhkan status sudah login. Ini mencegah satu perubahan teks tombol login merusak semua E2E.
QA mobile screenshot dan code block
Artikel teknis sering rusak karena baris kode panjang, tabel, atau media. Spec ini memeriksa CTA, menyimpan screenshot mobile, dan gagal jika ada overflow horizontal.
// tests/e2e/article-quality.spec.ts
import { test, expect } from '@playwright/test';
const articlePath = process.env.ARTICLE_PATH ?? '/id/blog/claude-code-playwright-testing/';
test.describe('article quality checks', () => {
test('article has monetization CTAs', async ({ page }) => {
await page.goto(articlePath);
await expect(page.getByRole('heading', { level: 1 })).toContainText(/Playwright|E2E|Claude Code/i);
await expect(page.locator('a[href="/products/"], a[href="/products"]').first()).toBeVisible();
await expect(page.locator('a[href="/training/"], a[href="/training"]').first()).toBeVisible();
});
test('mobile layout has no horizontal overflow', async ({ page }, testInfo) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(articlePath);
await expect(page.locator('main, article').first()).toBeVisible();
const overflow = await page.evaluate(() => ({
viewport: window.innerWidth,
documentWidth: document.documentElement.scrollWidth,
offenders: Array.from(document.querySelectorAll('pre, table, img, iframe, .prose'))
.filter((node) => {
const rect = node.getBoundingClientRect();
return rect.left < -1 || rect.right > window.innerWidth + 1;
})
.map((node) => {
const rect = node.getBoundingClientRect();
return `${node.tagName.toLowerCase()} ${Math.round(rect.left)}-${Math.round(rect.right)}`;
}),
}));
expect(overflow.documentWidth, JSON.stringify(overflow)).toBeLessThanOrEqual(overflow.viewport + 2);
expect(overflow.offenders).toEqual([]);
await page.screenshot({ path: testInfo.outputPath('article-mobile.png'), fullPage: true });
});
test('code examples are present and copyable', async ({ page }) => {
await page.goto(articlePath);
const blocks = page.locator('pre code');
await expect(blocks.first()).toBeVisible();
expect(await blocks.count()).toBeGreaterThanOrEqual(3);
await expect(blocks.nth(0)).toContainText(/playwright|defineConfig|test/i);
});
});
Screenshot berguna untuk review manusia; assertion overflow berguna untuk CI.
Lindungi CTA revenue dan training
// tests/e2e/revenue-flows.spec.ts
import { test, expect } from '@playwright/test';
const articlePath = process.env.ARTICLE_PATH ?? '/id/blog/claude-code-playwright-testing/';
test.describe('revenue and learning flows', () => {
test('reader can move from article to products', async ({ page }) => {
await page.goto(articlePath);
await page.locator('a[href="/products/"], a[href="/products"]').first().click();
await expect(page).toHaveURL(/\/products\/?$/);
await expect(page.locator('main').first()).toBeVisible();
});
test('training CTA is reachable on mobile', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(articlePath);
await page.locator('a[href="/training/"], a[href="/training"]').first().click();
await expect(page).toHaveURL(/\/training\/?$/);
await expect(page.locator('main').first()).toBeVisible();
});
test('main navigation can open the blog index', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('navigation').first()).toBeVisible();
await page.getByRole('link', { name: /blog|記事|articles/i }).first().click();
await expect(page).toHaveURL(/blog/);
});
});
Di situs multilingual, teks CTA dapat berubah. Untuk aksi penting, href stabil atau data-testid berbasis bisnis lebih aman.
Selector yang tidak mudah flaky
| Prioritas | Selector | Contoh | Alasan |
|---|---|---|---|
| Tinggi | Role dan nama | page.getByRole('button', { name: /save/i }) | Dekat dengan pengalaman user dan aksesibilitas |
| Tinggi | Label | page.getByLabel(/email/i) | Memastikan semantik form |
| Sedang | Text | page.getByText(/Start trial/) | Jelas, tapi rentan perubahan copy |
| Sedang | Test id | page.getByTestId('checkout-submit') | Baik untuk aksi bisnis stabil |
| Rendah | Struktur CSS | .card:nth-child(3) | Mudah rusak saat layout berubah |
Hindari page.waitForTimeout(). Gunakan toBeVisible(), toHaveURL(), dan toContainText() agar Playwright menunggu kondisi nyata.
Debug dengan Trace Viewer
npx playwright test --trace on
npx playwright show-report
npx playwright show-trace test-results/path-to-trace/trace.zip
Saat mengembalikan failure ke Claude Code, sertakan nama test, kondisi visual dari trace, dan perilaku user yang diharapkan. Ini mengarah ke perbaikan selector, data setup, atau UI, bukan sekadar menambah timeout.
CI dengan retry dan artifact
# .github/workflows/playwright.yml
name: Playwright E2E
on:
pull_request:
push:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: site
env:
BASE_URL: http://127.0.0.1:4321
PLAYWRIGHT_WEB_SERVER: "1"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: site/package-lock.json
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: site/playwright-report
retention-days: 7
Retry tidak menyembuhkan flaky test; retry hanya mengklasifikasikan dan meninggalkan bukti.
Pitfall yang sering terjadi
Pertama, semua hal dipaksa menjadi E2E. Perhitungan, validasi, dan permission kecil lebih cocok di unit atau integration test.
Kedua, auth state ikut ter-commit. Gunakan user test dengan permission terbatas dan keluarkan playwright/.auth dari git.
Ketiga, instability ditutupi retry. Jika test hanya lulus setelah retry, risikonya masih ada.
Keempat, scope untuk Claude Code terlalu luas. Tambahkan failing test, baca trace, lalu lakukan perbaikan produk sekecil mungkin.
Untuk mulai sendiri, gunakan template ClaudeCodeLab di products. Untuk tim, training membantu menyamakan aturan review, CI, dan onboarding.
Saya mencoba workflow ini pada halaman artikel lokal bergaya ClaudeCodeLab: screenshot 390px, overflow code block, navigasi /products/ dan /training/, serta konfigurasi retry CI. Failure pertama yang berguna bukan berasal dari Playwright, tetapi dari baris kode terlalu panjang dan nama link yang ambigu. Setelah itu diperbaiki, Trace Viewer menjadi bukti konkret untuk prompt Claude Code berikutnya.
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
Permission receipt Claude Code: mencatat scope, bukti, dan rollback
Pola permission receipt untuk Claude Code: aksi yang diizinkan, batas approval, command verifikasi, rollback, dan cek CTA revenue.
Agent Harness Aman untuk Claude Code dan Codex: Permission, Verifikasi, dan Rollback
Rancang Agent Harness praktis untuk Claude Code dan Codex dengan policy, plan, verification, dan recovery layer.
Subagent Claude Code: panduan praktis untuk delegasi artikel dan kode
Panduan subagent Claude Code untuk membagi pekerjaan artikel dan kode: aturan delegasi, prompt, risiko, dan checklist.