proteinea / tests /tool-invocation.spec.ts
Mahmoud Eljendy
feat: Antibody Studio — AI-native antibody design workspace by Proteinea
30cc31a
import { test, expect } from "@playwright/test";
const FAKE_SKILLS = [
{
endpoint_name: "mafft",
display_name: "MAFFT",
description: "Multiple sequence alignment.",
category: "alignment",
version: "7.5",
author: "Hub",
compute: { gpu: false, typical_runtime_seconds: 30 },
},
];
const FAKE_DETAIL = {
endpoint_name: "mafft",
display_name: "MAFFT",
description: "Multiple sequence alignment.",
category: "alignment",
license: "BSD",
compute: { gpu: false, typical_runtime_seconds: 30 },
parameters: {
type: "object",
properties: {
input: { type: "string", description: "Input FASTA." },
},
required: ["input"],
},
};
const FAKE_ESTIMATE = {
estimated_usd: 0.12,
gpu_type: "CPU",
minutes_used: 0.5,
cost_alert: false,
compute_time_raw: "30s",
};
const FAKE_JOB_SUBMIT = {
campaign_job_id: "job_123",
hub_task_execution_id: "hub_456",
provider: "runpod",
hub_response: {},
};
const FAKE_JOB_POLLING = {
campaign_job_id: "job_123",
campaign_id: "debug-campaign",
role: "run",
submitted_by: "dev@test",
hub_task_execution_id: "hub_456",
hub: { status: "running" },
artifacts: [],
};
const FAKE_JOB_COMPLETED = {
...FAKE_JOB_POLLING,
hub: { status: "completed", output_data: {} },
artifacts: [
{
id: "art_001",
hub_output_key: "alignment.fasta",
phylo_tags: [],
scientist_note: null,
},
],
};
test("Tool invocation: drawer Run Tool -> configure -> submit -> poll -> complete", async ({
page,
}) => {
let pollCount = 0;
// Stub API routes
await page.route("**/api/skills", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
skills: FAKE_SKILLS.map((t) => ({
id: t.endpoint_name,
name: t.display_name,
description: t.description,
version: t.version,
author: t.author,
inputs: {},
outputs: {},
category: t.category,
gpu: t.compute.gpu,
})),
}),
});
});
await page.route("**/api/skills/mafft", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(FAKE_DETAIL),
});
});
await page.route("**/api/tools/mafft/estimate", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(FAKE_ESTIMATE),
});
});
await page.route("**/api/jobs/submit", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(FAKE_JOB_SUBMIT),
});
});
await page.route("**/api/jobs/job_123", async (route) => {
pollCount++;
const body = pollCount >= 2 ? FAKE_JOB_COMPLETED : FAKE_JOB_POLLING;
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(body),
});
});
await page.goto("/debug/skills");
// Open detail drawer
await expect(page.getByText("MAFFT")).toBeVisible();
await page
.getByTestId("skill-row")
.first()
.getByRole("button", { name: /Inspect MAFFT/ })
.click();
const drawer = page.getByTestId("skill-detail-drawer");
await expect(drawer).toBeVisible();
// Click "Run Tool" button in the drawer
await drawer.getByTestId("skill-run-tool-button").click();
// ToolRunPanel should appear
const panel = page.getByTestId("tool-run-panel");
await expect(panel).toBeVisible();
// Cost estimate should load
await expect(panel.getByTestId("estimate-cost")).toContainText("$0.12");
// Upload a fake file
const fileInput = panel.getByTestId("tool-run-file-input");
await fileInput.setInputFiles({
name: "test.fasta",
mimeType: "text/plain",
buffer: Buffer.from(">seq1\nACGT"),
});
// Click Run
await panel.getByTestId("tool-run-button").click();
// Should show polling state
await expect(panel.getByTestId("tool-run-polling")).toBeVisible();
// Wait for completion (poll returns "completed" on 2nd call)
await expect(panel.getByTestId("tool-run-completed")).toBeVisible({
timeout: 15_000,
});
// Artifact name should be visible
await expect(panel.getByText("alignment.fasta")).toBeVisible();
});
test("Tool invocation: shows error on failed job", async ({ page }) => {
await page.route("**/api/skills", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
skills: FAKE_SKILLS.map((t) => ({
id: t.endpoint_name,
name: t.display_name,
description: t.description,
version: t.version,
author: t.author,
inputs: {},
outputs: {},
category: t.category,
gpu: t.compute.gpu,
})),
}),
});
});
await page.route("**/api/skills/mafft", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(FAKE_DETAIL),
});
});
await page.route("**/api/tools/mafft/estimate", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(FAKE_ESTIMATE),
});
});
await page.route("**/api/jobs/submit", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(FAKE_JOB_SUBMIT),
});
});
await page.route("**/api/jobs/job_123", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
...FAKE_JOB_POLLING,
hub: { status: "failed", error: "Out of GPU memory" },
}),
});
});
await page.goto("/debug/skills");
// Open drawer, click Run Tool
await page
.getByTestId("skill-row")
.first()
.getByRole("button", { name: /Inspect MAFFT/ })
.click();
await page.getByTestId("skill-detail-drawer").getByTestId("skill-run-tool-button").click();
const panel = page.getByTestId("tool-run-panel");
await expect(panel).toBeVisible();
// Upload file and run
await panel.getByTestId("tool-run-file-input").setInputFiles({
name: "test.fasta",
mimeType: "text/plain",
buffer: Buffer.from(">seq1\nACGT"),
});
await panel.getByTestId("tool-run-button").click();
// Should show error
await expect(panel.getByTestId("tool-run-error")).toBeVisible({
timeout: 15_000,
});
await expect(panel.getByTestId("tool-run-error")).toContainText(
"Out of GPU memory",
);
});