mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
test(e2e): mount every overview section before asserting none paints
The AI-optional overview and account journeys asserted zero error cards, Coach hand-offs and briefings without scrolling the deferred sections into view, so the sections below the first three never mounted and the zero counts held whatever they would paint. Both journeys now reveal every section and wait for the reads those sections fire to land; waiting on networkidle again resolves at once, because the page reached that state before.
This commit is contained in:
1 parent
2bbcce73e4
commit
3681b23d30
3 files changed
+62
-5
No files matched your search
@@ -25,6 +25,7 @@ import {
|
||||
seedStoredBriefing,
|
||||
} from "./setup/ai-optional-fixture";
|
||||
import { expect, test } from "./setup/test";
|
||||
import { revealDeferredSectionsSettled } from "./utils/deferred-sections";
|
||||
|
||||
test.use({ storageState: AI_OPTIONAL_STORAGE_STATE_PATH });
|
||||
test.describe.configure({ mode: "serial" });
|
||||
@@ -36,12 +37,18 @@ test.afterAll(async () => {
|
||||
await resetAiOptionalAccount();
|
||||
});
|
||||
|
||||
/**
|
||||
* The overview with every section mounted. Sections past the first three
|
||||
* render as an empty sentinel until they near the viewport, so a count of
|
||||
* zero taken before they mount would pass whatever they paint.
|
||||
*/
|
||||
async function openOverview(page: Page): Promise<void> {
|
||||
await page.goto("/insights");
|
||||
await expect(page.locator('[data-slot="insights-hero-strip"]')).toBeVisible({
|
||||
timeout: 20_000,
|
||||
});
|
||||
await page.waitForLoadState("networkidle");
|
||||
await revealDeferredSectionsSettled(page);
|
||||
}
|
||||
|
||||
test("J5: withdrawing AI consent deletes the notes written under it", async ({
|
||||
@@ -80,12 +87,12 @@ test("J4: the Coach memory stays readable and deletable with the Coach hidden",
|
||||
const title = `Stored conversation ${Date.now()}`;
|
||||
await seedHiddenCoachWithConversation(title);
|
||||
|
||||
// Hide Coach takes the Coach page and its launcher away...
|
||||
await page.goto("/insights");
|
||||
await expect(page.locator('[data-slot="insights-hero-strip"]')).toBeVisible({
|
||||
timeout: 20_000,
|
||||
});
|
||||
// Hide Coach takes the Coach page, its launcher and every per-card
|
||||
// hand-off away...
|
||||
await openOverview(page);
|
||||
await expect(page.locator('[data-slot="coach-fab"]')).toHaveCount(0);
|
||||
await expect(page.locator('[data-slot="ask-coach-action"]')).toHaveCount(0);
|
||||
await expect(page.locator('[data-slot="ask-coach-icon"]')).toHaveCount(0);
|
||||
|
||||
// ...and Settings, AI lists what the Coach stored.
|
||||
await page.goto("/settings/ai");
|
||||
|
||||
@@ -21,9 +21,19 @@ import type { Page, Request, Response } from "@playwright/test";
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
import { aiBlockUnavailable, serveAiBlock } from "./setup/ai-capabilities";
|
||||
import { expect, test } from "./setup/test";
|
||||
import { revealDeferredSectionsSettled } from "./utils/deferred-sections";
|
||||
|
||||
test.use({ storageState: STORAGE_STATE_PATH });
|
||||
|
||||
/**
|
||||
* Mount every overview section, then let their reads land. Sections past the
|
||||
* first three render as an empty sentinel until they near the viewport, so a
|
||||
* count of zero taken before this would pass whatever those sections paint.
|
||||
*/
|
||||
async function mountWholeOverview(page: Page): Promise<void> {
|
||||
await revealDeferredSectionsSettled(page);
|
||||
}
|
||||
|
||||
/** Routes that call a model or serve model-written text. */
|
||||
const AI_ROUTE = new RegExp(
|
||||
[
|
||||
@@ -89,6 +99,7 @@ test.describe("J1: AI switched off by the operator", () => {
|
||||
);
|
||||
// Let every section's reads land before judging what painted.
|
||||
await page.waitForLoadState("networkidle");
|
||||
await mountWholeOverview(page);
|
||||
|
||||
// A read refused for want of AI paints one of these on the overview:
|
||||
// the section boundary, the error card, or the dense error row.
|
||||
@@ -137,6 +148,7 @@ test.describe("J3: no AI provider", () => {
|
||||
timeout: 20_000,
|
||||
});
|
||||
await page.waitForLoadState("networkidle");
|
||||
await mountWholeOverview(page);
|
||||
await expect(page.locator('[data-slot="coach-fab"]')).toHaveCount(0);
|
||||
await expect(page.locator('[data-slot="daily-briefing"]')).toHaveCount(0);
|
||||
// The per-card Coach hand-offs paint only while the Coach is available.
|
||||
@@ -163,6 +175,7 @@ test.describe("J3: no AI provider", () => {
|
||||
{ timeout: 20_000 },
|
||||
);
|
||||
await page.waitForLoadState("networkidle");
|
||||
await mountWholeOverview(page);
|
||||
await expect(page.locator('[data-slot="ai-setup-hint"]')).toHaveCount(0);
|
||||
|
||||
expect(network.aiRequests).toEqual([]);
|
||||
|
||||
@@ -36,3 +36,40 @@ export async function revealDeferredSections(page: Page): Promise<void> {
|
||||
.toBe(0);
|
||||
await page.evaluate(() => document.querySelector("main")?.scrollTo(0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* `revealDeferredSections`, then wait until the reads the newly mounted
|
||||
* sections fired have landed: no request in flight for 750 ms. A
|
||||
* `waitForLoadState("networkidle")` cannot do this: the page reached that
|
||||
* state once already, so it resolves at once, and a count of zero taken then
|
||||
* passes before a deferred section has painted anything.
|
||||
*/
|
||||
export async function revealDeferredSectionsSettled(page: Page): Promise<void> {
|
||||
let inFlight = 0;
|
||||
const started = () => {
|
||||
inFlight += 1;
|
||||
};
|
||||
const ended = () => {
|
||||
inFlight -= 1;
|
||||
};
|
||||
page.on("request", started);
|
||||
page.on("requestfinished", ended);
|
||||
page.on("requestfailed", ended);
|
||||
try {
|
||||
await revealDeferredSections(page);
|
||||
let quietSince = Date.now();
|
||||
await expect
|
||||
.poll(
|
||||
() => {
|
||||
if (inFlight > 0) quietSince = Date.now();
|
||||
return Date.now() - quietSince >= 750;
|
||||
},
|
||||
{ timeout: 20_000, intervals: [100] },
|
||||
)
|
||||
.toBe(true);
|
||||
} finally {
|
||||
page.off("request", started);
|
||||
page.off("requestfinished", ended);
|
||||
page.off("requestfailed", ended);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user