From 58e0cd9e6c63209485adedd84ffd3f7c5d359cf8 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 14 Aug 2026 15:38:48 +0100 Subject: [PATCH] Refresh Patrol browser qualification --- .../tests/18-patrol-runtime-state.spec.ts | 5 +- ...patrol-assistant-operator-briefing.spec.ts | 15 ++-- .../78-monitor-first-patrol-workbench.spec.ts | 23 +++---- .../tests/81-actions-inbox.spec.ts | 68 ++++++++++++++----- .../82-autopilot-acknowledgement.spec.ts | 1 + .../83-product-trust-accessibility.spec.ts | 4 +- ...erational-trust-protection-posture.spec.ts | 2 +- ...rational-trust-attention-workbench.spec.ts | 59 +++++----------- ...erational-trust-availability-facet.spec.ts | 8 ++- 9 files changed, 96 insertions(+), 89 deletions(-) diff --git a/tests/integration/tests/18-patrol-runtime-state.spec.ts b/tests/integration/tests/18-patrol-runtime-state.spec.ts index 6f02c65d5..e1c6c6dd5 100644 --- a/tests/integration/tests/18-patrol-runtime-state.spec.ts +++ b/tests/integration/tests/18-patrol-runtime-state.spec.ts @@ -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( diff --git a/tests/integration/tests/73-patrol-assistant-operator-briefing.spec.ts b/tests/integration/tests/73-patrol-assistant-operator-briefing.spec.ts index 9e66e86ad..f0a21b7c6 100644 --- a/tests/integration/tests/73-patrol-assistant-operator-briefing.spec.ts +++ b/tests/integration/tests/73-patrol-assistant-operator-briefing.spec.ts @@ -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(); diff --git a/tests/integration/tests/78-monitor-first-patrol-workbench.spec.ts b/tests/integration/tests/78-monitor-first-patrol-workbench.spec.ts index 41164069e..38ecf39c9 100644 --- a/tests/integration/tests/78-monitor-first-patrol-workbench.spec.ts +++ b/tests/integration/tests/78-monitor-first-patrol-workbench.spec.ts @@ -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 diff --git a/tests/integration/tests/81-actions-inbox.spec.ts b/tests/integration/tests/81-actions-inbox.spec.ts index b7af19610..2dc5b8324 100644 --- a/tests/integration/tests/81-actions-inbox.spec.ts +++ b/tests/integration/tests/81-actions-inbox.spec.ts @@ -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( diff --git a/tests/integration/tests/82-autopilot-acknowledgement.spec.ts b/tests/integration/tests/82-autopilot-acknowledgement.spec.ts index c753514b2..29e62ebd9 100644 --- a/tests/integration/tests/82-autopilot-acknowledgement.spec.ts +++ b/tests/integration/tests/82-autopilot-acknowledgement.spec.ts @@ -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" }); diff --git a/tests/integration/tests/83-product-trust-accessibility.spec.ts b/tests/integration/tests/83-product-trust-accessibility.spec.ts index ac72fbb09..479c50e4a 100644 --- a/tests/integration/tests/83-product-trust-accessibility.spec.ts +++ b/tests/integration/tests/83-product-trust-accessibility.spec.ts @@ -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); diff --git a/tests/integration/tests/90-operational-trust-protection-posture.spec.ts b/tests/integration/tests/90-operational-trust-protection-posture.spec.ts index 1be52ebe4..d152db16a 100644 --- a/tests/integration/tests/90-operational-trust-protection-posture.spec.ts +++ b/tests/integration/tests/90-operational-trust-protection-posture.spec.ts @@ -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/, diff --git a/tests/integration/tests/91-operational-trust-attention-workbench.spec.ts b/tests/integration/tests/91-operational-trust-attention-workbench.spec.ts index df3992fee..41018fd4c 100644 --- a/tests/integration/tests/91-operational-trust-attention-workbench.spec.ts +++ b/tests/integration/tests/91-operational-trust-attention-workbench.spec.ts @@ -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: { diff --git a/tests/integration/tests/92-operational-trust-availability-facet.spec.ts b/tests/integration/tests/92-operational-trust-availability-facet.spec.ts index 4a84cc088..69749ab3f 100644 --- a/tests/integration/tests/92-operational-trust-availability-facet.spec.ts +++ b/tests/integration/tests/92-operational-trust-availability-facet.spec.ts @@ -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();