Why Your Acceptance Criteria Are Too Shallow — And How to Fix Them in 5 Minutes
By A. Perico
11 min read
Given-When-Then is useful, but not enough. Most bugs hide in alternatives, exceptions, permissions, stale data, and failure paths.
Turn Systems Engineering context into delivery alignment.
Ellygent helps teams define system intent, structure engineering context, maintain traceability, and make approved context usable across implementation and AI-assisted workflows.
Why Your Acceptance Criteria Are Too Shallow — And How to Fix Them in 5 Minutes
Most acceptance criteria are not wrong.
They are just too shallow.
You see something like this:
Scenario: Successful login
Given a registered user
When they enter correct credentials
Then they are logged in
...
This is not bad.
It is clear. It is testable. It describes the happy path.
But it is not enough.
Because production bugs usually do not live in the happy path.
They live in the alternatives, exceptions, permissions, expired states, stale data, duplicate requests, integration failures, and edge cases nobody wrote down.
That is where shallow acceptance criteria become expensive.
The team thinks the story is ready. The developer implements the obvious case. The tester validates the main scenario. The feature ships.
Then reality appears.
A disabled account tries to log in. A user enters the wrong password too many times. The session expires halfway through the flow. The billing API is down. A user double-clicks the submit button. Two admins edit the same record at the same time.
And suddenly the acceptance criteria were not acceptance criteria.
They were only a happy-path reminder.
Given-When-Then is not the problem
I like Given-When-Then.
It is simple. It forces behavior into a structure. It helps teams discuss expected outcomes.
The problem is not the format.
The problem is that teams use the format only for the easiest scenario.
They write:
Given everything is valid
When the user does the expected thing
Then the system does the obvious result
That is useful, but it does not prove the feature is robust.
It only proves that the feature works when nothing interesting happens.
Real software is not like that.
Real software has permissions. Real software has retries. Real software has timeouts. Real software has stale data. Real software has partial failures. Real software has users who do things twice. Real software has integrations that return 503 at the worst possible moment.
So the goal is not to abandon Given-When-Then.
The goal is to go deeper.
The three depths of acceptance criteria
I think about acceptance criteria in three levels.
DepthCoverageExample Level 1: ShallowHappy path onlyUser logs in with correct credentials Level 2: AdequateHappy path plus common alternativesUser logs in; user enters wrong password and sees error Level 3: DeepHappy path, alternatives, exceptions, edge cases, and constraintsPassword attempt limit exceeded; account disabled; expired session; concurrent login behavior; audit event recordedMost teams stop at Level 1 or Level 2.
Level 1 is usually enough to start a conversation.
Level 2 is often enough for simple UI behavior.
But Level 3 is where robust software lives.
This does not mean every story needs twenty scenarios. That would be too much.
It means every important workflow should include the scenarios most likely to break the feature, create rework, or surprise users in production.
The missing piece: scenario depth
A user story tells you what someone wants.
A requirement tells you what the system should do.
Acceptance criteria should tell you how we know the behavior is correct.
But to write good acceptance criteria, you need scenario depth.
A shallow scenario says:
User changes subscription plan.
A deeper scenario asks:
- Is the subscription active?
- Does the change happen now or next billing cycle?
- Is there an unpaid invoice?
- Is the user allowed to change the plan?
- What happens to proration?
- What happens if the billing provider is unavailable?
- What happens if the user clicks twice?
- What email is sent?
- What audit event is recorded?
This is not overthinking.
This is finding the real behavior before users find it for you.
The 5-minute method
Take any user story, requirement, or workflow step.
For each step, ask two questions:
- Alternative: What is a different but valid way this could happen?
- Exception: What could break this step?
That is it.
Alternative means:
The behavior is still valid, but it follows a different path.
Exception means:
Something prevents the normal path from completing.
This small distinction is powerful.
It keeps the conversation organized.
You are not randomly brainstorming bugs. You are systematically expanding the scenario.
Example: change subscription plan
Let’s use a common SaaS example.
Initial story:
As a workspace admin, I want to change my subscription plan so that I can adjust my workspace to current needs.
Shallow acceptance criterion:
Given an active subscription
When the admin selects a new plan and confirms
Then the subscription is updated
This is Level 1.
It describes the happy path.
But it is too shallow.
Go one level deeper: alternatives
Ask:
What is a different but valid way to change the plan?
Example:
The user may want the plan change to happen at the next billing cycle instead of immediately.
Now write it:
Scenario: Schedule plan change for next billing cycle
Given a workspace has an active subscription
And the admin selects a new plan
When the admin chooses to apply the change at the next billing cycle
Then the system schedules the plan change for the renewal date
And the current plan remains active until the renewal date
And no immediate proration is applied
This is not a failure.
It is a valid alternative path.
Without this scenario, one developer may apply all plan changes immediately. Another may schedule downgrades but apply upgrades immediately. Another may leave it to the billing provider defaults.
All of those could be reasonable.
But only one is intended.
That is why alternatives matter.
Now go deeper: exceptions
Ask:
What could break this step?
Example:
There is an unpaid invoice.
Scenario: Block plan change when invoice is unpaid
Given a workspace has an active subscription
And there is an unpaid invoice
When the admin tries to change the subscription plan
Then the system blocks the plan change
And shows the message "Pay the outstanding invoice before changing your plan"
And no plan change is sent to the billing provider
Now the team has decided the behavior.
Not guessed. Decided.
Another exception:
The billing API is unavailable.
Scenario: Billing provider unavailable during plan change
Given a workspace has an active subscription
And the billing provider is unavailable
When the admin confirms a plan change
Then the system does not apply the plan change immediately
And the system shows the message "We could not process the change right now. Please try again shortly."
And the failed attempt is logged for support investigation
This is important.
Without this scenario, developers may handle the failure in different ways:
- retry automatically
- show a generic error
- leave the subscription in a partial state
- update the local database but fail to update the billing provider
- fail silently
- queue the change without telling the user
Again, many technically possible behaviors.
Only one should be accepted.
The Level 3 version
Now the acceptance criteria are deeper.
We have:
- Happy path
- Valid alternative
- Business-rule exception
- Integration failure exception
That is already much better.
The story now has enough behavioral depth for implementation, testing, and review.
It no longer says only:
“The admin can change the plan.”
It now defines what the system should do when:
- everything works
- the change is scheduled for later
- the account has an unpaid invoice
- the billing provider is unavailable
This is where acceptance criteria become useful.
Common places where acceptance criteria are too shallow
Here are the areas where I see teams miss depth most often.
1. Permissions
Shallow:
Given a user opens the page
When they click Delete
Then the item is deleted
Better questions:
- Is the user allowed to delete it?
- What role is required?
- What if the user had permission when the page loaded but lost permission before clicking?
- Should failed attempts be logged?
Deeper criterion:
Given a user without delete permission
When they attempt to delete the item
Then the system denies the action
And the item remains unchanged
2. Empty states
Shallow:
Given a user opens the dashboard
Then they see their reports
Better questions:
- What if there are no reports?
- Is this an error or a valid empty state?
- Should the system show guidance?
- Should the call-to-action be different?
Deeper criterion:
Given the user has no reports
When they open the dashboard
Then the system shows an empty state explaining that no reports exist yet
And provides an action to create the first report
3. Duplicate actions
Shallow:
When the user clicks Submit
Then the form is submitted
Better questions:
- What if the user clicks twice?
- What if the browser retries?
- What if the same request arrives twice?
- Is the operation idempotent?
Deeper criterion:
Given the user has already submitted the form
When the same submission request is received again
Then the system does not create a duplicate record
And returns the existing submission status
4. Stale data
Shallow:
Given an admin edits a user role
When they save changes
Then the role is updated
Better questions:
- What if another admin changed the same role first?
- What if the record version is stale?
- Should the system overwrite, reject, or ask for confirmation?
Deeper criterion:
Given an admin is editing a user role
And another admin updates the same user role before the first admin saves
When the first admin submits the stale change
Then the system rejects the update
And asks the admin to reload the latest user data
5. External service failure
Shallow:
When the user confirms payment
Then the payment is processed
Better questions:
- What if the payment provider times out?
- What if the provider returns success but the local update fails?
- What if the provider returns a pending status?
- What does the user see?
Deeper criterion:
Given the payment provider returns a timeout
When the user confirms payment
Then the system shows that the payment status is pending
And does not grant paid access until confirmation is received
And records the payment attempt for reconciliation
6. Notifications
Shallow:
Then the user receives an email
Better questions:
- When should the email be sent?
- What event triggers it?
- What happens if email delivery fails?
- Is the email required for the workflow to complete?
- Is the event logged?
Deeper criterion:
Given the export file is generated successfully
When the system sends the download email
Then the email contains a download link that expires after 24 hours
And the system records the notification status
A simple acceptance criteria checklist
Before you mark a story as ready, ask:
- Do we have the happy path?
- Do we have at least one valid alternative path?
- Do we have at least one likely exception?
- Are permissions defined?
- Are empty states defined?
- Are duplicate requests handled?
- Are external service failures handled?
- Are data consistency rules clear?
- Are timing expectations clear?
- Do we know what test proves the behavior?
You do not need all ten for every story.
But if the story is important, risky, customer-facing, or connected to money, permissions, data, security, or integrations, shallow criteria are not enough.
The fastest way to improve acceptance criteria
Use this mini-template:
Happy path:
What happens when everything works?
Alternative:
What is a different but valid path?
Exception:
What can break the flow?
Edge case:
What unusual but possible situation should not surprise us?
Verification:
How do we know the behavior is correct?
For the subscription example:
Happy path:
Active subscription changes immediately.
Alternative:
Admin schedules the change for the next billing cycle.
Exception:
Unpaid invoice blocks the change.
Exception:
Billing provider is unavailable.
Verification:
Plan status, billing event, email, and audit log are correct.
In five minutes, the story is already much stronger.
Why this matters for developers
Shallow acceptance criteria push product ambiguity into development.
That creates a familiar pattern:
- developer implements the obvious path
- reviewer asks about an edge case
- tester finds missing behavior
- product clarifies late
- developer reworks
- release gets delayed
- everyone says “we should have caught this earlier”
Yes.
You should have.
But the way to catch it earlier is not to ask people to “be more careful.”
The way to catch it earlier is to use a repeatable method.
Happy path. Alternative. Exception. Edge case. Verification.
This is simple enough for a small team and strong enough to prevent real problems.
Where AI can help
AI can be very useful here.
But only if the workflow gives it context.
If you ask:
“Generate acceptance criteria for subscription change.”
You will probably get generic criteria.
If you give AI:
- the user journey
- the actor
- the happy path
- business rules
- permissions
- external integrations
- known risks
- expected verification
Then AI can suggest:
- missing alternatives
- likely exceptions
- edge cases
- permission scenarios
- integration failure cases
- acceptance criteria
- test cases
This is the difference between AI generating text and AI supporting engineering reasoning.
And this is exactly where Ellygent fits.
Ellygent’s Workflow-to-Acceptance-Criteria approach
Ellygent is built around the idea that requirements and acceptance criteria should not be isolated text.
They should be derived from context.
That context can include:
- problem statement
- users and stakeholders
- user journeys
- operational scenarios
- product capabilities
- system functions
- requirements
- constraints
- risks
- verification methods
- traceability
For a small software team, this means Ellygent can help turn a workflow into deeper acceptance criteria.
For example:
Workflow:
Admin changes subscription plan.
Ellygent can help ask:
- What is the trigger?
- What is the happy path?
- What alternatives are valid?
- What exceptions are likely?
- What permissions apply?
- What systems are involved?
- What should be verified?
Then it can help generate acceptance criteria that are deeper than the first happy path.
Not because the AI is guessing.
But because the system context is structured.
That is the point.
Define the system. Give AI real context.
Final thought
Given-When-Then is useful.
But it is not enough if you only use it for the happy path.
The value of acceptance criteria is not the format. The value is the thinking behind the format.
A good acceptance criterion should reduce ambiguity before implementation starts.
It should help the developer know what to build. It should help the tester know what to verify. It should help the product owner know what they are accepting. It should help the team catch expensive surprises earlier.
So next time you write acceptance criteria, do not stop at:
Given everything is valid
When the user does the expected thing
Then the system works
Go one level deeper.
Ask:
- What is a valid alternative?
- What could break?
- What edge case would be expensive if we miss it?
- How will we verify the behavior?
Five minutes of this thinking can save days of rework later.
That is not bureaucracy.
That is professional software engineering.
Related Ellygent workflows
Connect this topic to product workflows for system definition, traceability, export, and AI-assisted engineering.
About the author
A. Perico writes about Systems Engineering definition, traceability, AI-assisted engineering workflows, and ways to keep implementation aligned with approved system context.
Define system context with Ellygent.
See how Ellygent supports Systems Engineering workflows from definition through traceability, baselines, and context export.