@@ -211,11 +211,111 @@ export async function fillRadioGroup(page, radioGroupElement, clicksCount = 2, l
211211 }
212212}
213213
214+ /**
215+ * Fill a text input with an intentional mistake, trigger validation, then correct it.
216+ * Simulates real users making typos in email/URL fields and hitting validation errors.
217+ * @param {Page } page - Puppeteer page object
218+ * @param {ElementHandle } element - The input/textarea element
219+ * @param {string } text - Correct text to ultimately type
220+ * @param {Function } log - Logging function
221+ * @returns {Promise<boolean> } Success status
222+ */
223+ export async function fillTextInputWithMistake ( page , element , text = null , log = console . log ) {
224+ try {
225+ await element . scrollIntoViewIfNeeded ( ) ;
226+ await sleep ( randomBetween ( 100 , 300 ) ) ;
227+
228+ // Get element info to decide what kind of mistake to make
229+ const elementInfo = await page . evaluate (
230+ el => ( {
231+ type : el . type || 'text' ,
232+ placeholder : el . placeholder || '' ,
233+ tagName : el . tagName . toLowerCase ( )
234+ } ) ,
235+ element
236+ ) ;
237+
238+ // Use provided text or select from test data
239+ let correctText = text ;
240+ if ( ! correctText ) {
241+ const termType = [ 'email' , 'search' , 'password' , 'url' , 'tel' , 'number' ] . includes ( elementInfo . type )
242+ ? elementInfo . type
243+ : 'text' ;
244+ const availableTerms = formTestData [ termType ] || formTestData . text ;
245+ correctText = availableTerms [ Math . floor ( Math . random ( ) * availableTerms . length ) ] ;
246+ }
247+
248+ // Generate a mistake based on the field type
249+ let wrongText ;
250+ const mistakeType = Math . random ( ) ;
251+
252+ if ( elementInfo . type === 'email' && correctText . includes ( '@' ) ) {
253+ // Remove the @ from an email — triggers HTML5 validation
254+ wrongText = correctText . replace ( '@' , '' ) ;
255+ log ( ` └─ 🤦 <span style="color: #F8BC3B;">Intentional mistake:</span> missing @ in email` ) ;
256+ } else if ( elementInfo . type === 'url' && correctText . startsWith ( 'http' ) ) {
257+ // Drop the protocol — triggers validation
258+ wrongText = correctText . replace ( / ^ h t t p s ? : \/ \/ / , '' ) ;
259+ log ( ` └─ 🤦 <span style="color: #F8BC3B;">Intentional mistake:</span> missing protocol in URL` ) ;
260+ } else if ( elementInfo . type === 'tel' ) {
261+ // Add letters to a phone number
262+ wrongText = correctText . replace ( / \d { 2 } / , 'ab' ) ;
263+ log ( ` └─ 🤦 <span style="color: #F8BC3B;">Intentional mistake:</span> letters in phone number` ) ;
264+ } else if ( mistakeType < 0.5 ) {
265+ // Truncate the text (user didn't finish typing)
266+ wrongText = correctText . substring ( 0 , Math . max ( 2 , Math . floor ( correctText . length * 0.4 ) ) ) ;
267+ log ( ` └─ 🤦 <span style="color: #F8BC3B;">Intentional mistake:</span> incomplete input` ) ;
268+ } else {
269+ // Swap two adjacent characters
270+ const swapIdx = Math . floor ( Math . random ( ) * ( correctText . length - 1 ) ) ;
271+ wrongText =
272+ correctText . substring ( 0 , swapIdx ) +
273+ correctText [ swapIdx + 1 ] +
274+ correctText [ swapIdx ] +
275+ correctText . substring ( swapIdx + 2 ) ;
276+ log ( ` └─ 🤦 <span style="color: #F8BC3B;">Intentional mistake:</span> transposed characters` ) ;
277+ }
278+
279+ // Type the wrong text
280+ await element . click ( { clickCount : 3 } ) ;
281+ await sleep ( randomBetween ( 50 , 100 ) ) ;
282+ for ( const char of wrongText ) {
283+ await page . keyboard . type ( char ) ;
284+ await sleep ( randomBetween ( 25 , 75 ) ) ;
285+ }
286+
287+ // Try to submit to trigger validation error
288+ await page . keyboard . press ( 'Enter' ) ;
289+ await sleep ( randomBetween ( 500 , 1500 ) ) ; // Stare at the error
290+
291+ // Tab away and back (another common pattern to trigger blur validation)
292+ await page . keyboard . press ( 'Tab' ) ;
293+ await sleep ( randomBetween ( 300 , 800 ) ) ;
294+
295+ log ( ` └─ 😤 <span style="color: #CC332B;">Validation error triggered</span> — meeple correcting...` ) ;
296+
297+ // Fix it — select all and retype correctly
298+ await element . click ( { clickCount : 3 } ) ;
299+ await sleep ( randomBetween ( 100 , 200 ) ) ;
300+ for ( const char of correctText ) {
301+ await page . keyboard . type ( char ) ;
302+ await sleep ( randomBetween ( 25 , 75 ) ) ;
303+ }
304+
305+ await sleep ( randomBetween ( 200 , 500 ) ) ;
306+ log ( ` └─ ✅ <span style="color: #07B096;">Corrected input</span>` ) ;
307+ return true ;
308+ } catch ( error ) {
309+ log ( ` └─ ⚠️ <span style="color: #F8BC3B;">Form mistake simulation failed:</span> ${ error . message } ` ) ;
310+ return false ;
311+ }
312+ }
313+
214314/**
215315 * Intelligently fill any form element based on its type
216316 * @param {Page } page - Puppeteer page object
217317 * @param {ElementHandle } element - The form element
218- * @param {Object } options - Options like {text, value, clicksPerGroup}
318+ * @param {Object } options - Options like {text, value, clicksPerGroup, formMistakes }
219319 * @param {Function } log - Logging function
220320 * @returns {Promise<boolean> } Success status
221321 */
@@ -237,6 +337,10 @@ export async function fillFormElement(page, element, options = {}, log = console
237337 } else if ( elementInfo . tagName === 'select' ) {
238338 return await fillSelectDropdown ( page , element , options . value , log ) ;
239339 } else if ( elementInfo . tagName === 'textarea' || elementInfo . tagName === 'input' ) {
340+ // When formMistakes is enabled, 30% chance to make an intentional mistake
341+ if ( options . formMistakes && Math . random ( ) < 0.3 ) {
342+ return await fillTextInputWithMistake ( page , element , options . text , log ) ;
343+ }
240344 return await fillTextInput ( page , element , options . text , log ) ;
241345 }
242346
@@ -250,8 +354,11 @@ export async function fillFormElement(page, element, options = {}, log = console
250354/**
251355 * Interact with forms - search boxes, email inputs, etc.
252356 * ENHANCED: Now supports ALL form element types including radios and checkboxes
357+ * @param {Page } page - Puppeteer page object
358+ * @param {Function } log - Logging function
359+ * @param {Object } [opts] - Options like { formMistakes: boolean }
253360 */
254- export async function interactWithForms ( page , log = console . log ) {
361+ export async function interactWithForms ( page , log = console . log , opts = { } ) {
255362 try {
256363 // Check if page is still responsive
257364 await page . evaluate ( ( ) => document . readyState ) ;
@@ -386,7 +493,11 @@ export async function interactWithForms(page, log = console.log) {
386493 action = `🔽 <span style="color: #9B59B6;">Select option chosen</span>` ;
387494 } else {
388495 // Text input, textarea, or other input types
389- success = await fillTextInput ( page , elementHandle , null , log ) ;
496+ if ( opts . formMistakes && Math . random ( ) < 0.3 ) {
497+ success = await fillTextInputWithMistake ( page , elementHandle , null , log ) ;
498+ } else {
499+ success = await fillTextInput ( page , elementHandle , null , log ) ;
500+ }
390501
391502 // Sometimes submit (30%), sometimes just leave it
392503 if ( Math . random ( ) < 0.3 ) {
0 commit comments