Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions apps/dojo/e2e/pages/agnoPages/HumanInLoopPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Page, Locator, expect } from '@playwright/test';

export class HumanInLoopPage {
readonly page: Page;
readonly planTaskButton: Locator;
readonly chatInput: Locator;
readonly sendButton: Locator;
readonly agentGreeting: Locator;
readonly plan: Locator;
readonly performStepsButton: Locator;
readonly agentMessage: Locator;
readonly userMessage: Locator;

constructor(page: Page) {
this.page = page;
this.planTaskButton = page.getByRole('button', { name: 'Human in the loop Plan a task' });
this.agentGreeting = page.getByText("Hi, I'm an agent specialized in helping you with your tasks. How can I help you?");
this.chatInput = page.getByRole('textbox', { name: 'Type a message...' });
this.sendButton = page.locator('[data-test-id="copilot-chat-ready"]');
this.plan = page.getByTestId('select-steps');
this.performStepsButton = page.getByRole('button', { name: 'Confirm' });
this.agentMessage = page.locator('.copilotKitAssistantMessage');
this.userMessage = page.locator('.copilotKitUserMessage');
}

async openChat() {
await this.agentGreeting.isVisible();
}

async sendMessage(message: string) {
await this.chatInput.click();
await this.chatInput.fill(message);
await this.sendButton.click();
}

async selectItemsInPlanner() {
await expect(this.plan).toBeVisible({ timeout: 10000 });
await this.plan.click();
}

async getPlannerOnClick(name: string | RegExp) {
return this.page.getByRole('button', { name });
}

async uncheckItem(identifier: number | string): Promise<string> {
const plannerContainer = this.page.getByTestId('select-steps');
const items = plannerContainer.getByTestId('step-item');

let item;
if (typeof identifier === 'number') {
item = items.nth(identifier);
} else {
item = items.filter({
has: this.page.getByTestId('step-text').filter({ hasText: identifier })
}).first();
}
const stepTextElement = item.getByTestId('step-text');
const text = await stepTextElement.innerText();
await item.click();

return text;
}

async isStepItemUnchecked(target: number | string): Promise<boolean> {
const plannerContainer = this.page.getByTestId('select-steps');
const items = plannerContainer.getByTestId('step-item');

let item;
if (typeof target === 'number') {
item = items.nth(target);
} else {
item = items.filter({
has: this.page.getByTestId('step-text').filter({ hasText: target })
}).first();
}
const checkbox = item.locator('input[type="checkbox"]');
return !(await checkbox.isChecked());
}

async performSteps() {
await this.performStepsButton.click();
}

async assertAgentReplyVisible(expectedText: RegExp) {
await expect(this.agentMessage.last().getByText(expectedText)).toBeVisible();
}

async assertUserMessageVisible(message: string) {
await expect(this.page.getByText(message)).toBeVisible();
}
}
38 changes: 29 additions & 9 deletions apps/dojo/e2e/tests/agnoTests/backendToolRenderingPage.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { test, expect } from "@playwright/test";

test("[Agno] Backend Tool Rendering displays weather cards", async ({ page }) => {
test("[Agno] Backend Tool Rendering displays weather cards", async ({
page,
}) => {
// Set shorter default timeout for this test
test.setTimeout(30000); // 30 seconds total

await page.goto("/agno/feature/backend_tool_rendering");

// Verify suggestion buttons are visible
await expect(page.getByRole("button", { name: "Weather in San Francisco" })).toBeVisible({
await expect(
page.getByRole("button", { name: "Weather in San Francisco" }),
).toBeVisible({
timeout: 5000,
});

Expand All @@ -26,29 +30,45 @@
await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 });
}

// Verify weather content is present (use flexible selectors)
// Verify all weather data fields are present and correctly displayed
const hasHumidity = await page
.getByText("Humidity")
.getByTestId("weather-humidity")
.isVisible()
.catch(() => false);
const hasWind = await page
.getByText("Wind")
.getByTestId("weather-wind")
.isVisible()
.catch(() => false);
const hasFeelsLike = await page
.getByTestId("weather-feels-like")
.isVisible()
.catch(() => false);
const hasCityName = await page
.locator("h3")
.getByTestId("weather-city")
.filter({ hasText: /San Francisco/i })
.isVisible()
.catch(() => false);

// At least one of these should be true
expect(hasHumidity || hasWind || hasCityName).toBeTruthy();
// Verify all critical fields are present
expect(hasHumidity).toBeTruthy();
expect(hasWind).toBeTruthy();
expect(hasFeelsLike).toBeTruthy();
expect(hasCityName).toBeTruthy();

// Verify temperature is displayed (should show both C and F)
const temperatureText = await page
.locator("text=/\\d+°\\s*C/")
.isVisible()
.catch(() => false);
expect(temperatureText).toBeTruthy();

Check failure on line 63 in apps/dojo/e2e/tests/agnoTests/backendToolRenderingPage.spec.ts

View workflow job for this annotation

GitHub Actions / dojo / agno

[chromium] › tests/agnoTests/backendToolRenderingPage.spec.ts:3:5 › [Agno] Backend Tool Rendering displays weather cards

1) [chromium] › tests/agnoTests/backendToolRenderingPage.spec.ts:3:5 › [Agno] Backend Tool Rendering displays weather cards Error: expect(received).toBeTruthy() Received: false 61 | .isVisible() 62 | .catch(() => false); > 63 | expect(temperatureText).toBeTruthy(); | ^ 64 | 65 | // Click second suggestion 66 | await page.getByRole("button", { name: "Weather in New York" }).click(); at /home/runner/work/ag-ui/ag-ui/apps/dojo/e2e/tests/agnoTests/backendToolRenderingPage.spec.ts:63:27

// Click second suggestion
await page.getByRole("button", { name: "Weather in New York" }).click();
await page.waitForTimeout(2000);

// Verify at least one weather-related element is still visible
const weatherElements = await page.getByText(/Weather|Humidity|Wind|Temperature/i).count();
const weatherElements = await page
.getByText(/Weather|Humidity|Wind|Temperature/i)
.count();
expect(weatherElements).toBeGreaterThan(0);
});
91 changes: 91 additions & 0 deletions apps/dojo/e2e/tests/agnoTests/humanInTheLoopPage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { test, expect, waitForAIResponse, retryOnAIFailure } from "../../test-isolation-helper";
import { HumanInLoopPage } from "../../pages/agnoPages/HumanInLoopPage";

test.describe("Human in the Loop Feature", () => {
test("[Agno] should interact with the chat and perform steps", async ({
page,
}) => {
await retryOnAIFailure(async () => {
const humanInLoop = new HumanInLoopPage(page);

await page.goto(
"/agno/feature/human_in_the_loop"
);

await humanInLoop.openChat();

await humanInLoop.sendMessage("Hi");
await humanInLoop.agentGreeting.isVisible();

await humanInLoop.sendMessage(
"Give me a plan to make brownies, there should be only one step with eggs and one step with oven, this is a strict requirement so adhere"
);
await waitForAIResponse(page);
await expect(humanInLoop.plan).toBeVisible({ timeout: 10000 });

const itemText = "eggs";
await page.waitForTimeout(5000);
await humanInLoop.uncheckItem(itemText);
await humanInLoop.performSteps();

await page.waitForFunction(
() => {
const messages = Array.from(document.querySelectorAll('.copilotKitAssistantMessage'));
const lastMessage = messages[messages.length - 1];
const content = lastMessage?.textContent?.trim() || '';
return messages.length >= 3 && content.length > 0;
},
{ timeout: 30000 }
);

await humanInLoop.sendMessage(
`Does the planner include ${itemText}? ⚠️ Reply with only words 'Yes' or 'No' (no explanation, no punctuation).`
);
await waitForAIResponse(page);
});
});

test("[Agno] should interact with the chat using predefined prompts and perform steps", async ({
page,
}) => {
await retryOnAIFailure(async () => {
const humanInLoop = new HumanInLoopPage(page);

await page.goto(
"/agno/feature/human_in_the_loop"
);

await humanInLoop.openChat();

await humanInLoop.sendMessage("Hi");
await humanInLoop.agentGreeting.isVisible();
await humanInLoop.sendMessage(
"Plan a mission to Mars with the first step being Start The Planning"
);
await waitForAIResponse(page);
await expect(humanInLoop.plan).toBeVisible({ timeout: 10000 });

const uncheckedItem = "Start The Planning";

await page.waitForTimeout(5000);
await humanInLoop.uncheckItem(uncheckedItem);
await humanInLoop.performSteps();

await page.waitForFunction(
() => {
const messages = Array.from(document.querySelectorAll('.copilotKitAssistantMessage'));
const lastMessage = messages[messages.length - 1];
const content = lastMessage?.textContent?.trim() || '';

return messages.length >= 3 && content.length > 0;
},
{ timeout: 30000 }
);

await humanInLoop.sendMessage(
`Does the planner include ${uncheckedItem}? ⚠️ Reply with only words 'Yes' or 'No' (no explanation, no punctuation).`
);
await waitForAIResponse(page);
});
});
});
3 changes: 3 additions & 0 deletions apps/dojo/src/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@ export const agentsIntegrations: AgentIntegrationConfig[] = [
backend_tool_rendering: new AgnoAgent({
url: `${envVars.agnoUrl}/backend_tool_rendering/agui`,
}),
human_in_the_loop: new AgnoAgent({
url: `${envVars.agnoUrl}/human_in_the_loop/agui`,
}),
};
},
},
Expand Down
Loading
Loading