Repair stable Core E2E contracts

This commit is contained in:
rcourtman
2026-08-05 09:29:23 +01:00
parent 1b0b54534b
commit 9f870bf474
6 changed files with 273 additions and 208 deletions
@@ -1,8 +1,13 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { test as base, expect, type Locator, type Page } from '@playwright/test';
import { createAuthenticatedStorageState } from './helpers';
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
test as base,
expect,
type Locator,
type Page,
} from "@playwright/test";
import { createAuthenticatedStorageState } from "./helpers";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -14,23 +19,26 @@ const test = base.extend<{}, WorkerFixtures>({
storageState: async ({ authStorageStatePath }, use) => {
await use(authStorageStatePath);
},
authStorageStatePath: [async ({ browser }, use, workerInfo) => {
const storageStatePath = path.resolve(
__dirname,
'..',
'..',
'tmp',
'playwright-auth',
`telemetry-disclosure-${workerInfo.project.name}.json`,
);
fs.mkdirSync(path.dirname(storageStatePath), { recursive: true });
await createAuthenticatedStorageState(browser, storageStatePath);
try {
await use(storageStatePath);
} finally {
fs.rmSync(storageStatePath, { force: true });
}
}, { scope: 'worker' }],
authStorageStatePath: [
async ({ browser }, use, workerInfo) => {
const storageStatePath = path.resolve(
__dirname,
"..",
"..",
"tmp",
"playwright-auth",
`telemetry-disclosure-${workerInfo.project.name}.json`,
);
fs.mkdirSync(path.dirname(storageStatePath), { recursive: true });
await createAuthenticatedStorageState(browser, storageStatePath);
try {
await use(storageStatePath);
} finally {
fs.rmSync(storageStatePath, { force: true });
}
},
{ scope: "worker" },
],
});
async function expectPopupDoc(
@@ -39,70 +47,83 @@ async function expectPopupDoc(
pathname: string,
expectedText: string,
) {
const [popup] = await Promise.all([
page.waitForEvent('popup'),
link.click(),
]);
const [popup] = await Promise.all([page.waitForEvent("popup"), link.click()]);
await popup.waitForLoadState('domcontentloaded');
await popup.waitForLoadState("domcontentloaded");
expect(new URL(popup.url()).pathname).toBe(pathname);
await expect(popup.locator('body')).toContainText(expectedText);
await expect(popup.locator("body")).toContainText(expectedText);
await popup.close();
}
async function readTelemetryPreview(page: Page) {
const preview = page.locator('pre[aria-label="Telemetry payload preview"]');
await expect(preview).toBeVisible();
return JSON.parse(await preview.textContent() ?? '{}') as {
return JSON.parse((await preview.textContent()) ?? "{}") as {
install_id: string;
event: string;
};
}
test.describe('Telemetry disclosure', () => {
test.describe("Telemetry disclosure", () => {
test.setTimeout(180_000);
test('general settings opens the shipped privacy document', async ({ page }, testInfo) => {
test.skip(testInfo.project.name.startsWith('mobile-'), 'Desktop-only telemetry disclosure coverage');
test("general settings opens the shipped privacy document", async ({
page,
}, testInfo) => {
test.skip(
testInfo.project.name.startsWith("mobile-"),
"Desktop-only telemetry disclosure coverage",
);
await page.goto('/settings/system-general', { waitUntil: 'domcontentloaded' });
await page.goto("/settings/system-general", {
waitUntil: "domcontentloaded",
});
await page.waitForURL(/\/settings/, { timeout: 15_000 });
const telemetrySummary = page.getByText(
/rotating pseudonymous install ID, normalized release identity, runtime platform/i,
);
await expect(telemetrySummary).toBeVisible();
await expect(telemetrySummary).toContainText('Telemetry rows are retained for up to 90 days');
await expect(telemetrySummary).toContainText(
'are not stored in telemetry rows',
"Telemetry rows are retained for up to 90 days",
);
await expect(telemetrySummary).toContainText(
"are not stored in telemetry rows",
);
const disclosureLink = page.getByRole('link', { name: 'Full details' }).first();
await expect(disclosureLink).toHaveAttribute('href', '/docs/PRIVACY.md');
const disclosureLink = page
.getByRole("link", { name: "Full details" })
.first();
await expect(disclosureLink).toHaveAttribute("href", "/docs/PRIVACY");
await expectPopupDoc(
page,
disclosureLink,
'/docs/PRIVACY.md',
'Pulse has one outbound usage-data scope',
"/docs/PRIVACY",
"Pulse has one outbound usage-data scope",
);
});
test('general settings lets operators preview and rotate the telemetry payload', async ({
test("general settings lets operators preview and rotate the telemetry payload", async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name.startsWith('mobile-'), 'Desktop-only telemetry disclosure coverage');
test.skip(
testInfo.project.name.startsWith("mobile-"),
"Desktop-only telemetry disclosure coverage",
);
await page.goto('/settings/system-general', { waitUntil: 'domcontentloaded' });
await page.goto("/settings/system-general", {
waitUntil: "domcontentloaded",
});
await page.waitForURL(/\/settings/, { timeout: 15_000 });
await page.getByRole('button', { name: 'Preview payload' }).click();
await page.getByRole("button", { name: "Preview payload" }).click();
const initialPreview = await readTelemetryPreview(page);
expect(initialPreview.event).toBe('heartbeat');
expect(initialPreview.event).toBe("heartbeat");
expect(initialPreview.install_id).toBeTruthy();
page.once('dialog', (dialog) => dialog.accept());
await page.getByRole('button', { name: 'Reset ID' }).click();
page.once("dialog", (dialog) => dialog.accept());
await page.getByRole("button", { name: "Reset ID" }).click();
await expect
.poll(async () => {
@@ -1,8 +1,8 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { test as base, expect } from '@playwright/test';
import { createAuthenticatedStorageState } from './helpers';
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { test as base, expect } from "@playwright/test";
import { createAuthenticatedStorageState } from "./helpers";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -10,80 +10,87 @@ type WorkerFixtures = {
authStorageStatePath: string;
};
const SCREENSHOT_PATH = '/tmp/truenas-alert-thresholds.png';
const SCREENSHOT_PATH = "/tmp/truenas-alert-thresholds.png";
const test = base.extend<{}, WorkerFixtures>({
storageState: async ({ authStorageStatePath }, use) => {
await use(authStorageStatePath);
},
authStorageStatePath: [async ({ browser }, use, workerInfo) => {
const storageStatePath = path.resolve(
__dirname,
'..',
'..',
'tmp',
'playwright-auth',
`truenas-alert-thresholds-${workerInfo.project.name}.json`,
);
fs.mkdirSync(path.dirname(storageStatePath), { recursive: true });
await createAuthenticatedStorageState(browser, storageStatePath);
try {
await use(storageStatePath);
} finally {
fs.rmSync(storageStatePath, { force: true });
}
}, { scope: 'worker' }],
authStorageStatePath: [
async ({ browser }, use, workerInfo) => {
const storageStatePath = path.resolve(
__dirname,
"..",
"..",
"tmp",
"playwright-auth",
`truenas-alert-thresholds-${workerInfo.project.name}.json`,
);
fs.mkdirSync(path.dirname(storageStatePath), { recursive: true });
await createAuthenticatedStorageState(browser, storageStatePath);
try {
await use(storageStatePath);
} finally {
fs.rmSync(storageStatePath, { force: true });
}
},
{ scope: "worker" },
],
});
// The thresholds page renders live websocket state, so REST stubs of
// /api/resources get overwritten by the next state frame. The assertions
// pin the mock scenario's TrueNAS fixture (truenas-main with pool tank,
// tank/* datasets, and physical disks) instead of stubbed payloads.
test.describe('TrueNAS alert thresholds', () => {
test.describe("TrueNAS alert thresholds", () => {
test.setTimeout(180_000);
// Desktop presentation spec. The mobile layout collapses the platform
// scope filter into the FilterBar sheet, so the 'TrueNAS' scope button
// this spec clicks does not exist there and the click falls through to
// the platform nav tab; mobile coverage lives in 04-mobile.spec.ts.
test.skip(({ isMobile }) => Boolean(isMobile), 'desktop-presentation spec');
// Desktop presentation spec. Mobile coverage lives in 04-mobile.spec.ts.
test.skip(({ isMobile }) => Boolean(isMobile), "desktop-presentation spec");
test('surfaces TrueNAS systems, pools, datasets, and disks under the TrueNAS thresholds scope', async ({
test("surfaces TrueNAS systems, pools, datasets, and disks under the TrueNAS thresholds scope", async ({
page,
}) => {
// Fresh test backends boot with alerts deactivated, which gates the
// thresholds route behind the Alerts Overview activation screen. The
// config endpoint is REST-fed and stubable (same pattern as the Ceph
// thresholds spec); entity data below still comes from live mock state.
await page.route('**/api/alerts/config', async (route) => {
await page.route("**/api/alerts/config", async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify({
enabled: true,
activationState: 'active',
activationState: "active",
overrides: {},
}),
});
});
await page.goto('/alerts/thresholds/infrastructure', {
waitUntil: 'domcontentloaded',
await page.goto("/alerts/thresholds/infrastructure", {
waitUntil: "domcontentloaded",
});
await expect(page).toHaveURL(/\/alerts\/thresholds/);
await expect(page.getByRole('heading', { name: 'Alert Thresholds' })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Alert Thresholds" }),
).toBeVisible();
// Threshold scopes are platform-first; TrueNAS entities live under
// their own scope instead of the retired neutral groupings.
await page.getByRole('button', { name: 'TrueNAS', exact: true }).click();
await page
.getByRole("tablist", { name: "Threshold platform" })
.getByRole("tab", { name: "TrueNAS", exact: true })
.click();
await expect(page).toHaveURL(/\/alerts\/thresholds\/truenas/);
await expect(page.getByRole('heading', { name: 'Systems' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Pools' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Datasets' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Disks' })).toBeVisible();
await expect(page.getByText('truenas-main', { exact: true }).first()).toBeVisible();
await expect(page.getByText('tank', { exact: true }).first()).toBeVisible();
await expect(page.getByRole("heading", { name: "Systems" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Pools" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Datasets" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Disks" })).toBeVisible();
await expect(
page.getByText("truenas-main", { exact: true }).first(),
).toBeVisible();
await expect(page.getByText("tank", { exact: true }).first()).toBeVisible();
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
});
@@ -1,8 +1,8 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { test as base, expect } from '@playwright/test';
import { createAuthenticatedStorageState } from './helpers';
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { test as base, expect } from "@playwright/test";
import { createAuthenticatedStorageState } from "./helpers";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -10,70 +10,74 @@ type WorkerFixtures = {
authStorageStatePath: string;
};
const SCREENSHOT_PATH = '/tmp/truenas-alert-resource-links.png';
const SCREENSHOT_PATH = "/tmp/truenas-alert-resource-links.png";
const test = base.extend<{}, WorkerFixtures>({
storageState: async ({ authStorageStatePath }, use) => {
await use(authStorageStatePath);
},
authStorageStatePath: [async ({ browser }, use, workerInfo) => {
const storageStatePath = path.resolve(
__dirname,
'..',
'..',
'tmp',
'playwright-auth',
`truenas-alert-resource-links-${workerInfo.project.name}.json`,
);
fs.mkdirSync(path.dirname(storageStatePath), { recursive: true });
await createAuthenticatedStorageState(browser, storageStatePath);
try {
await use(storageStatePath);
} finally {
fs.rmSync(storageStatePath, { force: true });
}
}, { scope: 'worker' }],
authStorageStatePath: [
async ({ browser }, use, workerInfo) => {
const storageStatePath = path.resolve(
__dirname,
"..",
"..",
"tmp",
"playwright-auth",
`truenas-alert-resource-links-${workerInfo.project.name}.json`,
);
fs.mkdirSync(path.dirname(storageStatePath), { recursive: true });
await createAuthenticatedStorageState(browser, storageStatePath);
try {
await use(storageStatePath);
} finally {
fs.rmSync(storageStatePath, { force: true });
}
},
{ scope: "worker" },
],
});
const ALERT_START = new Date(Date.now() - 45 * 60 * 1000).toISOString();
const ALERT_LAST_SEEN = new Date(Date.now() - 30 * 60 * 1000).toISOString();
test.describe('TrueNAS alert resource links', () => {
test.describe("TrueNAS alert resource links", () => {
test.setTimeout(180_000);
test('keeps TrueNAS alert investigation on the scoped resource incidents panel', async ({
test("keeps TrueNAS alert investigation on the scoped resource incidents panel", async ({
page,
isMobile,
}) => {
await page.route('**/api/resources**', async (route) => {
await page.route("**/api/resources**", async (route) => {
const requestUrl = new URL(route.request().url());
if (requestUrl.pathname !== '/api/resources') {
if (requestUrl.pathname !== "/api/resources") {
await route.continue();
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify({
data: [
{
id: 'truenas-main',
type: 'agent',
name: 'truenas-main',
displayName: 'TrueNAS Main',
platformId: 'truenas-main',
platformType: 'truenas',
sourceType: 'hybrid',
sources: ['agent', 'truenas'],
status: 'online',
id: "truenas-main",
type: "agent",
name: "truenas-main",
displayName: "TrueNAS Main",
platformId: "truenas-main",
platformType: "truenas",
sourceType: "hybrid",
sources: ["agent", "truenas"],
status: "online",
lastSeen: ALERT_LAST_SEEN,
canonicalIdentity: {
displayName: 'TrueNAS Main',
hostname: 'truenas-main',
platformId: 'truenas-main',
displayName: "TrueNAS Main",
hostname: "truenas-main",
platformId: "truenas-main",
},
platformData: {
sources: ['agent', 'truenas'],
sources: ["agent", "truenas"],
},
},
],
@@ -87,57 +91,57 @@ test.describe('TrueNAS alert resource links', () => {
});
});
await page.route('**/api/alerts/config', async (route) => {
await page.route("**/api/alerts/config", async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify({
enabled: true,
activationState: 'active',
activationState: "active",
overrides: {},
}),
});
});
await page.route('**/api/alerts/active', async (route) => {
await page.route("**/api/alerts/active", async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify([]),
});
});
await page.route('**/api/alerts/history**', async (route) => {
await page.route("**/api/alerts/history**", async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify([
{
id: 'truenas-alert-1',
type: 'host-offline',
level: 'critical',
id: "truenas-alert-1",
type: "host-offline",
level: "critical",
startTime: ALERT_START,
lastSeen: ALERT_LAST_SEEN,
resourceId: 'truenas-main',
resourceName: 'TrueNAS Main',
message: 'TrueNAS Main is offline',
resourceId: "truenas-main",
resourceName: "TrueNAS Main",
message: "TrueNAS Main is offline",
acknowledged: false,
node: 'truenas-main',
nodeDisplayName: 'TrueNAS Main',
node: "truenas-main",
nodeDisplayName: "TrueNAS Main",
metadata: {
resourceType: 'agent',
resourceType: "agent",
},
},
]),
});
});
await page.route('**/api/alerts/incidents**', async (route) => {
await page.route("**/api/alerts/incidents**", async (route) => {
const requestUrl = new URL(route.request().url());
if (requestUrl.searchParams.get('resource_id') !== 'truenas-main') {
if (requestUrl.searchParams.get("resource_id") !== "truenas-main") {
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify(null),
});
return;
@@ -145,22 +149,22 @@ test.describe('TrueNAS alert resource links', () => {
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify([
{
id: 'incident-truenas-1',
alertType: 'Host Offline',
level: 'critical',
status: 'open',
id: "incident-truenas-1",
alertType: "Host Offline",
level: "critical",
status: "open",
acknowledged: false,
openedAt: ALERT_START,
message: 'TrueNAS Main is offline',
message: "TrueNAS Main is offline",
events: [
{
id: 'incident-event-1',
type: 'opened',
id: "incident-event-1",
type: "opened",
timestamp: ALERT_START,
summary: 'Alert opened',
summary: "Alert opened",
},
],
},
@@ -168,18 +172,18 @@ test.describe('TrueNAS alert resource links', () => {
});
});
await page.route('**/api/notifications/email', async (route) => {
await page.route("**/api/notifications/email", async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify({
enabled: false,
provider: '',
server: '',
provider: "",
server: "",
port: 587,
username: '',
password: '',
from: '',
username: "",
password: "",
from: "",
to: [],
tls: false,
startTLS: false,
@@ -187,39 +191,48 @@ test.describe('TrueNAS alert resource links', () => {
});
});
await page.route('**/api/notifications/apprise', async (route) => {
await page.route("**/api/notifications/apprise", async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
contentType: "application/json",
body: JSON.stringify({
enabled: false,
}),
});
});
await page.goto('/alerts/history', {
waitUntil: 'domcontentloaded',
await page.goto("/alerts/history", {
waitUntil: "domcontentloaded",
});
await expect(page.getByRole('heading', { name: 'Alert History' })).toBeVisible();
const alertRow = page
.locator('tr')
.filter({ hasText: 'TrueNAS Main' })
.filter({ has: page.getByRole('button', { name: 'Resource' }) })
await expect(
page.getByRole("heading", { name: "Alert History" }),
).toBeVisible();
const alertItem = (
isMobile
? page.getByTestId("alert-history-mobile-list").locator("article")
: page.locator("tr")
)
.filter({ hasText: "TrueNAS Main" })
.filter({ has: page.getByRole("button", { name: "Resource" }) })
.first();
await expect(alertRow).toBeVisible();
await alertRow.getByRole('button', { name: 'Resource' }).click();
await expect(alertItem).toBeVisible();
await alertItem.getByRole("button", { name: "Resource" }).click();
// Cross-link affordances into the retired standalone routes were removed
// with platform-first navigation; the canonical handoff is the resource
// incidents panel scoped to the alerting resource.
await expect(page.getByRole('heading', { name: 'Resource incidents' })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Resource incidents" }),
).toBeVisible();
const incidentsPanel = page
.locator('div')
.filter({ has: page.getByRole('heading', { name: 'Resource incidents' }) })
.locator("div")
.filter({
has: page.getByRole("heading", { name: "Resource incidents" }),
})
.last();
await expect(page.getByText('TrueNAS Main').first()).toBeVisible();
await expect(page.getByText('· 1 incident')).toBeVisible();
await expect(page.getByText("TrueNAS Main").first()).toBeVisible();
await expect(page.getByText("· 1 incident")).toBeVisible();
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
});
@@ -7,11 +7,14 @@ test.describe("VMware alert history resource incidents", () => {
test("opens VMware resource incidents through the shared alert history surface", async ({
page,
isMobile,
}) => {
// Recent timestamps keep the fixture inside the history view's default
// period window; fixed dates silently age out of it.
const INCIDENT_START = new Date(Date.now() - 30 * 60 * 1000).toISOString();
const INCIDENT_LAST_SEEN = new Date(Date.now() - 28 * 60 * 1000).toISOString();
const INCIDENT_LAST_SEEN = new Date(
Date.now() - 28 * 60 * 1000,
).toISOString();
await page.route("**/api/alerts/config", async (route) => {
await route.fulfill({
status: 200,
@@ -186,24 +189,33 @@ test.describe("VMware alert history resource incidents", () => {
await expect(
page.getByRole("heading", { name: "Alert History" }),
).toBeVisible();
await expect(page.getByText("app-01")).toBeVisible();
const historyRow = page.locator("tr").filter({ hasText: "app-01" }).first();
await expect(historyRow).toContainText(
const historyItem = (
isMobile
? page.getByTestId("alert-history-mobile-list").locator("article")
: page.locator("tr")
)
.filter({ hasText: "app-01" })
.first();
await expect(historyItem).toContainText(
"VM vm-201 has VMware alarm VM replication fault (red)",
);
await historyRow.getByRole("button", { name: "Resource" }).click();
await historyItem.getByRole("button", { name: "Resource" }).click();
// The message renders in both the history table cell and the opened
// resource drawer; assert the drawer copy specifically.
const incidentsHeading = page.getByRole("heading", {
name: "Resource incidents",
});
const incidentsPanel = incidentsHeading.locator("xpath=../../..");
await expect(
page
.getByRole("paragraph")
.filter({ hasText: "VM vm-201 has VMware alarm VM replication fault (red)" }),
incidentsPanel.getByText(
"VM vm-201 has VMware alarm VM replication fault (red)",
),
).toBeVisible();
await expect(page.getByText("Resource incidents")).toBeVisible();
await expect(incidentsHeading).toBeVisible();
// "Resource Health" renders as both the incident label and a type badge
// inside the drawer.
await expect(page.getByText("Resource Health").first()).toBeVisible();
await expect(
incidentsPanel.getByText("Resource Health").first(),
).toBeVisible();
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
});
@@ -2,6 +2,21 @@ import { expect, test, type Page, type Route } from "@playwright/test";
type AttentionMode = "active" | "calm" | "failed";
async function selectAttentionState(page: Page, label: string, value: string) {
const queue = page.getByRole("region", { name: "Needs attention" });
const segmentedChoice = queue.getByRole("button", {
name: label,
exact: true,
});
if (await segmentedChoice.isVisible()) {
await segmentedChoice.click();
return;
}
await queue
.getByRole("combobox", { name: "Attention state" })
.selectOption(value);
}
test.beforeEach(async ({ page }) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
@@ -714,31 +729,23 @@ test("makes active operational work primary and preserves the evidence boundary"
page.getByRole("button", { name: "Explain with Assistant" }),
).toHaveCount(0);
await queue
.getByRole("button", { name: "Acknowledged 1", exact: true })
.click();
await selectAttentionState(page, "Acknowledged 1", "acknowledged");
await expect(
queue.getByText("Memory pressure acknowledged on pve-lab"),
).toBeVisible();
await queue
.getByRole("button", { name: "Suppressed 1", exact: true })
.click();
await selectAttentionState(page, "Suppressed 1", "suppressed");
await expect(
queue.getByText("Maintenance alert suppressed on pve-maintenance"),
).toBeVisible();
await queue
.getByRole("button", { name: "Stale or unknown 1", exact: true })
.click();
await selectAttentionState(page, "Stale or unknown 1", "stale_unknown");
await expect(
queue.getByText("Connection state unknown for pve-edge"),
).toBeVisible();
await queue
.getByRole("button", { name: "Recent resolved 1", exact: true })
.click();
await selectAttentionState(page, "Recent resolved 1", "resolved");
await expect(
queue.getByText("Storage pressure resolved on pve-recovered"),
).toBeVisible();
await queue.getByRole("button", { name: "Active 2", exact: true }).click();
await selectAttentionState(page, "Active 2", "active");
const itemButton = queue.getByRole("button", {
name: "Open CPU pressure on pve-main",
@@ -130,7 +130,9 @@ test("audit log fails closed, recovers, pages atomically, and ignores stale resp
mode = "busy";
await page.getByRole("button", { name: "Refresh" }).click();
await expect(page.locator("main").getByRole("alert")).toContainText("storage is busy");
await expect(page.locator("main").getByRole("alert")).toContainText(
"storage is busy",
);
await expect(page.getByText("initial-row", { exact: true })).toHaveCount(0);
mode = "recovered";
@@ -139,6 +141,7 @@ test("audit log fails closed, recovers, pages atomically, and ignores stale resp
await expect(page.locator("main").getByRole("alert")).toHaveCount(0);
mode = "page-size";
await page.getByRole("button", { name: "View", exact: true }).click();
await page.getByLabel("Audit page size").selectOption("25");
await expect(page.getByText("Showing 1-25 of 250")).toBeVisible();
expect(
@@ -159,6 +162,8 @@ test("audit log fails closed, recovers, pages atomically, and ignores stale resp
mode = "invalid";
await page.getByRole("button", { name: "Refresh" }).click();
await expect(page.locator("main").getByRole("alert")).toContainText("invalid response");
await expect(page.locator("main").getByRole("alert")).toContainText(
"invalid response",
);
await expect(page.getByText("latest-row", { exact: true })).toHaveCount(0);
});