Skip to content
Draft
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
59 changes: 59 additions & 0 deletions apps/dojo/e2e/pages/awsStrandsPages/AgenticUIGenPage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Page, Locator, expect } from '@playwright/test';

export class AgenticGenUIPage {
readonly page: Page;
readonly chatInput: Locator;
readonly planTaskButton: Locator;
readonly agentMessage: Locator;
readonly userMessage: Locator;
readonly agentGreeting: Locator;
readonly agentPlannerContainer: Locator;
readonly sendButton: Locator;

constructor(page: Page) {
this.page = page;
this.planTaskButton = page.getByRole('button', { name: 'Agentic Generative UI' });
this.chatInput = page.getByRole('textbox', { name: 'Type a message...' });
this.sendButton = page.locator('[data-test-id="copilot-chat-ready"]');
this.agentMessage = page.locator('.copilotKitAssistantMessage');
this.userMessage = page.locator('.copilotKitUserMessage');
this.agentGreeting = page.getByText('This agent demonstrates');
this.agentPlannerContainer = page.getByTestId('task-progress');
}

async plan() {
const stepItems = this.agentPlannerContainer.getByTestId('task-step-text');
const count = await stepItems.count();
expect(count).toBeGreaterThan(0);
for (let i = 0; i < count; i++) {
const stepText = await stepItems.nth(i).textContent();
console.log(`Step ${i + 1}: ${stepText?.trim()}`);
await expect(stepItems.nth(i)).toBeVisible();
}
}

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

async sendMessage(message: string) {
await this.chatInput.fill(message);
await this.page.waitForTimeout(5000)
}

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

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

async getUserText(textOrRegex) {
return await this.page.getByText(textOrRegex).isVisible();
}

async assertUserMessageVisible(message: string) {
await expect(this.userMessage.getByText(message)).toBeVisible();
}
}
115 changes: 115 additions & 0 deletions apps/dojo/e2e/tests/awsStrandsTests/agenticChatPage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
test,
expect,
waitForAIResponse,
retryOnAIFailure,
} from "../../test-isolation-helper";
import { AgenticChatPage } from "../../featurePages/AgenticChatPage";

test("[Strands] Agentic Chat sends and receives a message", async ({
page,
}) => {
await retryOnAIFailure(async () => {
await page.goto(
"/aws-strands/feature/agentic_chat"
);

const chat = new AgenticChatPage(page);

await chat.openChat();
await chat.agentGreeting.isVisible;
await chat.sendMessage("Hi, I am duaa");

await waitForAIResponse(page);
await chat.assertUserMessageVisible("Hi, I am duaa");
await chat.assertAgentReplyVisible(/Hello/i);
});
});

test("[Strands] Agentic Chat changes background on message and reset", async ({
page,
}) => {
await retryOnAIFailure(async () => {
await page.goto(
"/aws-strands/feature/agentic_chat"
);

const chat = new AgenticChatPage(page);

await chat.openChat();
await chat.agentGreeting.waitFor({ state: "visible" });

// Store initial background color
const backgroundContainer = page.locator('[data-testid="background-container"]')
const initialBackground = await backgroundContainer.evaluate(el => getComputedStyle(el).backgroundColor);
console.log("Initial background color:", initialBackground);

// 1. Send message to change background to blue
await chat.sendMessage("Hi change the background color to blue");
await chat.assertUserMessageVisible(
"Hi change the background color to blue"
);
await waitForAIResponse(page);

await expect(backgroundContainer).not.toHaveCSS('background-color', initialBackground, { timeout: 7000 });
const backgroundBlue = await backgroundContainer.evaluate(el => getComputedStyle(el).backgroundColor);
// Check if background is blue (string color name or contains blue)
expect(backgroundBlue.toLowerCase()).toMatch(/blue|rgb\(.*,.*,.*\)|#[0-9a-f]{6}/);

// 2. Change to pink
await chat.sendMessage("Hi change the background color to pink");
await chat.assertUserMessageVisible(
"Hi change the background color to pink"
);
await waitForAIResponse(page);

await expect(backgroundContainer).not.toHaveCSS('background-color', backgroundBlue, { timeout: 7000 });
const backgroundPink = await backgroundContainer.evaluate(el => getComputedStyle(el).backgroundColor);
// Check if background is pink (string color name or contains pink)
expect(backgroundPink.toLowerCase()).toMatch(/pink|rgb\(.*,.*,.*\)|#[0-9a-f]{6}/);
});
});

test("[Strands] Agentic Chat retains memory of user messages during a conversation", async ({
page,
}) => {
await retryOnAIFailure(async () => {
await page.goto(
"/aws-strands/feature/agentic_chat"
);

const chat = new AgenticChatPage(page);
await chat.openChat();
await chat.agentGreeting.click();

await chat.sendMessage("Hey there");
await chat.assertUserMessageVisible("Hey there");
await waitForAIResponse(page);
await chat.assertAgentReplyVisible(/how can I assist you/i);

const favFruit = "Mango";
await chat.sendMessage(`My favorite fruit is ${favFruit}`);
await chat.assertUserMessageVisible(`My favorite fruit is ${favFruit}`);
await waitForAIResponse(page);
await chat.assertAgentReplyVisible(new RegExp(favFruit, "i"));

await chat.sendMessage("and I love listening to Kaavish");
await chat.assertUserMessageVisible("and I love listening to Kaavish");
await waitForAIResponse(page);
await chat.assertAgentReplyVisible(/Kaavish/i);

await chat.sendMessage("tell me an interesting fact about Moon");
await chat.assertUserMessageVisible(
"tell me an interesting fact about Moon"
);
await waitForAIResponse(page);
await chat.assertAgentReplyVisible(/Moon/i);

await chat.sendMessage("Can you remind me what my favorite fruit is?");
await chat.assertUserMessageVisible(
"Can you remind me what my favorite fruit is?"
);
await waitForAIResponse(page);
await chat.assertAgentReplyVisible(new RegExp(favFruit, "i"));
});
});
68 changes: 68 additions & 0 deletions apps/dojo/e2e/tests/awsStrandsTests/agenticGenUI.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { test, expect } from "@playwright/test";
import { AgenticGenUIPage } from "../../pages/awsStrandsPages/AgenticUIGenPage";

test.describe("Agent Generative UI Feature", () => {
// Flaky. Sometimes the steps render but never process.
test("[Strands] should interact with the chat to get a planner on prompt", async ({
page,
}) => {
const genUIAgent = new AgenticGenUIPage(page);

await page.goto(
"/aws-strands/feature/agentic_generative_ui"
);

await genUIAgent.openChat();
await genUIAgent.sendMessage("Hi");
await genUIAgent.sendButton.click();
await genUIAgent.assertAgentReplyVisible(/Hello/);

await genUIAgent.sendMessage("give me a plan to make brownies");
await genUIAgent.sendButton.click();
await expect(genUIAgent.agentPlannerContainer).toBeVisible({ timeout: 15000 });
await genUIAgent.plan();

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 }
);
});

test("[Strands] should interact with the chat using predefined prompts and perform steps", async ({
page,
}) => {
const genUIAgent = new AgenticGenUIPage(page);

await page.goto(
"/aws-strands/feature/agentic_generative_ui"
);

await genUIAgent.openChat();
await genUIAgent.sendMessage("Hi");
await genUIAgent.sendButton.click();
await genUIAgent.assertAgentReplyVisible(/Hello/);

await genUIAgent.sendMessage("Go to Mars");
await genUIAgent.sendButton.click();

await expect(genUIAgent.agentPlannerContainer).toBeVisible({ timeout: 15000 });
await genUIAgent.plan();

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 }
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { test, expect } from "@playwright/test";

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

await page.goto("/aws-strands/feature/backend_tool_rendering");

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

// Click first suggestion and verify weather card appears
await page.getByRole("button", { name: "Weather in San Francisco" }).click();

// Wait for either test ID or fallback to "Current Weather" text
const weatherCard = page.getByTestId("weather-card");
const currentWeatherText = page.getByText("Current Weather");

// Try test ID first, fallback to text
try {
await expect(weatherCard).toBeVisible({ timeout: 10000 });
} catch (e) {
// Fallback to checking for "Current Weather" text
await expect(currentWeatherText.first()).toBeVisible({ timeout: 10000 });
}

// Verify weather content is present (use flexible selectors)
const hasHumidity = await page
.getByText("Humidity")
.isVisible()
.catch(() => false);
const hasWind = await page
.getByText("Wind")
.isVisible()
.catch(() => false);
const hasCityName = await page
.locator("h3")
.filter({ hasText: /San Francisco/i })
.isVisible()
.catch(() => false);

// At least one of these should be true
expect(hasHumidity || hasWind || hasCityName).toBeTruthy();

// 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();
expect(weatherElements).toBeGreaterThan(0);
});
56 changes: 56 additions & 0 deletions apps/dojo/e2e/tests/awsStrandsTests/sharedStatePage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { test, expect } from "@playwright/test";
import { SharedStatePage } from "../../featurePages/SharedStatePage";

test.describe("Shared State Feature", () => {
test("[Strands] should interact with the chat to get a recipe on prompt", async ({
page,
}) => {
const sharedStateAgent = new SharedStatePage(page);

// Update URL to new domain
await page.goto(
"/aws-strands/feature/shared_state"
);

await sharedStateAgent.openChat();
await sharedStateAgent.sendMessage('Please give me a pasta recipe of your choosing, but one of the ingredients should be "Pasta"');
await sharedStateAgent.loader();
await sharedStateAgent.awaitIngredientCard('Pasta');
await sharedStateAgent.getInstructionItems(
sharedStateAgent.instructionsContainer
);
});

test("[Strands] should share state between UI and chat", async ({
page,
}) => {
const sharedStateAgent = new SharedStatePage(page);

await page.goto(
"/aws-strands/feature/shared_state"
);

await sharedStateAgent.openChat();

// Add new ingredient via UI
await sharedStateAgent.addIngredient.click();

// Fill in the new ingredient details
const newIngredientCard = page.locator('.ingredient-card').last();
await newIngredientCard.locator('.ingredient-name-input').fill('Potatoes');
await newIngredientCard.locator('.ingredient-amount-input').fill('12');

// Wait for UI to update
await page.waitForTimeout(1000);

// Ask chat for all ingredients
await sharedStateAgent.sendMessage("Give me all the ingredients");
await sharedStateAgent.loader();

// Verify chat response includes both existing and new ingredients
await expect(sharedStateAgent.agentMessage.getByText(/Potatoes/)).toBeVisible();
await expect(sharedStateAgent.agentMessage.getByText(/12/)).toBeVisible();
await expect(sharedStateAgent.agentMessage.getByText(/Carrots/)).toBeVisible();
await expect(sharedStateAgent.agentMessage.getByText(/All-Purpose Flour/)).toBeVisible();
});
});
1 change: 1 addition & 0 deletions apps/dojo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@ag-ui/server-starter-all-features": "workspace:*",
"@ag-ui/spring-ai": "workspace:*",
"@ag-ui/vercel-ai-sdk": "workspace:*",
"@ag-ui/aws-strands-integration": "workspace:*",
"@ai-sdk/openai": "^2.0.42",
"@copilotkit/react-core": "1.10.6",
"@copilotkit/react-ui": "1.10.6",
Expand Down
5 changes: 5 additions & 0 deletions apps/dojo/scripts/prep-dojo-everything.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ const ALL_TARGETS = {
name: "Pydantic AI",
cwd: path.join(integrationsRoot, "pydantic-ai/python/examples"),
},
"aws-strands": {
command: "poetry install",
name: "AWS Strands",
cwd: path.join(integrationsRoot, "aws-strands/python/examples"),
},
"adk-middleware": {
command: "uv sync",
name: "ADK Middleware",
Expand Down
7 changes: 7 additions & 0 deletions apps/dojo/scripts/run-dojo-everything.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ const ALL_SERVICES = {
cwd: path.join(integrationsRoot, 'pydantic-ai/python/examples'),
env: { PORT: 8009 },
}],
'aws-strands': [{
command: 'poetry run dev',
name: 'AWS Strands',
cwd: path.join(integrationsRoot, 'aws-strands/python/examples'),
env: { PORT: 8017 },
}],
'adk-middleware': [{
command: 'uv run dev',
name: 'ADK Middleware',
Expand Down Expand Up @@ -174,6 +180,7 @@ const ALL_SERVICES = {
A2A_MIDDLEWARE_FINANCE_URL: 'http://localhost:8012',
A2A_MIDDLEWARE_IT_URL: 'http://localhost:8013',
A2A_MIDDLEWARE_ORCHESTRATOR_URL: 'http://localhost:8014',
AWS_STRANDS_URL: 'http://localhost:8017',
NEXT_PUBLIC_CUSTOM_DOMAIN_TITLE: 'cpkdojo.local___CopilotKit Feature Viewer',
},
}],
Expand Down
Loading