Skip to content

[Detail Bug] Observability: Test suite detail page shows blank header and malformed tab title instead of error state on fetch failure #32683

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_3377c26d-da48-4ccd-b83a-22c542f4fe83/bugs/bug_d54842da-0e22-4e1a-a0b4-4f1c65a5f86b

Introduced in #31911 by @karanh37 on Aug 29, 2026

Summary

  • Context: ObservabilityPageShell is the shared layout/title wrapper for the observability detail pages; it renders <DocumentTitle title={pageTitle} /> (a react-helmet-async Helmet) which writes the browser tab title as `${pageTitle} | Collate`.
  • Bug: TestSuiteDetail never guards against testSuite being undefined after the suite fetch errors (e.g. a 404 for a just-deleted suite), so it falls through its loading and permission checks and renders ObservabilityPageShell with pageTitle = t('label.entity-detail-plural', { entity: getEntityName(testSuite) }) where getEntityName(testSuite) returns ''.
  • Actual vs. expected: Instead of showing an ErrorPlaceHolder for the failed fetch — what the sibling detail pages (TestCaseDetail, AlertDetailsPage) already do — the page renders an empty header (every header field is guarded by optional chaining / getEntityName, so it renders blank, no crash) and the shell writes a malformed browser-tab title " Details | Collate" (the entity-detail-plural template "{{entity}} Details" interpolates to " Details").
  • Impact: Users hitting a first-fetch failure for a test suite (suite deleted between the permission GET and the entity GET, a 5xx that exhausts the 2× retry policy, or a non-HTTP network error that exhausts retries) see a degraded page with an empty header and a malformed tab title rather than a clean error state. An error toast does fire from the hook, so the user gets some signal — the symptom is cosmetic/UX-polish, not a crash.

Code with Bug

if (isLoading) {
  return <Loader />;
}

if (!testSuitePermissions.ViewAll && !testSuitePermissions.ViewBasic) {
  return (
    <ErrorPlaceHolder
      className="border-none"
      permissionValue={t('label.view-entity', {
        entity: t('label.test-suite'),
      })}
      type={ERROR_PLACEHOLDER_TYPE.PERMISSION}
    />
  );
}

return (
  <ObservabilityPageShell
    data-testid="test-suite-detail-page"
    header={...}
    pageTitle={t('label.entity-detail-plural', {
      entity: getEntityName(testSuite), // <-- BUG 🔴 testSuite can be undefined here; becomes " Details"
    })}>
<DocumentTitle title={pageTitle} />   // <-- BUG 🔴 pageTitle can be " Details" → tab title " Details | Collate"

Explanation

  • The suite query’s isLoading becomes false when the query settles, including on error; on a fetch error testSuite remains undefined.
  • TestSuiteDetail only guards for loading and permission, so on error it still renders ObservabilityPageShell.
  • getEntityName(testSuite) is entity?.displayName || entity?.name || '', so it returns '' for undefined and produces the interpolated i18n title " Details" ("{{entity}} Details"). DocumentTitle appends the brand name verbatim, resulting in " Details | Collate".
  • Header fields are all optional-chained, so the page does not crash; it silently renders a blank header instead of an error placeholder.

Codebase Inconsistency

Sibling observability detail pages handle the same error state by returning <ErrorPlaceHolder /> after the permission check:

// TestCaseDetail.tsx
if (isLoading) { return <Loader />; }
if (!testCasePermissions.ViewAll && !testCasePermissions.ViewBasic) {
  return <ErrorPlaceHolder /* ...permission... */ />;
}
if (isUndefined(testCase)) {
  return <ErrorPlaceHolder />;
}

Failing Test

it('renders malformed tab title and no ErrorPlaceHolder when testSuite fetch errors', () => {
  let capturedTitle: string | undefined;
  // Override the auto-mock to capture the title prop instead of returning null
  jest
    .requireMock('components/common/DocumentTitle/DocumentTitle')
    .default.mockImplementation(({ title }: { title: string }) => {
      capturedTitle = title;
      return null;
    });

  mockUseTestSuiteDetailsPage.mockReturnValue({
    testSuite: undefined,                          // fetch errored
    isLoading: false,                              // query settled
    testSuitePermissions: { ViewAll: true, ViewBasic: true },  // permission guard passes
    testSuiteError: undefined,
    breadcrumbs: [],
    testCaseData: [],
    // ...rest of required fields populated as in the happy-path mock
  });

  render(<TestSuiteDetail />, { wrapper: MemoryRouter });

  // (a) Current buggy behaviour — assert to flip to ErrorPlaceHolder after the fix
  expect(screen.queryByTestId('error-placeholder')).not.toBeInTheDocument();
  // (b) The malformed title
  expect(capturedTitle).toBe(' Details');          // → " Details | Collate" tab title
});

Recommended Fix

Add an isUndefined(testSuite) guard after the permission check so permission-denied users still see the permission placeholder, mirroring sibling pages:

import { isUndefined } from 'lodash';
// ...
if (isLoading) {
  return <Loader />;
}

if (!testSuitePermissions.ViewAll && !testSuitePermissions.ViewBasic) {
  return (
    <ErrorPlaceHolder
      className="border-none"
      permissionValue={t('label.view-entity', { entity: t('label.test-suite') })}
      type={ERROR_PLACEHOLDER_TYPE.PERMISSION}
    />
  );
}

if (isUndefined(testSuite)) {          // <-- FIX 🟢 render error state on fetch failure
  return <ErrorPlaceHolder />;
}

History

This bug was introduced in commit 2807684.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Done ✅

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions