How to Write Manual Test Cases — Structure, Template and Four Rules from a Project with a Coding Agent

In mid-September 2026 the catalog of manual test cases in the DI² project had 32 active entries. 27 of them existed only because something had been broken before. For just two test cases, what has to be visible on screen was settled before the code existed.

A catalog that came almost entirely from bugs secures what has already gone wrong once. It hardly asks about anything new. The first full run of this catalog still found 19 bugs in the application. Almost all of them surfaced in passing during the run, in places no test case asked about.

This article describes how to write manual test cases when a coding agent writes the requirement, the code and the test case, and there is no second person to read along. A test case then consists of three things that come into being at different times. What has to be visible on screen is in the requirement before the code exists. The sequence of clicks comes from the finished user interface. The run comes last, and only its log is evidence that something was checked.

All rules and numbers come from a single project and from three weeks of practice. They are the rules of this project and not general testing doctrine. The catalog, its rules, the automatic structural check and the logs were written by a coding agent, Claude Code with the models Claude Opus 5, Claude Fable 5 and Claude Fable 5.1, following the maintainer’s instructions. The maintainer gave the instructions, clicked and decided, and does not read the application’s code.

The essentials up front:

  • In the DI² project, the catalog of manual test cases only takes what can be checked on screen alone: for example the moment a message appears. Everything a program can reliably check by itself belongs in the automated tests.
  • A test case puts every step next to what has to be visible at that step: In a table, nobody has to jump back and forth between steps and expectations while testing, and it shows when one of the two is missing. The result of a run never goes into the test case but into a log of its own.
  • Four writing rules are meant to stand in for the missing second person: The expectation comes from the requirement and not from the finished code, it contains no word that only the programmer knows, it says at least once what must not happen, and it names the point in time where that matters. They do not fully replace the second person.
  • Where a test case comes from shapes what it asks about: A test case that came from a bug mainly asks whether that bug comes back. In the DI² project, 27 of 32 test cases had recently come about this way, and that number is recalculated automatically.
  • A written test case is a promise, only a logged run is evidence: In the DI² project a bug was about to be closed, and its test case had never run. It came up through a follow-up question.

Prerequisite: None. The examples come from a web application that manages tables and columns of a database, and that is all you need to know about it. The catalog consists of Markdown files in the repository and a small check script, no test management tool is needed. The text keeps two words apart: test case means a written sequence of steps with expectations that a human executes on screen. Test means an automated check that ends green or red.

Contents

Why a Catalog of Manual Test Cases Sits Next to the Automated Tests

The catalog in the DI² project came about on 27 August 2026 in the middle of a test session. The maintainer had dictated a list of click steps to the agent in the chat in order to check a change. After the session that list would have been gone, and next time someone would have had to reinvent it. So the assignment was: the test cases are to live in the repository, be repeatable and be grouped by area of the application.

What belongs in this catalog is narrowly defined. A test case here only checks what can be checked on screen alone. That includes the moment a message appears, the behavior of a page on a small screen, or a user’s path through several views. Everything a program can reliably check by itself belongs in the automated tests. There it runs along with every change on its own, while a test case only runs when someone clicks. This boundary is a decision of the project, because what is in a database or in a file can be checked by hand as well.

Key point: A catalog of manual test cases does not replace automated tests. In the DI² project it takes what only a human can judge on screen, and it makes sure that this check does not have to be reinvented next time.

How a Test Case Is Structured: Template and Example

Every test case is a Markdown file of its own with the same structure. The following template shows it in full, and anyone wondering how to write manual test cases in their own repository can adopt it as is:

# TEST-NNNN: <title>

- **Suites:** <area>[, <area> …]
- **Run class:** Smoke | Regression
- **Status:** Active | Retired (<reason>)
- **Reference:** <the requirement with its criterion, or the bug the test case came from>
- **Coverage:**
  - <file of the application the test case targets>

**Goal:** <What is this test case meant to show? One sentence.>

**Precondition:** <State from which the test case starts.>

**Steps:**

| # | Step | Expected result |
|---|---|---|
| 1 | <click step> | <what has to be visible at that step> |

**Expected throughout:**
- <statement that belongs to no single step>

**Cleanup:** <How the starting state is restored.>

At the top is a header with five mandatory fields. A suite is a group of test cases for one area of the application. The run class says when the test case is due: a smoke test case runs with every deployment, and if it fails, nothing is deployed. A regression test case runs when one of the files listed under coverage has been changed. Both words stand here only for when a test case is due in the DI² catalog, in common usage they mean more. The reference names the occasion for the test case. The article comes back to this field when it gets to origin.

The header is followed by the goal in one sentence, the precondition and the steps as a table. The precondition describes the state before the first click, and that is above all the state in the database: which table the test case works with, and that no column is selected there yet. Added to that are details such as the role someone is logged in with, or the width of the screen. The following example is a shortened and generalized test case from the catalog. It checks that the application shows a message immediately when a validation rule no longer fits its column:

#StepExpected result
1Open a table and select a numeric column.The column counts as selected.
2Create a validation rule for a value range on this column and save.The rule appears in the list. No message appears, because the rule is valid.
3Switch the column to text and save.The column shows the text type.
4Look at the messages without any further action.One open message names the rule that has become invalid and the reason. It appears immediately, without having to switch views.
5Reload the view.Still exactly one message, no duplicate.

Below the table, the section “Expected throughout” holds what belongs to no single step, such as conditions that apply throughout, or tolerances. At the end comes the cleanup, which restores the starting state.

Two rules belong to this format. A step without an observation of its own gets a dash in the result column, which is more honest than inventing an expectation. And every test case runs on its own: the precondition never refers to the end state of another test case. Otherwise a forgotten setup step looks like a bug in the application, and that is exactly what happened in the first session.

That step and expectation sit side by side came from a complaint. On 28 August the maintainer worked through the catalog for the first time. Steps and expected results were in two separate sections back then, and testing meant jumping back and forth between the two and keeping the numbers in mind. All seven test cases were therefore rebuilt as a table. Since then every row holds the step next to what has to be visible at that step, and whoever works through the test case reads it from top to bottom. In this form it also shows when a step lacks its expectation or an expectation lacks its step.

The test case itself contains no date, no result and no reference to an environment. This article calls that file the declaration. Every run gets its own log, which records who executed which test cases when and in which environment, and what came of it. The reason for the separation is in the catalog itself: whoever reads an old result in the test case unconsciously confirms it on the next run. A log is also never rewritten afterwards. A new run after a correction is a new log, and the old failure stays as a record.

Key point: A test case is a document to work through, not to read. In the table every step sits next to its expectation. That saves jumping between two lists while testing and shows where one of the two is missing. The result belongs in the log, so that the test case is read without bias on the next run.

How to Write Manual Test Cases: Three Rules and a Fourth

In the classic division of labor, the developer does not write the test cases. Whoever wrote the code easily aligns the test case with their own implementation. Paths they did not think of while programming are then missing from the test case as well. In the DI² project this second person does not exist. The same agent writes the requirement, the code and the test case. When the maintainer raised this on 28 August, the objection could be counted in the existing catalog: all seven test cases had been written after the implementation.

The obvious solution would be to create the test case before the implementation. In this format that does not work completely, because precondition, steps and cleanup name concrete clicks and presuppose the finished user interface. But a test case consists of two parts with different sources, and one of them can be settled before the implementation:

Part of the test caseSourceSettled
Goal and expected resultthe criterion of the requirementbefore the implementation, in the text of the requirement
Precondition, steps, cleanupthe finished user interfaceafter the implementation

This became three rules for writing, plus a fourth that had come about a day earlier. How well they work is barely documented after three weeks. Where there is evidence, it is given with the rule.

First rule: The expectation comes from the requirement and not from the finished code. Whoever derives the expected result from the code writes down what the application does, and such a test case can hardly fail anymore. Whoever takes it from the requirement writes down what the application is supposed to do. The missing second person is thereby replaced by a distance in time: the expectation is fixed in writing before the code exists. In the project it is therefore decided while writing a requirement which of its criteria can only be checked on screen, and that criterion is worded so that it later works as an expected result.

For almost three weeks this rule only stood in the catalog, and nobody had applied it yet. It happened for the first time in mid-September 2026. In a requirement dated 13 September, two criteria were marked as ones that can only be checked on screen. Three days later the code was written, and the two test cases for it took their expectations from these two criteria. So what has to be visible on screen was settled before the code existed.

The very first run of these two test cases showed what the rule does not deliver. The maintainer saw the new user interface for the first time and did not agree with how it was operated. The requirement had specified that a certain value could only be changed in a dialog of its own. The maintainer wanted to change it directly in the table row, the way it was already possible elsewhere in the application. The requirement was then changed and the user interface rebuilt. Both test cases were rewritten and executed again from the start.

So the distance in time did what it was supposed to do: the expectation had not been written along the finished code. It did not prevent the expectation itself from having a gap. Whoever writes the requirement and already knows how they will implement it writes it accordingly. So far there is no more than this one case.

Second rule: The expected result contains no word that only the programmer knows. The test case is executed by a human on screen. If the expectation contains the name of a procedure, a view, an internal code or a database column, only someone who knows the code can judge this test case. It is then worded wrongly. Terms that the user interface itself uses are explicitly allowed. This rule can be checked against the text in seconds, without knowing the code. It applies to test cases on screen. Whoever checks the state of a database by hand after a run needs exactly this vocabulary, because there it is the subject matter.

Third rule: Every test case contains at least one statement about what must not happen. A test case written along the implementation describes the path that exists. The enforced negation brings back part of the rest. In the example above there are two: no message for a valid rule and no duplicate after reloading. In a test case on scroll position, exactly one such statement, namely that the jump happens only once, covers a follow-up fix that the main path does not cover. A negation that merely satisfies the rule is worthless, though. It has to rule out something that could plausibly happen with this feature.

Fourth rule: Where the point in time matters, it belongs in the expectation. While wording the example test case, the expectation first read “The message appears”. That raised the question: when exactly? Only this question made the bug visible. The message did appear, but only after the user had left the view and entered it again. Without a written test case this would have passed as “it shows up, doesn’t it”. The bug behind it had two independent causes, and the expectation “immediately, without switching views” has been in the test case ever since.

Key point: Bias does the most damage in the expectation. That is why it is settled before the code, and why it is worded so that a human without knowledge of the code can judge it. The sequence of clicks is not free of bias either, because it only describes the path that exists. The rules do not fully replace a second person.

Where the Test Cases Come From

In the DI² catalog a test case has two possible occasions. It comes from a requirement and then checks a promise. Or it comes from a bug and then secures a known damage. A test case of the first kind asks about something nobody has checked yet. A test case of the second kind mainly asks whether its bug comes back. It finds something new only if that lies on its path.

The catalog of the DI² project has no field of its own for the origin. It follows from the reference field: if that names a bug, the test case came from a bug. The catalog overview states in a line of its own how many of the active test cases came about this way, most recently 27 of 32. This line is not maintained by hand. The catalog’s automatic structural check recalculates it on every change, because a number that follows from other data and is still maintained by hand is wrong after a few weeks.

The share of test cases that came from a bug says something about the composition of the catalog, not about its quality and not about the discipline behind it. The practice was three weeks old at the last count, and it is to be expected that a young catalog comes mostly from bugs: damage that has occurred is the most obvious reason to write a test case at all. So the statement is not that 27 of 32 are too many. It is that a high share has a consequence: the catalog mainly asks about bugs that have already occurred once. The two test cases from the previous section made the share drop for the first time. But two test cases from one requirement are not a trend yet.

The first full run of the catalog from 13 to 15 September fits this and at the same time shows a flip side. All 30 active test cases ran. In those three days 19 bugs in the application came to light, one severe, four medium and 14 minor. What counts as a bug here is every bug file created during the run, and the severity was assigned by the agent when creating the file, on the project’s scale. Only two of the 19 bugs were found by the test cases in the intended way, because a step did not produce the expected result. Both test cases had come from a bug, and both finds were new bugs on their path and not a recurrence. The other 17 surfaced in passing during the run: because something happened during a test case that no step asked about, while setting up the starting state, while cleaning up, while re-testing a bug that had just been fixed, or because the agent read the code before a test case. So the expectations of this catalog found little that was new. The run itself found a lot. How this run went in detail, and why 16 of the 30 test cases themselves were changed along the way, is described in the article The First Run Tests the Test Case.

Key point: The origin of a test case shapes what its expectation asks about. The share of test cases that came from a bug therefore belongs in the catalog overview, and it should be recalculated automatically. A high share is not a flaw. It fits the fact that in the first full run almost all finds lay beside the expectations. Whether one causes the other is something a single run does not show.

Which Fields a Test Case Needs and Which It Does Not

On 28 August the maintainer brought a recommendation from another AI tool into the session. It proposed giving every test case three attributes: a test objective from a table of 15 test classes, from smoke through security to performance, plus a test level and a priority. The catalog’s run class with its two values smoke and regression would have been just two of 15 classes within that test objective. The question was not whether the agent could implement this. It was whether it makes sense to differentiate that far in the catalog already.

The agent checked the recommendation against the existing catalog and not against the textbook. The test level would have been the same for every test case in this catalog, because it only contains what is checked on screen. A field with exactly one possible value says nothing and still costs maintenance. For large parts of the test objective the project had no procedure: performance is not measured (yet), there is no process for accessibility, and security is checked by a skill of its own that goes through the whole project and is described in the article on the control loop. A test case that carries such a class in its header promises something nobody delivers. Finally, the priority would have answered a question that two fields already answer. What has to run is said by the run class and the coverage. A third answer to the same question, maintained by hand, drifts away from the first two at some point. So none of the three attributes made it into the catalog.

The list of test classes was not discarded, though. It has since served as a list of questions for writing a test case. It does not answer how to classify a test case but what the writer has not thought of yet:

  • Main path: Does the feature do what it was built for?
  • Reverse direction: Does the state go back again as well?
  • Blocked path: Is what should be forbidden actually forbidden?
  • Point in time: When does the result become visible, immediately, after reloading or only on entering the view again?
  • Screen size: Does the page behave differently on a small screen?
  • Permission: Do different roles see different things, and are they allowed to do different things?
  • Language: Is it right in German and in English?
  • Boundary value: What happens exactly at the threshold and not just far above it?
  • Failure case: What does the user see when the call in the background fails?

In the DI² catalog every hit on this list becomes a test case of its own and not another item in an existing one. Otherwise it is unclear on a failure which statement was broken, and the test case cannot be repeated selectively. Deliberately not covering a question is fine. Then that decision belongs in the test case as a sentence, so the next reader does not take it for an oversight.

There is a serious objection to this frugality. The classifications from the textbooks are complete because they are meant to fit every project. Whoever cuts them down goes by today’s state and pays when the project grows. The argument for frugality is a matter of effort and not of principle. It holds for a good 30 test cases and one maintainer. With 400 test cases and three teams the calculation can come out differently.

Key point: In the DI² catalog an additional classification only goes into the header of a test case when its value changes when or where the test case runs. That is a matter of effort with a good 30 test cases and not a general rule. A classification that only makes work as a mandatory field can still be useful as a list of questions while writing.

The Run Is the Evidence

On 29 August a bug in the DI² project was about to be closed. The associated test case was written, listed in the overview and linked from the bug. It appeared in none of the eight logs that existed by then. This came up through a follow-up question from the maintainer, because nothing in the process asks for it. The whole incident, and what the first run of this test case then showed, is told in the article The First Run Tests the Test Case.

The gap was not in the order of things, because the test case was written before the closing. What was missing was enforcement. The tool used to close bugs in the project checks whether the files named in the fix were really changed. It does not check whether an associated test case has run. A duty that exists only as a sentence in a description gets skipped unnoticed at some point.

A hard block would still be the wrong answer. A test case on screen needs an environment in which someone can click, and at a local workstation sometimes even the login is missing. A block then produces items that are done in substance and open on paper. Only the version “run, or visibly recorded as backlog” is workable. The real shortcoming was not the missing run. It was the invisible backlog, buried in the running text of a QA report. The article on the control loop also addresses this point: a verdict travels further than the caveat attached to it.

As of today this check does not exist in the project. The duty still stands as a sentence in the description, and it is currently being followed: the most recent bug was closed only after the passed run. A second duty of the same kind is followed less well. Two completed requirements with criteria marked as checkable only on screen did not lead to any test case.

A reported “pass” is at first a promise by the person executing as well, and the article on the first run shows how a database trace turns it into evidence. For automated tests a related lesson holds: a green test at first only confirms the assumptions it was written with. That is the subject of the article The Green Test That Proves Nothing.

Key point: A test case that is only written is a promise. Only the log of a run proves that it was executed and what was visible while doing so. That the application is free of bugs is something even a log does not prove. A duty to execute needs a place where a backlog becomes visible, otherwise it only comes up when someone asks.

Working Prompts as Examples

The sessions are not versioned. The two prompts here are reconstructed from the catalog’s rules and the notes on the sessions and are not quotations. They show what the maintainer demanded, not how the agent implemented it.

On the question of fields, the maintainer brought the outside recommendation along and had it checked instead of having it implemented:

Here is a recommendation for classifying test cases from another tool:
<insert list>

Does it make sense to introduce this for us already? Assess it against our
actual catalog, not against the textbook. Acceptance: for every proposed
attribute, tell me which values it would have for our test cases today, and
which attributes would get the same value for all of them.

The answer named the values in the existing catalog for every attribute, and that made visible that the test level would have the same value everywhere. The list of questions for writing came out of this answer.

On the objection about the missing second person, the maintainer first had the imbalance measured:

Check our existing test cases for two questions and answer with numbers:
1. How many were written before the implementation, how many after?
2. How many exist only because a bug occurred before?

Acceptance: two numbers, each derived from a piece of information that is in
the files. If you cannot derive a number from the files, say so and do not
guess.

The result was “none before the implementation” and “5 of 7”. Only this second number turned an exchange of opinions into a decision.

What Holds in the End

The expectation is in the requirement, the sequence of clicks comes from the finished user interface, and the log only comes into being with the run. Whoever keeps these three things apart writes expectations that are not copied from the code. Such an expectation can fail even when the code does something other than what the requirement promises from the very start. This does not fully replace a second person, and the one case in the DI² project in which the expectation really was settled before the code showed exactly that.

How to write manual test cases under these conditions comes down to five questions before a test case goes into the catalog:

QuestionHow to check it
Does the expectation come from the requirement?The reference names the criterion, and the expectation can be traced back to it.
Do step and expectation sit side by side?Every row of the table has both, or deliberately a dash.
Is the point in time named where it matters?The expectation says when something becomes visible, not just that it does.
Is there a statement about what must not happen?At least one expectation is a negation.
Has the test case run once?It appears in a log.

FAQ

What belongs in a manual test case?

A manual test case needs a header stating when it is due and what it refers to, the goal in one sentence, the precondition, the steps as a table, and the cleanup. In the table every step sits next to its expected result. Date, result and environment of a run do not belong in it, they go into the log. The section on structure shows a template to adopt.

What is the difference between a test case and an automated test?

An automated test runs on its own with every change and ends green or red. A manual test case is a written sequence of steps with expectations that a human executes on screen and logs. A catalog of manual test cases should only hold what a program cannot reliably check by itself, such as the moment a message appears.

Who should write the test cases when there is only one person and a coding agent?

Only the agent is left, and that is the problem, because it also wrote the code. In the DI² project the missing second person is replaced by a distance in time: the expectation is in the requirement as a criterion before the code is written. That is meant to reduce the bias, it cannot remove it. The judgment on screen stays with the human.

How many test cases does a catalog need?

The number says little. Two other figures are more telling: how many test cases came from a requirement and not from a bug, and how many appear in a log at least once. In the DI² project, 27 of 32 test cases had recently come from a bug. In the first full run, the expectations of this catalog found two of 19 bugs, all the others surfaced in passing.

Does a test case need a priority?

Only if it changes a decision. In the DI² catalog the run class says what has to run with every deployment, and the coverage says what is due after a change. A priority would be a third answer to the same question, maintained by hand. A new classification only pays off there when two test cases would get different values and the value changes when or where the test case runs.

Spokes of this article:

Parent article:

Control and rules:

Next door: