Hopfly is a social app for beer. Its whole point is that your friends see what you post, and that makes it annoying to test. To check a single notification I needed a second phone, a second account, and a friend who was willing to like my post right now.
So I built a dashboard that plays the friend. The first usable version took about an hour, and I wrote almost none of it by hand. I think every project past a certain size should have one, and a test lab is also the safest place to start vibecoding.
The problem: features that need other people
Hopfly’s features nearly all involve a second person:
- A friend posts, so I get a push.
- Someone comments, I reply, they reply back, they like my reply.
- I hit the next rank and my friends get a “promotion”.
- A friend request comes in, or someone blocks someone.
You can’t test any of that alone. I was creating throwaway accounts, logging in and out, and waiting for pushes to arrive on the right phone. It was slow, I skipped things because it was annoying, and the skipped things were where the bugs were.
What I built
The test lab runs at test.hopfly.app. It’s a Next.js app with shadcn/ui and Tailwind. Its job is to do things a second phone and a real friend would otherwise do:
- Create fake users and make them friends with me.
- Post as a friend, with a generated picture, then like and comment as that friend.
- Send any notification type to any user, or trigger “a friend just posted”.
- Preview the real e-mail templates.
- Grant XP and Hops, and push maintenance or “update required” prompts.
- Run scenarios: one click for “friend posts, I get a push”, “social day”, “busy feed”, “rank-up”, “fresh onboarding”.
That was the first hour, and it was already worth it. [Add one sentence on the first thing it saved you from.]
The part I’m happiest with: it goes through the real code
The dashboard never touches the app’s database. Every action calls a /api/test-lab/* route in the backend, and that route runs the real logic: the same create_post, react_to_post and notify functions the phone uses.
This matters more than it sounds. A “fake” post made by writing rows into a table skips the XP, the Hops and the notification fan-out, so you end up testing your test tool. A post made through the real function triggers all of those, exactly like on a phone. When something breaks in the lab, it breaks for real.
For features without a lab route, such as groups, meetups, the casino and the shop, there’s an act as mode. It calls the normal app API with a test user’s own token, like the phone would.
Then it grew into flows
Clicking through scenarios got old, so I asked for something bigger: a visual flow builder. You wire blocks into a graph, such as create user, befriend, post, wait 10 seconds, comment, reply, like. It has about 65 blocks and 17 templates now. A flow can:
- Run branches side by side.
- Wait, pause until I press Continue, or check that something actually happened.
- Run in the background on the server, or on a schedule.
- Keep its history and compare two runs block by block.
- Replay a run with the same random seed, so the “random” parts repeat.
The “comment, reply, reply, like” scenario that used to take me ten minutes with two phones is now a template. I press a button and my phone buzzes. A scheduled flow is basically a smoke test that runs by itself and pushes me if it fails.
Why vibecoding fits this so well
I wouldn’t hand the payment code or the auth system to a model and walk away. An internal test tool is the opposite case:
- Low stakes. If a button is ugly or a block is buggy, nobody is hurt. Only I use it.
- Boring, repetitive structure. Forms, tables, server actions and one block handler per feature are what a model is fastest at.
- I know what “right” looks like. I use the thing every day, so I notice a wrong result straight away.
- Fast feedback. I change something, press a button and see the outcome, so there’s no waiting for a release.
The trick is the rules, not the prompts
The speed doesn’t come from clever prompting. It comes from a CLAUDE.md in the repo that writes down the rules I’d otherwise repeat in every session. Mine says things like:
- The app’s data goes only through the backend’s test routes. Never straight into its database.
- The dashboard’s own data (saved flows, runs, schedules) lives in its own Postgres. It refuses to start if it’s pointed at the backend’s database.
- Never point at production. The backend URL check refuses
api.hopfly.app. - Anything that could reach a real person, like a push or an e-mail, is limited by the backend’s allowlists. Don’t add a way around them.
- Every page and action checks the session first.
- Adding a block means one catalog entry, one handler and one icon.
With rules like that, “add a block for the casino” is a small request, because the model already knows where everything goes and what it must not do. Safety rails like the production refusal and the allowlists are what let me move fast without worrying about a model’s bad day reaching real users.
Why you should build one
If your project has any of these, a test lab pays for itself within a week:
- Features that involve more than one user.
- Things you can only verify on a device, like pushes, deep links and e-mails.
- State that takes forever to set up by hand, like “a user at level 40 with 3 pinned posts”.
- A staging environment you’re afraid to touch.
You don’t need 65 blocks. Start with “create a user, post as them, send me a push”. Write your rules into CLAUDE.md first. Add one button every time you catch yourself doing something by hand twice.
What I’d watch out for
- Keep it honest. If the lab bypasses the real logic, it will lie to you. Route everything through the real functions.
- Fence it in. Separate database, no production URL, allowlists for anything that reaches real people. Do that on day one, not after the first accident.
- Read the code. I vibecoded it, but I still review what it does around data and sessions. [Add your own experience here.]
- It grows. One hour got me a useful tool, but the flow builder took a few more days of building on top. [Adjust to your real timeline.]
Wrapping up
Vibecoding gets a bad reputation because people point it at the hardest part of their product first. Point it at the tool that makes your product easier to test, and you get something you’ll use every day, with little risk if it’s wrong.
Hopfly is more fun to build now, because I can test a social app on my own. [Closing line or link to the Hopfly project page.]