Refresh Patrol browser qualification

This commit is contained in:
rcourtman
2026-08-14 15:38:48 +01:00
parent daccf6c4d7
commit 58e0cd9e6c
9 changed files with 96 additions and 89 deletions
@@ -587,7 +587,7 @@ test.describe("Patrol runtime-state browser contract", () => {
await expect(page.getByText("Patrol paused").first()).toBeVisible();
await expect(page.getByText(PATROL_BLOCK_REASON).first()).toBeVisible();
await expect(
page.getByRole("button", { name: "Run Patrol" }),
page.getByRole("button", { name: "Check now" }),
).toBeDisabled();
await expect(page.getByText(/Patrol quickstart/i)).toHaveCount(0);
await expect(page.getByText("Health A · 100/100")).toHaveCount(0);
@@ -631,7 +631,7 @@ test.describe("Patrol runtime-state browser contract", () => {
await page.goto("/patrol", { waitUntil: "domcontentloaded" });
const runButton = page.getByRole("button", { name: "Run Patrol" });
const runButton = page.getByRole("button", { name: "Check now" });
await expect(runButton).toBeEnabled();
await runButton.click();
@@ -801,6 +801,7 @@ test.describe("Patrol runtime-state browser contract", () => {
await page.goto("/patrol", { waitUntil: "domcontentloaded" });
await page.getByText("How Patrol operates", { exact: true }).click();
const modeGroup = page.getByRole("group", { name: "Patrol mode" });
await expect(modeGroup).toBeVisible();
await expect(
@@ -605,9 +605,8 @@ test.describe("Patrol Assistant operator briefing", () => {
await page.goto("/patrol", { waitUntil: "domcontentloaded" });
await page
.getByText("Patrol checks, investigations, and run history", {
exact: true,
})
.locator("summary")
.filter({ hasText: "Operational records and run history" })
.click();
await page.getByText("High CPU usage").click();
@@ -745,9 +744,8 @@ test.describe("Patrol Assistant operator briefing", () => {
includePendingApproval = false;
await page.reload({ waitUntil: "domcontentloaded" });
await page
.getByText("Patrol checks, investigations, and run history", {
exact: true,
})
.locator("summary")
.filter({ hasText: "Operational records and run history" })
.click();
const queuedFindingTitle = page.getByText("High CPU usage").first();
await expect(queuedFindingTitle).toBeVisible();
@@ -779,9 +777,8 @@ test.describe("Patrol Assistant operator briefing", () => {
includeInvestigationProposedFix = true;
await page.reload({ waitUntil: "domcontentloaded" });
await page
.getByText("Patrol checks, investigations, and run history", {
exact: true,
})
.locator("summary")
.filter({ hasText: "Operational records and run history" })
.click();
const expiredFindingTitle = page.getByText("High CPU usage").first();
await expect(expiredFindingTitle).toBeVisible();
@@ -659,7 +659,7 @@ test.describe("Monitor-first Patrol workbench browser contract", () => {
).toBeVisible();
await expect(
desktopNav.getByRole("tab", {
name: "Patrol: 1 active attention item",
name: /Patrol: 1 (?:action awaits approval|active attention item)/,
}),
).toBeVisible();
await expect(
@@ -714,9 +714,9 @@ test.describe("Monitor-first Patrol workbench browser contract", () => {
page.getByRole("heading", { level: 1, name: "Patrol" }),
).toBeVisible();
await expect(
page.getByRole("heading", { level: 2, name: "Needs attention" }),
page.getByRole("heading", { level: 2, name: "Needs you" }),
).toBeVisible();
await expect(page.getByText("Nothing needs your attention")).toBeVisible();
await expect(page.getByText("Nothing needs you right now")).toBeVisible();
await expect(
page.getByRole("list", { name: "Patrol protection posture" }),
).toHaveCount(0);
@@ -760,7 +760,7 @@ test.describe("Monitor-first Patrol workbench browser contract", () => {
await page.getByRole("tab", { name: /Patrol/ }).click();
await expect(page).toHaveURL(/\/patrol$/);
await expect(
page.getByRole("heading", { level: 2, name: "Needs attention" }),
page.getByRole("heading", { level: 2, name: "Needs you" }),
).toBeVisible();
await expect(
page.getByRole("list", { name: "Patrol attention items" }),
@@ -883,9 +883,8 @@ test.describe("Monitor-first Patrol workbench browser contract", () => {
page.getByRole("heading", { level: 1, name: "Patrol" }),
).toBeVisible();
await page
.getByText("Patrol checks, investigations, and run history", {
exact: true,
})
.locator("summary")
.filter({ hasText: "Operational records and run history" })
.click();
await expect(
page.getByText("Operating system updates need review").first(),
@@ -931,9 +930,8 @@ test.describe("Monitor-first Patrol workbench browser contract", () => {
);
await page.reload({ waitUntil: "domcontentloaded" });
await page
.getByText("Patrol checks, investigations, and run history", {
exact: true,
})
.locator("summary")
.filter({ hasText: "Operational records and run history" })
.click();
const cleanupTitle = page
.getByText("Downloaded package data is using needed space")
@@ -990,9 +988,8 @@ test.describe("Monitor-first Patrol workbench browser contract", () => {
);
await page.reload({ waitUntil: "domcontentloaded" });
await page
.getByText("Patrol checks, investigations, and run history", {
exact: true,
})
.locator("summary")
.filter({ hasText: "Operational records and run history" })
.click();
await page.getByRole("button", { name: "Resolved", exact: true }).click();
const resolvedTitle = page
@@ -361,6 +361,8 @@ const routeActionFixtures = async (
new URL(route.request().url()).pathname.split("/").pop() || "",
);
const audit = all.find((candidate) => candidate.id === id);
const hasDeliveryRecord =
audit && audit.state !== "planned" && audit.state !== "pending_approval";
await route.fulfill({
status: audit ? 200 : 404,
contentType: "application/json",
@@ -369,20 +371,37 @@ const routeActionFixtures = async (
? {
audit,
events: [],
attempt: {
id: `${id}-attempt`,
actionId: id,
state: "receipt_recorded",
createdAt: audit.createdAt,
updatedAt: audit.updatedAt,
dispatchCount: 1,
},
receipt: {
attemptId: `${id}-attempt`,
actionId: id,
transportRequestId: `${id}-transport`,
receivedAt: "2026-07-12T10:05:00Z",
readiness: {
ready: audit.state === "planned" || audit.state === "pending_approval",
code:
audit.state === "planned" || audit.state === "pending_approval"
? "ready"
: "action_not_actionable",
message:
audit.state === "planned" || audit.state === "pending_approval"
? "Action is ready for approval and dispatch."
: "This action is no longer open for approval or dispatch.",
refreshable: false,
checkedAt: audit.updatedAt,
},
...(hasDeliveryRecord
? {
attempt: {
id: `${id}-attempt`,
actionId: id,
state: "receipt_recorded",
createdAt: audit.createdAt,
updatedAt: audit.updatedAt,
dispatchCount: 1,
},
receipt: {
attemptId: `${id}-attempt`,
actionId: id,
transportRequestId: `${id}-transport`,
receivedAt: "2026-07-12T10:05:00Z",
},
}
: {}),
}
: { error: "not found" },
),
@@ -390,7 +409,7 @@ const routeActionFixtures = async (
});
};
test("Actions inbox exposes the canonical decision packet and durable calm history", async ({
test("Activity history exposes the canonical decision packet and durable calm history", async ({
page,
}, testInfo) => {
await page.route("**/api/actions?*", async (route) => {
@@ -409,14 +428,31 @@ test("Actions inbox exposes the canonical decision packet and durable calm histo
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ audit: action, events: [] }),
body: JSON.stringify({
audit: action,
events: [],
readiness: {
ready: true,
code: "ready",
message: "Action is ready for approval and dispatch.",
refreshable: false,
checkedAt: action.updatedAt,
},
}),
}),
);
await page.goto("/actions?action=action-1", {
waitUntil: "domcontentloaded",
});
await expect(page.getByRole("heading", { name: "Actions" })).toBeVisible();
await expect(
page.getByRole("heading", { name: "Activity history" }),
).toBeVisible();
await expect(page.getByRole("dialog", { name: "Restart" })).toBeVisible();
await expect(
page.getByRole("dialog", { name: "Restart" }).getByRole("button", {
name: "Approve",
}),
).toBeVisible();
await page.getByRole("button", { name: "Close action review" }).click();
await expect(page).toHaveURL(/\/actions$/);
await expect(
@@ -36,6 +36,7 @@ test("Autopilot records a versioned server acknowledgement before effective full
await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ success: true, settings: { autonomy_level: active ? "full" : "monitor", requested_autonomy_level: active ? "full" : "monitor", effective_autonomy_level: active ? "full" : "monitor", full_mode_unlocked: active, autopilot_acknowledgement: status, investigation_budget: 15, investigation_timeout_sec: 300 }, autonomy_level: active ? "full" : "monitor", requested_autonomy_level: active ? "full" : "monitor", effective_autonomy_level: active ? "full" : "monitor", full_mode_unlocked: active, autopilot_acknowledgement: status, investigation_budget: 15, investigation_timeout_sec: 300 }) });
});
await page.goto("/patrol", { waitUntil: "domcontentloaded" });
await page.getByText("How Patrol operates", { exact: true }).click();
await page.getByRole("button", { name: "Autopilot" }).click();
await expect(page.getByRole("dialog", { name: "Activate Autopilot" })).toBeVisible();
const activate = page.getByRole("button", { name: "Record acknowledgement and activate" });
@@ -16,11 +16,11 @@ const test = base.extend<{}, WorkerFixtures>({
}, { scope: "worker" }],
});
test("Actions remains named, keyboard reachable, and free of horizontal overflow at phone width", async ({ page }, testInfo) => {
test("Activity history remains named, keyboard reachable, and free of horizontal overflow at phone width", async ({ page }, testInfo) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.route("**/api/actions?*", (route) => route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ view: "pending", actions: [], count: 0 }) }));
await page.goto("/actions", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("heading", { name: "Actions", exact: true })).toBeVisible();
await expect(page.getByRole("heading", { name: "Activity history", exact: true })).toBeVisible();
await expect(page.getByRole("tablist", { name: "Action views" })).toBeVisible();
await expect(page.getByRole("button", { name: "Refresh actions" })).toBeVisible();
const overflow = await page.evaluate(() => document.documentElement.scrollWidth > document.documentElement.clientWidth);
@@ -68,7 +68,7 @@ test.describe("Operational trust protection posture", () => {
const detailRow = coverageTable.locator("[data-inline-detail-for]").first();
await expect(detailRow).toBeVisible();
await expect(detailRow).toContainText(
/Protected:|Attention:|Unprotected:|Unknown:/,
/Protected:|Needs attention:|Unprotected:|Unknown:/,
);
await expect(detailRow).toContainText(
/Restore evidence|No restore evidence has been discovered/,
@@ -2,21 +2,6 @@ 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[] = [];
@@ -689,7 +674,7 @@ test("starts from the normal monitor shell and reaches the canonical attention q
const patrolNavigation = testInfo.project.name.startsWith("mobile-")
? page
.getByRole("tablist", { name: "Mobile navigation" })
.getByRole("navigation", { name: "Mobile navigation" })
.getByRole("button", { name: /Patrol/ })
: page
.getByRole("tab", { name: /Patrol/ })
@@ -698,7 +683,7 @@ test("starts from the normal monitor shell and reaches the canonical attention q
await expect(page).toHaveURL(/\/patrol/);
await expect(
page.getByRole("region", { name: "Needs attention" }),
page.getByRole("region", { name: "Needs you" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Open CPU pressure on pve-main" }),
@@ -718,8 +703,8 @@ test("makes active operational work primary and preserves the evidence boundary"
}),
),
).toBeVisible();
const queue = page.getByRole("region", { name: "Needs attention" });
await expect(queue.getByLabel("2 active attention items")).toBeVisible();
const queue = page.getByRole("region", { name: "Needs you" });
await expect(queue.getByLabel("2 decisions need you")).toBeVisible();
await expect(
queue.getByText(
"CPU has remained above the configured threshold for two collection cycles.",
@@ -733,28 +718,6 @@ test("makes active operational work primary and preserves the evidence boundary"
page.getByRole("button", { name: "Explain with Assistant" }),
).toHaveCount(0);
await selectAttentionState(page, "Acknowledged 1", "acknowledged");
await expect(
queue.getByText("Memory pressure acknowledged on pve-lab"),
).toBeVisible();
await selectAttentionState(page, "Suppressed 1", "suppressed");
await expect(
queue.getByText("Maintenance alert suppressed on pve-maintenance"),
).toBeVisible();
await selectAttentionState(page, "Stale or unknown 1", "stale_unknown");
await expect(
queue.getByText("Connection state unknown for pve-edge"),
).toBeVisible();
await expect(queue.getByText("Evidence incomplete")).toBeVisible();
await expect(queue.getByText(/timing unavailable/)).toHaveCount(0);
await expect(queue.getByText("Protection Unknown")).toHaveCount(0);
await expect(queue.getByText("Unknown / Partial")).toHaveCount(0);
await selectAttentionState(page, "Recent resolved 1", "resolved");
await expect(
queue.getByText("Storage pressure resolved on pve-recovered"),
).toBeVisible();
await selectAttentionState(page, "Active 2", "active");
const itemButton = queue.getByRole("button", {
name: "Open CPU pressure on pve-main",
});
@@ -873,10 +836,10 @@ test("shows calm only with current coverage and never converts failure into heal
const fixture = await mockAttention(page, "calm");
await page.goto("/patrol", { waitUntil: "domcontentloaded" });
await expect(page.getByText("Nothing needs your attention")).toBeVisible();
await expect(page.getByText("Nothing needs you right now")).toBeVisible();
await expect(
page.getByText(
"The current operational lifecycle evaluation has no active items.",
"The current operational evaluation has no active items.",
{
exact: false,
},
@@ -1263,6 +1226,16 @@ async function mockGovernedAttentionAction(
body: JSON.stringify({
audit,
events: [],
readiness: {
ready: actionState !== "completed",
code: actionState === "completed" ? "action_not_actionable" : "ready",
message:
actionState === "completed"
? "This action is already complete."
: "The target is feasible and ready for approval.",
refreshable: actionState !== "completed",
checkedAt: evaluatedAt,
},
...(actionState === "completed"
? {
attempt: {
@@ -64,7 +64,7 @@ async function routeResources(page: Page, resources: RouteResource[]) {
// Keep the fixture authoritative: an empty mocked socket makes the
// websocket-first resource hook fall back to the routed REST snapshot
// without accepting a live backend frame.
await page.routeWebSocket("**/ws", () => {});
await page.routeWebSocket("**/ws*", () => {});
await page.route("**/api/resources**", async (route) => {
const requestUrl = new URL(route.request().url());
if (requestUrl.pathname !== "/api/resources") {
@@ -587,10 +587,12 @@ test.describe("Operational trust availability resource facet", () => {
await page.goto("/patrol", { waitUntil: "domcontentloaded" });
const queue = page.getByRole("region", { name: "Needs attention" });
const queue = page.getByRole("region", { name: "Needs you" });
await expect(queue).toBeVisible({ timeout: 30_000 });
await expect(
page.getByRole("tab", { name: "Patrol: 1 active attention item" }),
page.getByRole("tab", {
name: /Patrol: 1 (?:action awaits approval|active attention item)/,
}),
).toBeVisible();
await expect(queue.getByText(item.plainLanguageSummary)).toBeVisible();