{"slug": "how-to-test-a-web-app-built-with-claude-code-or-cursor-before-you-ship", "title": "How to test a web app built with Claude Code or Cursor before you ship", "summary": "A developer outlined a workflow for validating web apps generated by coding agents like Claude Code and Cursor, arguing that agents verify compilation and known tests but not whether real users can sign up, log in, or pay. The approach centers on a short CRITICAL_FLOWS.md list of must-never-break journeys, a Claude Code Stop hook that reruns typecheck and unit tests and blocks completion on failure, and Playwright tests run against per-branch preview deploys using role and label selectors.", "body_md": "Your coding agent says the feature is done. The build is green. Then a user tells you they can't log in.\n\nThis happens a lot with apps built in Claude Code or Cursor. The agent checks that the code compiles and that the tests it knows about pass. It doesn't check that a real person can still sign up, log in and pay. That part is still your job.\n\nHere's the short answer: **pick the few flows that must never break, make the agent run fast checks before it stops, and run those flows in a real browser against a preview deploy before every release.** The rest of this post is the step-by-step version I use.\n\nBefore you write any test, write a list. Not every page. Only the journeys a customer would notice first if they broke.\n\nFor most SaaS apps that's something like:\n\nKeep it under ten. Put it in a file in the repo, like `CRITICAL_FLOWS.md`, so both you and the agent can read it. A one-line description per flow is enough:\n\n``` php\n# Critical flows\n1. Sign up: new email -> confirm -> lands on empty dashboard\n2. Log in: existing user -> dashboard shows their projects\n3. Create project: name it -> it appears in the list after reload\n4. Upgrade: pick Pro -> test card -> plan shows \"Pro\" in settings\n```\n\nThe \"after reload\" part matters. A lot of agent bugs only show when the data has to come back from the server.\n\nAgents follow rules files: `CLAUDE.md` for Claude Code, `.cursor/rules` or `AGENTS.md` for Cursor. Add two things:\n\n```\n## Before you finish a task\n- Run `npm run typecheck && npm test` and fix anything that fails.\n- If you touched auth, billing or the dashboard, list which items in\n  CRITICAL_FLOWS.md your change could affect.\n```\n\nThe second line is cheap and useful. It makes the agent tell you the blast radius of its change, so you know what to check.\n\nBut rules are advice. The agent can still skip them.\n\nClaude Code has hooks, which run a command at fixed points. A `Stop` hook runs when the agent is about to finish. If the command exits with code 2, Claude Code keeps the agent working and shows it the error.\n\nIn `.claude/settings.json`:\n\n```\n{\n  \"hooks\": {\n    \"Stop\": [\n      {\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"npm run typecheck 1>&2 && npm test --silent 1>&2 || exit 2\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\nKeep this command fast. Typecheck and unit tests, maybe your access-control tests. If it takes five minutes on every turn, you'll turn it off. Browser tests belong later in the pipeline.\n\nCursor has its own hooks feature too. Check its docs for the current format, since it has been changing.\n\nYou can't test the app a user sees from inside the repo. You need it running.\n\nVercel, Netlify and Cloudflare Pages all build a unique preview URL for each branch or pull request. Turn that on. Now every change the agent makes has a real address you can open, with nothing mocked.\n\nUse a separate test database or seeded test accounts for previews. You don't want a signup test creating real users in production.\n\nThis is the step that catches the things unit tests miss: a button whose `onClick` was dropped, a form that submits but never saves, a redirect loop after login.\n\nIf you write code-based tests, Playwright is a good default. Here is the login flow from the list above:\n\n``` js\nimport { test, expect } from '@playwright/test';\n\nconst BASE_URL = process.env.PREVIEW_URL ?? 'http://localhost:3000';\n\ntest('existing user can log in and sees their projects', async ({ page }) => {\n  await page.goto(`${BASE_URL}/login`);\n  await page.getByLabel('Email').fill(process.env.TEST_USER_EMAIL!);\n  await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD!);\n  await page.getByRole('button', { name: 'Log in' }).click();\n\n  await expect(page).toHaveURL(/\\/dashboard/);\n  await expect(page.getByText('My first project')).toBeVisible();\n});\n```\n\nTwo habits make these tests worth having:\n\nUse role and label selectors (`getByRole`, `getByLabel`) rather than CSS classes. Agents rename classes all the time. They change visible button text much less often.\n\nSome bugs look fine in the browser. The API returns `200 OK`, the page shows a spinner and then nothing, and the real problem is a wrong field in the JSON.\n\nFor the endpoints behind your critical flows, check more than the status code:\n\n``` js\ntest('projects API returns the user\\'s projects', async ({ request }) => {\n  const res = await request.get(`${BASE_URL}/api/projects`, {\n    headers: { Authorization: `Bearer ${process.env.TEST_USER_TOKEN}` },\n  });\n  expect(res.status()).toBe(200);\n  const body = await res.json();\n  expect(Array.isArray(body.projects)).toBe(true);\n  expect(body.projects[0]).toHaveProperty('name');\n});\n```\n\nAlso try one request as the wrong user. Agent-written backends often forget to check that a record belongs to the person asking for it.\n\nRun the same checks on every pull request, whoever or whatever wrote it:\n\n```\nname: checks\non: pull_request\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n      - run: npm ci\n      - run: npm run typecheck && npm test\n      - run: npx playwright install --with-deps chromium\n      - run: npx playwright test\n        env:\n          PREVIEW_URL: ${{ vars.PREVIEW_URL }}\n          TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}\n          TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}\n```\n\nGetting the preview URL for each PR depends on your host. Most of them post a deployment status you can wait for, then read the URL from. Mark the job as required in your branch protection settings so a red run actually blocks the merge.\n\nWhen a browser check fails, don't just tell the agent \"a test is red.\" Give it the flow, the step that failed, and what the page showed. Paste the Playwright error and the trace or screenshot.\n\nA message like \"After clicking Log in, the page stayed on /login and showed 'Invalid session'\" gets you a much better fix than a stack trace alone.\n\nTests only check what someone wrote down. Before a bigger release, click through your critical flows yourself once, on a phone and in Safari. Try a payment failure in test mode. It takes ten minutes and catches the things nobody thought to automate.\n\nNone of this needs a QA team. It needs a short list and the habit of checking it every time.\n\n*I'm building [AlwaysQA](https://alwaysqa.com/), which checks login, checkout and other critical flows in a real browser after your coding agent ships a change. It only checks the flows you describe, and it doesn't replace your unit tests.*", "url": "https://wpnews.pro/news/how-to-test-a-web-app-built-with-claude-code-or-cursor-before-you-ship", "canonical_source": "https://dev.to/maciek_chmura_building/how-to-test-a-web-app-built-with-claude-code-or-cursor-before-you-ship-1cip", "published_at": "2026-11-03 09:16:21+00:00", "updated_at": "2026-11-03 09:28:45.601101+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Claude Code", "Cursor", "Playwright", "Vercel", "Netlify", "Cloudflare Pages", "Anthropic"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/how-to-test-a-web-app-built-with-claude-code-or-cursor-before-you-ship", "markdown": "https://wpnews.pro/news/how-to-test-a-web-app-built-with-claude-code-or-cursor-before-you-ship.md", "text": "https://wpnews.pro/news/how-to-test-a-web-app-built-with-claude-code-or-cursor-before-you-ship.txt", "jsonld": "https://wpnews.pro/news/how-to-test-a-web-app-built-with-claude-code-or-cursor-before-you-ship.jsonld"}}