Why AI-Generated Web Code Still Needs Rigorous Testing
Large Language Models (LLMs) can automate the development process by generating substantial amounts of web application code in just a few minutes. Nonetheless, it is important to bear in mind that these models are pattern-based and not deterministic. Work in the domain of AI programming assistants shows that AI-based code often exhibits security vulnerabilities in real-world testing. A study on GitHub's features showed that approximately 40% of the generated code was susceptible to security issues, emphasizing the need for careful testing and scrutiny.
In other words, programmers and engineers employ a particular mode of working rooted in software methodology, which enables them to tackle this problem straightforwardly and continually incorporate AI-produced code as it is generated. However, AI speed tends to result in errors being overlooked every now and then. In some instances, project managers allocate more rigorous testing because they have to ensure that what people often call correct code" has become "perfect, functional, and secure code." Every code that is deemed complete has to go through several tests, from simple static checkups and unit tests to more sophisticated integration tests, end-to-end tests, automated checks for security breaches, capacity checks, and manual code reviews, to ensure that the delivered software is functionally adequate and meets security requirements.
This article presents several testing methods for LLM systems that generate HTML code intended for use on the web. Frameworks such as Node.js and React are examples of the development environments commonly used in these applications. To support safe code integration, the article also includes a pre-merge checklist for combining code branches, along with recommendations for testing the prompts and triggers themselves, ensuring that AI-generated output does not introduce security risks when incorporated into the final codebase
Why AI-Generated Web Code Requires Extra Scrutiny
It is widely accepted that traditional software bugs stem from human error, since humans remain central to most development processes. AI-generated bugs, however, arise in a different way. When generating code, AI models often attempt to fill in missing context based on patterns learned from training data. As a result, the code may appear to work under certain testing conditions and pass initial checks, but fail when those conditions change. These logical gaps often emerge at system boundaries—such as authentication mechanisms, responses to unusual or malformed requests, concurrent operations, application reloads, version differences, or vulnerabilities introduced through improperly configured security defaults.
Security, therefore, is not merely a legal obligation but a practical necessity. One well-known study examined GitHub Copilot’s ability to generate code for security-sensitive tasks and found that a significant portion of the suggestions contained insecure implementations. The researchers also noted that the wording and context of prompts played an important role in shaping these recommendations. Follow-up studies using newer versions of similar tools and more advanced methodologies have confirmed that AI-generated code can still introduce security weaknesses if left unchecked.
These findings highlight the responsibility of developers who rely on LLMs to build web features. Moving beyond the traditional “it works on my machine” mindset requires adapting development practices to better evaluate AI-generated code. Efforts should focus on standardizing how inputs and outputs are tested, ensuring that code executes reliably across diverse scenarios that mimic real-world conditions, and continuously refining the guidance provided to AI systems through clearer prompts and structured feedback.
Testing Layers for LLM-Generated Code
The main idea is not to rely on a single test format but to use multiple approaches, each targeting different types of 'AI errors.'
To detect fundamental problems, static assessments such as linting and type verification can be performed, which can help identify certain issues early on. These issues must be identified and addressed promptly, as they are expected to be easy to detect and fix quickly. A good tool that can be used for this is ESLint, as it detects the code patterns in JavaScript, which is also well or very well adaptable to the best coding conventions of your organization.
How-to (ESLint quick start):
npm init @eslint/config@latest
npx eslint src/
According to the official documentation of the ESLint tool, it is preferable to execute the following sequence of commands: "npm init @eslint/config@latest” and then “npx eslint” the required files and folders.
It is also worth considering that the ruleset should include those designed for heightened security. For example, there is a security plug-in called eslint-plugin-security that is specifically created to monitor the presence of known security problems in JavaScript and Node.js codes. Although there may be instances of information misuse, eslint-plugin-security provides good support for developers.
How-to (security lint):
npm i -D eslint-plugin-security
Once completed, turn on the appropriate rules in your ESLint configuration, noting that different ESLint setups may require slightly different setup techniques.
During testing, attention should be paid to the more elusive aspects of the program, such as logic algorithms, edge cases, and consistency testing of the generated results. One of the strategies that is readily comprehensible and offered by Jest is to write tests and use the expect() construct that includes tools and the toBe mechanism to confirm the results as desired.
How-to (Node/JS + Jest):
// utils/sanitizeSlug.js
export const sanitizeSlug = (s) => s.trim().toLowerCase().replace(/\s+/g, "-");
// utils/sanitizeSlug.test.js
import { sanitizeSlug } from "./sanitizeSlug";
test("sanitizes slugs", () => {
expect(sanitizeSlug(" Hello World ")).toBe("hello-world");
});
A helpful routine for improving an LLM model is to consider assumptions about the input. If the model handles input in the form of “slugs being space-separated”, it must be stated in the code, or there is a danger that this code will lead to bugs during real practice tests.
Component tests: test React like a user, not like a compiler
React Testing Library is widely recognized for its focus on usability-driven testing, which helps build confidence in how an application behaves from a user’s perspective. Rather than relying solely on abstract best practices, the official React testing guidance encourages developers to adopt React Testing Library as the primary tool for testing components and application behavior.
How-to (React + React Testing Library + Jest):
// LoginButton.jsx
export function LoginButton({ onLogin }) {
return <button onClick={onLogin}>Log in</button>;
}
// LoginButton.test.jsx
import { render, screen, fireEvent } from "@testing-library/react";
import { LoginButton } from "./LoginButton";
test("calls onLogin when clicked", () => {
const onLogin = jest.fn();
render(<LoginButton onLogin={onLogin} />);
fireEvent.click(screen.getByText("Log in"));
expect(onLogin).toHaveBeenCalledTimes(1);
});
Integration tests: verify contracts between modules and services
Usually, AI-generated code fails to pass the integration testing. This is because the AI model was imprecise in defining the contract, such as response structures, status codes, operation of authentication middleware, database connection procedures, and similar details.
When it comes to Node.js applications, many developers would opt for Supertest which is an extension and support of SuperAgent, which provides HTTP assertion support for testing Node HTTP servers.
How-to (Express + Supertest):
import request from "supertest";
import app from "../app";
test("GET /health returns ok", async () => {
await request(app)
.get("/health")
.expect(200);
});
E2E tests: make the browser prove the feature works
End-to-end (E2E) tests can reveal defects that other testing approaches may miss. They are particularly effective at validating navigation flows, live views, data persistence, HTTP cookies, access controls, and the overall behavior of an application when users interact with it in unpredictable ways.
Cypress strives to be more than just a solution for end-to-end testing. Its documentation contains examples that help you write an end-to-end test from scratch. In contrast, action + assertion chains are more important from the perspective of the playwright, with additional functions for waiting inside elements, which greatly alleviates the necessity to use sleep states only for checks.
How-to (install Cypress):
npm install cypress --save-dev
npx cypress open
How-to (Playwright E2E test snippet):
import { test, expect } from "@playwright/test";
test("login redirects to dashboard", async ({ page }) => {
await page.goto("/login");
await page.getByLabel("Email").fill("user@example.com");
await page.getByLabel("Password").fill("password123");
await page.getByRole("button", { name: "Log in" }).click();
await expect(page).toHaveURL(/dashboard/);
});
Playwright documents this general “do actions, then assert the state” structure, and notes its auto-waiting behavior.
Security testing: treat “generated code” as a risk multiplier
Use dependency scanning and static analysis to improve the security of web applications.
When it comes to reviewing the endpoints and UI flows (auth, access control, injection etc.) that are generated by the AI model, one may refer to the list of the OWASP Top 10 guidelines as a checklist.
Dependency Scanning (Snyk + npm audit):
The snyk test checks for open-source vulnerabilities and license issues.
npm audit exits non-zero on found vulnerabilities (ideal for CI gates).
How-to (Snyk):
snyk test
How-to (Snyk Code SAST):
snyk code test
Snyk code test, called Snyk, performs Static Application Security Testing against the source code.
Although not required, it is advised to activate the CodeQL extension in GitHub Actions in order to conduct code scanning.
Performance testing: “It works” is not “it survives traffic.”
AI-scripted functionalities are also known to cause performance detriment, either through the introduction of additional DB access calls or multiple N+1 queries; thus, it is advisable to smoke load test critical routes. k6 has proper documentation on how to write and execute such tests.
How-to (k6 smoke test):
import http from "k6/http";
import { check, sleep } from "k6";
export default function () {
const res = http.get("https://example.com/api/health");
check(res, { "status is 200": (r) => r.status === 200 });
sleep(1);
}
Both k6 and Artillery are equipped with documentation on how to formulate HTTP requests and set up tests. Artillery can be installed either through npm or npx to execute tests.
Snapshot and golden master testing: use sparingly, review aggressively
Creating snapshot tests is useful for monitoring changes in different versions of the app that should not be changed quietly (such as HTML email templates, stable fragments of the user interface, etc.). The Jest snapshot file requires verification of snapshot outputs alongside code modifications, which are then reviewed to prevent misunderstandings; Jest compares future runs with past snapshots and reports errors if discrepancies are found.
How-to (Jest snapshot):
import renderer from "react-test-renderer";
import { Banner } from "./Banner";
test("banner matches snapshot", () => {
const tree = renderer.create(<Banner />).toJSON();
expect(tree).toMatchSnapshot();
});
The Ultimate Golden Master Hack for LLM Code: Conformed merely pictures on the screen are called “the updater’s theatre”. Insist on a detailed code changes review.
Code Review: The Essential Human Layer
Code review remains a critical step in the development process, providing an opportunity to ask key questions such as, “Is this approach valid?” and “Does it align with the system’s architecture?” The Secure Software Development Framework (SSDF), developed by the National Institute of Standards and Technology (NIST), was introduced to address a common gap in many Software Development Life Cycles (SDLCs), where security is often overlooked early in development. SSDF encourages teams to integrate secure practices and behavioral patterns throughout the development cycle. Within this framework, mechanisms such as code reviews and process controls remain essential, as they rely on human judgment rather than automated systems.
For AI-generated PRs, code review should explicitly check:
- authz/authn boundaries
- input validation and encoding
- error handling and logging
- dependency choices
- “magic” regexes and crypto (danger zone)
Testing the prompt and validating LLM outputs
A great number of teams skip over the small fact that the prompt is itself a code construct. Since the wording of the prompt can generate particular behavior, it is imperative that it is put to tests as is done for APIs.
Workflow:
i. Define prompt contract: Templates with stack, versions, constraints, and testing requirements.
ii. Request tests: Generate and run unit/integration tests before trusting features.
iii. Create regression suite: Store prompts, invariants, and run tests/scripts.
iv. Use checklists: Keep prompts as review checklists for each PR.
Before merging AI-generated code, require:
- lint/type checks pass (ESLint)
- unit + integration pass (Jest + Supertest patterns)
- at least one E2E flow passes (Cypress/Playwright)
- dependency scan passes or is triaged (Snyk / npm audit)
Commonly Used Commands
- ESLint:
npm init @eslint/config@latest then npx eslint src/
- Cypress:
npm install cypress --save-dev then npx cypress open
- Snyk Dependency Scan:
snyk test
- Snyk SAST:
snyk code test
- npm Dependency Audit:
npm audit
- k6: Write a script, then run with k6
- Artillery:
npm install -g artillery@latest (or npx artillery@latest), then artillery run my-test.yml
CI automation with test gates
The main purpose of the layers is to verify that they are genuine and feasible. GitHub Actions are automation scripts written in YAML syntax that can contain steps and jobs. According to the instructions provided in the official guide for Node.js Actions by GitHub, the following three standard procedures must be followed: installing Node, injecting dependencies into the environment, and testing.
Minimal GitHub Actions workflow example
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Lint
run: npx eslint .
- name: Unit + integration
run: npm test
- name: Dependency scan
run: |
npm audit
snyk test
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: SAST scan (optional but strong)
run: snyk code test
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
The actions/setup-node install and caches the Node in workflows. The playwright furnishes CI hints and assists in creating GitHub Actions workflows. GitHub documentation goes into depth on how CodeQL operates within GitHub actions workflow.
Before you merge: checklist, pitfalls, and mitigations
Pre-merge checklist for AI-generated web code
- Lint passes: Ensure lint passes and security lint is reviewed.
- Unit tests: Cover model assumptions (edge cases, input shapes).
- Integration tests: Confirm API contracts (status codes, auth, schema).
- E2E tests: Cover at least one critical user journey (e.g., login).
- Dependency scan: Run Snyk or npm audit and triage findings.
- SAST: Run Snyk code test or CodeQL for risky changes.
- Snapshot diffs: Review snapshot diffs like code; no auto-updates.
- CI checks: Require CI checks before merge; no exceptions.
Common Pitfalls (and How to Avoid Them)
Passing tests: Test real user behavior, as React Testing Library suggests.
Over-mocking: Avoid mocking everything—use a real test DB.
Flaky E2E tests: Use Playwright to reduce timing issues.
Snapshot testing: Don’t auto-update snapshots; review them.
Skipping security scanning: Include security checks for all PRs, big or small.
Closing thought
The most important responsibility assigned to an AI for web generation is to ensure that validation becomes unobtrusive and a straightforward activity. Individuals tend to trust processes that are uniform and happen repeatedly.
Although LLMs streamline the process of generating the coding, it is the exhaustive examination that makes the process of its correctness more efficient. Those who achieve the desired outcomes are the ones who do all the above: set up CI-enforced gates, apply testing at different levels, and treat prompts as part of the design.