From daf385875914251410d2885d0d457373ecf97d45 Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Thu, 27 Aug 2026 02:28:08 +0800 Subject: [PATCH] fix(inspector): restore client console and response bodies --- .../inspector/src/host/inspection/network.ts | 14 ++------ .../src/worker/realms/client/runtime.ts | 2 -- .../inspector/tests/client-browser.e2e.ts | 22 +++++++++++- .../tests/fetch-observer.host.spec.ts | 36 +++++++++++++++++-- 4 files changed, 57 insertions(+), 17 deletions(-) diff --git a/packages/experimental/inspector/src/host/inspection/network.ts b/packages/experimental/inspector/src/host/inspection/network.ts index d73d979fa9..fcbdadb76d 100644 --- a/packages/experimental/inspector/src/host/inspection/network.ts +++ b/packages/experimental/inspector/src/host/inspection/network.ts @@ -117,19 +117,9 @@ export function installFetchObserver( responseClone.body, options.maxResponseBodyBytes, options.maxChunkBytes, - AbortSignal.any([controller.signal, request.signal]), + controller.signal, (data) => { publisher.publish('fetch/response-body-chunk', { requestId, data }) }, ).then((outcome) => { - if (request.signal.aborted) { - publisher.publish('fetch/error', { - requestId, - message: request.signal.reason === undefined - ? 'AbortError: request aborted during response body capture' - : renderError(request.signal.reason), - canceled: true, - }) - return - } publisher.publish('fetch/end', { requestId, capturedBytes: outcome.capturedBytes, @@ -210,7 +200,7 @@ async function captureBody( } return { capturedBytes, truncated } } catch (error) { - return { capturedBytes, truncated, captureError: renderError(error) } + return { capturedBytes, truncated: true, captureError: renderError(error) } } finally { signal.removeEventListener('abort', abort) reader.releaseLock() diff --git a/packages/experimental/inspector/src/worker/realms/client/runtime.ts b/packages/experimental/inspector/src/worker/realms/client/runtime.ts index ef44975849..fd298bcb56 100644 --- a/packages/experimental/inspector/src/worker/realms/client/runtime.ts +++ b/packages/experimental/inspector/src/worker/realms/client/runtime.ts @@ -143,8 +143,6 @@ function assertClientEvaluationOptions(request: Parameters { const context = event.params?.context as Record | undefined return String(context?.name).startsWith('Client —') }) - const contextId = (contextEvent.params?.context as Record).id + const context = contextEvent.params?.context as Record + const contextId = context.id + const uniqueContextId = context.uniqueId expect(contextId).toBeTypeOf('number') + expect(uniqueContextId).toBeTypeOf('string') + + const evaluated = await cdp.call('Runtime.evaluate', { + expression: 'globalThis.__inspectorConsoleEvaluation = { answer: 6 * 7 }', + objectGroup: 'console', + includeCommandLineAPI: true, + silent: false, + returnByValue: false, + generatePreview: true, + userGesture: true, + awaitPromise: false, + replMode: true, + allowUnsafeEvalBlockedByCSP: false, + uniqueContextId, + }) + expect(evaluated.error).toBeUndefined() + expect(asRecord(evaluated.result?.result).objectId).toMatch(/^runtime:/u) + expect(await page.evaluate(() => Reflect.get(globalThis, '__inspectorConsoleEvaluation'))).toEqual({ answer: 42 }) await page.evaluate(() => { const value = { browser: true, nested: { ready: true } } diff --git a/packages/experimental/inspector/tests/fetch-observer.host.spec.ts b/packages/experimental/inspector/tests/fetch-observer.host.spec.ts index cabeb5a1a5..c078b4e23c 100644 --- a/packages/experimental/inspector/tests/fetch-observer.host.spec.ts +++ b/packages/experimental/inspector/tests/fetch-observer.host.spec.ts @@ -79,7 +79,7 @@ describe('full fetch observer', () => { expect(payload(records, 'fetch/end')).toMatchObject({ capturedBytes: 4, responseBodyTruncated: true }) }) - it('reports cancellation after response headers as a canceled request', async () => { + it('retains a truncated response when the caller cancels after response headers', async () => { const records: InspectorRecordInput[] = [] Object.defineProperty(globalThis, 'fetch', { value: vi.fn(async (request: Request) => new Response(new ReadableStream({ @@ -103,9 +103,41 @@ describe('full fetch observer', () => { const response = await fetch('https://example.test/cancel-body', { signal: abort.signal }) abort.abort() await expect(response.text()).rejects.toThrow() - await vi.waitFor(() => { expect(records.some(record => record.topic === 'fetch/error')).toBe(true) }) + await vi.waitFor(() => { expect(records.some(record => record.topic === 'fetch/end')).toBe(true) }) + + expect(decodeChunks(records, 'fetch/response-body-chunk')).toBe('first') + expect(payload(records, 'fetch/end')).toMatchObject({ + capturedBytes: 5, + responseBodyTruncated: true, + responseCaptureError: 'AbortError: aborted', + }) + expect(records.some(record => record.topic === 'fetch/error')).toBe(false) + }) + + it('reports a fetch rejected before response headers as a canceled request', async () => { + const records: InspectorRecordInput[] = [] + Object.defineProperty(globalThis, 'fetch', { + value: vi.fn(async (request: Request) => await new Promise((_resolve, reject) => { + request.signal.addEventListener('abort', () => { + reject(new DOMException('aborted', 'AbortError')) + }, { once: true }) + })), + writable: true, + configurable: true, + }) + observer = installFetchObserver({ + publish(topic: string, payload: InspectorJsonValue, monotonicMs = performance.now()) { + records.push({ topic, payload, monotonicMs }) + }, + }, { maxRequestBodyBytes: 1_024, maxResponseBodyBytes: 1_024, maxChunkBytes: 4 }) + const abort = new AbortController() + + const pending = fetch('https://example.test/cancel-before-response', { signal: abort.signal }) + abort.abort() + await expect(pending).rejects.toThrow() expect(payload(records, 'fetch/error')).toMatchObject({ canceled: true }) + expect(records.some(record => record.topic === 'fetch/response')).toBe(false) expect(records.some(record => record.topic === 'fetch/end')).toBe(false) }) })