Acceptance testing means at least three different things depending on who's doing it and when. UAT is business stakeholders confirming requirements before sign-off, ATDD is executable tests written before development starts, and functional acceptance testing is QA verifying the spec is fully implemented. The failure modes that matter: testing too late, involving the wrong people, and writing criteria vague enough to argue about.
What Acceptance Testing Actually Means
The term covers several related concepts:
User Acceptance Testing (UAT): The actual users (or business stakeholders) test the software in a staging environment to confirm it meets their requirements before sign-off. Acceptance test-driven development (ATDD): Requirements are written as executable tests before development starts. Tests pass when the feature is complete. Functional acceptance testing: QA verifies that all requirements from the spec are implemented and working correctly.In practice, "acceptance testing" usually means one of two things: UAT done by business people, or functional acceptance testing done by QA against the formal requirements.
Acceptance Criteria: The Foundation
Acceptance testing starts with acceptance criteria — the specific conditions a feature must meet to be considered complete.
Bad acceptance criteria:The login page should work properly.Good acceptance criteria:
- Users can log in with a valid email and password and are redirected to the dashboard
- If email or password is wrong, an error message "Invalid email or password" appears
- After 5 failed attempts in 10 minutes, the account is locked and a "too many attempts" message appears
- Successful login creates a session that expires after 24 hours of inactivity
- "Forgot password" link on the login page sends a reset email within 2 minutes
Good criteria are specific, testable, and unambiguous.
Writing Acceptance Criteria with Gherkin
Gherkin is a structured language for writing acceptance criteria in a Given/When/Then format. It's readable by non-technical stakeholders and executable by tools like Cucumber:
Feature: User Login
Scenario: Successful login with valid credentials
Given I am on the login page
When I enter "user@example.com" as email
And I enter "ValidPass1" as password
And I click the "Sign In" button
Then I should be redirected to the dashboard
And I should see "Welcome, Test User" in the header
Scenario: Login with invalid credentials
Given I am on the login page
When I enter "user@example.com" as email
And I enter "WrongPassword" as password
And I click the "Sign In" button
Then I should see "Invalid email or password"
And I should remain on the login page
Scenario: Account lockout after failed attempts
Given I am on the login page
When I enter "user@example.com" with wrong password 5 times
Then I should see "Too many failed attempts. Try again in 10 minutes."
And the Sign In button should be disabledUAT Process: Step by Step
1. Define the test scope
Which features are included in this UAT cycle? Usually tied to a release or sprint.
2. Write test scenarios with business users
Involve the actual users or product owners in writing scenarios. They know what "correct" looks like from a business perspective.
3. Prepare the environment
UAT should run in an environment close to production — same data, same integrations, same configuration. A developer's laptop is not a UAT environment.
4. Create test data
Users need realistic test data that represents their actual use cases — not just "test@test.com" with "test123" as the password.
5. Execute tests
Ideally, real users run the tests. If not, QA runs them — but the acceptance criteria must be defined by business stakeholders, not QA.
6. Document results
Every test gets a pass/fail status. Failures get bug reports with steps to reproduce.
7. Fix and re-test
Bugs found in UAT go back to developers. QA re-tests once fixed.
8. Sign-off
Business stakeholders formally approve the release. Without sign-off, don't ship.
UAT vs QA Testing
| Aspect | QA Testing | UAT |
|--------|-----------|-----|
| Who does it | QA engineers | Business users / product owners |
| Goal | Find bugs | Confirm requirements met |
| Focus | Technical correctness | Business value |
| When | During development | Before release |
| Criteria | Test cases | Acceptance criteria |
| Sign-off | QA lead | Business stakeholder |
Both are necessary. QA catches the bugs before UAT so business users aren't doing basic bug finding.
Common Acceptance Testing Failure Modes
Testing too late:UAT scheduled 2 days before release with no buffer for fixes. Every bug found is a crisis.
Wrong participants:Having developers do UAT defeats the purpose. They know how the code works and won't test it like a user.
Vague acceptance criteria:"The checkout should be smooth" leads to disagreements about what "smooth" means when bugs are found.
Testing in the wrong environment:UAT on a development server with test data that doesn't reflect real volumes or integrations will miss real bugs.
No regression after fixes:Bug found in UAT, fixed, deployed — but nobody checks if the fix broke something else.
Automated Acceptance Testing
You can automate acceptance tests with Playwright:
// acceptance/login.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Login — Acceptance Criteria', () => {
test('AC1: Valid credentials redirect to dashboard', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@test.com');
await page.fill('[data-testid="password"]', 'ValidPass1');
await page.click('[data-testid="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.getByTestId('welcome')).toContainText('Welcome');
});
test('AC2: Invalid credentials show error message', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@test.com');
await page.fill('[data-testid="password"]', 'WrongPassword');
await page.click('[data-testid="submit"]');
await expect(page.getByTestId('error-message')).toHaveText('Invalid email or password');
await expect(page).toHaveURL('/login');
});
test('AC3: Session expires after 24h inactivity', async ({ page, context }) => {
// This would typically be tested with mock time or API manipulation
// showing the concept:
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@test.com');
await page.fill('[data-testid="password"]', 'ValidPass1');
await page.click('[data-testid="submit"]');
// Simulate session expiry via API
await page.request.post('/api/auth/expire-session');
await page.reload();
await expect(page).toHaveURL('/login');
});
});Acceptance Testing Checklist
Before starting UAT:
- [ ] Acceptance criteria written and reviewed by business
- [ ] Feature deployed to UAT environment (not dev or staging)
- [ ] Test data prepared (realistic, not trivial)
- [ ] Business users briefed on what to test
- [ ] Bug tracking process established
- [ ] Schedule includes time for fixes and re-testing
- [ ] Sign-off authority identified
During UAT:
- [ ] Tests executed against acceptance criteria
- [ ] All failures documented with reproduction steps
- [ ] Screenshots/recordings for complex issues
- [ ] Severity assigned to each finding
After UAT:
- [ ] All P1/P2 bugs fixed and re-tested
- [ ] Business sign-off received in writing
- [ ] Release notes updated with known P3/P4 issues
Summary
Acceptance testing confirms that software delivers the business value it was built for. Key points:
- Based on acceptance criteria written by business stakeholders, not QA
- UAT involves real users; functional acceptance testing is done by QA against requirements
- Gherkin (Given/When/Then) makes criteria readable by both technical and non-technical stakeholders
- Run in a production-like environment with realistic data
- Requires formal sign-off before release
- Can be automated with Playwright — especially valuable for regression on future changes
The difference between a QA sign-off and a business sign-off matters: QA says the code works; business says the feature solves their problem.
→ See also: Verification vs. Validation in Software Testing: What's the Difference? | How to Write a Test Plan: Template and Real Examples | Risk-Based Testing: Prioritizing What to Test When You Can't Test Everything