Forms Are Where Bugs Live
Forms are the primary way users interact with web applications — login, registration, search, checkout, profile updates, contact forms. They are also where the most bugs live. Every form field is an entry point for invalid data, unexpected characters, and edge cases.
A thorough form tester does not just fill in valid data and click submit. They think about every possible input a user — or an attacker — might provide.
Anatomy of a Web Form
A typical form includes:
- Text inputs: Names, emails, addresses, free text
- Selectors: Dropdowns, radio buttons, checkboxes
- Special inputs: Date pickers, file uploads, color pickers, range sliders
- Buttons: Submit, reset, cancel
- Hidden fields: CSRF tokens, user IDs, tracking data
Each element type has its own class of bugs and test cases.
Client-Side vs. Server-Side Validation
Client-Side Validation
Happens in the browser using HTML attributes and JavaScript. It provides instant feedback but can be bypassed.
HTML validation attributes:
<input type="email" required minlength="5" maxlength="255" pattern="[a-z@.]+">
JavaScript validation:
if (password.length < 8) {
showError('Password must be at least 8 characters');
return false;
}
Server-Side Validation
Happens on the server after form submission. This is the security boundary — it cannot be bypassed by the user.
Critical testing rule: Always test server-side validation by bypassing client-side validation. In DevTools Console:
document.querySelector('form').noValidate = true;
// Or remove 'required' attributes:
document.querySelectorAll('[required]').forEach(el => el.removeAttribute('required'));
Then submit the form with invalid data. The server must still reject it.
Test Cases by Field Type
Text Fields
| Test Case | Input | Expected Result |
|---|---|---|
| Empty required field | (nothing) | Validation error |
| Minimum length | 1 character | Accept or reject per spec |
| Maximum length | Max + 1 characters | Reject or truncate |
| Special characters | <script>alert('xss')</script> | Sanitized, no script execution |
| Unicode characters | 名前 Ñoño Ünïcödë | Accept if international names supported |
| Leading/trailing spaces | John | Accept but trim, or reject per spec |
| Only spaces | | Reject (empty after trimming) |
| Very long input | 10,000+ characters | Handle gracefully, no crash |
| SQL injection | '; DROP TABLE users; -- | Safely handled, no SQL execution |
| HTML entities | & < ' | Displayed correctly |
| Emojis | John 🎉 Doe | Accept or reject consistently |
| Numbers in name field | John123 | Per specification |
| Null bytes | John\0Doe | Handle safely |
Email Fields
| Test Case | Input | Expected |
|---|---|---|
| Valid email | user@example.com | Accept |
| Missing @ | userexample.com | Reject |
| Missing domain | user@ | Reject |
| Missing local part | @example.com | Reject |
| Double dots | user@example..com | Reject |
| Subdomain | user@mail.example.com | Accept |
| Plus addressing | user+tag@example.com | Accept |
| Very long email | 255+ characters | Per RFC limit |
| International domain | user@例え.jp | Per spec |
| Case sensitivity | User@EXAMPLE.COM | Accept (emails are case-insensitive in domain) |
Password Fields
| Test Case | Input | Expected |
|---|---|---|
| Minimum length | 7 chars (if min is 8) | Reject with message |
| Maximum length | Very long (1000+ chars) | Accept or enforce max |
| No uppercase | password123! | Per policy |
| No number | Password!!! | Per policy |
| No special char | Password123 | Per policy |
| Common passwords | password123, 123456 | Reject if blocklist exists |
| Spaces in password | My Password 1! | Accept (spaces are valid) |
| Paste disabled? | (Paste from clipboard) | Should allow paste |
| Show/hide toggle | (Click eye icon) | Reveals/hides text |
Numeric Fields
Test boundaries carefully: minimum value, maximum value, min-1, max+1, zero, negative numbers, decimal numbers, non-numeric characters.
Form Submission Testing
Submit Button States
- Before interaction: Submit should be enabled (or disabled if specific fields are required)
- During submission: Button should be disabled to prevent double submission; show loading indicator
- After success: Redirect or show success message; form may clear
- After failure: Show error messages; preserve user input; re-enable submit button
Double Submission
Click the submit button rapidly multiple times. Does the form:
- Submit only once? (Correct)
- Submit multiple times, creating duplicate records? (Bug)
- Show a loading state that prevents re-clicking? (Good UX)
Advanced Form Testing
Tab Order and Keyboard Navigation
- Click in the first field and press Tab repeatedly
- Verify focus moves through fields in a logical order
- Verify you can submit the form using Enter
- Verify dropdown menus can be navigated with arrow keys
- Verify checkboxes toggle with Space
- Verify radio buttons switch with arrow keys
Autofill Testing
Browsers auto-fill forms using saved data. Test:
- Does the form accept autofilled values correctly?
- Are field names semantic enough for browsers to autofill appropriately? (
name="email",name="given-name", etc.) - Does autofill trigger client-side validation?
- Does the form break if autofill provides unexpected values?
Error Message Quality
Good error messages are:
- Specific: “Email must include an @ symbol” not “Invalid input”
- Located near the field: Inline errors, not just a list at the top
- Persistent: Remain visible until the error is fixed
- Accessible: Screen readers can announce them (use
aria-describedby) - Non-blaming: “Please enter a valid email” not “You entered an invalid email”
Multi-Step Form Testing
Wizard-style forms with multiple steps need additional testing:
- Can you navigate back to previous steps without losing data?
- Does validation happen per step or only at the end?
- What happens if you refresh mid-wizard?
- If you open the URL in a new tab, does it start from step 1 or remember progress?
- Can you deep-link to step 3 directly?
Exercise: Complete Form Audit
Find a registration or checkout form and test it using this checklist:
- Required fields: Submit the form with each required field empty, one at a time
- Boundary values: Test minimum and maximum lengths for every text field
- Special characters: Enter
<script>alert(1)</script>in every field - SQL injection: Enter
' OR 1=1 --in every field - Server-side validation: Bypass client-side validation in DevTools and resubmit
- Double submission: Click submit 5 times rapidly
- Tab order: Navigate the entire form with keyboard only
- Error messages: Trigger every validation error and document the message quality
- Autofill: Let the browser autofill and verify correctness
- Back button: Submit successfully, then press the browser back button
Document each finding:
| Field | Test | Result | Bug? |
|---|---|---|---|
SQL injection ' OR 1=1 -- | Server returns 500 error | Yes - Critical | |
| Name | 1000 characters | Accepted, but breaks profile layout | Yes - Medium |
| Password | Paste disabled | Cannot paste password | Yes - UX issue |
| All | Double submit | Two accounts created | Yes - Critical |
Pro Tips
Tip 1: Test form submission with network throttling. Slow connections expose timing bugs like double submission.
Tip 2: Check what the server actually receives. In the Network tab, inspect the request payload — the data sent might differ from what was displayed.
Tip 3: Test required field indicators. If a field shows a red asterisk (*), it must actually be required. Sometimes the visual indicator and the validation logic get out of sync.
Key Takeaways
- Forms are the highest-bug-density area of web applications
- Always test both client-side and server-side validation independently
- Boundary values, special characters, and empty fields are where most bugs hide
- Double submission prevention must be tested on every form
- Keyboard navigation and tab order affect both usability and accessibility
- Error messages should be specific, well-positioned, and accessible
- Bypassing client-side validation to test server-side is a critical security testing technique