tests/e2e/diff-rendering-observability.spec.ts
Rama main · réplica de lectura del repositorio original.
import { expect, test } from "@playwright/test";
import { enableScopedE2EAuth, scopedPath } from "./helpers/auth";
import { emptyAutomationFailuresResponse } from "./helpers/activation-fixtures";
import {
connectedUser,
fulfillJson,
mockBaseApp,
modelId,
repo,
} from "./helpers/diff-rendering-fixtures";
test("observability renders nested tool output diffs with the shared viewer", async ({
page,
}) => {
const now = new Date().toISOString();
const patch = [
"diff --git a/src/agent.ts b/src/agent.ts",
"index 1111111..2222222 100644",
"--- a/src/agent.ts",
"+++ b/src/agent.ts",
"@@ -1 +1 @@",
"-export const mode = 'idle'",
"+export const mode = 'ready'",
"",
].join("\n");
// Register the base mocks FIRST: Playwright matches routes in reverse
// registration order (last registered wins), so the observability-specific
// mocks below must come after mockBaseApp's empty-calls defaults.
await enableScopedE2EAuth(page);
await mockBaseApp(page);
await page.route("**/api/realtime/events?*", (route) =>
route.fulfill({ status: 204 })
);
await page.route("**/api/observability/calls*", (route) =>
fulfillJson(route, {
calls: [
{
id: "call-1",
user_id: connectedUser.id,
type: "chat",
model: modelId,
input_tokens: 10,
output_tokens: 12,
total_tokens: 22,
duration_ms: 1200,
started_at: now,
completed_at: now,
status: "success",
error: null,
conversation_id: "conversation-1",
job_run_id: null,
repo_id: repo.id,
tool_calls_count: 1,
tool_calls: [
{
name: "git_diff",
input_preview: '{"repo":"acme/demo-app"}',
input: { repo: repo.full_name },
output_preview: "diff --git a/src/agent.ts b/src/agent.ts",
output: { stdout: patch },
duration_ms: 75,
},
],
metadata: { repo: repo.full_name },
},
],
total: 1,
page: 1,
limit: 20,
})
);
await page.route(/\/api\/observability\/call-events(?:\?.*)?$/, (route) =>
fulfillJson(route, { events: [] })
);
await page.route("**/api/observability/stats*", (route) =>
fulfillJson(route, {
summary: {
total_calls: 1,
total_tokens: 22,
total_cost: 0,
cost_today: 0,
reconciliation_pending: 0,
avg_duration_ms: 1200,
success_rate: 1,
calls_today: 1,
tokens_today: 22,
sandbox_time_ms: 0,
sandbox_active: 0,
sandbox_total: 0,
job_runs_total: 0,
job_runs_running: 0,
job_runs_pending: 0,
job_runs_stale_pending: 0,
job_runs_failed_in_range: 0,
job_runs_repaired_in_range: 0,
job_runs_success_rate_in_range: 0,
suppressed_in_range: 0,
deferred_in_range: 0,
start_failed_in_range: 0,
limit_allowed_in_range: 0,
limit_denied_in_range: 0,
oldest_pending_age_ms: 0,
},
by_model: [],
by_type: [],
})
);
await page.route(
/\/api\/observability\/automation-events(?:\?.*)?$/,
(route) =>
fulfillJson(route, {
events: [],
total: 0,
page: 1,
limit: 25,
})
);
await page.route(
/\/api\/observability\/automation-failures(?:\?.*)?$/,
(route) => fulfillJson(route, emptyAutomationFailuresResponse)
);
await page.route(/\/api\/observability\/jobs(?:\?.*)?$/, (route) =>
fulfillJson(route, {
jobs: [],
total: 0,
page: 1,
limit: 25,
})
);
// Expand the call row via the call_id query param instead of clicking the
// row: the row-toggle handler navigates through buildObservabilityHref's
// unscoped "/observability?..." href, which only works in production because
// proxy.ts rescue-redirects it back into the scope — the e2e bypass skips
// that rescue, so a click would strand the test on a bogus
// "/observability" scope.
await page.goto(scopedPath("observability?call_id=call-1"));
await page.waitForLoadState("networkidle");
await expect(
page.getByRole("heading", { name: "Observability", level: 1 })
).toBeVisible();
// The call_id deep link lands the table tab group on the Activity tab.
await expect(page.getByRole("tab", { name: /^Usage/ })).toHaveAttribute(
"aria-selected",
"true"
);
await expect(page.getByRole("row", { name: /minimax-m2\.5/i })).toBeVisible();
await expect(page.getByText("Diff source: stdout")).toBeVisible();
await expect(page.getByText("src/agent.ts")).toBeVisible();
await expect(page.getByText("export const mode = 'ready'")).toBeVisible();
});