Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

182 lines
6.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect, type Page } from "@playwright/test";
// Spec for the Session Vital Signs panel. Replaces the old
// ActivityMinimap spec — the minimap component is gone, and
// the right column now shows the four-section vital-signs
// panel rendered by SessionVitals.svelte.
//
// The fixture session `test-session-duration-showcase` is
// seeded by cmd/testfixture and exercised by scripts/e2e-server.sh.
// It contains: a solo Read turn, a parallel turn (two Reads + one
// Task with a sub-agent), and a slow Bash turn — exactly the
// shape needed to cover all four section interactions.
const SHOWCASE = "test-session-duration-showcase";
// The conversation scrolls inside `.message-list-scroll`
// (`SessionsPage.scroller`). The plan's sketch referenced
// `.conv-body`, which does not exist in the live DOM.
const SCROLLER = ".message-list-scroll";
async function gotoShowcase(page: Page) {
// Vitals panel defaults to closed. Open it via localStorage
// so the panel renders on first paint.
await page.addInitScript(() => {
localStorage.setItem("agentsview-session-vitals", "true");
});
await page.goto(`/sessions/${SHOWCASE}`);
await expect(page.locator("aside.vitals")).toBeVisible({
timeout: 5_000,
});
// Wait for timing data — the Calls section renders rows once
// the API response lands. Without this, early assertions race
// the fetch.
await expect(
page.locator(".calls .call").first(),
).toBeVisible({ timeout: 5_000 });
}
test.describe("Session Vital Signs", () => {
test("renders all four sections", async ({ page }) => {
await gotoShowcase(page);
// Section headers live inside `.v-h > span` (text-only spans),
// not semantic <h*> headings, so we match by text on .v-h
// instead of the plan-sketch's `getByRole("heading")`.
const headers = page
.locator(".v-section .v-h > span:first-child")
.filter({ hasText: /^(Session|Time spent|Timeline|Calls)$/ });
await expect(headers).toHaveCount(4);
await expect(
page.locator(".v-section .v-h", { hasText: "Session" }),
).toBeVisible();
await expect(
page.locator(".v-section .v-h", { hasText: "Time spent" }),
).toBeVisible();
await expect(
page.locator(".v-section .v-h", { hasText: "Timeline" }),
).toBeVisible();
await expect(
page.locator(".v-section .v-h", { hasText: "Calls" }),
).toBeVisible();
});
test("slowest-call link scrolls the conversation", async ({
page,
}) => {
await gotoShowcase(page);
const scroller = page.locator(SCROLLER);
// Reset to top so the click-induced scroll is observable.
// (Default scrollTop is already 0 for this fixture, but be
// explicit to avoid coupling to startup state.)
await scroller.evaluate((el) => {
el.scrollTop = 0;
});
await page.locator(".stat-grid .val-link").click();
// ui.scrollToOrdinal sets pending state; the conversation
// scrolls once MessageList processes the request.
await expect
.poll(() => scroller.evaluate((el) => el.scrollTop), {
timeout: 3_000,
})
.toBeGreaterThan(0);
});
test("Time spent row click filters siblings", async ({
page,
}) => {
await gotoShowcase(page);
// Bash exists in the showcase fixture — pick it as the
// active filter target. (Plan sketch suggested Bash.)
const bashRow = page
.locator(".agg-row")
.filter({ has: page.locator(".agg-name", { hasText: "Bash" }) });
const taskRow = page
.locator(".agg-row")
.filter({ has: page.locator(".agg-name", { hasText: "Task" }) });
await expect(bashRow).toHaveCount(1);
await expect(taskRow).toHaveCount(1);
await bashRow.click();
await expect(bashRow).toHaveClass(/\bactive\b/);
await expect(taskRow).toHaveClass(/\bdimmed\b/);
// Filter chip lives in the Time-spent section header.
const chip = page.locator(".filter-chip");
await expect(chip).toBeVisible();
await expect(chip).toContainText("Bash");
// Clear via the × inside the chip.
await chip.locator(".x").click();
await expect(page.locator(".filter-chip")).toHaveCount(0);
await expect(taskRow).not.toHaveClass(/\bdimmed\b/);
await expect(bashRow).not.toHaveClass(/\bactive\b/);
});
test("clicking a slow call scrolls the conversation", async ({
page,
}) => {
await gotoShowcase(page);
const scroller = page.locator(SCROLLER);
await scroller.evaluate((el) => {
el.scrollTop = 0;
});
// The slow threshold algorithm marks only the longest call
// when fewer than 10 measurable calls exist; in the showcase
// that's the Task call (120s) inside the parallel group.
// We want a `.call` body click (not the chevron), so target
// the row's name span explicitly.
const slowCall = page.locator(".call.slow").first();
await expect(slowCall).toBeVisible();
await slowCall.locator(".cn").click();
await expect
.poll(() => scroller.evaluate((el) => el.scrollTop), {
timeout: 3_000,
})
.toBeGreaterThan(0);
});
test("sub-agent expands and collapses inline via chevron", async ({
page,
}) => {
await gotoShowcase(page);
// The sub-agent lives on the Task call inside the parallel
// group (`.cgroup`). CallRow renders `button.chev` only for
// calls with a subagent_session_id.
const taskRow = page
.locator(".cgroup .call")
.filter({
has: page.locator(".cn", { hasText: "Task" }),
});
await expect(taskRow).toHaveCount(1);
const chev = taskRow.locator("button.chev");
await expect(chev).toBeVisible();
await expect(chev).toHaveAttribute("aria-expanded", "false");
await chev.click();
const saExpand = page.locator(".sa-expand");
await expect(saExpand).toBeVisible();
// The expanded state is mirrored on the chevron button so
// assistive tech sees the toggle work.
await expect(chev).toHaveAttribute("aria-expanded", "true");
// Collapse — re-clicking the chevron tears down the panel.
await chev.click();
await expect(saExpand).toHaveCount(0);
await expect(chev).toHaveAttribute("aria-expanded", "false");
});
});