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:
@@ -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': {
|
case 'Runtime.enable': {
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -3668,6 +3668,56 @@ describe('Service Worker Target Tests', () => {
|
|||||||
await browser.close()
|
await browser.close()
|
||||||
await page.close()
|
await page.close()
|
||||||
}, 60000)
|
}, 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', () => {
|
describe('Auto-enable Tests', () => {
|
||||||
|
|||||||
@@ -356,6 +356,31 @@ console.log(data);
|
|||||||
|
|
||||||
Clean up listeners when done: `page.removeAllListeners('request'); page.removeAllListeners('response');`
|
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
|
## capabilities
|
||||||
|
|
||||||
Examples of what playwriter can do:
|
Examples of what playwriter can do:
|
||||||
|
|||||||
Reference in New Issue
Block a user