Form Submission
Form Submission
This example covers common form handling patterns including text inputs, selections, checkboxes, file uploads, and form validation.
Simple Contact Form
Basic form filling and submission:
import { Stepwright } from '@korvol/stepwright';
interface ContactFormData { name: string; email: string; message: string; submitted: boolean;}
const script = Stepwright.create<ContactFormData>('Contact Form') .config({ headless: true, screenshotOnFailure: true, }) .data({ name: 'John Doe', email: 'john@example.com', message: 'Hello, this is a test message.', submitted: false, })
.checkpoint('Fill Form')
.step('Navigate to contact page', async (ctx) => { await ctx.page.goto('https://example.com/contact'); await ctx.page.waitForSelector('form#contact-form'); ctx.log('Contact form loaded'); })
.step('Fill name field', async (ctx) => { await ctx.page.fill('#name', ctx.data.name); ctx.log('Name filled:', ctx.data.name); })
.step('Fill email field', async (ctx) => { await ctx.page.fill('#email', ctx.data.email); ctx.log('Email filled:', ctx.data.email); })
.step('Fill message field', async (ctx) => { await ctx.page.fill('#message', ctx.data.message); ctx.log('Message filled'); })
.endCheckpoint()
.checkpoint('Submit Form')
.step('Submit form', async (ctx) => { await ctx.page.click('button[type="submit"]');
// Wait for success message await ctx.page.waitForSelector('.success-message');
ctx.data.submitted = true; ctx.log('Form submitted successfully'); })
.step('Verify confirmation', async (ctx) => { const confirmation = await ctx.page.textContent('.success-message'); ctx.log('Confirmation:', confirmation);
await ctx.screenshot('form-submitted'); })
.endCheckpoint();
const result = await script.run();
if (result.success) { console.log('Form submitted:', result.data.submitted);}Complex Form with Multiple Input Types
Handle various form controls:
import { Stepwright } from '@korvol/stepwright';
interface RegistrationData { firstName: string; lastName: string; email: string; password: string; country: string; interests: string[]; newsletter: boolean; termsAccepted: boolean;}
const script = Stepwright.create<RegistrationData>('Registration Form') .config({ headless: true, defaultTimeout: 30000, }) .data({ firstName: 'Jane', lastName: 'Smith', email: 'jane.smith@example.com', password: 'SecurePass123!', country: 'US', interests: ['technology', 'design'], newsletter: true, termsAccepted: true, })
.checkpoint('Personal Info')
.step('Navigate to registration', async (ctx) => { await ctx.page.goto('https://example.com/register'); await ctx.page.waitForSelector('form#registration'); })
.step('Fill text inputs', async (ctx) => { await ctx.page.fill('#firstName', ctx.data.firstName); await ctx.page.fill('#lastName', ctx.data.lastName); await ctx.page.fill('#email', ctx.data.email); await ctx.page.fill('#password', ctx.data.password);
ctx.log('Text fields filled'); })
.endCheckpoint()
.checkpoint('Selections')
.step('Select country from dropdown', async (ctx) => { // Standard select element await ctx.page.selectOption('#country', ctx.data.country); ctx.log('Country selected:', ctx.data.country); })
.step('Select interests (multi-select)', async (ctx) => { // Multi-select by clicking checkboxes for (const interest of ctx.data.interests) { await ctx.page.check(`input[name="interests"][value="${interest}"]`); ctx.log('Interest checked:', interest); } })
.step('Set newsletter preference', async (ctx) => { // Toggle checkbox based on data const checkbox = ctx.page.locator('#newsletter'); const isChecked = await checkbox.isChecked();
if (ctx.data.newsletter && !isChecked) { await checkbox.check(); } else if (!ctx.data.newsletter && isChecked) { await checkbox.uncheck(); }
ctx.log('Newsletter:', ctx.data.newsletter); })
.step('Accept terms', async (ctx) => { if (ctx.data.termsAccepted) { await ctx.page.check('#terms'); ctx.log('Terms accepted'); } })
.endCheckpoint()
.checkpoint('Submit')
.step('Submit registration', async (ctx) => { await ctx.screenshot('before-submit');
await ctx.page.click('#submit-registration');
// Wait for redirect to welcome page await ctx.page.waitForURL('**/welcome**');
ctx.log('Registration complete'); await ctx.screenshot('registration-complete'); })
.endCheckpoint();
const result = await script.run();console.log('Registration success:', result.success);Handling Form Validation
Test form validation behavior:
import { Stepwright } from '@korvol/stepwright';
interface ValidationTestData { validationErrors: string[];}
const script = Stepwright.create<ValidationTestData>('Form Validation') .config({ headless: true, }) .data({ validationErrors: [], })
.checkpoint('Test Empty Submit')
.step('Navigate to form', async (ctx) => { await ctx.page.goto('https://example.com/form'); })
.step('Submit empty form', async (ctx) => { await ctx.page.click('#submit');
// Wait for validation errors await ctx.page.waitForSelector('.error-message');
// Collect all error messages const errors = await ctx.page.locator('.error-message').allTextContents(); ctx.data.validationErrors = errors;
ctx.log('Validation errors:', errors.length); for (const error of errors) { ctx.log(' -', error); } })
.endCheckpoint()
.checkpoint('Test Invalid Email')
.step('Enter invalid email', async (ctx) => { await ctx.page.fill('#email', 'not-an-email'); await ctx.page.click('#submit');
// Check for email-specific error const emailError = await ctx.page.locator('#email-error').textContent(); ctx.log('Email error:', emailError);
// Verify error contains expected text if (!emailError?.includes('valid email')) { throw new Error('Expected email validation error'); } })
.step('Fix email and verify error clears', async (ctx) => { await ctx.page.fill('#email', 'valid@example.com');
// Trigger validation (blur event) await ctx.page.locator('#email').blur();
// Wait for error to disappear await ctx.page.waitForSelector('#email-error', { state: 'hidden' }); ctx.log('Email error cleared'); })
.endCheckpoint()
.checkpoint('Test Password Requirements')
.step('Test weak password', async (ctx) => { await ctx.page.fill('#password', '123'); await ctx.page.locator('#password').blur();
const passwordError = await ctx.page.locator('#password-error').textContent(); ctx.log('Password error:', passwordError); })
.step('Test strong password', async (ctx) => { await ctx.page.fill('#password', 'StrongPass123!'); await ctx.page.locator('#password').blur();
// Check password strength indicator const strength = await ctx.page.locator('.password-strength').textContent(); ctx.log('Password strength:', strength);
// Verify no error const hasError = await ctx.page.locator('#password-error').isVisible(); if (hasError) { throw new Error('Password should be valid'); } })
.endCheckpoint();File Upload
Handle file input elements:
import { Stepwright } from '@korvol/stepwright';import path from 'path';
interface UploadData { filePath: string; uploadedFileName: string;}
const script = Stepwright.create<UploadData>('File Upload') .config({ headless: true, }) .data({ filePath: path.join(__dirname, '../fixtures/test-file.pdf'), uploadedFileName: '', })
.step('Navigate to upload page', async (ctx) => { await ctx.page.goto('https://example.com/upload'); })
.step('Upload file', async (ctx) => { // Standard file input const fileInput = ctx.page.locator('input[type="file"]'); await fileInput.setInputFiles(ctx.data.filePath);
ctx.log('File selected:', ctx.data.filePath); })
.step('Wait for upload and verify', async (ctx) => { // Wait for upload indicator await ctx.page.waitForSelector('.upload-progress', { state: 'hidden' });
// Get uploaded file name from UI const fileName = await ctx.page.locator('.uploaded-file-name').textContent(); ctx.data.uploadedFileName = fileName || '';
ctx.log('Uploaded:', ctx.data.uploadedFileName); await ctx.screenshot('file-uploaded'); })
.step('Submit form with file', async (ctx) => { await ctx.page.click('#submit-upload'); await ctx.page.waitForSelector('.upload-success');
ctx.log('Upload submitted successfully'); });
// Multiple file upload exampleconst multiUploadScript = Stepwright.create('Multiple File Upload') .step('Upload multiple files', async (ctx) => { await ctx.page.goto('https://example.com/multi-upload');
const fileInput = ctx.page.locator('input[type="file"][multiple]'); await fileInput.setInputFiles([ './fixtures/file1.pdf', './fixtures/file2.pdf', './fixtures/file3.pdf', ]);
ctx.log('Multiple files selected'); });Dynamic Form Elements
Handle dynamically generated form fields:
import { Stepwright } from '@korvol/stepwright';
interface DynamicFormData { productCount: number; products: Array<{ name: string; quantity: number }>;}
const script = Stepwright.create<DynamicFormData>('Dynamic Form') .config({ headless: true, }) .data({ productCount: 3, products: [ { name: 'Widget A', quantity: 5 }, { name: 'Widget B', quantity: 10 }, { name: 'Widget C', quantity: 2 }, ], })
.step('Navigate to order form', async (ctx) => { await ctx.page.goto('https://example.com/order'); })
.step('Add product rows', async (ctx) => { // Form starts with 1 row, we need productCount rows for (let i = 1; i < ctx.data.productCount; i++) { await ctx.page.click('#add-product');
// Wait for new row to appear await ctx.page.waitForSelector(`#product-row-${i}`); ctx.log('Added row:', i); } })
.step('Fill product details', async (ctx) => { for (let i = 0; i < ctx.data.products.length; i++) { const product = ctx.data.products[i];
// Dynamic selectors based on row index await ctx.page.fill(`#product-name-${i}`, product.name); await ctx.page.fill(`#product-qty-${i}`, product.quantity.toString());
ctx.log(`Filled product ${i}:`, product.name); } })
.step('Remove a product row', async (ctx) => { // Click remove button on second row await ctx.page.click('#product-row-1 .remove-btn');
// Wait for row to be removed await ctx.page.waitForSelector('#product-row-1', { state: 'detached' });
ctx.log('Removed product row'); })
.step('Submit order', async (ctx) => { await ctx.screenshot('before-order-submit'); await ctx.page.click('#submit-order');
await ctx.page.waitForSelector('.order-confirmation'); ctx.log('Order submitted'); });Autocomplete and Search Forms
Handle autocomplete inputs:
import { Stepwright } from '@korvol/stepwright';
const script = Stepwright.create('Autocomplete Form') .config({ headless: true, })
.step('Navigate to search', async (ctx) => { await ctx.page.goto('https://example.com/search'); })
.step('Use autocomplete', async (ctx) => { const searchInput = ctx.page.locator('#search-input');
// Type slowly to trigger autocomplete await searchInput.pressSequentially('type', { delay: 100 });
// Wait for autocomplete dropdown await ctx.page.waitForSelector('.autocomplete-dropdown');
// Get suggestions const suggestions = await ctx.page .locator('.autocomplete-item') .allTextContents(); ctx.log('Suggestions:', suggestions);
// Click first suggestion await ctx.page.locator('.autocomplete-item').first().click();
// Verify selection const selectedValue = await searchInput.inputValue(); ctx.log('Selected:', selectedValue); })
.step('Clear and try another', async (ctx) => { const searchInput = ctx.page.locator('#search-input');
// Clear input await searchInput.clear();
// Type different query await searchInput.fill('script');
// Press Enter to search without using autocomplete await ctx.page.keyboard.press('Enter');
// Wait for results await ctx.page.waitForSelector('.search-results'); ctx.log('Search completed'); });Date Picker Forms
Handle date picker components:
import { Stepwright } from '@korvol/stepwright';
interface BookingData { checkIn: string; checkOut: string;}
const script = Stepwright.create<BookingData>('Date Picker') .config({ headless: true, }) .data({ checkIn: '2025-03-15', checkOut: '2025-03-20', })
.step('Navigate to booking', async (ctx) => { await ctx.page.goto('https://example.com/booking'); })
.step('Set check-in date', async (ctx) => { // For native date input await ctx.page.fill('input[type="date"]#check-in', ctx.data.checkIn);
// Or for custom date picker - click to open await ctx.page.click('#check-in-trigger'); await ctx.page.waitForSelector('.date-picker-popup');
// Navigate to correct month/year if needed // Click specific date await ctx.page.click(`.date-cell[data-date="${ctx.data.checkIn}"]`);
ctx.log('Check-in set:', ctx.data.checkIn); })
.step('Set check-out date', async (ctx) => { await ctx.page.click('#check-out-trigger'); await ctx.page.waitForSelector('.date-picker-popup');
await ctx.page.click(`.date-cell[data-date="${ctx.data.checkOut}"]`);
ctx.log('Check-out set:', ctx.data.checkOut); })
.step('Verify date range', async (ctx) => { const checkIn = await ctx.page.locator('#check-in-display').textContent(); const checkOut = await ctx.page.locator('#check-out-display').textContent();
ctx.log('Selected range:', checkIn, 'to', checkOut); });Running the Examples
# Run contact form examplenpx tsx scripts/contact-form.ts
# Run with visible browsernpx stepwright run scripts/complex-form.ts --headed
# Run with screenshotsnpx stepwright run scripts/form-validation.ts --screenshotsKey Takeaways
- Use specific selectors for form fields (
#id,[name="field"]) - Handle different input types appropriately (fill, check, selectOption, setInputFiles)
- Wait for validation feedback after form interactions
- Take screenshots before and after submission
- Use checkpoints to group related form sections
Next Steps
- Authentication - Login and session handling
- Data Extraction - Extract form data
- Best Practices - Form automation tips