A good automation framework is less about the tool and more about the structure around it. Here's the shape I keep coming back to for Playwright projects that need to cover both the UI and the API.
Start with the layers
I split tests into clear layers so each one has a single responsibility:
- UI specs drive the browser through user journeys.
- API specs hit endpoints directly for fast, deterministic coverage.
- Fixtures wire up shared setup (auth, test data, page objects).
import { test as base } from "@playwright/test";
import { LoginPage } from "./pages/login.page";
export const test = base.extend<{ loginPage: LoginPage }>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
});Page Object Model, but lightweight
The Page Object Model keeps selectors out of specs, but it's easy to over-engineer. I keep page objects thin — locators and a few intent-revealing methods — and let the specs describe the behaviour.
CI/CD from day one
The framework only earns its keep when it runs on every change. I wire Playwright into GitHub Actions early so the team gets fast feedback and a shift-left safety net rather than a slow, end-of-sprint regression crunch.
The best time to add CI is when you have one passing test. The second best time is now.
That's the core of it: clear layers, thin page objects, and automation that runs continuously.