Skip to content

feat: add playwright integration feature #141

Open
miinhho wants to merge 7 commits intofacebook:mainfrom
miinhho:feat/playwright-integration
Open

feat: add playwright integration feature #141
miinhho wants to merge 7 commits intofacebook:mainfrom
miinhho:feat/playwright-integration

Conversation

@miinhho
Copy link
Copy Markdown
Contributor

@miinhho miinhho commented Apr 22, 2026

Summary

New @memlab/playwright package — a Playwright fixture that attaches memlab's leak detection to existing Playwright tests by destructuring a memlab parameter. No scenario rewrite, no Puppeteer dependency.

import {test, expect} from '@memlab/playwright';

test('modal close does not leak', async ({page, memlab}) => {
  await page.goto('/');
  await memlab.baseline();
  await page.click('text=Open');
  await page.click('text=Close');
  await memlab.expectNoLeaks();
});

Motivation

memlab's existing APIs (@memlab/api, memlab run) drive the browser through a Puppeteer-based scenario runner. Teams already running Playwright e2e suites can't attach leak detection without rewriting their specs. This package bridges the gap by piggybacking on the user's existing page via a CDP session.

Design notes

  • Chromium only. Heap snapshots go over CDP, which Playwright only exposes for Chromium. On Firefox / WebKit the fixture becomes a no-op: the test still runs, no leak detection, a memlab-skip annotation is attached. README calls this out.
  • Opt-in per test. Fixture setup runs only when memlab is destructured. Tests that don't use it pay no overhead
  • 3-phase snapshot (baseline / target / final) wired to fixture lifecycle. forceFullGC via HeapProfiler.collectGarbage CDP, no --expose-gc flag required.
  • Configurable. memlab.configure({gc, leakFilter}) for per-project GC tuning and custom filters. Merge semantics preserve prior fields.
  • CI-friendly failure output. formatLeakMessage picks the $memLabTag:leaked hop (not the Window root), strips memlab-internal $tabsOrder metadata from attached JSON, and heap snapshots only attach when leaks are detected (skips ~13 MB per clean test).

API

  • memlab.baseline(): Capture baseline heap snapshot
  • memlab.target() / memlab.final(): Manual phase control
  • memlab.configure({gc?, leakFilter?}): Per-test tuning
  • memlab.findLeaks(): Returns ISerializedInfo[] | null
  • memlab.expectNoLeaks(): Hard assertion with triage-friendly error

If neither findLeaks nor expectNoLeaks is called, the fixture auto-captures missing phases and soft-asserts no leaks during teardown.

miinhho and others added 7 commits April 23, 2026 01:35
New package giving existing Playwright tests opt-in memory-leak detection
via a single fixture. Destructuring `memlab` in the test body activates
CDP-based heap capture; omitting it leaves the test untouched.

- src/snapshot.ts: CDPLike duck-typed session + writeHeapSnapshot /
  forceFullGC, usable against Playwright or Puppeteer CDP without
  reshaping the existing @memlab/e2e code path.
- src/capturer.ts: PlaywrightHeapCapturer low-level API (attach, snapshot,
  findLeaks via SnapshotResultReader).
- src/test.ts: test.extend fixture that captures baseline/target/final,
  attaches memlab-leaks.json plus raw .heapsnapshot artifacts to testInfo,
  and soft-fails on detected leaks. Non-Chromium projects get a no-op
  fixture with a memlab-skip annotation.
- __tests__/smoke.mjs: manual spike verifying CDP snapshot round-trip.
…lter

Verifies the package end-to-end against a leaky React component (setInterval
closure without cleanup), a clean counterpart, and a no-op control.

- snapshot.ts: discard CDP console entries before the GC cycle so detached
  DOM nodes held by Chrome's console ring aren't reported as leaks.
- test.ts: post-filter leak traces whose retainer path is owned by CDP
  inspector state (DevTools console, Inspector, CommandLineAPI). These are
  artifacts of how Playwright drives the page, not application leaks.
- tsconfig.json: drop the base paths extend so Playwright's TS loader
  resolves @memlab/* via node_modules instead of rewriting to monorepo
  source files (which hit a circular import at require time).
- __tests__/: React fixtures (unpkg UMD, no build step), playwright config,
  shielded tsconfig with empty paths, and the spec itself. Run with
  `npm run test-e2e` inside packages/playwright.
The previous spec used React UMD via unpkg loaded from file:// URLs, which
skips JSX compilation, module resolution, and the HTTP origin semantics of
any real application. This commit swaps in a Vite dev server running a
production-shaped React 18 fixture so the spec exercises the same code
paths users hit in practice.

- packages/playwright/__tests__/fixtures/vite-react/: new fixture app with
  JSX (Leaky + Clean components driven by ?mode=leaky|clean), registered
  as a monorepo workspace so `npm install` at the root pulls in its deps.
- playwright.config.ts: webServer launches `vite` on 127.0.0.1:5174 and
  Playwright sets baseURL so specs use relative paths.
- react-leak.spec.ts: navigate to http://.../?mode=leaky|clean instead of
  pathToFileURL(...).
- HMR disabled in vite.config.ts — the HMR websocket client pollutes heap
  snapshots and muddies the leak signal.

All three existing specs continue to pass (leaky detected, clean clean,
no-op fixture inert).
Adds a 7-closure-pattern × leaky/clean matrix (plus detached-DOM) that
asserts each leak's retainer trace includes the retained payload
(>=1MB) and that, without a user leakFilter, memlab's built-in rules
silently miss closure-retained JS state -- the gap that leakFilter
exists to address. Required adding the leakFilter option on
PlaywrightHeapCapturer and expanding the fixture App into ?mode=-routed
leak-pattern components.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rting

Split fixture into concern-scoped modules (types.ts, leak.ts,
fixture.ts, thin index.ts barrel). Collapse the `@memlab/playwright/test`
subpath into the root entry so users import from `@memlab/playwright`.
Bring package metadata in line with other memlab packages: add LICENSE,
publishConfig.access=public, tsconfig extends ../../tsconfig.base.json,
test-pkg runs the e2e suite, README trimmed to the house format.

Fix leakSummary: ISerializedInfo is a recursive dict where retainedSize
is encoded inside keys as \`\$retained-size:N\`, so reading flat
\`retainedSize\`/\`name\`/\`type\` was always undefined and the auto-mode
teardown message always rendered \"facebook#1: leak\". It now picks the first
non-\$tabsOrder trace key as the one-line digest. isInspectorArtifact
walks the full serialized trace instead of only top-level keys so
DevTools/Inspector retainers appearing mid-chain are filtered too.

Expose GC tuning through MemlabFixture.configure({gc: {...}}) instead
of the hardcoded 6-cycle; tighten CDPLike (send returns Promise<unknown>,
event handlers stay \`any\` to accept narrower Playwright/Puppeteer
signatures under strict function types).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the auto-teardown's array dump with a scalar assertion, pick the
leaked-node key (not the Window root) for summaries, strip internal
$tabsOrder metadata from attached JSON, and skip heap-snapshot attachment
on clean runs. Add expectNoLeaks() for the common "run a flow, assert no
leaks" pattern with triage-friendly failure messages. Replace the
algorithm-focused matrix spec with an integration-focused fixture spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The fixture only needs to exercise leak patterns (detached DOM,
subscription, interval closure) against a real HTTP origin — React
and Vite were incidental. Replace with a static HTML + vanilla JS
page served by a 27-line node:http script. Drops four test-only
dependencies (react, react-dom, vite, @vitejs/plugin-react), removes
the CI npm-install step, and simplifies clean-pkg. Rename the smoke
spec to drop the now-inaccurate [react] tag.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant