Skip to content

Commit a3f21f8

Browse files
authored
Fix/update e2e test cases (ag-ui-protocol#277)
* fix:updated the test cases for humanInTheLoop & AgenticGenerativeUI to use data-test-ids * Fix: test cases involving toolBasedGenUI & predictiveStateUpdate * Added toolbased gen ui tests in mastra, mastra local & Agno * Added test cases for pydantic AI * Improved prompts a bit * Renoved unecessary checks in agentic chat tests
1 parent 423b31b commit a3f21f8

File tree

58 files changed

+2290
-703
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2290
-703
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Page, Locator, expect } from '@playwright/test';
2+
3+
export class ToolBaseGenUIPage {
4+
readonly page: Page;
5+
readonly haikuAgentIntro: Locator;
6+
readonly messageBox: Locator;
7+
readonly sendButton: Locator;
8+
readonly applyButton: Locator;
9+
readonly appliedButton: Locator;
10+
readonly haikuBlock: Locator;
11+
readonly japaneseLines: Locator;
12+
13+
constructor(page: Page) {
14+
this.page = page;
15+
this.haikuAgentIntro = page.getByText("I'm a haiku generator 👋. How can I help you?");
16+
this.messageBox = page.getByRole('textbox', { name: 'Type a message...' });
17+
this.sendButton = page.locator('[data-test-id="copilot-chat-ready"]');
18+
this.haikuBlock = page.locator('[data-testid="haiku-card"]');
19+
this.applyButton = page.getByRole('button', { name: 'Apply' });
20+
this.japaneseLines = page.locator('[data-testid="haiku-line"]');
21+
}
22+
23+
async generateHaiku(message: string) {
24+
await this.messageBox.click();
25+
await this.messageBox.fill(message);
26+
await this.sendButton.click();
27+
}
28+
29+
async checkGeneratedHaiku() {
30+
await this.page.locator('[data-testid="haiku-card"]').last().isVisible();
31+
const mostRecentCard = this.page.locator('[data-testid="haiku-card"]').last();
32+
await mostRecentCard.locator('[data-testid="haiku-line"]').first().waitFor({ state: 'visible', timeout: 10000 });
33+
}
34+
35+
async extractChatHaikuContent(page: Page): Promise<string> {
36+
await page.waitForTimeout(3000);
37+
await page.locator('[data-testid="haiku-card"]').first().waitFor({ state: 'visible' });
38+
const allHaikuCards = page.locator('[data-testid="haiku-card"]');
39+
const cardCount = await allHaikuCards.count();
40+
let chatHaikuContainer;
41+
let chatHaikuLines;
42+
43+
for (let cardIndex = cardCount - 1; cardIndex >= 0; cardIndex--) {
44+
chatHaikuContainer = allHaikuCards.nth(cardIndex);
45+
chatHaikuLines = chatHaikuContainer.locator('[data-testid="haiku-line"]');
46+
const linesCount = await chatHaikuLines.count();
47+
48+
if (linesCount > 0) {
49+
try {
50+
await chatHaikuLines.first().waitFor({ state: 'visible', timeout: 5000 });
51+
break;
52+
} catch (error) {
53+
continue;
54+
}
55+
}
56+
}
57+
58+
if (!chatHaikuLines) {
59+
throw new Error('No haiku cards with visible lines found');
60+
}
61+
62+
const count = await chatHaikuLines.count();
63+
const lines: string[] = [];
64+
65+
for (let i = 0; i < count; i++) {
66+
const haikuLine = chatHaikuLines.nth(i);
67+
const japaneseText = await haikuLine.locator('p').first().innerText();
68+
lines.push(japaneseText);
69+
}
70+
71+
const chatHaikuContent = lines.join('').replace(/\s/g, '');
72+
return chatHaikuContent;
73+
}
74+
75+
async extractMainDisplayHaikuContent(page: Page): Promise<string> {
76+
const mainDisplayLines = page.locator('[data-testid="main-haiku-line"]');
77+
const mainCount = await mainDisplayLines.count();
78+
const lines: string[] = [];
79+
80+
if (mainCount > 0) {
81+
for (let i = 0; i < mainCount; i++) {
82+
const haikuLine = mainDisplayLines.nth(i);
83+
const japaneseText = await haikuLine.locator('p').first().innerText();
84+
lines.push(japaneseText);
85+
}
86+
}
87+
88+
const mainHaikuContent = lines.join('').replace(/\s/g, '');
89+
return mainHaikuContent;
90+
}
91+
92+
async checkHaikuDisplay(page: Page): Promise<void> {
93+
const chatHaikuContent = await this.extractChatHaikuContent(page);
94+
95+
await page.waitForTimeout(5000);
96+
97+
const mainHaikuContent = await this.extractMainDisplayHaikuContent(page);
98+
99+
if (mainHaikuContent === '') {
100+
expect(chatHaikuContent.length).toBeGreaterThan(0);
101+
return;
102+
}
103+
104+
if (chatHaikuContent === mainHaikuContent) {
105+
expect(mainHaikuContent).toBe(chatHaikuContent);
106+
} else {
107+
await page.waitForTimeout(3000);
108+
109+
const updatedMainContent = await this.extractMainDisplayHaikuContent(page);
110+
111+
expect(updatedMainContent).toBe(chatHaikuContent);
112+
}
113+
}
114+
}

typescript-sdk/apps/dojo/e2e/pages/crewAIPages/AgenticUIGenPage.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,16 @@ export class AgenticGenUIPage {
1313
constructor(page: Page) {
1414
this.page = page;
1515
this.planTaskButton = page.getByRole('button', { name: 'Agentic Generative UI' });
16-
17-
// Remove iframe references
1816
this.chatInput = page.getByRole('textbox', { name: 'Type a message...' });
1917
this.sendButton = page.locator('[data-test-id="copilot-chat-ready"]');
2018
this.agentMessage = page.locator('.copilotKitAssistantMessage');
2119
this.userMessage = page.locator('.copilotKitUserMessage');
2220
this.agentGreeting = page.getByText('This agent demonstrates');
23-
this.agentPlannerContainer = page.locator('div.bg-gray-100.rounded-lg.w-\\[500px\\].p-4.text-black.space-y-2');
21+
this.agentPlannerContainer = page.getByTestId('task-progress');
2422
}
2523

2624
async plan() {
27-
const stepItems = this.agentPlannerContainer.locator('div.text-sm');
25+
const stepItems = this.agentPlannerContainer.getByTestId('task-step-text');
2826
const count = await stepItems.count();
2927
expect(count).toBeGreaterThan(0);
3028
for (let i = 0; i < count; i++) {
@@ -44,16 +42,14 @@ export class AgenticGenUIPage {
4442
}
4543

4644
getPlannerButton(name: string | RegExp) {
47-
// Remove iframe reference
4845
return this.page.getByRole('button', { name });
4946
}
5047

5148
async assertAgentReplyVisible(expectedText: RegExp) {
52-
await expect(this.agentMessage.getByText(expectedText)).toBeVisible();
49+
await expect(this.agentMessage.last().getByText(expectedText)).toBeVisible();
5350
}
5451

5552
async getUserText(textOrRegex) {
56-
// Remove iframe reference
5753
return await this.page.getByText(textOrRegex).isVisible();
5854
}
5955

typescript-sdk/apps/dojo/e2e/pages/crewAIPages/HumanInLoopPage.ts

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,17 @@ export class HumanInLoopPage {
1313

1414
constructor(page: Page) {
1515
this.page = page;
16-
// Remove the planTaskButton since it doesn't exist in this interface
1716
this.planTaskButton = page.getByRole('button', { name: 'Human in the loop Plan a task' });
18-
19-
// Update greeting text to match actual content
2017
this.agentGreeting = page.getByText("Hi, I'm an agent specialized in helping you with your tasks. How can I help you?");
2118
this.chatInput = page.getByRole('textbox', { name: 'Type a message...' });
2219
this.sendButton = page.locator('[data-test-id="copilot-chat-ready"]');
23-
this.plan = page.locator("div.bg-gray-100.rounded-lg").last();
20+
this.plan = page.getByTestId('select-steps');
2421
this.performStepsButton = page.getByRole('button', { name: 'Confirm' });
2522
this.agentMessage = page.locator('.copilotKitAssistantMessage');
2623
this.userMessage = page.locator('.copilotKitUserMessage');
2724
}
2825

2926
async openChat() {
30-
// Since there's no button to click, just wait for the chat to be ready
31-
// The chat is already open and visible
3227
await this.agentGreeting.isVisible();
3328
}
3429

@@ -44,59 +39,50 @@ export class HumanInLoopPage {
4439
}
4540

4641
async getPlannerOnClick(name: string | RegExp) {
47-
// Remove iframe reference
4842
return this.page.getByRole('button', { name });
4943
}
5044

5145
async uncheckItem(identifier: number | string): Promise<string> {
52-
// Use the last planner (most recent one)
53-
const plannerContainer = this.page.locator("div.bg-gray-100.rounded-lg").last();
54-
const items = plannerContainer.locator('div.text-sm.flex.items-center');
46+
const plannerContainer = this.page.getByTestId('select-steps');
47+
const items = plannerContainer.getByTestId('step-item');
5548

5649
let item;
5750
if (typeof identifier === 'number') {
5851
item = items.nth(identifier);
5952
} else {
60-
item = items.filter({ hasText: identifier }).first();
53+
item = items.filter({
54+
has: this.page.getByTestId('step-text').filter({ hasText: identifier })
55+
}).first();
6156
}
62-
63-
const text = await item.innerText();
64-
65-
// Force click with JavaScript since the checkboxes might be custom controlled
66-
await item.evaluate((element) => {
67-
const checkbox = element.querySelector('input[type="checkbox"]');
68-
if (checkbox) {
69-
checkbox.click();
70-
} else {
71-
// If no checkbox found, click the element itself
72-
element.click();
73-
}
74-
});
57+
const stepTextElement = item.getByTestId('step-text');
58+
const text = await stepTextElement.innerText();
59+
await item.click();
7560

7661
return text;
7762
}
7863

7964
async isStepItemUnchecked(target: number | string): Promise<boolean> {
80-
// Remove iframe reference
81-
const items = this.page.locator('div.text-sm.flex.items-center');
65+
const plannerContainer = this.page.getByTestId('select-steps');
66+
const items = plannerContainer.getByTestId('step-item');
8267

8368
let item;
8469
if (typeof target === 'number') {
8570
item = items.nth(target);
8671
} else {
87-
item = items.filter({ hasText: target });
72+
item = items.filter({
73+
has: this.page.getByTestId('step-text').filter({ hasText: target })
74+
}).first();
8875
}
89-
90-
const span = item.locator('span');
91-
return await span.evaluate(el => el.classList.contains('line-through'));
76+
const checkbox = item.locator('input[type="checkbox"]');
77+
return !(await checkbox.isChecked());
9278
}
9379

9480
async performSteps() {
9581
await this.performStepsButton.click();
9682
}
9783

9884
async assertAgentReplyVisible(expectedText: RegExp) {
99-
await expect(this.agentMessage.getByText(expectedText)).toBeVisible();
85+
await expect(this.agentMessage.last().getByText(expectedText)).toBeVisible();
10086
}
10187

10288
async assertUserMessageVisible(message: string) {

typescript-sdk/apps/dojo/e2e/pages/crewAIPages/PredictiveStateUpdatesPage.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,21 @@ export class PredictiveStateUpdatesPage {
1010
readonly approveButton: Locator;
1111
readonly acceptedButton: Locator;
1212
readonly confirmedChangesResponse: Locator;
13+
readonly rejectedChangesResponse: Locator;
1314
readonly agentMessage: Locator;
1415
readonly userMessage: Locator;
1516
readonly highlights: Locator;
1617

1718
constructor(page: Page) {
1819
this.page = page;
19-
// Remove iframe references and use actual greeting text
2020
this.agentGreeting = page.getByText("Hi 👋 How can I help with your document?");
2121
this.chatInput = page.getByRole('textbox', { name: 'Type a message...' });
2222
this.sendButton = page.locator('[data-test-id="copilot-chat-ready"]');
2323
this.agentResponsePrompt = page.locator('div.tiptap.ProseMirror');
2424
this.userApprovalModal = page.locator('div.bg-white.rounded.shadow-lg >> text=Confirm Changes');
2525
this.acceptedButton = page.getByText('✓ Accepted');
26-
this.confirmedChangesResponse = page.locator('div.copilotKitMarkdown');
26+
this.confirmedChangesResponse = page.locator('div.copilotKitMarkdown').first();
27+
this.rejectedChangesResponse = page.locator('div.copilotKitMarkdown').last();
2728
this.highlights = page.locator('.tiptap em');
2829
this.agentMessage = page.locator('.copilotKitAssistantMessage');
2930
this.userMessage = page.locator('.copilotKitUserMessage');
@@ -45,51 +46,42 @@ export class PredictiveStateUpdatesPage {
4546
}
4647

4748
async getButton(page, buttonName) {
48-
// Remove iframe reference
4949
return page.getByRole('button', { name: buttonName }).click();
5050
}
5151

5252
async getStatusLabelOfButton(page, statusText) {
53-
// Remove iframe reference
5453
return page.getByText(statusText, { exact: true });
5554
}
5655

5756
async getUserApproval() {
5857
await this.userApprovalModal.isVisible();
5958
await this.getButton(this.page, "Confirm");
6059
const acceptedLabel = this.userApprovalModal.locator('text=✓ Accepted');
61-
// await expect(acceptedLabel).toBeVisible();
62-
// const acceptedLabel = await this.getStatusLabelOfButton(this.page, "✓ Accepted");
63-
// await acceptedLabel.isVisible();
6460
}
6561

6662
async getUserRejection() {
6763
await this.userApprovalModal.isVisible();
6864
await this.getButton(this.page, "Reject");
69-
await this.acceptedButton.isVisible();
70-
const acceptedLabel = await this.getStatusLabelOfButton(this.page, "✕ Rejected");
71-
await acceptedLabel.isVisible();
65+
const rejectedLabel = await this.getStatusLabelOfButton(this.page, "✕ Rejected");
66+
await rejectedLabel.isVisible();
7267
}
7368

7469
async verifyAgentResponse(dragonName) {
75-
// Remove iframe reference
7670
const paragraphWithName = await this.page.locator(`div.tiptap >> text=${dragonName}`).first();
7771

7872
const fullText = await paragraphWithName.textContent();
7973
if (!fullText) {
8074
return null;
8175
}
8276

83-
const match = fullText.match(new RegExp(dragonName, 'i')); // case-insensitive
77+
const match = fullText.match(new RegExp(dragonName, 'i'));
8478
return match ? match[0] : null;
8579
}
8680

8781
async verifyHighlightedText(){
88-
// Check for highlights BEFORE accepting the changes
89-
// The highlights appear when changes are proposed, not after they're accepted
9082
const highlightSelectors = [
91-
'.tiptap em', // For new/added text
92-
'.tiptap s', // For strikethrough/removed text
83+
'.tiptap em',
84+
'.tiptap s',
9385
'div.tiptap em',
9486
'div.tiptap s'
9587
];
@@ -98,16 +90,13 @@ export class PredictiveStateUpdatesPage {
9890
for (const selector of highlightSelectors) {
9991
count = await this.page.locator(selector).count();
10092
if (count > 0) {
101-
console.log(`Found ${count} highlighted elements with selector: ${selector}`);
10293
break;
10394
}
10495
}
10596

10697
if (count > 0) {
10798
expect(count).toBeGreaterThan(0);
10899
} else {
109-
// If no highlights found, verify the changes are visible in the modal instead
110-
console.log("No highlights in document, checking for confirmation modal");
111100
const modal = this.page.locator('div.bg-white.rounded.shadow-lg');
112101
await expect(modal).toBeVisible();
113102
}

0 commit comments

Comments
 (0)