End-to-End Testing
By the Syncfusion DevTutorials team · Last updated
Why green unit tests and a list full of "passes": true can still hide a feature that is never turned on, and how an end-to-end check in the passes rule (Playwright for the React frontend, HTTP tests for the ASP.NET Core backend) proves each feature works in the running app.
Green Tests, Broken App
Your harness now has a feature list, a handoff note and clean exits.
Each session builds one feature and runs the unit tests.
Then it flips "passes" to true.
At the end, every test is green. Every item in feature_list.json says "passes": true.
Now open the app, the way a user would. One feature is not there.
End-to-end check = a test that uses the whole running app, like a user does.
e2e = short for end-to-end. Commands like pnpm e2e run these checks.
For example:
- In the React Taskboard, it opens the page, types in the search box and reads the list.
- In the ASP.NET Core Pricing API, it calls
GET /orders?status=openand reads the JSON.
A Real-World Example
Think about building a new kitchen.
The electrician tests every socket on a bench. Each one works.
Then the kitchen is done. You plug in the kettle. Nothing happens.
One socket was never connected to the fuse box.
Agent work is the same:
- Unit tests are the bench test. Each part works alone.
- An end-to-end check is plugging in the kettle. It proves the parts are connected.
Green tests, broken app
Click the button and open the Sessions & App tab.
- Sessions: all five end with green unit tests and a commit.
- The App log: one feature has code but is never turned on.
- "(the list says it passes)": the feature list is wrong about it.
- The Terminal tab: type
pnpm test, thenpnpm e2e. The first is green. The second names the broken feature. (For the .NET project:dotnet test --filter Category=E2E.)
Simple lesson: a feature can have code, a green test and a "passes" flag, and still not work.
What You Will Learn
In this lesson, you will learn:
- Why green unit tests can hide a broken app.
- What wiring is, and why unit tests cannot see it.
- The test pyramid: unit, integration and end-to-end tests.
- How to write an end-to-end test for the React Taskboard (Playwright).
- How to write an end-to-end test for the Pricing API (real HTTP calls).
- How to make the end-to-end check part of "done".
Lab vs real tools
- The lab's agent is simulated, so it does the same thing every run. That is how your work can be checked.
- A real agent is less predictable. Try each idea on your own project too.
Why Green Tests Can Lie
Watch It Happen
Here is the Taskboard run from the lab, one step at a time.
Step through all five frames.
- Frames 1 and 2: every session is green. Every item says it passes.
- Frame 3: session 2 writes
search.ts. It never adds the import line. - Frame 4:
App.tsximports four features, not five. - Frame 5: the running app. Search does not work.
Unit Tests Check Parts, Not Wiring
Unit test = a test that calls one function on its own.
The search unit test calls the search function and checks the answer. It never starts the app.
Wiring = the line that turns a feature on in the app.
- React:
import "./features/search";inApp.tsx. - .NET:
app.MapFilter();inProgram.cs.
The unit test does not look at that line. So it stays green when the line is missing.
A Different Feature Each Time
The lab has three versions of the same job. In each one, the agent forgets a different line.
Step through all four frames.
- Frames 1 and 2: unit tests only. A different feature is unwired in each version.
- Frames 3 and 4: the end-to-end check is in "done". Every feature works, on both stacks.
Simple lesson: you cannot guess which line will be missing, so check the whole app every time.
The Test Pyramid
Three Kinds of Tests
- Unit test = one function or class on its own. It is fast. You have many.
- Integration test = two or more real parts together, like a service and a database. You have fewer. They are slower.
- End-to-end test = the whole running app, used like a user. You have the fewest. They are the slowest.
Only the top layer sees the wiring.
Many fast unit tests at the bottom, fewer integration tests, and a few end-to-end checks at the top. Only the top layer sees whether a feature is turned on in the running app.
Which Test Catches Which Bug
A bug inside one function shows up in its unit test.
A broken file stops the build. The unit tests go red too.
A missing import or a missing app.Map<Id>(); is different. Every part still passes.
Only a test that starts the app sees it.
Simple lesson: wiring bugs only show up at the top of the pyramid.
Test Like a User
What an End-to-End Test Looks Like
You do not need to read every line. Each test does what a user would do, then checks the screen or the reply.
Playwright = a tool that drives a real browser. It opens the page, types, clicks and reads the screen.
Here is an end-to-end test for Search:
// e2e/search.spec.ts
import { test, expect } from "@playwright/test";
test("typing in search shows only matching tickets", async ({ page }) => {
await page.goto("/");
await page.getByRole("searchbox", { name: "Search tickets" }).fill("login");
const rows = page.getByRole("listitem");
await expect(rows).toHaveCount(1);
await expect(rows.first()).toContainText("Fix login");
});
The test finds the search box by its role and name. A screen reader finds it the same way.
If Search is never imported, there is no search box. The test fails.
In package.json, pnpm e2e runs Playwright:
"scripts": {
"test": "vitest run",
"e2e": "playwright test"
}
For an API, "like a user" means real HTTP calls.
It uses xUnit (the .NET test library). WebApplicationFactory starts the whole app in memory, with the real Program.cs. Then the test calls URLs.
// Pricing.Tests/E2E/FilterE2ETests.cs
public class FilterE2ETests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly HttpClient _client;
public FilterE2ETests(WebApplicationFactory<Program> factory) => _client = factory.CreateClient();
[Fact, Trait("Category", "E2E")]
public async Task Filter_returns_only_open_orders()
{
var orders = await _client.GetFromJsonAsync<Order[]>("/orders?status=open");
Assert.NotEmpty(orders);
Assert.All(orders, o => Assert.Equal("open", o.Status));
}
}
The Trait is a label. dotnet test --filter Category=E2E runs only the tests with that label.
If Program.cs never calls app.MapFilter();, the filter is ignored. The test gets closed orders back, and it fails.
Write failure messages for the agent.
"No searchbox named Search tickets on the page" points straight at the missing wiring.
"Expected 1, got 0" does not.
Simple lesson: for an API, the end-to-end check is a real request to the real app.
Make It Part of Done
One Line in the Rules
A check helps only if the agent runs it before it says "done".
So put the end-to-end command in the line that flips "passes".
For the React Taskboard:
- Flip "passes" to true only after `pnpm test` and `pnpm e2e` both pass.
For the ASP.NET Core Pricing API:
- Flip "passes" to true only after `dotnet test` and `dotnet test --filter Category=E2E` both pass.
Step through all four frames.
- Frame 1: the new rules line, marked in green.
- Frame 2: session 2 again. It adds the import, then runs
pnpm e2e. - Frame 3: the check runs once per feature, inside each session.
- Frame 4: both app logs side by side. Same green unit tests. Only one app works.
Lab Exercise: Add the Check to Done
Change the rule that flips "passes". Name the end-to-end command for your stack.
Make the end-to-end check part of done
Click the button, change the rules file, then press Check my harness.
- The check: 5 sessions on each of 3 versions.
- To pass: every feature must work in the running app.
- And: no item may say
"passes": truewithout proof.
Simple lesson: use the right command for the stack. pnpm e2e does nothing in a .NET project.
The Seam Between Front and Back
Two Green Sides Can Still Break
Seam = the place where the frontend and the backend meet.
Each side can pass its own end-to-end check. They can still break each other.
Say the API renames status to state. The Taskboard now shows empty labels.
Two checks guard the seam:
- A contract test = the API response must match
openapi.json. - A full-stack end-to-end test = Playwright opens the Taskboard while it talks to the real Pricing API.
Where Each Check Runs
- In each session, before "done": the unit tests and the end-to-end check for that one feature. Keep these fast.
- In CI on every change: the full end-to-end suite, the contract tests and the full-stack tests.
The lab runs the per-session checks only. CI and contract tests happen in your own project.
CI (see Tests and Linters) runs the checks on every change.
Never mock the thing you are trying to prove.
To mock means to use a fake copy.
A Playwright test against a fake API cannot catch a renamed field.
A test that swaps out Program.cs cannot catch a missing app.MapFilter();.
Simple lesson: test against the real app, or the test proves nothing about it.
TL;DR
- Unit test = one part on its own. It cannot see wiring.
- Wiring = the line that turns a feature on: an import in
App.tsx, orapp.Map<Id>();inProgram.cs. - End-to-end check = the running app, used like a user.
- Test pyramid = many unit tests, fewer integration tests, a few end-to-end checks.
- Frontend e2e = Playwright, run with
pnpm e2e. - Backend e2e =
WebApplicationFactoryand real HTTP calls, run withdotnet test --filter Category=E2E. - Done = flip
"passes"only after the unit tests and the end-to-end check both pass. - Seam check = a contract test or a full-stack test, in CI.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Unit tests only | "All tests pass, so it works." | Add an end-to-end check to done. | A feature has code but is never turned on. |
| E2E not in the passes rule | "E2E runs in CI anyway." | Flip passes only after e2e passes. | The list says a broken feature passes. |
| Wrong stack command | "pnpm e2e works everywhere." | Use dotnet test --filter Category=E2E for .NET. | The check never runs, so nothing is proved. |
| Mocked backend | "Mocks make it fast." | Use the real app for the check. | Renamed fields and missing routes slip through. |
| No E2E trait in .NET | "dotnet test runs everything." | Add Trait("Category", "E2E") and the filter. | The agent cannot run only the e2e checks. |
| CSS selectors | "Find it by .search-input." | Find it by role and name. | Tests break on every style change. |
Check Your Understanding
Exercises
These exercises practise end-to-end checks from this lesson.
Press Check when you are ready. Use Show Hint if you get stuck.
What to Remember
- Unit tests check parts. They cannot see whether a feature is turned on.
- Wiring is one line: an import in
App.tsx, orapp.Map<Id>();inProgram.cs. - An end-to-end check uses the running app: clicks for the frontend, HTTP calls for the backend.
- Frontend: Playwright with
pnpm e2e. Backend:WebApplicationFactorywithdotnet test --filter Category=E2E. - Flip
"passes"only after the unit tests and the end-to-end check both pass. - Check the seam with a contract test or a full-stack test in CI.
Further Reading
- Effective harnesses for long-running agents, by Anthropic: testing features end to end with browser automation, as a user would.
- Harness engineering: leveraging Codex in an agent-first world, by OpenAI: letting the agent drive the running app to check its own work.
- Harness engineering for coding agent users, on martinfowler.com: sensors at different levels of a test suite.
Next: Evaluator Agents. A second agent checks the work, so the builder does not grade itself.