UNPKG

chromancer

Version:

A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.

309 lines (308 loc) 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@oclif/core"); const base_js_1 = require("../base.js"); const fs_1 = require("fs"); class Fill extends base_js_1.BaseCommand { static description = 'Intelligently fill forms with auto-generated test data, JSON input, or smart field detection - handles all input types with submit support'; static examples = [ '<%= config.bin %> <%= command.id %> --data \'{"username": "john", "email": "john@example.com"}\'', '<%= config.bin %> <%= command.id %> --file form-data.json', '<%= config.bin %> <%= command.id %> --selector "#login-form" --data \'{"username": "admin", "password": "secret"}\'', '<%= config.bin %> <%= command.id %> --auto-generate --submit', ]; static flags = { ...base_js_1.BaseCommand.baseFlags, data: core_1.Flags.string({ char: 'd', description: 'JSON data to fill forms with', exclusive: ['file', 'auto-generate'], }), file: core_1.Flags.string({ char: 'f', description: 'JSON file containing form data', exclusive: ['data', 'auto-generate'], }), selector: core_1.Flags.string({ char: 's', description: 'CSS selector for specific form', }), 'auto-generate': core_1.Flags.boolean({ description: 'Generate random test data for all fields', exclusive: ['data', 'file'], }), submit: core_1.Flags.boolean({ description: 'Submit the form after filling', default: false, }), 'wait-after': core_1.Flags.integer({ description: 'Wait time in ms after filling each field', default: 100, }), }; async run() { const { flags } = await this.parse(Fill); await this.connectToChrome(flags.port, flags.host, flags.launch, flags.profile, flags.headless, flags.verbose, flags.keepOpen); if (!this.page) { this.error('Failed to connect to Chrome'); } await this.fillForms(this.page); } async fillForms(page) { const { flags } = await this.parse(Fill); // Get form data let formData = {}; if (flags.data) { try { formData = JSON.parse(flags.data); } catch (error) { this.error('Invalid JSON data'); } } else if (flags.file) { try { const content = await fs_1.promises.readFile(flags.file, 'utf-8'); formData = JSON.parse(content); } catch (error) { this.error(`Failed to read file: ${flags.file}`); } } else if (flags['auto-generate']) { formData = await this.generateTestData(page, flags.selector); } else { this.error('No form data provided. Use --data, --file, or --auto-generate'); } // Fill the form const formSelector = flags.selector || 'form'; try { // Wait for form to be present await page.waitForSelector(formSelector, { timeout: 5000 }); // Fill each field for (const [key, value] of Object.entries(formData)) { const filled = await this.fillField(page, formSelector, key, value); if (filled && flags['wait-after'] > 0) { await page.waitForTimeout(flags['wait-after']); } } this.log('✅ Form filled successfully'); // Submit if requested if (flags.submit) { await this.submitForm(page, formSelector); } } catch (error) { this.error(`Failed to fill form: ${error.message}`); } } async fillField(page, formSelector, key, value) { const { flags } = await this.parse(Fill); // Try different strategies to find the field const selectors = [ `${formSelector} [name="${key}"]`, `${formSelector} #${key}`, `${formSelector} [id="${key}"]`, `${formSelector} [data-field="${key}"]`, `${formSelector} input[placeholder*="${key}" i]`, `${formSelector} label:has-text("${key}") input`, ]; for (const selector of selectors) { try { const element = await page.$(selector); if (!element) continue; const tagName = await element.evaluate(el => el.tagName); const type = await element.evaluate(el => el.type); if (tagName === 'INPUT') { if (type === 'checkbox' || type === 'radio') { if (value === true || value === 'true' || value === '1') { await element.check(); if (flags.verbose) { this.logVerbose(`Checked: ${key}`); } } } else if (type === 'file') { if (typeof value === 'string') { await element.setInputFiles(value); if (flags.verbose) { this.logVerbose(`Set file: ${key} = ${value}`); } } } else { await element.fill(String(value)); if (flags.verbose) { this.logVerbose(`Filled: ${key} = ${value}`); } } return true; } else if (tagName === 'SELECT') { if (Array.isArray(value)) { await element.selectOption(value); } else { await element.selectOption(String(value)); } if (flags.verbose) { this.logVerbose(`Selected: ${key} = ${value}`); } return true; } else if (tagName === 'TEXTAREA') { await element.fill(String(value)); if (flags.verbose) { this.logVerbose(`Filled textarea: ${key}`); } return true; } } catch (error) { // Try next selector continue; } } this.warn(`Could not find field: ${key}`); return false; } async generateTestData(page, formSelector) { const selector = formSelector || 'form'; return await page.evaluate((sel) => { const form = document.querySelector(sel); if (!form) throw new Error(`Form not found: ${sel}`); const data = {}; // Generate data based on field types and names const generateValue = (element) => { const name = element.name || element.id; const type = element.type || element.tagName.toLowerCase(); // Generate based on common field names if (/email/i.test(name)) return 'test@example.com'; if (/phone|tel/i.test(name)) return '+1234567890'; if (/name/i.test(name)) { if (/first/i.test(name)) return 'John'; if (/last/i.test(name)) return 'Doe'; return 'John Doe'; } if (/user/i.test(name)) return 'testuser'; if (/pass/i.test(name)) return 'TestPass123!'; if (/age/i.test(name)) return '25'; if (/date/i.test(name)) return new Date().toISOString().split('T')[0]; if (/time/i.test(name)) return '12:00'; if (/url|website/i.test(name)) return 'https://example.com'; if (/zip|postal/i.test(name)) return '12345'; if (/address/i.test(name)) return '123 Test Street'; if (/city/i.test(name)) return 'Test City'; if (/state/i.test(name)) return 'CA'; if (/country/i.test(name)) return 'United States'; if (/message|comment|description|bio/i.test(name)) return 'This is a test message.'; // Generate based on input type switch (type) { case 'email': return 'test@example.com'; case 'tel': return '+1234567890'; case 'url': return 'https://example.com'; case 'number': return '42'; case 'date': return new Date().toISOString().split('T')[0]; case 'time': return '12:00'; case 'datetime-local': return new Date().toISOString().slice(0, 16); case 'month': return new Date().toISOString().slice(0, 7); case 'week': return new Date().toISOString().split('T')[0]; case 'color': return '#3498db'; case 'range': return '50'; case 'checkbox': return true; case 'radio': return true; case 'select': case 'select-one': const options = element.options; if (options.length > 1) { return options[1].value; // Skip first option (often empty) } return options[0]?.value; case 'select-multiple': const opts = Array.from(element.options); return opts.slice(1, 3).map(o => o.value); // Select first two non-empty options case 'textarea': return 'This is test content for the textarea field.'; default: return 'Test Value'; } }; // Find all form fields form.querySelectorAll('input, select, textarea').forEach((field) => { const element = field; const name = element.name || element.id; if (!name) return; // Skip hidden, submit, button, reset fields if (element.type === 'hidden' || element.type === 'submit' || element.type === 'button' || element.type === 'reset') { return; } // Skip if already has data for radio buttons if (element.type === 'radio' && data[name]) { return; } data[name] = generateValue(element); }); return data; }, selector); } async submitForm(page, formSelector) { this.log('📤 Submitting form...'); // Try to find and click submit button const submitSelectors = [ `${formSelector} button[type="submit"]`, `${formSelector} input[type="submit"]`, `${formSelector} button:has-text("Submit")`, `${formSelector} button:has-text("Send")`, `${formSelector} button:has-text("Save")`, `${formSelector} button:has-text("Continue")`, ]; for (const selector of submitSelectors) { try { const button = await page.$(selector); if (button) { await button.click(); this.log('✅ Form submitted'); return; } } catch (error) { // Try next selector } } // Fallback: submit via Enter key try { await page.evaluate((sel) => { const form = document.querySelector(sel); if (form) { form.submit(); } }, formSelector); this.log('✅ Form submitted'); } catch (error) { this.warn('Could not find submit button'); } } } exports.default = Fill;