Frontend Development Agents

By the Syncfusion DevTutorials team · Last updated

How a frontend agent changes components until the tests pass: state handling, UI debugging, integration tests, and the right tools and stop rule.

Think about building with LEGO from instructions.

A careful builder looks at the picture of the finished model first. They check which pieces they already have, so they don't build a part that's in the box already. After each section, they compare it with the picture.

A careless builder stops as soon as the pieces are all used. They never compare the result with the picture, so they don't notice the wheel is on backwards.

A frontend development agent builds the part of the app people see. The "picture of the finished model" is the tests.

The main idea: a frontend agent changes components until the tests pass. The most important settings are its tools and its stop rule: "stop when every test passes", not "stop when the code is written".

A Real-World Example

The design is complete. Now the planning agent's "panel" task is assigned to the frontend agent: update web/TicketPage.js so it displays all 4 export statuses.

The tests in web/TicketPage.test.js already define the required behavior:

StatusThe page must show
queuedExport queued
runningExport running
done, 80000 rowsDone: 80,000 rows
failedExport failed <button>Retry</button>

At the moment, the component only handles the "done" status and displays 80000 without commas. The frontend agent must fix the component, and it is not allowed to change the tests.

In simple words: the tests are the picture on the box. Keep building until the result matches the picture.

What You Will Learn

In this lesson, you will learn:

  1. How a frontend development agent works with a component.
  2. How to handle all the possible states of a component.
  3. How an agent debugs UI output by comparing the actual result with the expected result.
  4. What integration tests are and why they catch more problems.
  5. Why the agent's tools and stop condition determine whether the task is truly finished.

The frontend agent has three tools: read_file, write_file, and run_tests. It can modify files in web/, but it can never modify a .test.js file. This rule prevents the shortcut you saw in Lesson 5.

The frontend agent's loop: run the tests first, read the component and its helpers, make changes, run the tests again, and stop only when all tests pass.

Component Work

A component is a single part of the user interface, such as the status panel. In this course, a component is a simple function that takes data as input and returns the text that should be displayed.

A component is a function from data to output

function renderStatus(exp) {
  return "Done: " + exp.rows + " rows";
}

console.log(renderStatus({ status: "done", rows: 80000 }));
console.log(renderStatus({ status: "queued" }));

Expected output:

Done: 80000 rows
Done: undefined rows

This is the starting component. It always displays "Done", even when the export is still queued, and it displays undefined when there is no row count. Both of these are bugs that users would see.

State Handling

A component's state represents the situation it is currently displaying, such as queued, running, done, or failed. Good state handling means the component provides a clear response for every state, including unexpected ones.

A safe approach is to handle each known state separately and finish with a fallback for any unknown state.

Handle every state, plus a fallback

function formatRows(n) {
  return n.toLocaleString("en-US") + " rows";
}

function renderStatus(exp) {
  if (exp.status === "queued") return "Export queued";
  if (exp.status === "running") return "Export running";
  if (exp.status === "done") return "Done: " + formatRows(exp.rows);
  if (exp.status === "failed") return "Export failed <button>Retry</button>";
  return "Unknown status";
}

for (const status of ["queued", "running", "done", "failed", "paused"]) {
  console.log(status.padEnd(8) + "-> " + renderStatus({ status, rows: 80000 }));
}

Expected output:

queued  -> Export queued
running -> Export running
done    -> Done: 80,000 rows
failed  -> Export failed <button>Retry</button>
paused  -> Unknown status

"paused" is not a valid status at the moment. If the backend ever sends it, the page displays "Unknown status" instead of crashing or showing incorrect information.

UI Debugging

UI debugging means finding out why the screen shows the wrong result. An agent uses the same approach as a person: compare what is currently displayed with what is expected, then identify the first difference.

Find the first difference between wanted and got

function firstDifference(wanted, got) {
  for (let i = 0; i < Math.max(wanted.length, got.length); i++) {
    if (wanted[i] !== got[i]) return "at character " + i + ": wanted " + JSON.stringify(wanted.slice(i, i + 6)) + ", got " + JSON.stringify(got.slice(i, i + 6));
  }
  return "same";
}

console.log(firstDifference("Done: 80,000 rows", "Done: 80000 rows"));
console.log(firstDifference("Export queued", "Done: undefined rows"));
console.log(firstDifference("Export running", "Export running"));

Expected output:

at character 8: wanted ",000 r", got "000 ro"
at character 0: wanted "Export", got "Done: "
same

The first difference points directly to the problem. For the "done" status, the comma is missing, so the agent needs to add number formatting. For the "queued" status, the text is incorrect from the very first character, which means that state is not handled at all.

Integration Tests

A unit test checks a single function by itself. An integration test checks whether multiple parts work correctly together.

The TicketPage tests are integration tests. renderStatus calls formatRows from another file, web/format.js. If either function breaks, or if they no longer work correctly together, the test fails.

That is why the agent reads web/format.js before making changes. A helper function for formatting row counts already exists. Reusing it keeps number formatting in one place, and the tests verify that both parts work correctly together.

A unit test and an integration test

function formatRows(n) { return n.toLocaleString("en-US") + " rows"; }
function renderStatus(exp) { return exp.status === "done" ? "Done: " + formatRows(exp.rows) : "Export " + exp.status; }

const check = (name, got, wanted) => console.log((got === wanted ? "PASS " : "FAIL ") + name);
check("unit: formatRows alone", formatRows(80000), "80,000 rows");
check("integration: renderStatus uses formatRows", renderStatus({ status: "done", rows: 80000 }), "Done: 80,000 rows");
check("integration: failed shows Retry", renderStatus({ status: "failed" }), "Export failed <button>Retry</button>");

Expected output:

PASS unit: formatRows alone
PASS integration: renderStatus uses formatRows
FAIL integration: failed shows Retry

Only one integration test passes, and so does the unit test. However, the Failed state is missing the Retry button. Only a test that checks the complete output can catch that problem.

Tools and Stop Rule

Two settings determine whether a frontend agent actually finishes the task.

Tools. Without run_tests, the agent cannot compare the result with the expected behavior. As you saw in Lesson 3, it can write code, but it cannot verify whether the code is correct.

The stop rule. "Stop when the code is written" sounds reasonable, but it allows the agent to stop before checking anything. The correct rule is: "stop when all tests pass, or when you are blocked."

Two stop rules on the same run

const run = [
  { step: "run_tests", testsPass: false },
  { step: "read TicketPage.js", testsPass: false },
  { step: "write TicketPage.js", testsPass: false, codeWritten: true },
  { step: "run_tests", testsPass: true, codeWritten: true },
];

const weak = (s) => s.codeWritten === true;
const strong = (s) => s.testsPass === true;
console.log("weak rule stops after: " + run.find(weak).step + " (step " + (run.findIndex(weak) + 1) + ")");
console.log("strong rule stops after: " + run.find(strong).step + " (step " + (run.findIndex(strong) + 1) + ")");

Expected output:

weak rule stops after: write TicketPage.js (step 3)
strong rule stops after: run_tests (step 4)

One step apart, but only one of them knows whether the code works.

Frontend agent: export status

The agent starts by running the tests and finds that 4 tests fail. It reads the component, then reads web/format.js so it can reuse formatRows. Next, it adds support for all 4 states, runs the tests again, and stops only when all 8 tests pass.

Open the Preview tab to view the ticket page in a browser-style view, with one card for each status. Enable Show the files before the agent ran to compare the original version, where every card displayed "Done" and the row counts had no commas.

Frontend agent with a weak stop rule

This uses the same agent, but the stop rule is "stop when the code is written". The agent writes a shorter solution and reports "Done" at step 5.

Open the Files tab and click Run tests. The "failed" test still fails because there is no Retry button. The queued and running tests pass only by coincidence because "Export " + status happens to produce the expected text.

Try It with a Real Model

Everything above uses the scripted brain, so every run produces the same result. Real coding agents use a language model, and a model may make different decisions on different runs.

You can try that here:

  1. Open Settings in the Agent Lab and select Live model. The first time you use it, a small model (about 0.9 GB) is downloaded and runs inside your browser. It requires a browser that supports WebGPU.
  2. Choose Frontend agent: export status, or Agent, bug A from Lesson 1 if you want an easier example.
  3. Click Run 5 times. The model attempts the same task 5 times, using a small amount of randomness in each run.

You'll see results such as "3 of 5 reached the goal". Open Show trace on a failed run and look for the first incorrect step, just like you learned in Lesson 25. You may find invalid JSON, an incorrect line number, or a run that stopped before all tests passed.

In live mode, the agent gets two additional tools that real coding agents also use:

  • read_file shows line numbers.
  • replace_lines can update a small group of lines without rewriting the entire file.

Small models are much better at making small, targeted edits.

The model used here is much smaller than the models used by real coding agents. However, the lesson remains the same at any scale: the tests and the guardrails determine whether the work is complete, not the model's own claims.

Some scenarios remain scripted even in live mode. The Agent Lab explains why, for example: "This task is too long for a small in-browser model."

Easy Way to Remember: The tests are the picture on the box. Look at them before you start and after every change. Stop only when the result matches them.

Never allow a frontend agent to modify test files just to make the tests pass. Block *.test.js files in the agent's configuration, as you did in Lesson 5.

Common Mistakes

MistakeWrong IdeaCorrect IdeaWhat Goes Wrong
Weak stop rule"Code written means done."Stop only when all tests pass.Broken code is reported as finished.
No test tool"The agent can understand the code by reading it."Give the agent access to run_tests.The agent cannot verify its own work.
Missing states"Done is the main case."Handle all four states and provide a fallback.Queued exports display incorrect text such as "Done: undefined rows".
Duplicating helper logic"It's faster to rewrite it."Reuse formatRows.Different versions of number formatting become inconsistent.
Editing tests"The test is wrong."Block changes to *.test.js.The tests pass, but the original bug remains.

Exercises

Each exercise ends with a value on the last line. Press Check to compare it with the expected answer.

What to Remember

  • The tests are the picture on the box. Check them before making changes and after every change.
  • A component is a function of its state. Handle every known state and provide a fallback for unexpected states.
  • Debug UI issues by finding the first difference between the expected output and the actual output.
  • Integration tests verify that multiple parts work correctly together.
  • Give the agent access to run_tests, and stop only when all tests pass.

Next: Backend and API Agents, the agent that builds the endpoint the panel reads from.