Authentication and Navigation Patterns
Two of the most common anti-patterns in browser test automation appear in the first tests every QA engineer writes: logging in through the UI before every test, and scripting step-by-step navigation that duplicates what users do instead of abstracting it into reusable layers. These patterns seem natural — they mirror how users interact with the application — but they create slow, fragile, hard-to-maintain test suites.
The Login Page Lie
Anti-Pattern: Every test begins by navigating to the login page, typing a username and password, and clicking "Sign in." A 500-test suite spends 40 minutes just on login.
Pattern: Authenticate via API or saved browser state. Only the login test itself should test the login page.
Authentication is a precondition, not the thing being tested. When every test exercises the login flow, you are:
- Wasting time (network round-trips, UI rendering, redirects — multiplied by every test)
- Creating fragility (any change to the login page breaks every test in the suite)
- Masking real failures (a login timeout looks the same as a checkout bug)
- Blocking parallelization (shared user sessions conflict across parallel workers)
Example of the pattern: Playwright's storageState approach — authenticate once in a setup project, save cookies and localStorage to a file, and reuse that state in every subsequent test. The entire suite runs authenticated without touching the login page.
Page Objects as Behaviors, Not Selectors
Anti-Pattern: Page objects are thin wrappers around selectors —
loginPage.emailInput,loginPage.passwordInput,loginPage.submitButton. Tests still script low-level interactions.
Pattern: Page objects expose behaviors —
loginPage.loginAs(user),dashboardPage.createReport(params). Tests describe intent, not clicks.
The three-layer architecture that scales:
| Layer | Responsibility | Example |
|---|---|---|
| Test Layer | Describes what is being verified | expect(dashboard.reportCount).toBe(5) |
| Service Layer | Orchestrates multi-page workflows | app.createUserWithOrders(3) |
| Page Object Layer | Encapsulates single-page interactions | orderPage.fillShipping(address) |
When a UI element changes, only the page object changes. When a workflow changes, only the service layer changes. Tests remain stable because they describe intent, not implementation.
Key Takeaways
- Authenticate via API or saved state — only the login test should exercise the login page
- Page objects should expose behaviors (
loginAs,createReport), not element handles (emailInput,submitButton) - Use a three-layer architecture (test / service / page object) to isolate change impact
- Every minute spent on login in a large test suite is a minute wasted — multiply by hundreds of tests to see the real cost