Skip to content

fix(oc-docs): contextual error messages for failed try-it requests (BRU-3408)#40

Open
sundram-bruno wants to merge 2 commits into
opencollection-dev:mainfrom
sundram-bruno:fix/bru-3408-try-it-error-messages
Open

fix(oc-docs): contextual error messages for failed try-it requests (BRU-3408)#40
sundram-bruno wants to merge 2 commits into
opencollection-dev:mainfrom
sundram-bruno:fix/bru-3408-try-it-error-messages

Conversation

@sundram-bruno

@sundram-bruno sundram-bruno commented Jun 9, 2026

Copy link
Copy Markdown

Problem

A failed try-it request in the collection docs showed only an opaque "Request Failed / Failed to fetch", and the runner hardcoded every network failure as a "CORS error" — including connection-refused, same-origin, and timeouts (see opencollection #24). Timeouts were also missed: the catch checked for AbortError, but AbortSignal.timeout() throws a TimeoutError, so real timeouts leaked the raw "signal timed out".

Approach

Browser fetch collapses CORS, DNS, connection-refused, offline, and TLS into one opaque failure with no detail — the real cause lives only in devtools. So we classify from the request context the browser does expose (timeout, page vs target scheme, same-origin vs cross-origin / file), not the error text.

Five cases

Case Detected by Message
timeout abort/timeout signal "Request timed out. The server didn't respond in time."
mixed-content page https, target http "Request blocked: this page is secure (https) but the URL is insecure (http). Use an https URL, or run it from the Bruno desktop app."
browser-blocked cross-origin, or docs opened from a file "Request blocked by your browser, usually CORS: the API didn't allow requests from this page. Try it in the Bruno desktop app."
unreachable same-origin "Couldn't reach the server. It may be down, or the URL may be wrong."
unknown non-network error / unparseable URL the underlying error message

Same-vs-different is compared by origin (scheme+host+port), not site, because CORS is enforced per-origin (https://docs.example.comhttps://api.example.com is cross-origin even though both are example.com). CORS is suggested only for cross-origin or opened-from-file failures, never same-origin (AC #2).

Changes

  • classifyRequestError — pure classifier returning { type, title, message }; takes the resolved request URL + the page URL (window.location.href, passed in to stay pure). Unparseable URL → underlying message.
  • RequestExecutor — catch delegates to the classifier; removed the hardcoded CORS string and the dead network/ssl branches fetch can never produce.
  • ErrorBanner — small reusable UI (bold title + monospace message) mirroring Bruno desktop's response error banner.
  • ResponsePane — banner renders inside the Response tab (consistent with a success response); status bar hidden on failure (no HTTP status exists).
  • 4xx/5xx remain normal responses. Response display, default timeout, and OAuth2 handling are unchanged.

Tests

  • Unit (classifyRequestError.spec.ts): all five cases + edges (cross-origin, file origin, same-origin never-CORS, unparseable URL, non-Error throw).
  • E2E (request-errors.spec.ts): cross-origin → browser-blocked (inside Response tab), same-origin → unreachable (never mentions CORS), 4xx → renders normally. First e2e to actually drive Try → Send; uses page.route for deterministic failures.

All existing e2e (63) and unit tests pass; new code is lint-clean.

Acceptance criteria

  • Failure classified from request context (timeout; scheme; same-origin vs cross-origin; file)
  • Blanket "CORS error" removed; CORS only for cross-origin / file, never same-origin
  • timeout / mixed-content / browser-blocked / unreachable each show their message; anything else shows the underlying error
  • Only the failure message changes; response display, default timeout, OAuth2 unchanged
  • A test per classified case

Out of scope (follow-ups)

  • Script/assertion error display (rich script-error card)
  • Response-body view modes (rendered HTML / preview), per Improve Error Message Display #24's 404 example
  • Native error codes (needs a non-browser execution path)

🤖 Generated with Claude Code

sundram-bruno and others added 2 commits June 10, 2026 01:53
…RU-3408)

A failed try-it request previously showed an opaque "Request Failed /
Failed to fetch" and hardcoded every network failure as a "CORS error" —
including connection-refused and timeouts. Timeouts were also missed
entirely (the catch checked for `AbortError`, but `AbortSignal.timeout()`
throws a `TimeoutError`), so they leaked the raw "signal timed out".

Browser fetch collapses CORS, DNS, connection-refused, offline, and TLS
into one opaque `TypeError: Failed to fetch`, so only two states are
actually distinguishable in code: timeout and opaque. Anything else is
reported as uncategorized rather than guessed at.

Changes:
- Add `classifyRequestError` — a pure classifier returning a structured
  { type, title, message, hint } for the three states (timeout / opaque /
  uncategorized). The opaque message is CORS-first but hedged, and names
  the file-origin `null` case, without asserting CORS as certain.
- RequestExecutor delegates its catch to the classifier; remove the
  hardcoded CORS string and the dead network/ssl branches that fetch can
  never produce.
- Add a reusable `ErrorBanner` UI (bold title, monospace message, optional
  next-step hint) mirroring Bruno desktop's response error banner.
- Render the banner inside the Response tab (keeping the tab shell) and
  hide the status bar on failure, since no HTTP status exists.
- 4xx/5xx remain normal responses (not failures).

Tests:
- Unit: classifyRequestError — all three states plus edge cases.
- E2E: opaque banner renders in the Response tab, never mislabels as a
  definite CORS error, and a 4xx renders normally.

Out of scope (separate follow-ups): script/assertion error display, and
response-body view modes (rendered HTML / preview).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refine the failure classification per the updated ticket: instead of one
opaque "almost always CORS" bucket, classify from the request context the
browser does expose — whether it timed out, the page vs target scheme, and
same-origin vs cross-origin / file.

Five cases:
- timeout            -> "Request timed out. The server didn't respond in time."
- mixed-content      -> secure page (https) calling an insecure (http) URL
- browser-blocked    -> cross-origin, or docs opened from a file (CORS)
- unreachable        -> same-origin failure (server down / wrong URL)
- unknown            -> the underlying error message

Same-vs-different is compared by ORIGIN (scheme+host+port), not site, since
CORS is enforced per-origin. CORS is now suggested only for cross-origin or
opened-from-file failures, never same-origin (AC opencollection-dev#2). An unparseable request
URL (e.g. an unresolved {{var}}) falls through to the underlying message.

classifyRequestError now takes the resolved request URL and the page URL
(window.location.href, passed in to keep it pure). The errorHint field is
dropped — the new copy embeds the next step in the message. Response display,
default timeout, and OAuth2 handling are unchanged.

Tests reworked to cover all five cases (unit) and browser-blocked,
same-origin-unreachable, and 4xx-not-a-failure (e2e).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants