Skip to content

[Bug]: Locator actions retain last target element in memory, keeping detached DOM, can affect leak detection #41462

Description

@apocalyp0sys

Version

1.61.0

Steps to reproduce

Minimal reproduction example:

import { chromium } from 'playwright';

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.setContent(`<button id="btn">click</button>`);

  // 1) A normal locator action -> the button becomes retained
  await page.locator('#btn').click();

  // 2) Track the button with a WeakRef, then detach it from the DOM.
  await page.evaluate(() => {
    globalThis.ref = new WeakRef(document.getElementById('btn'));
    document.getElementById('btn').remove();
  });

  // 3) Request garbage collection, wait
  await page.requestGC();
  await page.waitForTimeout(1000);
  await page.requestGC();
  // 4) Check if button was collected
  console.log('collected after detach:', await page.evaluate(() => globalThis.ref.deref() === undefined)); // => false (leaked)

  // 5) Perform any action on another element -> removes button retention
  await page.locator('body').hover();

  // 6) Request garbage collection again, check again
  await page.requestGC();
  console.log('collected after extra action:', await page.evaluate(() => globalThis.ref.deref() === undefined)); // => true

  await browser.close();
})();

This script was based on example from Page.requestGC() documentation.

Expected behavior

Locator actions should not retain detached DOM nodes in memory.

Actual behavior

Somehow last target dom element gets retained and cannot be garbage collected. This can affect memory leak detection. Same manual actions outside of playwright environment do not produce such problem.

Additional context

For some reason, dom node leak does not happen if you run reproduction example in headed mode, but it does in my production app.

I'm attaching a heap snapshot captured from reproduction example headless via CDP (HeapProfiler.takeHeapSnapshot) after first GC attempt and WeakRef.deref(), but i couldnt find anything useful in it.

pw.example.heapsnapshot.json

However, I suspect playwright's InjectedScript.markTargetElements‎ might be involved.

I am willing to investigate further, and produce a PR if needed.

Environment

System:
    OS: Windows 11 10.0.26100
    CPU: (20) x64 13th Gen Intel(R) Core(TM) i7-13700H
    Memory: 15.01 GB / 47.53 GB
  Binaries:
    Node: 24.16.0 - C:\Program Files\nodejs\node.EXE
    npm: 11.13.0 - C:\Program Files\nodejs\npm.CMD
  npmPackages:
    @playwright/test: ^1.61.0 => 1.61.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions