How to Write Test Cases for Form Validation (With Examples)
How to Write Test Cases for Form Validation (With Examples)
How to Write Test Cases for Form Validation (With Examples)
How to Write Test Cases for Form Validation (With Examples): Foundations
Test case anatomy
A test case is a structured artifact that documents the conditions under which a system behaves as expected or fails. For form validation, each case must capture the input scenario, the validation rule exercised, and the observable outcome. The minimal anatomy includes a unique identifier, preconditions that set the test environment, a step‑by‑step procedure, and an expected result expressed in measurable terms (e.g., “error message ‘Invalid email’ appears below the field”). Optional fields such as priority, tags, and links to requirement IDs improve traceability and enable selective execution. Writing cases with this anatomy ensures that reviewers can reproduce the test without ambiguity and that automation engineers can map steps directly to test scripts.
Positive vs negative vs edge vs boundary vs negative
Positive cases verify that the form accepts valid data and proceeds without obstruction. For a typical email field, a positive case might supply “user@example.com” and expect the submit button to enable. Negative cases inject data that violates a rule—such as “user@” or “user@domain”—and expect a specific validation message. Edge cases sit at the limits of the input domain but still conform to the rule; for a password length rule of 8‑20 characters, an edge case provides exactly 8 or exactly 20 characters. Boundary cases step just outside the legal range, providing 7 or 21 characters, and should trigger rejection. Distinguishing these categories helps designers cover the full spectrum of validation logic while avoiding redundant tests.
Traceability to requirements
Each validation rule originates from a functional or non‑functional requirement (e.g., “The email field shall conform to RFC 5322”). When drafting a test case, record the requirement ID in a dedicated column or tag. This creates a bidirectional link: you can verify that every requirement has at least one validating test case, and you can impact‑analyze requirement changes by locating all associated cases. Traceability also supports regulatory audits where evidence of validation coverage is required. In practice, many teams embed the requirement tag as a prefix in the test case ID (e.g., REQ‑EMAIL‑001) to make the relationship explicit during test‑run reporting.
How to Write Test Cases for Form Validation (With Examples): Building a Test Matrix
Defining IDs and preconditions
Assign each test case a machine‑readable ID that encodes the feature area, the validation rule, and a sequential number. Example: FORM‑LOGIN‑EMAIL‑001. Preconditions describe the state the application must be in before the first step executes. For web forms, preconditions often include “User is on the login page” and “No existing session cookies”. For mobile apps, preconditions may involve “App is launched from a clean state” and “Network simulator set to 3G”. Explicit preconditions prevent flaky tests caused by hidden dependencies and make it easier to parallelize execution.
Steps and expected result
Steps should be imperative, atomic, and free of implementation details that belong in automation scripts. Use the format: “1. Locate the email input field. 2. Enter the value ‘test@example.com’. 3. Press Tab to trigger validation.” The expected result must be observable through the UI or an API response. For validation, typical expectations are: “The field border turns green”, “An inline help text disappears”, or “The submit button becomes enabled”. Avoid vague phrasing like “The form works correctly”; instead, specify the exact UI state or data change that indicates success or failure.
Example table with 20+ cases
Below is a comprehensive test matrix for a registration form containing fields for first name, last name, email, password, and phone number. Each row follows the ID‑precondition‑steps‑expected result pattern.
| ID | Preconditions | Steps | Expected Result |
|---|---|---|---|
| REG‑FN‑001 | User on registration page, no prior data | 1. Enter “John” in First Name 2. Tab out | Field border turns green, no error message |
| REG‑FN‑002 | Same as above | 1. Enter “J0hn!” (contains digit & symbol) 2. Tab out | Red border, error “Only letters allowed” |
| REG‑FN‑003 | Same as above | 1. Leave field blank 2. Tap Submit | Inline error “First name is required” |
| REG‑FN‑004 | Same as above | 1. Enter 30‑character string of ‘A’ 2. Tab out | Green border (max length 30 accepted) |
| REG‑FN‑005 | Same as above | 1. Enter 31‑character string 2. Tab out | Red border, error “Maximum 30 characters” |
| REG‑LN‑001 | Same as above | 1. Enter “O’Connor” (apostrophe) 2. Tab out | Green border (apostrophe allowed) |
| REG‑LN‑002 | Same as above | 1. Enter “Smith” (zero‑width space) 2. Tab out | Red border, error “Invalid character” |
| REG‑EMAIL‑001 | Same as above | 1. Enter “alice@example.com” 2. Tab out | Green border, no error |
| REG‑EMAIL‑002 | Same as above | 1. Enter “alice@localhost” 2. Tab out | Red border, error “Domain must have a TLD” |
| REG‑EMAIL‑003 | Same as above | 1. Enter “alice@@example.com” 2. Tab out | Red border, error “Invalid format” |
| REG‑EMAIL‑004 | Same as above | 1. Paste a 255‑character valid email 2. Tab out | Green border (max length 255) |
| REG‑EMAIL‑005 | Same as above | 1. Paste a 256‑character email 2. Tab out | Red border, error “Exceeds maximum length” |
| REG‑PW‑001 | Same as above | 1. Enter “Str0ng!Pwd” (8 chars, mixed) 2. Tab out | Green border |
| REG‑PW‑002 | Same as above | 1. Enter “weak” (4 chars) 2. Tab out | Red border, error “Minimum 8 characters” |
| REG‑PW‑003 | Same as above | 1. Enter “AAAAAAAAAAAAAAAAAAAA” (20 ‘A’s) 2. Tab out | Green border (max 20) |
| REG‑PW‑004 | Same as above | 1. Enter 21‑character string 2. Tab out | Red border, error “Maximum 20 characters” |
| REG‑PW‑005 | Same as above | 1. Enter password with only letters 2. Tab out | Red border, error “Requires at least one digit and one symbol” |
| REG‑PH‑001 | Same as above | 1. Enter “+1 555 123 4567” 2. Tab out | Green border (international format accepted) |
| REG‑PH‑002 | Same as above | 1. Enter “555‑123‑4567” 2. Tab out | Green border (national format) |
| REG‑PH‑003 | Same as above | 1. Enter “12345” 2. Tab out | Red border, error “Invalid phone number” |
| REG‑PH‑004 | Same as above | 1. Paste a string with leading spaces “ +1 555 123 4567” 2. Tab out | Red border, error “Leading/trailing spaces not allowed” |
| REG‑SUBMIT‑001 | All fields valid per above | 1. Click Submit button | Navigation to welcome page, server receives payload with all fields |
| REG‑SUBMIT‑002 | Email invalid, others valid | 1. Click Submit button | Submit remains disabled, inline email error persists |
| REG‑SUBMIT‑003 | Password missing, others valid | 1. Click Submit button | Submit disabled, password field shows required error |
| REG‑SUBMIT‑004 | Phone contains letters, others valid | 1. Click Submit button | Submit disabled, phone field error shown |
| REG‑RESET‑001 | Any invalid state | 1. Click Reset button | All fields cleared, borders return to default, no error messages |
This matrix demonstrates how to cover positive, negative, edge, and boundary conditions for each field, as well as form‑level interactions such as submit enablement and reset behavior.
How to Write Test Cases for Form Validation (With Examples): Data Setup and Management
Test data generation
Reliable form validation tests depend on reproducible data sets. For static rules (e.g., max length), you can hard‑code values directly in the test case. For dynamic constraints (e.g., email domains pulled from a configuration service), generate data at runtime using a factory or a data‑driven approach. Tools such as Faker.js, Python’s Faker library, or custom SQL scripts can produce realistic yet controlled inputs. When generating borderline values, compute them programmatically: max_len = config.get('email_max_len'); test_val = 'a' * (max_len + 1). This eliminates manual arithmetic errors and ensures the test adapts if the requirement changes.
Using data pools
For fields that accept a list of permissible values (e.g., country codes), maintain a CSV or JSON pool that the test harness reads. Each test iteration selects the next entry, looping back when exhausted. This technique expands coverage without proliferating test case IDs. Tag each pool entry with a classification (valid, invalid, edge) so that the test report can break down failures by data class. In CI pipelines, version‑control the pool alongside the test suite to guarantee that the same data set is used across branches.
Handling dynamic values
Some forms embed timestamps, tokens, or CAPTCHA challenges that change on each load. For validation testing, isolate the dynamic component: either disable it in a test environment or mock the service that provides it. For example, if a form includes a time‑based one‑time password (OTP) field for verification, replace the OTP generator with a stub that always returns “123456”. This allows the validation logic for the OTP field (length, numeric only) to be exercised without being blocked by a moving target. Document any such overrides in the test case precondition so that reviewers understand the test’s why a particular step is skipped or replaced is clear.
Prioritization and Risk-Based Selection
Risk matrix
Not all validation rules carry equal weight. A risk matrix plots the likelihood of a defect against its impact on users or business. High‑likelihood, high‑impact items (e.g., email format validation that prevents account creation) receive top priority. Low‑likelihood, low‑impact items (e.g., accepting a trailing space in a phone number that gets stripped server‑side) can be scheduled for later cycles. Populate the matrix using historical defect data, production monitoring, and stakeholder input. The resulting scores guide test case ordering and help decide which cases to automate first.
Priority levels (P0‑P3)
Define four priority tiers:
- P0: Must‑pass for release; failure blocks deployment (e.g., required field enforcement, security‑relevant validation).
- P1: Important for user experience; release can proceed with known issues but they should be fixed in the next sprint (e.g., misleading error text, minor UI glitch).
- P2: Nice‑to‑have; enhances completeness but does not affect core functionality (e.g., accepting uncommon Unicode characters in a name field).
- P3: Experimental or low‑value; can be deferred indefinitely (e.g., validating a field that is hidden behind a feature flag not yet enabled).
Assign each test case a priority label in its metadata. Test runners can then filter by priority to smoke‑test (P0 only) or run a full regression (all priorities).
Example prioritization table
Below is a shortened view of the registration matrix with priority annotations.
| ID | Field | Description | Priority |
|---|---|---|---|
| REG‑FN‑001 | First Name | Valid entry | P1 |
| REG‑FN‑002 | First Name | Invalid chars | P0 |
| REG‑FN‑003 | First Name | Empty required | P0 |
| REG‑FN‑004 | First Name | Max length | P1 |
| REG‑FN‑005 | First Name | Over max | P0 |
| REG‑EMAIL‑001 | Valid format | P1 | |
| REG‑EMAIL‑002 | Missing TLD | P0 | |
| REG‑EMAIL‑003 | Double @ | P0 | |
| REG‑EMAIL‑004 | Max length | P1 | |
| REG‑EMAIL‑005 | Over max length | P0 | |
| REG‑PW‑001 | Password | Valid complex | P1 |
| REG‑PW‑002 | Password | Too short | P0 |
| REG‑PW‑003 | Password | Max length | P1 |
| REG‑PW‑004 | Password | Over max | P0 |
| REG‑PW‑005 | Password | Missing digit/symbol | P0 |
| REG‑PH‑001 | Phone | Valid intl | P1 |
| REG‑PH‑002 | Phone | Valid national | P1 |
| REG‑PH‑003 | Phone | Too short | P0 |
| REG‑PH‑004 | Phone | Leading spaces | P0 |
| REG‑SUBMIT‑001 | Form | All valid → submit | P0 |
| REG‑SUBMIT‑002 | Form | Email invalid → block | P0 |
| REG‑SUBMIT‑003 | Form | Password missing → block | P0 |
| REG‑SUBMIT‑004 | Form | Phone invalid → block | P0 |
| REG‑RESET‑001 | Form | Reset clears state | P2 |
This table makes it trivial to generate a smoke suite (all P0 cases) or a comprehensive nightly run (all priorities).
Manual Execution vs Automated Execution
When to manual
Manual testing remains valuable for exploratory validation, usability checks, and scenarios that are difficult to automate reliably (e.g., testing the behavior of a form when the device’s accessibility zoom is set to 200%). Use manual sessions to:
- Verify that error messages are grammatically correct and tone‑appropriate.
- Confirm that visual focus moves logically after validation failures.
- Detect issues that depend on timing, such as a debounce interval that is too short or too long.
- Validate that the form works correctly with assistive technologies (screen readers, voice control).
Manual testers should follow the same test case IDs and steps, recording any deviations in a test log. This log becomes a source for converting effective exploratory findings into new automated cases.
Automation strategies (Appium, Playwright)
For web forms, Playwright offers a robust, cross‑browser API. A typical validation test might look like:
# test_email_validation.py
from playwright.sync_api import expect, sync_playwright
def test_email_format():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("https://example.com/register")
# Fill email with invalid value
page.fill('input[name="email"]', "user@")
expect(page.locator('div.error[for="email"]')).to_have_text("Enter a valid email")
# Fill with valid value
page.fill('input[name="email"]', "user@example.com")
expect(page.locator('input[name="email"]')).to_have_css("border-color", "rgb(0, 128, 0)") # green
browser.close()
For native Android forms, Appium provides device‑level interaction:
// EmailValidationTest.java
@Test
public void testEmailRequired() {
AndroidDriver<MobileElement> driver = new AndroidDriver<>(new URL("http://localhost:4723/wd/hub"), caps);
MobileElement emailField = driver.findElement(By.id("email_input"));
emailField.clear();
emailField.sendKeys(""); // empty
MobileElement submit = driver.findElement(By.id("submit_btn"));
Assert.assertFalse(submit.isEnabled(), "Submit should be disabled when email empty");
MobileElement error = driver.findElement(By.id("email_error"));
Assert.assertEquals(error.getText(), "Email is required");
driver.quit();
}
Both snippets illustrate how to assert UI state (error text, border color, button enabled state) directly from the validation rules defined in the test case.
Code snippets for data‑driven execution
To avoid hard‑coding each value, feed the test matrix into a loop:
// Playwright data‑driven example
const testData = [
{email: "alice@example.com", valid: true},
{email: "alice@", valid: false},
{email: "alice@@example.com", valid: false},
// …more rows
];
testData.forEach(({email, valid}) => {
test(`email validation: ${email}`, async ({page}) => {
await page.goto('/register');
await page.fill('input[name="email"]', email);
const error = page.locator('div.error[for="email"]');
if (valid) {
await expect(error).toBeHidden();
await expect(page.locator('input[name="email"]')).toHaveCSS('border-color', 'rgb(0, 128, 0)');
} else {
await expect(error).toBeVisible();
await expect(page.locator('input[name="email"]')).toHaveCSS('border-color', 'rgb(255, 0, 0)');
}
});
});
Such a script can be generated automatically from the CSV version of the test matrix, reducing maintenance overhead.
Edge Cases that Surface Only in Production
Real‑world examples
Even the most thorough matrix can miss conditions that appear only under specific production stresses:
- Unicode normalization: A name field accepting “José” may pass validation, but if the backend stores the string in NFC form while the UI uses NFD, a mismatch can cause duplicate‑account bugs.
- Paste‑jacking: Users may paste a phone number that includes hidden Unicode characters (e.g., zero‑width joiner) that pass a regex test but break downstream SMS gateways.
- Autocomplete interference: Browser autofill may populate a field with a value that violates a pattern (e.g., filling “123‑456‑7890” into a field that expects only digits). If the validation script runs before the autofill value is fully committed, the form may incorrectly enable the submit button.
- Network latency lag: Validation that depends on an asynchronous server call (e.g., checking if a username is already‑time out, leaving the field in an ambiguous to race condition where two rapid successive inputs (fast typing, copy‑paste) trigger validation twice, causing the error message to flash or the submit button to toggle erratically.
How to capture them
To surface these issues, extend your test suite with:
- Character‑normalization tests: Provide input in both NFC and NFD forms and assert that the backend receives identical bytes.
- Clipboard simulation: Use the OS clipboard API to paste strings containing invisible characters and verify that the validation routine strips or rejects them.
- Autofill scripts: In Playwright, invoke
page.evaluate(() => { /* trigger autofill */ })before validation checks, then assert the final state. - Throttled network: Simulate slow 3G or offline conditions using Chrome DevTools Protocol or Android’s
netemtool, then confirm that client‑side validation does not rely on a missing server response. - Rapid‑input stress: Use a loop that types 10 characters per second for 5 seconds and monitors that validation state stabilizes after the final keystroke.
Record any failures as new test cases (e.g., REG‑EMAIL‑UNI‑001) and add them to the regression suite. This practice transforms production incidents into preventive safeguards.
Checklist for Form Validation Test Cases
- [ ] Each validation rule has at least one positive test case.
- [ ] Each validation rule has at least one negative test case covering the most common mistake format.
- [ ] Edge cases (minimum, maximum, exact boundary) are documented.
- [ ] Boundary cases (just outside limits) are documented.
- [ ] Test case IDs follow the
convention.‑ ‑ ‑ - [ ] Preconditions explicitly state page/view, auth state, and any mocked services.
- [ ] Steps are imperative, UI‑agnostic where possible, and do not contain implementation details.
- [ ] Expected results are observable (UI state, API response, log entry) and unambiguous.
- [ ] Priority (P0‑P3) is assigned based on risk matrix.
- [ ] Data source (hard‑coded, pool, generator) is noted in the test case metadata.
- [ ] Traceability link to requirement ID or user story is present.
- [ ] For web tests, a Playwright or Selenium snippet is provided or referenced.
- [ ] For mobile tests, an Appium or Espresso snippet is provided or referenced.
- [ ] Any environment overrides (disabled CAPTCHA, mocked OTP) are listed in preconditions.
- [ ] The test case has been reviewed by a developer and a QA peer.
- [ ] The test case is version‑controlled alongside the automation framework.
Running through this checklist before committing a test case reduces the likelihood of ambiguous or duplicated efforts and ensures that the suite remains maintainable as the form evolves.
Leveraging Autonomous QA with SUSA for Form Validation
SUSA explores an application without pre‑written scripts by simulating a variety of user personas—curious, impatient, novice, power‑user, and accessibility‑focused—each with distinct interaction patterns. When pointed at a registration form, SUSA will automatically:
- Tap every field, attempt diverse input combinations (including emojis, whitespace, and rapid successive taps),
- Trigger validation on blur, on submit, and after asynchronous calls,
- Detect crashes, ANRs, disabled buttons that should be enabled, and misleading error messages,
- Generate a regression script in Appium (Android) or Playwright (Web) that reproduces the discovered flow.
Because Susa’s exploration is guided by learned behavior profiles, it often discovers edge cases that a manual tester might overlook, such as a form that accepts a 256‑character email when the backend truncates at 255, causing a silent data loss. The generated scripts can be fed directly into your CI pipeline, providing an automated safety net that evolves as the application changes. Teams report that a single SUSA run typically uncovers 15‑20% more validation‑related defects than a manually curated test suite, especially in areas involving internationalization and accessibility.
Integrating SUSA-Generated Scripts into CI
To make Susa part of your continuous delivery pipeline, install the agent locally or in your build agent:
pip install susatest-agent
susatest run --app ./app-debug.apk --personas all --output ./susa-artifacts
The command above instructs SUSA to exercise the APK using all defined personas, store logs, screenshots, and the auto‑generated Appium test suite in the susa-artifacts directory. In a GitHub Actions workflow, you might add:
name: Form Validation CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install SUSA
run: pip install susatest-agent
- name: Run SUSA exploration
run: susatest run --app ./app-debug.apk --personas all --output ./susa-artifacts
- name: Deploy generated Appium tests
run: |
cd susa-artifacts/generated_tests
npm install
npx wdio run wdio.conf.js
The workflow pulls the latest code, runs SUSA to produce fresh validation tests, then executes those tests with the existing Appium infrastructure. If any newly discovered defect causes a failure, the pipeline halts, giving the team immediate feedback. Over successive runs, SUSA’s cross‑session learning ensures that previously explored dead ends are skipped, focusing effort on new code paths and gradually increasing the depth of form‑validation coverage without manual test‑case authoring overhead.
Closing Takeaways
Writing effective test cases for form validation begins with a solid anatomy: unique ID, explicit preconditions, clear steps, and measurable expected results. Separate tests into positive, negative, edge, and boundary categories to ensure you probe both the happy path and the limits of each rule. Link every case to its source requirement so that you can trace coverage and assess impact when requirements evolve. Prioritize using a risk‑based matrix (P0‑P3) to focus early efforts on the defects that would block release or harm users most.
Complement manual exploratory testing with automated scripts built from Playwright for web or Appium for native mobile. Use data‑driven techniques to avoid hard‑coding values and to keep the suite adaptive to changing limits. Actively hunt for production‑only edge cases—Unicode normalization, paste‑jacking, autofill interactions, and network latency—by adding targeted scenarios that mimic those stresses. A concise checklist helps maintain consistency and quality across the team.
Finally, consider augmenting your authored cases with autonomous exploration via SUSA. Its persona‑driven crawling discovers hidden validation gaps and outputs ready‑to‑run regression scripts, which can be plugged into your CI pipeline for continuous feedback. When you combine carefully crafted test cases with smart, self‑learning exploration, you achieve high‑signal coverage of form validation that survives both lab testing and the unpredictable realities of real‑world usage.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free