From b81f25ad749d0b625dacfdfe90472ec76d39ad80 Mon Sep 17 00:00:00 2001 From: "privilegedescalation-engineer[bot]" <269729446+privilegedescalation-engineer[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 14:05:48 +0000 Subject: [PATCH] fix: combine E2E infrastructure fixes (selectors + metrics heading + timeout) (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QA + CTO approved. CI + E2E passing. E2E test fix PR — UAT via automated suite. Merged by CEO. --- e2e/intel-gpu.spec.ts | 8 ++++---- src/api/IntelGpuDataContext.test.tsx | 23 +++++++++++++++++++++++ src/api/IntelGpuDataContext.tsx | 21 ++++++++++++++++++--- src/components/MetricsPage.test.tsx | 4 +++- src/components/MetricsPage.tsx | 11 +++++------ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/e2e/intel-gpu.spec.ts b/e2e/intel-gpu.spec.ts index 7b58492..df4b62d 100644 --- a/e2e/intel-gpu.spec.ts +++ b/e2e/intel-gpu.spec.ts @@ -43,7 +43,7 @@ test.describe('Intel GPU plugin smoke tests', () => { test('device plugins page renders or shows empty state', async ({ page }) => { await page.goto('/c/main/intel-gpu/device-plugins'); - await expect(page.getByRole('heading', { name: /device plugin/i })).toBeVisible({ + await expect(page.getByRole('heading', { name: /Intel GPU — Device Plugins/i })).toBeVisible({ timeout: 15_000, }); @@ -66,13 +66,13 @@ test.describe('Intel GPU plugin smoke tests', () => { }); await page.goto('/c/main/intel-gpu/nodes'); - await expect(page.getByRole('heading', { name: /node/i })).toBeVisible({ timeout: 15_000 }); + await expect(page.getByRole('heading', { name: /Intel GPU — Nodes/i })).toBeVisible({ timeout: 15_000 }); await page.goto('/c/main/intel-gpu/pods'); - await expect(page.getByRole('heading', { name: /pod/i })).toBeVisible({ timeout: 15_000 }); + await expect(page.getByRole('heading', { name: /Intel GPU — Pods/i })).toBeVisible({ timeout: 15_000 }); await page.goto('/c/main/intel-gpu/metrics'); - await expect(page.getByRole('heading', { name: /metric/i })).toBeVisible({ timeout: 15_000 }); + await expect(page.getByRole('heading', { name: /Intel GPU — Metrics/i })).toBeVisible({ timeout: 15_000 }); }); test('plugin settings page shows intel-gpu plugin entry', async ({ page }) => { diff --git a/src/api/IntelGpuDataContext.test.tsx b/src/api/IntelGpuDataContext.test.tsx index 631a395..d9e97bf 100644 --- a/src/api/IntelGpuDataContext.test.tsx +++ b/src/api/IntelGpuDataContext.test.tsx @@ -151,4 +151,27 @@ describe('IntelGpuDataProvider', () => { expect(callCountAfter).toBeGreaterThan(callCountBefore); }); }); + + it('treats a hanging CRD request as unavailable after 2s timeout', async () => { + vi.useFakeTimers(); + const nodeWrapper = { jsonData: {} }; + vi.mocked(K8s.ResourceClasses.Node.useList).mockReturnValue([[nodeWrapper], null] as any); + vi.mocked(K8s.ResourceClasses.Pod.useList).mockReturnValue([[nodeWrapper], null] as any); + vi.mocked(ApiProxy.request) + .mockReturnValueOnce(new Promise(() => {})) + .mockResolvedValueOnce({ items: [] }) + .mockResolvedValueOnce({ items: [] }) + .mockResolvedValueOnce({ items: [] }); + + const { result } = renderHook(() => useIntelGpuContext(), { wrapper: Wrapper }); + + expect(result.current.loading).toBe(true); + + vi.advanceTimersByTime(2000); + await act(async () => {}); + expect(result.current.crdAvailable).toBe(false); + expect(result.current.loading).toBe(false); + + vi.useRealTimers(); + }); }); diff --git a/src/api/IntelGpuDataContext.tsx b/src/api/IntelGpuDataContext.tsx index bf724a9..bd4beb8 100644 --- a/src/api/IntelGpuDataContext.tsx +++ b/src/api/IntelGpuDataContext.tsx @@ -69,6 +69,18 @@ export function useIntelGpuContext(): IntelGpuContextValue { // Helpers // --------------------------------------------------------------------------- +const DEFAULT_REQUEST_TIMEOUT_MS = 2_000; + +/** Wraps a promise with a timeout, rejecting if it doesn't settle within ms. */ +function withTimeout(promise: Promise, ms: number): Promise { + return Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error(`Request timed out after ${ms}ms`)), ms) + ), + ]); +} + /** Extract raw Kubernetes JSON from Headlamp KubeObject wrappers. */ const extractJsonData = (items: unknown[]): unknown[] => items.map(item => @@ -108,8 +120,11 @@ export function IntelGpuDataProvider({ children }: { children: React.ReactNode } try { // GpuDevicePlugin CRDs — graceful degradation if CRD not installed try { - const pluginList = await ApiProxy.request( - `/apis/${INTEL_DEVICE_PLUGIN_API_GROUP}/${INTEL_DEVICE_PLUGIN_API_VERSION}/gpudeviceplugins` + const pluginList = await withTimeout( + ApiProxy.request( + `/apis/${INTEL_DEVICE_PLUGIN_API_GROUP}/${INTEL_DEVICE_PLUGIN_API_VERSION}/gpudeviceplugins` + ), + DEFAULT_REQUEST_TIMEOUT_MS ); if (!cancelled && isKubeList(pluginList)) { setCrdAvailable(true); @@ -139,7 +154,7 @@ export function IntelGpuDataProvider({ children }: { children: React.ReactNode } for (const url of pluginPodSelectors) { try { - const list = await ApiProxy.request(url); + const list = await withTimeout(ApiProxy.request(url), DEFAULT_REQUEST_TIMEOUT_MS); if (!cancelled && isKubeList(list)) { const gpuPluginPods = filterIntelGpuPluginPods(list.items); foundPluginPods.push(...gpuPluginPods); diff --git a/src/components/MetricsPage.test.tsx b/src/components/MetricsPage.test.tsx index b92cae8..f775303 100644 --- a/src/components/MetricsPage.test.tsx +++ b/src/components/MetricsPage.test.tsx @@ -106,11 +106,13 @@ describe('MetricsPage', () => { vi.clearAllMocks(); }); - it('shows loader when ctxLoading=true', () => { + it('shows loader when ctxLoading=true but heading is visible immediately', () => { vi.mocked(useIntelGpuContext).mockReturnValue(makeContext({ loading: true })); // fetchGpuMetrics should never be called in loading state vi.mocked(fetchGpuMetrics).mockResolvedValue(null); render(); + // Heading renders immediately, loader appears below it while waiting for context + expect(screen.getByText('Intel GPU — Metrics')).toBeInTheDocument(); expect(screen.getByTestId('loader')).toHaveTextContent('Loading Intel GPU data...'); }); diff --git a/src/components/MetricsPage.tsx b/src/components/MetricsPage.tsx index 1493590..607bd45 100644 --- a/src/components/MetricsPage.tsx +++ b/src/components/MetricsPage.tsx @@ -230,10 +230,6 @@ export default function MetricsPage() { }; }, [ctxLoading, fetchSeq]); - if (ctxLoading) { - return ; - } - return ( <>
+ {ctxLoading && } + {fetching && !metrics && }