CoachSync Goes Enterprise: 238 E2E Tests and Zero Excuses
CoachSync started as a weekend project. A booking form, a coach dashboard, Stripe checkout. The kind of thing you knock together to see if an idea has legs. But as the feature list grew, so did the gap between "demo-ready" and "enterprise-ready." If coaches are going to trust their livelihood to this thing, it cannot be held together with optimism and a single integration test that checks whether the homepage loads.
The gap between "works on my machine" and "enterprise-ready" is not about features. It is about trust. A coach in Cork books a Thursday 2pm lesson, a parent in Dublin pays for it, and that transaction needs to land correctly. Every time. Once real money enters the system, "probably fine" stops being an engineering position.
This is how we took CoachSync from scrappy MVP to 1,631 unit tests and 238 end-to-end tests. It is also about what we learned about security, performance, and the quietly satisfying experience of watching a green test suite scroll past at 3am.
// the MVP trap
Ship fast, iterate later. Good advice, right up until "later" never arrives.
You ship the MVP, get traction, bolt on features, and one morning you realise you are building on a foundation that was never designed to bear weight. The codebase turns into a Jenga tower. Every new block might be the one that brings it down.
CoachSync hit that wall fast. The booking logic grew from a simple time-slot picker into a maze of edge cases. Recurring lessons. Multi-venue coaches. Timezone handling for coaches splitting time between the UK and Ireland. Cancellation policies per coach, refund windows per payment method.
Every new feature risked breaking something upstream. Touch the cancellation flow, invoicing might break. Tweak a notification template, the email queue chokes. We were spending more time manually testing regressions than building anything new.
The turning point was realising how easy it would be for a booking to silently fail. A parent sees a confirmation page, gets a confirmation email, but the lesson never appears on the coach's calendar. Money collected for a service that was never scheduled. That is not a bug. That is a trust violation. We decided to make it impossible before it ever happened.
We did not need more features. We needed confidence that the existing ones actually worked.
// the testing strategy
The testing pyramid says: lots of unit tests at the base, fewer integration tests in the middle, a thin layer of E2E at the top. Fair enough. But when the critical path involves a browser, a payment processor, a calendar, and an email service, unit tests alone will not get you to confidence. You need tests that exercise the full stack the way a real user does.
So we built three layers. At the base, 1,631 unit tests cover business logic in isolation. Booking conflict detection, pricing calculations, timezone conversions, cancellation policies, refund eligibility, template rendering. They run in under 30 seconds and catch the obvious mistakes before anything gets near a browser.
The middle layer is integration. Real database connections, API endpoints, Stripe webhook processing, email pipelines. These verify that queries return the right data, that webhooks handle every event type Stripe might throw at us, and that email templates render correctly with real data.
At the top, 238 Playwright E2E tests walk through real user journeys. A coach signs up, completes onboarding, sets availability, publishes their profile. A parent finds them, books a lesson, pays, gets confirmation. The coach sees the booking, marks it complete, the parent gets a follow-up. Every critical path, step by step, in a real browser.
$ npm run test:unit
PASS src/lib/booking/conflict-detection.test.ts (42 tests)
PASS src/lib/pricing/calculator.test.ts (38 tests)
PASS src/lib/scheduling/timezone.test.ts (27 tests)
PASS src/lib/notifications/templates.test.ts (54 tests)
...
Test Suites: 189 passed, 189 total
Tests: 1,631 passed, 1,631 total
Snapshots: 0 total
Time: 28.4s
$ npx playwright test
Running 238 tests using 4 workers
✓ [chromium] booking/full-journey.spec.ts (14 tests)
✓ [chromium] onboarding/coach-setup.spec.ts (11 tests)
✓ [chromium] payments/stripe-checkout.spec.ts (9 tests)
✓ [chromium] scheduling/recurring-lessons.spec.ts (12 tests)
✓ [chromium] notifications/email-flow.spec.ts (8 tests)
...
238 passed (4m 12s)
That is not a screenshot from a good day. It is what we see every time we push to main. The CI pipeline runs the full suite on every pull request. Nothing merges until everything is green. No exceptions, no "we will fix it later" overrides, no skipped tests. The suite is the gatekeeper and it does not negotiate.
// the security audit
Tests tell you whether your code does what you intended. They tell you nothing about whether your intentions were secure.
A booking system that processes every payment correctly is still vulnerable if an attacker can enumerate user accounts through the password reset flow, or if a coach can access another coach's client list by fiddling with API parameters.
We ran a full security audit in March 2026. Not a checkbox exercise with an automated scanner. A proper review of every endpoint, every auth flow, every data access pattern. Authentication, authorisation, input validation, API security, data exposure, infrastructure config.
The first pass found things. A critical IDOR in the client management API that would have let a coach access another coach's records by iterating through IDs. Insufficient rate limiting on auth endpoints. Overly permissive CORS. Missing security headers. Inconsistent input validation.
Every critical and high severity issue was fixed within 48 hours. We ran a second review to verify the fixes and catch anything the first pass missed. It came back clean. The fixes are all covered by dedicated regression tests now.
Security is not something you bolt on at the end. It is a property of the system. We learned that the hard way during the audit, which is still better than learning it from an incident report.
// the website builder
The feature that really pushed us toward proper testing was the coach website builder. CoachSync generates a complete, production-ready website for every coach who finishes onboarding. Bio, qualifications, venues, pricing, availability - all pulled from their onboarding data. If they have linked a YouTube channel, their coaching videos get embedded. Upload photos, those get optimised and placed.
It is a code generation pipeline. Structured data in, static HTML and CSS and assets out, deployed to a CDN. The output has to be correct across every combination of inputs. Coaches with one venue or five. YouTube or no YouTube. Custom pricing tiers or standard rates. Professional headshot or nothing.
The combinatorial space is huge. A coach might have three venues with different availability, custom junior and adult pricing, a YouTube channel with 200 videos, and a bio full of special characters that need proper HTML escaping. Every combination needs to produce a valid, accessible, performant site. You cannot test that by hand.
We built a test harness that generates coach profiles with controlled variations and checks the output against our quality bar. Navigation links work. Venue info renders correctly. CTAs are present and functional. Meta tags are correct for SEO. Content security policy headers are set. The harness runs as part of the E2E suite, generating real websites and verifying them in a real browser.
Every generated site passes the same checks regardless of input data. No manual review, no "looks fine to me" sign-off. The tests are the sign-off.
// performance as a feature
"It loads" is not a performance target. When a parent is trying to book a lesson during their lunch break, every second of latency is a second closer to them giving up and just calling the coach.
We set performance budgets. Homepage under 1.5 seconds on 4G. Booking flow under 3 seconds from time selection to payment confirmed. Search results within 200 milliseconds. These are not aspirational. They are enforced by CI checks that fail the build if any budget is exceeded.
The biggest win was rethinking how we load coach profiles. The original implementation fetched everything at once: schedule, reviews, venue data, pricing, photos. A busy coach with lots of reviews could trigger dozens of database queries on a single page load. We switched to progressive loading where critical content renders immediately and supplementary data loads in the background. Perceived load time dropped from 2.8 seconds to 0.6.
Edge caching for static assets, precomputed search queries, connection pooling. Each change was small. The cumulative effect was a platform that feels instant. Performance is not one big optimisation. It is a hundred small decisions that compound.
The performance budgets live alongside the test suite as another gatekeeper. If a new feature adds latency, we optimise it before merging or we do not merge it.
// shipping with confidence
The test suite is not a safety net. It is the foundation. You do not build on top of hope. You build on top of passing tests.
The real value of 1,631 unit tests and 238 E2E tests is not catching bugs. They do catch bugs, regularly, but that is a side effect. The real value is velocity. When every critical path is covered, you can refactor the booking engine without worrying about breaking payments. Update the notification system without wondering if coaches will stop getting emails. Ship new features knowing that if anything breaks, you will find out in CI, not from an angry email.
Before the test suite, shipping took three days. One to build, two to manually verify nothing else broke. Now it takes hours. Build, write tests, push, wait for green, merge. The feedback loop is tight and the confidence is high.
CoachSync handles real bookings, real money, real people's schedules. The test suite is how we earn the right to do that. Not with promises, not with "we will monitor it in production," but with 1,869 automated checks on every single change. Zero excuses.