128 lines
4.8 KiB
TypeScript
128 lines
4.8 KiB
TypeScript
/**
|
|
* 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<void> {
|
|
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 <form>, submit button, or text input is found.
|
|
*/
|
|
export async function hasForm(scope: Page | Locator): Promise<boolean> {
|
|
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;
|
|
}
|