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]