From 0ba4acfbce368c18db618221bb96402a816056b1 Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Thu, 15 Jan 2026 15:55:18 +0100 Subject: [PATCH] fix: disable Network buffering by default to fix SSE streaming Fixes #22 CDP's Network.enable buffers response bodies by default, which breaks SSE/streaming - data arrives at Chrome but ReadableStream never receives it. Changes: - Intercept Network.enable in relay, default maxTotalBufferSize: 0 - Agents can re-enable buffering via Network.disable + Network.enable with explicit buffer sizes when they need response.body() - Added test and documentation for reading response bodies --- playwriter/src/cdp-relay.ts | 17 +++++++++++++ playwriter/src/mcp.test.ts | 50 +++++++++++++++++++++++++++++++++++++ playwriter/src/prompt.md | 25 +++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/playwriter/src/cdp-relay.ts b/playwriter/src/cdp-relay.ts index 3f91aa6..dda9821 100644 --- a/playwriter/src/cdp-relay.ts +++ b/playwriter/src/cdp-relay.ts @@ -359,6 +359,23 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127. }) } + // Network.enable: Default to zero buffering to fix SSE streaming issues. + // CDP's Network domain buffers response bodies by default, which breaks ReadableStream + // for SSE/streaming responses. Setting maxTotalBufferSize: 0 disables this buffering. + // Agents can re-enable buffering by calling Network.disable then Network.enable with + // explicit buffer sizes if they need to read response bodies via Network.getResponseBody. + case 'Network.enable': { + const modifiedParams = { + maxTotalBufferSize: 0, + maxResourceBufferSize: 0, + ...params + } + return await sendToExtension({ + method: 'forwardCDPCommand', + params: { sessionId, method, params: modifiedParams } + }) + } + case 'Runtime.enable': { if (!sessionId) { break diff --git a/playwriter/src/mcp.test.ts b/playwriter/src/mcp.test.ts index 4c9480c..6415e71 100644 --- a/playwriter/src/mcp.test.ts +++ b/playwriter/src/mcp.test.ts @@ -3668,6 +3668,56 @@ describe('Service Worker Target Tests', () => { await browser.close() await page.close() }, 60000) + + it('should allow reading response bodies after re-enabling Network buffering', async () => { + // By default, the relay sets maxTotalBufferSize: 0 to fix SSE streaming. + // This test verifies that agents can re-enable buffering to read response bodies + // using Playwright's response.body() API after re-enabling Network buffering via CDP. + + const browserContext = getBrowserContext() + const serviceWorker = await getExtensionServiceWorker(browserContext) + + const page = await browserContext.newPage() + await page.goto('https://example.com/') + await page.bringToFront() + + await serviceWorker.evaluate(async () => { + await globalThis.toggleExtensionForActiveTab() + }) + await new Promise(r => setTimeout(r, 100)) + + const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })) + const cdpPage = browser.contexts()[0].pages().find(p => p.url().includes('example.com')) + expect(cdpPage).toBeDefined() + + // Get CDP session to re-enable buffering (must use getCDPSessionForPage, not newCDPSession) + const wsUrl = getCdpUrl({ port: TEST_PORT }) + const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl }) + + // Re-enable Network domain with buffering to allow response.body() to work + await cdpSession.send('Network.disable') + await cdpSession.send('Network.enable', { + maxTotalBufferSize: 10000000, + maxResourceBufferSize: 5000000 + }) + + // Use Playwright's response API to capture and read response body + const [response] = await Promise.all([ + cdpPage!.waitForResponse(resp => resp.url() === 'https://example.com/'), + cdpPage!.goto('https://example.com/') + ]) + + // Now response.body() should work because we re-enabled buffering + const body = await response.text() + + expect(body).toBeDefined() + expect(body).toContain('Example Domain') + expect(body).toContain('') + + cdpSession.close() + await browser.close() + await page.close() + }, 60000) }) describe('Auto-enable Tests', () => { diff --git a/playwriter/src/prompt.md b/playwriter/src/prompt.md index f01c084..d817e67 100644 --- a/playwriter/src/prompt.md +++ b/playwriter/src/prompt.md @@ -356,6 +356,31 @@ console.log(data); Clean up listeners when done: `page.removeAllListeners('request'); page.removeAllListeners('response');` +## reading response bodies + +By default, playwriter disables CDP response body buffering to ensure SSE streaming works properly. If you need to read response bodies (e.g., for HAR recording or inspecting API responses), re-enable buffering first: + +```js +// Get CDP session and re-enable Network buffering +const cdp = await getCDPSession({ page }); +await cdp.send('Network.disable'); +await cdp.send('Network.enable', { + maxTotalBufferSize: 10000000, // 10MB total buffer + maxResourceBufferSize: 5000000 // 5MB per resource +}); + +// Now use Playwright's response API - it will work because buffering is enabled +const [response] = await Promise.all([ + page.waitForResponse(resp => resp.url().includes('/api/data')), + page.click('button.load-data') +]); + +const body = await response.text(); // or response.json(), response.body() +console.log(body); +``` + +**Note**: This may cause SSE/streaming responses to buffer. Only enable when you specifically need response bodies. + ## capabilities Examples of what playwriter can do: