| name | qa-strategy |
| description | E2E QA strategy โ flow-based product testing with disciplined triage, regression detection, and autonomous execution |
E2E QA Strategy
Test the product the way a user uses it. Every test simulates a real user flow โ navigating, clicking, filling forms, waiting for responses.
This is NOT unit or integration testing. This pipeline tests complete user journeys through the running application.
Quick Reference
| Aspect | Detail |
|---|
| Scope | End-to-end Playwright tests against a running app |
| Input | Running app URL, optional prior test results for regression comparison |
| Output | Test files (one flow per file), triage verdicts for every failure, regression report |
| Core rule | Every failure gets a verdict before the next test runs |
| Priority | Critical/High flows first; stop adding flows when context reaches ~80% capacity |
| Independence | Each test is self-contained โ no shared state, no execution order dependency |
Flow Prioritization
| Priority | What Breaks | Examples |
|---|
| Critical | Revenue or access | Login, checkout, payment, signup |
| High | Core product value | Create/edit main entities, dashboard, search |
| Medium | Secondary features | Settings, profile, notifications |
| Low | Polish | Theme toggle, tooltips, animations |
A session that thoroughly tests 5 critical flows beats one that superficially touches 20.
Flow Discovery
Before writing tests, understand what the product does:
- Scan routes and pages โ every URL is a potential flow entry point
- Identify forms โ login, signup, search, create, edit โ high-value interaction points
- Map navigation โ how does a user get from A to B? What's the happy path?
- Find auth boundaries โ public vs protected; test both sides
- Check CRUD operations โ create, read, update, delete for core entities
If prior test results exist, compare to detect new, removed, and changed flows.
Playwright Discipline
Locators โ Resilient Only
page.getByRole('button', { name: 'Submit' })
page.getByLabel('Email')
page.getByText('Welcome back')
page.getByTestId('user-avatar')
page.locator('.btn-primary')
page.locator('#submit-btn')
page.locator('div > form > button:nth-child(2)')
Waiting โ Explicit Conditions Only
await page.waitForResponse(resp => resp.url().includes('/api/users'));
await expect(page.getByText('Success')).toBeVisible();
await expect(page.getByText('Loading...')).not.toBeVisible();
await page.waitForTimeout(3000);
await page.waitForLoadState('networkidle');
One Flow Per File
test.describe('Checkout flow', () => {
test('adds item to cart', async ({ page }) => { });
test('fills shipping info', async ({ page }) => { });
test('completes payment', async ({ page }) => { });
test('shows confirmation', async ({ page }) => { });
});
Failure Triage
When a test fails, classify it immediately โ before running the next test.
| Verdict | Meaning | Action |
|---|
| Bug | App behavior is wrong | Record as regression. Do not change the test assertion โ the test is correct, the app is broken. |
| Stale assertion | App changed intentionally | Update the test to match new behavior. |
| Flaky | Non-deterministic failure (evidence of randomness required) | Fix the locator or wait condition. Re-run once. |
| Test error | Test code itself is wrong | Fix the test code and re-run. |
Triage Process
- Read the error. What element wasn't found? What URL didn't match? What assertion failed?
- Check if the app changed. Did a route move? Did a button get renamed? New loading state?
- Distinguish bug from change. Intentional change โ update test. Unintentional breakage โ regression.
- Don't retry blindly. If you can't explain why a test failed, investigate before retrying.
Example: Classifying a Real Failure
Error: expect(getByRole('heading', { name: 'Dashboard' })).toBeVisible()
โ Timeout 5000ms exceeded.
โ Call log: waiting for getByRole('heading', { name: 'Dashboard' })
| Step | Finding |
|---|
| Read error | Heading "Dashboard" not found after login |
| Check app | Route /dashboard now redirects to /home; heading changed to "Home" |
| Verdict | Stale assertion โ intentional redesign |
| Action | Update test: navigate to /home, assert "Home" heading |
Regression Analysis
A regression is a flow that was passing and now fails.
Regressions are the pipeline's highest-priority output.
For each regression, record:
| Field | Value |
|---|
| Flow | Which user flow broke |
| Previous status | Last known passing state |
| Current error | Error message and failing assertion |
| Classification | Real bug or intentional change |
Session Checklist
Before finishing, verify every item:
| Check | Pass | Fail |
|---|
| Every failure has a verdict | โ | Failures left unclassified |
| Critical flows tested before lower-priority | โ | Random or low-priority-first ordering |
| Regressions recorded with all fields | โ | Vague "some tests failed" |
| Tests are independent and resilient | โ | Tests depend on execution order or shared state |
| Context spent on high-value flows | โ | Low-priority flows tested while critical flows skipped |
| Error states tested (API errors, bad input) | โ | Only happy paths covered |
MUST / MUST NOT
| MUST | MUST NOT |
|---|
| Triage every failure before proceeding | Accumulate failures to analyze later |
| Use resilient locators (role, label, text, testid) | Use CSS selectors or DOM position |
| Wait for explicit conditions (element, response) | Use waitForTimeout or networkidle |
| Test what the user sees | Test internal state (stores, localStorage, cookies) |
| Provide evidence before classifying a failure as "flaky" | Classify regressions as flaky without proof of non-determinism |
| Test error states and edge cases | Only test happy paths |