Good — one change, clearly scoped. Folding the repo name out cleanly now.
TLDR: Don't add E2E after you fix the board. Add it first — so you can actually fix the board.
the wall
We were deep in our KOL CRM for scheduling webinar content, building a drag-and-drop calendar board on top of @dnd-kit/core and SWR (our data-fetching library).
The board worked — in the sense that tsc, eslint, and next build all came back clean.
But every time I used it? Something was broken.
Drag ghost disappearing behind the detail panel. Scroll artifacts from backdrop-filter GPU layers. Cards flickering out on mutation. Tab focus triggering a full refetch storm. A will-change:transform line hiding quietly in the CSS, silently killing drag-to-slot for days.
None of this showed up in unit tests. TypeScript catches zero visual bugs. That's just the truth.
what I tried first (and why it didn't hold)
I started fixing things by eye — load in the browser, click around, ship, repeat.
But each fix broke something adjacent. I'd clear the scroll artifact and the loading skeleton would go clunky. I'd tighten the SWR cache and the post-mutation flash would come back. I was chasing smoke.
The problem wasn't the individual bugs. It was that I had no floor — no layer that would tell me when I'd kicked something I'd already fixed.
the move that changed everything
I added Playwright (browser-automation test tool) infrastructure before I'd finished the fixes.
Not after. Before.
test(e2e): add Playwright infrastructure and board stability tests went in first. Then the wave of fixes landed — loading flicker, notes race condition, SWR refetch storms, the will-change:transform culprit, a global SWRConfig provider to kill the tab-focus storm, optimistic cache updates to kill the post-mutation flash.
And THEN a second test commit: test(e2e): add board interaction tests — month nav, detail panel, rapid clicks.
Those three targets — month nav, detail panel, rapid clicks — weren't random. They were the exact categories of breakage I'd been chasing. Month nav left stale state. The detail panel would desync after mutations. Rapid clicks exposed race conditions that slower hands never hit.
Playwright didn't discover those bugs. It just made sure that when I fixed one, I didn't quietly break another.
why it matters to me
I used to think of E2E testing as a polish step. Something you do when things are stable and you want to "lock them in."
That framing is wrong — or at least it was wrong here.
A complex drag-and-drop board with SWR, dnd-kit, and optimistic updates has so many ways to visually break that a test pyramid built on unit tests and TypeScript will never catch. Playwright isn't the cherry on top. It's the net you stretch under the trapeze before you start the act.
Next time I'm building something with this much interaction surface, the infrastructure goes in on day one.