@@ -208,6 +208,36 @@ var PlaywrightGenerator = (() => {
208208 }
209209
210210 // src/utils.ts
211+ var filterClickDetails = ( clickDetails , assertions , config ) => {
212+ if ( ! clickDetails . length )
213+ return [ ] ;
214+ let filtered = config . filterAssertionClicks ? clickDetails . filter (
215+ ( click ) => ! assertions . some (
216+ ( assertion ) => Math . abs ( click . timestamp - assertion . timestamp ) < 1e3 && ( click . selectors . primary . includes ( assertion . selector ) || assertion . selector . includes ( click . selectors . primary ) || click . selectors . fallbacks . some (
217+ ( f ) => f . includes ( assertion . selector ) || assertion . selector . includes ( f )
218+ ) )
219+ )
220+ ) : clickDetails ;
221+ const clicksBySelector = { } ;
222+ filtered . forEach ( ( click ) => {
223+ const key = click . selectors . primary ;
224+ if ( ! clicksBySelector [ key ] )
225+ clicksBySelector [ key ] = [ ] ;
226+ clicksBySelector [ key ] . push ( click ) ;
227+ } ) ;
228+ const result = [ ] ;
229+ Object . values ( clicksBySelector ) . forEach ( ( clicks ) => {
230+ clicks . sort ( ( a , b ) => a . timestamp - b . timestamp ) ;
231+ let lastClick = null ;
232+ clicks . forEach ( ( click ) => {
233+ if ( ! lastClick || click . timestamp - lastClick . timestamp > config . multiClickInterval ) {
234+ result . push ( click ) ;
235+ }
236+ lastClick = click ;
237+ } ) ;
238+ } ) ;
239+ return result . sort ( ( a , b ) => a . timestamp - b . timestamp ) ;
240+ } ;
211241 function getRelativeUrl ( url ) {
212242 if ( ! url )
213243 return "/" ;
@@ -242,6 +272,10 @@ var PlaywrightGenerator = (() => {
242272 }
243273
244274 // src/playwright-generator.ts
275+ var CLICK_FILTER_CONFIG = {
276+ filterAssertionClicks : true ,
277+ multiClickInterval : 300
278+ } ;
245279 var RecordingManager = class {
246280 constructor ( ) {
247281 this . trackingData = {
@@ -413,11 +447,32 @@ var PlaywrightGenerator = (() => {
413447 break ;
414448 }
415449 }
450+ /**
451+ * trackingData with the same assertion-adjacent click filtering the in-iframe
452+ * processResults() pipeline applies (filterClickDetails in utils.ts) — e.g. the
453+ * click a text-selection drag can spuriously register right before an assertion.
454+ * generateTest()/getReplaySteps() are the ONLY paths ever used for output (the
455+ * host only calls recorder.generateTest()/getReplaySteps(); the filtered
456+ * staktrak-results payload from processResults() is not read for generation — see
457+ * hive useStaktrak.ts and mcp/tests/hooks.js), so without this, that filter never
458+ * actually ran and generated tests could carry an extra bogus click.
459+ */
460+ filteredTrackingData ( ) {
461+ return __spreadProps ( __spreadValues ( { } , this . trackingData ) , {
462+ clicks : __spreadProps ( __spreadValues ( { } , this . trackingData . clicks ) , {
463+ clickDetails : filterClickDetails (
464+ this . trackingData . clicks . clickDetails ,
465+ this . trackingData . assertions ,
466+ CLICK_FILTER_CONFIG
467+ )
468+ } )
469+ } ) ;
470+ }
416471 /**
417472 * Generate Playwright test from current data
418473 */
419474 generateTest ( url , options ) {
420- const actions = resultsToActions ( this . trackingData ) ;
475+ const actions = resultsToActions ( this . filteredTrackingData ( ) ) ;
421476 return generatePlaywrightTestFromActions ( actions , __spreadValues ( {
422477 baseUrl : url
423478 } , options ) ) ;
@@ -427,7 +482,7 @@ var PlaywrightGenerator = (() => {
427482 * directly (no generated text, no re-parse). Single source of truth = trackingData.
428483 */
429484 getReplaySteps ( url ) {
430- const actions = resultsToActions ( this . trackingData ) ;
485+ const actions = resultsToActions ( this . filteredTrackingData ( ) ) ;
431486 return actionsToReplaySteps ( actions , { baseUrl : url } ) ;
432487 }
433488 /**
@@ -484,7 +539,7 @@ var PlaywrightGenerator = (() => {
484539 }
485540 return null ;
486541 }
487- function clickExpr ( locator ) {
542+ function locatorExpr ( locator ) {
488543 const testId = extractTestId ( locator ) ;
489544 if ( testId )
490545 return `page.getByTestId('${ q ( testId ) } ')` ;
@@ -494,10 +549,6 @@ var PlaywrightGenerator = (() => {
494549 const [ , role , name ] = roleM ;
495550 return name ? `page.getByRole('${ q ( role ) } ', { name: '${ q ( name ) } ' })` : `page.getByRole('${ q ( role ) } ')` ;
496551 }
497- return cssLocator ( locator ) ;
498- }
499- function cssLocator ( locator ) {
500- const sel = ( locator == null ? void 0 : locator . stableSelector ) || ( locator == null ? void 0 : locator . primary ) || "" ;
501552 return `page.locator('${ q ( sel ) } ')` ;
502553 }
503554 function generatePlaywrightTestFromActions ( actions , options = { } ) {
@@ -518,17 +569,17 @@ var PlaywrightGenerator = (() => {
518569 case "click" : {
519570 if ( ! ( ( _a = action . locator ) == null ? void 0 : _a . primary ) && ! ( ( _b = action . locator ) == null ? void 0 : _b . stableSelector ) )
520571 return "" ;
521- return ` await ${ clickExpr ( action . locator ) } .click();` ;
572+ return ` await ${ locatorExpr ( action . locator ) } .click();` ;
522573 }
523574 case "input" : {
524575 if ( ! ( ( _c = action . locator ) == null ? void 0 : _c . primary ) || action . value === void 0 )
525576 return "" ;
526- return ` await ${ cssLocator ( action . locator ) } .fill('${ q ( action . value ) } ');` ;
577+ return ` await ${ locatorExpr ( action . locator ) } .fill('${ q ( action . value ) } ');` ;
527578 }
528579 case "form" : {
529580 if ( ! ( ( _d = action . locator ) == null ? void 0 : _d . primary ) )
530581 return "" ;
531- const target = cssLocator ( action . locator ) ;
582+ const target = locatorExpr ( action . locator ) ;
532583 if ( action . formType === "checkbox" || action . formType === "radio" ) {
533584 return action . checked ? ` await ${ target } .check();` : ` await ${ target } .uncheck();` ;
534585 } else if ( action . formType === "select" && action . value !== void 0 ) {
@@ -539,7 +590,7 @@ var PlaywrightGenerator = (() => {
539590 case "assertion" : {
540591 if ( ! ( ( _e = action . locator ) == null ? void 0 : _e . primary ) || action . value === void 0 )
541592 return "" ;
542- return ` await expect(${ cssLocator ( action . locator ) } ).toContainText('${ q ( action . value ) } ');` ;
593+ return ` await expect(${ locatorExpr ( action . locator ) } ).toContainText('${ q ( action . value ) } ');` ;
543594 }
544595 default :
545596 return "" ;
0 commit comments