Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f1d4599
feat: Add support for cloud
LuisValdesZero Sep 10, 2025
c5bcd65
Add script to run dojo
LuisValdesZero Sep 10, 2025
1c1bb28
Add default value for tools
LuisValdesZero Sep 10, 2025
e1be909
Add initial value
LuisValdesZero Sep 11, 2025
f162db9
Increase timeout
LuisValdesZero Sep 11, 2025
46cd1e1
Revert "Increase timeout"
LuisValdesZero Sep 11, 2025
ded8132
Run tests conditionally
LuisValdesZero Sep 11, 2025
4f6ba11
Generate json file
LuisValdesZero Sep 16, 2025
ea7edfe
Merge branch 'main' into feat/integrate-e2e-with-cloud-agents
LuisValdesZero Sep 16, 2025
1651636
Update packages to latest version
LuisValdesZero Sep 17, 2025
51861e1
Merge branch 'main' into feat/integrate-e2e-with-cloud-agents
LuisValdesZero Sep 17, 2025
0c677f7
Update file
LuisValdesZero Sep 17, 2025
5c7a1ca
Merge branch 'main' into feat/integrate-e2e-with-cloud-agents
LuisValdesZero Sep 18, 2025
a74d065
Set default value an empty array
LuisValdesZero Sep 18, 2025
3b824c9
Add onrender python version file
LuisValdesZero Sep 18, 2025
b912e3a
Remove patch
LuisValdesZero Sep 18, 2025
5595874
Change python version
LuisValdesZero Sep 18, 2025
e024c73
Add configuration for onrender
LuisValdesZero Sep 18, 2025
82cf10e
Unset environment variable
LuisValdesZero Sep 18, 2025
8df85fe
Change python version
LuisValdesZero Sep 18, 2025
017da0b
Add missing tool
LuisValdesZero Sep 18, 2025
5474e4e
Add tool to change background
LuisValdesZero Sep 18, 2025
c32f8bd
Modify testing
LuisValdesZero Sep 18, 2025
c6e3f34
Add tool
LuisValdesZero Sep 18, 2025
4df8754
Change logic to get the background
LuisValdesZero Sep 18, 2025
05aa779
Add condition to wait for haiku lines equals to 3
LuisValdesZero Sep 18, 2025
cd8eb55
Disable ADK tests when running cloud agents
LuisValdesZero Sep 18, 2025
14490ed
Add logic to wait for main content to be edited
LuisValdesZero Sep 18, 2025
d490264
Add weatherTool
LuisValdesZero Sep 18, 2025
d63923e
Add logging
LuisValdesZero Sep 18, 2025
d5f1bce
chore: Install only chromium
LuisValdesZero Sep 18, 2025
c07b23a
fix: Add state
LuisValdesZero Sep 18, 2025
fd8be8c
fix: Add state
LuisValdesZero Sep 18, 2025
019563d
Add deps
LuisValdesZero Sep 18, 2025
baf5ab6
fix: Remove import
LuisValdesZero Sep 18, 2025
ad03b68
Fix
LuisValdesZero Sep 18, 2025
feba8ae
chore: Add json file
LuisValdesZero Sep 19, 2025
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
129 changes: 110 additions & 19 deletions typescript-sdk/apps/dojo/e2e/featurePages/AgenticChatPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,33 +57,92 @@ export class AgenticChatPage {
async getBackground(
property: "backgroundColor" | "backgroundImage" = "backgroundColor"
): Promise<string> {
// Wait a bit for background to apply
await this.page.waitForTimeout(500);
// Wait for React to render and apply styles
await this.page.waitForTimeout(2000);

// Try multiple selectors for the background element
// Wait for the main container with background style to be present
await this.page.waitForSelector('.flex.justify-center.items-center.h-full.w-full', {
state: 'visible',
timeout: 10000
});

// Try to get the background from the main container
const mainContainer = this.page.locator('.flex.justify-center.items-center.h-full.w-full').first();

try {
const backgroundValue = await mainContainer.evaluate((el) => {
// Get the inline style background value
const inlineBackground = el.style.background;
if (inlineBackground && inlineBackground !== '--copilot-kit-background-color') {
return inlineBackground;
}

// Get computed style
const computedStyle = getComputedStyle(el);
const computedBackground = computedStyle.background;
const computedBackgroundColor = computedStyle.backgroundColor;

// Check if it's a CSS custom property
if (inlineBackground === '--copilot-kit-background-color') {
// Try to resolve the CSS custom property
const customPropValue = computedStyle.getPropertyValue('--copilot-kit-background-color');
if (customPropValue) {
return customPropValue;
}
}

// Return computed values
if (computedBackground && computedBackground !== 'rgba(0, 0, 0, 0)' && computedBackground !== 'transparent') {
return computedBackground;
}

if (computedBackgroundColor && computedBackgroundColor !== 'rgba(0, 0, 0, 0)' && computedBackgroundColor !== 'transparent') {
return computedBackgroundColor;
}

return computedBackground || computedBackgroundColor;
});

console.log(`Main container background: ${backgroundValue}`);

if (backgroundValue && backgroundValue !== 'rgba(0, 0, 0, 0)' && backgroundValue !== 'transparent') {
return backgroundValue;
}
} catch (error) {
console.log('Error getting background from main container:', error);
}

// Fallback: try other selectors
const selectors = [
'div[style*="background"]',
'div[style*="background-color"]',
'.flex.justify-center.items-center.h-full.w-full',
'div.flex.justify-center.items-center.h-full.w-full',
'[class*="bg-"]',
'div[class*="background"]'
'.copilotKitWindow',
'body'
];

for (const selector of selectors) {
try {
const element = this.page.locator(selector).first();
if (await element.isVisible({ timeout: 1000 })) {
console.log(`Checking fallback selector: ${selector}`);

if (await element.isVisible({ timeout: 5000 })) {
const value = await element.evaluate(
(el, prop) => {
// Check inline style first
if (el.style.background) return el.style.background;
if (el.style.backgroundColor) return el.style.backgroundColor;
const computedStyle = getComputedStyle(el);
const inlineStyle = el.style[prop as any];

// Prefer inline style
if (inlineStyle && inlineStyle !== 'rgba(0, 0, 0, 0)' && inlineStyle !== 'transparent') {
return inlineStyle;
}

// Then computed style
return getComputedStyle(el)[prop as any];
const computedValue = computedStyle[prop as any];
return computedValue;
},
property
);

if (value && value !== "rgba(0, 0, 0, 0)" && value !== "transparent") {
console.log(`[${selector}] ${property}: ${value}`);
return value;
Expand All @@ -94,13 +153,45 @@ export class AgenticChatPage {
}
}

// Fallback to original element
const value = await this.chatBackground.first().evaluate(
(el, prop) => getComputedStyle(el)[prop as any],
property
);
console.log(`[Fallback] ${property}: ${value}`);
return value;
// Final fallback
const fallbackValue = await this.page.evaluate((prop) => {
return getComputedStyle(document.body)[prop as any];
}, property);

console.log(`[Final Fallback] ${property}: ${fallbackValue}`);
return fallbackValue;
}

async waitForBackgroundChange(expectedBackground?: string, timeout: number = 10000): Promise<void> {
const startTime = Date.now();

while (Date.now() - startTime < timeout) {
try {
const currentBackground = await this.getBackground();

// If we're looking for a specific background
if (expectedBackground) {
if (currentBackground.includes(expectedBackground) ||
currentBackground === expectedBackground) {
return;
}
} else {
// Just wait for any non-default background
if (currentBackground !== 'oklch(1 0 0)' &&
currentBackground !== 'rgba(0, 0, 0, 0)' &&
currentBackground !== 'transparent' &&
!currentBackground.includes('--copilot-kit-background-color')) {
return;
}
}

await this.page.waitForTimeout(500);
} catch (error) {
await this.page.waitForTimeout(500);
}
}

throw new Error(`Background did not change to expected value within ${timeout}ms`);
}

async getGradientButtonByName(name: string | RegExp) {
Expand Down
95 changes: 75 additions & 20 deletions typescript-sdk/apps/dojo/e2e/featurePages/ToolBaseGenUIPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,47 +33,69 @@ export class ToolBaseGenUIPage {
}

async extractChatHaikuContent(page: Page): Promise<string> {
await page.waitForTimeout(3000);
await page.locator('[data-testid="haiku-card"]').first().waitFor({ state: 'visible' });
// Wait for haiku cards to be visible
await page.waitForSelector('[data-testid="haiku-card"]', { state: 'visible' });

const allHaikuCards = page.locator('[data-testid="haiku-card"]');
const cardCount = await allHaikuCards.count();
let chatHaikuContainer;
let chatHaikuLines;

// Find the most recent haiku card with lines
for (let cardIndex = cardCount - 1; cardIndex >= 0; cardIndex--) {
chatHaikuContainer = allHaikuCards.nth(cardIndex);
chatHaikuLines = chatHaikuContainer.locator('[data-testid="haiku-line"]');
const linesCount = await chatHaikuLines.count();

if (linesCount > 0) {
try {
await chatHaikuLines.first().waitFor({ state: 'visible', timeout: 5000 });
break;
} catch (error) {
continue;
}

try {
// Wait for at least 3 haiku lines to be present in this card
await page.waitForFunction((cardIdx) => {
const cards = document.querySelectorAll('[data-testid="haiku-card"]');
if (cards[cardIdx]) {
const lines = cards[cardIdx].querySelectorAll('[data-testid="haiku-line"]');
return lines.length >= 3;
}
return false;
}, cardIndex, { timeout: 10000 });

// Verify the lines are visible
await chatHaikuLines.first().waitFor({ state: 'visible', timeout: 5000 });
break;
} catch (error) {
continue;
}
}

if (!chatHaikuLines) {
throw new Error('No haiku cards with visible lines found');
throw new Error('No haiku cards with 3 visible lines found');
}

const count = await chatHaikuLines.count();
const lines: string[] = [];

for (let i = 0; i < count; i++) {
const haikuLine = chatHaikuLines.nth(i);
const japaneseText = await haikuLine.locator('p').first().innerText();
lines.push(japaneseText);
if (count > 0) {
for (let i = 0; i < count; i++) {
const haikuLine = chatHaikuLines.nth(i);
const japaneseText = await haikuLine.locator('p').first().innerText();
lines.push(japaneseText);
}
}

const chatHaikuContent = lines.join('').replace(/\s/g, '');
return chatHaikuContent;
}

async extractMainDisplayHaikuContent(page: Page): Promise<string> {
// Wait for the main haiku display to be visible
await page.waitForSelector('[data-testid="main-haiku-display"]', { state: 'visible' });

const mainDisplayLines = page.locator('[data-testid="main-haiku-line"]');

// Wait for at least 3 haiku lines to be present
await page.waitForFunction(() => {
const elements = document.querySelectorAll('[data-testid="main-haiku-line"]');
return elements.length >= 3;
});

const mainCount = await mainDisplayLines.count();
const lines: string[] = [];

Expand All @@ -90,9 +112,13 @@ export class ToolBaseGenUIPage {
}

async checkHaikuDisplay(page: Page): Promise<void> {
// Wait for both chat and main display to be fully loaded
await page.waitForTimeout(3000);

const chatHaikuContent = await this.extractChatHaikuContent(page);

await page.waitForTimeout(5000);
// Wait a bit more for main display to sync
await page.waitForTimeout(2000);

const mainHaikuContent = await this.extractMainDisplayHaikuContent(page);

Expand All @@ -101,14 +127,43 @@ export class ToolBaseGenUIPage {
return;
}

// Check if contents match exactly
if (chatHaikuContent === mainHaikuContent) {
expect(mainHaikuContent).toBe(chatHaikuContent);
return;
}

// If they don't match, check if one is a substring of the other (partial loading)
if (mainHaikuContent.includes(chatHaikuContent) || chatHaikuContent.includes(mainHaikuContent)) {
console.log(`Content partially matches - Chat: "${chatHaikuContent}", Main: "${mainHaikuContent}"`);

// Wait for content to stabilize and try again
await page.waitForTimeout(5000);

const finalChatContent = await this.extractChatHaikuContent(page);
const finalMainContent = await this.extractMainDisplayHaikuContent(page);

// Use the longer content as the expected result (more complete)
const expectedContent = finalChatContent.length >= finalMainContent.length ? finalChatContent : finalMainContent;

expect(finalMainContent).toBe(expectedContent);
expect(finalChatContent).toBe(expectedContent);
} else {
await page.waitForTimeout(3000);
// Contents are completely different - this might indicate an error
console.log(`Content mismatch - Chat: "${chatHaikuContent}", Main: "${mainHaikuContent}"`);

// Wait longer and try one more time
await page.waitForTimeout(5000);

const retryMainContent = await this.extractMainDisplayHaikuContent(page);
const retryChatContent = await this.extractChatHaikuContent(page);

const updatedMainContent = await this.extractMainDisplayHaikuContent(page);
// At least verify both have content
expect(retryChatContent.length).toBeGreaterThan(0);
expect(retryMainContent.length).toBeGreaterThan(0);

expect(updatedMainContent).toBe(chatHaikuContent);
// Try to match again
expect(retryMainContent).toBe(retryChatContent);
}
}
}
2 changes: 1 addition & 1 deletion typescript-sdk/apps/dojo/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"description": "Scheduled Playwright smoke tests for CopilotKit demo apps",
"scripts": {
"postinstall": "playwright install --with-deps",
"postinstall": "playwright install --with-deps chromium",
"test": "playwright test",
"test:ui": "playwright test --ui",
"report": "playwright show-report"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
} from "../../test-isolation-helper";
import { AgenticChatPage } from "../../featurePages/AgenticChatPage";

// Skip all tests in this file when CLOUD_AGENTS is set
test.skip(!!process.env.CLOUD_AGENTS, 'Skipping ADK Middleware tests when CLOUD_AGENTS is set');

test.describe("Agentic Chat Feature", () => {
test("[ADK Middleware] Agentic Chat sends and receives a message", async ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { test, expect, waitForAIResponse, retryOnAIFailure } from "../../test-isolation-helper";
import { HumanInLoopPage } from "../../pages/adkMiddlewarePages/HumanInLoopPage";

// Skip all tests in this file when CLOUD_AGENTS is set
test.skip(!!process.env.CLOUD_AGENTS, 'Skipping ADK Middleware tests when CLOUD_AGENTS is set');

test.describe("Human in the Loop Feature", () => {
test("[ADK Middleware] should interact with the chat and perform steps", async ({
page,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {
} from "../../test-isolation-helper";
import { PredictiveStateUpdatesPage } from "../../pages/adkMiddlewarePages/PredictiveStateUpdatesPage";

// Skip all tests in this file when CLOUD_AGENTS is set
test.skip(!!process.env.CLOUD_AGENTS, 'Skipping ADK Middleware tests when CLOUD_AGENTS is set');

test.describe("Predictive State Updates Feature", () => {
test("[ADK Middleware] should interact with agent and approve asked changes", async ({
page,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { test, expect } from "@playwright/test";
import { SharedStatePage } from "../../featurePages/SharedStatePage";

// Skip all tests in this file when CLOUD_AGENTS is set
test.skip(!!process.env.CLOUD_AGENTS, 'Skipping ADK Middleware tests when CLOUD_AGENTS is set');

test.describe("Shared State Feature", () => {
test("[ADK Middleware] should interact with the chat to get a recipe on prompt", async ({
page,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { test, expect } from "@playwright/test";
import { ToolBaseGenUIPage } from "../../featurePages/ToolBaseGenUIPage";

// Skip all tests in this file when CLOUD_AGENTS is set
test.skip(!!process.env.CLOUD_AGENTS, 'Skipping ADK Middleware tests when CLOUD_AGENTS is set');

const pageURL = "/adk-middleware/feature/tool_based_generative_ui";

test.describe("Tool Based Generative UI Feature", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
} from "../../test-isolation-helper";
import { AgenticChatPage } from "../../featurePages/AgenticChatPage";

// Skip all tests in this file when CLOUD_AGENTS is set
test.skip(!!process.env.CLOUD_AGENTS, 'Skipping MastraAgentLocal tests when CLOUD_AGENTS is set');

test("[MastraAgentLocal] Agentic Chat sends and receives a message", async ({
page,
}) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { test, expect } from "@playwright/test";
import { SharedStatePage } from "../../featurePages/SharedStatePage";

// Skip all tests in this file when CLOUD_AGENTS is set
test.skip(!!process.env.CLOUD_AGENTS, 'Skipping MastraAgentLocal tests when CLOUD_AGENTS is set');

test.describe("Shared State Feature", () => {
test("[MastraAgentLocal] should interact with the chat to get a recipe on prompt", async ({
page,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { test, expect } from "@playwright/test";
import { ToolBaseGenUIPage } from "../../featurePages/ToolBaseGenUIPage";

// Skip all tests in this file when CLOUD_AGENTS is set
test.skip(!!process.env.CLOUD_AGENTS, 'Skipping MastraAgentLocal tests when CLOUD_AGENTS is set');

const pageURL =
"/mastra-agent-local/feature/tool_based_generative_ui";

Expand Down
Loading
Loading