Ctrl K
ComunidadGuías…

tests/e2e/flows-pane-runs-observability.spec.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { expect, test, type Page } from "@playwright/test";
import { buildE2EAuthHeaders } from "./helpers/auth";
import { connectedUser, fulfillJson } from "./helpers/flows-pane-runs-fixtures";

test("recent runs open a detailed observability dialog", async ({ page }) => {
  await checkRunDialog(page, false);
});

test("draft snapshots are identified in the run dialog", async ({ page }) => {
  await checkRunDialog(page, true);
});

async function checkRunDialog(page: Page, draftTest: boolean) {
  await page.route("**/api/realtime/events?*", (route) =>
    route.fulfill({ status: 204 })
  );
  // Width must be >= 1520 so the flows container (minus ~240px sidebar) exceeds
  // the 1280px dock-mode threshold.
  await page.setViewportSize({ width: 1600, height: 900 });
  await page.context().setExtraHTTPHeaders({
    ...buildE2EAuthHeaders(connectedUser.id),
    "x-mogplex-scope-kind": "personal",
    "x-mogplex-scope-slug": connectedUser.username,
    "x-mogplex-scope-id": connectedUser.id,
  });
  await page.addInitScript(() => {
    window.localStorage.setItem("theme", "dark");
    window.localStorage.setItem(
      "flow-assistant-panel",
      JSON.stringify({
        state: {
          open: true,
        },
        version: 0,
      })
    );
  });
  const flowPayload = {
    id: "flow-1",
    installation_id: 101,
    name: "NEXTJS-REVIEWER · PR opened",
    description: "Migrated from Trigger",
    notes: "Capture intent, guardrails, and context for this flow.",
    source_kind: "github",
    status: "active",
    last_run_status: "success",
    published_version_id: "version-1",
    published_version: {
      id: "version-1",
      flow_id: "flow-1",
      version_number: 1,
      graph: {
        nodes: [
          {
            id: "start",
            type: "start",
            position: { x: 120, y: 160 },
            data: { label: "PR opened", event: "pr_opened" },
          },
          {
            id: "agent-1",
            type: "agent",
            position: { x: 380, y: 160 },
            data: { label: "NEXTJS-REVIEWER", agentId: "agent-1" },
          },
          {
            id: "end",
            type: "end",
            position: { x: 660, y: 160 },
            data: { label: "Done" },
          },
        ],
        edges: [
          { id: "edge-1", source: "start", target: "agent-1" },
          { id: "edge-2", source: "agent-1", target: "end" },
        ],
        viewport: { x: 0, y: 0, zoom: 1 },
      },
      created_at: "2026-03-28T17:00:00.000Z",
    },
    draft_graph: {
      nodes: [
        {
          id: "start",
          type: "start",
          position: { x: 120, y: 160 },
          data: { label: "PR opened", event: "pr_opened" },
        },
        {
          id: "agent-1",
          type: "agent",
          position: { x: 380, y: 160 },
          data: { label: "NEXTJS-REVIEWER", agentId: "agent-1" },
        },
        {
          id: "end",
          type: "end",
          position: { x: 660, y: 160 },
          data: { label: "Done" },
        },
      ],
      edges: [
        { id: "edge-1", source: "start", target: "agent-1" },
        { id: "edge-2", source: "agent-1", target: "end" },
      ],
      viewport: { x: 0, y: 0, zoom: 1 },
    },
  };

  const runSummary = {
    id: "job-1",
    assignment_id: null,
    trigger_id: null,
    flow_id: "flow-1",
    flow_version_id: "version-1",
    runtime_provider: "trigger",
    runtime_run_id: "run_123",
    workflow_run_id: null,
    retry_of_job_run_id: null,
    status: "failed",
    created_at: "2026-03-28T11:00:00.000Z",
    started_at: "2026-03-28T11:01:00.000Z",
    completed_at: "2026-03-28T11:02:00.000Z",
    input_tokens: 120,
    output_tokens: 80,
    duration_ms: 60000,
    error: "Lint failed on CI",
    start_attempts: 2,
    last_start_attempt_at: "2026-03-28T11:01:00.000Z",
    last_start_error: "Lint failed on CI",
    last_start_source: "webhook",
    cancel_requested_at: null,
    cancelled_at: null,
    cancel_reason: null,
    cancel_error: null,
    metadata: {
      repo_id: "repo-1",
      repo_full_name: "webrenew/blackbox",
      source_type: "pr_opened",
      pr_number: 42,
      pr_url: "https://github.com/webrenew/blackbox/pull/42",
    },
    source_kind: "flow",
    source_type: "pr_opened",
    repo: {
      id: "repo-1",
      full_name: "webrenew/blackbox",
    },
    agent: {
      id: null,
      name: null,
      slug: null,
    },
    latest_ai_call: {
      id: "call-1",
      status: "success",
      model: "minimax/minimax-m2.5",
      total_tokens: 200,
      tool_calls_count: 1,
      started_at: "2026-03-28T11:01:05.000Z",
    },
    cancelable: false,
    repairable: false,
    requeueable: true,
    latest_dispatch_event: {
      outcome: "start_failed",
      reason: "LINT_FAILED",
      created_at: "2026-03-28T11:01:30.000Z",
    },
    node_runs: [
      {
        id: "node-run-1",
        user_id: "user-1",
        job_run_id: "job-1",
        flow_id: "flow-1",
        flow_version_id: "version-1",
        node_id: "agent-1",
        node_type: "agent",
        node_label: "NEXTJS-REVIEWER",
        status: "failed",
        started_at: "2026-03-28T11:01:00.000Z",
        completed_at: "2026-03-28T11:02:00.000Z",
        duration_ms: 60000,
        error: "Lint failed on CI",
        output: {
          role: "review",
          text: "Reported lint failures and suggested the exact fix path.",
        },
        created_at: "2026-03-28T11:01:00.000Z",
      },
    ],
  };

  const editPatch = [
    "diff --git a/src/widget.ts b/src/widget.ts",
    "index 1111111..2222222 100644",
    "--- a/src/widget.ts",
    "+++ b/src/widget.ts",
    "@@ -1 +1 @@",
    "-export const widget = 'old'",
    "+export const widget = 'new'",
  ].join("\n");

  const runDetail = {
    ...runSummary,
    metadata: {
      ...runSummary.metadata,
      flow_version_number: 1,
      automation_test: draftTest,
      flow_version_is_test: draftTest,
      working_branch: "mogplex/automation-0123456789abcdef",
      input: { slug: "acme-plumbing" },
      trigger: {
        credential: "integration",
        key_id: "key-1",
        label: "Webrenew agent",
      },
    },
    node_runs: [
      ...runSummary.node_runs,
      {
        id: "node-run-2",
        user_id: "user-1",
        job_run_id: "job-1",
        flow_id: "flow-1",
        flow_version_id: "version-1",
        node_id: "agent-2",
        node_type: "agent",
        node_label: "PR-FIXER",
        status: "success",
        started_at: "2026-03-28T11:02:00.000Z",
        completed_at: "2026-03-28T11:02:30.000Z",
        duration_ms: 30000,
        error: null,
        output: {
          role: "edit",
          text: "Applied a focused widget fix.",
          tool_calls: [
            {
              name: "updateFile",
              input: { path: "src/widget.ts" },
              output: {
                success: true,
                path: "src/widget.ts",
                branch: "fix/widget",
                commitSha: "abcdef1234567890",
                commitUrl:
                  "https://github.com/webrenew/blackbox/commit/abcdef1234567890",
                patch: editPatch,
              },
            },
          ],
        },
        created_at: "2026-03-28T11:02:00.000Z",
      },
    ],
    dispatch_events: [
      {
        id: "dispatch-1",
        event_kind: "enqueue",
        outcome: "queued",
        reason: null,
        metadata: { source: "webhook" },
        created_at: "2026-03-28T11:00:30.000Z",
      },
      {
        id: "dispatch-2",
        event_kind: "start",
        outcome: "start_failed",
        reason: "LINT_FAILED",
        metadata: { retryable: true },
        created_at: "2026-03-28T11:01:30.000Z",
      },
    ],
    ai_calls: [
      {
        id: "call-1",
        user_id: "user-1",
        type: "agent",
        model: "minimax/minimax-m2.5",
        input_tokens: 120,
        output_tokens: 80,
        total_tokens: 200,
        duration_ms: 45000,
        started_at: "2026-03-28T11:01:05.000Z",
        completed_at: "2026-03-28T11:01:50.000Z",
        status: "success",
        error: null,
        conversation_id: null,
        job_run_id: "job-1",
        repo_id: "repo-1",
        limit_claim_id: null,
        cancel_requested_at: null,
        control_state: "active",
        runtime_command_id: "cmd_123",
        tool_calls_count: 1,
        tool_calls: [
          {
            name: "postComment",
            input_preview: "Review comment",
            output_preview: "comment created",
          },
        ],
        metadata: { agent_id: "agent-1", sandbox_record_id: "sbx-1" },
        events: [
          {
            id: "call-event-1",
            ai_call_id: "call-1",
            user_id: "user-1",
            conversation_id: null,
            repo_id: "repo-1",
            event_type: "started",
            tool_name: null,
            message: "Agent started",
            payload: {},
            created_at: "2026-03-28T11:01:05.000Z",
          },
          {
            id: "call-event-2",
            ai_call_id: "call-1",
            user_id: "user-1",
            conversation_id: null,
            repo_id: "repo-1",
            event_type: "finished",
            tool_name: null,
            message: "Agent finished",
            payload: { finishReason: "stop" },
            created_at: "2026-03-28T11:01:50.000Z",
          },
        ],
      },
    ],
    review_findings: [],
  };

  await page.route("**/api/auth/user", (route) =>
    fulfillJson(route, { user: connectedUser })
  );
  await page.route("**/api/settings", (route) =>
    fulfillJson(route, {
      default_model: "minimax/minimax-m2.5",
      theme: "dark",
    })
  );
  await page.route("**/api/models", (route) =>
    fulfillJson(route, {
      models: [
        {
          id: "minimax/minimax-m2.5",
          provider: "minimax",
          name: "MiniMax M2.5",
          context_length: 200000,
          capabilities: ["text"],
          is_available: true,
        },
      ],
      catalog: [
        {
          id: "minimax/minimax-m2.5",
          provider: "minimax",
          name: "MiniMax M2.5",
          context_length: 200000,
          capabilities: ["text"],
          is_available: true,
          is_enabled: true,
        },
      ],
    })
  );
  await page.route("**/api/agents", (route) =>
    fulfillJson(route, [
      {
        id: "agent-1",
        name: "NEXTJS-REVIEWER",
        slug: "nextjs-reviewer",
        model: "minimax/minimax-m2.5",
      },
    ])
  );
  await page.route("**/api/github/installations", (route) =>
    fulfillJson(route, [
      {
        id: "inst-1",
        installation_id: 101,
        account_login: "webrenew",
        repositories: [{ id: "repo-1", full_name: "webrenew/blackbox" }],
      },
    ])
  );
  await page.route("**/api/flows", (route) =>
    fulfillJson(route, [flowPayload])
  );
  await page.route("**/api/flows/flow-1", (route) =>
    fulfillJson(route, flowPayload)
  );
  await page.route("**/api/flows/flow-1/runs?limit=12", (route) =>
    fulfillJson(route, { runs: [runSummary] })
  );
  await page.route("**/api/flows/flow-1/runs/job-1", (route) =>
    fulfillJson(route, { run: runDetail })
  );

  await page.goto("/alex/workflows");
  await page.waitForLoadState("networkidle");
  await expect(page.getByTestId("flow-assistant-panel")).toBeVisible();
  await page.getByTestId("flows-runs-tab").click();
  await expect(page.getByTestId("flows-runs-tab")).toHaveAttribute(
    "aria-selected",
    "true"
  );
  await expect(page).toHaveURL(/\/alex\/workflows\?tab=runs$/);
  await expect(page.getByText("Run history")).toBeVisible();

  await page.reload();
  await page.waitForLoadState("networkidle");
  await expect(page).toHaveURL(/\/alex\/workflows\?tab=runs$/);
  await expect(page.getByTestId("flows-runs-tab")).toHaveAttribute(
    "aria-selected",
    "true"
  );
  await expect(page.getByText("Run history")).toBeVisible();

  const runCard = page.getByTestId("flow-run-card-job-1");
  await expect(runCard).toBeVisible();
  await expect(runCard).not.toContainText("run_123");
  await expect(runCard).not.toContainText("trigger · run_123");
  await expect(runCard.getByRole("button", { name: "Retry" })).toBeVisible();
  await expect(runCard.getByRole("button", { name: "Repair" })).toHaveCount(0);
  await expect(runCard.getByRole("button", { name: "Cancel" })).toHaveCount(0);

  await runCard.click();

  const dialog = page.locator("[data-slot='dialog-content']");
  await expect(dialog.getByText("Run details", { exact: true })).toBeVisible();
  const dialogBox = await dialog.boundingBox();
  expect(dialogBox?.width ?? 0).toBeGreaterThan(1000);
  await expect(dialog.getByText("Dispatch timeline")).toBeVisible();
  await expect(
    dialog.getByText("Node execution", { exact: true })
  ).toBeVisible();
  await expect(dialog.getByText("AI calls", { exact: true })).toBeVisible();
  await expect(dialog.getByText("Runtime provider")).toHaveCount(0);
  await expect(dialog.getByText("Runtime handle")).toHaveCount(0);
  await expect(dialog.getByText("run_123")).toHaveCount(0);
  await expect(
    dialog.getByText("PR OPENED", { exact: true }).first()
  ).toBeVisible();
  await expect(dialog.getByRole("link", { name: "PR #42" })).toHaveAttribute(
    "href",
    "https://github.com/webrenew/blackbox/pull/42"
  );
  await expect(dialog.getByText("v1", { exact: true })).toBeVisible();
  await expect(
    dialog.getByText(draftTest ? "Draft test snapshot" : "Automation version", {
      exact: true,
    })
  ).toBeVisible();
  await expect(
    dialog.getByText("Integration · Webrenew agent", { exact: true })
  ).toBeVisible();
  await expect(
    dialog.getByText("mogplex/automation-0123456789abcdef", { exact: true })
  ).toBeVisible();
  await expect(dialog).toContainText('"slug": "acme-plumbing"');
  await expect(
    dialog.getByRole("link", { name: "Open transcript" })
  ).toHaveAttribute("href", "/alex/observability?call_id=call-1");
  await expect(
    dialog.getByRole("link", { name: "Open sandbox" })
  ).toHaveAttribute(
    "href",
    "/alex/observability?repo_id=repo-1&sandbox_record_id=sbx-1"
  );
  await expect(dialog.getByText("Edits", { exact: true })).toBeVisible();
  await expect(
    dialog.getByText("src/widget.ts", { exact: true }).first()
  ).toBeVisible();
  await expect(dialog.getByRole("link", { name: "abcdef1" })).toHaveAttribute(
    "href",
    "https://github.com/webrenew/blackbox/commit/abcdef1234567890"
  );
  await expect(
    dialog
      .locator("[data-line-type='change-addition']")
      .filter({ hasText: "export const widget = 'new'" })
  ).toBeVisible();
  await expect(dialog.getByRole("button", { name: "Retry" })).toBeVisible();
  await expect(dialog.getByRole("button", { name: "Repair" })).toHaveCount(0);
  await expect(dialog.getByRole("button", { name: "Cancel" })).toHaveCount(0);
  await expect(
    dialog
      .getByText("Reported lint failures and suggested the exact fix path.")
      .first()
  ).toBeVisible();
  await expect(dialog).toContainText("Lint failed on CI");
  await dialog.getByText("Tool calls").click();
  await expect(dialog.getByText("postComment")).toBeVisible();
  await dialog.getByText("Call events").click();
  await expect(dialog.getByText("Agent finished")).toBeVisible();
}