You have the pieces. This lecture assembles them, because a suite is more than a pile of tests and the difference is what a team actually experiences.
Decide what goes in it.
Not everything. A regression suite protects the journeys that cost money when they break: sign in, the main create-and-view flow, checkout, and the permissions that keep users out of each other's data.
Anything cheaper to check elsewhere should be checked elsewhere. Validation rules, formatting, date maths: unit tests, or API tests with Playwright's request fixture. Putting them in the browser makes the suite slow without making it safer.
Arrange it by feature.
tests/
smoke/
login.spec.ts
dashboard.spec.ts
contacts/
create.spec.ts
search.spec.ts
delete.spec.ts
checkout/
payment.spec.ts
fixtures/
test.ts your extended test with custom fixtures
data.ts builders for unique test data
pages/
ContactsPage.ts page objects
playwright.config.ts
smoke runs first on every commit. If it fails, nothing else needs to run.
One extended test for the whole project. This is the Playwright-specific piece that ties everything together:
// fixtures/test.ts
export const test = base.extend<Fixtures>({
loggedInPage: async ({ page }, use) => { /* ... */ },
contact: async ({ request }, use) => { /* ... */ },
contactsPage: async ({ page }, use) => {
await use(new ContactsPage(page));
},
});
export { expect } from '@playwright/test';
Every spec imports from here instead of from @playwright/test. Now a test declares what it needs and gets it, with cleanup handled:
import { test, expect } from '../fixtures/test';
test('deleting a contact removes it from the list', async ({ contactsPage, contact }) => {
await contactsPage.goto();
await contactsPage.delete(contact.name);
await expect(contactsPage.row(contact.name)).toBeHidden();
});
Read that test. It says what it does and nothing else. No login, no setup, no cleanup, no selectors. That is the target.
Keep every test independent. The rule the whole track has built toward:
- unique data, created by the test
- session through
storageStateor a fixture, never from a previous test - arrange through the API where the UI is not the point
- assert on your own records, never on totals you do not control
A suite built this way runs in parallel and shards across machines. A suite that is not cannot be made parallel later without a rewrite, which is why this is decided at the start.
When it gets slow, in order:
- Arrange through the API. Usually the biggest single win.
- Reuse authentication.
storageStateturns eighty logins into one. - Increase workers, then shard. Only works if tests are independent.
- Cut browsers on the fast run. One engine per commit, all three nightly.
- Delete tests. The unpopular one. A test that has never failed in two years, on a screen nobody uses, costs you time on every run. Deleting it is a decision, not a defeat.
Agree the flaky rule before you need it. On a calm day, decide: a test that fails intermittently is fixed within a set time or deleted. What kills suites is the middle option, where flaky tests linger for months and the team learns to shrug at red.
Remember this: a test suite is a product, its users are your team, and it only works if they trust the red.
Reference: Best practices.