Skip to main content
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.
I