tests/e2e/observability-work-inspector.spec.ts
Rama main · réplica de lectura del repositorio original.
import { expect, test } from "@playwright/test";
import type { ObservabilityJobDetail } from "../../lib/types";
import { enableScopedE2EAuth, scopedPath } from "./helpers/auth";
import { mockActivationFlow } from "./helpers/activation-fixtures";
import { buildObservabilitySummary } from "./helpers/sandbox-fixtures";
import { fulfillJson } from "./helpers/automation-control-plane-fixtures";
import { textContrast } from "./helpers/text-contrast";
const run: ObservabilityJobDetail = {
id: "00000000-0000-4000-8000-000000000077",
assignment_id: null,
trigger_id: null,
status: "success",
created_at: "2026-09-05T10:00:00Z",
started_at: "2026-09-05T10:00:00Z",
completed_at: "2026-09-05T10:01:10Z",
input_tokens: 100,
output_tokens: 20,
duration_ms: 70000,
cost_usd: 0.352,
error: null,
start_attempts: 1,
metadata: {
pr_number: 1477,
pr_title: "Fix mobile canvas header overlap",
head_ref: "fix/mobile",
},
source_kind: "flow",
source_type: "pr_opened",
repo: { id: "repo-1", full_name: "acme/widgets" },
agent: { id: "agent-1", name: "PR Review Agent", slug: "review" },
latest_ai_call: null,
latest_dispatch_event: {
id: "event-1",
event_kind: "control",
outcome: "completed",
reason: "PR_REVIEW_NO_FINDINGS",
metadata: null,
created_at: "2026-09-05T10:01:10Z",
},
repairable: false,
requeueable: false,
cancelable: false,
dispatch_events: [],
ai_calls: [],
review_findings: [],
};
test("scrolling past the inspector end does not move the page", async ({
page,
}) => {
await page.setViewportSize({ width: 1440, height: 900 });
await enableScopedE2EAuth(page);
await mockActivationFlow(page);
const longRun: ObservabilityJobDetail = {
...run,
review_findings: Array.from({ length: 10 }, (_, ordinal) => ({
id: `finding-${ordinal}`,
user_id: "user-1",
job_run_id: run.id,
repo_id: "repo-1",
repo_full_name: "acme/widgets",
pr_number: 1477,
head_sha: null,
ordinal,
fingerprint: `finding-${ordinal}`,
status: "open",
issue_number: null,
issue_url: null,
dismissed_at: null,
created_at: run.created_at,
updated_at: run.created_at,
severity: "suggestion",
title: `Review finding ${ordinal + 1}`,
body: "Keep the current behavior when updating the shared component. ".repeat(
8
),
path: "components/example.tsx",
line: null,
})),
};
await page.route("**/api/observability/stats*", (route) =>
fulfillJson(route, buildObservabilitySummary([]))
);
await page.route("**/api/observability/jobs?*", (route) =>
fulfillJson(route, { jobs: [run], total: 1, page: 1, limit: 25 })
);
await page.route(`**/api/observability/jobs/${run.id}?*`, (route) =>
fulfillJson(route, { run: longRun })
);
await page.goto(scopedPath(`observability?view=runs&run_id=${run.id}`));
const inspector = page.getByRole("region", { name: "Run details" });
await expect(
inspector.getByRole("heading", { name: "Review PR #1477" })
).toBeVisible();
const shell = page.locator(".app-shell-content");
const pageScroll = await shell.evaluate((element) => element.scrollTop);
const end = await inspector.evaluate(
(element) => element.scrollHeight - element.clientHeight
);
expect(end).toBeGreaterThan(0);
const bounds = await inspector.boundingBox();
if (!bounds) throw new Error("Inspector is not visible");
await page.mouse.move(bounds.x + bounds.width / 2, bounds.y + 30);
const scrolled = inspector.evaluate(
(element) =>
new Promise<void>((resolve) =>
// eslint-disable-next-line github/prefer-observers -- One-shot native scroll synchronization in this browser test.
element.addEventListener("scroll", () => resolve(), { once: true })
)
);
await page.mouse.wheel(0, 10000);
await scrolled;
expect(
await inspector.evaluate(
(element) =>
element.scrollHeight - element.clientHeight - element.scrollTop
)
).toBeLessThan(2);
await page.mouse.wheel(0, 500);
// Observe after the browser has painted the second native wheel gesture.
await page.evaluate(
() =>
new Promise<void>((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(() => resolve()))
)
);
expect(await shell.evaluate((element) => element.scrollTop)).toBe(pageScroll);
await expect(inspector).toHaveCSS("overscroll-behavior-y", "contain");
await expect(shell).toHaveCSS("overscroll-behavior-y", "none");
await expect(page.locator("html")).toHaveCSS("overscroll-behavior-y", "none");
await expect(shell).toHaveCSS("overscroll-behavior-x", "auto");
await expect(page.locator("html")).toHaveCSS("overscroll-behavior-x", "auto");
});
test.describe("touch scrolling", () => {
test.use({
hasTouch: true,
isMobile: true,
viewport: { width: 390, height: 844 },
});
test("keeps native pull-to-refresh available", async ({ page }) => {
await enableScopedE2EAuth(page);
await mockActivationFlow(page);
await page.goto(scopedPath("agents/roster"));
await expect(page.getByTestId("dashboard-shell")).toBeVisible();
await expect(page.locator("html")).toHaveCSS(
"overscroll-behavior-y",
"auto"
);
await expect(page.locator("body")).toHaveCSS(
"overscroll-behavior-y",
"auto"
);
await expect(page.locator(".app-shell-content")).toHaveCSS(
"overscroll-behavior-y",
"auto"
);
});
});
for (const viewport of [
{ width: 1440, height: 1000 },
{ width: 834, height: 1112 },
{ width: 390, height: 844 },
]) {
for (const theme of ["dark", "light"]) {
test(`outcome-first inspector ${viewport.width} ${theme}`, async ({
page,
}, testInfo) => {
await page.setViewportSize(viewport);
await enableScopedE2EAuth(page);
await mockActivationFlow(page);
await page.route("**/api/observability/stats*", (route) =>
fulfillJson(route, buildObservabilitySummary([]))
);
await page.route("**/api/flows/approvals", (route) =>
fulfillJson(route, { approvals: [] })
);
await page.route("**/api/observability/jobs?*", (route) =>
fulfillJson(route, { jobs: [run], total: 1, page: 1, limit: 25 })
);
await page.route(`**/api/observability/jobs/${run.id}?*`, (route) =>
fulfillJson(route, { run })
);
await page.goto(scopedPath("observability?run_source=flow"));
// Exercise both themes against the same real component tree.
await page.evaluate(
(value) =>
document.documentElement.classList.toggle("dark", value === "dark"),
theme
);
await page
.getByRole("button", { name: "Inspect Review PR #1477", exact: true })
.click();
const inspector = page.getByRole("region", { name: "Run details" });
await expect(
inspector.getByRole("heading", { name: "Review PR #1477" })
).toBeVisible();
await expect(
inspector.getByText(
"The review completed with no findings. This does not mean the PR has been merged."
)
).toBeVisible();
await expect(
inspector.getByRole("link", { name: "Open PR", exact: true })
).toHaveAttribute("href", "https://github.com/acme/widgets/pull/1477");
await expect(
inspector.getByText("Cancellation requested", { exact: true })
).toHaveCount(0);
await expect(
inspector.getByText("Sanitized metadata", { exact: true })
).toHaveCount(0);
await expect(page).toHaveURL(/run_id=/);
await page.reload();
await page.evaluate(
(value) =>
document.documentElement.classList.toggle("dark", value === "dark"),
theme
);
await expect(
inspector.getByRole("heading", { name: "Review PR #1477" })
).toBeVisible();
await expect(page).toHaveURL(/run_source=flow/);
await inspector.scrollIntoViewIfNeeded();
expect(
await textContrast(
inspector.locator("span").filter({ hasText: /^Completed$/ })
)
).toBeGreaterThanOrEqual(4.5);
expect(
await textContrast(
inspector.getByRole("link", { name: "Open PR", exact: true })
)
).toBeGreaterThanOrEqual(4.5);
expect(
await textContrast(
inspector.getByText("Fix mobile canvas header overlap", {
exact: true,
})
)
).toBeGreaterThanOrEqual(4.5);
await page.screenshot({
path: testInfo.outputPath("inspector.png"),
fullPage: true,
});
expect(
await page
.locator(".observability-work")
.evaluate((element) => element.scrollWidth <= element.clientWidth)
).toBe(true);
await inspector
.getByRole("tab", { name: "Diagnostics", exact: true })
.click();
await expect(
inspector.getByText("Cancellation requested", { exact: true })
).toHaveCount(0);
const metadata = inspector.locator("details");
await expect(metadata).not.toHaveAttribute("open", "");
await metadata.locator("summary").click();
await expect(metadata).toHaveAttribute("open", "");
await inspector.getByRole("button", { name: "Back to runs" }).click();
await expect(
page.getByRole("button", {
name: "Inspect Review PR #1477",
exact: true,
})
).toBeVisible();
await page
.getByRole("tab", { name: "Needs attention", exact: true })
.click();
await expect(
page.getByText(
"No workflow approval requests are waiting for your decision."
)
).toBeVisible();
await page.getByRole("tab", { name: /Automation events/ }).click();
await expect(
page.getByText(/Counts are events, not failed runs/)
).toBeVisible();
await page.getByRole("tab", { name: /^Usage/ }).click();
await expect(
page.getByRole("heading", { name: "Usage and cost" })
).toBeVisible();
await expect(page).toHaveURL(/view=usage/);
});
}
}
test("failed run confirms new-attempt consequences and never invents recovery", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await mockActivationFlow(page);
const failed = {
...run,
status: "failed" as const,
error: "Timed out",
requeueable: true,
latest_dispatch_event: null,
};
let attempts = 0;
await page.route("**/api/observability/stats*", (route) =>
fulfillJson(route, buildObservabilitySummary([]))
);
await page.route(`**/api/observability/jobs/${run.id}?*`, (route) =>
fulfillJson(route, { run: failed })
);
await page.route(`**/api/observability/jobs/${run.id}/requeue`, (route) => {
attempts++;
return fulfillJson(route, { ok: true });
});
await page.goto(scopedPath(`observability?view=runs&run_id=${run.id}`));
const inspector = page.getByRole("region", { name: "Run details" });
await expect(inspector.getByText("Timed out", { exact: true })).toBeVisible();
await expect(
inspector.getByRole("button", { name: "Resume saved work" })
).toHaveCount(0);
await inspector
.getByRole("button", { name: "Retry as new run", exact: true })
.click();
expect(attempts).toBe(0);
await expect(
inspector.getByText(/may repeat work and incur additional cost/)
).toBeVisible();
await inspector
.getByRole("button", { name: "Confirm retry as new run" })
.click();
await expect(
inspector.getByRole("group", { name: "Retry as new run" })
).toHaveCount(0);
expect(attempts).toBe(1);
});
test("inaccessible run has a recoverable error without unrelated details", async ({
page,
}) => {
await enableScopedE2EAuth(page);
await mockActivationFlow(page);
await page.route("**/api/observability/stats*", (route) =>
fulfillJson(route, buildObservabilitySummary([]))
);
await page.route(`**/api/observability/jobs/${run.id}?*`, (route) =>
fulfillJson(route, { error: "Not found" }, 404)
);
await page.goto(scopedPath(`observability?view=runs&run_id=${run.id}`));
await expect(
page.getByRole("alert").filter({ hasText: "This run is unavailable" })
).toBeVisible();
await expect(
page
.getByRole("region", { name: "Run details" })
.getByRole("link", { name: "Open PR" })
).toHaveCount(0);
});