TL;DR
Apps built entirely by AI (Cursor, Bolt, Lovable) have a 1.7x higher defect rate than human-coded apps. Why? Because AI generates code based on prompts, not specifications. Traditional QA catches bugs. Vibe code needs intent-based validation. We'll explain the defect gap, why traditional testing fails, and show you the new QA playbook for non-technical founders shipping AI-generated apps.
Non-technical founders have a superpower now: they can describe what they want, point Cursor or Bolt at a codebase, and get a working app in hours.
The catch? That app is probably buggier than one a human engineer wrote.
We're seeing this in production data: apps built entirely by AI-assisted tools have a 1.7x higher defect rate than traditionally coded applications. Not because the AI is incompetent, but because the entire testing and validation paradigm needs to shift.
The Vibe Coding Problem
Vibe coding is the practice of describing what you want to build and letting an AI handle the implementation details. "Build me a marketplace where sellers can list items and buyers can discover them." The AI goes to work. Two hours later, you have a functioning marketplace.
This is incredible for speed. It's terrible for confidence.
Why Vibe-Coded Apps Have More Bugs
Traditional development: engineer reads specification, implements it, writes tests to verify the specification is met. Code quality is deterministic, if the spec is followed and tests pass, the code is good.
Vibe coding: founder describes vague intent ("users should be able to upload images"), AI generates code that might match the intent, might not. Testing happens later (if at all). The gap between intent and implementation is huge.
The problem isn't AI code quality. It's that AI generates to a prompt, not a specification. Prompts are ambiguous. Specifications are not.
Real examples we've seen:
- File upload: Founder wanted "users can upload images". AI implemented it. Doesn't validate file size. Doesn't check MIME types. Doesn't resize large images. It works 95% of the time. The other 5% is production chaos.
- Payment processing: Founder wanted "stripe integration". AI hooked it up. Doesn't handle failed charges. Doesn't retry on network failure. First time a payment fails, the order enters a broken state.
- Search: Founder wanted "users can search products". AI implemented basic text search. Doesn't handle special characters. Doesn't handle empty searches. Doesn't paginate results. Works fine with 10 products, breaks with 1000.
None of these are "bad code." They're code that doesn't handle edge cases because the AI couldn't infer them from a vague prompt.
Why Traditional QA Fails on Vibe Code
Here's the trap: you can't test an ambiguous specification.
Traditional QA would write test cases like this:
// Traditional QA test
test('user can upload image', async () => {
const file = new File(['content'], 'test.jpg');
const result = await uploadImage(file);
expect(result.success).toBe(true);
expect(result.url).toBeTruthy();
});
test('uploaded image is retrievable', async () => {
const url = await uploadImage(validFile);
const retrieved = await fetch(url);
expect(retrieved.status).toBe(200);
});
These tests pass. The feature "works." But they don't catch:
- 5 GB video file uploaded as "image"
- Image with embedded malware
- Upload during network interruption (retry not implemented)
- Multiple concurrent uploads from same user
- Storage running out of space
The AI code isn't broken, it just doesn't implement the edge cases nobody specified.
The New QA Playbook: Intent-Based Testing
Testing vibe-coded apps requires a different approach. Instead of testing against a specification (which doesn't exist), test against intent.
Step 1: Clarify the Actual Intent
Before testing, make the implicit intent explicit. Take your vague prompt and turn it into a detailed intent specification:
// What the founder said:
"Users can upload images"
// What intent-based testing asks:
- What file sizes are acceptable? (max 10MB)
- What file types? (JPG, PNG, WebP only)
- What happens on upload failure? (retry 3 times, then error message)
- What happens during network interruption? (pause/resume, not restart)
- What's the user experience? (progress bar, estimated time)
- What if storage fails? (graceful error, don't charge user)
- What if user uploads duplicate? (warn, prevent, or allow?)
- What's the success criteria? (file persists, can be retrieved, can be deleted)
This transforms the prompt from vague to testable.
Step 2: Test Against Intent, Not Implementation
// Intent-based test for file upload
describe('file upload intent', () => {
test('accepts valid image files', async () => {
const validFormats = ['image/jpeg', 'image/png', 'image/webp'];
for (const format of validFormats) {
const file = new File(['data'], 'test', { type: format });
const result = await uploadImage(file);
expect(result.success).toBe(true);
}
});
test('rejects invalid file types', async () => {
const file = new File(['data'], 'test.exe', { type: 'application/octet-stream' });
const result = await uploadImage(file);
expect(result.success).toBe(false);
expect(result.error).toContain('file type');
});
test('enforces file size limits', async () => {
const largeFile = new File([new ArrayBuffer(11 * 1024 * 1024)], 'large.jpg', { type: 'image/jpeg' });
const result = await uploadImage(largeFile);
expect(result.success).toBe(false);
expect(result.error).toContain('size');
});
test('handles network failures gracefully', async () => {
simulateNetworkFailure();
const result = await uploadImage(validFile);
// Should retry, not fail immediately
expect(result.retries).toBeGreaterThan(0);
});
test('provides upload progress feedback', async () => {
const progressEvents = [];
uploadImage(largeFile, {
onProgress: (progress) => progressEvents.push(progress)
});
expect(progressEvents.length).toBeGreaterThan(1);
expect(progressEvents[progressEvents.length - 1]).toBe(100);
});
});
These tests don't care how the upload is implemented. They care that the intent, "users can upload images, safely, reliably", is actually delivered.
Step 3: Workflow Validation Testing
Vibe code often works in isolation but fails in workflows. Test the full user journeys, not individual features:
describe('seller product upload workflow', () => {
test('seller can upload product with image and sell it', async () => {
const seller = await createSeller();
// Upload product
const product = await seller.createProduct({
name: 'Vintage Chair',
description: 'Beautiful mid-century modern chair',
image: validImage
});
expect(product.id).toBeTruthy();
// Product should be discoverable
const foundProduct = await searchProducts('Vintage Chair');
expect(foundProduct).toContainEqual(
expect.objectContaining({ id: product.id })
);
// Buyer should be able to purchase
const buyer = await createBuyer();
const purchase = await buyer.purchase(product.id);
expect(purchase.status).toBe('pending');
// Seller should see order
const orders = await seller.getOrders();
expect(orders).toContainEqual(
expect.objectContaining({ productId: product.id, buyerId: buyer.id })
);
});
});
This test catches bugs that isolated feature tests miss: payment flow issues, search indexing delays, data consistency problems across workflows.
Monitoring Vibe Code in Production
Since vibe-coded apps have inherent edge case gaps, production monitoring is critical:
Intent Health Metrics
// Monitor whether the core intent is being achieved
const intentMetrics = {
// File upload intent: "users can reliably upload images"
uploadSuccessRate: 0.98, // Should be >95%
uploadRetryRate: 0.02, // Some retries are fine
uploadAbandonmentRate: 0.005, // Rate users give up
averageUploadTime: '3.2s', // Baseline for comparison
// Search intent: "users can discover products"
searchResultsPerQuery: 12, // Not too few, not too many
searchZeroResultsRate: 0.05, // Acceptable rate of "nothing found"
clickThroughRate: 0.35, // Users actually click results
// Payment intent: "users can complete purchases"
paymentSuccessRate: 0.97, // Should be >95%
paymentRetryCount: 1.2, // Average retries per failed attempt
abandonmentRate: 0.02 // Rate users give up at checkout
};
// Alert if intent metrics degrade
monitorMetrics(intentMetrics, {
onDegradeion: (metric, threshold, actual) => {
alert(`INTENT FAILURE: ${metric} dropped to ${actual} (threshold: ${threshold})`);
}
});
The Complete Vibe QA Playbook
Before You Ship
- Clarify intent for each major feature (file upload, search, payment, etc.)
- Write intent tests that validate edge cases, not just happy paths
- Test full workflows from end-to-end, not feature isolation
- Security review the generated code (AI often misses CORS, CSRF, injection risks)
- Performance test with realistic data volumes
During Alpha/Beta
- Canary deploy to small user group first
- Monitor intent metrics from day one
- Collect error reports and add tests for each edge case found
- Survey users on whether the intent is actually met (not just whether it works)
Post-Launch
- Watch for intent degradation as load increases
- Build a feedback loop from production errors back to test cases
- Continuously expand intent coverage as new edge cases emerge
The Honest Truth
Vibe-coded apps will always have higher defect rates than carefully engineered software. That's not a problem if you accept it and compensate with better testing and monitoring.
The companies shipping vibe-coded apps successfully aren't the ones pretending the code is as solid as hand-written software. They're the ones building intent-based QA infrastructure that catches the edge cases the AI missed.
Vibe coding isn't about eliminating engineering. It's about shifting where engineering effort goes, from implementation to validation.
The math works out: it takes 40 hours to hand-code a marketplace and 2 hours to vibe-code it. Even if you spend 10 hours on intent-based QA, you've saved 28 hours. That's a 7x speedup. You just have to be disciplined about testing.
Shipping a vibe-coded app? Don't skip QA.
alt.qa helps teams build intent-based testing infrastructure for AI-generated code. From edge case discovery to intent metric monitoring, we've got the tools for the vibe era.
Get QA infrastructure