The Problem: Coverage That Looked Good on Paper
At the start of March our Playwright suite reported 40 passing tests. Sounds reasonable — until you looked closely. Nine of those tests contained only expect(true).toBe(true). They always passed, because they never actually tested anything. The remaining 31 tests all used a single hardcoded session token, which meant the entire suite exercised exactly one user role (ADMIN) across five spec files.
We set out to fix this properly.
Database-Injected Sessions: Real Auth, No Mocking
The first architectural change was replacing the hardcoded session token with database-injected sessions. A seed script (e2e/helpers/seed.ts) creates five real users in the database — one for each platform role — and generates valid Lucia sessions for each. The resulting session tokens are written to e2e/helpers/sessions.json.
Each spec file imports the session it needs and sets the btfy_session cookie directly on the Playwright browser context before any navigation happens. This means:
- Tests authenticate with the same mechanism production uses — no mock auth layer
- Role-specific behaviour is testable: an ADMIN and a CLIENT can both run through the same page and receive different UI and API responses
- Session expiry and cookie behaviour are implicitly tested
The five seeded roles are OWNER, ADMIN, MANAGER, DEVELOPER, and CLIENT. Each gets a unique user record with a fresh password hash and a valid session record with an appropriate expiry.
Role-Access Matrix: 25 Tests in One Spec
One of the most valuable new spec files is the role-access matrix. It tests five roles against five representative routes and asserts the correct outcome for each combination — 25 tests in total.
The routes tested are: the admin panel root, the client billing page, the developer dashboard, a manager-only route, and the owner settings page. For each (role, route) pair the test either asserts the page renders with the expected heading or asserts a redirect to the appropriate error or sign-in page. Any regression that accidentally grants a CLIENT access to the admin panel, or blocks a MANAGER from their own zone, will be caught immediately.
SSR Streaming Fix: waitForContent()
Next.js 16 streams server-rendered HTML progressively. waitForLoadState("load") fires before the streamed content arrives, causing intermittent failures where assertions ran against an empty shell. We introduced a waitForContent(page) helper that polls for the presence of any meaningful DOM node before proceeding. This change was applied across all 38 spec files and eliminated an entire class of flaky failures.
Cookie Consent: Dismissed Before Every Test
The cookie consent banner was blocking click targets in several tests. We resolved this permanently by adding an addInitScript() call to each browser context that sets the consent localStorage key before any page loads. The banner never appears in test runs.
38 Spec Files Covering the Full Platform
The expanded suite covers:
- Authentication flows: sign-in, sign-up, password reset, 2FA TOTP, OAuth callbacks, session timeout warnings
- Admin zones: users, billing, settings, server management, WAF, DNS, SSL, K8s workloads
- Manager zones: team management, project creation, access control
- Developer zones: API keys, webhooks, environment variables
- Client zones: orders, invoices, billing, app subscriptions
- Marketplace: app listing, search, category filtering
- Deployment wizard: plan selection, configuration, payment (skipped — requires live Stripe), deploy, verify
- Blog: public article rendering, category pages, RSS feed
Four tests are intentionally skipped with documented reasons: the post-deploy success screen (blocked by a known CLIENT_ERROR in the seed environment), the wizard payment step (requires a live Stripe test account), and the app detail page (requires a stable seeded app ID).
Running the Suite
# 1. Seed test data (run once per environment reset)
npx tsx e2e/helpers/seed.ts
# 2. Start the dev server
npm run dev
# 3. Run all tests
npx playwright test
# Debug a specific spec
npx playwright test e2e/auth.spec.ts --debug
Deep Codebase Audit: What Else We Fixed
Alongside the testing work we ran a systematic audit of the codebase. Key findings and fixes:
- 9 missing error.tsx boundary files added — the admin billing, plans, settings, services, webhooks, and marketplace pages, plus the client billing, marketing, and orders sections, all had routes with no error boundary. Unhandled errors in these zones now show a formatted error page instead of a blank screen.
- SSL: OCSP stapling removed — Let's Encrypt's newer E7/R12 certificate chain uses CRL instead of OCSP. The stapling code was sending requests to an endpoint that no longer exists. Removed.
- SSL: wildcard certificate support — added
canIssueWildcard()to check whether a zone is managed by Hetzner DNS before attempting wildcard issuance. The provisioner now issues wildcard certs for Hetzner-managed zones automatically. - Deleted 3 dead files —
icon-button.tsx,use-form-state.ts, anduse-pagination.tshad zero imports across the codebase. Removed. - SkipLink added to help layout — the help section was the last layout missing a skip-to-content link. Now all layouts have one.
- Auth pages migrated to CSS variables — 21 auth-related files still had hardcoded hex values. Migrated to
bg-card,text-muted-foreground,border-borderetc. - Deployment timeout — raised from 10 minutes to 15 minutes to accommodate slow Kubernetes image pulls over the Hetzner network.



