tests/e2e/agents-memories-page.spec.ts
Rama main · réplica de lectura del repositorio original.
import { expect, test } from "@playwright/test";
import type { Route } from "@playwright/test";
import { enableScopedE2EAuth, scopedPath } from "./helpers/auth";
import { linkedVercelCapability } from "./helpers/activation-fixtures";
const connectedUser = {
id: "user-1",
email: "alex@example.com",
username: "alex",
name: "Alex",
avatar_url: "https://example.com/avatar.png",
github_connected: true,
github_app_connected: true,
github_app_available: true,
github_connection_mode: "app" as const,
vercel: linkedVercelCapability,
};
const repo = {
id: "repo-1",
full_name: "acme/widgets",
owner: "acme",
name: "widgets",
default_branch: "main",
};
const longContent = `${"Prefers pnpm over npm. ".repeat(40)}END-OF-MEMORY`;
const memoriesPayload = {
session: [],
semantic: [
{
id: "11111111-1111-4111-8111-111111111111",
lane: "semantic",
content: longContent,
metadata: { repo_id: repo.id, source: "memories-pane" },
created_at: "2026-09-10T00:00:00.000Z",
updated_at: "2026-09-10T00:00:00.000Z",
},
{
id: "22222222-2222-4222-8222-222222222222",
lane: "semantic",
content: "Charles signs off on all pricing numbers.",
metadata: { source: "promotion" },
created_at: "2026-09-11T00:00:00.000Z",
updated_at: "2026-09-11T00:00:00.000Z",
},
],
episodic: [],
procedural: [],
counts: { session: 3, semantic: 2, episodic: 11266, procedural: 0 },
};
async function fulfillJson(route: Route, data: unknown, status = 200) {
await route.fulfill({
status,
contentType: "application/json",
body: JSON.stringify(data),
});
}
test("memories page shows exact lane totals, origin chips, and prunes noise", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await page.route("**/api/auth/user", (route) =>
fulfillJson(route, { user: connectedUser })
);
await page.route("**/api/settings", (route) =>
fulfillJson(route, { default_model: "openai/gpt-5", theme: "dark" })
);
await page.route("**/api/memberships", (route) => fulfillJson(route, []));
await page.route("**/api/repos**", (route) => fulfillJson(route, [repo]));
await page.route("**/api/memories?**", (route) =>
fulfillJson(route, memoriesPayload)
);
await page.route("**/api/memories", (route) =>
fulfillJson(route, memoriesPayload)
);
const actions: string[] = [];
await page.route("**/api/memories/actions", async (route) => {
const body = route.request().postDataJSON() as { action: string };
actions.push(body.action);
await fulfillJson(route, {
ok: true,
pruned: { harnessPrompts: 3, automationOutcomes: 11266 },
});
});
await page.goto(scopedPath("agents/context"));
// Agents is a primary destination: the sidebar highlights it, not Settings.
await expect(page.getByTestId("app-nav-agents")).toHaveAttribute(
"aria-current",
"page"
);
await expect(page.getByTestId("app-nav-settings")).not.toHaveAttribute(
"aria-current",
"page"
);
// Lane tabs show exact server totals, not the 50-row page size.
await expect(page.getByRole("button", { name: "Facts (2)" })).toBeVisible();
await expect(
page.getByRole("button", { name: "Events (11266)" })
).toBeVisible();
await expect(page.getByText("11271 stored")).toBeVisible();
// Facts is the default lane; cards carry repo and origin chips.
const cards = page.getByTestId("memory-card");
await expect(cards).toHaveCount(2);
await expect(cards.first().getByText("acme/widgets")).toBeVisible();
await expect(
cards.nth(1).getByText("promoted from a checkpoint")
).toBeVisible();
// Long content collapses until the reader asks for it.
await expect(cards.first()).not.toContainText("END-OF-MEMORY");
await cards.first().getByRole("button", { name: "Show more" }).click();
await expect(cards.first()).toContainText("END-OF-MEMORY");
// Prune runs the stale-session compaction and the noise purge.
await page.getByRole("button", { name: "Prune" }).click();
await expect.poll(() => actions).toEqual(["compact", "prune_noise"]);
});