Advance TS port Effect workbench

This commit is contained in:
elpresidank 2026-06-01 16:22:25 -05:00
parent 92dae8c374
commit 3515106670
116 changed files with 12286 additions and 9584 deletions

View file

@ -0,0 +1,49 @@
import { expect, expectCanvasNonBlank, expectHeading, gotoWorkbench, test } from "./fixtures";
test("chat streams, cancels, regenerates, copies, and clears messages", async ({ page }) => {
await gotoWorkbench(page, "/chat", { chat: { delayFrames: 6 } });
await expectHeading(page, "Chat");
await page.getByPlaceholder(/Ask using Graph RAG/).fill("cancel this request");
await page.getByLabel("Send message").click();
await expect(page.getByLabel("Cancel request")).toBeVisible();
await page.getByLabel("Cancel request").click();
await expect(page.getByText("(Cancelled)")).toBeVisible();
await page.getByLabel("Clear conversation").click();
await expect(page.getByText("Start a conversation with TrustGraph.")).toBeVisible();
await page.getByPlaceholder(/Ask using Graph RAG/).fill("Who does Alice know?");
await page.getByLabel("Send message").click();
await expect(page.getByText(/Mock graph answer from QA knowledge graph/)).toBeVisible();
await expect(page.getByText("qa-model")).toBeVisible();
await page.getByText(/Mock graph answer/).hover();
await page.getByLabel("Copy message").click();
await expect.poll(() => page.evaluate(() => (window as Window & { __TRUSTGRAPH_WORKBENCH_CLIPBOARD__?: string }).__TRUSTGRAPH_WORKBENCH_CLIPBOARD__)).toContain("Mock graph answer");
await page.getByLabel("Regenerate response").click();
await expect(page.getByText(/Mock graph answer from QA knowledge graph/).last()).toBeVisible();
await page.getByText("Agent").click();
await page.getByPlaceholder(/Ask using Agent/).fill("Use the agent");
await page.getByLabel("Send message").click();
await expect(page.getByText("Thinking")).toBeVisible();
await expect(page.getByText("Observing")).toBeVisible();
await expect(page.getByText("Mock agent answer from TrustGraph.")).toBeVisible();
});
test("graph renders seeded triples and responds to controls", async ({ page }) => {
await gotoWorkbench(page, "/graph");
await expectHeading(page, "Graph");
await expect(page.getByText(/nodes/).first()).toBeVisible();
await expect(page.getByText(/edges/).first()).toBeVisible();
await expectCanvasNonBlank(page.locator("canvas").first());
await page.getByPlaceholder("Search graph...").fill("Alice");
await expect(page.getByText(/1 nodes/)).toBeVisible();
await page.getByRole("button", { name: "Labels" }).click();
await page.getByLabel("Node limit").selectOption("100");
await expect(page.getByText(/nodes/).first()).toBeVisible();
});

View file

@ -0,0 +1,88 @@
import { expect, test as base, type Locator, type Page } from "@playwright/test";
import type { MockWorkbenchFixture } from "../../src/qa/mock-api";
const consoleAllowlist = [
"Download the React DevTools",
'apple-mobile-web-app-capable',
];
export const test = base.extend({
page: async ({ page }, use) => {
const errors: string[] = [];
page.on("console", (message) => {
const text = message.text();
if (message.type() === "error" && !consoleAllowlist.some((allowed) => text.includes(allowed))) {
errors.push(text);
}
});
page.on("pageerror", (error) => {
errors.push(error.message);
});
await use(page);
expect(errors, "unexpected browser console/page errors").toEqual([]);
},
});
export { expect } from "@playwright/test";
export function fixture(overrides: MockWorkbenchFixture = {}): MockWorkbenchFixture {
return {
...overrides,
settings: {
...(overrides.settings ?? {}),
featureSwitches: {
mcpTools: true,
...(overrides.settings?.featureSwitches ?? {}),
},
},
};
}
export async function prepareWorkbench(page: Page, overrides: MockWorkbenchFixture = {}) {
await page.addInitScript((input) => {
const qaWindow = window as Window & {
__TRUSTGRAPH_WORKBENCH_QA__?: unknown;
__TRUSTGRAPH_WORKBENCH_CLIPBOARD__?: string;
};
window.localStorage.clear();
qaWindow.__TRUSTGRAPH_WORKBENCH_QA__ = {
enabled: true,
fixture: input,
flowId: "default",
};
Object.defineProperty(navigator, "clipboard", {
configurable: true,
value: {
writeText: (text: string) => {
qaWindow.__TRUSTGRAPH_WORKBENCH_CLIPBOARD__ = text;
return Promise.resolve();
},
readText: () => Promise.resolve(qaWindow.__TRUSTGRAPH_WORKBENCH_CLIPBOARD__ ?? ""),
},
});
}, fixture(overrides));
}
export async function gotoWorkbench(page: Page, path: string, overrides: MockWorkbenchFixture = {}) {
await prepareWorkbench(page, overrides);
await page.goto(path);
await expect(page.locator("body")).not.toContainText("Something went wrong");
}
export async function expectHeading(page: Page, name: string) {
await expect(page.getByRole("heading", { name, level: 1 })).toBeVisible();
}
export async function expectCanvasNonBlank(canvas: Locator) {
await expect(canvas).toBeVisible();
await expect.poll(() => canvas.evaluate((node) => {
if (!(node instanceof HTMLCanvasElement)) return false;
const context = node.getContext("2d");
if (context === null || node.width === 0 || node.height === 0) return false;
const pixels = context.getImageData(0, 0, node.width, node.height).data;
for (let index = 3; index < pixels.length; index += 16) {
if (pixels[index] !== 0) return true;
}
return false;
}), { timeout: 10_000 }).toBe(true);
}

View file

@ -0,0 +1,52 @@
import { expect, expectHeading, gotoWorkbench, test } from "./fixtures";
test("flows validate parameters, start, expand, and stop", async ({ page }) => {
await gotoWorkbench(page, "/flows");
await expectHeading(page, "Flows");
await expect(page.getByRole("button", { name: /qa-flow Seeded QA flow/ })).toBeVisible();
await page.getByRole("button", { name: "Start Flow" }).click();
const dialog = page.getByRole("dialog", { name: "Start Flow" });
await expect(dialog).toBeVisible();
await dialog.getByRole("button", { name: "Start" }).click();
await expect(page.getByText("Flow ID is required")).toBeVisible();
await dialog.getByPlaceholder("my-flow-id").fill("qa-started");
await dialog.locator("select").selectOption("qa-blueprint");
await dialog.getByPlaceholder("What this flow does").fill("Started from browser QA");
await dialog.getByText("Blueprint Details").click();
await expect(dialog.locator("pre")).toContainText("qa-blueprint");
await dialog.locator("textarea").fill("{bad");
await dialog.getByRole("button", { name: "Start" }).click();
await expect(page.getByText("Invalid JSON")).toBeVisible();
await dialog.locator("textarea").fill('{"temperature":0.2}');
await dialog.getByRole("button", { name: "Start" }).click();
await expect(dialog).toBeHidden();
const startedFlow = page.getByRole("button", { name: /qa-started Started from browser QA/ });
await expect(startedFlow).toBeVisible();
await startedFlow.click();
await expect(page.locator("pre").last()).toContainText("Started from browser QA");
await page.getByLabel("Stop flow qa-started").click();
await expect(startedFlow).toBeHidden();
});
test("knowledge cores load and delete with confirmation", async ({ page }) => {
await gotoWorkbench(page, "/knowledge-cores");
await expectHeading(page, "Knowledge Cores");
await expect(page.getByRole("row", { name: /qa-core/ })).toBeVisible();
await page.getByLabel("Load core qa-core").click();
await expect(page.getByText("Core loaded")).toBeVisible();
await page.getByLabel("Delete core qa-core").click();
const dialog = page.getByRole("dialog", { name: "Delete Knowledge Core" });
await expect(dialog).toBeVisible();
await dialog.getByRole("button", { name: "Cancel" }).click();
await expect(dialog).toBeHidden();
await page.getByLabel("Delete core qa-core").click();
await dialog.getByRole("button", { name: "Delete" }).click();
await expect(page.getByRole("row", { name: /qa-core/ })).toBeHidden();
});

View file

@ -0,0 +1,51 @@
import { expect, expectHeading, gotoWorkbench, test } from "./fixtures";
const routes = [
["/chat", "Chat"],
["/library", "Library"],
["/graph", "Graph"],
["/prompts", "Prompts"],
["/token-cost", "Token Cost"],
["/knowledge-cores", "Knowledge Cores"],
["/flows", "Flows"],
["/mcp-tools", "MCP Tools"],
["/settings", "Settings"],
] as const;
for (const [route, heading] of routes) {
test(`loads ${route}`, async ({ page }) => {
await gotoWorkbench(page, route);
await expectHeading(page, heading);
await expect(page.getByRole("navigation", { name: "Main navigation" })).toBeVisible();
});
}
test("settings persist view state and drive feature switches", async ({ page }) => {
await gotoWorkbench(page, "/settings");
await expectHeading(page, "Settings");
const apiKey = page.getByPlaceholder("Optional gateway bearer token");
await apiKey.fill("qa-secret");
await expect(apiKey).toHaveAttribute("type", "password");
await page.getByLabel("Show API key").click();
await expect(apiKey).toHaveAttribute("type", "text");
await page.getByLabel("Hide API key").click();
await expect(apiKey).toHaveAttribute("type", "password");
await page.getByText("Theme").click();
await expect.poll(() => page.evaluate(() => document.body.classList.contains("light"))).toBe(true);
await page.getByLabel("Create collection").click();
const collectionDialog = page.getByRole("dialog", { name: "Create Collection" });
await expect(collectionDialog).toBeVisible();
await collectionDialog.getByRole("textbox", { name: "Collection ID" }).fill("qa-created");
await collectionDialog.getByRole("textbox", { name: "Display Name" }).fill("QA Created");
await collectionDialog.getByRole("button", { name: "Create", exact: true }).click();
await expect(page.getByRole("option", { name: "qa-created" })).toBeAttached();
const mcpToggle = page.locator("label").filter({ hasText: "MCP Tools" }).getByRole("checkbox");
await mcpToggle.uncheck();
await expect(page.getByRole("navigation", { name: "Main navigation" }).getByRole("link", { name: "MCP Tools" })).toBeHidden();
await mcpToggle.check();
await expect(page.getByRole("navigation", { name: "Main navigation" }).getByRole("link", { name: "MCP Tools" })).toBeVisible();
});

View file

@ -0,0 +1,44 @@
import { writeFileSync } from "node:fs";
import { expect, expectHeading, gotoWorkbench, test } from "./fixtures";
test("library searches, shows metadata, uploads small and chunked files, and deletes", async ({ page }, testInfo) => {
await gotoWorkbench(page, "/library");
await expectHeading(page, "Library");
await expect(page.getByText("QA Document")).toBeVisible();
await page.getByPlaceholder("Search documents...").fill("missing");
await expect(page.getByText("No documents match your search.")).toBeVisible();
await page.getByPlaceholder("Search documents...").fill("QA");
await expect(page.getByText("QA Document")).toBeVisible();
await page.getByLabel("View QA Document").click();
await expect(page.getByRole("dialog", { name: "Document Details" })).toBeVisible();
await expect(page.getByText("Seeded document for browser QA")).toBeVisible();
await page.getByLabel("Close dialog").click();
const smallFile = testInfo.outputPath("qa-upload-small.txt");
writeFileSync(smallFile, "Small QA upload");
await page.getByRole("button", { name: "Upload", exact: true }).click();
const uploadDialog = page.getByRole("dialog", { name: "Upload Document" });
await expect(uploadDialog).toBeVisible();
await page.locator('input[type="file"]').setInputFiles(smallFile);
await expect(uploadDialog.getByRole("textbox", { name: "Title" })).toHaveValue("qa-upload-small");
await uploadDialog.getByRole("button", { name: "Upload" }).click();
await expect(uploadDialog).toBeHidden();
await expect(page.getByRole("row", { name: /qa-upload-small/ })).toBeVisible();
const chunkedFile = testInfo.outputPath("qa-upload-chunked.txt");
writeFileSync(chunkedFile, "chunked ".repeat(180_000));
await page.getByRole("button", { name: "Upload", exact: true }).click();
const chunkedDialog = page.getByRole("dialog", { name: "Upload Document" });
await page.locator('input[type="file"]').setInputFiles(chunkedFile);
await expect(chunkedDialog.getByRole("textbox", { name: "Title" })).toHaveValue("qa-upload-chunked");
await chunkedDialog.getByRole("button", { name: "Upload" }).click();
await expect(page.getByRole("row", { name: /qa-upload-chunked/ })).toBeVisible();
await page.getByLabel("Delete qa-upload-small").click();
const deleteDialog = page.getByRole("dialog", { name: "Delete Document" });
await expect(deleteDialog).toBeVisible();
await deleteDialog.getByRole("button", { name: "Delete", exact: true }).click();
await expect(page.getByRole("row", { name: /qa-upload-small/ })).toBeHidden();
});

View file

@ -0,0 +1,77 @@
import { expect, expectHeading, gotoWorkbench, test } from "./fixtures";
test("prompts and token costs render seeded config", async ({ page }) => {
await gotoWorkbench(page, "/prompts");
await expectHeading(page, "Prompts");
await expect(page.getByRole("tab", { name: "Templates" })).toHaveAttribute("aria-selected", "true");
await page.getByRole("button", { name: "qa-template" }).click();
await expect(page.getByText("QA template system")).toBeVisible();
await expect(page.getByText("Answer the QA question")).toBeVisible();
await page.getByLabel("Close prompt detail").click();
await page.getByRole("tab", { name: "System Prompt" }).click();
await expect(page.getByText("You are the QA system prompt.")).toBeVisible();
await page.goto("/token-cost");
await expectHeading(page, "Token Cost");
await expect(page.getByText("qa-model")).toBeVisible();
await expect(page.getByText("$1.25")).toBeVisible();
await expect(page.getByText("$2.50")).toBeVisible();
});
test("mcp tools add, validate, edit, and delete servers and tools", async ({ page }) => {
await gotoWorkbench(page, "/mcp-tools");
await expectHeading(page, "MCP Tools");
const main = page.locator("#main-content");
await expect(main.getByText("qa-search")).toBeVisible();
await page.getByRole("button", { name: "Add Server" }).click();
let dialog = page.getByRole("dialog", { name: "Add MCP Server" });
await dialog.getByLabel("Key").fill("qa-extra-server");
await dialog.getByLabel("URL").fill("http://localhost:8383/mcp-extra");
await dialog.getByLabel("Remote Name").fill("qa-extra");
const authToken = dialog.getByRole("textbox", { name: /Auth Token/ });
await authToken.fill("token");
await dialog.getByLabel("Show auth token").click();
await expect(authToken).toHaveAttribute("type", "text");
await dialog.getByRole("button", { name: "Save" }).click();
await expect(main.getByText("qa-extra-server")).toBeVisible();
await page.getByRole("button", { name: "Add Server" }).click();
dialog = page.getByRole("dialog", { name: "Add MCP Server" });
await dialog.getByLabel("Key").fill("qa-search");
await dialog.getByLabel("URL").fill("http://localhost:8383/duplicate");
await dialog.getByRole("button", { name: "Save" }).click();
await expect(page.getByText("A server with this key already exists")).toBeVisible();
await dialog.getByRole("button", { name: "Cancel" }).click();
await page.getByRole("button", { name: /Tools/ }).click();
await page.getByRole("button", { name: "Add Tool" }).click();
dialog = page.getByRole("dialog", { name: "Add MCP Tool" });
await dialog.getByLabel("Key").fill("qa-extra-tool");
await dialog.getByLabel("Name").fill("QA Extra Tool");
await dialog.getByLabel("Description").fill("Extra tool from browser QA");
await dialog.getByLabel("MCP Server").selectOption("qa-extra-server");
await dialog.getByLabel("Groups").fill("default, qa");
await dialog.getByRole("button", { name: "Add" }).click();
await dialog.getByPlaceholder("name").fill("query");
await dialog.getByPlaceholder("description").fill("Query to execute");
await dialog.getByRole("button", { name: "Save" }).click();
await expect(main.getByText("qa-extra-tool")).toBeVisible();
await page.getByLabel("Edit tool qa-extra-tool").click();
dialog = page.getByRole("dialog", { name: "Edit MCP Tool" });
await dialog.getByLabel("Name").fill("QA Extra Tool Edited");
await dialog.getByRole("button", { name: "Save" }).click();
await expect(page.getByText("QA Extra Tool Edited")).toBeVisible();
await page.getByLabel("Delete tool qa-extra-tool").click();
dialog = page.getByRole("dialog", { name: "Delete tool" });
await dialog.getByRole("button", { name: "Delete" }).click();
await expect(main.getByText("qa-extra-tool")).toBeHidden();
await page.getByRole("button", { name: /Servers/ }).click();
await page.getByLabel("Delete server qa-extra-server").click();
dialog = page.getByRole("dialog", { name: "Delete server" });
await dialog.getByRole("button", { name: "Delete" }).click();
await expect(main.getByText("qa-extra-server")).toBeHidden();
});