Playwright has quickly become one of the most powerful end-to-end automation frameworks in the QA Automation space.
Modern automation frameworks are rarely “just UI tests.”
A realistic enterprise test suite needs:
- UI automation
- API validations
- Database assertions
- Cross-layer flows (UI action → API response → DB verification)
Playwright is incredibly powerful because its fixture system allows all three layers—UI, API, and DB—to be integrated cleanly inside a single test runner.
Understanding Playwright Fixtures
Fixtures in Playwright are reusable setup/teardown utilities that help tests stay clean and isolated.
They can:
- Launch a browser
- Authenticate a user
- Provide test data
- Create database connections
- Boot APIs
- Provide page objects
- Manage any shared state safely
Fixtures run before your test starts and clean up after your test completes.
Built-in vs Custom Fixtures
Built-in (from @playwright/test)
browsercontextpagerequesttest(test runner itself)
Custom Fixtures
You can extend Playwright’s test runner to include your custom utilities, such as:
- Authenticated context
- API clients
- Database connection
- Page Object Manager
- Fake test data
- Feature toggles
- Multi-tenant test config
Fixtures make your framework professional and enterprise-grade.
UI Fixtures (Browser, Page, Page Objects)
Let’s extend Playwright with POM support.
fixtures/uiFixture.ts
import { test as base } from '@playwright/test';
import { HomePage } from '../pages/HomePage';
import { LoginPage } from '../pages/LoginPage';
export const test = base.extend({
homePage: async ({ page }, use) => {
await use(new HomePage(page));
},
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
}
});
export { expect } from '@playwright/test';
➡ Provides page objects as fixtures
➡ Every test gets fresh, isolated POM instances
API Fixtures (Authenticated API Client)
Playwright has a built-in request object.
We can create an authenticated API client fixture.
fixtures/apiFixture.ts
import { test as base, APIRequestContext, request } from '@playwright/test';
type ApiFixtures = {
apiClient: APIRequestContext;
generateToken: () => Promise;
};
export const test = base.extend({
generateToken: async ({}, use) => {
const getToken = async () => {
const req = await request.newContext({
baseURL: 'https://api.example.com'
});
const response = await req.post('/auth/token', {
data: {
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
scope: 'read write'
}
});
const json = await response.json();
return json.access_token;
};
await use(getToken);
},
apiClient: async ({ generateToken, request }, use) => {
const token = await generateToken();
const client = await request.newContext({
baseURL: 'https://api.example.com',
extraHTTPHeaders: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
await use(client);
await client.dispose();
}
});
This provides:
- Reusable authenticated API client with fresh tokens
- Already configured baseURL
- Can be accessed across all tests
DB Fixtures (SQL database validation)
There are multiple libraries for sql connection available in the npm. We can use tedious/ mssql based on the OS platform we run.
fixtures/dbFixture.ts
import { test as base } from '@playwright/test';
import { Connection, Request } from 'tedious';
import dbConfig from '../dbConfig';
export const test = base.extend({
db: async ({}, use) => {
const connection = new Connection(dbConfig);
await new Promise((resolve, reject) => {
connection.on('connect', err => err ? reject(err) : resolve(true));
connection.connect();
});
const dbClient = {
query: (sql: string) =>
new Promise((resolve, reject) => {
const rows = [];
const request = new Request(sql, err => err && reject(err));
request.on('row', cols => {
const row = {};
cols.forEach(col => (row[col.metadata.colName] = col.value));
rows.push(row);
});
request.on('requestCompleted', () => resolve(rows));
connection.execSql(request);
})
};
await use(dbClient);
connection.close();
}
});
This fixture returns a DB client with:
db.query("SELECT * FROM Users")
Combined Fixture: UI + API + DB in One Extended Test
Single unified test context: All UI, API, and DB utilities come from one fixture, making tests cleaner and easier to write.
No duplicate setup: Login, token generation, DB connection, page objects, and clients are initialized once and reused everywhere.
Consistent cross-layer flow: Lets you validate a scenario end-to-end — UI action → API response → DB state — using shared objects.
Centralized maintenance: Any change (endpoint URLs, DB config, login logic) is updated in just one fixture and reflected across all tests.
import base from '@playwright/test';
import { test as ui } from './uiFixture';
import { test as api } from './apiFixture';
import { test as db } from './dbFixture';
export const test = mergeTests(ui, api, db);
This gives a single test object with:
page,browser,contexthomePage,loginPageapiClientdb
All injected automatically.
What This Means for Your Framework
Playwright’s fixture system is powerful enough to replace custom frameworks written in:
- Selenium + TestNG
- RestAssured
- JDBC
- Third-party mocking tools
Because with fixtures you get:
- Flexible dependency injection
- Isolated test environment
- Clean abstraction layers
- Full E2E support
Using integrated UI + API + DB fixtures and combined fixtures creates a production-grade, enterprise-ready automation framework—without needing extra tools or complex architecture.