.jpg)
If you've ever spent a Friday afternoon buried in a lengthy user story, manually dissecting requirements and writing dozens of test scenarios, you're not alone. What if, instead of starting from a blank page, you had a trusted teammate who could read requirements, understand context, identify edge cases, and draft comprehensive test scenarios in seconds?
This is where AI Agents come in.
Rather than replacing testers, AI can act as a co-worker that helps automate repetitive tasks, improve test coverage, and free us to focus on what humans do best—critical thinking, risk analysis, and delivering quality software.
Before we build anything, let's get the foundational vocabulary right — because "AI" gets used to mean wildly different things.
For test scenario generation, we want an agent — something that can read a requirements document, reason about edge cases, write test cases, verify they make sense, and save them to a file, all without you intervening.
Before we write a single line of code, let's design our AI teammate.
Just like a human tester needs knowledge, tools, and context to do their job effectively, an AI Agent needs three core components:
The language model that understands requirements, reasons about what needs to be tested, identifies edge cases, and generates test scenarios.
The capabilities available to the agent, such as reading requirement files, writing test scenarios to a document, calling APIs, or integrating with systems like Jira.
The context the agent carries throughout its work—requirements it has already analyzed, test scenarios it has generated, validation results, and previous decisions.
With these components in place, the agent follows a simple workflow:
We'll use Node.js and the Google Gemini API (free tier — no credit card needed). The same pattern works with any LLM provider.
mkdir test-scenario-agent
cd test-scenario-agent
npm init -y
npm install @google/genai dotenv
Create a .env file and add your key:
GEMINI_API_KEY=your-key-here
Add "type": "module" to package.json so we can use import syntax.
The system prompt is the most important thing you'll write. It defines how the agent thinks, what it prioritises, and what format it outputs.
Think of the system prompt as the job description you hand to a new QA engineer on their first day. It sets the rules for everything that follows.
const SYSTEM_PROMPT = `You are a senior QA engineer with 10 years of experience.
When given a software requirement, you will:
1. Identify ALL testable scenarios: happy path, edge cases, negative tests,
boundary values, security, and accessibility.
2. Write each scenario with these exact fields:
- id: TC-001, TC-002, etc.
- title: a short descriptive name
- preconditions: array of strings
- steps: array of strings (numbered actions)
- expected_result: specific and measurable
- severity: critical / high / medium / low
3. Output ONLY valid JSON — no explanation, no markdown backticks.
`;
import { GoogleGenAI } from "@google/genai";
import * as dotenv from "dotenv";
import { writeFileSync } from "fs";
dotenv.config();
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const MODEL = "gemini-2.5-flash"; // Free tier, fast, reliable
// ─────────────────────────────────────────────
// THE SYSTEM PROMPT — the agent's brain
// ─────────────────────────────────────────────
const SYSTEM_PROMPT = `You are a senior QA engineer with 10 years of experience.
When given a software requirement, you will:
1. Identify ALL testable scenarios: happy path, edge cases, negative tests,
boundary values, security, and accessibility.
2. Write each scenario with these exact fields:
- id: "TC-001", "TC-002", etc.
- title: a short descriptive name
- preconditions: array of strings (what must be true before the test)
- steps: array of strings (numbered actions the tester performs)
- expected_result: what should happen (must be specific and measurable)
- severity: one of "critical", "high", "medium", "low"
3. Output ONLY valid JSON — no explanation, no markdown, no extra text.
JSON format:
{
"scenarios": [
{
"id": "TC-001",
"title": "...",
"preconditions": ["..."],
"steps": ["1. ...", "2. ..."],
"expected_result": "...",
"severity": "critical"
}
]
}`;
/ ─────────────────────────────────────────────
// Helper: safely parse JSON from AI response
// Gemini sometimes wraps output in ```json ... ```
// ─────────────────────────────────────────────
function parseJSON(text) {
const cleaned = text.replace(/```json|```/g, "").trim();
return JSON.parse(cleaned);
}
// ─────────────────────────────────────────────
// STEP A: Generate test scenarios
// Note: systemInstruction now goes inside config, not getGenerativeModel
// Note: response.text is a property now, not response.text()
// ─────────────────────────────────────────────
async function generateScenarios(requirement) {
console.log("Generating test scenarios...");
const response = await ai.models.generateContent({
model: MODEL,
contents: `Generate test scenarios for this requirement:\n\n${requirement}`,
config: {
systemInstruction: SYSTEM_PROMPT,
},
});
return parseJSON(response.text);
}
// ─────────────────────────────────────────────
// STEP B: Quality review — a second AI call acting as the critic
// ─────────────────────────────────────────────
async function critiqueScenarios(scenarios) {
console.log("Running quality review...");
const response = await ai.models.generateContent({
model: MODEL,
contents: `Review these test scenarios and find any that have:
- Ambiguous steps (could be interpreted in 2+ ways)
- Missing preconditions (the starting state is unclear)
- Vague expected results (no clear pass/fail criterion)
Return ONLY a JSON array of issues. If everything looks good, return [].
Format: [{ "id": "TC-001", "issue": "describe the problem" }]
Scenarios to review:
${JSON.stringify(scenarios, null, 2)}`,
config: {
systemInstruction:
"You are a strict QA reviewer. You only respond with valid JSON arrays.",
},
});
return parseJSON(response.text);
}
// ─────────────────────────────────────────────
// STEP C: Fix the issues found
// ─────────────────────────────────────────────
async function fixScenarios(scenarios, issues) {
console.log(`Fixing ${issues.length} quality issue(s)...`);
const response = await ai.models.generateContent({
model: MODEL,
contents: `Fix these specific issues in the test scenarios and return the complete updated list as JSON.
Issues to fix:
${JSON.stringify(issues, null, 2)}
Current scenarios:
${JSON.stringify(scenarios, null, 2)}`,
config: {
systemInstruction: SYSTEM_PROMPT,
},
});
return parseJSON(response.text);
}
A single-shot agent (prompt -> output) is useful, but it still needs a human to review the results. Loop engineering is the practice of designing feedback cycles so the agent evaluates and improves its own output — removing the human reviewer entirely.
Instead of generating once and hoping for the best, the agent goes through this cycle automatically:
Generate --> Validate --> Score --> Critique --> Refine --> (loops back until quality threshold is met)
The agent compares generated tests against original requirements, producing more scenarios if any conditions, actors, or edge cases lack coverage.
A critic agent reviews each scenario to flag ambiguous steps, missing preconditions, or vague expected results for the first agent to revise.
The agent identifies and merges duplicate scenarios testing the same functionality, keeping the test suite lean.
// ─────────────────────────────────────────────
// THE MAIN LOOP — generate → review → fix → repeat
// ─────────────────────────────────────────────
async function runAgent(requirement) {
const MAX_LOOPS = 3;
// Generate first draft
let result = await generateScenarios(requirement);
console.log(`Generated ${result.scenarios.length} scenarios`);
// Review and fix in a loop until clean
for (let loop = 1; loop <= MAX_LOOPS; loop++) {
const issues = await critiqueScenarios(result.scenarios);
if (issues.length === 0) {
console.log(`Quality check passed on loop ${loop} — no issues found!`);
break;
}
console.log(`Found ${issues.length} issue(s) on loop ${loop}, fixing...`);
result = await fixScenarios(result.scenarios, issues);
if (loop === MAX_LOOPS) {
console.log("Reached max loops — saving best version so far");
}
}
// Save output to file
const outputFile = "test-scenarios.json";
writeFileSync(outputFile, JSON.stringify(result, null, 2));
console.log(`\n Saved to ${outputFile}`);
console.log(`Total scenarios: ${result.scenarios.length}`);
return result;
}
// ─────────────────────────────────────────────
// YOUR REQUIREMENT — change this to anything you want
// ─────────────────────────────────────────────
const requirement = `
As a registered user, I should be able to reset my password by
entering my email address on the forgot password page.
The system should send a password reset link to my email
that is valid for 24 hours and can only be used once.
`;
runAgent(requirement);
node agent.js
Generating test scenarios...
Generated 11 scenarios
Running quality review...
Found 2 issue(s) on loop 1, fixing...
Running quality review...
Quality check passed on loop 2 — no issues found!
Saved to test-scenarios.json
Total scenarios: 18
Key insight: Each "check" function is itself an AI call with a specific prompt. You're using AI to evaluate AI — this is the core pattern behind autonomous quality control. The MAX_LOOPS guard prevents infinite loops in edge cases.
Generating test scenarios is just the beginning. To make the agent truly useful in a real-world testing workflow, the next step is to integrate it with your Test Management Tool.
Once the agent generates and validates the test scenarios, it can automatically create test cases in tools such as Jira, TestRail, Xray, or Zephyr using their APIs. Instead of manually copying scenarios from a document into your test management system, the agent can populate fields such as the test case title, preconditions, test steps, expected results, priority, and labels.
This not only saves significant manual effort but also ensures that your test repository stays up to date with the latest requirements. By combining AI-generated scenarios with your existing test management workflow, you can move from requirements to executable test cases in a matter of minutes.
Where to go next: Once you have test scenario generation working, the same loop architecture extends naturally to generating test data, writing automation code (Playwright/Cypress steps), detecting flaky tests, and triaging failed test runs. The pattern is always the same — give the agent a goal, tools, and a feedback loop.