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.
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
ObservabilityPageShellis the shared layout/title wrapper for the observability detail pages; it renders<DocumentTitle title={pageTitle} />(a react-helmet-asyncHelmet) which writes the browser tab title as`${pageTitle} | Collate`.TestSuiteDetailnever guards againsttestSuitebeingundefinedafter the suite fetch errors (e.g. a 404 for a just-deleted suite), so it falls through its loading and permission checks and rendersObservabilityPageShellwithpageTitle = t('label.entity-detail-plural', { entity: getEntityName(testSuite) })wheregetEntityName(testSuite)returns''.ErrorPlaceHolderfor 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"(theentity-detail-pluraltemplate"{{entity}} Details"interpolates to" Details").Code with Bug
Explanation
isLoadingbecomesfalsewhen the query settles, including on error; on a fetch errortestSuiteremainsundefined.TestSuiteDetailonly guards for loading and permission, so on error it still rendersObservabilityPageShell.getEntityName(testSuite)isentity?.displayName || entity?.name || '', so it returns''forundefinedand produces the interpolated i18n title" Details"("{{entity}} Details").DocumentTitleappends the brand name verbatim, resulting in" Details | Collate".Codebase Inconsistency
Sibling observability detail pages handle the same error state by returning
<ErrorPlaceHolder />after the permission check:Failing Test
Recommended Fix
Add an
isUndefined(testSuite)guard after the permission check so permission-denied users still see the permission placeholder, mirroring sibling pages:History
This bug was introduced in commit 2807684.