Mengelola test data: fixtures, factories, data seeding, cleanup strategies, dan Playwright fixtures untuk test data yang reliable dan isolated

Setelah di episode 9 kita membahas E2E strategy, pada episode ini kita mendalami komponen yang sering diabaikan: test data. Test data yang buruk menyebabkan flaky tests, false positives, dan maintenance nightmare.
// fixtures/users.ts
export const users = {
valid: {
name: 'John Doe',
email: 'john@test.com',
password: 'SecurePass123!'
},
invalid: {
name: '',
email: 'not-an-email',
password: '1'
},
expiredToken: {
name: 'Expired User',
token: 'expired-token-value'
}
};// factories/userFactory.ts
let userCounter = 0;
export function createUser(overrides = {}) {
userCounter++;
return {
name: `Test User ${userCounter}`,
email: `user${userCounter}@test.com`,
password: 'TestPass123!',
...overrides
};
}
export function createAdmin(overrides = {}) {
return createUser({
name: 'Admin User',
email: `admin${Date.now()}@test.com`,
role: 'admin',
...overrides
});
}// fixtures.ts
import { test as base } from '@playwright/test';
import { createUser } from './factories/userFactory';
type TestFixtures = {
user: ReturnType<typeof createUser>;
authenticatedPage: any;
};
export const test = base.extend<TestFixtures>({
user: async ({}, use) => {
const user = createUser();
// Setup: create user in database
await createTestUser(user);
await use(user);
// Cleanup: remove user
await deleteTestUser(user.email);
},
authenticatedPage: async ({ page, user }, use) => {
// Auto-login
await page.goto('/login');
await page.fill('#email', user.email);
await page.fill('#password', user.password);
await page.click('#login-button');
await expect(page).toHaveURL('/dashboard');
await use(page);
},
});// seed.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function seedTestData() {
// Create test users
await prisma.user.createMany({
data: [
{ name: 'User 1', email: 'user1@test.com', password: 'hashed' },
{ name: 'User 2', email: 'user2@test.com', password: 'hashed' },
]
});
// Create test products
await prisma.product.createMany({
data: [
{ name: 'Product A', price: 29.99 },
{ name: 'Product B', price: 49.99 },
]
});
}
// Cleanup
export async function cleanupTestData() {
await prisma.user.deleteMany({ where: { email: { contains: '@test.com' } } });
await prisma.product.deleteMany();
}Data Isolation Options:
├── Per-test: buat & hapus data di setiap test (slow tapi isolated)
├── Per-describe: buat data sekali, cleanup setelah suite
├── Per-run: seed sekali di awal, reset antar run
├── Database transactions: rollback setelah test
└── Separate database: test DB terpisah dari productionWarning
Jangan pernah pakai production data untuk testing. Gunakan test data yang terisolasi dan bisa di-reproduce.
// Setup test data
test.beforeAll(async () => {
await seedTestData();
});
// Cleanup
test.afterAll(async () => {
await cleanupTestData();
});
// Per-test cleanup
test.afterEach(async ({ page }) => {
await page.context().clearCookies();
await page.evaluate(() => localStorage.clear());
});Di episode 11 selanjutnya kita akan membahas CI/CD integration — menjalankan test di CI, parallelization, dan reporting. Sampai jumpa di episode 11!