Skip to content

test: de-flake lazy <webjs-frame> self-load e2e (racy body capture) #811

Description

@vivek7405

Problem

The e2e test frame: a lazy <webjs-frame src> self-loads on viewport entry, sending x-webjs-frame (test/e2e/e2e.test.mjs:2600) is intermittently red. It failed once on PR #810 on a docs-only commit that cannot touch the blog runtime, then passed on re-run with no code change. The framework behaviour is CORRECT here (all functional assertions pass); the flake is in the TEST'S response-body capture.

The failing assertion is line 2654: assert.ok(frameResponses.length >= 1, 'captured the deferred self-load response'). Every assertion before it passes, so the lazy self-load fired, the x-webjs-frame: deferred request was sent, and the content swapped in. Only the response-BODY capture failed.

Root cause: the response handler reads the body asynchronously and swallows failures:

const onResp = async (resp) => {
  const h = resp.request().headers();
  if (h['x-webjs-frame'] === 'deferred') {
    try { frameResponses.push({ url: resp.url(), body: await resp.text() }); } catch { /* ignore */ }
  }
};

loadFrame (packages/core/src/router-client.js) does fetch() then reads the body to apply the subtree, so the browser can free the response body before the handler's await resp.text() resolves. Puppeteer then throws ("Could not load body for this request"), the silent catch eats it, frameResponses stays empty, and line 2654 fails. The swallowing catch is what turns a transient capture race into an intermittent CI red instead of a loud, obvious failure.

Design / approach

Make the response-body capture robust and non-silent:

  • Prefer capturing the body EAGERLY at response time in a way that does not race the browser freeing it (e.g. buffer it immediately, or intercept via the request-scoped capture the harness already uses elsewhere), rather than an await resp.text() that can lose the body.
  • Remove the swallowing catch (or narrow it and record the failure) so a genuine capture failure is a clear assertion, not an empty array.
  • Alternatively, assert the subtree-render optimization (Add src-driven frame self-loading and server-scoped frame render #253: the response is the <webjs-frame id="deferred"> subtree, not a full document) via a mechanism that does not depend on re-reading a consumed fetch body, since the functional DOM assertions already prove the self-load worked.

Keep the functional assertions (request sent, content swapped, subtree-only) intact; only the body-capture path is fragile.

Implementation notes (for the implementing agent)

  • Where: test/e2e/e2e.test.mjs, the test at L2600 (the onResp handler L2612-2616 and the L2654 assertion). Look at how other frame/stream tests in the same file capture bodies for a non-racy pattern to reuse.
  • Landmine: the framework is NOT at fault. loadFrame reading the fetch body is correct; do not "fix" the runtime. This is purely a test-harness reliability fix.
  • Landmine: a swallowing catch { /* ignore */ } around an async body read is the anti-pattern; any replacement must make a real capture failure LOUD (so the test never again goes red without a diagnosable reason).
  • Related: the client-router flake work in dogfood: test client-router query-param preservation on every nav path #639 / dogfood: iOS back-swipe gesture flashes a blank page (client router) #641; and the "de-flake the prefetch e2e" precedent. Consider whether a shared robust-body-capture helper belongs alongside those.
  • Invariants: the test must still verify Add src-driven frame self-loading and server-scoped frame render #253 (subtree-only response) and the lazy self-load (x-webjs-frame: deferred request + content swap).
  • Tests + docs: e2e layer only; no doc surface (pure test reliability).

Acceptance criteria

  • The lazy-frame self-load test captures the deferred response deterministically (no reliance on a racy await resp.text() that can lose the body).
  • The swallowing catch is removed or narrowed so a real capture failure fails loudly with a diagnosable message.
  • The functional assertions (request sent, content swapped, subtree-only <webjs-frame id="deferred"> body, no full-document shell) are preserved.
  • The test passes reliably across repeated runs (e.g. run it in a loop locally to confirm it no longer flakes).

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions