Testing Web Forms

A web form collects input and sends it to a server: sign-up, checkout, booking, contact. Testing it means checking that each field accepts what it should and refuses what it should not, that every refusal explains itself, that submitting does one thing exactly once, and that the server stores what the user entered.

Form input is usually checked in two layers. Client-side validation runs in the browser before anything is sent: leave Email empty and the browser stops you. Server-side validation runs when the request arrives: only the server knows that this email is already registered. A tester checks both.

Why it matters

Forms are where users hand over data and money. A form that refuses a valid name with an apostrophe loses a customer; one that books twice on a double click uses up seats or charges twice. Such defects slip through when only a few tidy values are ever tried.

Client-side checks are also easy to bypass. The OWASP Input Validation Cheat Sheet requires validation on the server, because anything done in the browser can be circumvented. A form that looks strict may sit on a server that accepts anything, and only a tester who goes around the browser finds out.

Client-side and server-side validation

Client-side validation uses the HTML constraints from the lesson on HTML for testers, such as required, type="email" and maxlength, plus page scripts that check a field as you type or when you leave it. It gives fast feedback but no protection: anyone can remove an attribute, turn off JavaScript or send the request without the page.

Server-side validation is the enforcement. It must repeat every client rule, and only it can check rules that need stored data, such as a unique email or the seats left. Test each rule through the form, for the user experience, and around it, for enforcement, on a system you are allowed to test:

  • In DevTools, delete required or maxlength, or add novalidate to the <form> to switch off the browser's built-in checks, then submit. The edit lives only in your tab; the request is real.
  • Or copy the request from the Network panel as cURL, change one value and send it again with a test account, from a terminal or the API Request Builder; the cURL Parser splits the command into method, URL, headers and body. Expected: a 4xx response such as 422, the field named, nothing saved.
  • Compare the two rule sets. A server stricter than the page (the page allows 100 characters, the database column holds 50) shows up as a 500 or silently cut text; a page stricter than the server (an email pattern that refuses +) blocks real users.
An empty required field is stopped in the browser before any request; an email that is already registered, and a replayed request with invalid values, are refused only by the server.
  • Through the form

    • User
    • Browser client-side validation
    • Server server-side validation
    1. 1 From User To Browser: Clicks Register, Full name empty
    2. 2 Browser internally: required fails: no request is sent
    3. 3 From Browser To User: Message at Full name reply
    4. 4 From User To Browser: Fixes the name, clicks Register again Email sara@example.com, already registered
    5. 5 Browser internally: Client-side checks pass
    6. 6 From Browser To Server: Sends the registration POST /events/42/registrations
    7. 7 Server internally: Repeats the rules, checks stored registrations Only the server knows the email is taken
    8. 8 From Server To Browser: 422: email already registered reply {"field":"email","code":"already_registered"}
    9. 9 From Browser To User: Message under Email, input kept reply

    Outcome: The browser gives instant feedback; rules that need stored data are checked only on the server.

  • Around the form

    • Tester
    • Browser client-side validation
    • Server server-side validation
    1. 1 From Tester To Server: Replays the request without the page Copied as cURL: a name of spaces, 5 tickets {"name":" ","tickets":5}
    2. 2 Server internally: Repeats every client rule Trims the name, allows 1 to 4 tickets
    3. 3 From Server To Tester: 422, both fields named, nothing saved reply Pass

    Outcome: Expected: the server refuses on its own. A 201 here would mean it trusts the browser, which is a defect.

Try it: cURL Parser Try it: API Request Builder

Required fields and positive and negative inputs

Check that every required field is marked on screen and in the markup, and that every missing field gets reported. Browsers' built-in messages usually show only the first invalid field at a time; if the requirement asks for all errors at once, the page needs its own script that lists them. A text field containing only spaces satisfies the browser's required check, so the server must trim and refuse it. Optional fields left empty must be stored as empty, not as the text null; conditionally required fields, such as a VAT number for business accounts only, need both states tested.

Then apply positive and negative testing field by field. Real valid data is more varied than developers expect:

  • Positive: O'Brien, Al-Harbi, names in Arabic and Devanagari, sara+events@example.com, a phone number pasted with spaces such as +966 50 123 4567.
  • Negative: empty, only spaces, one character over the maximum, sara@, letters in a number field, 0 where the minimum is 1.
  • Text that looks like markup, such as <b>Sara</b>, must be stored and shown as typed, never rendered in bold; the lesson on web security basics goes further.
  • Arabic-Indic digits such as ٠٥٠١٢٣٤٥٦٧ in a phone field: converted or refused with a clear message, as the requirement says. If it says nothing, ask.

Try it: Validation Test Generator Try it: Negative Test Generator Try it: Unicode Dataset Generator

Error messages, submission and double submit

An error message is part of the expected result: it sits next to its field, says what to do ("Enter an email such as name@example.com", not "Invalid input"), keeps what the user typed and disappears once the field is fixed. Server errors must look like client errors and use the page's language; the browser's built-in messages follow the browser's language instead. The guide on accessible forms covers how errors are announced.

Submitting should do one thing once: progress is shown, success shows a confirmation and stores exactly one record, and a failure, including a lost connection, keeps the input. Pressing Enter in a single-line field usually submits too. RFC 9110 does not define POST as idempotent: two identical POST requests can have more effect than one, so the same form sent twice can create two records. With the network throttled in DevTools, try:

  • Double-click Submit, or press Enter twice.
  • Reload the confirmation page: the browser offers to send the data again. Many sites prevent this by redirecting after the POST to a page loaded with GET.
  • Go Back and submit again, or submit the same form in two tabs.
  • Expected: one record, one email. A disabled button stops most double clicks but not a reload, a second tab or a replayed request, so the server needs its own duplicate check.

Try it: Mock Error Payload Generator

Autofill and password managers

Many users never type: the browser or a password manager fills the form from saved data. The autocomplete attribute tells it what each field is, with tokens from the HTML standard such as name, email, tel and new-password; WCAG 2.2 success criterion 1.3.5 Identify Input Purpose expects them on fields that collect information about the user. Save test data in a separate browser profile, then check that:

  • Each value lands in the right field, not the full name in "First name".
  • Validation and the Submit button react to filled values: as in the lesson on JavaScript for testers, a check that listens only for key presses never runs after autofill.
  • Sign-in fields accept paste and password managers. Blocking them forces people to retype passwords from memory, which WCAG 2.2 success criterion 3.3.8 Accessible Authentication (Minimum) is meant to prevent.

Try it: Fake User Generator Try it: Fake Phone Number Generator

Worked example: an event registration form

Requirement: "To register for a free event, enter Full name (required, 2 to 60 characters), Email (required, one registration per email), Mobile (required, 05 and 8 digits), Company (optional) and Tickets (1 to 4, within the seats left). Register shows a reference number and sends an email." The browser can check presence, length, format and range; only the server knows which emails are registered and how many seats are left.

  • RF-01, positive: valid data → a reference number, one record, one email.
  • RF-02, positive: نورة القحطاني, nora+expo@example.com, no company → accepted and shown as entered.
  • RF-03, negative, browser: Register with every field empty → each required field reported, and the Network panel shows no request.
  • RF-04, negative, server: the request replayed with "name": " " and "tickets": 5 → 422, both fields named, nothing saved.
  • RF-05, negative, server only: an email that is already registered → a message under Email, the other input kept.
  • RF-06, double submit: double-click Register on a throttled connection → one record, one email.
  • RF-07, autofill: a saved phone +966501234567 → stored as 0501234567, or refused with a format message; the requirement must say which.

Common mistakes

  • Testing validation only through the form and never checking that the server enforces the same rules.
  • Trusting the message on screen instead of checking the response and the stored data.
  • Using only tidy test data: no apostrophes, no Arabic text, no + in emails, no pasted values.
  • Stopping at the first message of an empty submit instead of checking that every missing field is reported.
  • Clicking Submit once on a fast connection and concluding that double submits cannot happen.
  • Never trying autofill or a password manager, although many real users fill forms that way.

The QA perspective

Form testing starts in refinement. For each field, ask for the rule, who enforces it (browser, server or both), the exact message, and what happens on success, on failure and on a second submit. Rules that live only in a developer's head become mismatches between the page and the server.

Keep a field table as test data: one row per field, with valid and invalid values and the layer that checks each rule; later it feeds automated checks. In a bug report, name the layer: "the page refuses + in emails, the API accepts it" sends the developer to the right code. Bypass validation and test double submits only on environments you are allowed to test, with test accounts.

Practice exercise

Plan the tests for a support request form

Your task

A support page has the form below. Requirement: "Name and Email are required. Order number is optional; if given, it is ORD- and 6 digits. Message is required, 20 to 1,000 characters. One screenshot, PNG or JPG up to 5 MB, may be attached. Send creates one ticket and shows its number." Compare the markup with the requirement, then plan the tests.

When your list is ready, compare it with the output of the Validation Test Generator.

Material
<form action="/support" method="post">
  <label for="name">Name *</label>
  <input id="name" name="name" required>
  <label for="email">Email *</label>
  <input id="email" name="email" type="text" required>
  <label for="order">Order number</label>
  <input id="order" name="order" pattern="ORD-[0-9]{5}">
  <label for="msg">Message *</label>
  <textarea id="msg" name="message" minlength="20" maxlength="2000"></textarea>
  <label for="shot">Screenshot</label>
  <input id="shot" name="shot" type="file" accept=".png,.jpg,.pdf">
  <button>Send</button>
</form>
Steps
  1. List the mismatches between the markup and the requirement.
  2. For each field, write positive and negative inputs and the layer that must refuse each negative one.
  3. Add tests for error messages, double submit and autofill.
  4. Write your questions for the product owner.
Show the model answer
Model answer

Mismatches: Email is type="text", so the browser checks no format. The order pattern asks for 5 digits, not 6. Message has no required, and maxlength="2000" is twice the limit. accept adds .pdf, and it only filters the file picker anyway. Beyond the requirement, Name and Email have no autocomplete tokens. HTML cannot limit file size, so 5 MB is the server's job in any case.

Positive: a name with an apostrophe and an Arabic name; sara+help@example.com; no order number; ORD-123456, which the wrong pattern now refuses, a defect; messages of 20 and of 1,000 characters; a JPG and a PNG of exactly 5 MB. Negative, refused by the browser and the server: an empty Name or Email, a message of 19 characters. Refused only by the server, because this page lets them through: sara@, ORD-12345, an empty message, 20 spaces, 1,001 characters, a PDF, an image one byte over 5 MB, a text file renamed to .png.

Also: each error appears under its field and the other input is kept; double-click Send and reload the confirmation page on a throttled connection, expecting one ticket; once autocomplete="name" and autocomplete="email" are added, autofill fills Name and Email.

Questions: are spaces around values trimmed? How are characters counted in Arabic text and emoji? Does 5 MB mean 5,000,000 or 5,242,880 bytes, and does a .jpeg file count as JPG? Is the same message sent from a second tab a new ticket or a duplicate? What does the user see when the upload fails?

Frequently asked questions

If the server checks everything, why test client-side validation at all?

Because it is part of the user experience. Client-side checks give immediate, specific feedback without a round trip to the server. If they are wrong, they block valid users before the server is even asked, or let mistakes through to a slower and vaguer server error. Each layer has its own job, so test both.

Is going around the browser a security test?

It is the basic functional check that the server enforces its own rules, and every tester should do it on systems they are allowed to test. Trying to break the server on purpose, for example with injection payloads, is security testing: it comes later in the roadmap and needs written permission.

How do I test autofill without using my own data?

Create a separate browser profile and save test data in it: a fake name, an email address on a reserved domain such as example.com, and a test phone number. The Fake User Generator and the Fake Phone Number Generator produce such data. Never use your real personal or payment details on a test system.

Does disabling the Submit button prevent double submits?

Only partly. It stops a second click on the same page, but not a reload of the POST, the same form in a second tab or a replayed request. The reliable protection is on the server, for example a check that refuses a second identical registration. Test both.

References

  • HTML Living StandardWHATWGThe HTML language, including named character references (entities) and the parsing rules browsers follow.
  • OWASP Cheat Sheet Series: Input ValidationOWASP FoundationAllow-list validation, syntactic vs. semantic checks, and why client-side validation is never a security control.
  • RFC 9110: HTTP SemanticsIETF · 2022The meaning of HTTP methods, header fields, status codes and content negotiation, independent of the wire version.
  • Client-side form validation — MDNMDN Web Docs (Mozilla)Built-in and scripted form validation in the browser, and why it helps users but never replaces validation on the server.

Practise with Mutqan tools

Generate validation test cases for form fields: required, format, length, range, pattern and cross-field rules.

Test Design

Generate negative test cases for form fields and APIs: empty, null, wrong type, too long, injection, invalid format.

Test Design

Parse a cURL command into method, URL, headers and body, then convert it to fetch, axios, Python or a structured request.

Web

Compose and send HTTP requests (method, URL, headers, auth, body) and inspect the response — a lightweight Postman in your browser.

API Testing

Generate Unicode test strings across scripts (Arabic, Devanagari, CJK, Cyrillic), emoji, combining marks and normalisation forms.

Test Data

Generate fake user records (name, username, email, password, avatar seed, role) for test accounts.

Test Data

Go deeper

Your progress on this topic

Part of the QA Roadmap