@@ -10,6 +10,7 @@ import {
1010 getBrowserAndPages ,
1111 getResourcesPath ,
1212 goTo ,
13+ goToHtml ,
1314 goToResource ,
1415 goToResourceWithCustomHost ,
1516 waitFor ,
@@ -19,6 +20,13 @@ import {
1920} from '../../shared/helper.js' ;
2021import { getCurrentConsoleMessages } from '../helpers/console-helpers.js' ;
2122import { reloadDevTools , tabExistsInDrawer } from '../helpers/cross-tool-helper.js' ;
23+ import {
24+ getCategoryRow ,
25+ navigateToMemoryTab ,
26+ setClassFilter ,
27+ takeHeapSnapshot ,
28+ waitForNonEmptyHeapSnapshotData ,
29+ } from '../helpers/memory-helpers.js' ;
2230
2331const READY_LOCAL_METRIC_SELECTOR = '#local-value .metric-value:not(.waiting)' ;
2432const READY_FIELD_METRIC_SELECTOR = '#field-value .metric-value:not(.waiting)' ;
@@ -452,4 +460,51 @@ describe('The Performance panel landing page', () => {
452460 await targetSession . detach ( ) ;
453461 }
454462 } ) ;
463+
464+ it ( 'does not retain interaction nodes in memory' , async ( ) => {
465+ const { target, frontend} = await getBrowserAndPages ( ) ;
466+
467+ await target . bringToFront ( ) ;
468+
469+ const targetSession = await target . createCDPSession ( ) ;
470+ try {
471+ await goToHtml ( '<button>Click me!</button>' ) ;
472+
473+ const button = await target . waitForSelector ( 'button' ) ;
474+ await button ! . click ( ) ;
475+
476+ // This ensures that the interaction has time to make it's way through web-vitals.js and
477+ // be detected by the live metrics model in DevTools.
478+ //
479+ // If any unnecessary JS references to the node get created they will be created in this time period.
480+ await target . evaluate ( ( ) => new Promise ( requestAnimationFrame ) ) ;
481+ await target . evaluate ( ( ) => new Promise ( requestAnimationFrame ) ) ;
482+ await frontend . bringToFront ( ) ;
483+ await waitFor ( INTERACTION_SELECTOR ) ;
484+ await target . bringToFront ( ) ;
485+
486+ // Attempt to remove the node from memory
487+ await button ! . evaluate ( async el => {
488+ el . remove ( ) ;
489+ await new Promise ( requestAnimationFrame ) ;
490+ } ) ;
491+ await button ! . dispose ( ) ;
492+
493+ // Ensure the node is not preserved in a detached state
494+ const { detachedNodes} = await targetSession . send ( 'DOM.getDetachedDomNodes' ) ;
495+ assert . lengthOf ( detachedNodes , 0 ) ;
496+
497+ await frontend . bringToFront ( ) ;
498+
499+ // For redundancy, ensure the button node is removed from the memory heap
500+ await navigateToMemoryTab ( ) ;
501+ await takeHeapSnapshot ( ) ;
502+ await waitForNonEmptyHeapSnapshotData ( ) ;
503+ await setClassFilter ( 'Detached <button>' ) ;
504+ const row = await getCategoryRow ( 'Detached <button>' , false ) ;
505+ assert . isNull ( row ) ;
506+ } finally {
507+ await targetSession . detach ( ) ;
508+ }
509+ } ) ;
455510} ) ;
0 commit comments