From 8213652511d8d864409fc95434c2f84d7ab5683a Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Fri, 5 Jun 2026 13:37:16 +0200 Subject: [PATCH] fix(mcp): implement signalToPromise dispose to remove abort listener signalToPromise returned a no-op dispose, so the 'abort' listener it registered on the AbortSignal was never removed. The single caller in the test MCP context also discarded dispose entirely. Implement dispose to remove the listener and call it in a finally around the test run race. --- packages/isomorphic/manualPromise.ts | 12 +++++++----- packages/playwright/src/mcp/test/testContext.ts | 7 +++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/isomorphic/manualPromise.ts b/packages/isomorphic/manualPromise.ts index 87858e796e001..bb16ada8e217d 100644 --- a/packages/isomorphic/manualPromise.ts +++ b/packages/isomorphic/manualPromise.ts @@ -114,13 +114,15 @@ export class LongStandingScope { } export function signalToPromise(signal: AbortSignal): { promise: Promise, dispose: () => void } { + if (signal.aborted) + return { promise: Promise.resolve(), dispose: () => {} }; + let dispose: (() => void) | undefined; const promise = new Promise(resolve => { - if (signal.aborted) - resolve(); - else - signal.addEventListener('abort', () => resolve(), { once: true }); + const onAbort = () => resolve(); + signal.addEventListener('abort', onAbort, { once: true }); + dispose = () => signal.removeEventListener('abort', onAbort); }); - return { promise, dispose: () => {} }; + return { promise, dispose: dispose! }; } function cloneError(error: Error, frames: string[]) { diff --git a/packages/playwright/src/mcp/test/testContext.ts b/packages/playwright/src/mcp/test/testContext.ts index 3c53a966676a2..f073bb752e649 100644 --- a/packages/playwright/src/mcp/test/testContext.ts +++ b/packages/playwright/src/mcp/test/testContext.ts @@ -233,8 +233,9 @@ export class TestContext { } }; - const abortPromise = signal - ? signalToPromise(signal).promise.then(() => 'interrupted' as const) + const abort = signal ? signalToPromise(signal) : undefined; + const abortPromise = abort + ? abort.promise.then(() => 'interrupted' as const) : new Promise(() => {}); try { @@ -263,6 +264,8 @@ export class TestContext { testRunnerAndScreen.output.push(String(e)); await cleanup(); return { output: testRunnerAndScreen.output.join('\n'), status }; + } finally { + abort?.dispose(); } await cleanup();