Skip to content

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:

scripts/contact-form.ts
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:

scripts/complex-form.ts
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:

scripts/form-validation.ts
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:

scripts/file-upload.ts
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 example
const 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:

scripts/dynamic-form.ts
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:

scripts/autocomplete-form.ts
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:

scripts/date-picker.ts
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

Terminal window
# Run contact form example
npx tsx scripts/contact-form.ts
# Run with visible browser
npx stepwright run scripts/complex-form.ts --headed
# Run with screenshots
npx stepwright run scripts/form-validation.ts --screenshots

Key Takeaways

  1. Use specific selectors for form fields (#id, [name="field"])
  2. Handle different input types appropriately (fill, check, selectOption, setInputFiles)
  3. Wait for validation feedback after form interactions
  4. Take screenshots before and after submission
  5. Use checkpoints to group related form sections

Next Steps