deer-flow/frontend/tests/e2e/knowledge-scope.spec.ts
zhangwei-way b6503e9a35
feat(knowledge): add per-message RAGFlow retrieval scope (#5238)
* feat(knowledge): integrate RAGFlow retrieval and management

* test(knowledge): cover merged listing tool

* feat(knowledge): add per-message retrieval scope

* chore(docs): remove unrelated document

* docs(knowledge): add interaction screenshots

* feat(knowledge): simplify scope selector trigger

* docs(knowledge): refresh selector screenshot

* feat(knowledge): defer standalone management

* docs(knowledge): show chat-only scope UI

* fix(knowledge): honor scope on clarification replies

* fix(knowledge): harden scoped replay validation

* docs(knowledge): clarify replay scope precedence

* fix(knowledge): keep provider settings on tools

* fix(config): preserve tools-only knowledge settings

* fix(knowledge): submit custom assistant identity

* refactor(knowledge): trim PR scope changes

* fix(knowledge): sanitize document scope display

* feat(knowledge): enable scope selection in main chat

* fix(knowledge): emphasize active scope icon without button frame

* fix(knowledge): close context scrubbing and refresh e2e checks

* fix(knowledge): preserve idempotent canonical retries

* fix(knowledge): accept promptless conversation runs

* style(knowledge): format backend regression tests

* chore(knowledge): trim PR scope and fix frontend format

* fix(knowledge): remove shared-scope notice

* fix(knowledge): remove scope persistence notice

* docs(knowledge): include main chat in catalog scope

* fix(knowledge): preserve scope recovery and upgrades

* fix(config): preserve LightRAG knowledge upgrades

---------

Co-authored-by: foreleven <for-eleven@hotmail.com>
2026-09-18 16:59:31 +08:00

217 lines
7.2 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { handleRunStream, mockLangGraphAPI } from "./utils/mock-api";
test.describe("custom-agent knowledge scope", () => {
test("selects one file and sends one immutable scope snapshot", async ({
page,
}) => {
mockLangGraphAPI(page, {
agents: [
{
name: "researcher",
description: "Research agent",
tool_groups: ["knowledge"],
},
],
features: { knowledgeScopeSelectionEnabled: true },
});
await page.route("**/api/knowledge/retrieval-catalog/datasets?*", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
items: [{ id: "dataset-1", name: "Policies", selectable: true }],
page: 1,
page_size: 100,
total: 1,
}),
}),
);
await page.route(
"**/api/knowledge/retrieval-catalog/datasets/dataset-1/documents?*",
(route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
items: [
{ id: "doc-1", name: "Leave.pdf", selectable: true },
{ id: "doc-2", name: "Parsing.pdf", selectable: false },
],
page: 1,
page_size: 100,
total: 2,
}),
}),
);
let streamBody: Record<string, unknown> | undefined;
await page.route("**/api/langgraph/threads/*/runs/stream", (route) => {
streamBody = route.request().postDataJSON() as Record<string, unknown>;
return handleRunStream(route);
});
await page.goto("/workspace/agents/researcher/chats/new");
await expect(page.getByRole("link", { name: "Knowledge" })).toHaveCount(0);
const trigger = page.getByTestId("knowledge-scope-trigger");
await expect(trigger).toHaveText("");
await expect(trigger).toHaveAttribute("aria-pressed", "true");
await expect(trigger).toHaveClass(/text-foreground/);
await expect(trigger).not.toHaveClass(/bg-primary\/10/);
await trigger.click();
await page.getByLabel("Off").check();
await page.getByRole("button", { name: "Apply" }).click();
await expect(trigger).toHaveAttribute("aria-pressed", "false");
await expect(trigger).toHaveAttribute("aria-label", "Knowledge · Off");
await expect(trigger).not.toHaveClass(/bg-primary\/10/);
await trigger.click();
await page.getByLabel("Selected knowledge bases").check();
await page.getByLabel("Policies").check();
await page.getByRole("button", { name: "Files" }).click();
await page.getByLabel("Selected files").check();
await page.getByLabel("Leave.pdf").check();
await expect(page.getByLabel("Parsing.pdf")).toBeDisabled();
await page.getByRole("button", { name: "Apply" }).click();
await expect(trigger).toHaveAttribute("aria-pressed", "true");
await expect(trigger).toHaveClass(/text-foreground/);
await expect(trigger).not.toHaveClass(/bg-primary\/10/);
await page
.getByPlaceholder(/how can i assist you/i)
.fill("Find the policy");
await page.getByRole("button", { name: "Submit" }).click();
await expect.poll(() => streamBody).toBeDefined();
expect(streamBody).toMatchObject({
assistant_id: "researcher",
input: {
messages: [
{
type: "human",
additional_kwargs: {
knowledge_scope: {
version: 1,
mode: "selected",
dataset_ids: ["dataset-1"],
document_filters: [
{ dataset_id: "dataset-1", document_ids: ["doc-1"] },
],
display: {
datasets: [
{
id: "dataset-1",
name: "Policies",
documents: [{ id: "doc-1", name: "Leave.pdf" }],
},
],
},
},
},
},
],
},
});
});
test("main chat uses the selector and sends the lead-agent scope", async ({
page,
}) => {
mockLangGraphAPI(page, {
features: { knowledgeScopeSelectionEnabled: true },
});
await page.route("**/api/knowledge/retrieval-catalog/datasets?*", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
items: [{ id: "dataset-1", name: "Policies", selectable: true }],
page: 1,
page_size: 100,
total: 1,
}),
}),
);
await page.route(
"**/api/knowledge/retrieval-catalog/datasets/dataset-1/documents?*",
(route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
items: [{ id: "doc-1", name: "Leave.pdf", selectable: true }],
page: 1,
page_size: 100,
total: 1,
}),
}),
);
let streamBody: Record<string, unknown> | undefined;
await page.route("**/api/langgraph/threads/*/runs/stream", (route) => {
streamBody = route.request().postDataJSON() as Record<string, unknown>;
return handleRunStream(route);
});
await page.goto("/workspace/chats/new");
await expect(page.getByPlaceholder(/how can i assist you/i)).toBeVisible();
const trigger = page.getByTestId("knowledge-scope-trigger");
await expect(trigger).toHaveText("");
await expect(trigger).toHaveAttribute("aria-pressed", "true");
await trigger.click();
await page.getByLabel("Selected knowledge bases").check();
await page.getByLabel("Policies").check();
await page.getByRole("button", { name: "Files" }).click();
await page.getByLabel("Selected files").check();
await page.getByLabel("Leave.pdf").check();
await page.getByRole("button", { name: "Apply" }).click();
await page
.getByPlaceholder(/how can i assist you/i)
.fill("Find the policy");
await page.getByRole("button", { name: "Submit" }).click();
await expect.poll(() => streamBody).toBeDefined();
expect(streamBody).toMatchObject({
assistant_id: "lead_agent",
input: {
messages: [
{
type: "human",
additional_kwargs: {
knowledge_scope: {
version: 1,
mode: "selected",
dataset_ids: ["dataset-1"],
document_filters: [
{ dataset_id: "dataset-1", document_ids: ["doc-1"] },
],
},
},
},
],
},
});
});
test("main chat hides the selector when configuration disables it", async ({
page,
}) => {
let catalogRequests = 0;
mockLangGraphAPI(page, {
features: { knowledgeScopeSelectionEnabled: false },
});
await page.route("**/api/knowledge/retrieval-catalog/**", (route) => {
catalogRequests += 1;
return route.abort();
});
await page.goto("/workspace/chats/new");
await expect(page.getByPlaceholder(/how can i assist you/i)).toBeVisible();
await expect(page.getByTestId("knowledge-scope-trigger")).toHaveCount(0);
expect(catalogRequests).toBe(0);
});
});