Align first-session, migration, and settings-shell specs with shipped UI

The settings shell copy moved (General, Billing & Usage, Provider & Models
and their descriptions); spec 15 now mirrors settingsHeaderMeta instead of
pre-rework titles. The first-run agent handoff pre-provisions the scoped
install token, so spec 11 asserts that contract rather than a Generate
token button, exempts the app shell's Patrol open-work polling from the
no-AI-bootstrap guard (it powers the Patrol nav badge on every route),
treats navigation-aborted auth probes as benign console noise, and targets
/settings/support/reporting directly now that the /operations alias is
retired. Spec 12 scopes migration-notice assertions to the settings content
because the global banner legitimately renders the same copy.
This commit is contained in:
rcourtman 2026-07-08 11:10:08 +01:00
parent 5f3228cac4
commit d88248363e
3 changed files with 35 additions and 14 deletions

View file

@ -91,9 +91,12 @@ test.describe.serial("First-session experience", () => {
await expect(
agentDialog.getByRole("heading", { name: "Install on a host" }),
).toBeVisible();
// The first-run handoff pre-provisions the scoped install token, so no
// "Generate token" step exists in this variant of the dialog.
await expect(
agentDialog.getByRole("button", { name: "Generate token" }),
agentDialog.getByText(/already prepared the first scoped install token/i),
).toBeVisible();
await expect(agentDialog.getByText("Admin API Token")).toBeVisible();
});
test("shell shows key navigation elements", async ({ page }, testInfo) => {
@ -167,6 +170,11 @@ test.describe.serial("First-session experience", () => {
] as const;
const consoleErrors: string[] = [];
// The app shell polls Patrol open work (stored findings + pending
// approvals) on every authenticated route to drive the Patrol nav badge.
// Those are cheap reads, not AI invocations, so they are exempt here.
const appShellPatrolPolling =
/\/api\/ai\/(?:patrol\/findings|approvals)(?:\?|$)/;
const aiRequests = trackBrowserRequests(page, /\/api\/ai(\/|$)/);
page.on("console", (msg) => {
if (msg.type() === "error") {
@ -199,19 +207,25 @@ test.describe.serial("First-session experience", () => {
await page.waitForTimeout(500);
const unexpectedAIRequests = aiRequests
.urls()
.filter((url) => !appShellPatrolPolling.test(url));
expect(
aiRequests.count(),
`Non-AI settings route ${route} should not bootstrap AI endpoints: ${aiRequests.urls().join(", ")}`,
unexpectedAIRequests.length,
`Non-AI settings route ${route} should not bootstrap AI endpoints: ${unexpectedAIRequests.join(", ")}`,
).toBe(0);
// Filter out benign console noise.
// Filter out benign console noise. "Auth check error ... Failed to
// fetch" is the previous route's in-flight auth probe aborted by
// page.goto, not a product error.
const realErrors = consoleErrors.filter(
(e) =>
!e.includes("Download the React DevTools") &&
!e.includes("Warning:") &&
!e.includes("favicon") &&
!e.includes("ERR_CONNECTION_REFUSED") &&
!e.includes("net::"),
!e.includes("net::") &&
!(e.includes("Auth check error") && e.includes("Failed to fetch")),
);
expect(
@ -254,7 +268,7 @@ test.describe.serial("First-session experience", () => {
// advanced_reporting or audit_logging.
const gatedRoutes = [
{
route: "/operations/reporting",
route: "/settings/support/reporting",
expectedURL: /\/settings\/support\/reporting/,
feature: "advanced_reporting",
},

View file

@ -205,9 +205,12 @@ async function expectMigrationNotice(page: Page, migration: MigrationFixture) {
migration.reason,
migration.recommended_action,
);
await expect(page.getByText(expectedCopy.title)).toBeVisible();
// Scope to the settings content: the global CommercialMigrationBanner
// renders the same title outside <main>, which trips strict mode.
const settingsContent = page.getByRole('main');
await expect(settingsContent.getByText(expectedCopy.title)).toBeVisible();
for (const fragment of expectedCopy.bodyFragments) {
await expect(page.getByText(fragment)).toBeVisible();
await expect(settingsContent.getByText(fragment)).toBeVisible();
}
await expect(page.getByRole('button', { name: /start 14-day pro trial/i })).toHaveCount(0);
}

View file

@ -10,11 +10,13 @@ type WorkerFixtures = {
authStorageStatePath: string;
};
// Titles and descriptions mirror the canonical copy in
// frontend-modern/src/components/Settings/settingsHeaderMeta.ts.
const SETTINGS_SHELL_ROUTES = [
{
route: '/settings/system-general',
title: 'General Settings',
description: 'Configure appearance, layout, and default monitoring cadence.',
title: 'General',
description: 'Manage appearance, layout, and default monitoring cadence.',
},
{
route: '/settings/organization',
@ -28,8 +30,9 @@ const SETTINGS_SHELL_ROUTES = [
},
{
route: '/settings/organization/billing',
title: 'Organization Billing',
description: 'Track plan tier, usage, and upgrade options for multi-tenant deployments.',
title: 'Billing & Usage',
description:
'Review your organization plan, applicable usage policies, and subscription status for paid access.',
},
{
route: '/settings/system-relay',
@ -43,8 +46,9 @@ const SETTINGS_SHELL_ROUTES = [
},
{
route: '/settings/system-ai',
title: 'Assistant & Patrol',
description: 'Configure providers and models for Pulse Assistant and Patrol.',
title: 'Provider & Models',
description:
'Configure providers, default models, provider health, budget, and usage for Pulse Intelligence.',
},
{
route: '/settings/system-updates',