Hi there 👋! This is my first post since I joined dev.to
I’m a Junior QA Engineer who recently transitioned from Customer Support into QA.
While I don’t have tons of experience yet—especially when it comes to discussing automated testing at a deep level. However, during my day-to-day work as a QA Engineer, I’ve noticed one important thing:
Many QA Engineers write automated tests, but not all of them truly understand their test structure.
And... that often becomes a problem.
Without a clear and well-thought-out test structure, automated tests can quickly become:
- Hard to maintain
- Difficult to debug when tests fail
- Risky to scale as the product grows
One simple but often overlooked improvement is how we structure our automated tests.
Even though test structure sounds very basic, many QA Engineers—especially those who are new to automation or have limited hands-on experience with real projects—may not be fully aware of its importance.
In this post, I want to share a few simple tips about managing test structure, based on what I’ve learned so far.
It might be a small topic, but I believe it has a big impact.
Hopefully, through this tiny post, we can become better QA Engineers—not only in terms of technical skills, but also in:
- Business mindset
- Risk awareness
- Long-term maintainability thinking
Let’s get started 🚀
I'll use my automated-test project as a reference, and why this approach works better than sticking to the default structure.
📂 Default Playwright Test Structure (The Starting Point)
tests/
├── example.spec.ts
├── another-test.spec.ts
playwright.config.ts
This is totally fine for:
- demos
- tutorials
- very small projects
But as tests grow, this structure quickly starts to hurt you.
Common problems with the default structure:
- Selectors are duplicated across test files
- Tests become long and hard to read
- UI changes require updating many test files
- Debugging failed tests takes longer
That’s why, in my project, I decided to move away from the default structure early.
🧱 Custom Test Structure Used in the Repository
Instead of placing everything directly inside *.spec.ts files, I separated concerns clearly.
High-level structure (simplified):
tests/
├── pages/
├── fixtures/
├── utils/
├── ai/
│ └── ai-movie.spec.ts
📄 1. Page Object Model (POM)
The pages/ folder contains page classes that handle:
- selectors
- page actions (click, fill, submit, etc.)
Instead of writing selectors directly in test files, tests interact with the page through methods like:
- search movie
- submit prompt
- read AI response
Why this matters:
- UI changes only affect page files
- Test files stay clean and readable
- Less selector duplication
👉 Compared to the default Playwright approach, this dramatically reduces maintenance cost.
🔁 2. Fixtures for Shared Setup
The fixtures/ folder contains reusable setup logic, such as:
- navigating to the app
- preparing test state
- shared test configuration
This prevents every test from repeating the same setup steps.
Advantages over default structure:
- tests focus on behavior, not setup
- easier to standardize test flow
- fewer copy-paste mistakes
🧰 3. Utilities for Complex Logic
In AI-related testing, some validations are not trivial.
For example:
- Checking response format
- Validating content rules
- Handling async behavior consistently
Those logic pieces live in utils/.
Why this beats default structure:
- assertions stay reusable
- test files don’t become bloated
- logic can be improved without touching tests
📁 4. Feature-Based Test Grouping
Instead of random spec files, tests are grouped by feature.
For example:
- AI-related tests live in their own folder
- future features can follow the same pattern
Compared to default structure:
- Easier navigation
- Clearer ownership
- Better scalability as features grow
🧠 Final Thoughts
Even as a junior QA Engineer, I’ve learned that good structure is a skill, not a luxury.
You don’t need:
- A huge project
- Years of experience
- Perfect architecture
What you do need is the mindset that:
Automated tests are long-term assets, not one-time scripts.
By moving beyond Playwright’s default structure and organizing tests intentionally, you:
- Reduce future pain
- Improve collaboration
- Build better testing habits early
Hopefully, this post helps other junior QAs avoid the mistakes I’m still learning from 🚀
Feel free to take a look onto my project here and make it as reference
Let's connect!
Top comments (1)
Hi Mohammed. Congrats on your first post 👏
Good write-up overall, but I wanted to point out a few things that are worth clarifying:
Fixtures are meant to establish and control test environment state (auth, data setup/teardown, scoped or expensive resources). Navigation on its own usually isn’t a fixture; it’s either part of the test’s intent or belongs inside a page object. Misuse of fixtures for convenience can actually hide test flow rather than improve it.
I also skimmed through the GitHub repo; You're clearly using POM, but with a three-layer split (spec, handler, page). You have test logic split out into
handlers/and then invoked fromlogin.e2e.ts. This effectively turns the spec into a dispatcher rather than a test. Spec files are supposed to describe behavior directly.Related to that: using ticket-system QA / ticket IDs as file names (e.g.
QA-11006.ts) is a test management anti-pattern. Test IDs belong in annotations or tags, not in filenames.For example:
More generally, what's described in this post is pretty much what most teams end up with (POM, fixtures, utils, feature grouping) once a project lives longer than a demo or POC. The more interesting part, in my opinion, is when to introduce these abstractions and how far to take them before they start hurting readability or feedback speed.
Small last note: I noticed
API_BASEis declared insidefixtures/dashboard.ts— might be a good time to move that into.env🙂Looking forward to seeing follow-up posts as the project evolves 👍