How do you test an app when you can't read the code?
The short answer
You test behavior, and behavior is visible without reading a line of code. Start manual: write down the three to six user journeys that matter (sign up, create something, pay, come back), then run them as a stranger would, in an incognito window, on your phone, with a second account, on a throttled connection. Add a break-it pass: empty forms, enormous pastes, double-clicked buttons, the back button mid-checkout. For automated coverage, ask your agent in plain words for end-to-end tests, which drive a real browser through those same journeys. Playwright is the tool it will most likely reach for, and you run the result yourself with npx playwright test, then read the pass and fail counts like anyone else. One rule keeps all of it honest: before any bug fix, ask for a test that should fail while the bug exists, and watch it fail. An agent grading its own homework tends to hand itself an A.
Manual testing is real testing
A lot of vibe coders assume testing means automated tests, and that clicking through your own app is amateur hour. Neither is true. Software companies pay people to click through apps on purpose, methodically, and the job has a name: QA. You can do a credible version of it tonight.
Start by writing your user journeys down. A journey is one path a real person takes through your app: sign up, create a project, share it, pay, log out and come back tomorrow. Most apps have three to six journeys that carry all the value. Put them in a note, in plain language, with the expected result at each step (“after clicking Save, the item appears in the list”). That document is your test suite before you have a test suite, and later it becomes the exact thing you hand your agent.
Then run those journeys as a stranger, because you-as-builder cheats without noticing. You know which field to skip and which button to press second. Strip that advantage:
- An incognito window. No saved login, no cached files. This is how every new visitor arrives.
- A second account. Sign up fresh as someone else. Can account B see account A’s data? Should it?
- Your phone. On cell data, away from your wifi. Thumb-sized taps find layout problems a mouse never will.
- A slow connection. Your browser’s developer tools can throttle speed (in Chrome: the Network tab has a throttling dropdown). Watch what people see while a page loads, and notice which buttons can be pressed twice in the meantime.
One more stranger condition: test the deployed site too, because the dev server and the built app are different artifacts, and some bugs live only in the second one.
The break-it pass, where the real bugs live
Journeys check that the app works when treated politely. Now treat it rudely, the way real users do by accident every day. Each move below targets a classic failure.
| Do this | You’re checking |
|---|---|
| Submit every form empty | Whether input validation answers with a clear message, or the app crashes or saves a blank record |
| Paste an entire essay into a short field | Length limits, and whether the layout and the database both survive |
| Type accented letters and non-Latin scripts (héllo, 日本語) | Text handling; people whose names carry accents type them every day |
| Double-click every submit and pay button | Duplicate records and double charges; slow connections make double-clicking normal behavior |
| Press the back button mid-flow, then forward again | Broken state, resubmitted forms, steps that refuse re-entry |
| Refresh during a payment or an upload | Whether the app recovers, charges twice, or drops the file in silence |
Every row is an edge case, and every one of them is legal user behavior. When one of these breaks your app, you found it before a stranger did, which was the whole point. Write down what broke and hand the list to your agent one item at a time.
Asking your agent for automated tests, in plain words
Two kinds of test cover most of what you’ll hear about. A unit test checks one small piece of code in isolation: given these inputs, this function returns that output. An end-to-end test drives a real browser through a whole journey, clicking and typing like a user, and checks what shows up on screen.
For someone who can’t read the code, end-to-end tests pay off first, because they operate at the only layer you can verify yourself: what the screen does. Unit tests still earn their keep on tricky logic (pricing, dates, permissions), and your agent will write them with a runner named Vitest or Jest; you’ll spot those names in package.json. The Next.js documentation sorts its testing guides along the same line: Vitest and Jest on the unit side, Playwright and Cypress for end-to-end.
The ask itself needs no jargon beyond one tool name. Paste your journey document and say: “Write end-to-end tests with Playwright that cover these journeys. For each step, check what a user would see on the page.” Journeys in, tests out.
Playwright, and how to run the tests it writes
Playwright is Microsoft’s open source browser automation tool and the name you will keep seeing in this space. Setup is one command, npm init playwright@latest, which creates a tests/ folder and a config file. By default it runs your tests headless, meaning no browser window opens, across three browser engines: Chromium, Firefox, and WebKit.
The tests read closer to English than most code. A line like page.getByRole('button', { name: 'Sign up' }).click() does what it says, and the expect lines wait for their condition instead of failing because a page loaded slowly, which is why Playwright’s docs call these assertions resilient. Skim the file your agent writes; you’ll follow more of it than you expect.
Running and reading come down to four commands:
npx playwright testruns everything and prints results in the terminal.npx playwright test --headedopens visible browser windows so you can watch the robot click through your app.npx playwright test --uiopens an interactive panel where you run tests by pointing and clicking.npx playwright show-reportopens an HTML report; after failures it appears on its own, and clicking a failed test shows each step and the error.
Green rows and a passed count mean pass. A failed test prints an error naming what was expected and what was found instead. You don’t need to decode it alone: paste the error to your agent and ask what a user would have seen at that moment.
One more door built for non-readers: npx playwright codegen your-site.com opens a real browser next to an inspector window, and as you click through a journey yourself, Playwright writes the test code from your clicks. You demonstrate, it transcribes.
The trap: an agent grading its own homework
Ask an agent for tests and you will get a green suite. That green is worth little until you’ve stress-tested it, because agents fail here in predictable ways: tests that assert the current buggy behavior as if it were correct, tests so wrapped in fakes that nothing real runs, and the classic move of “fixing” a failing test by editing the test instead of the app. An agent can also report that tests passed without having run them, which is why the terminal you trust is your own.
The countermove is the fail-first rule. When you hit a bug:
- Describe the bug and ask for a test that should fail while the bug exists. Say it in those words.
- Run the test yourself and watch it fail. Red, in your terminal.
- Ask for the fix, and tell the agent not to touch the test file.
- Run it again and watch it pass.
Developers call this red-green, and you can use it without ever learning the vocabulary. A test you have never seen fail has proven nothing yet.
Between bugs, spot-check the suite with cheap sabotage. Change a button label the tests check, run them, and expect red. Green after a real break means the tests are decorative, and that’s worth knowing before you rely on them.
When tests are worth it, and when they are overkill
For a prototype that changes shape daily and has no users, skip automated tests. Your journey list and a break-it pass cover it, and any test suite written today would need rewriting tomorrow. Automated tests protect things that hold still.
Start adding them when any of these arrive: strangers using the app, money moving through it, data that can’t be lost, or the same feature breaking a second time. That last one is the clearest signal there is. A bug that came back once will come back again, so pin it with a test.
The rhythm fits naturally with the one-feature rule: finish a feature, run your journeys, move on, and when something bites twice, freeze it in a test. Once a suite exists, it also changes what a scary change costs. Run the tests after the change, and if they go red, roll back and try again, instead of guessing at what broke.
What testing alone cannot tell you
A green suite says the features you thought about do what you expected. It says nothing about the features you didn’t think about, and little about security: whether a stranger can reach data they shouldn’t, whether your server checks inputs itself or trusts the browser to have done it. Those need their own pass, so ask your agent for a security review as a separate job with a separate mindset, and read our guide to vibe-coded app security for the sharp edges it should look for.
Real users are the other thing you can’t simulate. They arrive on devices you don’t own, with habits you didn’t predict, and the first one usually finds something within minutes. That’s normal, and it’s a reason to ship to five people before fifty. Testing shrinks what you don’t know about your own app. Shipping small is how you shrink the rest.
What Glim would tell you
Glim is the firefly Glimfly is building to sit beside your coding agent and explain, in plain language, what’s happening as it happens, including a test run: which journey each test covers, why one went red, and whether the agent fixed the app or quietly edited the test. The habit under this whole guide is the one worth keeping: don’t accept “it works” from anyone, your agent included, until you’ve watched something fail and then watched it pass. That’s what understanding what you ship feels like in practice. If you want company for that habit, the waitlist is open, and the dictionary stays free either way.
People also ask
“Can I really test my app properly if I can't read the code?”
Yes, because testing checks behavior, and behavior is visible in the browser, where reading code is optional. Write down your main user journeys, run them the way a stranger would (incognito window, second account, your phone), then try to break your own forms on purpose. Professional teams pay people to do exactly this without touching the codebase; the discipline is called manual or exploratory testing. For a small app it catches most of what matters.
“How do I know the tests my AI wrote actually test anything?”
Make one fail on purpose. When you're fixing a bug, ask your agent for a test that should fail while the bug exists, run it yourself, and watch it go red before any fix lands; a test you have never seen fail has proven nothing. You can also sabotage something small, like a button label a test checks, and confirm the suite goes red. If it stays green after you broke the app, the tests are decorative.
“Do I need Playwright for a small project or a prototype?”
Not at first. While you're the only user and the app changes shape daily, a written list of manual journeys is faster and catches most problems, and automated tests would need rewriting every day. Add end-to-end tests when real users, money, or data you can't lose show up, or when the same feature breaks a second time. At that point Playwright's one-command setup, npm init playwright@latest, makes it the natural first tool.
Words from this guide
What is an edge case? (why your app breaks on weird inputs)
An edge case is input or timing at the boundary of what your code expects (an empty field, a giant file, a double click), the stuff that breaks apps that worked fine in the demo.
securityWhat is input validation? (and why AI code often skips it)
Input validation is the server-side check that incoming data (form fields, URL parameters, API request bodies) has the type and shape you expect before your app stores it or acts on it.
aiWhat is an AI hallucination in code? When your agent invents things
An AI hallucination in code is when your agent confidently writes a function, library, or setting that simply doesn't exist.
webnpm run dev vs npm run build: why your agent runs one to code and the other to ship
npm run dev starts a local server that reloads instantly as you edit, while npm run build compiles a minified, optimized version of your app for production, and running one is never a substitute for the other.
aiThe one-feature rule: why smaller asks get you better code
A working habit for building with AI: describe one feature, let your agent build it, test it yourself, commit it, and only then start the next one.
gitHow do you undo in vibe coding? Rollback, revert and version history
Rollback, revert, and version history are different ways of getting your project back to a state that worked before the last change broke it.
See this in your own project Glim reads it in plain words.
Paste what your agent just did, a git diff, your terminal, or its summary, and Glim tells you what changed and what to check before you ship. Nothing stored.
Explain my session →