This guide demonstrates how to test form submission and validation using Playwright in LoadForge. Perfect for testing user registration, login forms, contact forms, and complex multi-step forms.
Use Cases
- Testing user registration and login flows
- Validating form field validation rules
- Testing multi-step form processes
- Checking form error handling and success states
Key Features
- Form Field Testing: Fill various input types (text, email, password, select)
- Validation Testing: Test client-side and server-side validation
- Error Handling: Test form error states and messages
- Success Flow Testing: Validate successful form submissions
Example Implementation
from locust import task
from locust_plugins.users.playwright import PlaywrightUser, PageWithRetry, pw, event
from faker import Faker
fake = Faker()
class FormTestingUser(PlaywrightUser):
@task(3)
@pw
async def login_form_test(self, page: PageWithRetry):
"""Test login form submission and validation"""
async with event(self, "Load Login Page"):
await page.goto("/login")
# Test valid login
await page.fill('input[name="email"]', 'test@example.com')
await page.fill('input[name="password"]', 'validpassword123')
async with event(self, "Valid Login Submit"):
async with page.expect_navigation():
await page.click('button:has-text("Log in")')
# Verify successful login
assert await page.locator('text=Dashboard').is_visible(), "Login failed"
@task(2)
@pw
async def registration_form_test(self, page: PageWithRetry):
"""Test user registration form with validation"""
async with event(self, "Load Registration Page"):
await page.goto("/register")
# Fill registration form with generated data
await page.fill('input[name="first_name"]', fake.first_name())
await page.fill('input[name="last_name"]', fake.last_name())
await page.fill('input[name="email"]', fake.email())
await page.fill('input[name="password"]', 'SecurePass123!')
await page.fill('input[name="password_confirmation"]', 'SecurePass123!')
# Select from dropdown
await page.select_option('select[name="country"]', 'US')
# Check terms checkbox
await page.check('input[name="terms"]')
async with event(self, "Registration Submit"):
async with page.expect_navigation():
await page.click('button:has-text("Register")')
# Verify registration success
success_msg = await page.locator('.success-message').is_visible()
assert success_msg, "Registration did not show success message"
@task(2)
@pw
async def contact_form_test(self, page: PageWithRetry):
"""Test contact form with file upload"""
async with event(self, "Load Contact Page"):
await page.goto("/contact")
# Fill contact form
await page.fill('input[name="name"]', fake.name())
await page.fill('input[name="email"]', fake.email())
await page.fill('input[name="subject"]', 'Test Subject')
await page.fill('textarea[name="message"]', fake.text(max_nb_chars=200))
# Select inquiry type
await page.select_option('select[name="inquiry_type"]', 'support')
async with event(self, "Contact Form Submit"):
await page.click('button:has-text("Send Message")')
# Wait for success message without navigation
await page.wait_for_selector('.alert-success', timeout=5000)
@task(1)
@pw
async def form_validation_test(self, page: PageWithRetry):
"""Test form validation errors"""
async with event(self, "Load Form for Validation"):
await page.goto("/register")
# Test email validation
await page.fill('input[name="email"]', 'invalid-email')
await page.fill('input[name="password"]', '123') # Too short
async with event(self, "Submit Invalid Form"):
await page.click('button:has-text("Register")')
# Check for validation errors
email_error = await page.locator('.error:has-text("email")').is_visible()
password_error = await page.locator('.error:has-text("password")').is_visible()
assert email_error or password_error, "Form validation errors not displayed"
@task(1)
@pw
async def multi_step_form_test(self, page: PageWithRetry):
"""Test multi-step form process"""
async with event(self, "Load Multi-step Form"):
await page.goto("/onboarding")
# Step 1: Personal Info
await page.fill('input[name="first_name"]', fake.first_name())
await page.fill('input[name="last_name"]', fake.last_name())
await page.fill('input[name="phone"]', fake.phone_number())
async with event(self, "Step 1 Complete"):
await page.click('button:has-text("Next")')
# Step 2: Address Info
await page.fill('input[name="address"]', fake.address())
await page.fill('input[name="city"]', fake.city())
await page.fill('input[name="zip"]', fake.zipcode())
await page.select_option('select[name="state"]', 'CA')
async with event(self, "Step 2 Complete"):
await page.click('button:has-text("Next")')
# Step 3: Preferences
await page.check('input[name="newsletter"]')
await page.select_option('select[name="language"]', 'en')
async with event(self, "Final Submit"):
await page.click('button:has-text("Complete")')
# Verify completion
await page.wait_for_selector('.onboarding-complete')
@task(1)
@pw
async def dynamic_form_test(self, page: PageWithRetry):
"""Test forms with dynamic fields"""
async with event(self, "Load Dynamic Form"):
await page.goto("/dynamic-form")
# Select option that shows additional fields
await page.select_option('select[name="user_type"]', 'business')
# Wait for dynamic fields to appear
await page.wait_for_selector('input[name="company_name"]')
# Fill dynamic fields
await page.fill('input[name="company_name"]', fake.company())
await page.fill('input[name="tax_id"]', '12-3456789')
# Fill regular fields
await page.fill('input[name="email"]', fake.email())
async with event(self, "Dynamic Form Submit"):
await page.click('button:has-text("Submit")')
# Verify submission
await page.wait_for_selector('.form-success')
Form Field Strategies
1. Text Input Fields
await page.fill('input[name="username"]', 'testuser')
await page.fill('input[type="email"]', 'test@example.com')
await page.fill('textarea[name="description"]', 'Long text content')
2. Select Dropdowns
# By value
await page.select_option('select[name="country"]', 'US')
# By label
await page.select_option('select[name="country"]', label='United States')
# Multiple selections
await page.select_option('select[name="skills"]', ['python', 'javascript'])
3. Checkboxes and Radio Buttons
# Check/uncheck
await page.check('input[name="terms"]')
await page.uncheck('input[name="newsletter"]')
# Radio buttons
await page.click('input[name="plan"][value="premium"]')
4. File Uploads
# Single file
await page.set_input_files('input[type="file"]', 'test-file.pdf')
# Multiple files
await page.set_input_files('input[type="file"]', ['file1.pdf', 'file2.jpg'])
Validation Testing Patterns
Client-Side Validation
@task
@pw
async def client_validation_test(self, page: PageWithRetry):
await page.goto("/form")
# Test required field validation
await page.click('button[type="submit"]')
# Check for HTML5 validation
is_valid = await page.evaluate("""
document.querySelector('form').checkValidity()
""")
assert not is_valid, "Form should be invalid when required fields are empty"
Server-Side Validation
@task
@pw
async def server_validation_test(self, page: PageWithRetry):
await page.goto("/form")
# Submit invalid data
await page.fill('input[name="email"]', 'existing@example.com') # Duplicate email
await page.click('button[type="submit"]')
# Wait for server validation error
await page.wait_for_selector('.field-error:has-text("already exists")')
Advanced Form Testing
CSRF Token Handling
@task
@pw
async def csrf_protected_form(self, page: PageWithRetry):
await page.goto("/protected-form")
# CSRF token is automatically included in form
await page.fill('input[name="data"]', 'test data')
await page.click('button[type="submit"]')
# Verify successful submission
await page.wait_for_selector('.success-message')
Form with AJAX Submission
@task
@pw
async def ajax_form_test(self, page: PageWithRetry):
await page.goto("/ajax-form")
await page.fill('input[name="email"]', fake.email())
# Click submit but don't expect navigation
await page.click('button[type="submit"]')
# Wait for AJAX response
await page.wait_for_selector('.response-message')
# Check if form was reset
email_value = await page.input_value('input[name="email"]')
assert email_value == '', "Form should be reset after successful AJAX submission"
Error Handling Patterns
Network Error Testing
@task
@pw
async def network_error_test(self, page: PageWithRetry):
await page.goto("/form")
# Simulate network failure
await page.route('**/api/submit', lambda route: route.abort())
await page.fill('input[name="data"]', 'test')
await page.click('button[type="submit"]')
# Check error handling
await page.wait_for_selector('.network-error')
Performance Considerations
- Use
event()
to measure form submission times
- Test forms under different network conditions
- Validate that large forms don't cause performance issues
- Test file upload performance with various file sizes
Testing Tips
- Use Faker: Generate realistic test data with the Faker library
- Test Edge Cases: Empty forms, invalid data, boundary values
- Accessibility: Test form navigation with keyboard-only interaction
- Mobile Forms: Test form usability on different screen sizes
- Validation Messages: Verify error messages are clear and helpful
- Form State: Test form behavior when navigating away and returning
This comprehensive form testing script covers various form scenarios and validation patterns commonly found in web applications.