Environment variables help you store sensitive information such as URLs, usernames, passwords, API keys, and tokens outside your source code.
Step 1: Install dotenv Package
Playwright does not automatically load .env files, so install the dotenv package.
npm install dotenv
Step 2: Create a .env File
Create a file named .env in the project root directory.
BASE_URL=https://opensource-demo.orangehrmlive.com
USERNAME=Admin
PASSWORD=admin123
Project Structure:
PlaywrightProject/
│
├── .env
├── playwright.config.ts
├── tests/
└── package.json
Step 3: Load Environment Variables
Open playwright.config.ts.
import { defineConfig } from '@playwright/test';
import dotenv from 'dotenv';
dotenv.config();
export default defineConfig({
use: {
baseURL: process.env.BASE_URL
}
});
Now Playwright can access variables from .env.
Step 4: Use Environment Variables in Tests
Example:
import { test, expect } from '@playwright/test';
test('Login Test', async ({ page }) => {
await page.goto('/');
await page.fill('input[name="username"]', process.env.USERNAME!);
await page.fill('input[name="password"]', process.env.PASSWORD!);
await page.click('button[type="submit"]');
await expect(page.locator('.oxd-topbar-header-title'))
.toContainText('Dashboard');
});
Step 5: Create a Separate Environment Configuration File
Create config/env.ts
import dotenv from 'dotenv';
dotenv.config();
export const env = {
baseUrl: process.env.BASE_URL || '',
username: process.env.USERNAME || '',
password: process.env.PASSWORD || ''
};
Use it in your test:
import { test } from '@playwright/test';
import { env } from '../config/env';
test('Login Test', async ({ page }) => {
await page.goto(env.baseUrl);
await page.fill('input[name="username"]', env.username);
await page.fill('input[name="password"]', env.password);
});
This approach is cleaner and easier to maintain.
Step 6: Use Different .env Files for Different Environments
Create multiple files:
.env.dev
.env.qa
.env.stage
.env.prod
Example:
.env.qa
BASE_URL=https://qa.example.com
USERNAME=qauser
PASSWORD=qapassword
.env.prod
BASE_URL=https://prod.example.com
USERNAME=produser
PASSWORD=prodpassword
Step 7: Load Environment-Specific Files
Create env.ts
import dotenv from 'dotenv';
const environment = process.env.TEST_ENV || 'qa';
dotenv.config({
path: `.env.${environment}`
});
Run tests:
QA
TEST_ENV=qa npx playwright test
Production
TEST_ENV=prod npx playwright test
Step 8: Use Environment Variables in Playwright Config
import { defineConfig } from '@playwright/test';
import dotenv from 'dotenv';
dotenv.config();
export default defineConfig({
use: {
baseURL: process.env.BASE_URL,
headless: process.env.HEADLESS === 'true'
}
});
.env
BASE_URL=https://example.com
HEADLESS=false
Step 9: Use Environment Variables in Custom Fixtures
import { test as base } from '@playwright/test';
export const test = base.extend({
credentials: async ({}, use) => {
await use({
username: process.env.USERNAME,
password: process.env.PASSWORD
});
}
});
Usage:
import { test } from '../fixtures/customFixture';
test('Login', async ({ credentials }) => {
console.log(credentials.username);
console.log(credentials.password);
});
Step 10: Best Practices
✔ Store Sensitive Data in .env
USERNAME=Admin
PASSWORD=admin123
API_KEY=xyz123
✔ Add .env to .gitignore
.env
.env.*
✔ Use Separate Files Per Environment
.env.dev
.env.qa
.env.stage
.env.prod
✔ Create a Centralized Environment Helper
export const env = {
baseUrl: process.env.BASE_URL!,
username: process.env.USERNAME!,
password: process.env.PASSWORD!
};
✔ Validate Required Variables
if (!process.env.BASE_URL) {
throw new Error('BASE_URL is missing');
}
Real-World Framework Structure
project
│
├── .env.qa
├── .env.prod
│
├── config
│ └── env.ts
│
├── fixtures
│ └── customFixture.ts
│
├── pages
│ └── LoginPage.ts
│
├── tests
│ └── login.spec.ts
│
└── playwright.config.ts
This structure is commonly used in enterprise Playwright frameworks because it supports multiple environments, keeps secrets secure, and makes tests easier to maintain.