Skip to main content
Glama
dashboard-actions.ts11 kB
import { Page, expect } from "@playwright/test"; import { DELAY_2_SEC, TIMEOUT_5_SEC } from "../../../constants/delays"; const SERVER_NAME_MAP: Record<string, string> = { Memory: "memory", Time: "time", Slack: "slack", Playwright: "playwright", "Sequential Thinking": "sequential-thinking", Notion: "Notion", Asana: "asana", Atlassian: "atlassian", LaunchDarkly: "LaunchDarkly", PostgreSQL: "postgres", Snowflake: "snowflake", Redis: "redis", }; function getServerNameFromLabel(label: string): string { return SERVER_NAME_MAP[label] || label.toLowerCase(); } export class DashboardActions { constructor(private page: Page) {} async clickAddServerButton(): Promise<void> { const addServerButton = this.page .locator("button") .filter({ hasText: /Add Server/i }) .first(); await expect(addServerButton).toBeVisible({ timeout: TIMEOUT_5_SEC }); await expect(addServerButton).toBeEnabled(); await addServerButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); } async waitForAddServerModal(): Promise<void> { const modalTitle = this.page .locator("div") .filter({ hasText: /^Add Server$/i }); await expect(modalTitle).toBeVisible({ timeout: TIMEOUT_5_SEC }); await this.page.waitForTimeout(DELAY_2_SEC); } async selectServerFromCatalog(serverName: string): Promise<void> { await this.waitForAddServerModal(); const labelSpan = this.page .locator("span") .filter({ hasText: new RegExp(`^${serverName}$`, "i") }) .first(); await expect(labelSpan).toBeVisible({ timeout: TIMEOUT_5_SEC }); const serverCard = labelSpan .locator( 'xpath=ancestor::div[contains(@class, "border") and contains(@class, "rounded-xl")]', ) .first(); await expect(serverCard).toBeVisible({ timeout: TIMEOUT_5_SEC }); const plusButton = serverCard .locator("button") .locator("svg") .locator("..") .first(); await expect(plusButton).toBeVisible({ timeout: TIMEOUT_5_SEC }); await plusButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); const customTab = this.page .locator("button") .filter({ hasText: /^Custom$/i }); const isCustomTabActive = await customTab.getAttribute("data-state"); if (isCustomTabActive === "active") { const addButton = this.page .locator("button") .filter({ hasText: /^Add$/i }) .first(); await expect(addButton).toBeVisible({ timeout: TIMEOUT_5_SEC }); await expect(addButton).toBeEnabled({ timeout: TIMEOUT_5_SEC }); await addButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); } } async verifyServerOnCanvas(serverLabel: string): Promise<void> { const serverName = getServerNameFromLabel(serverLabel); const serverNode = this.page.locator(`[data-id^="server-${serverName}"]`); await this.page.waitForTimeout(DELAY_2_SEC); await expect(serverNode).toBeVisible({ timeout: TIMEOUT_5_SEC * 2 }); } async verifyServerCount(expectedCount: number): Promise<void> { const serverNodes = this.page.locator('[data-id^="server-"]'); const count = await serverNodes.count(); expect(count).toBeGreaterThanOrEqual(expectedCount); } async closeAddServerModal(): Promise<void> { const closeButton = this.page .locator("button") .filter({ hasText: /Close/i }) .or(this.page.locator('[aria-label="Close"]')) .first(); const isVisible = await closeButton.isVisible().catch(() => false); if (isVisible) { await closeButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); } } async createServerFromCatalog(serverLabel: string): Promise<void> { await this.clickAddServerButton(); await this.selectServerFromCatalog(serverLabel); await this.verifyServerOnCanvas(serverLabel); } async clickServerNode(serverName: string): Promise<void> { const serverNode = this.page.locator(`[data-id^="server-${serverName}"]`); await expect(serverNode).toBeVisible({ timeout: TIMEOUT_5_SEC }); await serverNode.click(); await this.page.waitForTimeout(DELAY_2_SEC); } async waitForServerDetailsDrawer(): Promise<void> { const drawer = this.page .locator('[role="dialog"]') .or(this.page.locator('div[class*="sheet"]')); await expect(drawer).toBeVisible({ timeout: TIMEOUT_5_SEC }); await this.page.waitForTimeout(DELAY_2_SEC); } async clickDeleteServerButton(): Promise<void> { await this.waitForServerDetailsDrawer(); const drawer = this.page .locator('[role="dialog"]') .or(this.page.locator('div[class*="sheet"]')) .first(); await expect(drawer).toBeVisible({ timeout: TIMEOUT_5_SEC }); const allButtons = drawer.locator("button"); const buttonCount = await allButtons.count(); for (let i = 0; i < buttonCount; i++) { const button = allButtons.nth(i); const svg = button.locator("svg").first(); const isVisible = await svg.isVisible().catch(() => false); if (isVisible) { const svgContent = await svg.innerHTML().catch(() => ""); if ( svgContent.includes("M16.875 3.75") || svgContent.includes("M3.75V16.25") || (svgContent.includes("V3.125") && svgContent.includes("V16.25")) ) { await button.click(); await this.page.waitForTimeout(DELAY_2_SEC); return; } } } throw new Error("Delete button not found"); } async confirmDeleteServer(): Promise<void> { const confirmButton = this.page .locator("button") .filter({ hasText: /^Ok$/i }) .first(); await expect(confirmButton).toBeVisible({ timeout: TIMEOUT_5_SEC }); await confirmButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); } async verifyServerDeleted(serverName: string): Promise<void> { const serverNode = this.page.locator(`[data-id^="server-${serverName}"]`); await expect(serverNode).not.toBeVisible({ timeout: TIMEOUT_5_SEC * 2 }); } async deleteServer(serverName: string): Promise<void> { await this.clickServerNode(serverName); await this.clickDeleteServerButton(); await this.confirmDeleteServer(); await this.verifyServerDeleted(serverName); } async clickAddAgentButton(): Promise<void> { const addAgentButton = this.page .locator("button") .filter({ hasText: /Add Agent/i }) .first(); await expect(addAgentButton).toBeVisible({ timeout: TIMEOUT_5_SEC }); await expect(addAgentButton).toBeEnabled(); await addAgentButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); } async waitForAddAgentModal(): Promise<void> { const dialog = this.page .locator('[role="dialog"]') .filter({ hasText: /Add AI Agent/i }) .first(); await expect(dialog).toBeVisible({ timeout: TIMEOUT_5_SEC }); const modalTitle = dialog .locator("h2") .filter({ hasText: /Add AI Agent/i }); await expect(modalTitle).toBeVisible({ timeout: TIMEOUT_5_SEC }); await this.page.waitForTimeout(DELAY_2_SEC); } async verifyAddAgentModalContent(): Promise<void> { await this.waitForAddAgentModal(); const dialog = this.page.locator('[role="dialog"]').first(); const description = dialog .locator("p") .filter({ hasText: /Select your agent type and copy the configuration JSON/i, }) .first(); await expect(description).toBeVisible({ timeout: TIMEOUT_5_SEC }); const selectLabel = dialog .locator("label") .filter({ hasText: /Select Agent Type/i }); await expect(selectLabel).toBeVisible({ timeout: TIMEOUT_5_SEC }); const selectTrigger = dialog.locator("button").filter({ hasText: /Choose an agent type/i, }); await expect(selectTrigger).toBeVisible({ timeout: TIMEOUT_5_SEC }); } async clickMcpxNode(): Promise<void> { const mcpxNode = this.page.locator('[data-testid="rf__node-mcpx"]'); await expect(mcpxNode).toBeVisible({ timeout: TIMEOUT_5_SEC }); await mcpxNode.click(); await this.page.waitForTimeout(DELAY_2_SEC); } async waitForMcpxDrawer(): Promise<void> { const drawer = this.page .locator('[role="dialog"]') .or(this.page.locator('div[class*="sheet"]')) .filter({ hasText: /MCPX/i }); await expect(drawer).toBeVisible({ timeout: TIMEOUT_5_SEC }); await this.page.waitForTimeout(DELAY_2_SEC); } async closeMcpxDrawer(): Promise<void> { await this.waitForMcpxDrawer(); const drawer = this.page .locator('[role="dialog"]') .or(this.page.locator('div[class*="sheet"]')) .filter({ hasText: /MCPX/i }) .first(); // Try to find close button in header const headerCloseButton = drawer .locator('div[class*="SheetHeader"]') .locator("button") .first(); const isVisible = await headerCloseButton.isVisible().catch(() => false); if (isVisible) { await headerCloseButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); return; } // Fallback: try any button with SVG const closeButton = drawer .locator("button") .filter({ has: drawer.locator("svg") }) .first(); await expect(closeButton).toBeVisible({ timeout: TIMEOUT_5_SEC }); await closeButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); } async toggleServerInDrawer(serverName: string): Promise<void> { await this.waitForMcpxDrawer(); const drawer = this.page .locator('[role="dialog"]') .or(this.page.locator('div[class*="sheet"]')) .filter({ hasText: /MCPX/i }) .first(); await this.page.waitForTimeout(DELAY_2_SEC); // Find server name first, then get the card const serverNameElement = drawer .locator("h3") .filter({ hasText: new RegExp(serverName, "i") }) .first(); await expect(serverNameElement).toBeVisible({ timeout: TIMEOUT_5_SEC }); const serverCard = serverNameElement .locator( 'xpath=ancestor::div[contains(@class, "border") and contains(@class, "bg-white")]', ) .first(); await expect(serverCard).toBeVisible({ timeout: TIMEOUT_5_SEC }); const toggleSwitch = serverCard.locator('button[role="switch"]').first(); await expect(toggleSwitch).toBeVisible({ timeout: TIMEOUT_5_SEC }); await toggleSwitch.click(); await this.page.waitForTimeout(DELAY_2_SEC); } async saveMcpxDrawerChanges(): Promise<void> { await this.waitForMcpxDrawer(); const drawer = this.page .locator('[role="dialog"]') .or(this.page.locator('div[class*="sheet"]')) .filter({ hasText: /MCPX/i }) .first(); const saveButton = drawer.locator("button").filter({ hasText: /^Save$/i }); await expect(saveButton).toBeEnabled({ timeout: TIMEOUT_5_SEC }); await saveButton.click(); await this.page.waitForTimeout(DELAY_2_SEC); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TheLunarCompany/lunar'

If you have feedback or need assistance with the MCP directory API, please join our Discord server