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
This commit is contained in:
Tommy D. Rossi
2026-01-15 15:55:18 +01:00
parent 171317f8cb
commit 0ba4acfbce
3 changed files with 92 additions and 0 deletions
+17
View File
@@ -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
+50
View File
@@ -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('</html>')
cdpSession.close()
await browser.close()
await page.close()
}, 60000)
})
describe('Auto-enable Tests', () => {
+25
View File
@@ -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: