Here is an uncomfortable truth about vibe coding: AI-generated code needs more testing than human-written code, not less. When a human developer writes code, they usually understand what each line does and can reason about edge cases. When an AI writes code based on your prompt, it produces something that works for the obvious case but may silently break in situations you did not describe. Testing is how you catch those silent failures before your users do.
This guide is not going to turn you into a test engineer. It is going to give you a practical framework for testing your vibe-coded app using the same AI tools you used to build it. If you can describe what your app should do in plain English, you can write tests.
Why Testing Matters More with AI-Generated Code
When you use Cursor, Lovable, or any AI coding tool, you are trading deep understanding for speed. That trade-off is often worth it. But it creates a specific risk: you do not know what the code does in situations your prompt did not cover.
Consider a simple example. You tell your AI to "build a form where users can enter their email and sign up for a newsletter." The AI generates a form that works perfectly when you type in a normal email address. But what happens when someone enters a blank field? What happens when they enter "test@" without a domain? What happens when they submit the form twice? What happens when the network is slow and they click the button three times?
A human developer would think about some of these cases while writing the code. An AI usually handles only the happy path — the scenario where everything goes right. Tests are how you verify that your app handles the unhappy paths too.
The good news: AI is excellent at writing tests. You can describe a scenario in plain English, and your AI assistant will generate the test code. Testing a vibe-coded app with AI-generated tests is a natural extension of the same workflow.
Two Kinds of Tests You Should Know About
The testing world has dozens of categories and subcategories. For a vibe-coded MVP, you only need to understand two kinds.
End-to-end tests (E2E) simulate a real user interacting with your app. They open a browser, navigate to your page, click buttons, fill in forms, and check that the right things happen. Think of them as a robot that uses your app the way a human would. The tool for this is Playwright.
Unit tests check that individual pieces of your code work correctly in isolation. They test a single function or component — "does this function correctly calculate tax?" or "does this component render the right text?" The tool for this is Vitest.
For most vibe coders, start with end-to-end tests. They are closer to how you think about your app ("a user should be able to sign up and create a project") and they catch the most impactful bugs. Unit tests become more important as your app grows, but they are not the first priority for an MVP.
Playwright: Testing What Users Actually Do
Playwright is a free, open-source testing framework from Microsoft. It launches a real browser (Chrome, Firefox, or Safari), navigates to your app, and performs actions like clicking, typing, and scrolling. Then it checks that the results are what you expect.
Here is what a Playwright test looks like in plain English, then in code:
What you want to test: "A user can visit the homepage, click Sign Up, enter their email and password, submit the form, and land on the dashboard."
import { test, expect } from '@playwright/test';
test('user can sign up and reach the dashboard', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.click('text=Sign Up');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'securepassword123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/dashboard/);
await expect(page.locator('h1')).toContainText('Dashboard');
});
You do not need to write this yourself. Tell your AI coding assistant: "Write a Playwright test that verifies a user can sign up with an email and password and reach the dashboard." The AI will generate the test, often getting the selectors right on the first try because it can see your codebase.
Setting Up Playwright: Step by Step
If you are using a code editor like Cursor or Windsurf, setup is straightforward.
Step 1: Tell your AI assistant: "Add Playwright to this project and configure it for testing." It will run the installation commands and create the configuration file.
Step 2: Create a test file. By convention, these go in a tests/ or e2e/ folder. Your AI can set this up for you.
Step 3: Run the tests. In your terminal: npx playwright test. Playwright will launch a browser, run through your tests, and report the results. Green means passed, red means failed, with clear error messages about what went wrong.
Step 4: Use the Playwright UI mode (npx playwright test --ui) to watch the tests run visually. This is useful for debugging — you can see exactly what the browser is doing at each step and where the test fails.
Vitest: Testing the Logic Behind the Scenes
While Playwright tests the full user experience, Vitest tests individual functions and components. Vitest is fast — it runs in milliseconds, not seconds — which makes it practical to run every time you make a change.
Vitest is particularly useful for vibe-coded apps because AI often generates utility functions that look correct but have subtle bugs. A Vitest test catches those bugs before they affect users.
Example: your app has a function that calculates pricing based on quantity and discount codes.
import { describe, it, expect } from 'vitest';
import { calculatePrice } from './pricing';
describe('calculatePrice', () => {
it('applies a 10% discount correctly', () => {
expect(calculatePrice(100, 'SAVE10')).toBe(90);
});
it('handles zero quantity', () => {
expect(calculatePrice(0, 'SAVE10')).toBe(0);
});
it('ignores invalid discount codes', () => {
expect(calculatePrice(100, 'FAKECODE')).toBe(100);
});
it('does not return negative prices', () => {
expect(calculatePrice(5, 'SAVE90')).toBeGreaterThanOrEqual(0);
});
});
Again, you do not need to write this yourself. Tell your AI: "Write Vitest tests for the calculatePrice function, including edge cases like zero quantity, invalid discount codes, and discount amounts larger than the price." The AI will generate comprehensive tests because it can read the function's source code.
Playwright vs Vitest: When to Use Each
| Feature | Playwright (E2E) | Vitest (Unit) |
|---|---|---|
| What it tests | Full user flows in a real browser | Individual functions and components |
| Speed | Seconds per test | Milliseconds per test |
| Setup complexity | Moderate | Simple |
| Best for catching | Broken flows, UI issues, integration bugs | Logic errors, calculation bugs, edge cases |
| When to run | Before deploying | After every change |
| Priority for MVP | Start here | Add as app grows |
Writing Tests with AI: The Practical Workflow
The beauty of testing in the age of vibe coding is that the same AI that wrote your code can write the tests for it. Here is the workflow that works best.
1. Describe the behavior, not the implementation. Instead of "test the handleSubmit function," say "test that when a user submits the contact form with a valid email, they see a success message, and when they submit with an empty email, they see an error message." The AI will figure out which functions and selectors to use.
2. Ask for edge cases explicitly. AI tends to write tests for the happy path just like it writes code for the happy path. Prompt it specifically: "Include tests for empty inputs, very long inputs, special characters, network errors, and rapid repeated submissions." The more scenarios you describe, the more robust your tests become.
3. Run the tests and iterate. Some AI-generated tests will fail on the first run. That is normal and actually useful — it tells you either the test has a bug (fix it) or your code has a bug (fix that instead). Tell your AI: "This test is failing with this error message. Fix it." The AI will adjust.
4. Keep tests in sync with your code. When you change a feature, tell your AI: "I updated the signup form to include a username field. Update the Playwright tests to include this new field." This is faster than writing tests manually and harder to forget.
The Minimum Viable Test Suite
You do not need 100% test coverage. You need tests for the things that would hurt the most if they broke. For a typical vibe-coded MVP, here is the minimum test suite worth maintaining:
- Authentication flow — Sign up, log in, log out. These are the gates to your app. If auth breaks, nothing else matters. This is one Playwright test.
- Core happy path — The main thing your app does, end to end. If it is a task manager: create a task, mark it complete, delete it. If it is a landing page builder: create a page, edit it, publish it. One Playwright test.
- Payment flow (if applicable) — If your app charges money, test the purchase flow. Use Stripe's test mode with test card numbers. This is the test you absolutely cannot skip.
- Data validation — Test that your forms reject invalid input. Empty fields, too-long inputs, SQL injection attempts. A few Vitest tests for your validation functions.
Four to six tests. That is all you need to start. You can add more as your app grows and as you discover what breaks. The goal is not perfection — it is confidence that the critical paths work every time you deploy.
Automated Testing with CI/CD
Once you have tests, the next step is running them automatically every time you push code. This is called continuous integration, and most hosting platforms support it.
If you deploy with Vercel or Netlify, you can configure your project to run Playwright tests before each deployment. If the tests fail, the deployment is blocked. This prevents you from accidentally breaking your live app.
The setup is straightforward. Tell your AI: "Configure this project to run Playwright tests in GitHub Actions before deploying to Vercel." The AI will create the configuration files. Checkly is another option that runs Playwright tests on a schedule against your live app, alerting you if something breaks in production.
Monitoring After Testing: The Complete Picture
Testing catches bugs before deployment. Monitoring catches the bugs that tests missed. The two work together.
Sentry captures runtime errors in your live app and sends you alerts. If a user hits an error that your tests did not cover, Sentry shows you the error message, the stack trace, and the user action that triggered it. Setting up Sentry alongside your tests gives you both proactive and reactive bug detection.
For product-level monitoring, PostHog shows you if users are completing the flows your tests verify. If your Playwright test says the signup flow works but PostHog shows 80% of users dropping off at the signup page, you have a UX problem that testing alone will not catch.
The Honest Take on Testing and Vibe Coding
Testing is the part of software development that gets skipped most often, by professionals and vibe coders alike. The temptation is to test manually — "I clicked through the app and everything worked" — and ship. Manual testing catches obvious bugs, but it misses the subtle ones that only appear with specific inputs, specific timing, or specific browser configurations.
You do not need to love testing. You need to have enough tests that you can deploy with confidence instead of anxiety. If every deployment makes you nervous about breaking something, you need more tests. If deploying feels boring because you know the important stuff is verified, you have enough.
Start with one Playwright test for your core flow. Get it running. Add a second test when something breaks that your first test did not cover. Build the habit incrementally. Your AI assistant makes writing tests almost as fast as describing what your app should do — and describing what your app should do is literally the skill you have been practicing this whole time.
For the full picture on moving from prototype to production, including testing, security, and performance, read our prototype to production guide. And for more on security basics, make sure your tests cover authentication and authorization flows.