Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Playwright testing guide covering E2E, component, API, visual, accessibility, and security tests.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
core/test-data.md
1# Test Data Factories & Generators23This file covers **reusable test data builders** (factories, Faker, data generators). For related topics:45- **Per-test database fixtures** (isolation, transaction rollback): See [fixtures-hooks.md](fixtures-hooks.md#database-fixtures)6- **One-time database setup** (migrations, snapshots): See [global-setup.md](global-setup.md#database-patterns)78## Table of Contents9101. [Factory Pattern](#factory-pattern)112. [Faker Integration](#faker-integration)123. [Data-Driven Testing](#data-driven-testing)134. [Test Data Fixtures](#test-data-fixtures)145. [Database Seeding](#database-seeding)1516## Factory Pattern1718### Basic Factory1920```typescript21// factories/user.factory.ts22interface User {23id: string;24email: string;25name: string;26role: "admin" | "user" | "guest";27createdAt: Date;28}2930let userIdCounter = 0;3132export function createUser(overrides: Partial<User> = {}): User {33userIdCounter++;34return {35id: `user-${userIdCounter}`,36email: `user${userIdCounter}@test.com`,37name: `Test User ${userIdCounter}`,38role: "user",39createdAt: new Date(),40...overrides,41};42}4344// Usage45const user = createUser();46const admin = createUser({ role: "admin", name: "Admin User" });47```4849### Factory with Traits5051```typescript52// factories/product.factory.ts53interface Product {54id: string;55name: string;56price: number;57stock: number;58category: string;59featured: boolean;60}6162type ProductTrait = "outOfStock" | "featured" | "expensive" | "sale";6364const traits: Record<ProductTrait, Partial<Product>> = {65outOfStock: { stock: 0 },66featured: { featured: true },67expensive: { price: 999.99 },68sale: { price: 9.99 },69};7071let productIdCounter = 0;7273export function createProduct(74overrides: Partial<Product> = {},75...traitNames: ProductTrait[]76): Product {77productIdCounter++;7879const appliedTraits = traitNames.reduce(80(acc, trait) => ({ ...acc, ...traits[trait] }),81{},82);8384return {85id: `prod-${productIdCounter}`,86name: `Product ${productIdCounter}`,87price: 29.99,88stock: 100,89category: "General",90featured: false,91...appliedTraits,92...overrides,93};94}9596// Usage97const product = createProduct();98const featuredProduct = createProduct({}, "featured");99const saleItem = createProduct({ name: "Sale Item" }, "sale", "featured");100const soldOut = createProduct({}, "outOfStock");101```102103### Factory with Relationships104105```typescript106// factories/order.factory.ts107import { createUser, User } from "./user.factory";108import { createProduct, Product } from "./product.factory";109110interface OrderItem {111product: Product;112quantity: number;113}114115interface Order {116id: string;117user: User;118items: OrderItem[];119total: number;120status: "pending" | "paid" | "shipped" | "delivered";121}122123let orderIdCounter = 0;124125export function createOrder(overrides: Partial<Order> = {}): Order {126orderIdCounter++;127128const user = overrides.user ?? createUser();129const items = overrides.items ?? [{ product: createProduct(), quantity: 1 }];130const total = items.reduce(131(sum, item) => sum + item.product.price * item.quantity,1320,133);134135return {136id: `order-${orderIdCounter}`,137user,138items,139total,140status: "pending",141...overrides,142};143}144145// Usage146const order = createOrder();147const bigOrder = createOrder({148items: [149{ product: createProduct({ price: 100 }), quantity: 5 },150{ product: createProduct({ price: 50 }), quantity: 2 },151],152});153```154155## Faker Integration156157### Setup Faker158159```bash160npm install -D @faker-js/faker161```162163```typescript164// factories/faker-user.factory.ts165import { faker } from "@faker-js/faker";166167interface User {168id: string;169email: string;170name: string;171avatar: string;172address: {173street: string;174city: string;175country: string;176zipCode: string;177};178}179180export function createFakeUser(overrides: Partial<User> = {}): User {181return {182id: faker.string.uuid(),183email: faker.internet.email(),184name: faker.person.fullName(),185avatar: faker.image.avatar(),186address: {187street: faker.location.streetAddress(),188city: faker.location.city(),189country: faker.location.country(),190zipCode: faker.location.zipCode(),191},192...overrides,193};194}195```196197### Seeded Faker for Reproducibility198199```typescript200import { faker } from "@faker-js/faker";201202// Set seed for reproducible data203faker.seed(12345);204205export function createDeterministicUser(): User {206return {207id: faker.string.uuid(),208email: faker.internet.email(),209name: faker.person.fullName(),210// Same seed = same data every time211};212}213214// Or seed per test215test("user profile", async ({ page }) => {216faker.seed(42); // Reset seed for this test217const user = createFakeUser();218// user will always have the same data219});220```221222### Faker Fixture223224```typescript225// fixtures/faker.fixture.ts226import { test as base } from "@playwright/test";227import { faker } from "@faker-js/faker";228229type FakerFixtures = {230fake: typeof faker;231};232233export const test = base.extend<FakerFixtures>({234fake: async ({}, use, testInfo) => {235// Seed based on test name for reproducibility236faker.seed(testInfo.title.length);237await use(faker);238},239});240241// Usage242test("create user with fake data", async ({ page, fake }) => {243await page.goto("/signup");244245await page.getByLabel("Name").fill(fake.person.fullName());246await page.getByLabel("Email").fill(fake.internet.email());247await page.getByLabel("Password").fill(fake.internet.password());248249await page.getByRole("button", { name: "Sign Up" }).click();250});251```252253## Data-Driven Testing254255### test.each with Arrays256257```typescript258const loginScenarios = [259{ email: "[email protected]", password: "pass123", expected: "Dashboard" },260{ email: "[email protected]", password: "admin123", expected: "Admin Panel" },261{262email: "[email protected]",263password: "wrong",264expected: "Invalid credentials",265},266];267268for (const { email, password, expected } of loginScenarios) {269test(`login with ${email}`, async ({ page }) => {270await page.goto("/login");271await page.getByLabel("Email").fill(email);272await page.getByLabel("Password").fill(password);273await page.getByRole("button", { name: "Sign In" }).click();274275await expect(page.getByText(expected)).toBeVisible();276});277}278```279280### Parameterized Tests281282```typescript283// data/checkout-scenarios.ts284export const checkoutScenarios = [285{286name: "standard shipping",287shipping: "standard",288expectedDays: "5-7 business days",289expectedCost: "$5.99",290},291{292name: "express shipping",293shipping: "express",294expectedDays: "2-3 business days",295expectedCost: "$14.99",296},297{298name: "overnight shipping",299shipping: "overnight",300expectedDays: "Next business day",301expectedCost: "$29.99",302},303];304```305306```typescript307import { checkoutScenarios } from "./data/checkout-scenarios";308309test.describe("shipping options", () => {310for (const scenario of checkoutScenarios) {311test(`checkout with ${scenario.name}`, async ({ page }) => {312await page.goto("/checkout");313314await page.getByLabel(scenario.shipping, { exact: false }).check();315316await expect(page.getByText(scenario.expectedDays)).toBeVisible();317await expect(page.getByText(scenario.expectedCost)).toBeVisible();318});319}320});321```322323### CSV/JSON Data Source324325```typescript326import fs from "fs";327328interface TestCase {329input: string;330expected: string;331}332333// Load test data from JSON334const testCases: TestCase[] = JSON.parse(335fs.readFileSync("./data/search-tests.json", "utf-8"),336);337338test.describe("search functionality", () => {339for (const { input, expected } of testCases) {340test(`search for "${input}"`, async ({ page }) => {341await page.goto("/search");342await page.getByLabel("Search").fill(input);343await page.getByLabel("Search").press("Enter");344345await expect(page.getByText(expected)).toBeVisible();346});347}348});349```350351## Test Data Fixtures352353### Fixture with Factory354355```typescript356// fixtures/data.fixture.ts357import { test as base } from "@playwright/test";358import { createUser, User } from "../factories/user.factory";359import { createProduct, Product } from "../factories/product.factory";360361type DataFixtures = {362testUser: User;363testProducts: Product[];364};365366export const test = base.extend<DataFixtures>({367testUser: async ({}, use) => {368const user = createUser({ name: "E2E Test User" });369await use(user);370},371372testProducts: async ({}, use) => {373const products = [374createProduct({ name: "Test Product 1" }),375createProduct({ name: "Test Product 2" }),376createProduct({ name: "Test Product 3" }),377];378await use(products);379},380});381382// Usage383test("add product to cart", async ({ page, testUser, testProducts }) => {384// Mock API with test data385await page.route("**/api/user", (route) => route.fulfill({ json: testUser }));386await page.route("**/api/products", (route) =>387route.fulfill({ json: testProducts }),388);389390await page.goto("/products");391await expect(page.getByText(testProducts[0].name)).toBeVisible();392});393```394395## Database Seeding396397### API-Based Seeding398399```typescript400// fixtures/seed.fixture.ts401import { test as base, APIRequestContext } from "@playwright/test";402import { createUser } from "../factories/user.factory";403404type SeedFixtures = {405seedUser: (overrides?: Partial<User>) => Promise<User>;406cleanupUsers: string[];407};408409export const test = base.extend<SeedFixtures>({410cleanupUsers: [],411412seedUser: async ({ request, cleanupUsers }, use) => {413await use(async (overrides = {}) => {414const userData = createUser(overrides);415416const response = await request.post("/api/test/users", {417data: userData,418});419const user = await response.json();420421cleanupUsers.push(user.id);422return user;423});424},425426// Cleanup after test427cleanupUsers: async ({ request }, use) => {428const userIds: string[] = [];429await use(userIds);430431// Delete all created users432for (const id of userIds) {433await request.delete(`/api/test/users/${id}`);434}435},436});437438// Usage439test("user profile page", async ({ page, seedUser }) => {440const user = await seedUser({ name: "John Doe" });441442await page.goto(`/users/${user.id}`);443await expect(page.getByText("John Doe")).toBeVisible();444});445```446447### Transaction Rollback Seeding448449```typescript450// fixtures/db.fixture.ts451export const test = base.extend<{}, { db: DbTransaction }>({452db: [453async ({}, use) => {454const client = await pool.connect();455await client.query("BEGIN");456457await use({458query: (sql: string, params?: any[]) => client.query(sql, params),459seed: async (table: string, data: object) => {460const keys = Object.keys(data);461const values = Object.values(data);462const placeholders = keys.map((_, i) => `$${i + 1}`);463464const result = await client.query(465`INSERT INTO ${table} (${keys.join(", ")}) VALUES (${placeholders.join(", ")}) RETURNING *`,466values,467);468return result.rows[0];469},470});471472await client.query("ROLLBACK");473client.release();474},475{ scope: "test" },476],477});478```479480## Anti-Patterns to Avoid481482| Anti-Pattern | Problem | Solution |483| ------------------------------- | ------------------------------- | -------------------------- |484| Hardcoded test data | Brittle, repetitive | Use factories |485| Random data without seed | Non-reproducible failures | Seed faker per test |486| Shared mutable test data | Tests interfere with each other | Create fresh data per test |487| Manual data creation everywhere | Duplication, maintenance burden | Centralize in factories |488489## Related References490491- **Fixtures**: See [fixtures-hooks.md](fixtures-hooks.md) for fixture patterns492- **API Testing**: See [test-suite-structure.md](test-suite-structure.md) for API mocking493