Form Testing

Test form submission and validation using Playwright with LoadForge

LoadForge can record your browser, graphically build tests, scan your site with a wizard and more. Sign up now to run your first test.

Sign up now


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

  1. Use Faker: Generate realistic test data with the Faker library
  2. Test Edge Cases: Empty forms, invalid data, boundary values
  3. Accessibility: Test form navigation with keyboard-only interaction
  4. Mobile Forms: Test form usability on different screen sizes
  5. Validation Messages: Verify error messages are clear and helpful
  6. 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.

Ready to run your test?
LoadForge is cloud-based locust.io testing.