A production-ready API test automation framework built on Playwright
Token caching · Controller pattern · Environment switching · Parallel + serial CRUD flows
| Feature | Description |
|---|---|
| 🔐 Token Caching | Module-level cache + in-flight dedup — 4 token calls across an entire suite |
| 🏗️ Controller Pattern | Thin API wrappers keep tests focused on assertions, not URL strings |
| 🌍 Environment Switching | ENV=dev / ENV=prod with a single flag, no code changes |
| ⚡ Parallel + Serial | Independent tests run in parallel; stateful CRUD flows run serial |
| 📊 HTML Reports | Built-in Playwright HTML report with one command |
playwright-api-starter/
├── controllers/
│ ├── BaseController.js # Shared HTTP methods + auth headers
│ ├── products/
│ │ └── ProductsController.js # Products API wrapper
│ └── users/
│ └── UsersController.js # Users API wrapper
├── tests/
│ └── products/
│ └── products.spec.js # GET list + CRUD flow tests
├── utils/
│ ├── authHelper.js # Token fetching with caching + deduplication
│ └── config.js # Environment-aware config
├── playwright.config.js
├── .env.example # Template — copy to .env and fill in values
└── package.json
# 1. Clone the repo
git clone https://github.com/Sehajpreet01/playwright-api-framework/settings
cd playwright-api-starter
# 2. Install dependencies
npm install
npx playwright install
# 3. Set up your environment
cp .env.example .env
# Edit .env with your API base URL and credentialsMake sure you have these installed before continuing:
| Tool | Min Version | Check |
|---|---|---|
| Node.js | 18+ | node -v |
| npm | 9+ | npm -v |
git clone https://github.com/your-username/playwright-api-starter.git
cd playwright-api-starternpm installThis installs:
@playwright/test— test runner and HTTP clientdotenv— loads.envfilescross-env— cross-platform environment variable support
npx playwright installNote: Playwright bundles its own browser binaries. This downloads them locally — internet access required on first run.
# On Mac/Linux
cp .env.example .env
# On Windows
copy .env.example .envOpen .env and fill in your values:
# ── Dev environment ────────────────────────────────
BASE_URL_dev=https://api.dev.example.com
TOKEN_URL_dev=https://auth.dev.example.com/oauth/token
CLIENT_ID_dev=your_dev_client_id
CLIENT_SECRET_dev=your_dev_client_secret
# ── Prod environment ───────────────────────────────
BASE_URL_prod=https://api.example.com
TOKEN_URL_prod=https://auth.example.com/oauth/token
CLIENT_ID_prod=your_prod_client_id
CLIENT_SECRET_prod=your_prod_client_secretNever commit your
.envfile. It is already listed in.gitignore.
Run the test suite against dev to confirm everything is wired up:
npm run test:devYou should see output like:
Running 6 tests using 4 workers
✓ [products] › GET /products returns a list (312ms)
✓ [products] › CRUD › POST creates a product (541ms)
✓ [products] › CRUD › GET returns the product (198ms)
✓ [products] › CRUD › PATCH updates the product (221ms)
✓ [products] › CRUD › DELETE removes the product (189ms)
✓ [products] › GET /products returns 401 without token (102ms)
6 passed (2.1s)
npm run reportThis opens a browser with a full interactive report showing pass/fail, timings, and request/response details for every test.
npx playwright test --grep @products# Run all tests against dev
npm run test:dev
# Run all tests against prod
npm run test:prod
# Filter by tag
npx playwright test --grep @products
# Open the last HTML report
npm run reportController Pattern — keep tests clean
Every API surface gets its own controller that extends BaseController. Controllers own URLs and request shape — tests stay pure assertions.
// tests/products/products.spec.js
const res = await api.createProduct({ name: 'Widget', price: 9.99 });
expect(res.status()).toBe(201);Adding a new API is 3 steps:
- Create
controllers/orders/OrdersController.jsextendingBaseController - Create
tests/orders/orders.spec.jsfollowing the GET + CRUD pattern - Add new env vars to
.env.exampleandutils/config.js
Token Caching — one call per worker, period
authHelper.js caches tokens at the module level for the lifetime of each Playwright worker process.
- Tokens are reused until
expires_in - 60s(buffer for clock skew) - In-flight deduplication: if multiple
beforeAllblocks fire at the same time, only one/oauth/tokenrequest is made — the rest await the same promise
Result: 4 total /oauth/token calls across a full suite with 4 workers — regardless of test count.
Environment Switching — zero code changes between envs
All credentials, base URLs, and IDs are read from .env using the ENV suffix pattern:
BASE_URL_dev=https://api.dev.example.com
BASE_URL_prod=https://api.example.com
CLIENT_ID_dev=abc123
CLIENT_ID_prod=xyz789
Switch by setting ENV at runtime:
npm run test:dev # ENV=dev → reads *_dev vars
npm run test:prod # ENV=prod → reads *_prod varsbeforeAll, not beforeEach — no redundant auth calls
Auth tokens are fetched once per describe block (beforeAll), not before every test. Combined with module-level caching, this eliminates all redundant auth calls.
Serial CRUD Flows — controlled state across tests
Tests that share state (create → get → update → delete) are grouped in test.describe.serial. Independent tests (GET list, 401, 403) live in a separate describe block and run in parallel.
// Parallel — no shared state
test.describe('GET /products', () => { ... });
// Serial — each test depends on the previous
test.describe.serial('Product CRUD', () => {
test('POST creates a product', ...);
test('GET returns the product', ...);
test('PATCH updates the product', ...);
test('DELETE removes the product', ...);
});Copy .env.example to .env and fill in your values:
# Base URLs
BASE_URL_dev=https://api.dev.example.com
BASE_URL_prod=https://api.example.com
# OAuth credentials (dev)
CLIENT_ID_dev=your_client_id
CLIENT_SECRET_dev=your_client_secret
TOKEN_URL_dev=https://auth.dev.example.com/oauth/token
# OAuth credentials (prod)
CLIENT_ID_prod=your_client_id
CLIENT_SECRET_prod=your_client_secret
TOKEN_URL_prod=https://auth.example.com/oauth/token| Package | Purpose |
|---|---|
@playwright/test |
Test runner + HTTP client |
dotenv |
Environment variable loading |
cross-env |
Cross-platform ENV= prefix |
Built with ❤️ using Playwright