// @ts-check
/**
 * Streaming page sandbox tests — verify suspense + resolve DOM behavior.
 *
 * Boots the WASM kernel in sandbox mode (no server), loads the streaming
 * demo components, renders the shell with suspense placeholders, and tests
 * that __sxResolve correctly replaces placeholders with resolved content.
 */
const { test, expect } = require('playwright/test');
const fs = require('fs');
const path = require('path');

const PROJECT_ROOT = path.resolve(__dirname, '../..');
const WASM_DIR = path.join(PROJECT_ROOT, 'shared/static/wasm');
const SX_DIR = path.join(WASM_DIR, 'sx');

const WEB_MODULES = [
  'render', 'core-signals', 'signals', 'deps', 'router',
  'page-helpers', 'freeze', 'dom', 'browser',
  'adapter-html', 'adapter-sx', 'adapter-dom',
  'boot-helpers', 'hypersx', 'engine', 'orchestration', 'boot',
];

function readModule(mod) {
  const sxPath = path.join(SX_DIR, mod + '.sx');
  try { return fs.readFileSync(sxPath, 'utf8'); } catch { return null; }
}

function readFile(relPath) {
  return fs.readFileSync(path.join(PROJECT_ROOT, relPath), 'utf8');
}

// Component files needed for streaming demo (order matters — deps first)
const COMPONENT_FILES = [
  'shared/sx/templates/tw.sx',
  'shared/sx/templates/tw-layout.sx',
  'shared/sx/templates/tw-type.sx',
  'shared/sx/templates/layout.sx',
  'shared/sx/templates/pages.sx',
  'sx/sx/streaming-demo.sx',
];

async function bootSandbox(page) {
  await page.goto('about:blank');

  // Set up a minimal HTML page with a body container
  await page.setContent(`
    <html><head></head><body>
      <div id="sx-root"></div>
    </body></html>
  `);

  const kernelSrc = fs.readFileSync(path.join(WASM_DIR, 'sx_browser.bc.js'), 'utf8');
  await page.addScriptTag({ content: kernelSrc });
  await page.waitForFunction('!!window.SxKernel', { timeout: 10000 });

  // Register host FFI natives
  await page.evaluate(() => {
    const K = window.SxKernel;
    K.registerNative('host-global', a => { const n=a[0]; return (n in globalThis)?globalThis[n]:null; });
    K.registerNative('host-get', a => { if(a[0]==null)return null; const v=a[0][a[1]]; return v===undefined?null:v; });
    K.registerNative('host-set!', a => { if(a[0]!=null)a[0][a[1]]=a[2]; return a[2]; });
    K.registerNative('host-call', a => {
      const[o,m,...r]=a;
      if(o==null){const f=globalThis[m];return typeof f==='function'?f.apply(null,r):null;}
      if(typeof o[m]!=='function')return null;
      try{const v=o[m].apply(o,r);return v===undefined?null:v;}catch(e){return null;}
    });
    K.registerNative('host-new', a => {
      const C=typeof a[0]==='string'?globalThis[a[0]]:a[0];
      return typeof C==='function'?new C(...a.slice(1)):null;
    });
    K.registerNative('host-callback', a => {
      const fn=a[0];
      if(typeof fn==='function'&&fn.__sx_handle===undefined)return fn;
      if(fn&&fn.__sx_handle!==undefined){
        return function(){const r=K.callFn(fn,Array.from(arguments));if(window._driveAsync)window._driveAsync(r);return r;};
      }
      return function(){};
    });
    K.registerNative('host-typeof', a => {
      const o=a[0]; if(o==null)return'nil';
      if(o instanceof Element)return'element'; if(o instanceof Text)return'text';
      if(o instanceof DocumentFragment)return'fragment'; if(o instanceof Document)return'document';
      if(o instanceof Event)return'event'; if(o instanceof Promise)return'promise';
      return typeof o;
    });
    K.registerNative('host-await', a => {
      const[p,cb]=a;if(p&&typeof p.then==='function'){const f=(cb&&cb.__sx_handle!==undefined)?v=>K.callFn(cb,[v]):()=>{};p.then(f);}
    });
    K.registerNative('load-library!', () => false);
    window._driveAsync = function driveAsync(result) {
      if(!result||!result.suspended)return;
      const req=result.request;const items=req&&(req.items||req);
      const op=items&&items[0];const opName=typeof op==='string'?op:(op&&op.name)||String(op);
      const arg=items&&items[1];
      function doResume(val,delay){setTimeout(()=>{try{const r=result.resume(val);driveAsync(r);}catch(e){}},delay);}
      if(opName==='io-sleep'||opName==='wait')doResume(null,Math.min(typeof arg==='number'?arg:0,10));
      else if(opName==='io-fetch')doResume({ok:true,text:''},1);
    };
    K.eval('(define SX_VERSION "streaming-test-1.0")');
    K.eval('(define SX_ENGINE "ocaml-vm-sandbox")');
    K.eval('(define parse sx-parse)');
    K.eval('(define serialize sx-serialize)');
  });

  // Load web modules (bytecode)
  const loadErrors = [];
  await page.evaluate(() => { if (window.SxKernel.beginModuleLoad) window.SxKernel.beginModuleLoad(); });
  for (const mod of WEB_MODULES) {
    const src = readModule(mod);
    if (!src) { loadErrors.push(mod); continue; }
    const err = await page.evaluate(s => {
      try { window.SxKernel.load(s); return null; } catch(e) { return e.message; }
    }, src);
    if (err) loadErrors.push(mod + ': ' + err);
  }
  await page.evaluate(() => { if (window.SxKernel.endModuleLoad) window.SxKernel.endModuleLoad(); });

  // Load component files (.sx source)
  for (const f of COMPONENT_FILES) {
    const src = readFile(f);
    const err = await page.evaluate(s => {
      try { window.SxKernel.load(s); return null; } catch(e) { return e.message; }
    }, src);
    if (err) loadErrors.push(f + ': ' + err);
  }

  // The boot module's imports prevent its defines from running in sandbox.
  // Define resolve-suspense inline using the same deps that ARE available.
  await page.evaluate(() => {
    const K = window.SxKernel;
    K.eval(`(define resolve-suspense (fn (id sx)
      (let ((el (dom-query (str "[data-suspense=\\"" id "\\"]"))))
        (when el
          (let ((exprs (sx-parse sx))
                (env (get-render-env nil)))
            (dom-set-text-content el "")
            (for-each (fn (expr) (dom-append el (render-to-dom expr env nil))) exprs))))))`);
  });

  // Set up Sx.resolveSuspense using the kernel (mirrors sx-platform.js)
  await page.evaluate(() => {
    const K = window.SxKernel;
    window.Sx = window.Sx || {};
    Sx.resolveSuspense = function(id, sx) {
      try {
        K.eval('(resolve-suspense "' + id.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '" "' + sx.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '")');
      } catch(e) {
        console.error("[sx] resolveSuspense error:", e);
      }
    };
    // Bootstrap (same as server sends in streaming response)
    window.__sxPending = [];
    window.__sxResolve = function(id, sx) {
      if (window.Sx && Sx.resolveSuspense) {
        Sx.resolveSuspense(id, sx);
      } else {
        window.__sxPending.push({ id, sx });
      }
    };
  });

  return loadErrors;
}

test.describe('Streaming sandbox', () => {
  test.describe.configure({ timeout: 60000 });

  test('suspense component renders placeholder with data-suspense attr', async ({ page }) => {
    const errors = await bootSandbox(page);
    expect(errors).toEqual([]);

    // Render a suspense component into the DOM
    const html = await page.evaluate(() => {
      const K = window.SxKernel;
      return K.eval('(render-to-dom (~shared:pages/suspense :id "test-slot" :fallback (div "Loading...")))');
    });

    // Mount it into the page
    await page.evaluate((h) => {
      const root = document.getElementById('sx-root');
      if (typeof h === 'object' && h instanceof Node) {
        root.appendChild(h);
      } else {
        // render-to-dom returns a DOM node — check if Sx.render works
        const node = window.SxKernel.eval('(render-to-dom (~shared:pages/suspense :id "test-slot" :fallback (div "Loading...")))');
        root.appendChild(node);
      }
    }, html);

    // Verify the placeholder exists with correct attributes
    const suspense = page.locator('[data-suspense="test-slot"]');
    await expect(suspense).toHaveCount(1);
    await expect(suspense).toContainText('Loading...');
  });

  test('__sxResolve replaces suspense placeholder content', async ({ page }) => {
    const errors = await bootSandbox(page);
    expect(errors).toEqual([]);

    // Render suspense placeholder
    await page.evaluate(() => {
      const K = window.SxKernel;
      const node = K.eval('(render-to-dom (~shared:pages/suspense :id "resolve-test" :fallback (div "skeleton")))');
      document.getElementById('sx-root').appendChild(node);
    });

    // Verify placeholder shows fallback
    await expect(page.locator('[data-suspense="resolve-test"]')).toContainText('skeleton');

    // Now resolve it — simulate server sending __sxResolve
    // Uses the real Sx.resolveSuspense (calls kernel's resolve-suspense fn)
    await page.evaluate(() => {
      window.__sxResolve('resolve-test', '(div :class "resolved" "Content loaded!")');
    });

    // Verify the placeholder content was replaced
    const slot = page.locator('[data-suspense="resolve-test"]');
    await expect(slot).toContainText('Content loaded!');
    await expect(slot).not.toContainText('skeleton');
  });

  test('multiple suspense slots resolve independently', async ({ page }) => {
    const errors = await bootSandbox(page);
    expect(errors).toEqual([]);

    // Render 3 suspense slots (like the streaming demo shell)
    await page.evaluate(() => {
      const K = window.SxKernel;
      const ids = ['stream-fast', 'stream-medium', 'stream-slow'];
      const root = document.getElementById('sx-root');
      for (const id of ids) {
        const node = K.eval(
          '(render-to-dom (~shared:pages/suspense :id "' + id + '" :fallback (div "Loading ' + id + '...")))'
        );
        root.appendChild(node);
      }
    });

    // All 3 placeholders should exist
    await expect(page.locator('[data-suspense]')).toHaveCount(3);
    await expect(page.locator('[data-suspense="stream-fast"]')).toContainText('Loading stream-fast');
    await expect(page.locator('[data-suspense="stream-slow"]')).toContainText('Loading stream-slow');

    // Resolve middle slot only — uses real kernel resolve-suspense
    await page.evaluate(() => {
      window.__sxResolve('stream-medium', '(div "Medium resolved!")');
    });

    // Medium resolved, others still loading
    await expect(page.locator('[data-suspense="stream-medium"]')).toContainText('Medium resolved!');
    await expect(page.locator('[data-suspense="stream-fast"]')).toContainText('Loading stream-fast');
    await expect(page.locator('[data-suspense="stream-slow"]')).toContainText('Loading stream-slow');

    // Resolve remaining slots
    await page.evaluate(() => {
      window.__sxResolve('stream-fast', '(div "Fast resolved!")');
      window.__sxResolve('stream-slow', '(div "Slow resolved!")');
    });

    await expect(page.locator('[data-suspense="stream-fast"]')).toContainText('Fast resolved!');
    await expect(page.locator('[data-suspense="stream-slow"]')).toContainText('Slow resolved!');
  });

  test('streaming-demo-data returns iterable list of dicts with stream-ids', async ({ page }) => {
    const errors = await bootSandbox(page);
    expect(errors).toEqual([]);

    // Verify the data function returns a list that can be iterated
    // (catches ListRef vs List type mismatch)
    const result = await page.evaluate(() => {
      const K = window.SxKernel;
      try {
        const type = K.eval('(type-of (streaming-demo-data))');
        const len = K.eval('(len (streaming-demo-data))');
        const ids = K.eval('(join "," (map (fn (item) (get item "stream-id")) (streaming-demo-data)))');
        const delays = K.eval('(join "," (map (fn (item) (str (get item "delay"))) (streaming-demo-data)))');
        return { type, len, ids, delays };
      } catch(e) {
        return { error: e.message };
      }
    });

    expect(result.error || '').toBe('');
    expect(result.type).toBe('list');
    expect(result.len).toBe(3);
    expect(result.ids).toContain('stream-fast');
    expect(result.ids).toContain('stream-medium');
    expect(result.ids).toContain('stream-slow');
    expect(result.delays).toContain('1000');
  });

  test('streaming shell renders with outer layout gutters', async ({ page }) => {
    const errors = await bootSandbox(page);
    expect(errors).toEqual([]);

    // Render shell wrapped in outer layout (like the server does)
    const result = await page.evaluate(() => {
      const K = window.SxKernel;
      try {
        const html = K.eval(`(render-to-html
          (~shared:layout/app-body :content
            (~streaming-demo/layout
              (~shared:pages/suspense :id "stream-fast"
                :fallback (~streaming-demo/stream-skeleton))
              (~shared:pages/suspense :id "stream-medium"
                :fallback (~streaming-demo/stream-skeleton))
              (~shared:pages/suspense :id "stream-slow"
                :fallback (~streaming-demo/stream-skeleton)))))`);
        document.getElementById('sx-root').innerHTML = html;
        return { ok: true };
      } catch (e) {
        return { ok: false, error: e.message };
      }
    });

    expect(result.error || '').toBe('');

    // Should have outer layout structure (gutters)
    await expect(page.locator('#root-panel')).toHaveCount(1);
    await expect(page.locator('#main-panel')).toHaveCount(1);

    // Should have 3 suspense slots inside the layout
    await expect(page.locator('[data-suspense]')).toHaveCount(3);

    // Should have the demo heading
    await expect(page.locator('h1')).toContainText('Streaming');
  });

  test('resolve fills suspense slots with chunk content end-to-end', async ({ page }) => {
    const errors = await bootSandbox(page);
    expect(errors).toEqual([]);

    // Phase 1: Render shell with suspense skeletons
    await page.evaluate(() => {
      const K = window.SxKernel;
      const html = K.eval(`(render-to-html
        (~shared:layout/app-body :content
          (~streaming-demo/layout
            (~shared:pages/suspense :id "stream-fast"
              :fallback (~streaming-demo/stream-skeleton))
            (~shared:pages/suspense :id "stream-medium"
              :fallback (~streaming-demo/stream-skeleton))
            (~shared:pages/suspense :id "stream-slow"
              :fallback (~streaming-demo/stream-skeleton)))))`);
      document.getElementById('sx-root').innerHTML = html;
    });

    // Verify skeletons are showing
    await expect(page.locator('[data-suspense="stream-fast"]')).toHaveCount(1);

    // Phase 2: Staggered resolve — simulates chunked transfer with async IO delays.
    // Each resolve arrives independently; other slots remain as skeletons.

    // Resolve "fast" first (~1s in production) — others still loading
    await page.evaluate(() => {
      window.__sxResolve('stream-fast',
        '(~streaming-demo/chunk :stream-label "Fast source" :stream-color "emerald" :stream-message "Resolved in ~1s (async IO)" :stream-time "~1s")');
    });
    await expect(page.locator('[data-suspense="stream-fast"]')).toContainText('Fast source');
    // Medium and slow still show skeletons
    await expect(page.locator('[data-suspense="stream-medium"]')).not.toContainText('Medium source');
    await expect(page.locator('[data-suspense="stream-slow"]')).not.toContainText('Slow source');

    // Resolve "medium" (~3s in production) — slow still loading
    await page.evaluate(() => {
      window.__sxResolve('stream-medium',
        '(~streaming-demo/chunk :stream-label "Medium source" :stream-color "amber" :stream-message "Resolved in ~3s (async IO)" :stream-time "~3s")');
    });
    await expect(page.locator('[data-suspense="stream-medium"]')).toContainText('Medium source');
    await expect(page.locator('[data-suspense="stream-slow"]')).not.toContainText('Slow source');

    // Resolve "slow" (~5s in production) — all done
    await page.evaluate(() => {
      window.__sxResolve('stream-slow',
        '(~streaming-demo/chunk :stream-label "Slow source" :stream-color "violet" :stream-message "Resolved in ~5s (async IO)" :stream-time "~5s")');
    });

    // Phase 3: All slots filled with actual content
    await expect(page.locator('[data-suspense="stream-fast"]')).toContainText('Fast source');
    await expect(page.locator('[data-suspense="stream-fast"]')).toContainText('~1s');
    await expect(page.locator('[data-suspense="stream-medium"]')).toContainText('Medium source');
    await expect(page.locator('[data-suspense="stream-medium"]')).toContainText('~3s');
    await expect(page.locator('[data-suspense="stream-slow"]')).toContainText('Slow source');
    await expect(page.locator('[data-suspense="stream-slow"]')).toContainText('~5s');
  });
});

// =========================================================================
// Chunked transfer test — spins up a real HTTP server with chunked encoding,
// serves the actual page using the real WASM kernel + sx-platform.js +
// component defs. Verifies resolve scripts execute and fill suspense slots.
// =========================================================================
const http = require('http');

function buildStreamingPage() {
  // Read component defs (same as server sends in <script type="text/sx">)
  const compFiles = [
    'shared/sx/templates/tw.sx', 'shared/sx/templates/tw-layout.sx',
    'shared/sx/templates/tw-type.sx', 'shared/sx/templates/pages.sx',
    'sx/sx/streaming-demo.sx',
  ];
  const compDefs = compFiles.map(f => readFile(f)).join('\n');

  // Shell body — suspense placeholders with script src tags (like real site)
  const shellBody = `<!doctype html><html><head><meta charset="utf-8"></head><body>
<div id="sx-root">
  <h1>Streaming &amp; Suspense Demo</h1>
  <div data-suspense="stream-fast" id="sx-suspense-stream-fast" style="display:contents">
    <div class="animate-pulse">Loading fast...</div>
  </div>
  <div data-suspense="stream-medium" id="sx-suspense-stream-medium" style="display:contents">
    <div class="animate-pulse">Loading medium...</div>
  </div>
  <div data-suspense="stream-slow" id="sx-suspense-stream-slow" style="display:contents">
    <div class="animate-pulse">Loading slow...</div>
  </div>
</div>
<script type="text/sx">${compDefs.replace(/<\//g, '<\\/')}</script>
<script src="/wasm/sx_browser.bc.js"></script>
<script src="/wasm/sx-platform.js"></script>`;

  // Bootstrap (same as _sx_streaming_bootstrap in sx_server.ml)
  const bootstrap = `<script>window.__sxPending=[];window.__sxResolve=function(i,s){` +
    `if(window.Sx&&Sx.resolveSuspense){Sx.resolveSuspense(i,s)}` +
    `else{window.__sxPending.push({id:i,sx:s})}}</script>`;

  // Resolve scripts (same as sx_streaming_resolve_script produces)
  const resolves = [
    { id: 'stream-fast', sx: '(div "Fast source resolved")', delay: 500 },
    { id: 'stream-medium', sx: '(div "Medium source resolved")', delay: 1000 },
    { id: 'stream-slow', sx: '(div "Slow source resolved")', delay: 1500 },
  ];

  const tail = '\n</body></html>';

  return { shellBody, bootstrap, resolves, tail };
}

function startStreamingServer() {
  const parts = buildStreamingPage();
  const wasmSrc = fs.readFileSync(path.join(WASM_DIR, 'sx_browser.bc.js'), 'utf8');
  const platformSrc = fs.readFileSync(path.join(PROJECT_ROOT, 'shared/static/wasm/sx-platform.js'), 'utf8');

  const server = http.createServer((req, res) => {
    // Serve static files from wasm directory (kernel, platform, .sxbc modules)
    if (req.url.startsWith('/wasm/') || req.url.startsWith('/static/wasm/')) {
      const relPath = req.url.replace('/static', '');
      const filePath = path.join(WASM_DIR, relPath.replace('/wasm/', ''));
      try {
        const data = fs.readFileSync(filePath);
        const ct = filePath.endsWith('.js') ? 'application/javascript'
          : filePath.endsWith('.sx') ? 'text/plain' : 'application/octet-stream';
        res.writeHead(200, { 'Content-Type': ct });
        res.end(data);
        return;
      } catch(e) {
        res.writeHead(404);
        res.end('Not found: ' + filePath);
        return;
      }
    }

    // Streaming page — chunked transfer
    res.writeHead(200, {
      'Content-Type': 'text/html; charset=utf-8',
      'Transfer-Encoding': 'chunked',
    });
    // Chunk 1: shell body (suspense placeholders + script tags for kernel/platform)
    res.write(parts.shellBody);
    // Chunk 2: bootstrap
    res.write(parts.bootstrap);
    // Chunks 3-5: resolve scripts with staggered delays
    let i = 0;
    function sendNext() {
      if (i >= parts.resolves.length) {
        res.end(parts.tail);
        return;
      }
      const r = parts.resolves[i++];
      setTimeout(() => {
        const script = `<script>window.__sxResolve&&window.__sxResolve(${JSON.stringify(r.id)},${JSON.stringify(r.sx)})</script>`;
        res.write(script);
        sendNext();
      }, r.delay);
    }
    sendNext();
  });
  return new Promise(resolve => {
    server.listen(0, () => resolve(server));
  });
}

test.describe('Streaming chunked server', () => {
  test.describe.configure({ timeout: 120000 });
  let server;
  let serverUrl;

  test.beforeAll(async () => {
    server = await startStreamingServer();
    const port = server.address().port;
    serverUrl = `http://localhost:${port}`;
  });

  test.afterAll(async () => {
    if (server) server.close();
  });

  test('suspense slots resolve via chunked transfer', async ({ page }) => {
    const consoleErrors = [];
    page.on('console', msg => { if (msg.type() === 'error') consoleErrors.push(msg.text()); });
    page.on('pageerror', e => consoleErrors.push('PAGE_ERROR: ' + e.message));

    await page.goto(serverUrl, { waitUntil: 'commit', timeout: 60000 });

    // Wait for WASM kernel + platform to boot
    await page.waitForFunction('!!window.Sx && !!window.Sx.resolveSuspense', { timeout: 60000 });

    // Shell should render with 3 suspense placeholders
    await expect(page.locator('[data-suspense]')).toHaveCount(3);

    // Debug: check state + console errors
    const dbg = await page.evaluate(() => ({
      pending: window.__sxPending,
      fastText: document.querySelector('[data-suspense="stream-fast"]')?.textContent?.substring(0, 40),
    }));
    console.log('CHUNKED DEBUG:', JSON.stringify(dbg), 'errors:', consoleErrors.slice(0, 5));

    // Wait for all resolves (500ms + 1000ms + 1500ms = 3s total, plus boot time)
    await expect(page.locator('[data-suspense="stream-fast"]'))
      .toContainText('Fast source resolved', { timeout: 15000 });
    await expect(page.locator('[data-suspense="stream-medium"]'))
      .toContainText('Medium source resolved', { timeout: 15000 });
    await expect(page.locator('[data-suspense="stream-slow"]'))
      .toContainText('Slow source resolved', { timeout: 15000 });
  });

  test('__sxResolve is defined after boot', async ({ page }) => {
    await page.goto(serverUrl, { waitUntil: 'commit', timeout: 60000 });
    // Wait for full boot (not just Sx.resolveSuspense, which is available eagerly)
    await page.waitForFunction(
      '!!document.documentElement.getAttribute("data-sx-ready")',
      { timeout: 60000 }
    );

    const state = await page.evaluate(() => ({
      resolveType: typeof window.__sxResolve,
      hasSx: typeof window.Sx,
      sxKeys: window.Sx ? Object.keys(window.Sx).join(',') : 'no Sx',
      sxResolveSuspense: typeof (window.Sx && Sx.resolveSuspense),
      pending: window.__sxPending,
    }));

    expect(state.resolveType).toBe('function');
    expect(state.sxResolveSuspense).toBe('function');
    // Pending should be drained (null) after boot
    expect(state.pending).toBeNull();
  });
});

// =========================================================================
// Live server tests — verify the actual chunked response works end-to-end
// =========================================================================
const BASE_URL = process.env.SX_TEST_URL || 'http://localhost:8013';

test.describe('Streaming live server', () => {
  test.describe.configure({ timeout: 30000 });

  test('streaming page resolves suspense slots from server', async ({ page }) => {
    // Load the streaming page from the actual server
    await page.goto(BASE_URL + '/sx/(geography.(isomorphism.streaming))', {
      waitUntil: 'networkidle',
      timeout: 20000,
    });

    // Should have 3 suspense slots
    await expect(page.locator('[data-suspense]')).toHaveCount(3);

    // All 3 slots should have resolved content (not empty, not just skeletons)
    await expect(page.locator('[data-suspense="stream-fast"]')).toContainText('Fast source', { timeout: 15000 });
    await expect(page.locator('[data-suspense="stream-medium"]')).toContainText('Medium source', { timeout: 15000 });
    await expect(page.locator('[data-suspense="stream-slow"]')).toContainText('Slow source', { timeout: 15000 });
  });

  test('no chunked encoding errors in console', async ({ page }) => {
    const errors = [];
    page.on('pageerror', e => errors.push(e.message));
    page.on('console', msg => {
      if (msg.type() === 'error') errors.push(msg.text());
    });

    await page.goto(BASE_URL + '/sx/(geography.(isomorphism.streaming))', {
      waitUntil: 'networkidle',
      timeout: 20000,
    });

    // Filter for chunked encoding errors specifically
    const chunkErrors = errors.filter(e =>
      e.includes('CHUNKED') || e.includes('INCOMPLETE') || e.includes('ERR_EMPTY'));
    expect(chunkErrors).toEqual([]);
  });
});
