/** * Smart Form Filler helper for cPad tests. * * Detects all interactive form controls on a page (or within a locator scope) * and fills them with sensible test values so form-validation tests do not * need to enumerate every field manually. * * Supported field types: * text, email, number, url, tel, search, password, date, time, textarea, * checkbox, radio, select, file (skipped) * * Hidden fields and readonly fields are ignored. */ import { type Locator, type Page } from '@playwright/test'; /** Seed text used for text-like inputs */ const RANDOM_TEXT = `Test_${Date.now()}`; const RANDOM_EMAIL = 'playwright-test@test.com'; const RANDOM_NUM = '42'; const RANDOM_URL = 'https://skinbase.test'; const RANDOM_DATE = '2025-01-01'; const RANDOM_TIME = '09:00'; const RANDOM_TEXTAREA = `Automated test content generated at ${new Date().toISOString()}.`; /** * Fill all detectable form controls inside `scope` (defaults to the whole page). * * @param page Playwright Page object (used for evaluate calls) * @param scope Optional Locator to restrict filling to a specific container */ export async function fillForm(page: Page, scope?: Locator): Promise { const root: Page | Locator = scope ?? page; // ── text / email / url / tel / search / password / date / time ────────── const textInputs = root.locator( 'input:not([type=hidden]):not([type=submit]):not([type=button])' + ':not([type=reset]):not([type=file]):not([type=checkbox]):not([type=radio])' + ':not([readonly]):not([disabled])', ); const inputCount = await textInputs.count(); for (let i = 0; i < inputCount; i++) { const input = textInputs.nth(i); const type = (await input.getAttribute('type'))?.toLowerCase() ?? 'text'; const name = (await input.getAttribute('name')) ?? ''; // Skip honeypot / internal fields whose names suggest they must stay empty if (/honeypot|_token|csrf/i.test(name)) continue; try { await input.scrollIntoViewIfNeeded(); switch (type) { case 'email': await input.fill(RANDOM_EMAIL); break; case 'number': case 'range': await input.fill(RANDOM_NUM); break; case 'url': await input.fill(RANDOM_URL); break; case 'date': await input.fill(RANDOM_DATE); break; case 'time': await input.fill(RANDOM_TIME); break; case 'password': await input.fill(`Pw@${Date.now()}`); break; default: await input.fill(RANDOM_TEXT); } } catch { // Field might have become detached or invisible — skip silently } } // ── textarea ───────────────────────────────────────────────────────────── const textareas = root.locator('textarea:not([readonly]):not([disabled])'); const taCount = await textareas.count(); for (let i = 0; i < taCount; i++) { try { await textareas.nth(i).scrollIntoViewIfNeeded(); await textareas.nth(i).fill(RANDOM_TEXTAREA); } catch { /* skip */ } } // ── select ──────────────────────────────────────────────────────────────── const selects = root.locator('select:not([disabled])'); const selCount = await selects.count(); for (let i = 0; i < selCount; i++) { try { // Pick the first non-empty option const firstOption = await selects.nth(i).locator('option:not([value=""])').first().getAttribute('value'); if (firstOption !== null) { await selects.nth(i).selectOption(firstOption); } } catch { /* skip */ } } // ── checkboxes (toggle unchecked → checked) ─────────────────────────────── const checkboxes = root.locator('input[type=checkbox]:not([disabled])'); const cbCount = await checkboxes.count(); for (let i = 0; i < cbCount; i++) { try { const isChecked = await checkboxes.nth(i).isChecked(); if (!isChecked) { await checkboxes.nth(i).check(); } } catch { /* skip */ } } } /** * Detect whether the given scope contains a visible, submittable form. * Returns true if any
, submit button, or text input is found. */ export async function hasForm(scope: Page | Locator): Promise { const formCount = await scope.locator('form').count(); const submitCount = await scope.locator('button[type=submit], input[type=submit]').count(); const inputCount = await scope.locator('input:not([type=hidden])').count(); return formCount > 0 || submitCount > 0 || inputCount > 0; }