🎯 I Built 18 Different Implementations of the Same Website - Here's What It Taught Me About Test Automation
The Problem No One Talks About
After so many years into my QA career, I watched another junior automation engineer struggle through the same problem I once faced: where do you actually practice test automation?
Sure, there are tutorials. Plenty of them. But they all use the same oversimplified examples - a login form with id="username", a button with id="submit". Clean. Perfect. Nothing like the real world.
Then you land your first automation job, open the company's React app, and find this:
<button className="btn-primary-lg flex items-center justify-center">
Submit
</button>
No ID. No data-testid. Just a generic class name shared by 47 other buttons.
Welcome to the real world.
So I decided to build something different: AutoTest Academy - a practice website with 18 implementations across 6 tech stacks and 3 difficulty levels. Same e-commerce application, completely different automation challenges.
What started as a "quick weekend project" turned into a deep exploration of how technology choices impact testability. Here's what I discovered.
🏗️ The Architecture: Why Three Difficulty Levels Matter
🥉 Bronze Level - Building Confidence First
Most tutorials throw beginners into the deep end. I wanted to create a space where confidence comes before complexity.
Bronze level HTML (jQuery implementation):
<button id="login-button"
class="btn btn-primary"
data-testid="login-btn"
name="login">
Login
</button>
Automation code:
// Playwright - Multiple reliable selector options
await page.locator('#login-button').click();
await page.locator('[data-testid="login-btn"]').click();
await page.locator('button[name="login"]').click();
Why this matters: Beginners need early wins. Every element has multiple selector options. Tests pass consistently. Confidence builds.
Features:
🥈 Silver Level - Welcome to Reality
This is where automation gets interesting. Silver level mirrors actual enterprise applications - the ones your company maintains.
Silver level HTML (React implementation):
<button className="btn field-submit"
type="submit">
Login
</button>
Automation code:
// Now we need to think strategically
await page.locator('button.btn.field-submit[type="submit"]').click();
await page.locator('button:has-text("Login")').click();
The shift: You can't rely on IDs anymore. You need to:
This is where junior engineers become competent automation engineers.
🥇 Gold Level - Framework Hell (The Truth)
Then there's Gold level. This is what modern frontend frameworks actually generate in production.
Gold level HTML (Angular implementation):
<button class="mat-button mat-raised-button"
ng-reflect-disabled="false">
<span class="mat-button-wrapper">Login</span>
</button>
Automation code:
// Advanced selector strategies needed
await page.locator('button.mat-raised-button >> text=Login').click();
await page.locator('button[ng-reflect-disabled="false"] .mat-button-wrapper').click();
Reality Check:
This is where competent engineers become experts.
🛠️ The Same Login Button Across 6 Frameworks
Here's where it gets fascinating. The exact same feature - a login button - presents completely different automation challenges depending on the tech stack.
1. jQuery/Vanilla JS (Bronze) - The Golden Standard
<button id="login-btn"
class="btn btn-primary"
data-testid="login">
Login
</button>
Automation difficulty: ⭐☆☆☆☆ Playwright code:
await page.locator('#login-btn').click();
Why it's easy: Developers control every attribute. Test-friendly by design.
2. React (Silver) - Component Abstraction
<Button
className="login-submit"
onClick={handleLogin}
type="submit">
Login
</Button>
Automation difficulty: ⭐⭐⭐☆☆ Playwright code:
await page.locator('button.login-submit[type="submit"]').click();
Challenge: JSX compilation creates generic classes. Must combine selectors. Best practice: Add data-testid in component props.
3. Angular (Gold) - Material Design Complexity
<button mat-raised-button
(click)="login()"
[disabled]="isLoading">
<span class="mat-button-wrapper">Login</span>
</button>
Automation difficulty: ⭐⭐⭐⭐☆ Playwright code:
await page.locator('button.mat-raised-button >> text=Login').click();
Challenge: Material components add wrapper elements and dynamic attributes. Best practice: Use [attr.data-testid] bindings in your components.
4. Vue.js (Silver) - Directive-Driven
<button @click="login"
:disabled="loading"
class="btn">
Login
</button>
Automation difficulty: ⭐⭐⭐☆☆ Playwright code:
await page.locator('button.btn:has-text("Login")').click();
Challenge: Vue directives don't translate to stable DOM attributes. Best practice: Combine v-bind with test attributes.
5. Blazor (Gold) - Server-Side Rendering
<button class="blazor-button"
@onclick="HandleLogin"
disabled="@isLoading">
Login
</button>
Automation difficulty: ⭐⭐⭐⭐☆ Playwright code:
await page.locator('button.blazor-button:has-text("Login")').click();
Challenge: Razor syntax and C# binding create unique identifier patterns. Best practice: Add explicit data attributes in Razor components.
6. CSS Frameworks - Bootstrap/Tailwind (Bronze)
<button class="btn btn-outline-primary"
type="submit"
data-test="login-submit">
Login
</button>
Automation difficulty: ⭐⭐☆☆☆ Playwright code:
await page.locator('[data-test="login-submit"]').click();
Challenge: Utility classes everywhere, but structure is predictable. Best practice: Rely on semantic HTML and data attributes.
📊 Key Insights: What 18 Implementations Taught Me
1. Framework Choice Directly Impacts Test Maintenance Costs
After building identical features across all frameworks, the data is clear:
Most testable (lowest maintenance):
Recommended by LinkedIn
Why: Developers have explicit control over every attribute. What you write is what automation tests see.
Moderate testability:
Why: Component abstractions add a layer between intent and output, but test attributes can be passed through props.
Highest maintenance:
Why: Heavy framework abstractions generate complex DOM structures with dynamic attributes.
The cost difference? In my experience, Gold-level Angular tests take 3-4x longer to write and maintain than Bronze-level jQuery tests for the same functionality.
2. The Selector Hierarchy That Actually Works
After thousands of test cases across all implementations, here's the selector strategy that minimizes flakiness:
Priority 1: Test-specific attributes
await page.locator('[data-testid="login-button"]').click();
Why: Explicit, immune to styling changes, clear intent.
Priority 2: Semantic IDs
await page.locator('#checkout-submit').click();
Why: Unique, fast, but requires developer discipline.
Priority 3: Text combination with Playwright
await page.locator('button:has-text("Login")').click();
Why: Resilient to class changes, readable, maintainable.
Priority 4: Class combinations (last resort)
await page.locator('button.btn.btn-primary.submit').click();
Why: Fragile, breaks with CSS refactoring, but sometimes unavoidable.
Never: Position-based selectors Why: Brittle, unreadable, breaks with any DOM change.
3. The Developer-QA Collaboration Gap
Building this taught me something surprising: most developers don't understand how their framework choice affects testability.
Conversation I've had multiple times:
Developer: "Why is automation taking so long?" Me: "Because your Angular Material button has no stable identifier." Developer: "Can't you just use the class name?" Me: "It's .mat-focus-indicator.mat-button.mat-button-base, and it's shared by every button." Developer: "Oh."
The solution: Add testability to your Definition of Done:
🎓 Real Learning Outcomes from User Feedback
Since launching AutoTest Academy, I've heard from automation engineers at different levels:
Junior Engineers (Bronze level)
"This is the first time I successfully automated a full user flow without help. The IDs made it so clear what I should target."
Impact: Confidence. They learn that automation isn't magic - it's methodical element selection and interaction.
Mid-level Engineers (Silver level)
"The Silver React implementation is exactly like our work app. Finally, a practice environment that matches reality."
Impact: Transfer of learning. Skills practiced here directly apply to their day job.
Senior Engineers (Gold level)
"The Angular Gold level forced me to learn advanced Playwright selectors properly. Now I can handle any framework."
Impact: Mastery. They develop framework-agnostic automation skills.
💡 What I'd Do Differently (Advice for Your Own Practice Projects)
1. Start with One Framework, Not Six
I went overboard. If you're building a practice environment, start with one well-implemented version. Get the user flows perfect, then expand.
2. Document the "Why" Behind Selector Choices
I added comments explaining WHY certain selectors work better than others. This turned code into a teaching tool.
// ❌ BAD: Breaks if CSS changes
await page.locator('.btn-primary').click();
// ✅ GOOD: Explicit test attribute, resilient to refactoring
await page.locator('[data-testid="submit-button"]').click();
3. Make It Realistic, Not Perfect
The checkout process isn't perfect. Some validation is simplified. That's okay. The goal is learning automation, not building production e-commerce.
🔮 The Future of Test Automation Education
Building AutoTest Academy taught me that our field needs:
1. More "Progressive Complexity" Resources
Too many resources are binary: "Hello World" examples or enterprise chaos. We need the middle steps.
2. Framework-Specific Automation Guides
General Playwright tutorials don't prepare you for Angular Material. We need specialized guides for each major framework.
3. Testability as a First-Class Development Concern
Frameworks should ship with built-in test hooks. Angular's ng-reflect-* attributes are close, but not stable enough. We can do better.
4. Collaboration Between Dev and QA Communities
QA engineers should understand framework internals. Developers should understand automation pain points. This project bridges that gap.
🚀 Try It Yourself
AutoTest Academy is live and open source:
🌐 Live Website: https://ankitrathi85.github.io/automation-practice-website/index.html 💻 GitHub Repository: https://github.com/ankitrathi85/automation-practice-website
Perfect for:
Features:
🤝 Join the Conversation
What's your experience with automation across different frameworks?
Drop a comment - I'd love to hear your war stories! 🧪
P.S. - This "quick weekend project" turned into 18 implementations across 6 frameworks and taught me more about modern web development than any tutorial ever could. Sometimes the best learning happens when you just start building. 🚀
#TestAutomation #QualityAssurance #WebDevelopment #Playwright #JavaScript #React #Angular #Vue #Blazor #QAEngineering #AutomationTesting #SoftwareEngineering #TechEducation #TestingStrategy #WebTesting #SoftwareTesting #CodingProject #OpenSource #TypeScript
This was a great read, Ankit. As someone from the QA and automation side, I really connect with your approach, repeating the same project across 18 stacks is like an A/B test for learning. Love that you shared the wins and the struggles both… Curious to know as to which implementation taught you the biggest lesson about performance or maintainability?