Ctrl K
ComunidadGuías…

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

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

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

test("running flow runs expose cancel in the recent runs rail", async ({
  page,
}) => {
  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");
  });

  let cancelCount = 0;
  let signalCancelStarted!: () => void;
  const cancelStarted = new Promise<void>((resolve) => {
    signalCancelStarted = resolve;
  });
  let releaseCancelResponse!: () => void;
  const cancelCanFinish = new Promise<void>((resolve) => {
    releaseCancelResponse = resolve;
  });

  const flowPayload = {
    id: "flow-1",
    installation_id: 101,
    name: "NEXTJS-REVIEWER · PR opened",
    description: "Migrated from Trigger",
    notes: null,
    source_kind: "github",
    status: "active",
    last_run_status: "running",
    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",
              role: "review",
            },
          },
          {
            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",
            role: "review",
          },
        },
        {
          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-running",
    assignment_id: null,
    trigger_id: null,
    flow_id: "flow-1",
    flow_version_id: "version-1",
    runtime_provider: "trigger",
    runtime_run_id: "run_live_1",
    workflow_run_id: null,
    retry_of_job_run_id: null,
    status: "running",
    created_at: "2026-03-29T11:00:00.000Z",
    started_at: "2026-03-29T11:00:05.000Z",
    completed_at: null,
    input_tokens: null,
    output_tokens: null,
    duration_ms: null,
    error: null,
    start_attempts: 1,
    last_start_attempt_at: "2026-03-29T11:00:05.000Z",
    last_start_error: null,
    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",
    },
    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: null,
    active_wait_count: 1,
    cancelable: true,
    repairable: false,
    requeueable: false,
    latest_dispatch_event: {
      outcome: "started",
      reason: null,
      created_at: "2026-03-29T11:00:06.000Z",
    },
    node_runs: [],
  };

  const cancelledRunSummary = {
    ...runSummary,
    status: "cancelled",
    completed_at: "2026-03-29T11:01:02.000Z",
    cancel_requested_at: "2026-03-29T11:01:00.000Z",
    cancelled_at: "2026-03-29T11:01:02.000Z",
    cancel_reason: "USER_REQUESTED",
    cancel_error: null,
    cancelable: false,
    latest_dispatch_event: {
      outcome: "cancelled",
      reason: "USER_REQUESTED",
      created_at: "2026-03-29T11:01:02.000Z",
    },
  };

  const failedRunSummary = {
    ...runSummary,
    id: "job-failed",
    runtime_run_id: "run_failed_1",
    status: "failed",
    completed_at: "2026-03-29T11:03:00.000Z",
    duration_ms: 175000,
    error: "Review findings need follow-up",
    cancel_requested_at: null,
    cancelled_at: null,
    cancel_reason: null,
    cancel_error: null,
    cancelable: false,
    repairable: false,
    requeueable: true,
    latest_dispatch_event: {
      outcome: "completed",
      reason: "REVIEW_REQUIRED",
      created_at: "2026-03-29T11:03:00.000Z",
    },
  };

  const cancelledRunDetail = {
    ...cancelledRunSummary,
    dispatch_events: [
      {
        id: "dispatch-1",
        event_kind: "enqueue",
        outcome: "queued",
        reason: null,
        metadata: { source: "webhook" },
        created_at: "2026-03-29T11:00:00.000Z",
      },
      {
        id: "dispatch-2",
        event_kind: "start",
        outcome: "started",
        reason: null,
        metadata: null,
        created_at: "2026-03-29T11:00:06.000Z",
      },
      {
        id: "dispatch-3",
        event_kind: "control",
        outcome: "cancel_requested",
        reason: "USER_REQUESTED",
        metadata: { cancel_requested_at: "2026-03-29T11:01:00.000Z" },
        created_at: "2026-03-29T11:01:00.000Z",
      },
      {
        id: "dispatch-4",
        event_kind: "control",
        outcome: "cancelled",
        reason: "USER_REQUESTED",
        metadata: { cancelled_at: "2026-03-29T11:01:02.000Z" },
        created_at: "2026-03-29T11:01:02.000Z",
      },
    ],
    ai_calls: [],
    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: [
        cancelCount > 0 ? cancelledRunSummary : runSummary,
        failedRunSummary,
      ],
    })
  );
  await page.route("**/api/flows/flow-1/runs/job-running", (route) =>
    fulfillJson(route, {
      run: cancelCount > 0 ? cancelledRunDetail : runSummary,
    })
  );
  await page.route(
    "**/api/observability/jobs/job-running/cancel",
    async (route) => {
      cancelCount += 1;
      signalCancelStarted();
      await cancelCanFinish;
      await fulfillJson(route, {
        ok: true,
        status: "cancelled",
        cancelRequestedAt: "2026-03-29T11:01:00.000Z",
        cancelledAt: "2026-03-29T11:01:02.000Z",
        cancelReason: "USER_REQUESTED",
        cancelError: null,
        runtimeProvider: "trigger",
        runtimeRunId: "run_live_1",
        aiCallsCancellationRequested: 0,
        releasedJobs: [],
      });
    }
  );

  await page.goto("/alex/workflows");
  await page.waitForLoadState("networkidle");
  await expect(page.getByTestId("flow-execution-log")).toContainText("waiting");
  await page.getByRole("tab", { name: /^Runs/ }).click();
  await expect(
    page
      .getByTestId("flow-run-card-job-running")
      .getByText("waiting", { exact: true })
  ).toBeVisible();

  await page
    .getByTestId("flow-run-card-job-running")
    .getByRole("button", { name: "Cancel" })
    .click();
  await cancelStarted;
  await expect(
    page
      .getByTestId("flow-run-card-job-running")
      .getByRole("button", { name: "Cancel" })
  ).toBeDisabled();
  await expect(
    page
      .getByTestId("flow-run-card-job-failed")
      .getByRole("button", { name: "Retry" })
  ).toBeEnabled();
  releaseCancelResponse();
  expect(cancelCount).toBe(1);
  await expect(page.getByTestId("flow-run-card-job-running")).toContainText(
    "cancelled"
  );
  await expect(page.getByTestId("flow-run-card-job-running")).toContainText(
    "Cancelled"
  );

  await page.getByTestId("flow-run-card-job-running").click();
  const dialog = page.locator("[data-slot='dialog-content']");
  await expect(dialog.getByText("Cancellation", { exact: true })).toBeVisible();
  await expect(dialog.getByRole("button", { name: "Cancel" })).toHaveCount(0);
  await expect(dialog.getByRole("button", { name: "Retry" })).toHaveCount(0);
  await expect(dialog.getByRole("button", { name: "Repair" })).toHaveCount(0);
  await expect(dialog.getByText("Cancel requested")).toBeVisible();
  await dialog.getByText("Technical details").click();
  await expect(dialog.getByText("Cancelled at")).toBeVisible();
});