Skip to content
Latest stable: v0.9.0.

Reproduce a Broken Markdown Render via Stream Replay

Markdown renderers in chat UIs break when a code fence arrives split across chunks: the opening ``` lands in one chunk and the language hint in the next, and a naive incremental renderer flips the whole rest of the message into a code block. This only reproduces with an exact chunk boundary, which is impossible to hit reliably against a live model. Replay a captured fixture and force the split.

  1. Capture (or hand-author) a fixture whose message contains a fenced code block.
  2. Replay it deterministically, with a split mutation that cuts the chunk carrying the fence in half.
  3. Assert the rendered output recovers instead of running away into a code block.

The fixture is loaded on the Node side and passed inline as ai.replay.data, so the run needs no live backend.

import { test, expect } from '@playwright/test';
import { injectChaos, getChaosLog, loadStreamFixture } from '@chaos-maker/playwright';
test('markdown render recovers when a code fence is split across chunks', async ({ page }) => {
const fixture = loadStreamFixture('fixtures/chat-with-code-fence.json');
await injectChaos(page, {
seed: 42,
ai: {
transport: 'fetch-stream',
replay: {
data: fixture,
urlPattern: '/chat',
// Chunk 3 carries "```ts"; cut it after the third character so the
// fence and the language hint arrive as two separate chunks.
mutations: [{ type: 'split', chunkIndex: 3, at: 3 }],
},
},
});
await page.goto('/chat');
await page.click('#send');
await expect(page.locator('#chat-status')).toHaveText('done');
// The message renders a single code block, not a runaway one that swallows
// the trailing prose.
await expect(page.locator('pre code')).toHaveCount(1);
const log = await getChaosLog(page);
expect(log.some((e) => e.detail.phase === 'ai:stream-replayed')).toBe(true);
});

Replay makes the failure exact and repeatable: the split lands on the same byte on every run and every browser, so a fix can be verified and the test cannot flake. Recording the fixture once (recordStreamFixture) captures a real message shape without pinning the test to a live model.