Test Case
beta · Family qa-docs · Phase develop · Sizes lean, full · ~2,200 tokens
The specification of one verification: what must be true before it runs, what is done, and what must happen as a result, written so that someone who is not its author can ask the same question tomorrow and get the same answer. A design artifact, not a record of a run: it carries no actual result and no pass or fail.
The short card. Why the document is shaped this way, and the argument behind every rule here, is in
test-case_companion.md. A fully worked instance is
test-case_example.md.
When to use
Section titled “When to use”- A verification must be repeatable by someone who is not you, next month.
- A defect has been fixed and you want it to stay fixed: a regression case is the guard.
- The behavior is high-risk (entitlement, money, data loss) and you want the check reviewed before it runs.
- The work is regulated or audited and every requirement must link to a test.
- A behavior needs checking under a configuration matrix, where “I tried it” is not evidence.
When NOT to use
Section titled “When NOT to use”- Once, throwing away. Checking something during development that nobody will re-run. Do the check.
- Instead of exploring. Cases verify what you already thought of. They do not find the thing you did not think of, and a suite that grows while nobody explores is a quiet failure mode.
- To hit a number. If cases are being written because a count is being watched, the count is the problem.
- As a bug report. A test case is written before, to define correct behavior; a bug report is written after, because something was not.
- As a test plan. The plan scopes and ranks the effort; the case is one unit inside it.
- As a record of a run. No actual-result or pass/fail field belongs in the specification. One case, many runs.
Case, scenario, script, or criterion? (the question people actually have)
Section titled “Case, scenario, script, or criterion? (the question people actually have)”| You want to say | Write |
|---|---|
| What must be true for the business to accept this story | An acceptance criterion (acceptance-criteria) |
| How we verify one specific behavior, repeatably | A test case (this template) |
| What we are testing, at a high level, before designing cases | Most teams call this a test scenario |
| The ordered sequence in which a set of cases is run | Most teams call this a test script or procedure |
A warning about those last two rows. The ISTQB glossary does not draw the line most practitioners draw: it gives test procedure and test script identical definitions, and defines test scenario as a synonym for test script rather than as a high-level description. Your team’s usage is probably the folk taxonomy, and that is fine. What is not fine is assuming everyone shares it. Write down which you mean; the vocabulary will not settle it for you (companion section 8).
And the one that matters most. Acceptance criteria are agreed with the business before the work; test cases are designed by whoever tests, and they continue past the agreement into negative paths, boundaries, regression and non-functional checks that nobody signed off on. Some practitioners argue the two should be merged, and that is a real position with real practitioners behind it. Whichever you choose, choose it deliberately: the failure mode is assuming the criteria already cover what test design would have found (companion section 6).
Pick a variant
Section titled “Pick a variant”Lean (four sections) is the default and should stay the default: Identification and Traceability, Preconditions and Test Data, Steps and Expected Results, Postconditions and Teardown. A test case is written hundreds of times over a project, so every field costs hundreds of times.
Full (eight sections) adds Design Rationale, Environment and Configuration, Automation Status, and Version and Approval. Reach for it when:
- the work is regulated or audited and the case is evidence;
- cases are reviewed as artifacts, not just executed;
- the case is configuration-sensitive and a result is meaningless without the configuration;
- the suite is large enough that knowing why a case exists is what lets you retire it.
Note that the master catalog marks this type single-size. This bundle ships two anyway, for the reasons in companion section 4; the catalog’s size calls are hypotheses, and this is one tested against evidence.
Quality rubric (self-grade)
Section titled “Quality rubric (self-grade)”Score each 0, 1 or 2. Under 11 out of 16 and the case will not survive its first re-run by someone else.
| # | Criterion | 0 | 1 | 2 |
|---|---|---|---|---|
| 1 | One objective | Verifies several unrelated things | Mostly one thing | Exactly one objective, stated in the title |
| 2 | Traceable | Traces to nothing | Named loosely | Traces to a specific ID: criterion, requirement, risk or defect |
| 3 | Preconditions real | None stated | Vague (“user is logged in”) | Specific enough that a stranger can construct the state |
| 4 | Expected results pre-written | Blank or written after the run | Present but vague | Observable, specific, and written before execution |
| 5 | No execution state | Has actual-result and pass/fail fields | One creeping in | Pure specification; runs recorded elsewhere |
| 6 | Independent | Requires another case to run first | Implicit ordering | Runs in any order; postconditions stated |
| 7 | Right level of detail | Every click, or “check it works” | Uneven | A competent stranger could run it; no more than that |
| 8 | Title survives a redesign | Describes the click path | Mixed | Describes the behavior and the condition |
Named anti-patterns (the usual wrecks)
Section titled “Named anti-patterns (the usual wrecks)”- The single-use case. Actual-result and status fields baked into the specification, so the reusable artifact and one run’s record are the same file. Fix: keep run state in the tool.
- Retrofitted expectations. Expected results written after execution. They cannot fail. Fix: write them first, always.
- The three-in-one. One case verifying three things, so a failure tells you nothing. Fix: split it.
- Order dependence. Passes in the suite, fails alone, because a predecessor left state behind. Fix: real preconditions, real teardown.
- Click-by-click. Every UI interaction enumerated, so the case breaks on a redesign that broke nothing. Fix: specify behavior and data, not the path.
- “Log in and check it works.” Not repeatable by anyone but the author. Fix: preconditions and an observable expected result.
- The orphan. Traces to nothing, so nobody can tell whether it still matters, and it is never deleted. Fix: every case names what it covers.
- Counting cases. “We have 5,000 tests” says nothing, and once the count is a target it gets gamed with shallow cases. Fix: measure risk coverage, never volume.
Pairing with a skill
Section titled “Pairing with a skill”pairs_with: [deliver-edge-cases]. There is no testing or QA skill in the pm-skills library (finding
EC-4 in the repository’s STATE.md). deliver-edge-cases is the one honest pairing, and for this member it
is a particularly direct one: the edge-case catalog it produces is exactly the negative, boundary and
error-state territory that acceptance criteria do not cover, which is where a large share of test cases should
come from. See companion sections 6 and 8.
The artifacts
Section titled “The artifacts”test-case_template-lean.md · ~2,200 tokens
---title: "{{case_title}}"case_id: "{{case_id}}"verifies: "{{what_this_traces_to}}"priority: "{{priority}}"case_status: "{{case_status}}"last_updated: "{{date}}"doc_type: test-casesize: leansource_template: test-casesource_template_version: 0.1.0---
<!--LEAN TEST CASE. The smallest specification that is still a real test case: what it verifies and what ittraces to, what must be true before it runs, the steps paired with what should happen, and the state itleaves behind. Use it for everyday cases written and run by one team. To grow it into a reviewable,auditable case (see test-case_template-full.md), ADD sections; never rename or reorder the ones below,because the full variant is a strict superset of this one.
A TEST CASE IS A DESIGN ARTIFACT, NOT A RECORD OF A RUN. There is deliberately no "actual result" and nopass/fail field below. Those belong to an execution record: one case is run many times, and a specificationthat carries the outcome of one run can only be used once. `case_status` above is the lifecycle of thisSPECIFICATION (draft, active, deprecated), not the outcome of a test. See test-case_companion.md sections 3and 7.
ONE CASE, ONE OBJECTIVE. A case that verifies three things tells you almost nothing when it fails. Oneobjective does not mean one assertion; a single outcome may need several checks to confirm it.
WHAT A TEST CASE IS, AND IS NOTIt is the specification of ONE verification. It is NOT a test plan (that scopes and ranks a whole effort),NOT a bug report (that is written after something failed), NOT a test run (that is an execution event), andNOT necessarily the same thing your team means by "test scenario" or "test script" - those words are usedinconsistently across the industry and even the certification glossary. See test-case_companion.md section 8.
HOW TO FILL THIS IN1. Read the comment under each heading: WHAT it wants, WHY it matters (with a pointer into test-case_companion.md), guiding questions to ASK, a GOOD and a WEAK example, and the TRAP to avoid. For the steps table, PRIORITY explains the ordering rule and ROW HINT says what a good row contains.2. Replace each {{placeholder}} with your content. Write the expected results BEFORE you run anything.3. If a section does not apply, write "N/A" and one line of why, rather than deleting it.4. Before you share it: self-grade against test-case_guide.md, then DELETE every HTML comment. They are guidance, not content.-->
# {{case_id}}: {{case_title}}
## Identification and Traceability
<!-- WHAT What this case verifies in one sentence, and what it traces to: an acceptance criterion, a requirement, a risk in the test plan, or a defect it now guards against. Plus its priority. WHY A case that traces to nothing is a case nobody can ever decide to delete, which is how suites turn into landfill. Traceability is also what an audit is actually auditing: linking every requirement to its test. Deep dive: test-case_companion.md section 3 (Identification and Traceability). ASK What single thing does this verify? What does it trace to, by ID? Why does this case exist - which risk or criterion would go uncovered without it? How important is it relative to the others? GOOD "Verifies that a restricted viewer opening a shared view receives only rows within their entitlement. Traces to: test plan risk R-05 (High). No acceptance criterion covers this case; it comes from test design, not from the agreed criteria. Priority: P1." WEAK "Tests sharing. Priority: high." (verifies nothing specific, traces to nothing, and gives no reason to keep or delete it next year) TRAP A title that describes the click path ("open Views menu, click Share") rather than the behavior. A behavior title survives a redesign; a path title does not. -->
{{identification_and_traceability}}
## Preconditions and Test Data
<!-- WHAT The system state, accounts, permissions and configuration that must exist before step 1, and the specific data this case uses. WHY This is what decides whether anyone other than the author can run the case and get the same answer. Practitioners rank understandability and repeatability at the top of what makes a case good, and a missing precondition is the usual cause of a case that "only works for Priya". Deep dive: test-case_companion.md section 3 (Preconditions and Test Data). ASK What must already be true: which accounts, which permissions, which feature flags, which data? What values does this case feed in? Where does that data come from, and who creates it? What must NOT be true (a state that would invalidate the run)? GOOD "Preconditions: saved_views flag on; dashboard DB-7 exists with a Region filter; user R (restricted viewer) has dashboard access but no entitlement to region EMEA; user O (owner) has a view on DB-7 filtered to region=EMEA, scope=shared. Data: view SV-31; users O and R from the standard persona set." WEAK "A shared view exists and a user opens it." (which user, with what entitlement, on which dashboard, filtered to what? Two testers will construct two different tests) TRAP Folding preconditions into step 1. A precondition is state that must already hold; a step is something the test does. Merging them makes the case unrepeatable and hides its dependencies. -->
{{preconditions_and_test_data}}
## Steps and Expected Results
<!-- WHAT The actions in order, each paired with what should happen. One row per step. WHY The pairing is the case. A step with no expected result is navigation, not verification. And the expected result has to be written BEFORE the run: filled in afterwards it is a description of what happened, which can never fail. Deep dive: test-case_companion.md section 3 (Steps and Expected Results). ASK What is the smallest sequence that exercises this one objective? What should happen at each step, observably? Which step is the actual verification, as opposed to setup? How would a tester know it failed? PRIORITY Keep the sequence minimal: every step that is not needed to reach or observe the behavior is maintenance you pay for forever. Specify behavior and data, not the click path. Do NOT add an "actual result" or "pass/fail" column; those belong to the run record. ROW HINT A good row has an action a competent stranger could perform without guessing, and an expected result that is observable and specific enough to be wrong. A weak row says "verify it works". GOOD | 3 | As user R, open dashboard DB-7 and select saved view SV-31 | The view loads. Rows are limited to regions R is entitled to; no EMEA row appears in the table or in any aggregate total | WEAK | 3 | Open the shared view | It works correctly | TRAP Enumerating every click. Over-specified cases break on cosmetic UI changes that broke nothing, and reviewers stop reading them. Under-specified cases cannot be repeated. Aim for what a competent tester unfamiliar with the feature needs, and no more. -->
| # | Action | Expected result ||---|---|---|| {{step_number}} | {{action}} | {{expected_result}} |
## Postconditions and Teardown
<!-- WHAT The state the system is left in, and anything that must be cleaned up before the next case runs. WHY A suite is a set of cases where one case's postcondition often becomes the next one's precondition. A case that quietly leaves state behind becomes a hidden dependency, and that is the usual reason a test passes alone and fails in a suite. Deep dive: test-case_companion.md section 3 (Postconditions and Teardown). ASK What has changed in the system after this case runs? Does anything need deleting, resetting or re-seeding? Can this case run twice in a row without cleanup? Does anything it leaves behind affect another case? GOOD "No data is created or modified; the case is read-only. User R's session is ended to avoid carrying an authenticated session into the next case." WEAK (section deleted, or "N/A" with no explanation) TRAP Skipping this because the case "does not change anything". Say that explicitly instead - "no state change; read-only" is information, and it is what tells the next person they can run this case in any order. -->
{{postconditions_and_teardown}}test-case_template-full.md · ~3,750 tokens
---title: "{{case_title}}"case_id: "{{case_id}}"verifies: "{{what_this_traces_to}}"priority: "{{priority}}"case_status: "{{case_status}}"case_version: "{{case_version}}"last_updated: "{{date}}"doc_type: test-casesize: fullsource_template: test-casesource_template_version: 0.1.0---
<!--FULL TEST CASE. The reviewable, auditable case: everything the lean variant carries, plus why this case wasdesigned the way it was, the environment it is valid for, its automation link, and its version and approvalrecord. Use it when the work is regulated or audited, when cases are reviewed as artifacts rather than justexecuted, when a configuration matrix is in play, or when the suite is large enough that knowing WHY a caseexists is what lets you retire it later.
THIS VARIANT IS A STRICT SUPERSET OF THE LEAN ONE. The four lean sections appear here in the same order, withthe same headings and the same placeholders, and four sections are added. Growing a lean case into this oneis additive.
BE SPARING WITH THIS VARIANT. A test case is written hundreds of times over a project, so every extra fieldis paid for hundreds of times. Reach for full where the extra fields are actually consumed by a reviewer, anauditor or an automation pipeline - not by default. See test-case_companion.md section 4.
A TEST CASE IS A DESIGN ARTIFACT, NOT A RECORD OF A RUN. There is deliberately no "actual result" and nopass/fail field below. Those belong to an execution record: one case is run many times, and a specificationthat carries the outcome of one run can only be used once. `case_status` above is the lifecycle of thisSPECIFICATION (draft, active, deprecated), not the outcome of a test. See test-case_companion.md sections 3and 7.
ONE CASE, ONE OBJECTIVE. A case that verifies three things tells you almost nothing when it fails. Oneobjective does not mean one assertion; a single outcome may need several checks to confirm it.
WHAT A TEST CASE IS, AND IS NOTIt is the specification of ONE verification. It is NOT a test plan (that scopes and ranks a whole effort),NOT a bug report (that is written after something failed), NOT a test run (that is an execution event), andNOT necessarily the same thing your team means by "test scenario" or "test script" - those words are usedinconsistently across the industry and even the certification glossary. See test-case_companion.md section 8.
HOW TO FILL THIS IN1. Read the comment under each heading: WHAT it wants, WHY it matters (with a pointer into test-case_companion.md), guiding questions to ASK, a GOOD and a WEAK example, and the TRAP to avoid. For tables, PRIORITY explains the ordering rule and ROW HINT says what a good row contains.2. Replace each {{placeholder}} with your content. Write the expected results BEFORE you run anything.3. If a section does not apply, write "N/A" and one line of why, rather than deleting it.4. Before you submit it for review: self-grade against test-case_guide.md, then DELETE every HTML comment. They are guidance, not content.-->
# {{case_id}}: {{case_title}}
## Identification and Traceability
<!-- WHAT What this case verifies in one sentence, and what it traces to: an acceptance criterion, a requirement, a risk in the test plan, or a defect it now guards against. Plus its priority. WHY A case that traces to nothing is a case nobody can ever decide to delete, which is how suites turn into landfill. In an audited context this section is the spine of the traceability matrix: linking every requirement to its test is the thing being checked. Deep dive: test-case_companion.md section 3 (Identification and Traceability). ASK What single thing does this verify? What does it trace to, by ID? Why does this case exist - which risk or criterion would go uncovered without it? How important is it relative to the others? GOOD "Verifies that a restricted viewer opening a shared view receives only rows within their entitlement. Traces to: test plan risk R-05 (High). No acceptance criterion covers this case; it comes from test design, not from the agreed criteria. Priority: P1." WEAK "Tests sharing. Priority: high." (verifies nothing specific, traces to nothing, and gives no reason to keep or delete it next year) TRAP A title that describes the click path ("open Views menu, click Share") rather than the behavior. A behavior title survives a redesign; a path title does not. -->
{{identification_and_traceability}}
## Preconditions and Test Data
<!-- WHAT The system state, accounts, permissions and configuration that must exist before step 1, and the specific data this case uses. WHY This is what decides whether anyone other than the author can run the case and get the same answer. Practitioners rank understandability and repeatability at the top of what makes a case good, and a missing precondition is the usual cause of a case that "only works for Priya". Deep dive: test-case_companion.md section 3 (Preconditions and Test Data). ASK What must already be true: which accounts, which permissions, which feature flags, which data? What values does this case feed in? Where does that data come from, and who creates it? What must NOT be true (a state that would invalidate the run)? GOOD "Preconditions: saved_views flag on; dashboard DB-7 exists with a Region filter; user R (restricted viewer) has dashboard access but no entitlement to region EMEA; user O (owner) has a view on DB-7 filtered to region=EMEA, scope=shared. Data: view SV-31; users O and R from the standard persona set." WEAK "A shared view exists and a user opens it." (which user, with what entitlement, on which dashboard, filtered to what? Two testers will construct two different tests) TRAP Folding preconditions into step 1. A precondition is state that must already hold; a step is something the test does. Merging them makes the case unrepeatable and hides its dependencies. -->
{{preconditions_and_test_data}}
## Steps and Expected Results
<!-- WHAT The actions in order, each paired with what should happen. One row per step. WHY The pairing is the case. A step with no expected result is navigation, not verification. And the expected result has to be written BEFORE the run: filled in afterwards it is a description of what happened, which can never fail. Deep dive: test-case_companion.md section 3 (Steps and Expected Results). ASK What is the smallest sequence that exercises this one objective? What should happen at each step, observably? Which step is the actual verification, as opposed to setup? How would a tester know it failed? PRIORITY Keep the sequence minimal: every step that is not needed to reach or observe the behavior is maintenance you pay for forever. Specify behavior and data, not the click path. Do NOT add an "actual result" or "pass/fail" column; those belong to the run record. ROW HINT A good row has an action a competent stranger could perform without guessing, and an expected result that is observable and specific enough to be wrong. A weak row says "verify it works". GOOD | 3 | As user R, open dashboard DB-7 and select saved view SV-31 | The view loads. Rows are limited to regions R is entitled to; no EMEA row appears in the table or in any aggregate total | WEAK | 3 | Open the shared view | It works correctly | TRAP Enumerating every click. Over-specified cases break on cosmetic UI changes that broke nothing, and reviewers stop reading them. Under-specified cases cannot be repeated. Aim for what a competent tester unfamiliar with the feature needs, and no more. -->
| # | Action | Expected result ||---|---|---|| {{step_number}} | {{action}} | {{expected_result}} |
## Postconditions and Teardown
<!-- WHAT The state the system is left in, and anything that must be cleaned up before the next case runs. WHY A suite is a set of cases where one case's postcondition often becomes the next one's precondition. A case that quietly leaves state behind becomes a hidden dependency, and that is the usual reason a test passes alone and fails in a suite. Deep dive: test-case_companion.md section 3 (Postconditions and Teardown). ASK What has changed in the system after this case runs? Does anything need deleting, resetting or re-seeding? Can this case run twice in a row without cleanup? Does anything it leaves behind affect another case? GOOD "No data is created or modified; the case is read-only. User R's session is ended to avoid carrying an authenticated session into the next case." WEAK (section deleted, or "N/A" with no explanation) TRAP Skipping this because the case "does not change anything". Say that explicitly instead - "no state change; read-only" is information, and it is what tells the next person they can run this case in any order. -->
{{postconditions_and_teardown}}
## Design Rationale
<!-- WHAT Which test design technique produced this case, and why these values rather than others. WHY This is what makes a case reviewable rather than merely runnable, and it is what lets someone later decide the case is redundant. Naming the technique also exposes gaps: if every case in a suite says "happy path", nobody has done boundary or combination analysis. Deep dive: test-case_companion.md section 3 (Design Rationale). ASK Which technique derived this: equivalence partitioning, boundary value analysis, a decision table, state transition, combinatorial? Why these specific values? Which partition or boundary does this case represent, and which sibling cases cover the others? What is deliberately NOT covered here? GOOD "Equivalence partitioning on entitlement: the partitions are entitled, partially entitled and not entitled. This case covers partially entitled, which is the partition where a filter-level leak can occur; TC-046 and TC-048 cover the other two. Boundary values are not meaningful for a set-membership check, so BVA is not applied." WEAK "Negative test." (names no technique, no partition, and gives no way to tell whether the set of cases is complete) TRAP Writing the rationale after the fact to satisfy the heading. If you cannot name why these values, the case may be arbitrary - which is worth discovering now rather than in a review. -->
{{design_rationale}}
## Environment and Configuration
<!-- WHAT The environment, build, configuration and device or browser matrix this case is valid for. WHY A case that passed somewhere is only evidence about that somewhere. Where a case is configuration-sensitive, an unrecorded environment makes the result unreproducible and, in an audit, unusable. Deep dive: test-case_companion.md section 3 (Environment and Configuration). ASK Which environment and build is this case valid against? Which configurations must it be run under, and which are out of scope? Is the case sensitive to data volume, locale, timezone or device? What would invalidate a past result? GOOD "Staging, build 2.3.1 or later, saved_views flag on. Entitlement checks are evaluated server-side, so this case is browser-independent and is run once on Chrome; the accessibility cases cover the browser matrix separately." WEAK "Any environment." (either untrue or an admission that nobody has thought about it) TRAP Listing a matrix nobody will run. A configuration named here is a commitment; if it will not be covered, say which are out of scope and why. -->
{{environment_and_configuration}}
## Automation Status
<!-- WHAT Whether this case is automated, what it is linked to, and what the automation does NOT cover. WHY Automating a case changes its role rather than retiring it: the case stays the specification and the code becomes an implementation of it. Recording the link is also how you avoid the common surprise that manual parameters do not carry into an automated run. Deep dive: test-case_companion.md section 3 (Automation Status) and section 8. ASK Is this automated, planned for automation, or deliberately manual? Where does the automated test live, by path or ID? What does the automated version not check that the manual one did? Who owns the automation? GOOD "Automated. Linked to `tests/entitlement/shared_view_restricted_viewer_spec.rb`. The automated version asserts on the API response only; the check that no EMEA value appears in a rendered aggregate total remains manual and is run once per release." WEAK "Yes." (no link, no owner, and no statement of what the automation gave up) TRAP Deleting the manual case once it is automated. The case is the specification of what should be true; the automation is one way of checking it, and it usually checks less. -->
{{automation_status}}
## Version and Approval
<!-- WHAT The version of this case, who reviewed or approved it, and when. WHY This exists for regulated and audited work, where the case is evidence rather than a convenience. The bar does not depend on who or what wrote the case: a generated case carries the same traceability and version-control obligation as a hand-written one. Everywhere else, delete this section and let version control do its job. Deep dive: test-case_companion.md section 3 (Version and Approval) and section 9. ASK What version is this, and what changed from the last one? Who reviewed it, in what role, and on what date? What kind of change requires re-review rather than an edit? Where does the history live? PRIORITY Reviewers are named individuals with roles, never a team. State plainly which changes need re-review; "material change" with no definition is not a rule. ROW HINT A good row names a person, a role, what they reviewed, and a date. A weak row is a role with no name and no date. GOOD | Sam Okafor | Security | Entitlement assertions and the partition choice | 2026-07-08 | WEAK | QA | | Reviewed | | TRAP Collecting approvals on a case nobody executed. A signature on an unrun case attests to the document, not to the software. -->
| Reviewer | Role | What they reviewed | Date ||---|---|---|---|| {{reviewer}} | {{reviewer_role}} | {{review_scope}} | {{review_date}} |
{{version_and_change_rule}}---title: "Restricted viewer opening a shared view receives only entitled rows"case_id: "TC-047"verifies: "Test plan risk R-05 (shared-view entitlement). No acceptance criterion covers this case."priority: "P1"case_status: "active"case_version: "1.1"last_updated: "2026-07-08"related: - "../test-plan/test-plan_example.md (Saved Views test plan; this case is in Risk-Ranked Approach row 1)" - "../sdd/sdd_example.md (Saved Views design; the entitlement re-check happens on recipient read)" - "../acceptance-criteria/acceptance-criteria_example.md (the agreed criteria, which do not cover this case)"doc_type: test-casesize: fullsource_template: test-casesource_template_version: 0.1.0---
<!--Worked example for the test-case bundle: a full-variant case for one verification, continuing the AcmeAnalytics "Saved Views" thread used across the delivery-docs and qa-docs families. Figures marked"illustrative" are made up for the example.
WHY THIS CASE WAS CHOSEN, and it is not because it is typical. It is the clearest available demonstration ofthe companion's section 6 argument: this case traces to a High-tier risk in the test plan and to NO acceptancecriterion. Nobody agreed to it, because nobody thought of it. That is what "test design continues past theagreed criteria" means in practice.
WHY THE FULL VARIANT: the case is security-relevant and was reviewed before it ran, it is partly automatedwith a stated gap, and it sits behind a formal gate in the test plan. A routine case on this feature would usethe lean variant.-->
# TC-047: Restricted viewer opening a shared view receives only entitled rows
## Identification and Traceability
Verifies that when a user opens a saved view shared with them, they receive only the rows their ownentitlements permit, including inside aggregate values, and never the rows the view's owner can see.
**Traces to:** risk **R-05** in the [Saved Views test plan](../test-plan/test-plan_example.md), the highesttier in its Risk-Ranked Approach, which inherits from the program risk register: a shared view embeds filtervalues, so a recipient without entitlement could see a segment and the personal data in it.
**Traces to no acceptance criterion, and that is deliberate.** The[acceptance criteria for the default-view story](../acceptance-criteria/acceptance-criteria_example.md) coverwhat the business agreed: a user can set, load and share views, and one user's default does not changeanother's. Nobody wrote a criterion about what a *restricted* viewer sees inside a shared view's aggregates,because the question only arises once you look at how sharing is implemented. This case comes from testdesign, not from the agreed criteria.
**Priority: P1.** A failure here is a reportable data-exposure incident, is not user-recoverable, and is asuspension event under the test plan rather than a defect to triage.
## Preconditions and Test Data
**System state**- Build 2.3.1 or later on staging, `saved_views` flag enabled.- Dashboard **DB-7** exists and carries a `region` filter field and at least one aggregate metric (total revenue) computed across regions.- User **O** (owner persona) has entitlement to all regions and owns saved view **SV-31** on DB-7, with `config.filters = [{ field: "region", op: "in", value: ["EMEA"] }]` and `scope = shared`.- User **R** (restricted viewer persona) has read access to dashboard DB-7 but **no entitlement to region EMEA**, and does have entitlement to region AMER.- DB-7 holds at least one row in EMEA and one in AMER (illustrative: 40 EMEA rows, 60 AMER rows), so that an unfiltered aggregate and an entitled aggregate differ by a detectable amount.
**Test data**- Personas O and R from the standard permission set created by Platform before test entry.- Saved view SV-31 as configured above.- Region reference data seeded with EMEA and AMER only, so that "everything R may see" is unambiguous.
**Must not be true:** R must not hold a wildcard or admin entitlement inherited from another group. Verifythis in step 1 rather than assuming it, because an inherited grant makes the whole case pass vacuously.
## Steps and Expected Results
| # | Action | Expected result ||---|---|---|| 1 | As an administrator, confirm user R's effective entitlements on DB-7 | R has AMER and does not have EMEA. If R holds EMEA by inheritance, stop: the preconditions are not met and the case cannot verify anything || 2 | As user O, confirm SV-31 is shared and its filter is `region in [EMEA]` | The view is listed as shared on DB-7 and its filter is unchanged || 3 | As user R, request `GET /dashboards/DB-7/views/SV-31` | HTTP 200. The response contains no EMEA rows. The response is not an error: the design re-checks the recipient's access on read so that a shared view can never widen what a recipient may see, which means a partly entitled reader gets their entitled data rather than a refusal. (The design's `stale_fields` degradation path is a different case, for deleted filter fields, and is covered by TC-052) || 4 | Inspect the aggregate metric in the same response | The total revenue is computed over R's entitled rows only (illustrative: the AMER-only total, not the EMEA total and not the combined total). A correct row filter with an uncorrected aggregate is the specific leak this case exists to catch || 5 | As user R, open dashboard DB-7 in the browser and select saved view SV-31 | The view loads and displays the same entitled subset. No EMEA value appears in any row, chart label, tooltip or total || 6 | As user O, open the same view | O sees the full EMEA-filtered result. The two users' results differ, confirming the difference in step 3 was entitlement and not an empty dataset |
## Postconditions and Teardown
No data is created, modified or deleted; the case is read-only against DB-7 and SV-31. Both sessions areended at the end of the run so that an authenticated session is not carried into the next case. SV-31 is leftshared, which is the precondition TC-048 expects.
## Design Rationale
**Equivalence partitioning on entitlement.** The input domain here is the recipient's entitlement relative tothe shared view's filter, and it has three partitions: **fully entitled** (recipient may see everything thefilter selects), **partially entitled** (recipient may see some of it), and **not entitled** (recipient maysee none of it).
This case covers **partially entitled**, which is the partition where a filter-level leak can actually occur:the other two tend to be handled correctly by construction, because an all-or-nothing check is the obviousimplementation and the one a developer writes first. TC-046 covers fully entitled and TC-048 covers notentitled; the three together exhaust the partition set.
Boundary value analysis is not applied, because entitlement here is set membership rather than an orderedrange, and there is no boundary to sit either side of.
**Step 4 exists because of a design fact plus an inference from it, not by symmetry**, and the two are worthkeeping apart. The **fact**, from the design document: the service re-checks the recipient's access on read,so a shared view can never widen what a recipient may see. The **inference**, which is test design rather thananything the design document states: the most likely implementation of that re-check is a row filter, and animplementation that computes aggregates before applying that filter would leak EMEA magnitudes to R even whenevery returned row is correct. Step 4 exists to catch that implementation risk. It is the case's realobjective; steps 1, 2 and 6 are setup and control.
## Environment and Configuration
Staging, build 2.3.1 or later, `saved_views` flag enabled. Entitlement is evaluated server-side, so theAPI portion of this case (steps 3 and 4) is browser-independent and is run once. Step 5 is run on Chrome only;the browser matrix is covered by the accessibility cases rather than repeated here, because nothing aboutentitlement is client-dependent.
Not valid against the pre-prod replica, whose entitlement data is anonymized in a way that collapses theregion grants (illustrative), which would make R appear fully entitled and the case pass vacuously.
## Automation Status
**Partially automated.** Steps 1 to 4 and step 6 are automated at`tests/entitlement/shared_view_restricted_viewer_spec.rb` (illustrative path) and run on every pipelineexecution for the release branch. Owner: Marcus Bell.
**Step 5 remains manual and is run once per release.** The automated version asserts on the API response; itdoes not verify that no EMEA value appears in a rendered tooltip or chart label, which is a genuine gap ratherthan an oversight, and is why the manual step is retained rather than deleted. Recording the gap here is thepoint: the automation checks less than the case specifies, and a reader who assumed otherwise would believethis behavior is fully guarded.
## Version and Approval
| Reviewer | Role | What they reviewed | Date ||---|---|---|---|| Sam Okafor | Security | The entitlement assertions and the partition choice | 2026-07-08 || Anjali Rao | QA Lead, Reporting | Steps, preconditions and the automation gap | 2026-07-08 |
**Version 1.1**, changed from 1.0 by adding step 4 (the aggregate check) and its design rationale, after SamOkafor's review observed that row-level filtering alone would not catch a leak through a pre-filter aggregate.Version 1.0 would have passed against a defective implementation, which is the strongest argument in thisbundle for reviewing cases before running them.
**Change rule.** Wording, path and data-value edits are made in place by the case owner. Any change to theassertions in steps 3, 4 or 5, or to the partition claim in Design Rationale, requires re-review by Securitybefore the case is run again, because those are the assertions the release's entitlement evidence rests on.Provenance
Section titled “Provenance”The reasoning, the history and every source, in the repository:
- Companion - the long-form argument: why these sections, where the sources disagree, and what the bundle refuses to claim
- History - what changed in this bundle, and when
- Research log - every source consulted, with what each one actually supports
- Catalog metadata - the machine-readable record this page is generated from
Catalog record: 8 sections across 1 format(s), methodology methodology-agnostic, typically owned by QA Engineer.