DEV Community

RamaMallika Kadali
RamaMallika Kadali

Posted on

Day 6: How I Organize and Scale Playwright Tests

Let’s be real — once your Playwright test suite grows beyond a few dozen tests, things can get chaotic fast.

When I first started using Playwright, I was focused on writing passing tests — that’s the goal, right?
But soon I hit that moment every automation engineer faces:

“Why is this file 500 lines long?”
“Where did I define that login flow again?”
“Why is half of my time spent maintaining tests instead of writing them?”

If you’ve been there, you’re not alone. Today, I’ll share how I organize and scale Playwright tests in a way that’s clean, modular, and honestly — sane.

Your Folder Structure Should Think Like Your App
I’ve made the mistake of lumping all tests into a single tests/ folder. It works for 5 tests. Not 50.

Here’s the structure I now swear by:

pgsql

tests/
├── login/
│ ├── login.spec.ts
│ └── loginHelper.ts
├── dashboard/
│ ├── dashboard.spec.ts
│ └── dashboardHelper.ts
fixtures/
├── test-fixtures.ts
pages/
├── LoginPage.ts
├── DashboardPage.ts
playwright.config.ts
Each folder mirrors part of the app — login, dashboard, reports, etc.
Helper files live beside their tests. Page classes live in pages/.
It’s intuitive, clean, and easier for anyone jumping into the repo.

Use Page Object Model
I used to repeat selectors in every test. Then I found Page Object Model (POM) — and never looked back.

Instead of writing:

ts

await page.fill('#username', 'admin');
await page.fill('#password', 'secret');
await page.click('button[type="submit"]');
I now use:

ts

await loginPage.login('admin', 'secret');
Behind the scenes, that’s just a LoginPage class:

ts

export class LoginPage {
constructor(private page: Page) {}

async login(username: string, password: string) {
await this.page.fill('#username', username);
await this.page.fill('#password', password);
await this.page.click('button[type="submit"]');
}
}
Cleaner. Reusable. Future-proof.

Stop Repeating Setup – Use Fixtures
I can’t tell you how many times I copy-pasted login code into every test — until I discovered Playwright fixtures.

Here’s a basic setup:

ts

test.beforeEach(async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.login('user', 'pass');
});
Even better, you can define custom fixtures like loggedInPage and just use them in your test.
It’s like handing your future self a debugging gift.

Use Tags to Keep Sanity in CI
Ever wanted to just run the smoke tests? Or only test the admin role?
Use tags:

ts

test('@smoke Login works as expected', async ({ page }) => {
// test code
});
Then run:

bash

npx playwright test --grep @smoke
It’s a small step, but it makes a big difference in large test suites.

Keep Your Tests Independent and Clean
Here’s a hard truth I’ve learned:
If your tests rely on each other, one bug can break your whole suite.

Some things that help me:

Use APIs to create test users or reset data.

Clean up test-created records.

Never let one test depend on another’s leftovers.

Is it extra work up front? Yes.
Does it save you from 2 AM CI failures? Absolutely.

Pro Tips
Use page.pause() liberally during test writing.

Name your tests clearly — your future self will thank you.

Don’t log everything — just the meaningful stuff.

Use parallel projects for testing different roles or environments.

Conclusion
You can have the best test framework in the world…
…but if it’s a mess, it’s going to slow you down.

Test organization isn’t flashy, but it’s one of the most valuable things you can do to scale automation with confidence.

Top comments (0)