How to Write Test Cases for Form Validation (With Examples)

How to Write Test Cases for Form Validation (With Examples)

June 21, 2026 · 15 min read · How-To Guides

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.

IDPreconditionsStepsExpected Result
REG‑FN‑001User on registration page, no prior data1. Enter “John” in First Name 2. Tab outField border turns green, no error message
REG‑FN‑002Same as above1. Enter “J0hn!” (contains digit & symbol) 2. Tab outRed border, error “Only letters allowed”
REG‑FN‑003Same as above1. Leave field blank 2. Tap SubmitInline error “First name is required”
REG‑FN‑004Same as above1. Enter 30‑character string of ‘A’ 2. Tab outGreen border (max length 30 accepted)
REG‑FN‑005Same as above1. Enter 31‑character string 2. Tab outRed border, error “Maximum 30 characters”
REG‑LN‑001Same as above1. Enter “O’Connor” (apostrophe) 2. Tab outGreen border (apostrophe allowed)
REG‑LN‑002Same as above1. Enter “Smith​” (zero‑width space) 2. Tab outRed border, error “Invalid character”
REG‑EMAIL‑001Same as above1. Enter “alice@example.com” 2. Tab outGreen border, no error
REG‑EMAIL‑002Same as above1. Enter “alice@localhost” 2. Tab outRed border, error “Domain must have a TLD”
REG‑EMAIL‑003Same as above1. Enter “alice@@example.com” 2. Tab outRed border, error “Invalid format”
REG‑EMAIL‑004Same as above1. Paste a 255‑character valid email 2. Tab outGreen border (max length 255)
REG‑EMAIL‑005Same as above1. Paste a 256‑character email 2. Tab outRed border, error “Exceeds maximum length”
REG‑PW‑001Same as above1. Enter “Str0ng!Pwd” (8 chars, mixed) 2. Tab outGreen border
REG‑PW‑002Same as above1. Enter “weak” (4 chars) 2. Tab outRed border, error “Minimum 8 characters”
REG‑PW‑003Same as above1. Enter “AAAAAAAAAAAAAAAAAAAA” (20 ‘A’s) 2. Tab outGreen border (max 20)
REG‑PW‑004Same as above1. Enter 21‑character string 2. Tab outRed border, error “Maximum 20 characters”
REG‑PW‑005Same as above1. Enter password with only letters 2. Tab outRed border, error “Requires at least one digit and one symbol”
REG‑PH‑001Same as above1. Enter “+1 555 123 4567” 2. Tab outGreen border (international format accepted)
REG‑PH‑002Same as above1. Enter “555‑123‑4567” 2. Tab outGreen border (national format)
REG‑PH‑003Same as above1. Enter “12345” 2. Tab outRed border, error “Invalid phone number”
REG‑PH‑004Same as above1. Paste a string with leading spaces “  +1 555 123 4567” 2. Tab outRed border, error “Leading/trailing spaces not allowed”
REG‑SUBMIT‑001All fields valid per above1. Click Submit buttonNavigation to welcome page, server receives payload with all fields
REG‑SUBMIT‑002Email invalid, others valid1. Click Submit buttonSubmit remains disabled, inline email error persists
REG‑SUBMIT‑003Password missing, others valid1. Click Submit buttonSubmit disabled, password field shows required error
REG‑SUBMIT‑004Phone contains letters, others valid1. Click Submit buttonSubmit disabled, phone field error shown
REG‑RESET‑001Any invalid state1. Click Reset buttonAll 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:

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.

IDFieldDescriptionPriority
REG‑FN‑001First NameValid entryP1
REG‑FN‑002First NameInvalid charsP0
REG‑FN‑003First NameEmpty requiredP0
REG‑FN‑004First NameMax lengthP1
REG‑FN‑005First NameOver maxP0
REG‑EMAIL‑001EmailValid formatP1
REG‑EMAIL‑002EmailMissing TLDP0
REG‑EMAIL‑003EmailDouble @P0
REG‑EMAIL‑004EmailMax lengthP1
REG‑EMAIL‑005EmailOver max lengthP0
REG‑PW‑001PasswordValid complexP1
REG‑PW‑002PasswordToo shortP0
REG‑PW‑003PasswordMax lengthP1
REG‑PW‑004PasswordOver maxP0
REG‑PW‑005PasswordMissing digit/symbolP0
REG‑PH‑001PhoneValid intlP1
REG‑PH‑002PhoneValid nationalP1
REG‑PH‑003PhoneToo shortP0
REG‑PH‑004PhoneLeading spacesP0
REG‑SUBMIT‑001FormAll valid → submitP0
REG‑SUBMIT‑002FormEmail invalid → blockP0
REG‑SUBMIT‑003FormPassword missing → blockP0
REG‑SUBMIT‑004FormPhone invalid → blockP0
REG‑RESET‑001FormReset clears stateP2

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:

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:

How to capture them

To surface these issues, extend your test suite with:

  1. Character‑normalization tests: Provide input in both NFC and NFD forms and assert that the backend receives identical bytes.
  2. Clipboard simulation: Use the OS clipboard API to paste strings containing invisible characters and verify that the validation routine strips or rejects them.
  3. Autofill scripts: In Playwright, invoke page.evaluate(() => { /* trigger autofill */ }) before validation checks, then assert the final state.
  4. Throttled network: Simulate slow 3G or offline conditions using Chrome DevTools Protocol or Android’s netem tool, then confirm that client‑side validation does not rely on a missing server response.
  5. 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

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:

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