🎭 Playwright: Test Data & Environment
The Recipe Analogy 🍳
Imagine you’re a chef cooking the same dish in three different kitchens—your home, a friend’s place, and a fancy restaurant.
Each kitchen has:
- Different ingredients (test data)
- Different ovens and tools (environment variables)
- Different portion sizes (parameterized tests)
But you want the same delicious result every time!
That’s exactly what Playwright’s test data and environment configuration does. It lets your tests “cook” perfectly in ANY kitchen!
🌿 Environment Variables
What Are They?
Think of environment variables like secret notes you stick on your refrigerator.
📌 Note on fridge: "Sugar jar is in the TOP cabinet"
Your tests read these notes to know where things are and how to behave.
Real Example
You have a website. In development, it’s at localhost:3000. In production, it’s at myapp.com.
Without environment variables:
// ❌ Hard to change!
await page.goto('http://localhost:3000');
With environment variables:
// ✅ Reads from your "note"!
await page.goto(process.env.BASE_URL);
How to Set Them
Method 1: In your terminal
BASE_URL=http://localhost:3000 npx playwright test
Method 2: Create a .env file
BASE_URL=http://localhost:3000
API_KEY=my-secret-key
TEST_USER=alice@test.com
Method 3: In playwright.config.ts
export default defineConfig({
use: {
baseURL: process.env.BASE_URL
|| 'http://localhost:3000',
},
});
🎯 Pro Tip
Never put real passwords in your code! Use environment variables:
// ✅ Safe!
const password = process.env.TEST_PASSWORD;
// ❌ Never do this!
const password = 'my-actual-password-123';
📦 Test Data Management
The Toy Box Approach
Imagine your tests are kids who need toys to play.
Bad approach: Each kid brings random toys. Chaos!
Good approach: You have a organized toy box where everyone knows what’s inside.
Creating a Test Data File
Make a file called testData.ts:
export const users = {
admin: {
email: 'admin@test.com',
password: 'AdminPass123',
role: 'administrator'
},
regular: {
email: 'user@test.com',
password: 'UserPass456',
role: 'user'
}
};
export const products = [
{ name: 'Red Ball', price: 10 },
{ name: 'Blue Car', price: 25 },
];
Using Test Data in Tests
import { users, products } from './testData';
test('admin can see all users', async ({ page }) => {
// Use data from your "toy box"
await page.fill('#email', users.admin.email);
await page.fill('#password', users.admin.password);
await page.click('button[type="submit"]');
// Now logged in as admin!
});
📊 Data Flow Diagram
graph TD A["testData.ts"] --> B["Test File 1"] A --> C["Test File 2"] A --> D["Test File 3"] B --> E["Same Data Everywhere!"] C --> E D --> E
Fixtures: Shared Setup
Playwright has fixtures—like a helpful assistant who prepares everything before each test.
// fixtures.ts
import { test as base } from '@playwright/test';
import { users } from './testData';
export const test = base.extend({
loggedInPage: async ({ page }, use) => {
// Setup: Log in before test
await page.goto('/login');
await page.fill('#email', users.regular.email);
await page.fill('#password', users.regular.password);
await page.click('button[type="submit"]');
// Give the logged-in page to the test
await use(page);
// Cleanup: Log out after test
await page.click('#logout');
},
});
Now your tests get a pre-logged-in page:
import { test } from './fixtures';
test('user sees dashboard', async ({ loggedInPage }) => {
// Already logged in! 🎉
await expect(loggedInPage).toHaveURL('/dashboard');
});
🔄 Parameterized Tests
The Cookie Cutter Concept
You want to make cookies in 5 different shapes using the same dough.
Instead of writing 5 separate recipes, you use ONE recipe with different cookie cutters.
Basic Parameterization
const browsers = ['chromium', 'firefox', 'webkit'];
for (const browser of browsers) {
test(`works on ${browser}`, async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle('My App');
});
}
This creates 3 tests from ONE code block!
Testing Multiple Users
const testUsers = [
{ name: 'Alice', email: 'alice@test.com' },
{ name: 'Bob', email: 'bob@test.com' },
{ name: 'Charlie', email: 'charlie@test.com' },
];
for (const user of testUsers) {
test(`${user.name} can sign up`, async ({ page }) => {
await page.goto('/signup');
await page.fill('#name', user.name);
await page.fill('#email', user.email);
await page.click('#submit');
await expect(page.locator('.welcome'))
.toContainText(`Hello, ${user.name}!`);
});
}
Using test.describe for Organization
const screenSizes = [
{ name: 'Mobile', width: 375, height: 667 },
{ name: 'Tablet', width: 768, height: 1024 },
{ name: 'Desktop', width: 1920, height: 1080 },
];
for (const size of screenSizes) {
test.describe(`${size.name} view`, () => {
test.use({
viewport: {
width: size.width,
height: size.height
}
});
test('menu is visible', async ({ page }) => {
await page.goto('/');
await expect(page.locator('nav'))
.toBeVisible();
});
});
}
🎯 Real-World Pattern
Testing a form with valid and invalid data:
const formTests = [
{
scenario: 'valid email',
email: 'good@email.com',
shouldPass: true
},
{
scenario: 'missing @',
email: 'bademail.com',
shouldPass: false
},
{
scenario: 'empty email',
email: '',
shouldPass: false
},
];
for (const { scenario, email, shouldPass } of formTests) {
test(`form with ${scenario}`, async ({ page }) => {
await page.goto('/contact');
await page.fill('#email', email);
await page.click('#submit');
if (shouldPass) {
await expect(page.locator('.success'))
.toBeVisible();
} else {
await expect(page.locator('.error'))
.toBeVisible();
}
});
}
🏆 Putting It All Together
Here’s how everything works together:
graph TD A[".env file"] --> B["Environment Variables"] B --> C["playwright.config.ts"] D["testData.ts"] --> E["Test Files"] C --> E E --> F["Parameterized Tests"] F --> G["Run on Chrome"] F --> H["Run on Firefox"] F --> I["Run on Safari"]
Complete Example
.env
BASE_URL=http://localhost:3000
TEST_PASSWORD=SecretPass123
testData.ts
export const users = [
{ name: 'Alice', email: 'alice@test.com' },
{ name: 'Bob', email: 'bob@test.com' },
];
playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: process.env.BASE_URL,
},
});
login.spec.ts
import { test, expect } from '@playwright/test';
import { users } from './testData';
for (const user of users) {
test(`${user.name} can log in`, async ({ page }) => {
await page.goto('/login');
await page.fill('#email', user.email);
await page.fill('#password', process.env.TEST_PASSWORD!);
await page.click('#submit');
await expect(page.locator('.welcome'))
.toContainText(user.name);
});
}
🎉 You Did It!
You now understand:
| Concept | What It Does |
|---|---|
| Environment Variables | Store secrets & settings outside code |
| Test Data Management | Organize reusable test data |
| Parameterized Tests | Run same test with different inputs |
Your tests can now run anywhere, with any data, using any configuration—just like a master chef cooking in any kitchen! 👨🍳
📌 Quick Reference
// Environment variable
const url = process.env.BASE_URL;
// Test data import
import { users } from './testData';
// Parameterized test
for (const user of users) {
test(`test for ${user.name}`, async ({ page }) => {
// test code here
});
}
Remember: Keep your tests flexible, your data organized, and your secrets safe! 🔐
