deer-flow/frontend/tests/e2e/subtask-card.spec.ts
AnoobFeng a692576993
fix(frontend): resolve stale subagent running state after stop (#3639)
* fix(frontend): resolve stale subagent running state after stop

Subtask cards were recreated from task tool calls as in_progress whenever
the thread was loading. If a user stopped a run before the task tool returned
a ToolMessage, the card could remain running after reload, and could flip back
to running when a later user turn started streaming.

Derive pending subtask state from the current assistant turn instead of the
global thread loading flag. A task now stays in progress only while its own
turn is loading or while a matching ToolMessage exists for result parsing;
otherwise it is marked failed.

Add unit coverage for task ToolMessage matching and for the later-turn loading
regression.

* fix(frontend): refresh subagent card on terminal status transition

- notify React via a ref + post-render effect when a subtask flips to
  completed/failed, so SubtaskCard updates without relying on an
  unrelated MessageList re-render
- tighten the current-turn heuristic to `groupIndex === lastGroupIndex`
  (precomputed once) — fixes the rare case where an earlier subagent
  group in the same turn was kept in_progress, and drops the per-group
  slice+some scan to O(1)
- rename `getPendingSubtaskStatus` → `derivePendingSubtaskStatus`
- narrow `toolCall.id` at MessageList call sites; skip task tool calls
  without an id instead of `id!`
- add Playwright e2e covering reload-after-stop showing `Subtask failed`
2026-06-19 16:25:17 +08:00

67 lines
1.7 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockLangGraphAPI, MOCK_THREAD_ID } from "./utils/mock-api";
const STOPPED_TASK_DESCRIPTION = "Research stopped reload regression";
const STOPPED_TASK_PROMPT =
"Investigate why the stopped subtask card should not remain running after reload.";
const stoppedSubtaskMessages = [
{
type: "human",
id: "msg-human-stopped-subtask",
content: [
{
type: "text",
text: "Start a subtask and then stop before the task tool returns.",
},
],
},
{
type: "ai",
id: "msg-ai-stopped-subtask",
content: "",
additional_kwargs: {},
response_metadata: {},
tool_calls: [
{
id: "call-stopped-subtask",
name: "task",
args: {
subagent_type: "general-purpose",
description: STOPPED_TASK_DESCRIPTION,
prompt: STOPPED_TASK_PROMPT,
},
type: "tool_call",
},
],
invalid_tool_calls: [],
},
];
test.describe("Subtask card", () => {
test("shows failed after a stopped task thread is reloaded", async ({
page,
}) => {
mockLangGraphAPI(page, {
threads: [
{
thread_id: MOCK_THREAD_ID,
title: "Stopped subtask",
updated_at: "2026-06-18T12:00:00Z",
messages: stoppedSubtaskMessages,
},
],
});
await page.goto(`/workspace/chats/${MOCK_THREAD_ID}`);
await page.reload();
await expect(page.getByText(STOPPED_TASK_DESCRIPTION)).toBeVisible({
timeout: 15_000,
});
await expect(page.getByText("Subtask failed")).toBeVisible();
await expect(page.getByText("Running subtask")).toHaveCount(0);
});
});