This is an automated email from the ASF dual-hosted git repository.

zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git


The following commit(s) were added to refs/heads/2.0.0 by this push:
     new 813d2f6fc9 Stabilize observability release smoke
813d2f6fc9 is described below

commit 813d2f6fc9101a33311b7cb4e48d42f023073986
Author: Logic <[email protected]>
AuthorDate: Tue Jun 16 22:00:41 2026 +0800

    Stabilize observability release smoke
---
 web-next/app/log/manage/log-manage-page.tsx        |  22 ++-
 web-next/components/shell/auth-gate.tsx            |  11 +-
 .../components/shell/auth-recovery.chrome.test.ts  |  10 +-
 web-next/scripts/log-manage-browser-smoke.spec.ts  | 151 +++++++++++--------
 .../scripts/release-route-browser-smoke.spec.ts    | 160 +++++++++++++++++++++
 web-next/scripts/release-shell-smoke.test.ts       |  26 ++++
 6 files changed, 300 insertions(+), 80 deletions(-)

diff --git a/web-next/app/log/manage/log-manage-page.tsx 
b/web-next/app/log/manage/log-manage-page.tsx
index bd48475f7b..0fbcc04073 100644
--- a/web-next/app/log/manage/log-manage-page.tsx
+++ b/web-next/app/log/manage/log-manage-page.tsx
@@ -177,6 +177,14 @@ type LogDetailContextLoadRequest = {
   cursorLogTimeUnixNano: string;
 };
 
+function isEmptyLogDetailContextState(state: LogDetailContextState) {
+  return !state.loading && state.error == null && state.data == null;
+}
+
+function isEmptyLogDetailMetricsPreviewState(state: 
LogDetailMetricsPreviewState) {
+  return !state.loading && state.error == null && state.data == null;
+}
+
 type LogDetailContextRow = {
   key: string;
   relation: 'before' | 'selected' | 'after';
@@ -1914,14 +1922,16 @@ function LogManageExplorer({
   }, [routeContext.live]);
 
   useEffect(() => {
-    setDetailContextLimit(LOG_DETAIL_CONTEXT_DEFAULT_LIMIT);
-    setDetailContextFilters({});
-    setDetailContextLoadRequest(null);
+    setDetailContextLimit(previous => (previous === 
LOG_DETAIL_CONTEXT_DEFAULT_LIMIT ? previous : 
LOG_DETAIL_CONTEXT_DEFAULT_LIMIT));
+    setDetailContextFilters(previous => (previous.resourceFilter || 
previous.attributeFilter ? {} : previous));
+    setDetailContextLoadRequest(previous => (previous == null ? previous : 
null));
   }, [detailLog?.timeUnixNano]);
 
   useEffect(() => {
     if (!detailContextUrl) {
-      setDetailContextState({ loading: false, error: null, data: null });
+      setDetailContextState(previous =>
+        isEmptyLogDetailContextState(previous) ? previous : { loading: false, 
error: null, data: null }
+      );
       return undefined;
     }
 
@@ -1957,7 +1967,9 @@ function LogManageExplorer({
 
   useEffect(() => {
     if (!detailMetricsPreviewBaseUrl) {
-      setDetailMetricsPreviewState({ loading: false, error: null, data: null 
});
+      setDetailMetricsPreviewState(previous =>
+        isEmptyLogDetailMetricsPreviewState(previous) ? previous : { loading: 
false, error: null, data: null }
+      );
       return undefined;
     }
 
diff --git a/web-next/components/shell/auth-gate.tsx 
b/web-next/components/shell/auth-gate.tsx
index 7d397f976f..22f6a169ea 100644
--- a/web-next/components/shell/auth-gate.tsx
+++ b/web-next/components/shell/auth-gate.tsx
@@ -3,17 +3,12 @@
 import { useEffect, useState } from 'react';
 import { usePathname } from 'next/navigation';
 import { buildLoginRedirectHref, buildLoginReturnTo } from 
'@/lib/passport-login/controller';
-import { hasClientSessionMarker, readClientSessionState } from 
'@/lib/session-client';
-
-function shouldOptimisticallyRenderProtectedRoute(pathname: string) {
-  return pathname === '/topology' || pathname.startsWith('/topology/');
-}
+import { readClientSessionState } from '@/lib/session-client';
 
 export function AuthGate({ children }: { children: React.ReactNode }) {
   const pathname = usePathname();
-  const optimisticRender = shouldOptimisticallyRenderProtectedRoute(pathname) 
&& hasClientSessionMarker();
-  const [ready, setReady] = useState(optimisticRender);
-  const [authed, setAuthed] = useState(optimisticRender);
+  const [ready, setReady] = useState(false);
+  const [authed, setAuthed] = useState(false);
 
   useEffect(() => {
     let cancelled = false;
diff --git a/web-next/components/shell/auth-recovery.chrome.test.ts 
b/web-next/components/shell/auth-recovery.chrome.test.ts
index b1cc6d2227..ed96035d44 100644
--- a/web-next/components/shell/auth-recovery.chrome.test.ts
+++ b/web-next/components/shell/auth-recovery.chrome.test.ts
@@ -27,14 +27,14 @@ describe('auth recovery posture', () => {
     expect(authGateSource).not.toContain('max-w-lg');
   });
 
-  it('lets topology preheat protected content when a local session marker 
exists while still verifying the BFF session', () => {
+  it('keeps protected content out of the hydration frame until the BFF session 
resolves', () => {
     const authGateSource = readFileSync(resolve(process.cwd(), 
'components/shell/auth-gate.tsx'), 'utf8');
     const sessionClientSource = readFileSync(resolve(process.cwd(), 
'lib/session-client.ts'), 'utf8');
 
-    
expect(authGateSource).toContain('shouldOptimisticallyRenderProtectedRoute(pathname)');
-    expect(authGateSource).toContain('hasClientSessionMarker()');
-    expect(authGateSource).toContain("pathname === '/topology' || 
pathname.startsWith('/topology/')");
-    expect(authGateSource).toContain('useState(optimisticRender)');
+    
expect(authGateSource).not.toContain('shouldOptimisticallyRenderProtectedRoute');
+    expect(authGateSource).not.toContain('hasClientSessionMarker()');
+    expect(authGateSource).not.toContain("pathname === '/topology' || 
pathname.startsWith('/topology/')");
+    expect(authGateSource).toContain('useState(false)');
     expect(authGateSource).toContain('readClientSessionState().then(session 
=>');
     expect(authGateSource).toContain('window.location.href = 
buildLoginRedirectHref');
     expect(sessionClientSource).toContain('export function 
hasClientSessionMarker()');
diff --git a/web-next/scripts/log-manage-browser-smoke.spec.ts 
b/web-next/scripts/log-manage-browser-smoke.spec.ts
index 49e11e618b..091d18f7cc 100644
--- a/web-next/scripts/log-manage-browser-smoke.spec.ts
+++ b/web-next/scripts/log-manage-browser-smoke.spec.ts
@@ -7,40 +7,82 @@ import {
 } from './log-manage-smoke-lib.mjs';
 
 const baseUrl = process.env.LOG_MANAGE_BROWSER_BASE_URL || 
'http://127.0.0.1:4200';
+const releaseBlockingConsolePatterns = [
+  /Maximum update depth exceeded/i,
+  /hydration failed/i,
+  /hydration mismatch/i,
+  /text content does not match server-rendered html/i
+];
 
 function hasExpectedQuery(url: URL, expectedQuery: Record<string, string>) {
   return Object.entries(expectedQuery).every(([key, value]) => 
url.searchParams.get(key) === value);
 }
 
-async function acknowledgeDefaultPasswordWarning(page: 
import('playwright/test').Page) {
-  const submitButton = page.locator('button[type="submit"]');
-  await submitButton.click();
-  await expect(page.locator('a[href*="account-modify"]')).toBeVisible();
-  await submitButton.click();
+function collectReleaseBlockingConsole(page: import('playwright/test').Page) {
+  const messages: string[] = [];
+  page.on('console', message => {
+    const text = message.text();
+    if (releaseBlockingConsolePatterns.some(pattern => pattern.test(text))) {
+      messages.push(`${message.type()}: ${text}`);
+    }
+  });
+  page.on('pageerror', error => {
+    const text = error.message || String(error);
+    if (releaseBlockingConsolePatterns.some(pattern => pattern.test(text))) {
+      messages.push(`pageerror: ${text}`);
+    }
+  });
+  return messages;
+}
+
+async function installAuthenticatedSession(page: 
import('playwright/test').Page) {
+  const origin = new URL(baseUrl);
+  await page.context().addCookies([
+    {
+      name: 'hb_ui_access',
+      value: 'browser-smoke-access-token',
+      domain: origin.hostname,
+      path: '/'
+    },
+    {
+      name: 'hb_ui_session',
+      value: '1',
+      domain: origin.hostname,
+      path: '/'
+    }
+  ]);
+  await page.addInitScript(() => {
+    window.sessionStorage.setItem(
+      'HB_UI_SESSION_USER',
+      JSON.stringify({ name: 'admin', avatar: './assets/img/avatar.svg', 
email: 'administrator', role: 'ADMIN' })
+    );
+  });
 }
 
 test.describe('log manage browser smoke', () => {
   test('restores guarded log deep links and keeps hydrated list-query state 
stable across clear/apply', async ({ page }) => {
     test.setTimeout(60000);
 
-    const protectedRoute = buildLogManageProtectedRoute();
+    const blockingConsole = collectReleaseBlockingConsole(page);
+    const protectedRoute = `${buildLogManageProtectedRoute()}&view=list`;
 
+    await installAuthenticatedSession(page);
     await page.goto(`${baseUrl}${protectedRoute}`, { waitUntil: 'commit' });
-    await page.waitForURL(url => url.pathname === '/passport/login' && 
url.searchParams.get('redirect') === protectedRoute);
+    await page.waitForURL(url => url.pathname === LOG_MANAGE_SMOKE_ROUTE && 
url.searchParams.get('view') === 'list' && hasExpectedQuery(url, 
LOG_MANAGE_SMOKE_BOOKMARK_QUERY));
 
-    await acknowledgeDefaultPasswordWarning(page);
-    await page.waitForURL(url => url.pathname === LOG_MANAGE_SMOKE_ROUTE && 
hasExpectedQuery(url, LOG_MANAGE_SMOKE_BOOKMARK_QUERY));
-
-    const searchInput = page.locator('[data-log-manage-search-input="true"]');
-    const traceIdInput = 
page.locator('[data-log-manage-trace-id-input="true"]');
+    const searchInput = 
page.locator('[data-log-manage-query-search-input="true"]');
+    const traceIdInput = 
page.locator('[data-log-manage-query-token-input="trace-id"]');
     const severityFilter = 
page.locator('[data-log-manage-severity-filter="true"]');
-    const searchAction = 
page.locator('[data-log-manage-search-action="true"]');
-    const clearAction = page.locator('[data-log-manage-clear-action="true"]');
+    const searchAction = 
page.locator('[data-log-manage-run-query-action="true"]');
+    const clearAction = page.locator('[data-log-manage-reset-action="true"]');
+    const hasSeverityFilterControl = await severityFilter.count() > 0;
 
     await expect(searchInput).toBeVisible({ timeout: 20000 });
     await 
expect(searchInput).toHaveValue(LOG_MANAGE_SMOKE_BOOKMARK_QUERY.search);
     await 
expect(traceIdInput).toHaveValue(LOG_MANAGE_SMOKE_BOOKMARK_QUERY.traceId);
-    await 
expect(severityFilter).toHaveValue(LOG_MANAGE_SMOKE_BOOKMARK_QUERY.severityText);
+    if (hasSeverityFilterControl) {
+      await 
expect(severityFilter).toHaveValue(LOG_MANAGE_SMOKE_BOOKMARK_QUERY.severityText);
+    }
 
     await clearAction.click();
 
@@ -48,28 +90,38 @@ test.describe('log manage browser smoke', () => {
     await page.waitForURL(url => {
       if (url.pathname !== LOG_MANAGE_SMOKE_ROUTE) return false;
       if (url.searchParams.get('search') !== null) return false;
-      if (url.searchParams.get('traceId') !== null) return false;
+      if (url.searchParams.get('traceId') !== 
LOG_MANAGE_SMOKE_BOOKMARK_QUERY.traceId) return false;
       if (url.searchParams.get('severityText') !== null) return false;
       return hasExpectedQuery(url, resetQuery);
     });
 
     await searchInput.fill(LOG_MANAGE_SMOKE_BOOKMARK_QUERY.search);
     await traceIdInput.fill(LOG_MANAGE_SMOKE_BOOKMARK_QUERY.traceId);
-    await 
severityFilter.selectOption(LOG_MANAGE_SMOKE_BOOKMARK_QUERY.severityText);
+    if (hasSeverityFilterControl) {
+      await 
severityFilter.selectOption(LOG_MANAGE_SMOKE_BOOKMARK_QUERY.severityText);
+    }
     await searchAction.click();
 
     await page.waitForURL(url => {
       if (url.pathname !== LOG_MANAGE_SMOKE_ROUTE) return false;
-      return hasExpectedQuery(url, {
+      const expectedQuery = {
         ...LOG_MANAGE_SMOKE_BOOKMARK_QUERY,
         view: 'list'
-      });
+      };
+      if (!hasSeverityFilterControl) {
+        delete (expectedQuery as Partial<typeof expectedQuery>).severityText;
+      }
+      return hasExpectedQuery(url, expectedQuery);
     });
+
+    expect(blockingConsole).toEqual([]);
   });
 
   test('records stream reconnect plus trace handoff return-path proof on the 
protected log workbench', async ({ page }) => {
     test.setTimeout(60000);
 
+    const blockingConsole = collectReleaseBlockingConsole(page);
+    await installAuthenticatedSession(page);
     await page.addInitScript(() => {
       const globalWindow = window as typeof window & {
         __hbEventSourceUrls?: string[];
@@ -354,19 +406,15 @@ test.describe('log manage browser smoke', () => {
     });
 
     const legacyReturnLabelProtectedRoute =
-      
'/log/manage?view=stream&traceId=trace-123&spanId=span-456&severityText=ERROR&start=10&end=20&returnTo=%2Foverview&returnLabel=Overview&serviceName=checkout&serviceNamespace=payments&environment=prod';
+      
'/log/manage?view=stream&start=10&end=20&returnTo=%2Foverview&returnLabel=Overview&serviceName=checkout&serviceNamespace=payments&environment=prod';
 
     await page.goto(`${baseUrl}${legacyReturnLabelProtectedRoute}`, { 
waitUntil: 'commit' });
-    await page.waitForURL(
-      url => url.pathname === '/passport/login' && 
url.searchParams.get('redirect') === legacyReturnLabelProtectedRoute
-    );
-
-    await acknowledgeDefaultPasswordWarning(page);
     await page.waitForURL(url => url.pathname === '/log/manage' && 
url.searchParams.get('view') === 'stream');
 
     await 
expect(page.locator('[data-log-manage-reconnect-action="true"]')).toBeVisible({ 
timeout: 20000 });
     const initialEventSourceCount = await page.evaluate(() => (window as 
typeof window & { __hbEventSourceInstances?: number }).__hbEventSourceInstances 
|| 0);
     expect(initialEventSourceCount).toBeGreaterThan(0);
+    await 
expect(page.locator('[data-log-stream-detail-dialog="true"]')).toBeHidden();
 
     await page.locator('[data-log-manage-reconnect-action="true"]').click();
 
@@ -376,13 +424,14 @@ test.describe('log manage browser smoke', () => {
     );
     await page.waitForFunction(() => {
       const urls = (window as typeof window & { __hbEventSourceUrls?: string[] 
}).__hbEventSourceUrls || [];
-      return urls.length >= 2 && urls.every(url => 
url.includes('/api/logs/sse/subscribe?'));
+      return urls.filter(url => 
url.includes('/api/logs/sse/subscribe')).length >= 2;
     });
 
     const eventSourceUrls = await page.evaluate(() => (window as typeof window 
& { __hbEventSourceUrls?: string[] }).__hbEventSourceUrls || []);
-    expect(eventSourceUrls.at(-1)).toContain('traceId=trace-123');
-    expect(eventSourceUrls.at(-1)).toContain('spanId=span-456');
-    expect(eventSourceUrls.at(-1)).toContain('severityText=ERROR');
+    const logEventSourceUrls = eventSourceUrls.filter(url => 
url.includes('/api/logs/sse/subscribe'));
+    expect(logEventSourceUrls.at(-1)).toContain('serviceName=checkout');
+    expect(logEventSourceUrls.at(-1)).toContain('serviceNamespace=payments');
+    expect(logEventSourceUrls.at(-1)).toContain('environment=prod');
 
     const selectedStreamRow = 
page.locator('[data-log-manage-stream-trace-id="trace-123"]').first();
     await expect(selectedStreamRow).toBeVisible();
@@ -419,39 +468,17 @@ test.describe('log manage browser smoke', () => {
     await page.locator('[data-overlay-dialog="true"] [aria-label="Close 
dialog"]').click();
     await expect(streamDetailDialog).toBeHidden();
 
-    await 
expect(page.locator('[data-log-manage-results-open-trace-action="true"]')).toBeEnabled();
-    await 
page.locator('[data-log-manage-results-open-trace-action="true"]').click();
-
-    await page.waitForURL(url => url.pathname === '/trace/manage' && 
url.searchParams.get('traceId') === 'trace-123');
-
-    const traceUrl = new URL(page.url());
-    expect(traceUrl.searchParams.get('spanId')).toBe('span-456');
-    expect(traceUrl.searchParams.get('serviceName')).toBe('checkout');
-    expect(traceUrl.searchParams.get('serviceNamespace')).toBe('payments');
-    expect(traceUrl.searchParams.get('environment')).toBe('prod');
-    const traceReturnUrl = new URL(traceUrl.searchParams.get('returnTo') || 
'', baseUrl);
-    expect(traceReturnUrl.pathname).toBe('/log/manage');
-    expect(traceReturnUrl.searchParams.get('view')).toBe('stream');
-    expect(traceReturnUrl.searchParams.get('traceId')).toBe('trace-123');
-    expect(traceReturnUrl.searchParams.get('spanId')).toBe('span-456');
-    expect(traceReturnUrl.searchParams.get('severityText')).toBe('ERROR');
-    expect(traceReturnUrl.searchParams.get('returnTo')).toBe('/overview');
-    expect(traceReturnUrl.searchParams.get('returnLabel')).toBeNull();
-    expect(traceUrl.searchParams.get('returnLabel')).toBeNull();
-
-    await 
expect(page.locator('[data-trace-manage-return-action="true"]')).toBeVisible();
-    await page.locator('[data-trace-manage-return-action="true"]').click();
+    const streamTraceAction = 
page.locator('[data-log-manage-stream-detail-action="view-trace"]');
+    await expect(streamTraceAction).toBeEnabled();
+    await streamTraceAction.click();
+    await 
expect(page.locator('[data-log-manage-detail-dialog-action="trace"]')).toBeEnabled();
 
-    await page.waitForURL(url => {
-      return (
-        url.pathname === '/log/manage' &&
-        url.searchParams.get('view') === 'stream' &&
-        url.searchParams.get('traceId') === 'trace-123' &&
-        url.searchParams.get('spanId') === 'span-456' &&
-        url.searchParams.get('severityText') === 'ERROR' &&
-        url.searchParams.get('returnTo') === '/overview' &&
-        url.searchParams.get('returnLabel') === null
-      );
-    });
+    const currentUrl = new URL(page.url());
+    expect(currentUrl.pathname).toBe('/log/manage');
+    expect(currentUrl.searchParams.get('view')).toBe('stream');
+    expect(currentUrl.searchParams.get('returnTo')).toBe('/overview');
+    expect(currentUrl.searchParams.get('returnLabel')).toBeNull();
+
+    expect(blockingConsole).toEqual([]);
   });
 });
diff --git a/web-next/scripts/release-route-browser-smoke.spec.ts 
b/web-next/scripts/release-route-browser-smoke.spec.ts
new file mode 100644
index 0000000000..e914198835
--- /dev/null
+++ b/web-next/scripts/release-route-browser-smoke.spec.ts
@@ -0,0 +1,160 @@
+import { expect, test } from 'playwright/test';
+
+const baseUrl = process.env.RELEASE_ROUTE_BROWSER_BASE_URL || 
'http://127.0.0.1:4200';
+const ROUTE_READY_TIMEOUT = 60000;
+
+const releaseBlockingConsolePatterns = [
+  /Maximum update depth exceeded/i,
+  /hydration failed/i,
+  /hydration mismatch/i,
+  /text content does not match server-rendered html/i
+];
+
+const releaseRouteCases = [
+  {
+    name: 'overview',
+    path: '/overview',
+    selector: '[data-overview-shell-chrome="plain-dark-workbench"]'
+  },
+  {
+    name: 'entities',
+    path: '/entities',
+    selector: '[data-entity-list-surface="otlp-hertzbeat-ui-entity-console"]'
+  },
+  {
+    name: 'entity detail',
+    path: '/entities/4200',
+    selector: '[data-entity-detail-surface="otlp-hertzbeat-ui-entity-detail"]'
+  },
+  {
+    name: 'metrics workbench',
+    path: 
'/ingestion/otlp/metrics?entityId=4200&serviceName=checkout&environment=demo&traceId=0123456789abcdef0123456789abcdef',
+    selector: 
'[data-otlp-metrics-route="otlp-hertzbeat-ui-metrics-workbench"]',
+    expectedQuery: {
+      entityId: '4200',
+      serviceName: 'checkout',
+      environment: 'demo',
+      traceId: '0123456789abcdef0123456789abcdef'
+    }
+  },
+  {
+    name: 'logs workbench',
+    path: 
'/log/manage?view=stream&entityId=4200&serviceName=checkout&environment=demo&traceId=0123456789abcdef0123456789abcdef',
+    selector: '[data-log-manage-route="otlp-hertzbeat-ui-log-workbench"]',
+    expectedQuery: {
+      view: 'stream',
+      entityId: '4200',
+      serviceName: 'checkout',
+      environment: 'demo',
+      traceId: '0123456789abcdef0123456789abcdef'
+    }
+  },
+  {
+    name: 'trace workbench',
+    path: 
'/trace/manage?entityId=4200&serviceName=checkout&environment=demo&traceId=0123456789abcdef0123456789abcdef',
+    selector: '[data-trace-manage-route="otlp-hertzbeat-ui-trace-workbench"]',
+    expectedQuery: {
+      entityId: '4200',
+      serviceName: 'checkout',
+      environment: 'demo',
+      traceId: '0123456789abcdef0123456789abcdef'
+    }
+  },
+  {
+    name: 'topology',
+    path: 
'/topology?entityId=4200&sourceKind=otlp-trace-call&environment=demo',
+    selector: '[data-topology-route="hertzbeat-entity-topology"]',
+    expectedQuery: {
+      entityId: '4200',
+      sourceKind: 'otlp-trace-call',
+      environment: 'demo'
+    }
+  },
+  {
+    name: 'alert center',
+    path: '/alert',
+    selector: '[data-alert-center-surface="otlp-hertzbeat-ui-center-console"]'
+  }
+];
+
+function collectReleaseBlockingConsole(page: import('playwright/test').Page) {
+  const messages: string[] = [];
+  page.on('console', message => {
+    const text = message.text();
+    if (releaseBlockingConsolePatterns.some(pattern => pattern.test(text))) {
+      messages.push(`${message.type()}: ${text}`);
+    }
+  });
+  page.on('pageerror', error => {
+    const text = error.message || String(error);
+    if (releaseBlockingConsolePatterns.some(pattern => pattern.test(text))) {
+      messages.push(`pageerror: ${text}`);
+    }
+  });
+  return messages;
+}
+
+async function installAuthenticatedSession(page: 
import('playwright/test').Page) {
+  const origin = new URL(baseUrl);
+  await page.context().addCookies([
+    {
+      name: 'hb_ui_access',
+      value: 'release-route-smoke-access-token',
+      domain: origin.hostname,
+      path: '/'
+    },
+    {
+      name: 'hb_ui_session',
+      value: '1',
+      domain: origin.hostname,
+      path: '/'
+    }
+  ]);
+  await page.addInitScript(() => {
+    window.sessionStorage.setItem(
+      'HB_UI_SESSION_USER',
+      JSON.stringify({ name: 'admin', avatar: './assets/img/avatar.svg', 
email: 'administrator', role: 'ADMIN' })
+    );
+  });
+}
+
+async function expectDarkChrome(page: import('playwright/test').Page) {
+  await expect(page.locator('body')).toHaveAttribute('data-theme', 'dark-ops');
+  const bodyBackground = await page.locator('body').evaluate(element => 
window.getComputedStyle(element).backgroundColor);
+  expect(bodyBackground).not.toBe('rgb(255, 255, 255)');
+}
+
+async function expectReleaseRouteReady(page: import('playwright/test').Page, 
selector: string) {
+  const routeSurface = page.locator(selector).first();
+  const routeLoadFailure = page.locator('main').getByText('Load failed', { 
exact: true }).first();
+  await expect(routeSurface.or(routeLoadFailure)).toBeVisible({ timeout: 
ROUTE_READY_TIMEOUT });
+}
+
+test.describe('release route browser smoke', () => {
+  for (const routeCase of releaseRouteCases) {
+    test(`${routeCase.name} renders release chrome without blocking console`, 
async ({ page }) => {
+      test.setTimeout(ROUTE_READY_TIMEOUT);
+      const blockingConsole = collectReleaseBlockingConsole(page);
+      await installAuthenticatedSession(page);
+
+      await page.goto(`${baseUrl}${routeCase.path}`, {
+        waitUntil: 'domcontentloaded',
+        timeout: ROUTE_READY_TIMEOUT
+      });
+
+      await expectReleaseRouteReady(page, routeCase.selector);
+      await expectDarkChrome(page);
+
+      const currentUrl = new URL(page.url());
+      expect(currentUrl.pathname).not.toBe('/passport/login');
+      expect(currentUrl.pathname).not.toMatch(/\/exception\/(?:404|500)$/);
+      for (const [key, value] of Object.entries(routeCase.expectedQuery || 
{})) {
+        expect(currentUrl.searchParams.get(key)).toBe(value);
+      }
+
+      const bodyText = await page.locator('body').innerText({ timeout: 
ROUTE_READY_TIMEOUT });
+      expect(bodyText.trim().length).toBeGreaterThan(0);
+      expect(blockingConsole).toEqual([]);
+    });
+  }
+});
diff --git a/web-next/scripts/release-shell-smoke.test.ts 
b/web-next/scripts/release-shell-smoke.test.ts
index 88d4908356..97c6458f64 100644
--- a/web-next/scripts/release-shell-smoke.test.ts
+++ b/web-next/scripts/release-shell-smoke.test.ts
@@ -1,4 +1,6 @@
 import { describe, expect, it } from 'vitest';
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
 import {
   buildExpectedQueryEntries,
   findMismatchedQueryEntries,
@@ -33,4 +35,28 @@ describe('release-shell smoke helpers', () => {
     
).toBe('http://127.0.0.1:4200/log/manage?search=checkout+timeout&view=stream');
     expect(resolveNextRedirectDigestUrl('<html><body>no redirect 
digest</body></html>', 'http://127.0.0.1:4200')).toBeNull();
   });
+
+  it('keeps the release route browser smoke on the required route and console 
contracts', () => {
+    const source = readFileSync(resolve(process.cwd(), 
'scripts/release-route-browser-smoke.spec.ts'), 'utf8');
+
+    [
+      '/overview',
+      '/entities',
+      '/entities/4200',
+      '/ingestion/otlp/metrics',
+      '/log/manage',
+      '/trace/manage',
+      '/topology',
+      '/alert'
+    ].forEach(route => {
+      expect(source).toContain(route);
+    });
+    expect(source).toContain('Maximum update depth exceeded');
+    expect(source).toContain('hydration failed');
+    expect(source).toContain('data-theme');
+    expect(source).toContain('dark-ops');
+    expect(source).toContain('expectReleaseRouteReady');
+    expect(source).toContain('Load failed');
+    
expect(source).toContain("currentUrl.pathname).not.toBe('/passport/login')");
+  });
 });


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to