
You're developing a great application with Prisma as your ORM and want to set up robust end-to-end tests with Playwright? Excellent choice! But wait... how do you test your application without data in your test database? That's where the magic of Prisma seeding comes in!
Let's discover together how to create a perfectly orchestrated test environment where your test data automatically generates every time your Playwright tests run.
π€ The Challenge: Testing an Empty Application
Imagine the scene: you've developed a fantastic application with complex features, entity relationships, and sophisticated business workflows. You launch Playwright to test your user interface and... completely empty! No users, no products, no orders. How do you test a shopping cart without products? How do you verify a user profile without a user? π±
This is exactly the problem we're going to solve today by combining the power of Prisma seeding with the efficiency of Playwright tests.
β¨ The Solution: Prisma Seeding to the Rescue!
Prisma ORM offers a built-in seeding feature that allows you to systematically populate your database with consistent test data. This approach ensures that every test run starts from a known and predictable state.
Seeding with Prisma can be triggered in two ways:
- Manually with
prisma db seed
- Automatically during
prisma migrate reset
orprisma migrate dev
π οΈ Configuration Step-by-Step
Step 1: Configure the Seed Script
First, let's add the seeding configuration to our package.json
:
{
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"scripts": {
"db:seed": "prisma db seed",
"db:reset": "prisma migrate reset",
"test:e2e": "playwright test",
"test:e2e:setup": "npm run db:reset && npm run test:e2e"
}
}
Step 2: Create a Robust Seed Script
Now, let's create our prisma/seed.ts
file with realistic data:
import { PrismaClient } from '@prisma/client'
import { faker } from '@faker-js/faker'
const prisma = new PrismaClient()
async function main() {
console.log('π± Starting seeding...')
// Clean up existing database
await prisma.order.deleteMany()
await prisma.product.deleteMany()
await prisma.user.deleteMany()
// Create users
const users = []
for (let i = 0; i < 5; i++) {
const user = await prisma.user.create({
data: {
name: faker.person.fullName(),
email: faker.internet.email(),
password: faker.internet.password(),
},
})
users.push(user)
}
// Create products
const products = []
for (let i = 0; i < 10; i++) {
const product = await prisma.product.create({
data: {
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: parseFloat(faker.commerce.price()),
imageUrl: faker.image.imageUrl(),
},
})
products.push(product)
}
// Create orders
for (const user of users) {
const numOrders = faker.number.int({ min: 0, max: 3 })
for (let i = 0; i < numOrders; i++) {
const orderItems = faker.helpers.arrayElements(products, faker.number.int({ min: 1, max: products.length }))
const totalAmount = orderItems.reduce((sum, item) => sum + item.price, 0)
await prisma.order.create({
data: {
user: {
connect: { id: user.id },
},
items: {
create: orderItems.map(item => ({
product: {
connect: { id: item.id },
},
quantity: faker.number.int({ min: 1, max: 3 }),
price: item.price,
})),
},
total: totalAmount,
status: faker.helpers.arrayElement(['PENDING', 'COMPLETED', 'CANCELLED']),
},
})
}
}
console.log('β
Seeding completed successfully!')
}
main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error('β Error during seeding:', e)
await prisma.$disconnect()
process.exit(1)
})
Step 3: Playwright Test Configuration
Now, let's configure Playwright to use our seeded database. In playwright.config.ts
:
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests',
// Crucial configuration: global setup
globalSetup: require.resolve('./tests/global-setup.ts'),
// Use a dedicated test database
use: {
baseURL: 'http://localhost:3000',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
// Development server for tests
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})
Step 4: Global Setup Script
Let's create tests/global-setup.ts
to orchestrate our test environment:
import { chromium, FullConfig } from '@playwright/test'
import { execSync } from 'child_process'
async function globalSetup(config: FullConfig) {
console.log('π Preparing the test environment...')
// Reset and seed the database
execSync('npm run db:reset -- --force', { stdio: 'inherit' })
console.log('β
Database ready for tests!')
}
export default globalSetup
π― Writing Effective Playwright Tests
With our setup in place, our tests become more predictable and robust: import test, expect from '@playwright/test'
test.describe('E-commerce Flow', () => {
test('user can browse and purchase products', async ({ page }) => {
await page.goto('/')
// Verify that products are present (thanks to seeding!)
await expect(page.locator('[data-testid="product-card"]')).toHaveCount(10)
// Click on the first product
await page.locator('[data-testid="product-card"]').first().click()
// Add to cart
await page.click('[data-testid="add-to-cart"]')
// Verify the cart
await page.goto('/cart')
await expect(page.locator('[data-testid="cart-item"]')).toHaveCount(1)
// Proceed to checkout
await page.click('[data-testid="checkout-button"]')
await expect(page).toHaveURL(/.*checkout/)
})
test('user can view their profile with their orders', async ({ page }) => {
await page.goto('/profile')
// Thanks to seeding, we know there are orders
await expect(page.locator('[data-testid="order-history"]')).toBeVisible()
await expect(page.locator('[data-testid="order-item"]')).toHaveCount.toBeGreaterThan(0)
})
})
π CI/CD Integration with GitHub Actions
For full integration into your pipeline, here's an example GitHub action:
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:13
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: testdb
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Setup database
run: |
npm run db:migrate
npm run db:seed
env:
DATABASE_URL: postgresql://postgres:test@localhost:5432/testdb
- name: Install Playwright
run: npx playwright install
- name: Run tests
run: npm run test:e2e
env:
DATABASE_URL: postgresql://postgres:test@localhost:5432/testdb
π The Benefits of this Approach
This Prisma + Playwright + Seeding integration strategy brings you:
π Consistency: Every test starts from the same state, eliminating false positives/negatives
β‘ Speed: No need for tedious manual setup, everything is automated
π― Realism: Your tests use data that resembles reality thanks to Faker.js
π Isolation: Each test run is isolated and predictable
π° Savings: Drastic reduction in test maintenance time
With this configuration, your Playwright tests become a true safety net for your application, allowing you to deploy with confidence!
So, ready to transform your tests into a well-oiled machine? π